diff --git a/canvas-context.ts b/canvas-context.ts index 04219a9..f9d36bd 100644 --- a/canvas-context.ts +++ b/canvas-context.ts @@ -21,6 +21,7 @@ export class CanvasContext { constructor(canvas: HTMLCanvasElement) { this.gl = regl({ canvas: canvas, + extensions: ['ANGLE_instanced_arrays'], optionalExtensions: ['EXT_disjoint_timer_query'], profile: true }) diff --git a/rectangle-batch-renderer.ts b/rectangle-batch-renderer.ts index d3be102..9c46a24 100644 --- a/rectangle-batch-renderer.ts +++ b/rectangle-batch-renderer.ts @@ -4,93 +4,67 @@ import { Rect, Vec2, AffineTransform } from './math' import { Color } from './color' export class RectangleBatch { - private vertexCapacity = 60 - private vertexCount = 0 - private positions = new Float32Array(this.vertexCapacity * 2) - private physicalSpaceOffsets = new Float32Array(this.vertexCapacity * 2) - private vertexColors = new Float32Array(this.vertexCapacity * 3) + private rectCapacity = 10 + private rectCount = 0 + + private configSpaceOffsets = new Float32Array(this.rectCapacity * 2) + private configSpaceSizes = new Float32Array(this.rectCapacity * 2) + private colors = new Float32Array(this.rectCapacity * 3) - private offset = { - topLeft: new Vec2(1, -1), - topRight: new Vec2(-1, -1), - bottomRight: new Vec2(-1, 1), - bottomLeft: new Vec2(1, 1) - } constructor(private gl: regl.Instance) { } - getVertexCount() { return this.vertexCount } + getRectCount() { return this.rectCount } - private positionBuffer: regl.Buffer | null = null - getPositionBuffer() { - if (!this.positionBuffer) this.positionBuffer = this.gl.buffer(this.positions) - return this.positionBuffer + private configSpaceOffsetBuffer: regl.Buffer | null = null + getConfigSpaceOffsetBuffer() { + if (!this.configSpaceOffsetBuffer) this.configSpaceOffsetBuffer = this.gl.buffer(this.configSpaceOffsets) + return this.configSpaceOffsetBuffer } - private offsetBuffer: any = null - getPhysicalSpaceOffsetBuffer() { - if (!this.offsetBuffer) this.offsetBuffer = this.gl.buffer(this.physicalSpaceOffsets) - return this.offsetBuffer + private configSpaceSizeBuffer: regl.Buffer | null = null + getConfigSpaceSizeBuffer() { + if (!this.configSpaceSizeBuffer) this.configSpaceSizeBuffer = this.gl.buffer(this.configSpaceSizes) + return this.configSpaceSizeBuffer } - private colorBuffer: any = null - getVertexColorBuffer() { - if (!this.colorBuffer) this.colorBuffer = this.gl.buffer(this.vertexColors) + private colorBuffer: regl.Buffer | null = null + getColorBuffer() { + if (!this.colorBuffer) this.colorBuffer = this.gl.buffer(this.colors) return this.colorBuffer } uploadToGPU() { - this.getPositionBuffer() - this.getPhysicalSpaceOffsetBuffer() - this.getVertexColorBuffer() - } - - private addVertex(y: number, x: number, offset: Vec2, color: vec3) { - const index = this.vertexCount++ - if (index >= this.vertexCapacity) { - // Not enough capacity, time to resize! We'll double the capacity each time. - this.vertexCapacity *= 2 - const positions = new Float32Array(this.vertexCapacity * 2) - const physicalSpaceOffsets = new Float32Array(this.vertexCapacity * 2) - const vertexColors = new Float32Array(this.vertexCapacity * 3) - - positions.set(this.positions) - physicalSpaceOffsets.set(this.physicalSpaceOffsets) - vertexColors.set(this.vertexColors) - - this.positions = positions - this.physicalSpaceOffsets = physicalSpaceOffsets - this.vertexColors = vertexColors - } - this.positions[index * 2] = x - this.positions[index * 2 + 1] = y - this.physicalSpaceOffsets[index * 2] = offset.x - this.physicalSpaceOffsets[index * 2 + 1] = offset.y - this.vertexColors[index * 3] = color[0] - this.vertexColors[index * 3 + 1] = color[1] - this.vertexColors[index * 3 + 2] = color[2] + this.getConfigSpaceOffsetBuffer() + this.getConfigSpaceSizeBuffer() + this.getColorBuffer() } addRect(rect: Rect, color: Color) { - const color_: vec3 = [color.r, color.g, color.b] + const index = this.rectCount++ + if (index >= this.rectCapacity) { + // Not enough capacity, time to resize! We'll double the capacity each time. + this.rectCapacity *= 2 + const configSpaceOffsets = new Float32Array(this.rectCapacity * 2) + const configSpaceSizes = new Float32Array(this.rectCapacity * 2) + const colors = new Float32Array(this.rectCapacity * 3) - const top = rect.top() - const bottom = rect.bottom() - const left = rect.left() - const right = rect.right() + configSpaceOffsets.set(this.configSpaceOffsets) + configSpaceSizes.set(this.configSpaceSizes) + colors.set(this.colors) - // 2 disjoint triangles. - // - // 0 +--+ 1 - // | /| - // |/ | - // 3 +--+ 2 - this.addVertex(top, left, this.offset.topLeft, color_) - this.addVertex(bottom, left, this.offset.bottomLeft, color_) - this.addVertex(top, right, this.offset.topRight, color_) + this.configSpaceOffsets = configSpaceOffsets + this.configSpaceSizes = configSpaceSizes + this.colors = colors + } + this.configSpaceOffsets[index * 2] = rect.origin.x + this.configSpaceOffsets[index * 2 + 1] = rect.origin.y - this.addVertex(bottom, left, this.offset.bottomLeft, color_) - this.addVertex(top, right, this.offset.topRight, color_) - this.addVertex(bottom, right, this.offset.bottomRight, color_) + this.configSpaceSizes[index * 2] = rect.size.x + this.configSpaceSizes[index * 2 + 1] = rect.size.y + + this.colors[index * 3] = color.r + this.colors[index * 3 + 1] = color.g + this.colors[index * 3 + 2] = color.b } } @@ -109,13 +83,37 @@ export class RectangleBatchRenderer { uniform mat3 configSpaceToNDC; uniform vec2 physicalSize; uniform float strokeSize; - attribute vec2 position; + + // Non-instanced + attribute float corner; + + // Instanced + attribute vec2 configSpaceOffset; + attribute vec2 configSpaceSize; attribute vec3 color; - attribute vec2 physicalSpaceOffset; varying vec3 vColor; + void main() { vColor = color; - vec2 roundedPosition = (configSpaceToNDC * vec3(position.xy, 1)).xy; + + vec2 configSpacePos = configSpaceOffset; + vec2 physicalSpaceOffset = vec2(0, 0); + + // Corners go NW, NE, SW, SE + if (corner == 0.0) { + physicalSpaceOffset = vec2(1, -1); + } else if (corner == 1.0) { + physicalSpaceOffset = vec2(-1, -1); + configSpacePos.x += configSpaceSize.x; + } else if (corner == 2.0) { + physicalSpaceOffset = vec2(1, 1); + configSpacePos.y += configSpaceSize.y; + } else if (corner == 3.0) { + physicalSpaceOffset = vec2(-1, 1); + configSpacePos += configSpaceSize; + } + + vec2 roundedPosition = (configSpaceToNDC * vec3(configSpacePos, 1)).xy; vec2 halfSize = physicalSize / 2.0; vec2 physicalPixelSize = 2.0 / physicalSize; roundedPosition = floor(roundedPosition * halfSize) / halfSize; @@ -136,28 +134,35 @@ export class RectangleBatchRenderer { `, attributes: { - position: (context, props) => { + // Non-instanced attributes + corner: gl.buffer(new Float32Array([0, 1, 2, 3])), + + // Instanced attributes + configSpaceOffset: (context, props) => { return { - buffer: props.batch.getPositionBuffer(), + buffer: props.batch.getConfigSpaceOffsetBuffer(), offset: 0, stride: 2 * 4, - size: 2 + size: 2, + divisor: 1 } }, - physicalSpaceOffset: (context, props) => { + configSpaceSize: (context, props) => { return { - buffer: props.batch.getPhysicalSpaceOffsetBuffer(), + buffer: props.batch.getConfigSpaceSizeBuffer(), offset: 0, stride: 2 * 4, - size: 2 + size: 2, + divisor: 1 } }, color: (context, props) => { return { - buffer: props.batch.getVertexColorBuffer(), + buffer: props.batch.getColorBuffer(), offset: 0, stride: 3 * 4, - size: 3 + size: 3, + divisor: 1 } } }, @@ -174,11 +179,13 @@ export class RectangleBatchRenderer { } }, - primitive: 'triangles', + instances: (context, props) => { + return props.batch.getRectCount() + }, - count: (context, props) => { - return props.batch.getVertexCount() - } + count: 4, + + primitive: 'triangle strip', }) }