diff --git a/src/extensions/renderer/canvas/webgl/drawing-edges-webgl.js b/src/extensions/renderer/canvas/webgl/drawing-edges-webgl.js index 1e2989ddd..7fb4146a9 100644 --- a/src/extensions/renderer/canvas/webgl/drawing-edges-webgl.js +++ b/src/extensions/renderer/canvas/webgl/drawing-edges-webgl.js @@ -83,55 +83,35 @@ export class EdgeDrawing { const vao = gl.createVertexArray(); gl.bindVertexArray(vao); - { // vertices - const buffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); - gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(instanceGeometry), gl.STATIC_DRAW); - gl.vertexAttribPointer(program.aPosition, 2, gl.FLOAT, false, 0, 0); - gl.enableVertexAttribArray(program.aPosition); - gl.bindBuffer(gl.ARRAY_BUFFER, null); - } - - const stride = 2 * 4; // 2 vertices * 4 bytes - { // source points - this.sourceData = new Float32Array(this.maxInstances * 2); - this.sourceBuffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, this.sourceBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.maxInstances * stride, gl.DYNAMIC_DRAW); - gl.enableVertexAttribArray(program.aSource); - gl.vertexAttribPointer(program.aSource, 2, gl.FLOAT, false, this.sourceBuffer.stride, 0); - gl.vertexAttribDivisor(program.aSource, 1); - } - { // target points - this.targetData = new Float32Array(this.maxInstances * 2); - this.targetBuffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, this.targetBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.maxInstances * stride, gl.DYNAMIC_DRAW); - gl.enableVertexAttribArray(program.aTarget); - gl.vertexAttribPointer(program.aTarget, 2, gl.FLOAT, false, stride, 0); - gl.vertexAttribDivisor(program.aTarget, 1); - } - - { // widths - this.widthData = new Float32Array(this.maxInstances); - this.widthBuffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, this.widthBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.maxInstances * 4, gl.DYNAMIC_DRAW); - gl.enableVertexAttribArray(program.aWidth); - gl.vertexAttribPointer(program.aWidth, 1, gl.FLOAT, false, 4, 0); - gl.vertexAttribDivisor(program.aWidth, 1); - } - - // TODO allow different color for source and target - { // colors - this.colorData = new Float32Array(this.maxInstances * 4); - this.colorBuffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.maxInstances * 4 * 4, gl.DYNAMIC_DRAW); - gl.enableVertexAttribArray(program.aColor); - gl.vertexAttribPointer(program.aColor, 4, gl.FLOAT, false, 4 * 4, 0); - gl.vertexAttribDivisor(program.aColor, 1); - } + util.createFloatBufferStaticDraw(gl, { + attributeLoc: program.aPosition, + dataArray: instanceGeometry, + size: 2 + }); + + this.sourceBuffer = util.createFloatBufferDynamicDraw(gl, { + attributeLoc: program.aSource, + maxInstances: this.maxInstances, + size: 2 + }); + + this.targetBuffer = util.createFloatBufferDynamicDraw(gl, { + attributeLoc: program.aTarget, + maxInstances: this.maxInstances, + size: 2 + }); + + this.widthBuffer = util.createFloatBufferDynamicDraw(gl, { + attributeLoc: program.aWidth, + maxInstances: this.maxInstances, + size: 1 + }); + + this.colorBuffer = util.createFloatBufferDynamicDraw(gl, { + attributeLoc: program.aColor, + maxInstances: this.maxInstances, + size: 4 + }); gl.bindVertexArray(null); return vao; @@ -142,8 +122,6 @@ export class EdgeDrawing { this.panZoomMatrix = panZoomMatrix; } this.instanceCount = 0; - this.sourcePoints = []; - this.targetPoints = []; } @@ -159,10 +137,11 @@ export class EdgeDrawing { const width = edge.pstyle('width').pfValue; - this.sourceData.set([sp.x, sp.y], this.instanceCount * 2); - this.targetData.set([tp.x, tp.y], this.instanceCount * 2); - this.widthData.set([width], this.instanceCount); - this.colorData.set([r, g, b, a], this.instanceCount * 4); + const i = this.instanceCount; + this.sourceBuffer.setDataAt([sp.x, sp.y], i); + this.targetBuffer.setDataAt([tp.x, tp.y], i); + this.widthBuffer.setDataAt([width], i); + this.colorBuffer.setDataAt([r, g, b, a], i); this.instanceCount++; @@ -173,37 +152,27 @@ export class EdgeDrawing { endBatch() { - if(this.instanceCount === 0) + const count = this.instanceCount; + if(count === 0) return; - console.log('drawing edges', this.instanceCount); + console.log('drawing edges', count); const { gl, program, vao } = this; gl.useProgram(program); gl.bindVertexArray(vao); - // TODO bufferSubData calls should only buffer this.instanceCount amount of data - // source points - gl.bindBuffer(gl.ARRAY_BUFFER, this.sourceBuffer); - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.sourceData); //, 0, this.instanceCount * 2); - - // target points - gl.bindBuffer(gl.ARRAY_BUFFER, this.targetBuffer); - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.targetData); - - // widths - gl.bindBuffer(gl.ARRAY_BUFFER, this.widthBuffer); - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.widthData); - - // colors - gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.colorData); + // buffer the attribute data + this.sourceBuffer.bufferSubData(count); + this.targetBuffer.bufferSubData(count); + this.widthBuffer.bufferSubData(count); + this.colorBuffer.bufferSubData(count); // Set the projection matrix uniform gl.uniformMatrix3fv(program.uPanZoomMatrix, false, this.panZoomMatrix); // draw! - gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this.instanceCount); // 6 verticies per edge + gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, count); // 6 verticies per edge gl.bindVertexArray(null); diff --git a/src/extensions/renderer/canvas/webgl/drawing-nodes-webgl.js b/src/extensions/renderer/canvas/webgl/drawing-nodes-webgl.js index fde7ee5a8..46a24f2e2 100644 --- a/src/extensions/renderer/canvas/webgl/drawing-nodes-webgl.js +++ b/src/extensions/renderer/canvas/webgl/drawing-nodes-webgl.js @@ -111,46 +111,22 @@ export class NodeDrawing { const vao = gl.createVertexArray(); gl.bindVertexArray(vao); - { // node quad - const buffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); - gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(unitQuad), gl.STATIC_DRAW); - gl.vertexAttribPointer(program.aPosition, 2, gl.FLOAT, false, 0, 0); - gl.enableVertexAttribArray(program.aPosition); - gl.bindBuffer(gl.ARRAY_BUFFER, null); - } - { // texture coords - const buffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); - gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texQuad), gl.STATIC_DRAW); - gl.vertexAttribPointer(program.aTexCoord, 2, gl.FLOAT, false, 0, 0); - gl.enableVertexAttribArray(program.aTexCoord); - gl.bindBuffer(gl.ARRAY_BUFFER, null); - } - { // matrix data - const matrixSize = 9; // 3x3 matrix - this.matrixData = new Float32Array(this.maxInstances * matrixSize); - - // use matrix views to set values directly into the matrixData array - this.matrixViews = new Array(this.maxInstances); - for(let i = 0; i < this.maxInstances; i++) { - const byteOffset = i * matrixSize * 4; // 4 bytes per float - this.matrixViews[i] = new Float32Array(this.matrixData.buffer, byteOffset, matrixSize); // array view - } - - this.matrixBuffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, this.matrixBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.matrixData.byteLength, gl.DYNAMIC_DRAW); - - // each row of the matrix needs to be a separate attribute - for(let i = 0; i < 3; i++) { - const loc = program.aNodeMatrix + i; - gl.enableVertexAttribArray(loc); - gl.vertexAttribPointer(loc, 3, gl.FLOAT, false, 3 * 12, i * 12); - gl.vertexAttribDivisor(loc, 1); - } - gl.bindBuffer(gl.ARRAY_BUFFER, null); - } + util.createFloatBufferStaticDraw(gl, { + attributeLoc: program.aPosition, + dataArray: unitQuad, + size: 2 + }); + + util.createFloatBufferStaticDraw(gl, { + attributeLoc: program.aTexCoord, + dataArray: texQuad, + size: 2 + }); + + this.matrixBuffer = util.create3x3MatrixBufferDynamicDraw(gl, { + attributeLoc: program.aNodeMatrix, + maxInstances: this.maxInstances + }); gl.bindVertexArray(null); return vao; @@ -178,22 +154,11 @@ export class NodeDrawing { return textureCanvas; } - function bufferTexture(textureCanvas) { - const texture = gl.createTexture(); - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize, texSize, 0, gl.RGBA, gl.UNSIGNED_BYTE, textureCanvas); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST); - gl.generateMipmap(gl.TEXTURE_2D); // important! - gl.bindTexture(gl.TEXTURE_2D, null); - return texture; - } - const styleKey = opts.getKey(node); let texture = this.styleKeyToTexture.get(styleKey); if(!texture) { const canvas = drawTextureCanvas(); - texture = bufferTexture(canvas); + texture = util.bufferTexture(gl, texSize, canvas); this.styleKeyToTexture.set(styleKey, texture); texture.styleKey = styleKey; // for debug } @@ -239,12 +204,14 @@ export class NodeDrawing { draw(node, type) { const opts = this.renderTypes.get(type); - // pass the array view to setTransformMatrix, no need to create a new instance every draw call - const matrixView = this.matrixViews[this.instanceCount]; + // pass the array view to setTransformMatrix + const matrixView = this.matrixBuffer.getMatrixView(this.instanceCount); this.setTransformMatrix(node, opts, matrixView); + // create the texture if needed const texture = this.createTexture(node, opts); this.textures.push(texture); + this.instanceCount++; if(this.instanceCount >= this.maxInstances) { @@ -253,22 +220,21 @@ export class NodeDrawing { } endBatch() { - if(this.instanceCount === 0) + const count = this.instanceCount; + if(count === 0) return; - console.log('drawing nodes', this.instanceCount); + console.log('drawing nodes', count); const { gl, program, vao } = this; gl.useProgram(program); gl.bindVertexArray(vao); // upload the new matrix data - gl.bindBuffer(gl.ARRAY_BUFFER, this.matrixBuffer); - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.matrixData); - gl.bindBuffer(gl.ARRAY_BUFFER, null); + this.matrixBuffer.bufferSubData(); // Activate all the texture units that we need - for(let i = 0; i < this.instanceCount; i++) { + for(let i = 0; i < count; i++) { gl.activeTexture(gl.TEXTURE0 + i); gl.bindTexture(gl.TEXTURE_2D, this.textures[i]); gl.uniform1i(program.uTextures[i], i); @@ -278,7 +244,7 @@ export class NodeDrawing { gl.uniformMatrix3fv(program.uPanZoomMatrix, false, this.panZoomMatrix); // draw! - gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this.instanceCount); // 6 verticies per node + gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, count); // 6 verticies per node gl.bindVertexArray(null); gl.bindTexture(gl.TEXTURE_2D, null); // TODO is this right when having multiple texture units? diff --git a/src/extensions/renderer/canvas/webgl/webgl-util.js b/src/extensions/renderer/canvas/webgl/webgl-util.js index f1f38db45..161805641 100644 --- a/src/extensions/renderer/canvas/webgl/webgl-util.js +++ b/src/extensions/renderer/canvas/webgl/webgl-util.js @@ -10,6 +10,7 @@ export function compileShader(gl, type, source) { return shader; } + export function createProgram(gl, vertexSource, fragementSource) { const vertexShader = compileShader(gl, gl.VERTEX_SHADER, vertexSource); const fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, fragementSource); @@ -23,6 +24,7 @@ export function createProgram(gl, vertexSource, fragementSource) { return program; } + export function createTextureCanvas(r, width, height) { if(height === undefined) { height = width; @@ -35,4 +37,107 @@ export function createTextureCanvas(r, width, height) { // ctx.strokeRect(0, 0, canvas.width, canvas.height); // ctx.restore(); return canvas; -} \ No newline at end of file +} + + +export function bufferTexture(gl, texSize, textureCanvas) { + const texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize, texSize, 0, gl.RGBA, gl.UNSIGNED_BYTE, textureCanvas); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST); + gl.generateMipmap(gl.TEXTURE_2D); // important! + gl.bindTexture(gl.TEXTURE_2D, null); + return texture; +} + + +/** @param {WebGLRenderingContext} gl */ +export function createFloatBufferStaticDraw(gl, { attributeLoc, dataArray, size }) { + const buffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(dataArray), gl.STATIC_DRAW); + gl.vertexAttribPointer(attributeLoc, size, gl.FLOAT, false, 0, 0); + gl.enableVertexAttribArray(attributeLoc); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + return buffer; +} + + +/** + * Creates a float buffer with gl.DYNAMIC_DRAW. + * The returned buffer object contains functions to easily set instance data + * and buffer the data before a draw call. + * @param {WebGLRenderingContext} gl + */ +export function createFloatBufferDynamicDraw(gl, { attributeLoc, maxInstances, size }) { + const dataArray = new Float32Array(maxInstances * size); + const buffer = gl.createBuffer(); + const stride = size * 4; // num elements * 4 bytes (float) + + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + gl.bufferData(gl.ARRAY_BUFFER, maxInstances * stride, gl.DYNAMIC_DRAW); + gl.enableVertexAttribArray(attributeLoc); + gl.vertexAttribPointer(attributeLoc, size, gl.FLOAT, false, stride, 0); + gl.vertexAttribDivisor(attributeLoc, 1); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + + buffer.dataArray = dataArray; + buffer.stride = stride; + buffer.size = size; + + buffer.setDataAt = (data, i) => { + dataArray.set(data, i * size); + }; + + buffer.bufferSubData = (count) => { + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + if(count) { + gl.bufferSubData(gl.ARRAY_BUFFER, 0, dataArray, 0, count * size); + } else { + gl.bufferSubData(gl.ARRAY_BUFFER, 0, dataArray); + } + } + + return buffer; +} + +/** + * Creates a buffer of 3x3 matrix data for use as attribute data. + * @param {WebGLRenderingContext} gl + */ +export function create3x3MatrixBufferDynamicDraw(gl, { maxInstances, attributeLoc } ) { + const matrixSize = 9; // 3x3 matrix + const matrixData = new Float32Array(maxInstances * matrixSize); + + // use matrix views to set values directly into the matrixData array + const matrixViews = new Array(maxInstances); + for(let i = 0; i < maxInstances; i++) { + const byteOffset = i * matrixSize * 4; // 4 bytes per float + matrixViews[i] = new Float32Array(matrixData.buffer, byteOffset, matrixSize); // array view + } + + const matrixBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, matrixBuffer); + gl.bufferData(gl.ARRAY_BUFFER, matrixData.byteLength, gl.DYNAMIC_DRAW); + + // each row of the matrix needs to be a separate attribute + for(let i = 0; i < 3; i++) { + const loc = attributeLoc + i; + gl.enableVertexAttribArray(loc); + gl.vertexAttribPointer(loc, 3, gl.FLOAT, false, 3 * 12, i * 12); + gl.vertexAttribDivisor(loc, 1); + } + gl.bindBuffer(gl.ARRAY_BUFFER, null); + + matrixBuffer.getMatrixView = (i) => { + return matrixViews[i]; + }; + + matrixBuffer.bufferSubData = () => { + gl.bindBuffer(gl.ARRAY_BUFFER, matrixBuffer); + gl.bufferSubData(gl.ARRAY_BUFFER, 0, matrixData); + }; + + return matrixBuffer; +}cs \ No newline at end of file