Support paletted rendering in WebGL
This commit is contained in:
@@ -8,6 +8,7 @@ import WebGLTileLayerRenderer, {
|
|||||||
Uniforms,
|
Uniforms,
|
||||||
} from '../renderer/webgl/TileLayer.js';
|
} from '../renderer/webgl/TileLayer.js';
|
||||||
import {
|
import {
|
||||||
|
PALETTE_TEXTURE_ARRAY,
|
||||||
ValueTypes,
|
ValueTypes,
|
||||||
expressionToGlsl,
|
expressionToGlsl,
|
||||||
getStringNumberEquivalent,
|
getStringNumberEquivalent,
|
||||||
@@ -77,6 +78,7 @@ import {assign} from '../obj.js';
|
|||||||
* @property {string} vertexShader The vertex shader.
|
* @property {string} vertexShader The vertex shader.
|
||||||
* @property {string} fragmentShader The fragment shader.
|
* @property {string} fragmentShader The fragment shader.
|
||||||
* @property {Object<string,import("../webgl/Helper.js").UniformValue>} uniforms Uniform definitions.
|
* @property {Object<string,import("../webgl/Helper.js").UniformValue>} uniforms Uniform definitions.
|
||||||
|
* @property {Array<import("../webgl/PaletteTexture.js").default>} paletteTextures Palette textures.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -209,6 +211,12 @@ function parseStyle(style, bandCount) {
|
|||||||
`uniform sampler2D ${Uniforms.TILE_TEXTURE_ARRAY}[${textureCount}];`
|
`uniform sampler2D ${Uniforms.TILE_TEXTURE_ARRAY}[${textureCount}];`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (context.paletteTextures) {
|
||||||
|
uniformDeclarations.push(
|
||||||
|
`uniform sampler2D ${PALETTE_TEXTURE_ARRAY}[${context.paletteTextures.length}];`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const functionDefintions = Object.keys(context.functions).map(function (
|
const functionDefintions = Object.keys(context.functions).map(function (
|
||||||
name
|
name
|
||||||
) {
|
) {
|
||||||
@@ -253,6 +261,7 @@ function parseStyle(style, bandCount) {
|
|||||||
vertexShader: vertexShader,
|
vertexShader: vertexShader,
|
||||||
fragmentShader: fragmentShader,
|
fragmentShader: fragmentShader,
|
||||||
uniforms: uniforms,
|
uniforms: uniforms,
|
||||||
|
paletteTextures: context.paletteTextures,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,6 +336,7 @@ class WebGLTileLayer extends BaseTileLayer {
|
|||||||
fragmentShader: parsedStyle.fragmentShader,
|
fragmentShader: parsedStyle.fragmentShader,
|
||||||
uniforms: parsedStyle.uniforms,
|
uniforms: parsedStyle.uniforms,
|
||||||
cacheSize: this.cacheSize_,
|
cacheSize: this.cacheSize_,
|
||||||
|
paletteTextures: parsedStyle.paletteTextures,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,6 +354,7 @@ class WebGLTileLayer extends BaseTileLayer {
|
|||||||
vertexShader: parsedStyle.vertexShader,
|
vertexShader: parsedStyle.vertexShader,
|
||||||
fragmentShader: parsedStyle.fragmentShader,
|
fragmentShader: parsedStyle.fragmentShader,
|
||||||
uniforms: parsedStyle.uniforms,
|
uniforms: parsedStyle.uniforms,
|
||||||
|
paletteTextures: parsedStyle.paletteTextures,
|
||||||
});
|
});
|
||||||
this.changed();
|
this.changed();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ function getRenderExtent(frameState, extent) {
|
|||||||
* @property {string} fragmentShader Fragment shader source.
|
* @property {string} fragmentShader Fragment shader source.
|
||||||
* @property {Object<string, import("../../webgl/Helper").UniformValue>} [uniforms] Additional uniforms
|
* @property {Object<string, import("../../webgl/Helper").UniformValue>} [uniforms] Additional uniforms
|
||||||
* made available to shaders.
|
* made available to shaders.
|
||||||
|
* @property {Array<import("../../webgl/PaletteTexture.js").default>} [paletteTextures] Palette textures.
|
||||||
* @property {number} [cacheSize=512] The texture cache size.
|
* @property {number} [cacheSize=512] The texture cache size.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -211,6 +212,12 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
this.tileTextureCache_ = new LRUCache(cacheSize);
|
this.tileTextureCache_ = new LRUCache(cacheSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Array<import("../../webgl/PaletteTexture.js").default>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.paletteTextures_ = options.paletteTextures || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -222,6 +229,7 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
|
|||||||
});
|
});
|
||||||
this.vertexShader_ = options.vertexShader;
|
this.vertexShader_ = options.vertexShader;
|
||||||
this.fragmentShader_ = options.fragmentShader;
|
this.fragmentShader_ = options.fragmentShader;
|
||||||
|
this.paletteTextures_ = options.paletteTextures || [];
|
||||||
|
|
||||||
if (this.helper) {
|
if (this.helper) {
|
||||||
this.program_ = this.helper.getProgram(
|
this.program_ = this.helper.getProgram(
|
||||||
@@ -510,19 +518,33 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
|
|||||||
this.helper.bindBuffer(this.indices_);
|
this.helper.bindBuffer(this.indices_);
|
||||||
this.helper.enableAttributes(attributeDescriptions);
|
this.helper.enableAttributes(attributeDescriptions);
|
||||||
|
|
||||||
for (
|
let textureSlot = 0;
|
||||||
let textureIndex = 0;
|
while (textureSlot < tileTexture.textures.length) {
|
||||||
textureIndex < tileTexture.textures.length;
|
const textureProperty = 'TEXTURE' + textureSlot;
|
||||||
++textureIndex
|
const uniformName = `${Uniforms.TILE_TEXTURE_ARRAY}[${textureSlot}]`;
|
||||||
) {
|
|
||||||
const textureProperty = 'TEXTURE' + textureIndex;
|
|
||||||
const uniformName = `${Uniforms.TILE_TEXTURE_ARRAY}[${textureIndex}]`;
|
|
||||||
gl.activeTexture(gl[textureProperty]);
|
gl.activeTexture(gl[textureProperty]);
|
||||||
gl.bindTexture(gl.TEXTURE_2D, tileTexture.textures[textureIndex]);
|
gl.bindTexture(gl.TEXTURE_2D, tileTexture.textures[textureSlot]);
|
||||||
gl.uniform1i(
|
gl.uniform1i(
|
||||||
this.helper.getUniformLocation(uniformName),
|
this.helper.getUniformLocation(uniformName),
|
||||||
textureIndex
|
textureSlot
|
||||||
);
|
);
|
||||||
|
++textureSlot;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (
|
||||||
|
let paletteIndex = 0;
|
||||||
|
paletteIndex < this.paletteTextures_.length;
|
||||||
|
++paletteIndex
|
||||||
|
) {
|
||||||
|
const paletteTexture = this.paletteTextures_[paletteIndex];
|
||||||
|
gl.activeTexture(gl['TEXTURE' + textureSlot]);
|
||||||
|
const texture = paletteTexture.getTexture(gl);
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||||||
|
gl.uniform1i(
|
||||||
|
this.helper.getUniformLocation(paletteTexture.name),
|
||||||
|
textureSlot
|
||||||
|
);
|
||||||
|
++textureSlot;
|
||||||
}
|
}
|
||||||
|
|
||||||
const alpha =
|
const alpha =
|
||||||
|
|||||||
@@ -3,8 +3,9 @@
|
|||||||
* @module ol/style/expressions
|
* @module ol/style/expressions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import PaletteTexture from '../webgl/PaletteTexture.js';
|
||||||
import {Uniforms} from '../renderer/webgl/TileLayer.js';
|
import {Uniforms} from '../renderer/webgl/TileLayer.js';
|
||||||
import {asArray, isStringColor} from '../color.js';
|
import {asArray, fromString, isStringColor} from '../color.js';
|
||||||
import {log2} from '../math.js';
|
import {log2} from '../math.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,6 +78,11 @@ import {log2} from '../math.js';
|
|||||||
* * `['color', red, green, blue, alpha]` creates a `color` value from `number` values; the `alpha` parameter is
|
* * `['color', red, green, blue, alpha]` creates a `color` value from `number` values; the `alpha` parameter is
|
||||||
* optional; if not specified, it will be set to 1.
|
* optional; if not specified, it will be set to 1.
|
||||||
* Note: `red`, `green` and `blue` components must be values between 0 and 255; `alpha` between 0 and 1.
|
* Note: `red`, `green` and `blue` components must be values between 0 and 255; `alpha` between 0 and 1.
|
||||||
|
* * `['palette', index, colors]` picks a `color` value from an array of colors using the given index; the `index`
|
||||||
|
* expression must evaluate to a number; the items in the `colors` array must be strings with hex colors
|
||||||
|
* (e.g. `'#86A136'`), colors using the rgba[a] functional notation (e.g. `'rgb(134, 161, 54)'` or `'rgba(134, 161, 54, 1)'`),
|
||||||
|
* named colors (e.g. `'red'`), or array literals with 3 ([r, g, b]) or 4 ([r, g, b, a]) values (with r, g, and b
|
||||||
|
* in the 0-255 range and a in the 0-1 range).
|
||||||
*
|
*
|
||||||
* Values can either be literals or another operator, as they will be evaluated recursively.
|
* Values can either be literals or another operator, as they will be evaluated recursively.
|
||||||
* Literal values can be of the following types:
|
* Literal values can be of the following types:
|
||||||
@@ -186,6 +192,7 @@ export function isTypeUnique(valueType) {
|
|||||||
* @property {Object<string, number>} stringLiteralsMap This object maps all encountered string values to a number
|
* @property {Object<string, number>} stringLiteralsMap This object maps all encountered string values to a number
|
||||||
* @property {Object<string, string>} functions Lookup of functions used by the style.
|
* @property {Object<string, string>} functions Lookup of functions used by the style.
|
||||||
* @property {number} [bandCount] Number of bands per pixel.
|
* @property {number} [bandCount] Number of bands per pixel.
|
||||||
|
* @property {Array<PaletteTexture>} [paletteTextures] List of palettes used by the style.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -418,6 +425,65 @@ Operators['var'] = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const PALETTE_TEXTURE_ARRAY = 'u_paletteTextures';
|
||||||
|
|
||||||
|
// ['palette', index, colors]
|
||||||
|
Operators['palette'] = {
|
||||||
|
getReturnType: function (args) {
|
||||||
|
return ValueTypes.COLOR;
|
||||||
|
},
|
||||||
|
toGlsl: function (context, args) {
|
||||||
|
assertArgsCount(args, 2);
|
||||||
|
assertNumber(args[0]);
|
||||||
|
const index = expressionToGlsl(context, args[0]);
|
||||||
|
const colors = args[1];
|
||||||
|
if (!Array.isArray(colors)) {
|
||||||
|
throw new Error('The second argument of palette must be an array');
|
||||||
|
}
|
||||||
|
const numColors = colors.length;
|
||||||
|
const palette = new Uint8Array(numColors * 4);
|
||||||
|
for (let i = 0; i < numColors; i++) {
|
||||||
|
const candidate = colors[i];
|
||||||
|
/**
|
||||||
|
* @type {import('../color.js').Color}
|
||||||
|
*/
|
||||||
|
let color;
|
||||||
|
if (typeof candidate === 'string') {
|
||||||
|
color = fromString(candidate);
|
||||||
|
} else {
|
||||||
|
if (!Array.isArray(candidate)) {
|
||||||
|
throw new Error(
|
||||||
|
'The second argument of palette must be an array of strings or colors'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const length = candidate.length;
|
||||||
|
if (length === 4) {
|
||||||
|
color = candidate;
|
||||||
|
} else {
|
||||||
|
if (length !== 3) {
|
||||||
|
throw new Error(
|
||||||
|
`Expected palette color to have 3 or 4 values, got ${length}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
color = [candidate[0], candidate[1], candidate[2], 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const offset = i * 4;
|
||||||
|
palette[offset] = color[0];
|
||||||
|
palette[offset + 1] = color[1];
|
||||||
|
palette[offset + 2] = color[2];
|
||||||
|
palette[offset + 3] = color[3] * 255;
|
||||||
|
}
|
||||||
|
if (!context.paletteTextures) {
|
||||||
|
context.paletteTextures = [];
|
||||||
|
}
|
||||||
|
const paletteName = `${PALETTE_TEXTURE_ARRAY}[${context.paletteTextures.length}]`;
|
||||||
|
const paletteTexture = new PaletteTexture(paletteName, palette);
|
||||||
|
context.paletteTextures.push(paletteTexture);
|
||||||
|
return `texture2D(${paletteName}, vec2((${index} + 0.5) / ${numColors}.0, 0.5))`;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const GET_BAND_VALUE_FUNC = 'getBandValue';
|
const GET_BAND_VALUE_FUNC = 'getBandValue';
|
||||||
|
|
||||||
Operators['band'] = {
|
Operators['band'] = {
|
||||||
|
|||||||
50
src/ol/webgl/PaletteTexture.js
Normal file
50
src/ol/webgl/PaletteTexture.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* @module ol/webgl/PaletteTexture
|
||||||
|
*/
|
||||||
|
|
||||||
|
class PaletteTexture {
|
||||||
|
/**
|
||||||
|
* @param {string} name The name of the texture.
|
||||||
|
* @param {Uint8Array} data The texture data.
|
||||||
|
*/
|
||||||
|
constructor(name, data) {
|
||||||
|
this.name = name;
|
||||||
|
this.data = data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {WebGLTexture}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.texture_ = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {WebGLRenderingContext} gl Rendering context.
|
||||||
|
* @return {WebGLTexture} The texture.
|
||||||
|
*/
|
||||||
|
getTexture(gl) {
|
||||||
|
if (!this.texture_) {
|
||||||
|
const texture = gl.createTexture();
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
||||||
|
gl.texImage2D(
|
||||||
|
gl.TEXTURE_2D,
|
||||||
|
0,
|
||||||
|
gl.RGBA,
|
||||||
|
this.data.length / 4,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
gl.RGBA,
|
||||||
|
gl.UNSIGNED_BYTE,
|
||||||
|
this.data
|
||||||
|
);
|
||||||
|
this.texture_ = texture;
|
||||||
|
}
|
||||||
|
return this.texture_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PaletteTexture;
|
||||||
BIN
test/rendering/cases/webgl-palette/expected.png
Normal file
BIN
test/rendering/cases/webgl-palette/expected.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
62
test/rendering/cases/webgl-palette/main.js
Normal file
62
test/rendering/cases/webgl-palette/main.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import DataTile from '../../../../src/ol/source/DataTile.js';
|
||||||
|
import Map from '../../../../src/ol/Map.js';
|
||||||
|
import TileLayer from '../../../../src/ol/layer/WebGLTile.js';
|
||||||
|
import View from '../../../../src/ol/View.js';
|
||||||
|
|
||||||
|
const size = 256;
|
||||||
|
|
||||||
|
const colors = [
|
||||||
|
'palegoldenrod',
|
||||||
|
'palegreen',
|
||||||
|
'paleturquoise',
|
||||||
|
'palevioletred',
|
||||||
|
'papayawhip',
|
||||||
|
'peachpuff',
|
||||||
|
'peru',
|
||||||
|
'pink',
|
||||||
|
'plum',
|
||||||
|
'powderblue',
|
||||||
|
'rosybrown',
|
||||||
|
'royalblue',
|
||||||
|
'saddlebrown',
|
||||||
|
'salmon',
|
||||||
|
'sandybrown',
|
||||||
|
'seagreen',
|
||||||
|
'seashell',
|
||||||
|
'sienna',
|
||||||
|
'skyblue',
|
||||||
|
];
|
||||||
|
|
||||||
|
new Map({
|
||||||
|
target: 'map',
|
||||||
|
layers: [
|
||||||
|
new TileLayer({
|
||||||
|
source: new DataTile({
|
||||||
|
loader: function () {
|
||||||
|
const data = new Float32Array(size * size);
|
||||||
|
const numColors = colors.length;
|
||||||
|
const numBlocks = 8;
|
||||||
|
const blockSize = size / numBlocks;
|
||||||
|
for (let row = 0; row < size; ++row) {
|
||||||
|
const r = Math.floor(row / blockSize);
|
||||||
|
for (let col = 0; col < size; ++col) {
|
||||||
|
const c = Math.floor(col / blockSize);
|
||||||
|
const colorIndex = (r * numBlocks + c) % numColors;
|
||||||
|
data[row * size + col] = colorIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
style: {
|
||||||
|
color: ['palette', ['band', 1], colors],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
view: new View({
|
||||||
|
center: [0, 0],
|
||||||
|
zoom: 0,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
render({message: 'Renders colors from a palette'});
|
||||||
Reference in New Issue
Block a user