Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify globe custom layer interface #12545

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
66 changes: 14 additions & 52 deletions debug/satellites-custom-layer.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,13 @@
const KM_TO_M = 1000;
const TIME_STEP = 3 * 1000;

const globeVertCode = `
const vertCode = `
attribute vec3 a_pos_ecef;
attribute vec3 a_pos_merc;

uniform mat4 u_projection;
uniform mat4 u_globeToMercMatrix;
uniform float u_globeToMercatorTransition;
uniform vec2 u_centerInMerc;
uniform float u_pixelsPerMeterRatio;

void main() {
vec4 p = u_projection * u_globeToMercMatrix * vec4(a_pos_ecef, 1.);
p /= p.w;
if (u_globeToMercatorTransition > 0.) {

vec4 merc = vec4(a_pos_merc, 1.);
merc.xy = (merc.xy - u_centerInMerc) * u_pixelsPerMeterRatio + u_centerInMerc;
merc.z *= u_pixelsPerMeterRatio;

merc = u_projection * merc;
merc /= merc.w;
p = mix(p, merc, u_globeToMercatorTransition);
}
gl_PointSize = 30.;
gl_Position = p;
}
`;

const mercVertCode = `
precision highp float;
attribute vec3 a_pos_merc;
uniform mat4 u_projection;

