diff --git a/src/data/array_types.js b/src/data/array_types.js index b5eca3189f0..11cdf02bba7 100644 --- a/src/data/array_types.js +++ b/src/data/array_types.js @@ -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] @@ -1063,6 +1103,7 @@ export { StructArrayLayout1ul4, StructArrayLayout6i1ul2ui20, StructArrayLayout2i2i2i12, + StructArrayLayout2f1f2i16, StructArrayLayout2ub2f12, StructArrayLayout2i2ui3ul3ui2f3ub1ul1i48, StructArrayLayout8i15ui1ul4f68, @@ -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, diff --git a/src/data/bucket/symbol_attributes.js b/src/data/bucket/symbol_attributes.js index aa58986c170..7b149e6a378 100644 --- a/src/data/bucket/symbol_attributes.js +++ b/src/data/bucket/symbol_attributes.js @@ -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([ diff --git a/src/render/draw_collision_debug.js b/src/render/draw_collision_debug.js index 0d8da497eee..33876e20750 100644 --- a/src/render/draw_collision_debug.js +++ b/src/render/draw_collision_debug.js @@ -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'; @@ -56,32 +56,10 @@ function drawCollisionDebug(painter: Painter, sourceCache: SourceCache, layer: S } function drawCollisionCircles(painter: Painter, sourceCache: SourceCache, layer: StyleLayer, coords: Array, 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]; @@ -89,9 +67,9 @@ function drawCollisionCircles(painter: Painter, sourceCache: SourceCache, layer: 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; @@ -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 { diff --git a/src/render/program/collision_program.js b/src/render/program/collision_program.js index c5fe05102ac..9d5fada05a2 100644 --- a/src/render/program/collision_program.js +++ b/src/render/program/collision_program.js @@ -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 |}; @@ -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) }); @@ -66,13 +64,11 @@ const collisionUniformValues = ( const collisionCircleUniformValues = ( matrix: Float32Array, invMatrix: Float32Array, - quads: Float32Array, transform: Transform ): UniformValues => { 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] }; diff --git a/src/shaders/collision_circle.vertex.glsl b/src/shaders/collision_circle.vertex.glsl index 58d17990bba..e64f1728297 100644 --- a/src/shaders/collision_circle.vertex.glsl +++ b/src/shaders/collision_circle.vertex.glsl @@ -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; @@ -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)),