From 0ebbec3cd894480621ca8a3a251d8b48d49c2860 Mon Sep 17 00:00:00 2001 From: felixpalmer Date: Wed, 11 Dec 2024 11:31:27 +0100 Subject: [PATCH] CARTO: Fix seams between tiles in RasterTileLayer (#9292) --- .../carto/src/layers/post-process-utils.ts | 31 ++++++++++++++++++- .../src/layers/raster-layer-vertex.glsl.ts | 18 ++++++----- modules/carto/src/layers/raster-layer.ts | 17 ++++++++-- modules/carto/src/layers/raster-tile-layer.ts | 23 ++++++++++++-- 4 files changed, 76 insertions(+), 13 deletions(-) diff --git a/modules/carto/src/layers/post-process-utils.ts b/modules/carto/src/layers/post-process-utils.ts index e69ae747c74..bc6e3b88ad1 100644 --- a/modules/carto/src/layers/post-process-utils.ts +++ b/modules/carto/src/layers/post-process-utils.ts @@ -1,5 +1,7 @@ import {Framebuffer, TextureProps} from '@luma.gl/core'; +import type {ShaderPass} from '@luma.gl/shadertools'; import { + _ConstructorOf, CompositeLayer, Layer, LayerContext, @@ -59,8 +61,10 @@ class DrawCallbackLayer extends Layer { * Resulting layer must be used as a sublayer of a layer created * with `PostProcessModifier` */ -export function RTTModifier(BaseLayer) { +export function RTTModifier>(BaseLayer: T): T { + // @ts-expect-error initializeState is abstract return class RTTLayer extends BaseLayer { + // @ts-expect-error typescript doesn't see static property static layerName = `RTT-${BaseLayer.layerName}`; draw(this: RTTLayer, opts: any) { @@ -176,5 +180,30 @@ export function PostProcessModifier { + fbo.destroy(); + }); + this.internalState.renderBuffers = null; + this.internalState.postProcess.cleanup(); + } }; } + +const fs = /* glsl */ `\ +vec4 copy_filterColor(vec4 color, vec2 texSize, vec2 texCoord) { + return color; +} +`; + +/** + * Copy + * Simple module that just copies input color to output + */ +export const copy = { + name: 'copy', + fs, + getUniforms: () => ({}), + passes: [{filter: true}] +} as const satisfies ShaderPass<{}>; diff --git a/modules/carto/src/layers/raster-layer-vertex.glsl.ts b/modules/carto/src/layers/raster-layer-vertex.glsl.ts index 31d81b0370d..40e7fd82382 100644 --- a/modules/carto/src/layers/raster-layer-vertex.glsl.ts +++ b/modules/carto/src/layers/raster-layer-vertex.glsl.ts @@ -29,12 +29,14 @@ out vec4 position_commonspace; void main(void) { // Rather than positioning using attribute, layout pixel grid using gl_InstanceID - vec2 common_position = offset.xy; + vec2 tileOrigin = offset.xy; float scale = offset.z; int yIndex = - (gl_InstanceID / BLOCK_WIDTH); int xIndex = gl_InstanceID + (yIndex * BLOCK_WIDTH); - common_position += scale * vec2(float(xIndex), float(yIndex - 1)); + + // Avoid precision issues by applying 0.5 offset here, rather than when laying out vertices + vec2 cellCenter = scale * vec2(float(xIndex) + 0.5, float(yIndex) - 0.5); vec4 color = isStroke ? instanceLineColors : instanceFillColors; @@ -45,7 +47,7 @@ void main(void) { // Get position directly from quadbin, rather than projecting // Important to set geometry.position before using project_ methods below // as geometry.worldPosition is not set (we don't know our lat/long) - geometry.position = vec4(common_position, 0.0, 1.0); + geometry.position = vec4(tileOrigin + cellCenter, 0.0, 1.0); if (project_uProjectionMode == PROJECTION_MODE_WEB_MERCATOR_AUTO_OFFSET) { geometry.position.xyz -= project_uCommonOrigin; } @@ -69,12 +71,12 @@ void main(void) { geometry.pickingColor = instancePickingColors; - // project center of column - vec2 offset = (vec2(0.5) + positions.xy * strokeOffsetRatio) * cellWidth * shouldRender; - vec3 pos = vec3(offset, project_size(elevation)); - DECKGL_FILTER_SIZE(pos, geometry); + // Cell coordinates centered on origin + vec2 base = positions.xy * scale * strokeOffsetRatio * coverage * shouldRender; + vec3 cell = vec3(base, project_size(elevation)); + DECKGL_FILTER_SIZE(cell, geometry); - geometry.position.xyz += pos; + geometry.position.xyz += cell; gl_Position = project_common_position_to_clipspace(geometry.position); geometry.normal = project_normal(normals); diff --git a/modules/carto/src/layers/raster-layer.ts b/modules/carto/src/layers/raster-layer.ts index 6657b3d2a4a..a3e91c66510 100644 --- a/modules/carto/src/layers/raster-layer.ts +++ b/modules/carto/src/layers/raster-layer.ts @@ -12,6 +12,7 @@ import {quadbinToOffset} from './quadbin-utils'; import {Raster} from './schema/carto-raster-tile-loader'; import vs from './raster-layer-vertex.glsl'; import {createBinaryProxy} from '../utils'; +import {RTTModifier} from './post-process-utils'; const defaultProps: DefaultProps = { ...ColumnLayer.defaultProps, @@ -26,7 +27,8 @@ const defaultProps: DefaultProps = { }; // Modified ColumnLayer with custom vertex shader -class RasterColumnLayer extends ColumnLayer { +// Use RTT to avoid inter-tile seams +class RasterColumnLayer extends RTTModifier(ColumnLayer) { static layerName = 'RasterColumnLayer'; getShaders() { @@ -137,7 +139,18 @@ export default class RasterLayer extends Composite dataComparator: wrappedDataComparator, offset, highlightedObjectIndex, - highlightColor + highlightColor, + + // RTT requires blending otherwise opacity < 1 blends with black + // render target + parameters: { + blendColorSrcFactor: 'one', + blendAlphaSrcFactor: 'one', + blendColorDstFactor: 'zero', + blendAlphaDstFactor: 'zero', + blendColorOperation: 'add', + blendAlphaOperation: 'add' + } } ); } diff --git a/modules/carto/src/layers/raster-tile-layer.ts b/modules/carto/src/layers/raster-tile-layer.ts index 5c91a0b69e5..7a180ca3bb7 100644 --- a/modules/carto/src/layers/raster-tile-layer.ts +++ b/modules/carto/src/layers/raster-tile-layer.ts @@ -1,10 +1,18 @@ -import {CompositeLayer, CompositeLayerProps, DefaultProps, Layer, LayersList} from '@deck.gl/core'; +import { + CompositeLayer, + CompositeLayerProps, + DefaultProps, + FilterContext, + Layer, + LayersList +} from '@deck.gl/core'; import RasterLayer, {RasterLayerProps} from './raster-layer'; import QuadbinTileset2D from './quadbin-tileset-2d'; import type {TilejsonResult} from '../sources/types'; import {injectAccessToken, TilejsonPropType} from './utils'; import {DEFAULT_TILE_SIZE} from '../constants'; import {TileLayer, TileLayerProps} from '@deck.gl/geo-layers'; +import {copy, PostProcessModifier} from './post-process-utils'; export const renderSubLayers = props => { const tileIndex = props.tile?.index?.q; @@ -14,6 +22,7 @@ export const renderSubLayers = props => { const defaultProps: DefaultProps = { data: TilejsonPropType, + refinementStrategy: 'no-overlap', tileSize: DEFAULT_TILE_SIZE }; @@ -27,6 +36,16 @@ type _RasterTileLayerProps = Omit, 'data'> & data: null | TilejsonResult | Promise; }; +class PostProcessTileLayer extends PostProcessModifier(TileLayer, copy) { + filterSubLayer(context: FilterContext) { + // Handle DrawCallbackLayer + const {tile} = (context.layer as Layer<{tile: any}>).props; + if (!tile) return true; + + return super.filterSubLayer(context); + } +} + export default class RasterTileLayer< DataT = any, ExtraProps extends {} = {} @@ -46,7 +65,7 @@ export default class RasterTileLayer< if (!tileJSON) return null; const {tiles: data, minzoom: minZoom, maxzoom: maxZoom} = tileJSON; - const SubLayerClass = this.getSubLayerClass('tile', TileLayer); + const SubLayerClass = this.getSubLayerClass('tile', PostProcessTileLayer); return new SubLayerClass(this.props, { id: `raster-tile-layer-${this.props.id}`, data,