void main() {
gl_PointSize = 30.;
gl_Position = u_projection * vec4(a_pos_merc, 1.);
gl_Position = project_custom_layer(a_pos_merc, a_pos_ecef);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could simplify this one step further on the globe custom layer, since it's now consolidated in the same shader program the user may not need to provide two buffers (ecef + merc) and we could ask for them to only provide the merc while doing the projection to ecef for them in project_custom_layer(a_pos_merc).

It would mean moving some computations from CPU to GPU but I think that's an ok tradeoff for the sake of simplified use. This also has the advantage that translating custom layer code to the new system becomes fairly trivial.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering though if this approach won't limit users. In the debug example where only a few points are rendered what's proposed above works quite ok. But what if a client decides to use a heavy model (e.g. a satellite model). In that case all the mercator points need to be transformed into ECEF and this could be quite expensive since the operation is not a linear one that could be done like before using a standard transformation matrix. What are your thoughts on that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed offline: let's hold on this suggestion, e.g. transform ECEF on the GPU on our side for performance concern. We'll still keep API conservative and simple as in this PR and introduce other hooks as needed. The idea being that we don't over-commit by giving too much through the API, since it's always easy to give more later than give too much now and introduce breaking changes by removing it.

}
`;

Expand Down Expand Up @@ -100,8 +71,7 @@ const satellitesLayer = {
this.posEcefVbo = gl.createBuffer();
this.posMercVbo = gl.createBuffer();

this.globeProgram = createProgram(gl, globeVertCode, fragCode);
this.mercProgram = createProgram(gl, mercVertCode, fragCode);
this.program = createProgram(gl, map.customLayerVertexHeader.concat(vertCode), fragCode);

fetch('space-track-leo.txt').then(r => r.text()).then(rawData => {
const tleData = rawData.replace(/\r/g, '')
Expand Down Expand Up @@ -143,30 +113,22 @@ const satellitesLayer = {
}
},

render (gl, projectionMatrix, projection, globeToMercMatrix, transition, centerInMercator, pixelsPerMeterRatio) {
getShaderProgram () {
return this.program;
},

render (gl, projectionMatrix) {
if (this.satData) {
this.updateBuffers();

const primitiveCount = this.posEcef.length / 3;
gl.disable(gl.DEPTH_TEST);
if (projection && projection.name === 'globe') { // globe projection and globe to mercator transition
gl.useProgram(this.globeProgram);

updateVboAndActivateAttrib(gl, this.globeProgram, this.posEcefVbo, this.posEcef, "a_pos_ecef");
updateVboAndActivateAttrib(gl, this.globeProgram, this.posMercVbo, this.posMerc, "a_pos_merc");
gl.uniformMatrix4fv(gl.getUniformLocation(this.globeProgram, "u_projection"), false, projectionMatrix);
gl.uniformMatrix4fv(gl.getUniformLocation(this.globeProgram, "u_globeToMercMatrix"), false, globeToMercMatrix);
gl.uniform1f(gl.getUniformLocation(this.globeProgram, "u_globeToMercatorTransition"), transition);
gl.uniform2f(gl.getUniformLocation(this.globeProgram, "u_centerInMerc"), centerInMercator[0], centerInMercator[1]);
gl.uniform1f(gl.getUniformLocation(this.globeProgram, "u_pixelsPerMeterRatio"), pixelsPerMeterRatio);

gl.drawArrays(gl.POINTS, 0, primitiveCount);
} else { // mercator projection
gl.useProgram(this.mercProgram);
updateVboAndActivateAttrib(gl, this.mercProgram, this.posMercVbo, this.posMerc, "a_pos_merc");
gl.uniformMatrix4fv(gl.getUniformLocation(this.mercProgram, "u_projection"), false, projectionMatrix);
gl.drawArrays(gl.POINTS, 0, primitiveCount);
}
gl.useProgram(this.program);

updateVboAndActivateAttrib(gl, this.program, this.posEcefVbo, this.posEcef, "a_pos_ecef");
updateVboAndActivateAttrib(gl, this.program, this.posMercVbo, this.posMerc, "a_pos_merc");

gl.drawArrays(gl.POINTS, 0, primitiveCount);
}
}
};
14 changes: 6 additions & 8 deletions src/geo/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -1654,14 +1654,12 @@ class Transform {
return this.mercatorMatrix.slice();
}

globeToMercatorMatrix(): ?Array<number> {
if (this.projection.name === 'globe') {
const pixelsToMerc = 1 / this.worldSize;
const m = mat4.fromScaling([], [pixelsToMerc, pixelsToMerc, pixelsToMerc]);
mat4.multiply(m, m, this.globeMatrix);
return m;
}
return undefined;
globeToMercatorMatrix(): Array<number> {
assert(this.projection.name === 'globe');
const pixelsToMerc = 1 / this.worldSize;
const m = mat4.fromScaling([], [pixelsToMerc, pixelsToMerc, pixelsToMerc]);
mat4.multiply(m, m, this.globeMatrix);
return m;
}

recenterOnTerrain() {
Expand Down
44 changes: 32 additions & 12 deletions src/render/draw_custom.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,27 @@ import DepthMode from '../gl/depth_mode.js';
import StencilMode from '../gl/stencil_mode.js';
import {warnOnce} from '../util/util.js';
import {globeToMercatorTransition} from './../geo/projection/globe_util.js';

import {mat4} from 'gl-matrix';
import type Painter from './painter.js';
import type {OverscaledTileID} from '../source/tile_id.js';
import type SourceCache from '../source/source_cache.js';
import type CustomStyleLayer from '../style/style_layer/custom_style_layer.js';
import MercatorCoordinate from '../geo/mercator_coordinate.js';
import assert from 'assert';

function createMercatorGlobeMatrix(projection, pixelsPerMeterRatio, centerInMerc) {
const mercToPixelMatrix = mat4.create();
mat4.identity(mercToPixelMatrix);

mercToPixelMatrix[0] = pixelsPerMeterRatio;
mercToPixelMatrix[5] = pixelsPerMeterRatio;
mercToPixelMatrix[10] = pixelsPerMeterRatio;
mercToPixelMatrix[12] = centerInMerc.x * (1.0 - pixelsPerMeterRatio);
mercToPixelMatrix[13] = centerInMerc.y * (1.0 - pixelsPerMeterRatio);

return mat4.multiply([], projection, mercToPixelMatrix);
}

function drawCustom(painter: Painter, sourceCache: SourceCache, layer: CustomStyleLayer, coords: Array<OverscaledTileID>) {

const context = painter.context;
Expand All @@ -32,12 +45,7 @@ function drawCustom(painter: Painter, sourceCache: SourceCache, layer: CustomSty
painter.setCustomLayerDefaults();
context.setColorMode(painter.colorModeForRenderPass());

if (painter.transform.projection.name === "globe") {
const center = painter.transform.pointMerc;
prerender.call(implementation, context.gl, painter.transform.customLayerMatrix(), painter.transform.getProjection(), painter.transform.globeToMercatorMatrix(), globeToMercatorTransition(painter.transform.zoom), [center.x, center.y], painter.transform.pixelsPerMeterRatio);
} else {
prerender.call(implementation, context.gl, painter.transform.customLayerMatrix());
}
prerender.call(implementation, context.gl, painter.transform.customLayerMatrix());

context.setDirty();
painter.setBaseState();
Expand Down Expand Up @@ -76,13 +84,25 @@ function drawCustom(painter: Painter, sourceCache: SourceCache, layer: CustomSty

context.setDepthMode(depthMode);

if (painter.transform.projection.name === "globe") {
const center = painter.transform.pointMerc;
implementation.render(context.gl, painter.transform.customLayerMatrix(), painter.transform.getProjection(), painter.transform.globeToMercatorMatrix(), globeToMercatorTransition(painter.transform.zoom), [center.x, center.y], painter.transform.pixelsPerMeterRatio);
} else {
implementation.render(context.gl, painter.transform.customLayerMatrix());
const program = implementation.getShaderProgram && implementation.getShaderProgram();
if (program) {
context.gl.useProgram(program);
layer.setUniform(context.gl, program, "u_isGlobe", +(painter.transform.projection.name === "globe"));
layer.setUniform(context.gl, program, "u_transition", globeToMercatorTransition(painter.transform.zoom));

if (painter.transform.projection.name === "globe") {
const center = painter.transform.pointMerc;
const globeProjection = mat4.multiply([], painter.transform.customLayerMatrix(), painter.transform.globeToMercatorMatrix());
const mercatorProjection = createMercatorGlobeMatrix(painter.transform.customLayerMatrix(), painter.transform.pixelsPerMeterRatio, center);
layer.setUniform(context.gl, program, "u_projection", globeProjection);
layer.setUniform(context.gl, program, "u_mercatorProjection", mercatorProjection);
} else {
layer.setUniform(context.gl, program, "u_projection", painter.transform.customLayerMatrix());
}
}

implementation.render(context.gl, painter.transform.customLayerMatrix());

context.setDirty();
painter.setBaseState();
context.bindFramebuffer.set(null);
Expand Down
48 changes: 47 additions & 1 deletion src/style/style_layer/custom_style_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import MercatorCoordinate from '../../geo/mercator_coordinate.js';
import type Map from '../../ui/map.js';
import assert from 'assert';
import type {ValidationErrors} from '../validate_style.js';
import {warnOnce} from '../../util/util.js';
import type {ProjectionSpecification} from '../../style-spec/types.js';

type CustomRenderMethod = (gl: WebGLRenderingContext, matrix: Array<number>, projection: ?ProjectionSpecification, projectionToMercatorMatrix: ?Array<number>, projectionToMercatorTransition: ?number, centerInMercator: ?Array<number>, pixelsPerMeterRatio: ?number) => void;
type CustomRenderMethod = (gl: WebGLRenderingContext, matrix: Array<number>) => void;

/**
* Interface for custom style layers. This is a specification for
Expand Down Expand Up @@ -155,6 +156,7 @@ export type CustomLayerInterface = {
type: "custom",
renderingMode: "2d" | "3d",
render: CustomRenderMethod,
getShaderProgram: ?(projection: ?ProjectionSpecification) => ?WebGLProgram,
prerender: ?CustomRenderMethod,
renderToTile: ?(gl: WebGLRenderingContext, tileId: MercatorCoordinate) => void,
shouldRerenderTiles: ?() => boolean,
Expand Down Expand Up @@ -189,13 +191,57 @@ export function validateCustomStyleLayer(layerObject: CustomLayerInterface): Val
return errors;
}

export function customLayerVertexHeader(): string {
return `
uniform mat4 u_projection;
uniform mat4 u_mercatorProjection;
uniform float u_isGlobe;
uniform float u_transition;

