Skip to content

Commit

Permalink
[wip] Remove instanced debug rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
mpulkki-mapbox committed Mar 24, 2020
1 parent f4f59e7 commit 439b613
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 144 deletions.
43 changes: 42 additions & 1 deletion src/data/array_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,46 @@ class StructArrayLayout2i2i2i12 extends StructArray {
StructArrayLayout2i2i2i12.prototype.bytesPerElement = 12;
register('StructArrayLayout2i2i2i12', StructArrayLayout2i2i2i12);

/**
* Implementation of the StructArray layout:
* [0]: Float32[2]
* [8]: Float32[1]
* [12]: Int16[2]
*
* @private
*/
class StructArrayLayout2f1f2i16 extends StructArray {
uint8: Uint8Array;
float32: Float32Array;
int16: Int16Array;

_refreshViews() {
this.uint8 = new Uint8Array(this.arrayBuffer);
this.float32 = new Float32Array(this.arrayBuffer);
this.int16 = new Int16Array(this.arrayBuffer);
}

emplaceBack(v0: number, v1: number, v2: number, v3: number, v4: number) {
const i = this.length;
this.resize(i + 1);
return this.emplace(i, v0, v1, v2, v3, v4);
}

emplace(i: number, v0: number, v1: number, v2: number, v3: number, v4: number) {
const o4 = i * 4;
const o2 = i * 8;
this.float32[o4 + 0] = v0;
this.float32[o4 + 1] = v1;
this.float32[o4 + 2] = v2;
this.int16[o2 + 6] = v3;
this.int16[o2 + 7] = v4;
return i;
}
}

StructArrayLayout2f1f2i16.prototype.bytesPerElement = 16;
register('StructArrayLayout2f1f2i16', StructArrayLayout2f1f2i16);

/**
* Implementation of the StructArray layout:
* [0]: Uint8[2]
Expand Down Expand Up @@ -1063,6 +1103,7 @@ export {
StructArrayLayout1ul4,
StructArrayLayout6i1ul2ui20,
StructArrayLayout2i2i2i12,
StructArrayLayout2f1f2i16,
StructArrayLayout2ub2f12,
StructArrayLayout2i2ui3ul3ui2f3ub1ul1i48,
StructArrayLayout8i15ui1ul4f68,
Expand All @@ -1086,7 +1127,7 @@ export {
StructArrayLayout3f12 as SymbolDynamicLayoutArray,
StructArrayLayout1ul4 as SymbolOpacityArray,
StructArrayLayout2i2i2i12 as CollisionBoxLayoutArray,
StructArrayLayout2i4 as CollisionCircleLayoutArray,
StructArrayLayout2f1f2i16 as CollisionCircleLayoutArray,
StructArrayLayout2ub2f12 as CollisionVertexArray,
StructArrayLayout3ui6 as TriangleIndexArray,
StructArrayLayout2ui4 as LineIndexArray,
Expand Down
4 changes: 3 additions & 1 deletion src/data/bucket/symbol_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ export const collisionBoxLayout = createLayout([ // used to render collision box
], 4);

export const collisionCircleLayout = createLayout([ // used to render collision circles for debugging purposes
{name: 'a_idx', components: 2, type: 'Int16'}
{name: 'a_pos', components: 2, type: 'Float32'},
{name: 'a_radius', components: 1, type: 'Float32'},
{name: 'a_flags', components: 2, type: 'Int16'}
], 4);

export const placement = createLayout([
Expand Down
180 changes: 77 additions & 103 deletions src/render/draw_collision_debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import StencilMode from '../gl/stencil_mode';
import CullFaceMode from '../gl/cull_face_mode';
import {collisionUniformValues, collisionCircleUniformValues} from './program/collision_program';

import {StructArrayLayout2i4, StructArrayLayout3ui6} from '../data/array_types';
import {StructArrayLayout2i4, StructArrayLayout3ui6, CollisionCircleLayoutArray} from '../data/array_types';
import {collisionCircleLayout} from '../data/bucket/symbol_attributes';
import SegmentVector from '../data/segment';
import {mat4} from 'gl-matrix';
Expand Down Expand Up @@ -56,42 +56,20 @@ function drawCollisionDebug(painter: Painter, sourceCache: SourceCache, layer: S
}

function drawCollisionCircles(painter: Painter, sourceCache: SourceCache, layer: StyleLayer, coords: Array<OverscaledTileID>, translate: [number, number], translateAnchor: 'map' | 'viewport') {
// Collision circle rendering is done by using simple shader batching scheme where dynamic properties of
// circles are passed to the GPU using shader uniforms. Circles are first encoded into 4-component vectors
// (center_x, center_y, radius, flag) and then uploaded in batches as "uniform vec4 u_quads[N]".
// Vertex data is just a collection of incremental index values pointing to the quads-array.
//
// If one quad uses 4 vertices then all required values can be deduced from the index value:
// int quad_idx = int(vertex.idx / 4);
// int corner_idx = int(vertex.idx % 4);
//
// OpenGL ES 2.0 spec defines that the maximum number of supported vertex uniform vectors (vec4) should be
// at least 128. Choosing a safe value 64 for the quad array should leave enough space for rest of the
// uniform variables.
const maxQuadsPerDrawCall = 64;

if (!quadVertices) {
quadVertices = createQuadVertices(maxQuadsPerDrawCall);
}
if (!quadTriangles) {
quadTriangles = createQuadTriangles(maxQuadsPerDrawCall);
}

const context = painter.context;
const quadVertexBuffer = context.createVertexBuffer(quadVertices, collisionCircleLayout.members, true);
const quadIndexBuffer = context.createIndexBuffer(quadTriangles, true);

const quads = new Float32Array(maxQuadsPerDrawCall * 4);
let tileBatches = [];
let circleCount = 0;
let circleOffset = 0;

for (let i = 0; i < coords.length; i++) {
const coord = coords[i];
const tile = sourceCache.getTile(coord);
const bucket: ?SymbolBucket = (tile.getBucket(layer): any);
if (!bucket) continue;

const arr = bucket.collisionCircleArray;
const circleArray = bucket.collisionCircleArray;

if (!arr.length)
if (!circleArray.length)
continue;

let posMatrix = coord.posMatrix;
Expand All @@ -103,92 +81,88 @@ function drawCollisionCircles(painter: Painter, sourceCache: SourceCache, layer:
// We need to know the projection matrix that was used for projecting collision circles to the screen.
// This might vary between buckets as the symbol placement is a continous process. This matrix is
// required for transforming points from previous screen space to the current one
const batchInvTransform = mat4.create();
const batchTransform = posMatrix;

mat4.mul(batchInvTransform, bucket.placementInvProjMatrix, painter.transform.glCoordMatrix);
mat4.mul(batchInvTransform, batchInvTransform, bucket.placementViewportMatrix);

let batchIdx = 0;
let quadOffset = 0;

while (quadOffset < arr.length) {
const quadsLeft = arr.length - quadOffset;
const quadSpaceInBatch = maxQuadsPerDrawCall - batchIdx;
const batchSize = Math.min(quadsLeft, quadSpaceInBatch);

// Copy collision circles from the bucket array
for (let qIdx = quadOffset; qIdx < quadOffset + batchSize; qIdx++) {
quads[batchIdx * 4 + 0] = arr.float32[qIdx * 4 + 0]; // width
quads[batchIdx * 4 + 1] = arr.float32[qIdx * 4 + 1]; // height
quads[batchIdx * 4 + 2] = arr.float32[qIdx * 4 + 2]; // radius
quads[batchIdx * 4 + 3] = arr.float32[qIdx * 4 + 3]; // collisionFlag
batchIdx++;
}

quadOffset += batchSize;

if (batchIdx === maxQuadsPerDrawCall) {
drawBatch(painter, batchTransform, batchInvTransform, quads, batchIdx, layer.id, quadVertexBuffer, quadIndexBuffer);
batchIdx = 0;
}
}
const invTransform = mat4.create();
const transform = posMatrix;

// Render the leftover batch
if (batchIdx > 0) {
drawBatch(painter, batchTransform, batchInvTransform, quads, batchIdx, layer.id, quadVertexBuffer, quadIndexBuffer);
}
mat4.mul(invTransform, bucket.placementInvProjMatrix, painter.transform.glCoordMatrix);
mat4.mul(invTransform, invTransform, bucket.placementViewportMatrix);

tileBatches.push({
circleArray,
circleOffset,
transform,
invTransform
});

circleCount += circleArray.length;
circleOffset = circleCount;
}

quadIndexBuffer.destroy();
quadVertexBuffer.destroy();
}
if (!tileBatches.length)
return;

function drawBatch(painter: Painter, proj: mat4, invPrevProj: mat4, quads: any, numQuads: number, layerId: string, vb: VertexBuffer, ib: IndexBuffer) {
const context = painter.context;
const gl = context.gl;
const circleProgram = painter.useProgram('collisionCircle');

const uniforms = collisionCircleUniformValues(
proj,
invPrevProj,
quads,
painter.transform);

circleProgram.draw(
context,
gl.TRIANGLES,
DepthMode.disabled,
StencilMode.disabled,
painter.colorModeForRenderPass(),
CullFaceMode.disabled,
uniforms,
layerId,
vb,
ib,
SegmentVector.simpleSegment(0, 0, numQuads * 4, numQuads * 2),
null,
painter.transform.zoom,
null,
null,
null);
}

function createQuadVertices(quadCount: number): StructArrayLayout2i4 {
const vCount = quadCount * 4;
const array = new StructArrayLayout2i4();

array.resize(vCount);
array._trim();
// Construct vertex data
const vertexData = new CollisionCircleLayoutArray();
vertexData.resize(circleCount * 4);
vertexData._trim();

let vertexOffset = 0;

for (const batch of tileBatches) {
for (let i = 0; i < batch.circleArray.length; i++) {
const circleIdx = i * 4;
const x = batch.circleArray.float32[circleIdx + 0];
const y = batch.circleArray.float32[circleIdx + 1];
const radius = batch.circleArray.float32[circleIdx + 2];
const collision = batch.circleArray.float32[circleIdx + 3];

// 4 floats per vertex, 4 vertices per quad
vertexData.emplace(vertexOffset++, x, y, radius, collision, 0);
vertexData.emplace(vertexOffset++, x, y, radius, collision, 1);
vertexData.emplace(vertexOffset++, x, y, radius, collision, 2);
vertexData.emplace(vertexOffset++, x, y, radius, collision, 3);
}
}
if (!quadTriangles || quadTriangles.length < circleCount * 2) {
quadTriangles = createQuadTriangles(circleCount);
}

// Fill the buffer with an incremental index value (2 per vertex)
// [0, 0, 1, 1, 2, 2, 3, 3, 4, 4...]
for (let i = 0; i < vCount; i++) {
array.int16[i * 2 + 0] = i;
array.int16[i * 2 + 1] = i;
const indexBuffer = context.createIndexBuffer(quadTriangles, true);
const vertexBuffer = context.createVertexBuffer(vertexData, collisionCircleLayout.members, true);

// Render batches
for (let batch of tileBatches) {
const uniforms = collisionCircleUniformValues(
batch.transform,
batch.invTransform,
painter.transform
);

circleProgram.draw(
context,
gl.TRIANGLES,
DepthMode.disabled,
StencilMode.disabled,
painter.colorModeForRenderPass(),
CullFaceMode.disabled,
uniforms,
layer.id,
vertexBuffer,
indexBuffer,
SegmentVector.simpleSegment(0, batch.circleOffset * 2, batch.circleArray.length * 4, batch.circleArray.length * 2),
null,
painter.transform.zoom,
null,
null,
null);
}

return array;
vertexBuffer.destroy();
indexBuffer.destroy();
}

function createQuadTriangles(quadCount: number): StructArrayLayout3ui6 {
Expand Down
4 changes: 0 additions & 4 deletions src/render/program/collision_program.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export type CollisionUniformsType = {|
export type CollisionCircleUniformsType = {|
'u_matrix': UniformMatrix4f,
'u_inv_matrix': UniformMatrix4f,
'u_quads': Uniform4fv,
'u_camera_to_center_distance': Uniform1f,
'u_viewport_size': Uniform2f
|};
Expand All @@ -40,7 +39,6 @@ const collisionUniforms = (context: Context, locations: UniformLocations): Colli
const collisionCircleUniforms = (context: Context, locations: UniformLocations): CollisionCircleUniformsType => ({
'u_matrix': new UniformMatrix4f(context, locations.u_matrix),
'u_inv_matrix': new UniformMatrix4f(context, locations.u_inv_matrix),
'u_quads': new Uniform4fv(context, locations.u_quads),
'u_camera_to_center_distance': new Uniform1f(context, locations.u_camera_to_center_distance),
'u_viewport_size': new Uniform2f(context, locations.u_viewport_size)
});
Expand All @@ -66,13 +64,11 @@ const collisionUniformValues = (
const collisionCircleUniformValues = (
matrix: Float32Array,
invMatrix: Float32Array,
quads: Float32Array,
transform: Transform
): UniformValues<CollisionCircleUniformsType> => {
return {
'u_matrix': matrix,
'u_inv_matrix': invMatrix,
'u_quads': quads,
'u_camera_to_center_distance': transform.cameraToCenterDistance,
'u_viewport_size': [transform.width, transform.height]
};
Expand Down
42 changes: 7 additions & 35 deletions src/shaders/collision_circle.vertex.glsl
Original file line number Diff line number Diff line change
@@ -1,35 +1,12 @@
// This shader implements a simple geometry instancing using uniform vector arrays. Per-circle data is stored
// in u_quads array (circles are rendered as quads) which is then referenced by vertices of different quads.
//
// It is possible to deduce all variables required to render sequential quads by using a single incremental
// index value as the only vertex data. If one quad uses 4 vertices then index of the quad can be found
// quad_idx = floor(vertex.idx / 4) and vertex_idx = vertex.idx % 4.
//
// 1 2 vertex offsets:
// *----* 0: vec2(-1, -1)
// | /| 1: vec2(-1, 1)
// | / | 2: vec2(1, 1)
// | / | 3: vec2(1, -1)
// |/ |
// *----*
// 0 3
//

attribute vec2 a_idx;
attribute vec2 a_pos;
attribute float a_radius;
attribute vec2 a_flags;

uniform mat4 u_matrix;
uniform mat4 u_inv_matrix;
uniform vec2 u_viewport_size;
uniform float u_camera_to_center_distance;

// Rendering information of each quad is packed into a single uniform array.
// NOTE: all values are in screen space (ie. in pixels) already!
// x: center_x
// y: center_y
// z: radius
// w: collision flag [0, 1]
uniform vec4 u_quads[64];

varying float v_radius;
varying vec2 v_extrude;
varying float v_perspective_ratio;
Expand All @@ -48,15 +25,10 @@ vec3 toTilePosition(vec2 screenPos) {
}

void main() {

highp float vertexIdx = mod(a_idx.x, 4.0);

// Get the quad this vertex belongs to
vec4 quad = u_quads[int(floor(a_idx.x / 4.0))];

vec2 quadCenterPos = quad.xy;
highp float radius = quad.z;
highp float collision = quad.w;
vec2 quadCenterPos = a_pos;
float radius = a_radius;
float collision = a_flags.x;
float vertexIdx = a_flags.y;

vec2 quadVertexOffset = vec2(
mix(-1.0, 1.0, float(vertexIdx >= 2.0)),
Expand Down

0 comments on commit 439b613

Please sign in to comment.