Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

CARTO: Fix seams between tiles in RasterTileLayer #9286

Merged
merged 3 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion modules/carto/src/layers/post-process-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
// Copyright (c) vis.gl contributors

import {Framebuffer, TextureProps} from '@luma.gl/core';
import type {ShaderPass} from '@luma.gl/shadertools';
import {
_ConstructorOf,
CompositeLayer,
Layer,
LayerContext,
Expand Down Expand Up @@ -63,8 +65,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<T extends _ConstructorOf<Layer>>(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) {
Expand Down Expand Up @@ -181,5 +185,30 @@ export function PostProcessModifier<T extends Constructor<DrawableCompositeLayer

this.internalState.renderInProgress = false;
}

_finalize(): void {
this.internalState.renderBuffers.forEach((fbo: Framebuffer) => {
fbo.destroy();
});
this.internalState.renderBuffers = null;
this.internalState.postProcess.cleanup();
}
};
}

const fs = /* glsl */ `\
vec4 copy_filterColor_ext(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<{}>;
18 changes: 10 additions & 8 deletions modules/carto/src/layers/raster-layer-vertex.glsl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ out vec4 position_commonspace;

void main(void) {
// Rather than positioning using attribute, layout pixel grid using gl_InstanceID
vec2 common_position = column.offset.xy;
vec2 tileOrigin = column.offset.xy;
float scale = column.widthScale; // Re-use widthScale prop to pass cell scale

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 = column.isStroke ? instanceLineColors : instanceFillColors;

Expand All @@ -39,7 +41,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.projectionMode == PROJECTION_MODE_WEB_MERCATOR_AUTO_OFFSET) {
geometry.position.xyz -= project.commonOrigin;
}
Expand All @@ -63,12 +65,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 * column.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);
Expand Down
17 changes: 15 additions & 2 deletions modules/carto/src/layers/raster-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,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<RasterLayerProps> = {
...ColumnLayer.defaultProps,
Expand All @@ -30,7 +31,8 @@ const defaultProps: DefaultProps<RasterLayerProps> = {
};

// 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() {
Expand Down Expand Up @@ -143,7 +145,18 @@ export default class RasterLayer<DataT = any, ExtraProps = {}> extends Composite
offset,
lineWidthScale, // Re-use widthScale prop to pass cell scale,
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'
}
}
);
}
Expand Down
23 changes: 21 additions & 2 deletions modules/carto/src/layers/raster-tile-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

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 '@carto/api-client';
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;
Expand All @@ -18,6 +26,7 @@ export const renderSubLayers = props => {

const defaultProps: DefaultProps<RasterTileLayerProps> = {
data: TilejsonPropType,
refinementStrategy: 'no-overlap',
tileSize: DEFAULT_TILE_SIZE
};

Expand All @@ -31,6 +40,16 @@ type _RasterTileLayerProps<DataT> = Omit<RasterLayerProps<DataT>, 'data'> &
data: null | TilejsonResult | Promise<TilejsonResult>;
};

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 {} = {}
Expand All @@ -50,7 +69,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,
Expand Down
Loading