@react-x11/components 0.7.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +295 -51
- package/dist/charts/node.d.ts +1 -1
- package/dist/charts/node.d.ts.map +1 -1
- package/dist/charts/node.js +6 -2
- package/dist/charts/node.js.map +1 -1
- package/dist/color-picker/ColorPicker.d.ts.map +1 -1
- package/dist/color-picker/ColorPicker.js +43 -33
- package/dist/color-picker/ColorPicker.js.map +1 -1
- package/dist/embed/client.d.ts +48 -3
- package/dist/embed/client.d.ts.map +1 -1
- package/dist/embed/client.js +79 -5
- package/dist/embed/client.js.map +1 -1
- package/dist/embed/index.d.ts +1 -1
- package/dist/embed/index.d.ts.map +1 -1
- package/dist/embed/index.js +1 -1
- package/dist/embed/index.js.map +1 -1
- package/dist/html/node.d.ts +3 -1
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +7 -5
- package/dist/html/node.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/{markdown → internal/markdown}/ast.d.ts +12 -0
- package/dist/internal/markdown/ast.d.ts.map +1 -0
- package/dist/internal/markdown/ast.js +22 -0
- package/dist/internal/markdown/ast.js.map +1 -0
- package/dist/internal/markdown/parse.d.ts.map +1 -0
- package/dist/{markdown → internal/markdown}/parse.js +112 -22
- package/dist/internal/markdown/parse.js.map +1 -0
- package/dist/internal/markdown/stringify.d.ts +4 -0
- package/dist/internal/markdown/stringify.d.ts.map +1 -0
- package/dist/internal/markdown/stringify.js +684 -0
- package/dist/internal/markdown/stringify.js.map +1 -0
- package/dist/internal/markdown/tags.d.ts.map +1 -0
- package/dist/internal/markdown/tags.js.map +1 -0
- package/dist/internal/scroll.d.ts +3 -2
- package/dist/internal/scroll.d.ts.map +1 -1
- package/dist/internal/scroll.js +33 -15
- package/dist/internal/scroll.js.map +1 -1
- package/dist/internal/units.d.ts +2 -0
- package/dist/internal/units.d.ts.map +1 -0
- package/dist/internal/units.js +35 -0
- package/dist/internal/units.js.map +1 -0
- package/dist/internal/window.d.ts.map +1 -1
- package/dist/internal/window.js +4 -2
- package/dist/internal/window.js.map +1 -1
- package/dist/maps/anchors.d.ts +98 -0
- package/dist/maps/anchors.d.ts.map +1 -0
- package/dist/maps/anchors.js +531 -0
- package/dist/maps/anchors.js.map +1 -0
- package/dist/maps/controller.d.ts +283 -0
- package/dist/maps/controller.d.ts.map +1 -0
- package/dist/maps/controller.js +743 -0
- package/dist/maps/controller.js.map +1 -0
- package/dist/maps/gl/buckets.d.ts +95 -0
- package/dist/maps/gl/buckets.d.ts.map +1 -0
- package/dist/maps/gl/buckets.js +362 -0
- package/dist/maps/gl/buckets.js.map +1 -0
- package/dist/maps/gl/build-worker.d.ts +13 -0
- package/dist/maps/gl/build-worker.d.ts.map +1 -0
- package/dist/maps/gl/build-worker.js +44 -0
- package/dist/maps/gl/build-worker.js.map +1 -0
- package/dist/maps/gl/color.d.ts +8 -0
- package/dist/maps/gl/color.d.ts.map +1 -0
- package/dist/maps/gl/color.js +57 -0
- package/dist/maps/gl/color.js.map +1 -0
- package/dist/maps/gl/cover.d.ts +51 -0
- package/dist/maps/gl/cover.d.ts.map +1 -0
- package/dist/maps/gl/cover.js +124 -0
- package/dist/maps/gl/cover.js.map +1 -0
- package/dist/maps/gl/index.d.ts +3 -0
- package/dist/maps/gl/index.d.ts.map +1 -0
- package/dist/maps/gl/index.js +20 -0
- package/dist/maps/gl/index.js.map +1 -0
- package/dist/maps/gl/markers.d.ts +28 -0
- package/dist/maps/gl/markers.d.ts.map +1 -0
- package/dist/maps/gl/markers.js +60 -0
- package/dist/maps/gl/markers.js.map +1 -0
- package/dist/maps/gl/overlays.d.ts +68 -0
- package/dist/maps/gl/overlays.d.ts.map +1 -0
- package/dist/maps/gl/overlays.js +300 -0
- package/dist/maps/gl/overlays.js.map +1 -0
- package/dist/maps/gl/pane.d.ts +12 -0
- package/dist/maps/gl/pane.d.ts.map +1 -0
- package/dist/maps/gl/pane.js +37 -0
- package/dist/maps/gl/pane.js.map +1 -0
- package/dist/maps/gl/placement.d.ts +111 -0
- package/dist/maps/gl/placement.d.ts.map +1 -0
- package/dist/maps/gl/placement.js +567 -0
- package/dist/maps/gl/placement.js.map +1 -0
- package/dist/maps/gl/renderer.d.ts +352 -0
- package/dist/maps/gl/renderer.d.ts.map +1 -0
- package/dist/maps/gl/renderer.js +1127 -0
- package/dist/maps/gl/renderer.js.map +1 -0
- package/dist/maps/gl/shaders.d.ts +120 -0
- package/dist/maps/gl/shaders.d.ts.map +1 -0
- package/dist/maps/gl/shaders.js +451 -0
- package/dist/maps/gl/shaders.js.map +1 -0
- package/dist/maps/gl/store.d.ts +115 -0
- package/dist/maps/gl/store.d.ts.map +1 -0
- package/dist/maps/gl/store.js +469 -0
- package/dist/maps/gl/store.js.map +1 -0
- package/dist/maps/gl/text.d.ts +154 -0
- package/dist/maps/gl/text.d.ts.map +1 -0
- package/dist/maps/gl/text.js +382 -0
- package/dist/maps/gl/text.js.map +1 -0
- package/dist/maps/gl/view.d.ts +54 -0
- package/dist/maps/gl/view.d.ts.map +1 -0
- package/dist/maps/gl/view.js +974 -0
- package/dist/maps/gl/view.js.map +1 -0
- package/dist/maps/index.d.ts +1 -1
- package/dist/maps/index.d.ts.map +1 -1
- package/dist/maps/index.js +216 -52
- package/dist/maps/index.js.map +1 -1
- package/dist/maps/labels.d.ts +37 -16
- package/dist/maps/labels.d.ts.map +1 -1
- package/dist/maps/labels.js +188 -119
- package/dist/maps/labels.js.map +1 -1
- package/dist/maps/node.d.ts +162 -56
- package/dist/maps/node.d.ts.map +1 -1
- package/dist/maps/node.js +607 -515
- package/dist/maps/node.js.map +1 -1
- package/dist/maps/overlay.d.ts +72 -9
- package/dist/maps/overlay.d.ts.map +1 -1
- package/dist/maps/overlay.js +108 -28
- package/dist/maps/overlay.js.map +1 -1
- package/dist/maps/paint.d.ts +3 -0
- package/dist/maps/paint.d.ts.map +1 -1
- package/dist/maps/paint.js.map +1 -1
- package/dist/maps/proj.d.ts +14 -0
- package/dist/maps/proj.d.ts.map +1 -1
- package/dist/maps/proj.js +28 -0
- package/dist/maps/proj.js.map +1 -1
- package/dist/maps/renderer.d.ts +69 -0
- package/dist/maps/renderer.d.ts.map +1 -0
- package/dist/maps/renderer.js +66 -0
- package/dist/maps/renderer.js.map +1 -0
- package/dist/maps/sources.d.ts +25 -3
- package/dist/maps/sources.d.ts.map +1 -1
- package/dist/maps/sources.js +17 -0
- package/dist/maps/sources.js.map +1 -1
- package/dist/maps/theme.d.ts +19 -0
- package/dist/maps/theme.d.ts.map +1 -0
- package/dist/maps/theme.js +44 -0
- package/dist/maps/theme.js.map +1 -0
- package/dist/maps/tiles.d.ts +117 -17
- package/dist/maps/tiles.d.ts.map +1 -1
- package/dist/maps/tiles.js +163 -30
- package/dist/maps/tiles.js.map +1 -1
- package/dist/maps/types.d.ts +188 -20
- package/dist/maps/types.d.ts.map +1 -1
- package/dist/markdown/expressions.d.ts +1 -1
- package/dist/markdown/expressions.d.ts.map +1 -1
- package/dist/markdown/expressions.js.map +1 -1
- package/dist/markdown/index.d.ts +7 -6
- package/dist/markdown/index.d.ts.map +1 -1
- package/dist/markdown/index.js +9 -7
- package/dist/markdown/index.js.map +1 -1
- package/dist/markdown/spans.d.ts +1 -1
- package/dist/markdown/spans.d.ts.map +1 -1
- package/dist/media-player/index.d.ts +11 -3
- package/dist/media-player/index.d.ts.map +1 -1
- package/dist/media-player/index.js +13 -2
- package/dist/media-player/index.js.map +1 -1
- package/dist/rich-text-editor/clipboard.d.ts +23 -0
- package/dist/rich-text-editor/clipboard.d.ts.map +1 -0
- package/dist/rich-text-editor/clipboard.js +307 -0
- package/dist/rich-text-editor/clipboard.js.map +1 -0
- package/dist/rich-text-editor/collab.d.ts +32 -0
- package/dist/rich-text-editor/collab.d.ts.map +1 -0
- package/dist/rich-text-editor/collab.js +55 -0
- package/dist/rich-text-editor/collab.js.map +1 -0
- package/dist/rich-text-editor/commands.d.ts +50 -0
- package/dist/rich-text-editor/commands.d.ts.map +1 -0
- package/dist/rich-text-editor/commands.js +376 -0
- package/dist/rich-text-editor/commands.js.map +1 -0
- package/dist/rich-text-editor/drag.d.ts +68 -0
- package/dist/rich-text-editor/drag.d.ts.map +1 -0
- package/dist/rich-text-editor/drag.js +148 -0
- package/dist/rich-text-editor/drag.js.map +1 -0
- package/dist/rich-text-editor/html.d.ts +145 -0
- package/dist/rich-text-editor/html.d.ts.map +1 -0
- package/dist/rich-text-editor/html.js +516 -0
- package/dist/rich-text-editor/html.js.map +1 -0
- package/dist/rich-text-editor/index.d.ts +263 -0
- package/dist/rich-text-editor/index.d.ts.map +1 -0
- package/dist/rich-text-editor/index.js +822 -0
- package/dist/rich-text-editor/index.js.map +1 -0
- package/dist/rich-text-editor/inline.d.ts +94 -0
- package/dist/rich-text-editor/inline.d.ts.map +1 -0
- package/dist/rich-text-editor/inline.js +252 -0
- package/dist/rich-text-editor/inline.js.map +1 -0
- package/dist/rich-text-editor/keymap.d.ts +71 -0
- package/dist/rich-text-editor/keymap.d.ts.map +1 -0
- package/dist/rich-text-editor/keymap.js +393 -0
- package/dist/rich-text-editor/keymap.js.map +1 -0
- package/dist/rich-text-editor/keys.d.ts +29 -0
- package/dist/rich-text-editor/keys.d.ts.map +1 -0
- package/dist/rich-text-editor/keys.js +93 -0
- package/dist/rich-text-editor/keys.js.map +1 -0
- package/dist/rich-text-editor/look.d.ts +71 -0
- package/dist/rich-text-editor/look.d.ts.map +1 -0
- package/dist/rich-text-editor/look.js +342 -0
- package/dist/rich-text-editor/look.js.map +1 -0
- package/dist/rich-text-editor/markdown.d.ts +18 -0
- package/dist/rich-text-editor/markdown.d.ts.map +1 -0
- package/dist/rich-text-editor/markdown.js +496 -0
- package/dist/rich-text-editor/markdown.js.map +1 -0
- package/dist/rich-text-editor/nodes.d.ts +135 -0
- package/dist/rich-text-editor/nodes.d.ts.map +1 -0
- package/dist/rich-text-editor/nodes.js +397 -0
- package/dist/rich-text-editor/nodes.js.map +1 -0
- package/dist/rich-text-editor/render.d.ts +68 -0
- package/dist/rich-text-editor/render.d.ts.map +1 -0
- package/dist/rich-text-editor/render.js +500 -0
- package/dist/rich-text-editor/render.js.map +1 -0
- package/dist/rich-text-editor/schema.d.ts +16 -0
- package/dist/rich-text-editor/schema.d.ts.map +1 -0
- package/dist/rich-text-editor/schema.js +386 -0
- package/dist/rich-text-editor/schema.js.map +1 -0
- package/dist/rich-text-editor/store.d.ts +11 -0
- package/dist/rich-text-editor/store.d.ts.map +1 -0
- package/dist/rich-text-editor/store.js +43 -0
- package/dist/rich-text-editor/store.js.map +1 -0
- package/dist/rich-text-editor/suggest.d.ts +127 -0
- package/dist/rich-text-editor/suggest.d.ts.map +1 -0
- package/dist/rich-text-editor/suggest.js +0 -0
- package/dist/rich-text-editor/suggest.js.map +1 -0
- package/dist/rich-text-editor/tables.d.ts +41 -0
- package/dist/rich-text-editor/tables.d.ts.map +1 -0
- package/dist/rich-text-editor/tables.js +204 -0
- package/dist/rich-text-editor/tables.js.map +1 -0
- package/dist/rich-text-editor/toolbar.d.ts +58 -0
- package/dist/rich-text-editor/toolbar.d.ts.map +1 -0
- package/dist/rich-text-editor/toolbar.js +548 -0
- package/dist/rich-text-editor/toolbar.js.map +1 -0
- package/dist/rich-text-editor/view.d.ts +368 -0
- package/dist/rich-text-editor/view.d.ts.map +1 -0
- package/dist/rich-text-editor/view.js +1968 -0
- package/dist/rich-text-editor/view.js.map +1 -0
- package/dist/rich-text-editor/virtual.d.ts +54 -0
- package/dist/rich-text-editor/virtual.d.ts.map +1 -0
- package/dist/rich-text-editor/virtual.js +262 -0
- package/dist/rich-text-editor/virtual.js.map +1 -0
- package/dist/richtext/node.d.ts +25 -2
- package/dist/richtext/node.d.ts.map +1 -1
- package/dist/richtext/node.js +46 -24
- package/dist/richtext/node.js.map +1 -1
- package/dist/table/index.d.ts.map +1 -1
- package/dist/table/index.js +14 -5
- package/dist/table/index.js.map +1 -1
- package/dist/terminal/backends.d.ts +4 -1
- package/dist/terminal/backends.d.ts.map +1 -1
- package/dist/terminal/backends.js.map +1 -1
- package/dist/terminal/index.d.ts +18 -7
- package/dist/terminal/index.d.ts.map +1 -1
- package/dist/terminal/index.js +28 -11
- package/dist/terminal/index.js.map +1 -1
- package/dist/terminal/vt/node.d.ts +56 -3
- package/dist/terminal/vt/node.d.ts.map +1 -1
- package/dist/terminal/vt/node.js +106 -26
- package/dist/terminal/vt/node.js.map +1 -1
- package/dist/terminal/vt/renderer.d.ts +4 -0
- package/dist/terminal/vt/renderer.d.ts.map +1 -1
- package/dist/terminal/vt/renderer.js +14 -5
- package/dist/terminal/vt/renderer.js.map +1 -1
- package/dist/tree/index.d.ts.map +1 -1
- package/dist/tree/index.js +6 -4
- package/dist/tree/index.js.map +1 -1
- package/package.json +23 -4
- package/src/charts/node.ts +6 -2
- package/src/color-picker/ColorPicker.ts +107 -81
- package/src/embed/client.ts +102 -8
- package/src/embed/index.ts +2 -0
- package/src/html/node.ts +7 -5
- package/src/index.ts +2 -0
- package/src/{markdown → internal/markdown}/ast.ts +24 -7
- package/src/{markdown → internal/markdown}/parse.ts +134 -24
- package/src/internal/markdown/stringify.ts +772 -0
- package/src/internal/scroll.ts +39 -18
- package/src/internal/units.ts +34 -0
- package/src/internal/window.ts +4 -2
- package/src/maps/anchors.ts +607 -0
- package/src/maps/controller.ts +999 -0
- package/src/maps/gl/buckets.ts +457 -0
- package/src/maps/gl/build-worker.ts +55 -0
- package/src/maps/gl/color.ts +62 -0
- package/src/maps/gl/cover.ts +170 -0
- package/src/maps/gl/index.ts +23 -0
- package/src/maps/gl/markers.ts +83 -0
- package/src/maps/gl/overlays.ts +379 -0
- package/src/maps/gl/pane.ts +42 -0
- package/src/maps/gl/placement.ts +816 -0
- package/src/maps/gl/renderer.ts +1576 -0
- package/src/maps/gl/shaders.ts +486 -0
- package/src/maps/gl/store.ts +566 -0
- package/src/maps/gl/text.ts +495 -0
- package/src/maps/gl/view.ts +1224 -0
- package/src/maps/index.ts +270 -81
- package/src/maps/labels.ts +214 -121
- package/src/maps/node.ts +763 -629
- package/src/maps/overlay.ts +148 -43
- package/src/maps/paint.ts +3 -0
- package/src/maps/proj.ts +31 -0
- package/src/maps/renderer.ts +155 -0
- package/src/maps/sources.ts +38 -3
- package/src/maps/theme.ts +51 -0
- package/src/maps/tiles.ts +191 -33
- package/src/maps/types.ts +188 -20
- package/src/markdown/expressions.ts +5 -1
- package/src/markdown/index.ts +14 -11
- package/src/markdown/spans.ts +1 -1
- package/src/media-player/index.ts +18 -4
- package/src/rich-text-editor/clipboard.ts +439 -0
- package/src/rich-text-editor/collab.ts +91 -0
- package/src/rich-text-editor/commands.ts +448 -0
- package/src/rich-text-editor/dom-globals.d.ts +23 -0
- package/src/rich-text-editor/drag.ts +211 -0
- package/src/rich-text-editor/html.ts +619 -0
- package/src/rich-text-editor/index.ts +1331 -0
- package/src/rich-text-editor/inline.ts +345 -0
- package/src/rich-text-editor/keymap.ts +510 -0
- package/src/rich-text-editor/keys.ts +119 -0
- package/src/rich-text-editor/look.ts +427 -0
- package/src/rich-text-editor/markdown.ts +653 -0
- package/src/rich-text-editor/nodes.ts +497 -0
- package/src/rich-text-editor/render.ts +800 -0
- package/src/rich-text-editor/schema.ts +431 -0
- package/src/rich-text-editor/store.ts +39 -0
- package/src/rich-text-editor/suggest.ts +597 -0
- package/src/rich-text-editor/tables.ts +272 -0
- package/src/rich-text-editor/toolbar.ts +724 -0
- package/src/rich-text-editor/view.ts +2287 -0
- package/src/rich-text-editor/virtual.ts +347 -0
- package/src/richtext/node.ts +59 -30
- package/src/table/index.ts +14 -5
- package/src/terminal/backends.ts +4 -1
- package/src/terminal/index.ts +43 -15
- package/src/terminal/vt/node.ts +128 -29
- package/src/terminal/vt/renderer.ts +27 -5
- package/src/tree/index.ts +6 -4
- package/dist/markdown/ast.d.ts.map +0 -1
- package/dist/markdown/ast.js +0 -17
- package/dist/markdown/ast.js.map +0 -1
- package/dist/markdown/parse.d.ts.map +0 -1
- package/dist/markdown/parse.js.map +0 -1
- package/dist/markdown/tags.d.ts.map +0 -1
- package/dist/markdown/tags.js.map +0 -1
- /package/dist/{markdown → internal/markdown}/parse.d.ts +0 -0
- /package/dist/{markdown → internal/markdown}/tags.d.ts +0 -0
- /package/dist/{markdown → internal/markdown}/tags.js +0 -0
- /package/src/{markdown → internal/markdown}/tags.ts +0 -0
|
@@ -0,0 +1,1127 @@
|
|
|
1
|
+
import { resolveZoomed } from '../style.js';
|
|
2
|
+
import { RECORD_BYTES, TILE_EXTENT } from './buckets.js';
|
|
3
|
+
import { parseColor, premultiplied } from './color.js';
|
|
4
|
+
import { MARKER_INSTANCE } from './markers.js';
|
|
5
|
+
import { LABEL_INSTANCE } from './placement.js';
|
|
6
|
+
import { ATTRIBUTES, BLIT_FRAGMENT, BLIT_VERTEX, CIRCLE_FRAGMENT, CIRCLE_VERTEX, COVER_FRAGMENT, COVER_VERTEX, FAN_FRAGMENT, FAN_VERTEX, LABEL_FRAGMENT, LABEL_VERTEX, LINE_FRAGMENT, LINE_VERTEX, MARKER_FRAGMENT, MARKER_VERTEX, RASTER_FRAGMENT, RASTER_VERTEX, linkProgram, } from './shaders.js';
|
|
7
|
+
import { quadInset } from './text.js';
|
|
8
|
+
/** Entries of a dash pattern the line program holds: four dashes. */
|
|
9
|
+
const DASH_ENTRIES = 8;
|
|
10
|
+
/**
|
|
11
|
+
* A style's or an overlay's dash array as the line program draws it: in
|
|
12
|
+
* device pixels, and read as a canvas reads one — an odd number of entries
|
|
13
|
+
* is repeated to make an even one, and an entry that is negative or not a
|
|
14
|
+
* number, or entries that are all zero, make a solid line. A pattern of
|
|
15
|
+
* more than four dashes is cut to its first four.
|
|
16
|
+
*/
|
|
17
|
+
export function dashPattern(dash, scale) {
|
|
18
|
+
if (!dash || dash.length === 0)
|
|
19
|
+
return null;
|
|
20
|
+
if (dash.some((d) => !Number.isFinite(d) || d < 0))
|
|
21
|
+
return null;
|
|
22
|
+
if (!dash.some((d) => d > 0))
|
|
23
|
+
return null;
|
|
24
|
+
const even = dash.length % 2 === 1 ? [...dash, ...dash] : dash;
|
|
25
|
+
return even.slice(0, DASH_ENTRIES).map((d) => d * scale);
|
|
26
|
+
}
|
|
27
|
+
const CLEAR = [0, 0, 0, 0];
|
|
28
|
+
const globals = globalThis;
|
|
29
|
+
const now = () => globals.performance?.now() ?? Date.now();
|
|
30
|
+
/** Whether a style layer draws at this zoom, with `detail` levels of its
|
|
31
|
+
* newest layers left out — `paint.ts`'s gate, plus adaptive quality's. */
|
|
32
|
+
function drawsAt(layer, zoom, detail) {
|
|
33
|
+
if (layer.visible === false)
|
|
34
|
+
return false;
|
|
35
|
+
if (layer.type !== 'fill' &&
|
|
36
|
+
layer.type !== 'line' &&
|
|
37
|
+
layer.type !== 'circle') {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
if (layer.minZoom !== undefined && zoom < layer.minZoom)
|
|
41
|
+
return false;
|
|
42
|
+
if (layer.maxZoom !== undefined && zoom >= layer.maxZoom)
|
|
43
|
+
return false;
|
|
44
|
+
if (detail > 0 &&
|
|
45
|
+
layer.minZoom !== undefined &&
|
|
46
|
+
layer.minZoom > zoom - detail) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
/** Label rasters taken into the atlas texture per frame at most. */
|
|
52
|
+
const ATLAS_UPLOADS_PER_FRAME = 24;
|
|
53
|
+
/** GL's own name for "which VAO is bound", which the table may not carry. */
|
|
54
|
+
const VERTEX_ARRAY_BINDING = 0x85b5;
|
|
55
|
+
const FRAMEBUFFER_BINDING = 0x8ca6;
|
|
56
|
+
export class GlMapRenderer {
|
|
57
|
+
_gl;
|
|
58
|
+
_options;
|
|
59
|
+
_line;
|
|
60
|
+
_fan;
|
|
61
|
+
_cover;
|
|
62
|
+
_blit = null;
|
|
63
|
+
_corners;
|
|
64
|
+
_coverVao;
|
|
65
|
+
_tiles = new Map();
|
|
66
|
+
_colors = new Map();
|
|
67
|
+
_stencil = null;
|
|
68
|
+
/** Whether the table sets each facing's stencil operation apart, which
|
|
69
|
+
* makes the non-zero winding one pass. */
|
|
70
|
+
_separate;
|
|
71
|
+
/** Offscreen targets by role: `'output'` stands in for a surface with no
|
|
72
|
+
* stencil buffer, `'fade'` holds the scene being faded in. */
|
|
73
|
+
_targets = new Map();
|
|
74
|
+
/** The label pass: its program, its instance buffer and the vertex array
|
|
75
|
+
* over it, and the atlas texture with the atlas generation it holds. */
|
|
76
|
+
_label = null;
|
|
77
|
+
_labelVao = null;
|
|
78
|
+
_labelBuffer = null;
|
|
79
|
+
_marker = null;
|
|
80
|
+
_markerVao = null;
|
|
81
|
+
_markerBuffer = null;
|
|
82
|
+
_circle = null;
|
|
83
|
+
_raster = null;
|
|
84
|
+
_atlasTexture = null;
|
|
85
|
+
_atlasOwner = null;
|
|
86
|
+
_stats = GlMapRenderer._emptyStats();
|
|
87
|
+
/** Bytes of tile geometry resident on the GPU. */
|
|
88
|
+
gpuBytes = 0;
|
|
89
|
+
constructor(gl, options = {}) {
|
|
90
|
+
this._gl = gl;
|
|
91
|
+
this._options = {
|
|
92
|
+
antialias: options.antialias ?? true,
|
|
93
|
+
fillRule: options.fillRule ?? 'nonzero',
|
|
94
|
+
offscreen: options.offscreen ?? 'auto',
|
|
95
|
+
};
|
|
96
|
+
// Asked of the table itself: an x11-dri from before 0.8 has no such
|
|
97
|
+
// entry at all, and an entry that is there works.
|
|
98
|
+
this._separate =
|
|
99
|
+
'stencilOpSeparate' in gl &&
|
|
100
|
+
typeof gl.stencilOpSeparate === 'function';
|
|
101
|
+
this._line = linkProgram(gl, LINE_VERTEX, LINE_FRAGMENT, [
|
|
102
|
+
'u_tile',
|
|
103
|
+
'u_viewport',
|
|
104
|
+
'u_half',
|
|
105
|
+
'u_color',
|
|
106
|
+
'u_dash_a',
|
|
107
|
+
'u_dash_b',
|
|
108
|
+
'u_period',
|
|
109
|
+
'u_part',
|
|
110
|
+
]);
|
|
111
|
+
this._fan = linkProgram(gl, FAN_VERTEX, FAN_FRAGMENT, [
|
|
112
|
+
'u_tile',
|
|
113
|
+
'u_viewport',
|
|
114
|
+
]);
|
|
115
|
+
this._cover = linkProgram(gl, COVER_VERTEX, COVER_FRAGMENT, ['u_color']);
|
|
116
|
+
const previous = gl.getParameter(VERTEX_ARRAY_BINDING);
|
|
117
|
+
// The quad every instance is drawn over. A capsule is a four-vertex
|
|
118
|
+
// strip; a fan triangle takes the first three corners.
|
|
119
|
+
this._corners = gl.createBuffer();
|
|
120
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this._corners);
|
|
121
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, -1, 1, -1, 0, 1, 1, 1]), gl.STATIC_DRAW);
|
|
122
|
+
this._coverVao = gl.createVertexArray();
|
|
123
|
+
gl.bindVertexArray(this._coverVao);
|
|
124
|
+
gl.enableVertexAttribArray(ATTRIBUTES.a_corner);
|
|
125
|
+
gl.vertexAttribPointer(ATTRIBUTES.a_corner, 2, gl.FLOAT, false, 0, 0);
|
|
126
|
+
gl.bindVertexArray(previous);
|
|
127
|
+
}
|
|
128
|
+
set antialias(on) {
|
|
129
|
+
this._options.antialias = on;
|
|
130
|
+
}
|
|
131
|
+
set fillRule(rule) {
|
|
132
|
+
this._options.fillRule = rule;
|
|
133
|
+
}
|
|
134
|
+
static _emptyStats() {
|
|
135
|
+
return {
|
|
136
|
+
tiles: 0,
|
|
137
|
+
layers: 0,
|
|
138
|
+
drawCalls: 0,
|
|
139
|
+
instances: 0,
|
|
140
|
+
uploads: 0,
|
|
141
|
+
uploadBytes: 0,
|
|
142
|
+
cpuMs: 0,
|
|
143
|
+
offscreen: false,
|
|
144
|
+
fade: 0,
|
|
145
|
+
labels: 0,
|
|
146
|
+
markers: 0,
|
|
147
|
+
overlays: 0,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* What a frame would cost, in the unit `stats.instances` counts — segment
|
|
152
|
+
* instances, every pass included — without drawing it. The same gates the
|
|
153
|
+
* frame itself applies, over the ranges' record counts, so it is a walk of
|
|
154
|
+
* a few hundred numbers: cheap enough to price several versions of the
|
|
155
|
+
* next frame and draw the one that fits.
|
|
156
|
+
*/
|
|
157
|
+
estimate(frame) {
|
|
158
|
+
const { width, height } = frame;
|
|
159
|
+
const layers = frame.style.layers;
|
|
160
|
+
const detail = frame.detail ?? 0;
|
|
161
|
+
const edges = frame.edges ?? this._options.antialias;
|
|
162
|
+
const stencil = this._options.fillRule === 'nonzero' && !this._separate ? 2 : 1;
|
|
163
|
+
let work = 0;
|
|
164
|
+
for (const tiles of frame.sources) {
|
|
165
|
+
const visible = tiles.map((t) => scissorOf(t.clip, width, height) !== null);
|
|
166
|
+
for (let i = 0; i < layers.length; i++) {
|
|
167
|
+
const layer = layers[i].layer;
|
|
168
|
+
if (!drawsAt(layer, frame.zoom, detail))
|
|
169
|
+
continue;
|
|
170
|
+
let records = 0;
|
|
171
|
+
for (let t = 0; t < tiles.length; t++) {
|
|
172
|
+
if (!visible[t])
|
|
173
|
+
continue;
|
|
174
|
+
const draw = tiles[t].data.draws[i];
|
|
175
|
+
if (!draw)
|
|
176
|
+
continue;
|
|
177
|
+
for (let r = 1; r < draw.ranges.length; r += 2) {
|
|
178
|
+
records += draw.ranges[r] - 1;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (layer.type === 'fill') {
|
|
182
|
+
const edge = layer.outlineColor !== undefined || edges ? 1 : 0;
|
|
183
|
+
work += records * (stencil + edge);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
work += records;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return work;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Draw a frame into whatever framebuffer is bound — and, given `fade`, a
|
|
194
|
+
* second scene over it at `fade.alpha`, and given `labels`, the labels
|
|
195
|
+
* over both.
|
|
196
|
+
*
|
|
197
|
+
* The second scene is drawn whole into an offscreen target and composited
|
|
198
|
+
* once, rather than layer by layer at reduced opacity: a map faded layer
|
|
199
|
+
* by layer shows its casings through its fills and its water through its
|
|
200
|
+
* land. Both scenes are opaque, so the composite is exactly a cross-fade.
|
|
201
|
+
* Labels go on after it, at full strength: they are placed for the view,
|
|
202
|
+
* not for either level, and a name that dimmed with the level it came
|
|
203
|
+
* from would blink at every level change.
|
|
204
|
+
*/
|
|
205
|
+
render(frame, extras = {}) {
|
|
206
|
+
const { fade, labels, overlays, markers, attribution } = extras;
|
|
207
|
+
const gl = this._gl;
|
|
208
|
+
const started = now();
|
|
209
|
+
const stats = (this._stats = GlMapRenderer._emptyStats());
|
|
210
|
+
const previousVao = gl.getParameter(VERTEX_ARRAY_BINDING);
|
|
211
|
+
const surface = gl.getParameter(FRAMEBUFFER_BINDING);
|
|
212
|
+
const { width, height } = frame;
|
|
213
|
+
// Before the probe: it draws, and a context that has never had a
|
|
214
|
+
// viewport set — a windowless one, bound to a framebuffer — has a 0×0
|
|
215
|
+
// one, which draws nothing and reads as "no stencil".
|
|
216
|
+
gl.viewport(0, 0, width, height);
|
|
217
|
+
if (this._stencil === null)
|
|
218
|
+
this._stencil = this._probeStencil();
|
|
219
|
+
const mode = this._options.offscreen;
|
|
220
|
+
const offscreen = mode === true || (mode === 'auto' && !this._stencil);
|
|
221
|
+
stats.offscreen = offscreen;
|
|
222
|
+
const output = offscreen ? this._target('output', width, height) : null;
|
|
223
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, output ? output.fbo : surface);
|
|
224
|
+
this._scene(frame);
|
|
225
|
+
if (fade && fade.alpha > 0) {
|
|
226
|
+
const layer = this._target('fade', width, height);
|
|
227
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, layer.fbo);
|
|
228
|
+
this._scene(fade.frame);
|
|
229
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, output ? output.fbo : surface);
|
|
230
|
+
this._composite(layer, width, height, Math.min(1, fade.alpha));
|
|
231
|
+
stats.fade = Math.min(1, fade.alpha);
|
|
232
|
+
}
|
|
233
|
+
// Even with nothing to draw: the batch's atlas may have rasters waiting
|
|
234
|
+
// for the texture, and until they are in it nothing of theirs can be.
|
|
235
|
+
if (labels)
|
|
236
|
+
stats.labels = this._labels(labels, width, height);
|
|
237
|
+
if (overlays)
|
|
238
|
+
stats.overlays = this._overlays(overlays, frame);
|
|
239
|
+
if (markers)
|
|
240
|
+
stats.markers = this._markers(markers, width, height);
|
|
241
|
+
// Last, over the labels and everything else, which is where the
|
|
242
|
+
// retained renderer draws it: a licence condition is not something a
|
|
243
|
+
// street name may cover.
|
|
244
|
+
if (attribution)
|
|
245
|
+
this._attribution(attribution, width, height);
|
|
246
|
+
if (output) {
|
|
247
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, surface);
|
|
248
|
+
this._composite(output, width, height, 1);
|
|
249
|
+
}
|
|
250
|
+
gl.bindVertexArray(previousVao);
|
|
251
|
+
stats.cpuMs = now() - started;
|
|
252
|
+
return stats;
|
|
253
|
+
}
|
|
254
|
+
/** Drop one tile's buffers. */
|
|
255
|
+
release(data) {
|
|
256
|
+
const gpu = this._tiles.get(data);
|
|
257
|
+
if (!gpu)
|
|
258
|
+
return;
|
|
259
|
+
const gl = this._gl;
|
|
260
|
+
for (const vao of gpu.vaos.values())
|
|
261
|
+
gl.deleteVertexArray(vao);
|
|
262
|
+
if (gpu.line)
|
|
263
|
+
gl.deleteBuffer(gpu.line);
|
|
264
|
+
if (gpu.fill)
|
|
265
|
+
gl.deleteBuffer(gpu.fill);
|
|
266
|
+
if (gpu.texture)
|
|
267
|
+
gl.deleteTexture(gpu.texture);
|
|
268
|
+
this.gpuBytes -= gpu.bytes;
|
|
269
|
+
this._tiles.delete(data);
|
|
270
|
+
}
|
|
271
|
+
/** Whether a tile's buffers are resident. */
|
|
272
|
+
resident(data) {
|
|
273
|
+
return this._tiles.has(data);
|
|
274
|
+
}
|
|
275
|
+
dispose() {
|
|
276
|
+
const gl = this._gl;
|
|
277
|
+
for (const data of [...this._tiles.keys()])
|
|
278
|
+
this.release(data);
|
|
279
|
+
for (const program of [
|
|
280
|
+
this._line,
|
|
281
|
+
this._fan,
|
|
282
|
+
this._cover,
|
|
283
|
+
this._blit,
|
|
284
|
+
this._label,
|
|
285
|
+
this._marker,
|
|
286
|
+
this._circle,
|
|
287
|
+
this._raster,
|
|
288
|
+
]) {
|
|
289
|
+
if (program)
|
|
290
|
+
gl.deleteProgram(program.program);
|
|
291
|
+
}
|
|
292
|
+
gl.deleteBuffer(this._corners);
|
|
293
|
+
gl.deleteVertexArray(this._coverVao);
|
|
294
|
+
if (this._labelVao)
|
|
295
|
+
gl.deleteVertexArray(this._labelVao);
|
|
296
|
+
if (this._labelBuffer)
|
|
297
|
+
gl.deleteBuffer(this._labelBuffer);
|
|
298
|
+
if (this._markerVao)
|
|
299
|
+
gl.deleteVertexArray(this._markerVao);
|
|
300
|
+
if (this._markerBuffer)
|
|
301
|
+
gl.deleteBuffer(this._markerBuffer);
|
|
302
|
+
if (this._atlasTexture)
|
|
303
|
+
gl.deleteTexture(this._atlasTexture);
|
|
304
|
+
for (const target of this._targets.values())
|
|
305
|
+
this._dropTarget(target);
|
|
306
|
+
this._targets.clear();
|
|
307
|
+
}
|
|
308
|
+
// --- the scene ------------------------------------------------------------
|
|
309
|
+
/** The whole style, into the bound framebuffer, from a clear. */
|
|
310
|
+
_scene(frame) {
|
|
311
|
+
const gl = this._gl;
|
|
312
|
+
const stats = this._stats;
|
|
313
|
+
const { width, height } = frame;
|
|
314
|
+
gl.viewport(0, 0, width, height);
|
|
315
|
+
const background = premultiplied(this._color(frame.background ?? '#ffffff') ?? [1, 1, 1, 1]);
|
|
316
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
317
|
+
gl.clearColor(background[0], background[1], background[2], background[3]);
|
|
318
|
+
gl.clearStencil(0);
|
|
319
|
+
gl.stencilMask(0xff);
|
|
320
|
+
gl.colorMask(true, true, true, true);
|
|
321
|
+
gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
|
|
322
|
+
gl.disable(gl.DEPTH_TEST);
|
|
323
|
+
gl.disable(gl.CULL_FACE);
|
|
324
|
+
gl.enable(gl.BLEND);
|
|
325
|
+
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
326
|
+
gl.enable(gl.SCISSOR_TEST);
|
|
327
|
+
for (const program of [this._line, this._fan]) {
|
|
328
|
+
gl.useProgram(program.program);
|
|
329
|
+
gl.uniform2f(program.uniforms.u_viewport, width, height);
|
|
330
|
+
}
|
|
331
|
+
const edges = frame.edges ?? this._options.antialias;
|
|
332
|
+
const detail = frame.detail ?? 0;
|
|
333
|
+
const layers = frame.style.layers;
|
|
334
|
+
for (const tiles of frame.sources) {
|
|
335
|
+
// Upload before drawing, so a frame's uploads are counted once and a
|
|
336
|
+
// tile arriving mid-frame is not half drawn.
|
|
337
|
+
const scissors = [];
|
|
338
|
+
for (const tile of tiles) {
|
|
339
|
+
this._gpu(tile.data);
|
|
340
|
+
scissors.push(scissorOf(tile.clip, width, height));
|
|
341
|
+
}
|
|
342
|
+
stats.tiles += tiles.length;
|
|
343
|
+
// A raster source is its images, and no style.
|
|
344
|
+
if (tiles.some((tile) => tile.data.raster)) {
|
|
345
|
+
this._rasters(tiles, scissors, width, height);
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
for (let i = 0; i < layers.length; i++) {
|
|
349
|
+
const layer = layers[i].layer;
|
|
350
|
+
if (!drawsAt(layer, frame.zoom, detail))
|
|
351
|
+
continue;
|
|
352
|
+
if (layer.type === 'fill') {
|
|
353
|
+
if (this._fill(frame, tiles, i, layer, scissors, edges)) {
|
|
354
|
+
stats.layers++;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
else if (layer.type === 'line') {
|
|
358
|
+
if (this._lines(frame, tiles, i, layer, scissors))
|
|
359
|
+
stats.layers++;
|
|
360
|
+
}
|
|
361
|
+
else if (layer.type === 'circle') {
|
|
362
|
+
if (this._circles(frame, tiles, i, layer, scissors))
|
|
363
|
+
stats.layers++;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
368
|
+
gl.disable(gl.STENCIL_TEST);
|
|
369
|
+
}
|
|
370
|
+
// --- layers ---------------------------------------------------------------
|
|
371
|
+
_fill(frame, tiles, index, layer, scissors, edges) {
|
|
372
|
+
const zoom = frame.zoom;
|
|
373
|
+
const opacity = layer.opacity === undefined ? 1 : resolveZoomed(layer.opacity, zoom);
|
|
374
|
+
if (opacity <= 0)
|
|
375
|
+
return false;
|
|
376
|
+
const base = this._color(resolveZoomed(layer.color, zoom));
|
|
377
|
+
if (!base)
|
|
378
|
+
return false;
|
|
379
|
+
const outline = layer.outlineColor === undefined
|
|
380
|
+
? undefined
|
|
381
|
+
: this._color(resolveZoomed(layer.outlineColor, zoom));
|
|
382
|
+
return this._fillWith(tiles, index, scissors, {
|
|
383
|
+
color: premultiplied(base, opacity),
|
|
384
|
+
// The edge: the outline colour a style asked for, at one logical
|
|
385
|
+
// pixel, or else the fill colour at half a device pixel — which is
|
|
386
|
+
// the antialiasing.
|
|
387
|
+
antialias: !outline && edges,
|
|
388
|
+
outline: outline ? premultiplied(outline, opacity) : null,
|
|
389
|
+
outlineHalf: Math.max(0.5, frame.scale / 2),
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* A polygon's ranges, over every tile that has them: the winding number
|
|
394
|
+
* into the stencil buffer, the colour wherever it is not zero, then the
|
|
395
|
+
* edge — the antialiasing, or an outline.
|
|
396
|
+
*/
|
|
397
|
+
_fillWith(tiles, index, scissors, paint) {
|
|
398
|
+
const gl = this._gl;
|
|
399
|
+
// The cover's extent: the union of the squares this layer drew in.
|
|
400
|
+
const box = this._unionOf(tiles, index, scissors);
|
|
401
|
+
if (!box)
|
|
402
|
+
return false;
|
|
403
|
+
const stats = this._stats;
|
|
404
|
+
// 1. The winding number, into the stencil buffer.
|
|
405
|
+
gl.useProgram(this._fan.program);
|
|
406
|
+
gl.colorMask(false, false, false, false);
|
|
407
|
+
gl.enable(gl.STENCIL_TEST);
|
|
408
|
+
gl.stencilFunc(gl.ALWAYS, 0, 0xff);
|
|
409
|
+
const fan = (count) => {
|
|
410
|
+
gl.drawArraysInstanced(gl.TRIANGLES, 0, 3, count);
|
|
411
|
+
};
|
|
412
|
+
const nonzero = this._options.fillRule === 'nonzero';
|
|
413
|
+
if (nonzero && this._separate) {
|
|
414
|
+
// Front faces count up and back faces down, in the one draw.
|
|
415
|
+
gl.disable(gl.CULL_FACE);
|
|
416
|
+
gl.stencilOpSeparate(gl.FRONT, gl.KEEP, gl.KEEP, gl.INCR_WRAP);
|
|
417
|
+
gl.stencilOpSeparate(gl.BACK, gl.KEEP, gl.KEEP, gl.DECR_WRAP);
|
|
418
|
+
this._eachTile(tiles, index, scissors, this._fan, true, fan);
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
const passes = nonzero
|
|
422
|
+
? [
|
|
423
|
+
[gl.BACK, gl.INCR_WRAP],
|
|
424
|
+
[gl.FRONT, gl.DECR_WRAP],
|
|
425
|
+
]
|
|
426
|
+
: [[null, gl.INVERT]];
|
|
427
|
+
for (const [cull, op] of passes) {
|
|
428
|
+
if (cull === null)
|
|
429
|
+
gl.disable(gl.CULL_FACE);
|
|
430
|
+
else {
|
|
431
|
+
gl.enable(gl.CULL_FACE);
|
|
432
|
+
gl.cullFace(cull);
|
|
433
|
+
}
|
|
434
|
+
gl.stencilOp(gl.KEEP, gl.KEEP, op);
|
|
435
|
+
this._eachTile(tiles, index, scissors, this._fan, true, fan);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
gl.disable(gl.CULL_FACE);
|
|
439
|
+
// 2. The colour, wherever the winding is not zero. An opaque fill sets
|
|
440
|
+
// the stencil back to zero as it goes, so the next layer starts clean;
|
|
441
|
+
// a translucent one keeps it for its edge, which is drawn outside the
|
|
442
|
+
// polygon only — laid over the fill's own colour it would be a darker
|
|
443
|
+
// rim round every translucent polygon — and clears it after.
|
|
444
|
+
const translucent = paint.color[3] < 1;
|
|
445
|
+
gl.colorMask(true, true, true, true);
|
|
446
|
+
gl.stencilFunc(gl.NOTEQUAL, 0, 0xff);
|
|
447
|
+
if (translucent)
|
|
448
|
+
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
|
|
449
|
+
else
|
|
450
|
+
gl.stencilOp(gl.ZERO, gl.ZERO, gl.ZERO);
|
|
451
|
+
gl.scissor(box[0], box[1], box[2], box[3]);
|
|
452
|
+
this._drawCover(paint.color);
|
|
453
|
+
stats.drawCalls++;
|
|
454
|
+
// 3. The edge, which is the fill stream drawn as segments.
|
|
455
|
+
if (translucent) {
|
|
456
|
+
if (paint.antialias) {
|
|
457
|
+
// Where the winding is zero and no edge has been yet, each pixel
|
|
458
|
+
// once, at what a pixel the polygon partly covers is covered by:
|
|
459
|
+
// a segment of no width, whose ramp is half a pixel either side.
|
|
460
|
+
gl.stencilFunc(gl.EQUAL, 0, 0xff);
|
|
461
|
+
gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR);
|
|
462
|
+
this._segments(tiles, index, scissors, { color: paint.color, half: 0, dash: null }, true, 0);
|
|
463
|
+
}
|
|
464
|
+
this._clearStencil(box);
|
|
465
|
+
gl.disable(gl.STENCIL_TEST);
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
gl.disable(gl.STENCIL_TEST);
|
|
469
|
+
if (paint.antialias) {
|
|
470
|
+
this._strokeWith(tiles, index, scissors, { color: paint.color, half: 0.5, dash: null }, true);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (paint.outline) {
|
|
474
|
+
this._strokeWith(tiles, index, scissors, { color: paint.outline, half: paint.outlineHalf, dash: null }, true);
|
|
475
|
+
}
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
_lines(frame, tiles, index, layer, scissors) {
|
|
479
|
+
const zoom = frame.zoom;
|
|
480
|
+
const logical = resolveZoomed(layer.width, zoom);
|
|
481
|
+
if (!(logical > 0))
|
|
482
|
+
return false;
|
|
483
|
+
const opacity = layer.opacity === undefined ? 1 : resolveZoomed(layer.opacity, zoom);
|
|
484
|
+
if (opacity <= 0)
|
|
485
|
+
return false;
|
|
486
|
+
const base = this._color(resolveZoomed(layer.color, zoom));
|
|
487
|
+
if (!base)
|
|
488
|
+
return false;
|
|
489
|
+
if (!this._unionOf(tiles, index, scissors))
|
|
490
|
+
return false;
|
|
491
|
+
// A road narrower than a pixel is drawn *at* a pixel, as paint.ts does:
|
|
492
|
+
// a motorway network that vanishes at zoom 6 is worse than a heavy one.
|
|
493
|
+
const width = Math.max(1, logical * frame.scale);
|
|
494
|
+
this._strokeWith(tiles, index, scissors, {
|
|
495
|
+
color: premultiplied(base, opacity),
|
|
496
|
+
half: width / 2,
|
|
497
|
+
dash: dashPattern(layer.dash, frame.scale),
|
|
498
|
+
}, false);
|
|
499
|
+
return true;
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* A circle layer: a disc per point, one instanced draw per tile — the
|
|
503
|
+
* radius, colours and stroke `paint.ts` reads, at this zoom.
|
|
504
|
+
*/
|
|
505
|
+
_circles(frame, tiles, index, layer, scissors) {
|
|
506
|
+
const zoom = frame.zoom;
|
|
507
|
+
const radius = (layer.radius === undefined ? 3 : resolveZoomed(layer.radius, zoom)) *
|
|
508
|
+
frame.scale;
|
|
509
|
+
if (!(radius > 0))
|
|
510
|
+
return false;
|
|
511
|
+
const opacity = layer.opacity === undefined ? 1 : resolveZoomed(layer.opacity, zoom);
|
|
512
|
+
if (opacity <= 0)
|
|
513
|
+
return false;
|
|
514
|
+
const base = this._color(resolveZoomed(layer.color, zoom));
|
|
515
|
+
if (!base)
|
|
516
|
+
return false;
|
|
517
|
+
if (!this._unionOf(tiles, index, scissors))
|
|
518
|
+
return false;
|
|
519
|
+
const ring = layer.strokeColor === undefined
|
|
520
|
+
? null
|
|
521
|
+
: this._color(resolveZoomed(layer.strokeColor, zoom));
|
|
522
|
+
const stroke = ring
|
|
523
|
+
? (layer.strokeWidth === undefined
|
|
524
|
+
? 1
|
|
525
|
+
: resolveZoomed(layer.strokeWidth, zoom)) * frame.scale
|
|
526
|
+
: 0;
|
|
527
|
+
const gl = this._gl;
|
|
528
|
+
const program = (this._circle ??= linkProgram(gl, CIRCLE_VERTEX, CIRCLE_FRAGMENT, [
|
|
529
|
+
'u_tile',
|
|
530
|
+
'u_viewport',
|
|
531
|
+
'u_radius',
|
|
532
|
+
'u_stroke',
|
|
533
|
+
'u_color',
|
|
534
|
+
'u_stroke_color',
|
|
535
|
+
]));
|
|
536
|
+
const u = program.uniforms;
|
|
537
|
+
const color = premultiplied(base, opacity);
|
|
538
|
+
const edge = ring ? premultiplied(ring, opacity) : CLEAR;
|
|
539
|
+
gl.useProgram(program.program);
|
|
540
|
+
gl.uniform2f(u.u_viewport, frame.width, frame.height);
|
|
541
|
+
gl.uniform1f(u.u_radius, radius);
|
|
542
|
+
gl.uniform1f(u.u_stroke, stroke);
|
|
543
|
+
gl.uniform4f(u.u_color, color[0], color[1], color[2], color[3]);
|
|
544
|
+
gl.uniform4f(u.u_stroke_color, edge[0], edge[1], edge[2], edge[3]);
|
|
545
|
+
this._eachTile(tiles, index, scissors, program, false, (count) => {
|
|
546
|
+
gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, count);
|
|
547
|
+
});
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* A raster source's tiles: each one's image over its square, clipped to
|
|
552
|
+
* the part of the view it draws. Past the source's depth the square is
|
|
553
|
+
* the data tile's, larger than the view's cells, so the image is drawn
|
|
554
|
+
* whole and stretched — the retained renderer's rule since #98.
|
|
555
|
+
*/
|
|
556
|
+
_rasters(tiles, scissors, width, height) {
|
|
557
|
+
const gl = this._gl;
|
|
558
|
+
const program = (this._raster ??= linkProgram(gl, RASTER_VERTEX, RASTER_FRAGMENT, ['u_tile', 'u_viewport', 'u_image']));
|
|
559
|
+
const u = program.uniforms;
|
|
560
|
+
gl.useProgram(program.program);
|
|
561
|
+
gl.uniform2f(u.u_viewport, width, height);
|
|
562
|
+
gl.uniform1i(u.u_image, 0);
|
|
563
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
564
|
+
gl.bindVertexArray(this._coverVao);
|
|
565
|
+
gl.disable(gl.STENCIL_TEST);
|
|
566
|
+
gl.enable(gl.BLEND);
|
|
567
|
+
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
568
|
+
for (let t = 0; t < tiles.length; t++) {
|
|
569
|
+
const tile = tiles[t];
|
|
570
|
+
const s = scissors[t];
|
|
571
|
+
const texture = this._tiles.get(tile.data)?.texture;
|
|
572
|
+
if (!s || !texture)
|
|
573
|
+
continue;
|
|
574
|
+
gl.scissor(s[0], s[1], s[2], s[3]);
|
|
575
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
576
|
+
gl.uniform3f(u.u_tile, tile.size, tile.x, tile.y);
|
|
577
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
578
|
+
this._stats.drawCalls++;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* A stroke of one layer's ranges — of the line stream, or of the fill
|
|
583
|
+
* stream as a polygon's outline. A translucent one is drawn each pixel
|
|
584
|
+
* once: two capsules overlap at every join, and colour laid down twice
|
|
585
|
+
* there is a darker bead at every vertex. So the pixels a capsule covers
|
|
586
|
+
* wholly go first, under the stencil, the first capsule to reach one
|
|
587
|
+
* taking it; then the antialiased fringe, where nothing has been yet.
|
|
588
|
+
*/
|
|
589
|
+
_strokeWith(tiles, index, scissors, paint, fillStream) {
|
|
590
|
+
if (paint.color[3] >= 1) {
|
|
591
|
+
this._segments(tiles, index, scissors, paint, fillStream, 0);
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
const box = this._unionOf(tiles, index, scissors);
|
|
595
|
+
if (!box)
|
|
596
|
+
return;
|
|
597
|
+
const gl = this._gl;
|
|
598
|
+
gl.enable(gl.STENCIL_TEST);
|
|
599
|
+
gl.stencilFunc(gl.EQUAL, 0, 0xff);
|
|
600
|
+
gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR);
|
|
601
|
+
this._segments(tiles, index, scissors, paint, fillStream, 1);
|
|
602
|
+
this._segments(tiles, index, scissors, paint, fillStream, 2);
|
|
603
|
+
this._clearStencil(box);
|
|
604
|
+
gl.disable(gl.STENCIL_TEST);
|
|
605
|
+
}
|
|
606
|
+
/** One layer's ranges as capsules, over every tile that has them. `part`
|
|
607
|
+
* 1 draws only the pixels a capsule covers wholly, 2 only the rest, and
|
|
608
|
+
* 0 both. */
|
|
609
|
+
_segments(tiles, index, scissors, paint, fillStream, part) {
|
|
610
|
+
const gl = this._gl;
|
|
611
|
+
const u = this._line.uniforms;
|
|
612
|
+
const { color, dash } = paint;
|
|
613
|
+
gl.useProgram(this._line.program);
|
|
614
|
+
gl.uniform4f(u.u_color, color[0], color[1], color[2], color[3]);
|
|
615
|
+
gl.uniform1f(u.u_half, paint.half);
|
|
616
|
+
// Unused entries are zero, and a dash of no length draws nothing.
|
|
617
|
+
const d = (i) => dash?.[i] ?? 0;
|
|
618
|
+
gl.uniform4f(u.u_dash_a, d(0), d(1), d(2), d(3));
|
|
619
|
+
gl.uniform4f(u.u_dash_b, d(4), d(5), d(6), d(7));
|
|
620
|
+
gl.uniform1f(u.u_period, dash ? dash.reduce((sum, v) => sum + v, 0) : 0);
|
|
621
|
+
gl.uniform1f(u.u_part, part);
|
|
622
|
+
this._eachTile(tiles, index, scissors, this._line, fillStream, (count) => {
|
|
623
|
+
gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, count);
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
/** The union of the squares a layer draws in, as a scissor box; null
|
|
627
|
+
* when no tile draws it. */
|
|
628
|
+
_unionOf(tiles, index, scissors) {
|
|
629
|
+
let x0 = Infinity;
|
|
630
|
+
let y0 = Infinity;
|
|
631
|
+
let x1 = -Infinity;
|
|
632
|
+
let y1 = -Infinity;
|
|
633
|
+
for (let t = 0; t < tiles.length; t++) {
|
|
634
|
+
const s = scissors[t];
|
|
635
|
+
if (!s || !tiles[t].data.draws[index])
|
|
636
|
+
continue;
|
|
637
|
+
if (s[0] < x0)
|
|
638
|
+
x0 = s[0];
|
|
639
|
+
if (s[1] < y0)
|
|
640
|
+
y0 = s[1];
|
|
641
|
+
if (s[0] + s[2] > x1)
|
|
642
|
+
x1 = s[0] + s[2];
|
|
643
|
+
if (s[1] + s[3] > y1)
|
|
644
|
+
y1 = s[1] + s[3];
|
|
645
|
+
}
|
|
646
|
+
return x0 === Infinity ? null : [x0, y0, x1 - x0, y1 - y0];
|
|
647
|
+
}
|
|
648
|
+
/** The stencil back to zero over a box, the colour untouched. */
|
|
649
|
+
_clearStencil(box) {
|
|
650
|
+
const gl = this._gl;
|
|
651
|
+
gl.colorMask(false, false, false, false);
|
|
652
|
+
gl.stencilFunc(gl.ALWAYS, 0, 0xff);
|
|
653
|
+
gl.stencilOp(gl.ZERO, gl.ZERO, gl.ZERO);
|
|
654
|
+
gl.scissor(box[0], box[1], box[2], box[3]);
|
|
655
|
+
this._drawCover(CLEAR);
|
|
656
|
+
this._stats.drawCalls++;
|
|
657
|
+
gl.colorMask(true, true, true, true);
|
|
658
|
+
}
|
|
659
|
+
// --- overlays ---------------------------------------------------------------
|
|
660
|
+
/**
|
|
661
|
+
* The overlays, over the labels — the retained renderer's order — from
|
|
662
|
+
* their bucket, placed this frame, pass by pass: a fill as a style's fill
|
|
663
|
+
* layer is drawn, a stroke as its line layer is. Answers how many passes
|
|
664
|
+
* it drew.
|
|
665
|
+
*/
|
|
666
|
+
_overlays(draw, frame) {
|
|
667
|
+
const gl = this._gl;
|
|
668
|
+
const { width, height, scale } = frame;
|
|
669
|
+
this._gpu(draw.data);
|
|
670
|
+
const clip = { x: 0, y: 0, width, height };
|
|
671
|
+
const tiles = [
|
|
672
|
+
{
|
|
673
|
+
data: draw.data,
|
|
674
|
+
x: draw.x,
|
|
675
|
+
y: draw.y,
|
|
676
|
+
size: draw.unit * TILE_EXTENT,
|
|
677
|
+
clip,
|
|
678
|
+
},
|
|
679
|
+
];
|
|
680
|
+
const scissors = [scissorOf(clip, width, height)];
|
|
681
|
+
gl.viewport(0, 0, width, height);
|
|
682
|
+
gl.enable(gl.SCISSOR_TEST);
|
|
683
|
+
gl.disable(gl.STENCIL_TEST);
|
|
684
|
+
gl.enable(gl.BLEND);
|
|
685
|
+
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
686
|
+
for (const program of [this._line, this._fan]) {
|
|
687
|
+
gl.useProgram(program.program);
|
|
688
|
+
gl.uniform2f(program.uniforms.u_viewport, width, height);
|
|
689
|
+
}
|
|
690
|
+
let drawn = 0;
|
|
691
|
+
for (const pass of draw.passes) {
|
|
692
|
+
const base = this._color(pass.color);
|
|
693
|
+
if (!base)
|
|
694
|
+
continue;
|
|
695
|
+
const color = premultiplied(base, pass.opacity);
|
|
696
|
+
if (pass.kind === 'fill') {
|
|
697
|
+
this._fillWith(tiles, pass.index, scissors, {
|
|
698
|
+
color,
|
|
699
|
+
antialias: this._options.antialias,
|
|
700
|
+
outline: null,
|
|
701
|
+
outlineHalf: 0,
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
else {
|
|
705
|
+
this._strokeWith(tiles, pass.index, scissors, {
|
|
706
|
+
color,
|
|
707
|
+
half: (pass.width * scale) / 2,
|
|
708
|
+
dash: dashPattern(pass.dash, scale),
|
|
709
|
+
}, pass.outline === true);
|
|
710
|
+
}
|
|
711
|
+
drawn++;
|
|
712
|
+
}
|
|
713
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
714
|
+
return drawn;
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Every tile's ranges for one layer: scissor, place, draw. The loop the
|
|
718
|
+
* whole frame is made of, so it allocates nothing and sets only what
|
|
719
|
+
* changes between tiles.
|
|
720
|
+
*/
|
|
721
|
+
_eachTile(tiles, index, scissors, program, fillStream, draw) {
|
|
722
|
+
const gl = this._gl;
|
|
723
|
+
const stats = this._stats;
|
|
724
|
+
for (let t = 0; t < tiles.length; t++) {
|
|
725
|
+
const tile = tiles[t];
|
|
726
|
+
const layerDraw = tile.data.draws[index];
|
|
727
|
+
const s = scissors[t];
|
|
728
|
+
if (!layerDraw || !s)
|
|
729
|
+
continue;
|
|
730
|
+
const gpu = this._tiles.get(tile.data);
|
|
731
|
+
if (!gpu)
|
|
732
|
+
continue;
|
|
733
|
+
gl.scissor(s[0], s[1], s[2], s[3]);
|
|
734
|
+
gl.uniform3f(program.uniforms.u_tile, tile.size / TILE_EXTENT, tile.x, tile.y);
|
|
735
|
+
// A fill layer's edges come from the fill stream too; which stream a
|
|
736
|
+
// range indexes is the draw's kind, not the program's.
|
|
737
|
+
const stream = fillStream || layerDraw.kind === 'fill';
|
|
738
|
+
const ranges = layerDraw.ranges;
|
|
739
|
+
for (let r = 0; r < ranges.length; r += 2) {
|
|
740
|
+
const instances = ranges[r + 1] - 1;
|
|
741
|
+
if (instances <= 0)
|
|
742
|
+
continue;
|
|
743
|
+
gl.bindVertexArray(this._vao(gpu, stream, ranges[r]));
|
|
744
|
+
draw(instances);
|
|
745
|
+
stats.drawCalls++;
|
|
746
|
+
stats.instances += instances;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
// --- labels -----------------------------------------------------------------
|
|
751
|
+
/**
|
|
752
|
+
* The attribution, over whatever is bound: its box — the cover program,
|
|
753
|
+
* scissored to it — and then its text, one more label quad.
|
|
754
|
+
*/
|
|
755
|
+
_attribution(draw, width, height) {
|
|
756
|
+
const gl = this._gl;
|
|
757
|
+
const box = scissorOf(draw.box, width, height);
|
|
758
|
+
if (box) {
|
|
759
|
+
gl.enable(gl.SCISSOR_TEST);
|
|
760
|
+
gl.scissor(box[0], box[1], box[2], box[3]);
|
|
761
|
+
gl.disable(gl.STENCIL_TEST);
|
|
762
|
+
gl.enable(gl.BLEND);
|
|
763
|
+
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
764
|
+
this._drawCover(draw.boxColor);
|
|
765
|
+
this._stats.drawCalls++;
|
|
766
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
767
|
+
}
|
|
768
|
+
this._labels(draw.text, width, height);
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* A batch of labels, over whatever is bound: one instanced draw of the
|
|
772
|
+
* batch's quads, after the atlas texture has what the batch draws.
|
|
773
|
+
* Answers how many it drew.
|
|
774
|
+
*/
|
|
775
|
+
_labels(batch, width, height) {
|
|
776
|
+
const gl = this._gl;
|
|
777
|
+
const atlas = batch.atlas;
|
|
778
|
+
const program = (this._label ??= linkProgram(gl, LABEL_VERTEX, LABEL_FRAGMENT, ['u_viewport', 'u_atlas', 'u_inset', 'u_image']));
|
|
779
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
780
|
+
this._uploadAtlas(atlas);
|
|
781
|
+
if (batch.count === 0)
|
|
782
|
+
return 0;
|
|
783
|
+
if (!this._labelVao) {
|
|
784
|
+
this._labelBuffer = gl.createBuffer();
|
|
785
|
+
this._labelVao = gl.createVertexArray();
|
|
786
|
+
gl.bindVertexArray(this._labelVao);
|
|
787
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this._corners);
|
|
788
|
+
gl.enableVertexAttribArray(ATTRIBUTES.a_corner);
|
|
789
|
+
gl.vertexAttribPointer(ATTRIBUTES.a_corner, 2, gl.FLOAT, false, 0, 0);
|
|
790
|
+
gl.vertexAttribDivisor(ATTRIBUTES.a_corner, 0);
|
|
791
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this._labelBuffer);
|
|
792
|
+
const stride = LABEL_INSTANCE * 4;
|
|
793
|
+
const fields = [
|
|
794
|
+
[ATTRIBUTES.a_anchor, 4, 0],
|
|
795
|
+
[ATTRIBUTES.a_rect, 4, 16],
|
|
796
|
+
[ATTRIBUTES.a_ink, 4, 32],
|
|
797
|
+
[ATTRIBUTES.a_halo, 4, 48],
|
|
798
|
+
[ATTRIBUTES.a_params, 2, 64],
|
|
799
|
+
];
|
|
800
|
+
for (const [location, size, offset] of fields) {
|
|
801
|
+
gl.enableVertexAttribArray(location);
|
|
802
|
+
gl.vertexAttribPointer(location, size, gl.FLOAT, false, stride, offset);
|
|
803
|
+
gl.vertexAttribDivisor(location, 1);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
gl.bindVertexArray(this._labelVao);
|
|
807
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this._labelBuffer);
|
|
808
|
+
gl.bufferData(gl.ARRAY_BUFFER, batch.instances.subarray(0, batch.count * LABEL_INSTANCE), gl.STREAM_DRAW);
|
|
809
|
+
gl.viewport(0, 0, width, height);
|
|
810
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
811
|
+
gl.disable(gl.STENCIL_TEST);
|
|
812
|
+
gl.enable(gl.BLEND);
|
|
813
|
+
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
814
|
+
gl.useProgram(program.program);
|
|
815
|
+
const u = program.uniforms;
|
|
816
|
+
gl.uniform2f(u.u_viewport, width, height);
|
|
817
|
+
gl.uniform2f(u.u_atlas, atlas.size, atlas.size);
|
|
818
|
+
gl.uniform1f(u.u_inset, quadInset(atlas.pad));
|
|
819
|
+
gl.uniform1i(u.u_image, 0);
|
|
820
|
+
gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, batch.count);
|
|
821
|
+
this._stats.drawCalls++;
|
|
822
|
+
return batch.count;
|
|
823
|
+
}
|
|
824
|
+
// --- markers ----------------------------------------------------------------
|
|
825
|
+
/**
|
|
826
|
+
* The markers, over whatever is bound: one instanced draw of a quad each,
|
|
827
|
+
* every pixel shaded by its distance to the marker's outline. Answers how
|
|
828
|
+
* many it drew.
|
|
829
|
+
*/
|
|
830
|
+
_markers(batch, width, height) {
|
|
831
|
+
if (batch.count === 0)
|
|
832
|
+
return 0;
|
|
833
|
+
const gl = this._gl;
|
|
834
|
+
const program = (this._marker ??= linkProgram(gl, MARKER_VERTEX, MARKER_FRAGMENT, ['u_viewport']));
|
|
835
|
+
if (!this._markerVao) {
|
|
836
|
+
this._markerBuffer = gl.createBuffer();
|
|
837
|
+
this._markerVao = gl.createVertexArray();
|
|
838
|
+
gl.bindVertexArray(this._markerVao);
|
|
839
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this._corners);
|
|
840
|
+
gl.enableVertexAttribArray(ATTRIBUTES.a_corner);
|
|
841
|
+
gl.vertexAttribPointer(ATTRIBUTES.a_corner, 2, gl.FLOAT, false, 0, 0);
|
|
842
|
+
gl.vertexAttribDivisor(ATTRIBUTES.a_corner, 0);
|
|
843
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this._markerBuffer);
|
|
844
|
+
const stride = MARKER_INSTANCE * 4;
|
|
845
|
+
const fields = [
|
|
846
|
+
[ATTRIBUTES.a_anchor, 4, 0],
|
|
847
|
+
[ATTRIBUTES.a_ink, 4, 16],
|
|
848
|
+
[ATTRIBUTES.a_halo, 4, 32],
|
|
849
|
+
[ATTRIBUTES.a_params, 2, 48],
|
|
850
|
+
];
|
|
851
|
+
for (const [location, size, offset] of fields) {
|
|
852
|
+
gl.enableVertexAttribArray(location);
|
|
853
|
+
gl.vertexAttribPointer(location, size, gl.FLOAT, false, stride, offset);
|
|
854
|
+
gl.vertexAttribDivisor(location, 1);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
gl.bindVertexArray(this._markerVao);
|
|
858
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this._markerBuffer);
|
|
859
|
+
gl.bufferData(gl.ARRAY_BUFFER, batch.instances.subarray(0, batch.count * MARKER_INSTANCE), gl.STREAM_DRAW);
|
|
860
|
+
gl.viewport(0, 0, width, height);
|
|
861
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
862
|
+
gl.disable(gl.STENCIL_TEST);
|
|
863
|
+
gl.enable(gl.BLEND);
|
|
864
|
+
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
865
|
+
gl.useProgram(program.program);
|
|
866
|
+
gl.uniform2f(program.uniforms.u_viewport, width, height);
|
|
867
|
+
gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, batch.count);
|
|
868
|
+
this._stats.drawCalls++;
|
|
869
|
+
return batch.count;
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Take what the atlas has waiting into the texture — a few dozen rasters
|
|
873
|
+
* a frame at most, so a batch landing is spread over frames rather than
|
|
874
|
+
* paid in one. A texture that is new, or another atlas's, starts over:
|
|
875
|
+
* the atlas queues everything again. Leaves the texture bound.
|
|
876
|
+
*
|
|
877
|
+
* The rasters go up as the text engine read them back — straight RGBA,
|
|
878
|
+
* white where the glyphs are — because the shader reads only alpha:
|
|
879
|
+
* premultiplying them first was a pass over every pixel for nothing.
|
|
880
|
+
*/
|
|
881
|
+
_uploadAtlas(atlas) {
|
|
882
|
+
const gl = this._gl;
|
|
883
|
+
if (!this._atlasTexture || this._atlasOwner !== atlas) {
|
|
884
|
+
if (this._atlasTexture)
|
|
885
|
+
gl.deleteTexture(this._atlasTexture);
|
|
886
|
+
this._atlasTexture = gl.createTexture();
|
|
887
|
+
gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture);
|
|
888
|
+
// Left undefined: every texel a label samples is one its own upload
|
|
889
|
+
// wrote, margin included.
|
|
890
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, atlas.size, atlas.size, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
891
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
892
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
893
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
894
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
895
|
+
this._atlasOwner = atlas;
|
|
896
|
+
atlas.restart();
|
|
897
|
+
}
|
|
898
|
+
else {
|
|
899
|
+
gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture);
|
|
900
|
+
}
|
|
901
|
+
for (const entry of atlas.takeUploads(ATLAS_UPLOADS_PER_FRAME)) {
|
|
902
|
+
gl.texSubImage2D(gl.TEXTURE_2D, 0, entry.x, entry.y, entry.width, entry.height, gl.RGBA, gl.UNSIGNED_BYTE, entry.pixels);
|
|
903
|
+
entry.ready = true;
|
|
904
|
+
this._stats.uploadBytes += entry.pixels.length;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
// --- resources ------------------------------------------------------------
|
|
908
|
+
_color(value) {
|
|
909
|
+
let color = this._colors.get(value);
|
|
910
|
+
if (color === undefined) {
|
|
911
|
+
color = parseColor(value);
|
|
912
|
+
this._colors.set(value, color);
|
|
913
|
+
}
|
|
914
|
+
return color;
|
|
915
|
+
}
|
|
916
|
+
_gpu(data) {
|
|
917
|
+
let gpu = this._tiles.get(data);
|
|
918
|
+
if (gpu)
|
|
919
|
+
return gpu;
|
|
920
|
+
const gl = this._gl;
|
|
921
|
+
gpu = { line: null, fill: null, vaos: new Map(), texture: null, bytes: 0 };
|
|
922
|
+
if (data.lineRecords > 1) {
|
|
923
|
+
gpu.line = gl.createBuffer();
|
|
924
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, gpu.line);
|
|
925
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Uint8Array(data.line), gl.STATIC_DRAW);
|
|
926
|
+
gpu.bytes += data.line.byteLength;
|
|
927
|
+
}
|
|
928
|
+
if (data.fillRecords > 1) {
|
|
929
|
+
gpu.fill = gl.createBuffer();
|
|
930
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, gpu.fill);
|
|
931
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Uint8Array(data.fill), gl.STATIC_DRAW);
|
|
932
|
+
gpu.bytes += data.fill.byteLength;
|
|
933
|
+
}
|
|
934
|
+
if (data.raster) {
|
|
935
|
+
const { width, height, pixels } = data.raster;
|
|
936
|
+
gpu.texture = gl.createTexture();
|
|
937
|
+
gl.bindTexture(gl.TEXTURE_2D, gpu.texture);
|
|
938
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
|
939
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
940
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
941
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
942
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
943
|
+
gpu.bytes += width * height * 4;
|
|
944
|
+
}
|
|
945
|
+
this._tiles.set(data, gpu);
|
|
946
|
+
this.gpuBytes += gpu.bytes;
|
|
947
|
+
this._stats.uploads++;
|
|
948
|
+
this._stats.uploadBytes += gpu.bytes;
|
|
949
|
+
return gpu;
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* The vertex array for a range: the corner quad per vertex, and records
|
|
953
|
+
* `first`, `first + 1` (and the third attribute) per instance.
|
|
954
|
+
*
|
|
955
|
+
* One per range because instanced attributes ignore `first` — GL 4.1 and
|
|
956
|
+
* ES 3.0 have no base instance — so the range's start is baked into the
|
|
957
|
+
* attribute offsets instead. A tile has one range per style layer it
|
|
958
|
+
* draws, so this is two dozen small objects per tile, made once.
|
|
959
|
+
*/
|
|
960
|
+
_vao(gpu, fill, first) {
|
|
961
|
+
const key = first * 2 + (fill ? 1 : 0);
|
|
962
|
+
let vao = gpu.vaos.get(key);
|
|
963
|
+
if (vao)
|
|
964
|
+
return vao;
|
|
965
|
+
const gl = this._gl;
|
|
966
|
+
vao = gl.createVertexArray();
|
|
967
|
+
gl.bindVertexArray(vao);
|
|
968
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this._corners);
|
|
969
|
+
gl.enableVertexAttribArray(ATTRIBUTES.a_corner);
|
|
970
|
+
gl.vertexAttribPointer(ATTRIBUTES.a_corner, 2, gl.FLOAT, false, 0, 0);
|
|
971
|
+
gl.vertexAttribDivisor(ATTRIBUTES.a_corner, 0);
|
|
972
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, fill ? gpu.fill : gpu.line);
|
|
973
|
+
const base = first * RECORD_BYTES;
|
|
974
|
+
const S = RECORD_BYTES;
|
|
975
|
+
gl.enableVertexAttribArray(ATTRIBUTES.a_p0);
|
|
976
|
+
gl.vertexAttribPointer(ATTRIBUTES.a_p0, 2, gl.SHORT, false, S, base);
|
|
977
|
+
gl.vertexAttribDivisor(ATTRIBUTES.a_p0, 1);
|
|
978
|
+
gl.enableVertexAttribArray(ATTRIBUTES.a_p1);
|
|
979
|
+
gl.vertexAttribPointer(ATTRIBUTES.a_p1, 2, gl.SHORT, false, S, base + S);
|
|
980
|
+
gl.vertexAttribDivisor(ATTRIBUTES.a_p1, 1);
|
|
981
|
+
gl.enableVertexAttribArray(ATTRIBUTES.a_x);
|
|
982
|
+
if (fill) {
|
|
983
|
+
gl.vertexAttribPointer(ATTRIBUTES.a_x, 2, gl.SHORT, false, S, base + 4);
|
|
984
|
+
}
|
|
985
|
+
else {
|
|
986
|
+
gl.vertexAttribPointer(ATTRIBUTES.a_x, 1, gl.FLOAT, false, S, base + 4);
|
|
987
|
+
}
|
|
988
|
+
gl.vertexAttribDivisor(ATTRIBUTES.a_x, 1);
|
|
989
|
+
gpu.vaos.set(key, vao);
|
|
990
|
+
return vao;
|
|
991
|
+
}
|
|
992
|
+
_drawCover(color) {
|
|
993
|
+
const gl = this._gl;
|
|
994
|
+
gl.useProgram(this._cover.program);
|
|
995
|
+
gl.uniform4f(this._cover.uniforms.u_color, color[0], color[1], color[2], color[3]);
|
|
996
|
+
gl.bindVertexArray(this._coverVao);
|
|
997
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* Does the bound framebuffer have a stencil buffer? Asked by drawing
|
|
1001
|
+
* rather than by querying, because the query differs between the default
|
|
1002
|
+
* framebuffer of a core profile (where `STENCIL_BITS` is gone) and a
|
|
1003
|
+
* user one, and the Cocoa surface is the second while XQuartz's is the
|
|
1004
|
+
* first. Two pixels, one read-back, on the first frame only.
|
|
1005
|
+
*
|
|
1006
|
+
* Both halves are needed, and the first version had only the second: **a
|
|
1007
|
+
* framebuffer with no stencil buffer passes every stencil test**, so "write
|
|
1008
|
+
* 1, then draw where it is 1" draws either way and cannot tell the two
|
|
1009
|
+
* apart. XQuartz's surface — which has none — answered yes, the frame went
|
|
1010
|
+
* straight to it, and every fill's cover pass painted the whole view in
|
|
1011
|
+
* that layer's colour. So pixel 0 asks the question that only a real
|
|
1012
|
+
* buffer answers "no" to: a draw where a *cleared* stencil is not zero.
|
|
1013
|
+
*/
|
|
1014
|
+
_probeStencil() {
|
|
1015
|
+
const gl = this._gl;
|
|
1016
|
+
const previousVao = gl.getParameter(VERTEX_ARRAY_BINDING);
|
|
1017
|
+
gl.disable(gl.BLEND);
|
|
1018
|
+
gl.enable(gl.SCISSOR_TEST);
|
|
1019
|
+
gl.scissor(0, 0, 2, 1);
|
|
1020
|
+
gl.clearColor(0, 0, 0, 0);
|
|
1021
|
+
gl.clearStencil(0);
|
|
1022
|
+
gl.stencilMask(0xff);
|
|
1023
|
+
gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
|
|
1024
|
+
gl.enable(gl.STENCIL_TEST);
|
|
1025
|
+
// Pixel 0: stencil is 0 everywhere, so NOTEQUAL 0 must draw nothing.
|
|
1026
|
+
gl.scissor(0, 0, 1, 1);
|
|
1027
|
+
gl.stencilFunc(gl.NOTEQUAL, 0, 0xff);
|
|
1028
|
+
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
|
|
1029
|
+
this._drawCover([1, 0, 1, 1]);
|
|
1030
|
+
// Pixel 1: write 1, then draw where it is 1 — which must draw.
|
|
1031
|
+
gl.scissor(1, 0, 1, 1);
|
|
1032
|
+
gl.stencilFunc(gl.ALWAYS, 1, 0xff);
|
|
1033
|
+
gl.stencilOp(gl.REPLACE, gl.REPLACE, gl.REPLACE);
|
|
1034
|
+
gl.colorMask(false, false, false, false);
|
|
1035
|
+
this._drawCover([0, 0, 0, 0]);
|
|
1036
|
+
gl.colorMask(true, true, true, true);
|
|
1037
|
+
gl.stencilFunc(gl.EQUAL, 1, 0xff);
|
|
1038
|
+
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
|
|
1039
|
+
this._drawCover([1, 0, 1, 1]);
|
|
1040
|
+
gl.disable(gl.STENCIL_TEST);
|
|
1041
|
+
const pixels = new Uint8Array(8);
|
|
1042
|
+
gl.readPixels(0, 0, 2, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
|
1043
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
1044
|
+
gl.bindVertexArray(previousVao);
|
|
1045
|
+
const magenta = (at) => pixels[at] > 200 && pixels[at + 1] < 60 && pixels[at + 2] > 200;
|
|
1046
|
+
return !magenta(0) && magenta(4);
|
|
1047
|
+
}
|
|
1048
|
+
/** An offscreen colour target with its own stencil, made or remade at
|
|
1049
|
+
* this size. Leaves its framebuffer bound. */
|
|
1050
|
+
_target(role, width, height) {
|
|
1051
|
+
const gl = this._gl;
|
|
1052
|
+
const existing = this._targets.get(role);
|
|
1053
|
+
if (existing && existing.width === width && existing.height === height) {
|
|
1054
|
+
return existing;
|
|
1055
|
+
}
|
|
1056
|
+
if (existing)
|
|
1057
|
+
this._dropTarget(existing);
|
|
1058
|
+
const texture = gl.createTexture();
|
|
1059
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
1060
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
1061
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
1062
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
1063
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
1064
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
1065
|
+
const depth = gl.createRenderbuffer();
|
|
1066
|
+
gl.bindRenderbuffer(gl.RENDERBUFFER, depth);
|
|
1067
|
+
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH24_STENCIL8, width, height);
|
|
1068
|
+
const fbo = gl.createFramebuffer();
|
|
1069
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
|
|
1070
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
|
|
1071
|
+
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depth);
|
|
1072
|
+
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, depth);
|
|
1073
|
+
const target = { fbo, texture, depth, width, height };
|
|
1074
|
+
this._targets.set(role, target);
|
|
1075
|
+
return target;
|
|
1076
|
+
}
|
|
1077
|
+
/** An offscreen target onto the bound framebuffer: a copy at `alpha` 1,
|
|
1078
|
+
* a cross-fade below it. */
|
|
1079
|
+
_composite(target, width, height, alpha) {
|
|
1080
|
+
const gl = this._gl;
|
|
1081
|
+
this._blit ??= linkProgram(gl, BLIT_VERTEX, BLIT_FRAGMENT, [
|
|
1082
|
+
'u_image',
|
|
1083
|
+
'u_alpha',
|
|
1084
|
+
]);
|
|
1085
|
+
gl.viewport(0, 0, width, height);
|
|
1086
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
1087
|
+
gl.disable(gl.STENCIL_TEST);
|
|
1088
|
+
if (alpha >= 1)
|
|
1089
|
+
gl.disable(gl.BLEND);
|
|
1090
|
+
else {
|
|
1091
|
+
gl.enable(gl.BLEND);
|
|
1092
|
+
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
1093
|
+
}
|
|
1094
|
+
gl.useProgram(this._blit.program);
|
|
1095
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
1096
|
+
gl.bindTexture(gl.TEXTURE_2D, target.texture);
|
|
1097
|
+
gl.uniform1i(this._blit.uniforms.u_image, 0);
|
|
1098
|
+
gl.uniform1f(this._blit.uniforms.u_alpha, alpha);
|
|
1099
|
+
gl.bindVertexArray(this._coverVao);
|
|
1100
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
1101
|
+
this._stats.drawCalls++;
|
|
1102
|
+
}
|
|
1103
|
+
_dropTarget(target) {
|
|
1104
|
+
const gl = this._gl;
|
|
1105
|
+
gl.deleteFramebuffer(target.fbo);
|
|
1106
|
+
gl.deleteRenderbuffer(target.depth);
|
|
1107
|
+
gl.deleteTexture(target.texture);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
/**
|
|
1111
|
+
* A clip rectangle as a GL scissor box: whole pixels, bottom-left origin,
|
|
1112
|
+
* clamped to the viewport, or null when nothing of it is visible.
|
|
1113
|
+
*
|
|
1114
|
+
* Rounded edge by edge rather than origin-and-size, so two tiles that share
|
|
1115
|
+
* an edge round it to the same pixel column and neither a gap nor a double
|
|
1116
|
+
* row can open between them at any fractional zoom.
|
|
1117
|
+
*/
|
|
1118
|
+
export function scissorOf(clip, width, height) {
|
|
1119
|
+
const x0 = Math.max(0, Math.round(clip.x));
|
|
1120
|
+
const x1 = Math.min(width, Math.round(clip.x + clip.width));
|
|
1121
|
+
const y0 = Math.max(0, Math.round(clip.y));
|
|
1122
|
+
const y1 = Math.min(height, Math.round(clip.y + clip.height));
|
|
1123
|
+
if (x1 <= x0 || y1 <= y0)
|
|
1124
|
+
return null;
|
|
1125
|
+
return [x0, height - y1, x1 - x0, y1 - y0];
|
|
1126
|
+
}
|
|
1127
|
+
//# sourceMappingURL=renderer.js.map
|