Skip to content

Commit

Permalink
viewspace symbol collision PoC
Browse files Browse the repository at this point in the history
  • Loading branch information
mpulkki-mapbox committed Jan 22, 2020
1 parent 4bc9b85 commit dae7208
Show file tree
Hide file tree
Showing 14 changed files with 452 additions and 133 deletions.
5 changes: 5 additions & 0 deletions src/data/bucket/symbol_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ export const collisionCircleLayout = createLayout([ // used to render collision
{name: 'a_extrude', components: 2, type: 'Int16'}
], 4);

export const collisionCircleLayoutTemp = createLayout([ // used to render collision circles for debugging purposes
{name: 'a_pos', components: 2, type: 'Int16'},
{name: 'a_reserved', components: 2, type: 'Int16'}
], 4);

export const placement = createLayout([
{type: 'Int16', name: 'anchorX'},
{type: 'Int16', name: 'anchorY'},
Expand Down
4 changes: 4 additions & 0 deletions src/data/bucket/symbol_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,8 @@ class SymbolBucket implements Bucket {
sortedAngle: number;
featureSortOrder: Array<number>;

collisionCircleArrayTemp: Array<number>;

text: SymbolBuffers;
icon: SymbolBuffers;
textCollisionBox: CollisionBuffers;
Expand Down Expand Up @@ -346,6 +348,8 @@ class SymbolBucket implements Bucket {
this.hasRTLText = false;
this.sortKeyRanges = [];

this.collisionCircleArrayTemp = [];

const layer = this.layers[0];
const unevaluatedLayoutValues = layer._unevaluatedLayout._values;

Expand Down
8 changes: 5 additions & 3 deletions src/gl/vertex_buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,13 @@ class VertexBuffer {
this.context.bindVertexBuffer.set(this.buffer);
}

updateData(array: StructArray) {
assert(array.length === this.length);
updateData(array: StructArray, elementOffset: ?number) {
elementOffset = elementOffset || 0;
assert(elementOffset + array.length <= this.length);
const byteOffset = elementOffset * array.bytesPerElement;
const gl = this.context.gl;
this.bind();
gl.bufferSubData(gl.ARRAY_BUFFER, 0, array.arrayBuffer);
gl.bufferSubData(gl.ARRAY_BUFFER, byteOffset, array.arrayBuffer);
}

enableAttributes(gl: WebGLRenderingContext, program: Program<*>) {
Expand Down
197 changes: 183 additions & 14 deletions src/render/draw_collision_debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,207 @@ import type SymbolBucket from '../data/bucket/symbol_bucket';
import DepthMode from '../gl/depth_mode';
import StencilMode from '../gl/stencil_mode';
import CullFaceMode from '../gl/cull_face_mode';
import {collisionUniformValues} from './program/collision_program';
import {collisionUniformValues, collisionUniformValuesTemp} from './program/collision_program';

import {StructArrayLayout4i8, StructArrayLayout3ui6} from '../data/array_types'
import {collisionCircleLayoutTemp} from '../data/bucket/symbol_attributes';
import SegmentVector from '../data/segment';
import { mat4, vec4 } from 'gl-matrix';

export default drawCollisionDebug;

//const matrixCache = {};

function drawCollisionDebugGeometry(painter: Painter, sourceCache: SourceCache, layer: StyleLayer, coords: Array<OverscaledTileID>, drawCircles: boolean,
translate: [number, number], translateAnchor: 'map' | 'viewport', isText: boolean) {
const context = painter.context;
const gl = context.gl;
const program = drawCircles ? painter.useProgram('collisionCircle') : painter.useProgram('collisionBox');
if (!drawCircles) {
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 buffers = drawCircles ? (isText ? bucket.textCollisionCircle : bucket.iconCollisionCircle) : (isText ? bucket.textCollisionBox : bucket.iconCollisionBox);
if (!buffers) continue;
let posMatrix = coord.posMatrix;
if (translate[0] !== 0 || translate[1] !== 0) {
posMatrix = painter.translatePosMatrix(coord.posMatrix, tile, translate, translateAnchor);
}
program.draw(context, drawCircles ? gl.TRIANGLES : gl.LINES,
DepthMode.disabled, StencilMode.disabled,
painter.colorModeForRenderPass(),
CullFaceMode.disabled,
collisionUniformValues(
posMatrix,
painter.transform,
tile),
layer.id, buffers.layoutVertexBuffer, buffers.indexBuffer,
buffers.segments, null, painter.transform.zoom, null, null,
buffers.collisionVertexBuffer);
}
}
// Render collision circles
const program2 = painter.useProgram('collisionCircleTemp');

const maxCirclesPerBatch = 4096;
const maxVerticesPerBatch = maxCirclesPerBatch * 4;
const maxIndicesPerBatch = maxCirclesPerBatch * 6;

if (!('vertexBuffer2' in layer)) {
const array = new StructArrayLayout4i8();

// use temporary array reserve space for x circles (4 vertex per quad)
array.resize(maxVerticesPerBatch);
array._trim();

layer.vertexBuffer2 = context.createVertexBuffer(array, collisionCircleLayoutTemp.members, true);
}

if (!('indexBuffer2' in layer)) {
const array = new StructArrayLayout3ui6();

// Pre-generate index buffer for quads
array.resize(maxIndicesPerBatch);
array._trim();

for (let i = 0; i < maxCirclesPerBatch; i++) {
const idx = i * 6;

array.uint16[idx + 0] = i * 4 + 0;
array.uint16[idx + 1] = i * 4 + 1;
array.uint16[idx + 2] = i * 4 + 2;
array.uint16[idx + 3] = i * 4 + 2;
array.uint16[idx + 4] = i * 4 + 3;
array.uint16[idx + 5] = i * 4 + 0;
}

layer.indexBuffer2 = context.createIndexBuffer(array, true);
}

let colorFlip = false;

// Gather collision circles of tiles and render them in batches
const batchVertices = new StructArrayLayout4i8();
batchVertices.resize(maxVerticesPerBatch);
batchVertices._trim();

const appendVertex = (idx, x, y, z, w) => {
batchVertices.int16[idx * 4 + 0] = x; // anchor center
batchVertices.int16[idx * 4 + 1] = y; // anchor center
batchVertices.int16[idx * 4 + 2] = z; // radius
batchVertices.int16[idx * 4 + 3] = w; // radius
}

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 buffers = drawCircles ? (isText ? bucket.textCollisionCircle : bucket.iconCollisionCircle) : (isText ? bucket.textCollisionBox : bucket.iconCollisionBox);
if (!buffers) continue;

const arr = bucket.collisionCircleArrayTemp;

if (!arr.length)
continue;

// Collision circle rendering is a little more complex now that they're stored in screen coordinates.
// Screen space positions of previous frames can be reused by transforming them first to tile space and
// then to the new clip space. Depth information of vertices is not preserved during circle generation
// so it has to be reconstructed in vertex shader
let posMatrix = coord.posMatrix;
if (translate[0] !== 0 || translate[1] !== 0) {
posMatrix = painter.translatePosMatrix(coord.posMatrix, tile, translate, translateAnchor);
}
program.draw(context, drawCircles ? gl.TRIANGLES : gl.LINES,
DepthMode.disabled, StencilMode.disabled,
painter.colorModeForRenderPass(),
CullFaceMode.disabled,
collisionUniformValues(
posMatrix,
painter.transform,
tile),
layer.id, buffers.layoutVertexBuffer, buffers.indexBuffer,
buffers.segments, null, painter.transform.zoom, null, null,
buffers.collisionVertexBuffer);

let prevInvPosMatrix = posMatrix;

if ('posMatrixCircles' in bucket) {
prevInvPosMatrix = mat4.invert([], bucket['posMatrixCircles']);
} else {
prevInvPosMatrix = mat4.invert([], posMatrix);
}

const uniforms = collisionUniformValuesTemp(painter.transform.glCoordMatrix, prevInvPosMatrix,
posMatrix, [painter.transform.width, painter.transform.height]);

// Upload and render quads in batches
let batchVertexIdx = 0;
let vertexOffset = 0;

while (vertexOffset < arr.length) {
const verticesLeft = arr.length - vertexOffset;
const vertexSpaceLeftInBatch = maxVerticesPerBatch - batchVertexIdx;
const batchSize = Math.min(verticesLeft, vertexSpaceLeftInBatch);

for (let vIdx = vertexOffset; vIdx < vertexOffset + batchSize; vIdx+=4) {
const r = arr[vIdx + 2];
appendVertex(batchVertexIdx + 0, arr[vIdx + 0], arr[vIdx + 1], -r, -r);
appendVertex(batchVertexIdx + 1, arr[vIdx + 0], arr[vIdx + 1], -r, r);
appendVertex(batchVertexIdx + 2, arr[vIdx + 0], arr[vIdx + 1], r, r);
appendVertex(batchVertexIdx + 3, arr[vIdx + 0], arr[vIdx + 1], r, -r);

batchVertexIdx += 4;
}

vertexOffset += batchSize;

// TODO: Proper buffer orphaning. This might currently cause CPU-GPU sync!
if (batchVertexIdx == maxVerticesPerBatch) {
// Render the batch
layer.vertexBuffer2.updateData(batchVertices, 0);

program2.draw(
context,
gl.TRIANGLES,
DepthMode.disabled,
StencilMode.disabled,
painter.colorModeForRenderPass(),
CullFaceMode.disabled,
uniforms,
layer.id,
layer.vertexBuffer2, // layoutVertexBuffer
layer.indexBuffer2, // indexbuffer,
SegmentVector.simpleSegment(0, 0, batchVertexIdx, batchVertexIdx / 2),
null,
painter.transform.zoom,
null,
null, // vertexBuffer
null // vertexBuffer
);

batchVertexIdx = 0;
}
}

// Render the leftover branch
if (batchVertexIdx) {
// Render the batch
layer.vertexBuffer2.updateData(batchVertices, 0);

program2.draw(
context,
gl.TRIANGLES,
DepthMode.disabled,
StencilMode.disabled,
painter.colorModeForRenderPass(),
CullFaceMode.disabled,
uniforms,
layer.id,
layer.vertexBuffer2, // layoutVertexBuffer
layer.indexBuffer2, // indexbuffer,
SegmentVector.simpleSegment(0, 0, batchVertexIdx, batchVertexIdx / 2),
null,
painter.transform.zoom,
null,
null, // vertexBuffer
null // vertexBuffer
);

batchVertexIdx = 0;
}
}
}

Expand Down
30 changes: 29 additions & 1 deletion src/render/program/collision_program.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ export type CollisionUniformsType = {|
'u_overscale_factor': Uniform1f
|};

export type COllisionUniformsTypeTemp = {|
'u_matrix': UniformMatrix4f,
'u_toWorld': UniformMatrix4f,
'u_fromWorld': UniformMatrix4f,
'u_viewport_size': Uniform2f
|};

const collisionUniforms = (context: Context, locations: UniformLocations): CollisionUniformsType => ({
'u_matrix': new UniformMatrix4f(context, locations.u_matrix),
'u_camera_to_center_distance': new Uniform1f(context, locations.u_camera_to_center_distance),
Expand All @@ -28,6 +35,13 @@ const collisionUniforms = (context: Context, locations: UniformLocations): Colli
'u_overscale_factor': new Uniform1f(context, locations.u_overscale_factor)
});

const collisionUniformsTemp = (context: Context, locations: UniformLocations): CollisionUniformsType => ({
'u_matrix': new UniformMatrix4f(context, locations.u_matrix),
'u_toWorld': new UniformMatrix4f(context, locations.u_toWorld),
'u_fromWorld': new UniformMatrix4f(context, locations.u_fromWorld),
'u_viewport_size': new Uniform2f(context, locations.u_viewport_size)
});

const collisionUniformValues = (
matrix: Float32Array,
transform: Transform,
Expand All @@ -46,4 +60,18 @@ const collisionUniformValues = (
};
};

export {collisionUniforms, collisionUniformValues};
const collisionUniformValuesTemp = (
matrix: Float32Array,
toWorld: Float32Array,
fromWorld: Float32Array,
viewportSize: array<number>
): UniformValues<COllisionUniformsTypeTemp> => {
return {
'u_matrix': matrix,
'u_toWorld': toWorld,
'u_fromWorld': fromWorld,
'u_viewport_size': viewportSize
};
};

export {collisionUniforms, collisionUniformValues, collisionUniformsTemp, collisionUniformValuesTemp};
3 changes: 2 additions & 1 deletion src/render/program/program_uniforms.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import {fillExtrusionUniforms, fillExtrusionPatternUniforms} from './fill_extrusion_program';
import {fillUniforms, fillPatternUniforms, fillOutlineUniforms, fillOutlinePatternUniforms} from './fill_program';
import {circleUniforms} from './circle_program';
import {collisionUniforms} from './collision_program';
import {collisionUniforms, collisionUniformsTemp} from './collision_program';
import {debugUniforms} from './debug_program';
import {clippingMaskUniforms} from './clipping_mask_program';
import {heatmapUniforms, heatmapTextureUniforms} from './heatmap_program';
Expand All @@ -23,6 +23,7 @@ export const programUniforms = {
circle: circleUniforms,
collisionBox: collisionUniforms,
collisionCircle: collisionUniforms,
collisionCircleTemp: collisionUniformsTemp,
debug: debugUniforms,
clippingMask: clippingMaskUniforms,
heatmap: heatmapUniforms,
Expand Down
15 changes: 9 additions & 6 deletions src/shaders/collision_circle.fragment.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,20 @@ void main() {
float alpha = 0.5;

// Red = collision, hide label
vec4 color = vec4(1.0, 0.0, 0.0, 1.0) * alpha;
vec4 color = vec4(0.0, 0.0, 1.0, 1.0) * alpha;

// Blue = no collision, label is showing
if (v_placed > 0.5) {
color = vec4(0.0, 0.0, 1.0, 0.5) * alpha;
}

if (v_notUsed > 0.5) {
// This box not used, fade it out
color *= .2;
}
else
discard;

//if (v_notUsed > 0.5) {
// // This box not used, fade it out
// color *= .2;
// discard;
//}

float extrude_scale_length = length(v_extrude_scale);
float extrude_length = length(v_extrude) * extrude_scale_length;
Expand Down
3 changes: 3 additions & 0 deletions src/shaders/collision_circle_temp.fragment.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
void main() {
gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
}
25 changes: 25 additions & 0 deletions src/shaders/collision_circle_temp.vertex.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
attribute vec2 a_pos;
attribute vec2 a_reserved;

uniform mat4 u_matrix;
uniform mat4 u_toWorld;
uniform mat4 u_fromWorld;
uniform vec2 u_viewport_size;

void main() {
// 100 = hard-coded padding used in collision logic
vec4 clipPos = u_matrix * vec4(a_pos - vec2(100), 0.0, 1.0);

vec4 rayStart = u_toWorld * vec4(clipPos.xy / clipPos.w, -1.0, 1.0);
vec4 rayEnd = u_toWorld * vec4(clipPos.xy / clipPos.w, 1.0, 1.0);

rayStart.xyz /= rayStart.w;
rayEnd.xyz /= rayEnd.w;

float t = (0.0 - rayStart.z) / (rayEnd.z - rayStart.z);
vec3 tilePos = mix(rayStart.xyz, rayEnd.xyz, t);

clipPos = u_fromWorld * vec4(tilePos, 1.0);

gl_Position = vec4(clipPos.xyz / clipPos.w, 1.0) + vec4(a_reserved / u_viewport_size * 2.0, 0.0, 0.0);
}
Loading

0 comments on commit dae7208

Please sign in to comment.