From c2227bc08f98d0a413cd349b3511d0767d978254 Mon Sep 17 00:00:00 2001 From: Thibaud Lopez Schneider Date: Wed, 24 Dec 2025 15:58:08 -0800 Subject: [PATCH 1/3] Delete .github/workflows/codeql.yml (#13610) follow-up #13240 --- .github/workflows/codeql.yml | 74 ------------------------------------ 1 file changed, 74 deletions(-) delete mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index c1c1052ba4d..00000000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,74 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" - -on: - push: - branches: [ main ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ main ] - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'javascript' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Learn more about CodeQL language support at https://git.io/codeql-language-support - - steps: - - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 - - - if: matrix.language == 'javascript-typescript' - name: Setup Node.js 20.x - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 #v4.4.0 - with: - node-version: 20.x - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f #v3.28.18 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@ff0a06e83cb2de871e5a09832bc6a81e7276941f #v3.28.18 - - # â„šī¸ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f #v3.28.18 From 18e906b2c474b6f0573b4f46d19df658657207cc Mon Sep 17 00:00:00 2001 From: Constantine Chukhlomin Date: Thu, 1 Jan 2026 17:25:55 -0500 Subject: [PATCH 2/3] feat: variable line-offset using line-progress --- debug/variable-line-offset-demo.html | 168 +++++++++++++++++++++++++++ src/data/bucket/line_bucket.ts | 27 ++++- src/render/draw_line.ts | 5 + src/render/program/line_program.ts | 2 +- src/shaders/line.vertex.glsl | 17 ++- src/shaders/line_pattern.vertex.glsl | 7 +- src/style-spec/reference/v8.json | 5 +- 7 files changed, 220 insertions(+), 11 deletions(-) create mode 100644 debug/variable-line-offset-demo.html diff --git a/debug/variable-line-offset-demo.html b/debug/variable-line-offset-demo.html new file mode 100644 index 00000000000..e0af53c52a3 --- /dev/null +++ b/debug/variable-line-offset-demo.html @@ -0,0 +1,168 @@ + + + + Variable Line Offset Demo - Multi-Line Rendering + + + + + + + +
+

Variable Line Offset Demo

+

Feature: line-offset with line-progress

+

The red line offset varies from -10px to +10px along its length.

+ +
+

Layer Controls

+
+ + +
+
+ + +
+
+
+ +
+ + + + + + diff --git a/src/data/bucket/line_bucket.ts b/src/data/bucket/line_bucket.ts index 79834775df8..0ff4cf1b693 100644 --- a/src/data/bucket/line_bucket.ts +++ b/src/data/bucket/line_bucket.ts @@ -99,6 +99,7 @@ type GradientTexture = { type LineProgressFeatures = { zOffset: number; variableWidth: number; + variableOffset: number; }; interface Subsegment { @@ -121,6 +122,7 @@ class LineBucket implements Bucket { lineClips: LineClips | null | undefined; zOffsetValue: PossiblyEvaluatedValue; variableWidthValue: PossiblyEvaluatedValue; + variableOffsetValue: PossiblyEvaluatedValue; lineFeature: BucketFeature; e1: number; @@ -455,6 +457,15 @@ class LineBucket implements Bucket { this.variableWidthValue = lineWidth; } + // Check layers for variable offset + for (const layer of this.layers) { + const lineOffset = layer.paint.get('line-offset').value; + if (lineOffset.kind !== 'constant' && lineOffset.isLineProgressConstant === false) { + this.variableOffsetValue = lineOffset; + break; + } + } + if (this.elevationType === 'road') { const vertexOffset = this.layoutVertexArray.length; const added = this.addElevatedRoadFeature(feature, geometry, canonical, elevationFeatures, join, cap, miterLimit, roundLimit); @@ -1019,7 +1030,7 @@ class LineBucket implements Bucket { evaluateLineProgressFeatures(distance: number): LineProgressFeatures | null { assert(distance >= 0); - if (!this.variableWidthValue && this.elevationType !== 'offset') { + if (!this.variableWidthValue && !this.variableOffsetValue && this.elevationType !== 'offset') { return null; } this.evaluationGlobals.lineProgress = 0; @@ -1032,14 +1043,20 @@ class LineBucket implements Bucket { if (this.variableWidthValue && this.variableWidthValue.kind !== 'constant') { variableWidth = this.variableWidthValue.evaluate(this.evaluationGlobals, this.lineFeature) || 0.0; } + + let variableOffset = 0.0; + if (this.variableOffsetValue && this.variableOffsetValue.kind !== 'constant') { + variableOffset = this.variableOffsetValue.evaluate(this.evaluationGlobals, this.lineFeature) || 0.0; + } + if (this.elevationType !== 'offset') { - return {zOffset: 0.0, variableWidth}; + return {zOffset: 0.0, variableWidth, variableOffset}; } if (this.zOffsetValue.kind === 'constant') { - return {zOffset: this.zOffsetValue.value, variableWidth}; + return {zOffset: this.zOffsetValue.value, variableWidth, variableOffset}; } const zOffset: number = this.zOffsetValue.evaluate(this.evaluationGlobals, this.lineFeature) || 0.0; - return {zOffset, variableWidth}; + return {zOffset, variableWidth, variableOffset}; } /** @@ -1181,7 +1198,7 @@ class LineBucket implements Bucket { this.zOffsetVertexArray.emplaceBack( lineProgressFeatures.zOffset, lineProgressFeatures.variableWidth, - lineProgressFeatures.variableWidth + lineProgressFeatures.variableOffset ); } assert(this.zOffsetVertexArray.length === this.layoutVertexArray.length || this.elevationType !== 'offset'); diff --git a/src/render/draw_line.ts b/src/render/draw_line.ts index 2b9457755c2..9581bcab5ef 100644 --- a/src/render/draw_line.ts +++ b/src/render/draw_line.ts @@ -133,6 +133,11 @@ export default function drawLine(painter: Painter, sourceCache: SourceCache, lay definesValues.push("VARIABLE_LINE_WIDTH"); } + const offset = layer.paint.get('line-offset').value; + if (offset.kind !== 'constant' && offset.isLineProgressConstant === false) { + definesValues.push("VARIABLE_LINE_OFFSET"); + } + if (isDraping) { if (painter.emissiveMode === 'dual-source-blending' && !constantEmissiveStrength) { definesValues.push('DUAL_SOURCE_BLENDING'); diff --git a/src/render/program/line_program.ts b/src/render/program/line_program.ts index 21caf90c14c..fb717ce0cb2 100644 --- a/src/render/program/line_program.ts +++ b/src/render/program/line_program.ts @@ -54,7 +54,7 @@ export type LinePatternUniformsType = { ['u_pattern_transition']: Uniform1f; }; -export type LineDefinesType = 'RENDER_LINE_GRADIENT' | 'RENDER_LINE_DASH' | 'RENDER_LINE_TRIM_OFFSET' | 'RENDER_LINE_BORDER' | 'LINE_JOIN_NONE' | 'ELEVATED' | 'VARIABLE_LINE_WIDTH' | 'CROSS_SLOPE_VERTICAL' | 'CROSS_SLOPE_HORIZONTAL' | 'ELEVATION_REFERENCE_SEA' | 'LINE_PATTERN_TRANSITION' | 'USE_MRT1' | 'DUAL_SOURCE_BLENDING'; +export type LineDefinesType = 'RENDER_LINE_GRADIENT' | 'RENDER_LINE_DASH' | 'RENDER_LINE_TRIM_OFFSET' | 'RENDER_LINE_BORDER' | 'LINE_JOIN_NONE' | 'ELEVATED' | 'VARIABLE_LINE_WIDTH' | 'VARIABLE_LINE_OFFSET' | 'CROSS_SLOPE_VERTICAL' | 'CROSS_SLOPE_HORIZONTAL' | 'ELEVATION_REFERENCE_SEA' | 'LINE_PATTERN_TRANSITION' | 'USE_MRT1' | 'DUAL_SOURCE_BLENDING'; const lineUniforms = (context: Context): LineUniformsType => ({ 'u_matrix': new UniformMatrix4f(context), diff --git a/src/shaders/line.vertex.glsl b/src/shaders/line.vertex.glsl index 2a0c8fd2017..e5992cacc66 100644 --- a/src/shaders/line.vertex.glsl +++ b/src/shaders/line.vertex.glsl @@ -12,7 +12,7 @@ in vec2 a_pos_normal; in vec4 a_data; -#if defined(ELEVATED) || defined(ELEVATED_ROADS) || defined(VARIABLE_LINE_WIDTH) +#if defined(ELEVATED) || defined(ELEVATED_ROADS) || defined(VARIABLE_LINE_WIDTH) || defined(VARIABLE_LINE_OFFSET) in vec3 a_z_offset_width; #endif @@ -130,6 +130,10 @@ void main() { offset = -1.0 * offset * u_width_scale; +#ifdef VARIABLE_LINE_OFFSET + offset = -1.0 * a_z_offset_width.z * u_width_scale; +#endif + // these transformations used to be applied in the JS and native code bases. // moved them into the shader for clarity and simplicity. gapwidth = gapwidth / 2.0; @@ -137,7 +141,16 @@ void main() { #ifdef VARIABLE_LINE_WIDTH bool left = normal.y == 1.0; float left_width = a_z_offset_width.y; - float right_width = a_z_offset_width.z; + float right_width = a_z_offset_width.y; + + #ifdef VARIABLE_LINE_OFFSET + // When both features active: use symmetric width, offset from z component + offset = -1.0 * a_z_offset_width.z * u_width_scale; + #else + // Original behavior: z component is right_width for asymmetric lines + right_width = a_z_offset_width.z; + #endif + halfwidth = (u_width_scale * (left ? left_width : right_width)) / 2.0; a_z_offset += left ? side_z_offset : 0.0; v_normal = side_z_offset > 0.0 && left ? vec2(0.0) : v_normal; diff --git a/src/shaders/line_pattern.vertex.glsl b/src/shaders/line_pattern.vertex.glsl index 33c1c5fa8e3..e97a522bddd 100644 --- a/src/shaders/line_pattern.vertex.glsl +++ b/src/shaders/line_pattern.vertex.glsl @@ -12,7 +12,7 @@ in vec2 a_pos_normal; in vec4 a_data; -#if defined(ELEVATED) || defined(ELEVATED_ROADS) +#if defined(ELEVATED) || defined(ELEVATED_ROADS) || defined(VARIABLE_LINE_OFFSET) in vec3 a_z_offset_width; #endif // Includes in order: a_uv_x, a_split_index, a_line_progress @@ -128,6 +128,11 @@ void main() { float halfwidth = (u_width_scale * width) / 2.0; offset = -1.0 * offset * u_width_scale; +#ifdef VARIABLE_LINE_OFFSET + // Variable offset uses the same scaling as regular offset + offset = -1.0 * a_z_offset_width.z * u_width_scale; +#endif + float inset = gapwidth + (gapwidth > 0.0 ? ANTIALIASING : 0.0); float outset = gapwidth + halfwidth * (gapwidth > 0.0 ? 2.0 : 1.0) + (halfwidth == 0.0 ? 0.0 : ANTIALIASING); diff --git a/src/style-spec/reference/v8.json b/src/style-spec/reference/v8.json index ded33e92f13..531f37c8e34 100644 --- a/src/style-spec/reference/v8.json +++ b/src/style-spec/reference/v8.json @@ -5271,7 +5271,7 @@ } }, "line-progress": { - "doc": "Returns the progress along a gradient line. Can only be used in the `line-gradient` and `line-z-offset` properties.", + "doc": "Returns the progress along a gradient line. Can only be used in the `line-gradient`, `line-z-offset`, and `line-offset` properties.", "group": "Feature data", "sdk-support": { "basic functionality": { @@ -8474,7 +8474,8 @@ "zoom", "feature", "feature-state", - "measure-light" + "measure-light", + "line-progress" ] }, "property-type": "data-driven" From b5bd01a9b934d9bbc4f533aeedcd74f798b3d322 Mon Sep 17 00:00:00 2001 From: Constantine Chukhlomin Date: Thu, 1 Jan 2026 23:47:43 -0500 Subject: [PATCH 3/3] add tests --- .../line-progress-curved/expected.png | Bin 0 -> 11432 bytes .../line-progress-curved/style.json | 72 +++++++++++++ .../line-progress-exponential/expected.png | Bin 0 -> 5842 bytes .../line-progress-exponential/style.json | 61 +++++++++++ .../expected.png | Bin 0 -> 5861 bytes .../style.json | 67 ++++++++++++ .../line-offset/line-progress/expected.png | Bin 0 -> 5308 bytes .../line-offset/line-progress/style.json | 61 +++++++++++ test/unit/data/line_bucket.test.ts | 96 ++++++++++++++++++ 9 files changed, 357 insertions(+) create mode 100644 test/integration/render-tests/line-offset/line-progress-curved/expected.png create mode 100644 test/integration/render-tests/line-offset/line-progress-curved/style.json create mode 100644 test/integration/render-tests/line-offset/line-progress-exponential/expected.png create mode 100644 test/integration/render-tests/line-offset/line-progress-exponential/style.json create mode 100644 test/integration/render-tests/line-offset/line-progress-with-variable-width/expected.png create mode 100644 test/integration/render-tests/line-offset/line-progress-with-variable-width/style.json create mode 100644 test/integration/render-tests/line-offset/line-progress/expected.png create mode 100644 test/integration/render-tests/line-offset/line-progress/style.json diff --git a/test/integration/render-tests/line-offset/line-progress-curved/expected.png b/test/integration/render-tests/line-offset/line-progress-curved/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..b549277341799049edffd96fec953fe8c9d6c604 GIT binary patch literal 11432 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 literal 0 HcmV?d00001 diff --git a/test/integration/render-tests/line-offset/line-progress-curved/style.json b/test/integration/render-tests/line-offset/line-progress-curved/style.json new file mode 100644 index 00000000000..edec091c23c --- /dev/null +++ b/test/integration/render-tests/line-offset/line-progress-curved/style.json @@ -0,0 +1,72 @@ +{ + "version": 8, + "metadata": { + "test": { + "width": 256, + "height": 256 + } + }, + "zoom": 2, + "sources": { + "line": { + "type": "geojson", + "lineMetrics": true, + "data": { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [-15, 15], + [-5, -15], + [5, 15], + [15, -15] + ] + } + } + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "reference-line", + "type": "line", + "source": "line", + "layout": { + "line-cap": "round", + "line-join": "round" + }, + "paint": { + "line-width": 15, + "line-color": "#cccccc" + } + }, + { + "id": "variable-offset-line", + "type": "line", + "source": "line", + "layout": { + "line-cap": "round", + "line-join": "round" + }, + "paint": { + "line-width": 4, + "line-color": "#ff0000", + "line-offset": [ + "interpolate", + ["linear"], + ["line-progress"], + 0, -8, + 0.5, 0, + 1, 8 + ] + } + } + ] +} diff --git a/test/integration/render-tests/line-offset/line-progress-exponential/expected.png b/test/integration/render-tests/line-offset/line-progress-exponential/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..63ac9b6f9c9ba6960f0c6439e4c985355329e70e GIT binary patch literal 5842 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 literal 0 HcmV?d00001 diff --git a/test/integration/render-tests/line-offset/line-progress-exponential/style.json b/test/integration/render-tests/line-offset/line-progress-exponential/style.json new file mode 100644 index 00000000000..92ae41cc7c1 --- /dev/null +++ b/test/integration/render-tests/line-offset/line-progress-exponential/style.json @@ -0,0 +1,61 @@ +{ + "version": 8, + "metadata": { + "test": { + "width": 256, + "height": 256 + } + }, + "zoom": 2, + "sources": { + "line": { + "type": "geojson", + "lineMetrics": true, + "data": { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [-20, 0], + [20, 0] + ] + } + } + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "reference-line", + "type": "line", + "source": "line", + "paint": { + "line-width": 40, + "line-color": "#cccccc" + } + }, + { + "id": "variable-offset-line", + "type": "line", + "source": "line", + "paint": { + "line-width": 4, + "line-color": "#0000ff", + "line-offset": [ + "interpolate", + ["exponential", 2], + ["line-progress"], + 0, -20, + 1, 20 + ] + } + } + ] +} diff --git a/test/integration/render-tests/line-offset/line-progress-with-variable-width/expected.png b/test/integration/render-tests/line-offset/line-progress-with-variable-width/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..a3f7b50eb2cc86eb3a389cf593c61498a06aa9d3 GIT binary patch literal 5861 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 literal 0 HcmV?d00001 diff --git a/test/integration/render-tests/line-offset/line-progress/style.json b/test/integration/render-tests/line-offset/line-progress/style.json new file mode 100644 index 00000000000..7e85e19edaf --- /dev/null +++ b/test/integration/render-tests/line-offset/line-progress/style.json @@ -0,0 +1,61 @@ +{ + "version": 8, + "metadata": { + "test": { + "width": 256, + "height": 256 + } + }, + "zoom": 2, + "sources": { + "line": { + "type": "geojson", + "lineMetrics": true, + "data": { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [-20, 0], + [20, 0] + ] + } + } + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "reference-line", + "type": "line", + "source": "line", + "paint": { + "line-width": 40, + "line-color": "#cccccc" + } + }, + { + "id": "variable-offset-line", + "type": "line", + "source": "line", + "paint": { + "line-width": 4, + "line-color": "#ff0000", + "line-offset": [ + "interpolate", + ["linear"], + ["line-progress"], + 0, -20, + 1, 20 + ] + } + } + ] +} diff --git a/test/unit/data/line_bucket.test.ts b/test/unit/data/line_bucket.test.ts index 60ff1b1f284..8191d7d47e4 100644 --- a/test/unit/data/line_bucket.test.ts +++ b/test/unit/data/line_bucket.test.ts @@ -137,3 +137,99 @@ test('LineBucket segmentation', () => { expect(console.warn).toHaveBeenCalledTimes(1); }); + +test('LineBucket with variable line-offset detects variable offset', () => { + const layer = new LineStyleLayer({ + id: 'test', + type: 'line', + paint: { + 'line-offset': [ + 'interpolate', + ['linear'], + ['line-progress'], + 0, -10, + 1, 10 + ] + } + }); + layer.recalculate({zoom: 14}); + + const bucket = new LineBucket({layers: [layer]}); + + const line = { + type: 2, + properties: { + mapbox_clip_start: 0, + mapbox_clip_end: 1 + } + }; + + // Test that variable offset is detected when layer is configured + const paint = layer.paint; + const lineOffset = paint.get('line-offset').value; + expect(lineOffset.kind).not.toBe('constant'); + expect(lineOffset.isLineProgressConstant).toBe(false); +}); + +test('LineBucket with both variable width and variable offset', () => { + const layer = new LineStyleLayer({ + id: 'test', + type: 'line', + paint: { + 'line-width': [ + 'interpolate', + ['linear'], + ['line-progress'], + 0, 2, + 1, 10 + ], + 'line-offset': [ + 'interpolate', + ['linear'], + ['line-progress'], + 0, -5, + 1, 5 + ] + } + }); + layer.recalculate({zoom: 14}); + + const bucket = new LineBucket({layers: [layer]}); + + // Both variable width and offset should be detected in layer configuration + const paint = layer.paint; + const lineWidth = paint.get('line-width').value; + const lineOffset = paint.get('line-offset').value; + + expect(lineWidth.kind).not.toBe('constant'); + expect(lineWidth.isLineProgressConstant).toBe(false); + expect(lineOffset.kind).not.toBe('constant'); + expect(lineOffset.isLineProgressConstant).toBe(false); +}); + +test('LineBucket with constant offset should not set variableOffsetValue', () => { + const layer = new LineStyleLayer({ + id: 'test', + type: 'line', + paint: { + 'line-offset': 5 + } + }); + layer.recalculate({zoom: 14}); + + const bucket = new LineBucket({layers: [layer]}); + + const line = { + type: 2, + properties: {} + }; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + bucket.addFeature(line, [[ + new Point(0, 0), + new Point(100, 0) + ]]); + + // Constant offset should not trigger variable offset + expect(bucket.variableOffsetValue).toBeFalsy(); +});