vec4 project_custom_layer(vec3 pos_merc, vec3 pos_ecef) {
if (u_isGlobe == 1.0) {
vec4 projected_pos = u_projection * vec4(pos_ecef, 1.0);
projected_pos /= projected_pos.w;

if (u_transition > 0.0) {
vec4 mercator = u_mercatorProjection * vec4(pos_merc, 1.0);
mercator /= mercator.w;
projected_pos = mix(projected_pos, mercator, u_transition);
}

return projected_pos;
} else {
return u_projection * vec4(pos_merc, 1.0);
}
}
`;
}

class CustomStyleLayer extends StyleLayer {

implementation: CustomLayerInterface;

uniformLocation: {string: WebGLUniformLocation};

constructor(implementation: CustomLayerInterface) {
super(implementation, {});
this.implementation = implementation;
this.uniformLocation = {};
}

setUniform(gl: WebGLRenderingContext, program: WebGLProgram, name: string, value: number | Array<number>) {
this.uniformLocation[name] = this.uniformLocation[name] || gl.getUniformLocation(program, name);
if (this.uniformLocation[name]) {
if (Array.isArray(value)) {
gl.uniformMatrix4fv(this.uniformLocation[name], false, value);
} else if (typeof value === 'number') {
gl.uniform1f(this.uniformLocation[name], value);
} else {
assert(false, "Unimplemented custom uniform type");
}
} else {
warnOnce(`Layer "${this.id}" is missing uniform location "${name}", make sure to include shader prelude with map.customLayerVertexHeader as part of your custom layer shader source`);
}
}

is3D(): boolean {
Expand Down
5 changes: 5 additions & 0 deletions src/ui/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import SourceCache from '../source/source_cache.js';
import {GLOBE_ZOOM_THRESHOLD_MAX} from '../geo/projection/globe_util.js';
import {setCacheLimits} from '../util/tile_request_cache.js';
import {Debug} from '../util/debug.js';
import {customLayerVertexHeader} from '../style/style_layer/custom_style_layer.js';

import type {PointLike} from '@mapbox/point-geometry';
import type {RequestTransformFunction} from '../util/mapbox.js';
Expand Down Expand Up @@ -3764,6 +3765,10 @@ class Map extends Camera {
*/

get version(): string { return version; }

get customLayerVertexHeader(): string {
return customLayerVertexHeader();
}
}

export default Map;
Expand Down