maplibre-gl
Version:
BSD licensed community fork of mapbox-gl, a WebGL interactive maps library
302 lines (264 loc) • 12.4 kB
text/typescript
import {beforeEach, describe, test, expect, vi} from 'vitest';
import {RenderToTexture} from './render_to_texture.ts';
import type {Painter, RTTObject} from '../render/painter.ts';
import type {LineStyleLayer} from '../style/style_layer/line_style_layer.ts';
import type {SymbolStyleLayer} from '../style/style_layer/symbol_style_layer.ts';
import {Context} from '../webgl/context.ts';
import {ColorMode} from '../webgl/color_mode.ts';
import {Terrain} from '../render/terrain.ts';
import {type Style} from '../style/style.ts';
import {Tile} from '../tile/tile.ts';
import {type Map} from '../ui/map.ts';
import {OverscaledTileID} from '../tile/tile_id.ts';
import {type TileManager} from '../tile/tile_manager.ts';
import {type TerrainSpecification} from '@maplibre/maplibre-gl-style-spec';
import {type FillStyleLayer} from '../style/style_layer/fill_style_layer.ts';
import {type RasterStyleLayer} from '../style/style_layer/raster_style_layer.ts';
import {type HillshadeStyleLayer} from '../style/style_layer/hillshade_style_layer.ts';
import {type BackgroundStyleLayer} from '../style/style_layer/background_style_layer.ts';
import {DepthMode} from '../webgl/depth_mode.ts';
import {createNullGL} from '../util/test/null_gl.ts';
describe('render to texture', () => {
const gl = createNullGL();
const backgroundLayer = {
id: 'maine-background',
type: 'background',
source: 'maine',
isHidden: () => false
} as any as BackgroundStyleLayer;
const fillLayer = {
id: 'maine-fill',
type: 'fill',
source: 'maine',
isHidden: () => false
} as any as FillStyleLayer;
const rasterLayer = {
id: 'maine-raster',
type: 'raster',
source: 'maine',
isHidden: () => false
} as any as RasterStyleLayer;
const hillshadeLayer = {
id: 'maine-hillshade',
type: 'line',
source: 'maine',
isHidden: () => false
} as any as HillshadeStyleLayer;
const lineLayer = {
id: 'maine-line',
type: 'line',
source: 'maine',
isHidden: () => false
} as any as LineStyleLayer;
const symbolLayer = {
id: 'maine-symbol',
type: 'symbol',
source: 'maine',
layout: {
'text-field': 'maine',
'symbol-placement': 'line'
},
isHidden: () => false
} as any as SymbolStyleLayer;
let layersDrawn = 0;
const painter = {
layersDrawn: 0,
context: new Context(gl),
transform: {zoom: 10, calculatePosMatrix: () => {}, getProjectionData(_a) {}, calculateFogMatrix: () => {}},
colorModeForRenderPass: () => ColorMode.alphaBlended,
getDepthModeFor3D: () => DepthMode.disabled,
useProgram: () => ({draw: () => { layersDrawn++; }}),
renderTileClippingMasks: vi.fn(),
renderLayer: vi.fn(),
acquireRTT: (size: number) => ({texture: {}, size}),
bindRTT: vi.fn(),
releaseRTT: vi.fn(),
drawFunctions: {
terrainDepth: vi.fn(),
terrainCoords: vi.fn(),
}
} as any as Painter;
const map = {painter} as Map;
const tile = new Tile(new OverscaledTileID(3, 0, 2, 1, 2), 512);
const tileManager = {
_source: {minzoom: 0, maxzoom: 2},
getTileByID: (_id) => tile,
getVisibleCoordinates: () => [tile.tileID]
} as TileManager;
const style = {
tileManagers: {
'maine': {
getVisibleCoordinates: () => [tile.tileID],
getSource: () => ({}),
getState: vi.fn().mockReturnValue({revision: 0})
}
},
_order: ['maine-fill', 'maine-symbol'],
_layers: {
'maine-background': backgroundLayer,
'maine-fill': fillLayer,
'maine-raster': rasterLayer,
'maine-hillshade': hillshadeLayer,
'maine-line': lineLayer,
'maine-symbol': symbolLayer
},
projection: {
transitionState: 0,
}
} as any as Style;
painter.style = style;
map.style = style;
style.map = map;
const terrain = new Terrain(painter, tileManager, {} as any as TerrainSpecification);
vi.spyOn(terrain.tileManager, 'getRenderableTiles').mockReturnValue([tile]);
vi.spyOn(terrain.tileManager, 'getTerrainCoords').mockReturnValue({[tile.tileID.key]: tile.tileID});
map.terrain = terrain;
const rtt = new RenderToTexture(painter, terrain);
rtt.prepareForRender(style, 0);
painter.renderToTexture = rtt;
beforeEach(() => {
tile.rttObjects.length = 0;
tile.rttFingerprint = {};
});
test('should call painter with overlay tiles for terrain tile', () => {
const renderLayerSpy = vi.spyOn(painter, 'renderLayer');
rtt.prepareForRender(style, 0);
const renderOptions = {isRenderingToTexture: false, isRenderingGlobe: false};
for (const layerId of style._order) {
const layer = style._layers[layerId];
rtt.renderLayer(layer, renderOptions);
}
expect(renderLayerSpy).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
expect.objectContaining({id: 'maine-fill'}),
[tile.tileID],
expect.anything()
);
});
test('should clear tile cache when overlaid tiles change and return rtt object to painter pool', () => {
rtt.prepareForRender(style, 0);
const obj = {texture: {}, size: 512} as unknown as RTTObject;
tile.rttFingerprint = {maine: '923#0'};
tile.rttObjects[0] = obj;
const otherTileID = new OverscaledTileID(3, 0, 2, 2, 2);
(vi.mocked(terrain.tileManager.getTerrainCoords)).mockReturnValueOnce({[tile.tileID.key]: otherTileID});
(vi.mocked(painter.releaseRTT)).mockClear();
rtt.prepareForRender(style, 0);
expect(tile.getRTT(0)).toBeUndefined();
expect(painter.releaseRTT).toHaveBeenCalledWith(obj);
});
test('should not clear tile cache if state remains same', () => {
rtt.prepareForRender(style, 0);
tile.rttFingerprint = {maine: '923#0'};
tile.rttObjects[0] = {texture: {}, size: 512} as unknown as RTTObject;
rtt.prepareForRender(style, 0);
expect(tile.getRTT(0)).toBeTruthy();
});
test('should render text after a line by not adding the text to the stack', () => {
style._order = ['maine-fill', 'maine-symbol'];
rtt.prepareForRender(style, 0);
layersDrawn = 0;
const renderOptions = {isRenderingToTexture: false, isRenderingGlobe: false};
expect(rtt._renderableLayerIds).toStrictEqual(['maine-fill', 'maine-symbol']);
expect(rtt.renderLayer(fillLayer, renderOptions)).toBeTruthy();
expect(rtt.renderLayer(symbolLayer, renderOptions)).toBeFalsy();
expect(layersDrawn).toBe(1);
});
test('render symbol between rtt layers', () => {
style._order = ['maine-background', 'maine-fill', 'maine-raster', 'maine-hillshade', 'maine-symbol', 'maine-line', 'maine-symbol'];
rtt.prepareForRender(style, 0);
layersDrawn = 0;
const renderOptions = {isRenderingToTexture: false, isRenderingGlobe: false};
expect(rtt._renderableLayerIds).toStrictEqual(['maine-background', 'maine-fill', 'maine-raster', 'maine-hillshade', 'maine-symbol', 'maine-line', 'maine-symbol']);
expect(rtt.renderLayer(backgroundLayer, renderOptions)).toBeTruthy();
expect(rtt.renderLayer(fillLayer, renderOptions)).toBeTruthy();
expect(rtt.renderLayer(rasterLayer, renderOptions)).toBeTruthy();
expect(rtt.renderLayer(hillshadeLayer, renderOptions)).toBeTruthy();
expect(rtt.renderLayer(symbolLayer, renderOptions)).toBeFalsy();
expect(rtt.renderLayer(lineLayer, renderOptions)).toBeTruthy();
expect(rtt.renderLayer(symbolLayer, renderOptions)).toBeFalsy();
expect(layersDrawn).toBe(2);
});
test('render more symbols between rtt layers', () => {
style._order = ['maine-background', 'maine-symbol', 'maine-hillshade', 'maine-symbol', 'maine-line', 'maine-symbol'];
rtt.prepareForRender(style, 0);
layersDrawn = 0;
const renderOptions = {isRenderingToTexture: false, isRenderingGlobe: false};
expect(rtt._renderableLayerIds).toStrictEqual(['maine-background', 'maine-symbol', 'maine-hillshade', 'maine-symbol', 'maine-line', 'maine-symbol']);
expect(rtt.renderLayer(backgroundLayer, renderOptions)).toBeTruthy();
expect(rtt.renderLayer(symbolLayer, renderOptions)).toBeFalsy();
expect(rtt.renderLayer(hillshadeLayer, renderOptions)).toBeTruthy();
expect(rtt.renderLayer(symbolLayer, renderOptions)).toBeFalsy();
expect(rtt.renderLayer(lineLayer, renderOptions)).toBeTruthy();
expect(rtt.renderLayer(symbolLayer, renderOptions)).toBeFalsy();
expect(layersDrawn).toBe(3);
});
test('should clear tile cache on source state update', () => {
const state = {revision: 0};
(vi.mocked(style.tileManagers['maine'].getState)).mockReturnValue(state as any);
tile.rttObjects[0] = {texture: {}, size: 512} as unknown as RTTObject;
tile.rttFingerprint = {maine: '923#0'};
rtt.prepareForRender(style, 0);
expect(tile.getRTT(0)).toBeTruthy();
state.revision = 1;
rtt.prepareForRender(style, 0);
expect(tile.getRTT(0)).toBeUndefined();
});
test('cache miss acquires an rtt object from the painter and stores it on the tile', () => {
style._order = ['maine-fill', 'maine-symbol'];
rtt.prepareForRender(style, 0);
const acquireSpy = vi.spyOn(painter, 'acquireRTT');
acquireSpy.mockClear();
const renderOptions = {isRenderingToTexture: false, isRenderingGlobe: false};
rtt.renderLayer(fillLayer, renderOptions);
rtt.renderLayer(symbolLayer, renderOptions);
expect(acquireSpy).toHaveBeenCalledWith(rtt.rttSize);
expect(tile.getRTT(0)).toBeTruthy();
expect(tile.getRTT(0).size).toBe(rtt.rttSize);
});
test('cache hit reuses cached RTT and skips acquireRTT', () => {
style._order = ['maine-fill', 'maine-symbol'];
rtt.prepareForRender(style, 0);
const cached = {texture: {}, size: rtt.rttSize} as unknown as RTTObject;
tile.rttObjects[0] = cached;
const acquireSpy = vi.spyOn(painter, 'acquireRTT');
acquireSpy.mockClear();
const renderOptions = {isRenderingToTexture: false, isRenderingGlobe: false};
rtt.renderLayer(fillLayer, renderOptions);
rtt.renderLayer(symbolLayer, renderOptions);
expect(acquireSpy).not.toHaveBeenCalled();
expect(tile.getRTT(0)).toBe(cached);
});
test('prepare only queries sources rendered to texture', () => {
const tileManager = () => ({
getVisibleCoordinates: vi.fn().mockReturnValue([tile.tileID]),
getSource: vi.fn().mockReturnValue({}),
getState: vi.fn().mockReturnValue({revision: 0})
});
const maineTileManager = tileManager();
const terrainTileManager = tileManager();
const symbolTileManager = tileManager();
const testStyle = {
...style,
terrain: {source: 'terrainSource'},
tileManagers: {
maine: maineTileManager,
terrainSource: terrainTileManager,
symbols: symbolTileManager
},
_order: ['maine-fill', 'symbols'],
_layers: {
'maine-fill': fillLayer,
symbols: {...symbolLayer, id: 'symbols', source: 'symbols'}
}
} as any as Style;
(vi.mocked(terrain.tileManager.getTerrainCoords)).mockClear();
rtt.prepareForRender(testStyle, 0);
expect(maineTileManager.getVisibleCoordinates).toHaveBeenCalledTimes(1);
expect(terrainTileManager.getVisibleCoordinates).not.toHaveBeenCalled();
expect(symbolTileManager.getVisibleCoordinates).not.toHaveBeenCalled();
expect(terrain.tileManager.getTerrainCoords).toHaveBeenCalledTimes(1);
expect(Object.keys(rtt._coordsAscending)).toStrictEqual(['maine']);
});
});