diff --git a/src/ol/layer/WebGLTile.js b/src/ol/layer/WebGLTile.js index 752f4ffab4..64c4b1167e 100644 --- a/src/ol/layer/WebGLTile.js +++ b/src/ol/layer/WebGLTile.js @@ -96,12 +96,22 @@ function parseStyle(style, bandCount) { const vertexShader = ` attribute vec2 ${Attributes.TEXTURE_COORD}; uniform mat4 ${Uniforms.TILE_TRANSFORM}; + uniform float ${Uniforms.TEXTURE_PIXEL_WIDTH}; + uniform float ${Uniforms.TEXTURE_PIXEL_HEIGHT}; + uniform float ${Uniforms.TEXTURE_RESOLUTION}; + uniform float ${Uniforms.TEXTURE_ORIGIN_X}; + uniform float ${Uniforms.TEXTURE_ORIGIN_Y}; uniform float ${Uniforms.DEPTH}; varying vec2 v_textureCoord; + varying vec2 v_mapCoord; void main() { v_textureCoord = ${Attributes.TEXTURE_COORD}; + v_mapCoord = vec2( + ${Uniforms.TEXTURE_ORIGIN_X} + ${Uniforms.TEXTURE_RESOLUTION} * ${Uniforms.TEXTURE_PIXEL_WIDTH} * v_textureCoord[0], + ${Uniforms.TEXTURE_ORIGIN_Y} - ${Uniforms.TEXTURE_RESOLUTION} * ${Uniforms.TEXTURE_PIXEL_HEIGHT} * v_textureCoord[1] + ); gl_Position = ${Uniforms.TILE_TRANSFORM} * vec4(${Attributes.TEXTURE_COORD}, ${Uniforms.DEPTH}, 1.0); } `; @@ -237,6 +247,8 @@ function parseStyle(style, bandCount) { #endif varying vec2 v_textureCoord; + varying vec2 v_mapCoord; + uniform vec4 ${Uniforms.RENDER_EXTENT}; uniform float ${Uniforms.TRANSITION_ALPHA}; uniform float ${Uniforms.TEXTURE_PIXEL_WIDTH}; uniform float ${Uniforms.TEXTURE_PIXEL_HEIGHT}; @@ -248,6 +260,15 @@ function parseStyle(style, bandCount) { ${functionDefintions.join('\n')} void main() { + if ( + v_mapCoord[0] < ${Uniforms.RENDER_EXTENT}[0] || + v_mapCoord[1] < ${Uniforms.RENDER_EXTENT}[1] || + v_mapCoord[0] > ${Uniforms.RENDER_EXTENT}[2] || + v_mapCoord[1] > ${Uniforms.RENDER_EXTENT}[3] + ) { + discard; + } + vec4 color = texture2D(${ Uniforms.TILE_TEXTURE_ARRAY }[0], v_textureCoord); diff --git a/src/ol/renderer/webgl/TileLayer.js b/src/ol/renderer/webgl/TileLayer.js index ed48cc2a6b..959f25e276 100644 --- a/src/ol/renderer/webgl/TileLayer.js +++ b/src/ol/renderer/webgl/TileLayer.js @@ -35,6 +35,10 @@ export const Uniforms = { DEPTH: 'u_depth', TEXTURE_PIXEL_WIDTH: 'u_texturePixelWidth', TEXTURE_PIXEL_HEIGHT: 'u_texturePixelHeight', + TEXTURE_RESOLUTION: 'u_textureResolution', // map units per texture pixel + TEXTURE_ORIGIN_X: 'u_textureOriginX', // map x coordinate of left edge of texture + TEXTURE_ORIGIN_Y: 'u_textureOriginY', // map y coordinate of top edge of texture + RENDER_EXTENT: 'u_renderExtent', // intersection of layer, source, and view extent RESOLUTION: 'u_resolution', ZOOM: 'u_zoom', }; @@ -150,7 +154,7 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer { this.renderComplete = false; /** - * This transform converts tile i, j coordinates to screen coordinates. + * This transform converts texture coordinates to screen coordinates. * @type {import("../../transform.js").Transform} * @private */ @@ -581,6 +585,19 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer { Uniforms.TEXTURE_PIXEL_HEIGHT, tileSize[1] ); + this.helper.setUniformFloatValue( + Uniforms.TEXTURE_RESOLUTION, + tileResolution + ); + this.helper.setUniformFloatValue( + Uniforms.TEXTURE_ORIGIN_X, + tileOrigin[0] + tileCenterI * tileSize[0] * tileResolution + ); + this.helper.setUniformFloatValue( + Uniforms.TEXTURE_ORIGIN_Y, + tileOrigin[1] - tileCenterJ * tileSize[1] * tileResolution + ); + this.helper.setUniformFloatVec4(Uniforms.RENDER_EXTENT, extent); this.helper.setUniformFloatValue( Uniforms.RESOLUTION, viewState.resolution diff --git a/src/ol/webgl/Helper.js b/src/ol/webgl/Helper.js index f7ceb515b8..3adb547403 100644 --- a/src/ol/webgl/Helper.js +++ b/src/ol/webgl/Helper.js @@ -962,6 +962,15 @@ class WebGLHelper extends Disposable { this.getGL().uniform1f(this.getUniformLocation(uniform), value); } + /** + * Give a value for a vec4 uniform + * @param {string} uniform Uniform name + * @param {Array} value Array of length 4. + */ + setUniformFloatVec4(uniform, value) { + this.getGL().uniform4fv(this.getUniformLocation(uniform), value); + } + /** * Give a value for a standard matrix4 uniform * @param {string} uniform Uniform name diff --git a/test/browser/spec/ol/layer/WebGLTile.test.js b/test/browser/spec/ol/layer/WebGLTile.test.js index 5b5a8b81ea..9d84c3f405 100644 --- a/test/browser/spec/ol/layer/WebGLTile.test.js +++ b/test/browser/spec/ol/layer/WebGLTile.test.js @@ -87,7 +87,11 @@ describe('ol/layer/WebGLTile', function () { #else precision mediump float; #endif + varying vec2 v_textureCoord; + varying vec2 v_mapCoord; + + uniform vec4 u_renderExtent; uniform float u_transitionAlpha; uniform float u_texturePixelWidth; uniform float u_texturePixelHeight; @@ -97,7 +101,16 @@ describe('ol/layer/WebGLTile', function () { uniform float u_var_g; uniform float u_var_b; uniform sampler2D u_tileTextures[1]; + void main() { + if ( + v_mapCoord[0] < u_renderExtent[0] || + v_mapCoord[1] < u_renderExtent[1] || + v_mapCoord[0] > u_renderExtent[2] || + v_mapCoord[1] > u_renderExtent[3] + ) { + discard; + } vec4 color = texture2D(u_tileTextures[0], v_textureCoord); color = vec4(u_var_r / 255.0, u_var_g / 255.0, u_var_b / 255.0, 1.0); if (color.a == 0.0) { @@ -112,12 +125,24 @@ describe('ol/layer/WebGLTile', function () { expect(compileShaderSpy.getCall(1).args[0].replace(/[ \n]+/g, ' ')).to.be( ` attribute vec2 a_textureCoord; + uniform mat4 u_tileTransform; + uniform float u_texturePixelWidth; + uniform float u_texturePixelHeight; + uniform float u_textureResolution; + uniform float u_textureOriginX; + uniform float u_textureOriginY; uniform float u_depth; varying vec2 v_textureCoord; + varying vec2 v_mapCoord; + void main() { v_textureCoord = a_textureCoord; + v_mapCoord = vec2( + u_textureOriginX + u_textureResolution * u_texturePixelWidth * v_textureCoord[0], + u_textureOriginY - u_textureResolution * u_texturePixelHeight * v_textureCoord[1] + ); gl_Position = u_tileTransform * vec4(a_textureCoord, u_depth, 1.0); } `.replace(/[ \n]+/g, ' ') @@ -163,6 +188,10 @@ describe('ol/layer/WebGLTile', function () { #else precision mediump float; #endif varying vec2 v_textureCoord; + + varying vec2 v_mapCoord; + + uniform vec4 u_renderExtent; uniform float u_transitionAlpha; uniform float u_texturePixelWidth; uniform float u_texturePixelHeight; @@ -188,6 +217,14 @@ describe('ol/layer/WebGLTile', function () { } void main() { + if ( + v_mapCoord[0] < u_renderExtent[0] || + v_mapCoord[1] < u_renderExtent[1] || + v_mapCoord[0] > u_renderExtent[2] || + v_mapCoord[1] > u_renderExtent[3] + ) { + discard; + } vec4 color = texture2D(u_tileTextures[0], v_textureCoord); color = vec4((getBandValue(4.0, 0.0, 0.0) / 3000.0), (getBandValue(1.0, 0.0, 0.0) / 3000.0), (getBandValue(2.0, 0.0, 0.0) / 3000.0), 1.0); if (color.a == 0.0) { diff --git a/test/rendering/cases/webgl-layer-extent/expected.png b/test/rendering/cases/webgl-layer-extent/expected.png new file mode 100644 index 0000000000..45fd3d2163 Binary files /dev/null and b/test/rendering/cases/webgl-layer-extent/expected.png differ diff --git a/test/rendering/cases/webgl-layer-extent/main.js b/test/rendering/cases/webgl-layer-extent/main.js new file mode 100644 index 0000000000..5e0aa810d6 --- /dev/null +++ b/test/rendering/cases/webgl-layer-extent/main.js @@ -0,0 +1,28 @@ +import Map from '../../../../src/ol/Map.js'; +import TileLayer from '../../../../src/ol/layer/WebGLTile.js'; +import View from '../../../../src/ol/View.js'; +import XYZ from '../../../../src/ol/source/XYZ.js'; +import {useGeographic} from '../../../../src/ol/proj.js'; +useGeographic(); + +new Map({ + layers: [ + new TileLayer({ + extent: [-100, -30, 50, 50], + source: new XYZ({ + url: '/data/tiles/satellite/{z}/{x}/{y}.jpg', + transition: 0, + }), + }), + ], + target: 'map', + view: new View({ + center: [0, 0], + zoom: 0, + rotation: Math.PI / 5, + }), +}); + +render({ + message: 'data outside the layer extent is not rendered', +}); diff --git a/test/rendering/cases/webgl-source-extent/expected.png b/test/rendering/cases/webgl-source-extent/expected.png new file mode 100644 index 0000000000..3fb5db8f80 Binary files /dev/null and b/test/rendering/cases/webgl-source-extent/expected.png differ diff --git a/test/rendering/cases/webgl-source-extent/main.js b/test/rendering/cases/webgl-source-extent/main.js new file mode 100644 index 0000000000..43bfb34ee9 --- /dev/null +++ b/test/rendering/cases/webgl-source-extent/main.js @@ -0,0 +1,58 @@ +import Map from '../../../../src/ol/Map.js'; +import TileGrid from '../../../../src/ol/tilegrid/TileGrid.js'; +import TileLayer from '../../../../src/ol/layer/WebGLTile.js'; +import View from '../../../../src/ol/View.js'; +import XYZ from '../../../../src/ol/source/XYZ.js'; +import {getHeight, getWidth} from '../../../../src/ol/extent.js'; +import {get as getProjection} from '../../../../src/ol/proj.js'; + +const fullExtent = getProjection('EPSG:3857').getExtent(); +const width = getWidth(fullExtent); +const height = getHeight(fullExtent); + +const partialExtent = [ + fullExtent[0], + fullExtent[1] + 0.4 * height, + fullExtent[2] - 0.4 * width, + fullExtent[3], +]; + +function resolutionsFromExtent(extent, maxZoom) { + const height = getHeight(extent); + const width = getWidth(extent); + + const maxResolution = Math.max(width / 256, height / 256); + + const length = maxZoom + 1; + const resolutions = new Array(length); + for (let z = 0; z < length; ++z) { + resolutions[z] = maxResolution / Math.pow(2, z); + } + return resolutions; +} + +new Map({ + layers: [ + new TileLayer({ + source: new XYZ({ + wrapX: false, + url: '/data/tiles/satellite/{z}/{x}/{y}.jpg', + transition: 0, + tileGrid: new TileGrid({ + extent: partialExtent, + resolutions: resolutionsFromExtent(fullExtent, 10), + }), + }), + }), + ], + target: 'map', + view: new View({ + center: [0, 0], + zoom: 0, + rotation: -Math.PI / 8, + }), +}); + +render({ + message: 'data outside the source tile grid extent is not rendered', +});