mirror of
https://github.com/openseadragon/openseadragon.git
synced 2024-11-24 14:16:10 +03:00
Removed date loader (will be handled by future OSD cache system). Attempt to use instanced rendering. Refactoring of the module.
This commit is contained in:
parent
31f9a71109
commit
cd9d340038
@ -70,7 +70,6 @@ module.exports = function(grunt) {
|
|||||||
//Aiosa's webgl drawer - needs optimization, polishing, trimming
|
//Aiosa's webgl drawer - needs optimization, polishing, trimming
|
||||||
"src/webgl/renderer.js",
|
"src/webgl/renderer.js",
|
||||||
"src/webgl/shaderLayer.js",
|
"src/webgl/shaderLayer.js",
|
||||||
"src/webgl/dataLoader.js",
|
|
||||||
"src/webgl/webGLContext.js",
|
"src/webgl/webGLContext.js",
|
||||||
"src/webgl/drawer.js",
|
"src/webgl/drawer.js",
|
||||||
"src/webgl/plainShader.js",
|
"src/webgl/plainShader.js",
|
||||||
|
@ -38,6 +38,12 @@ $.DrawerOptions = class DrawerOptions{
|
|||||||
constructor(options){}
|
constructor(options){}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} Point
|
||||||
|
* @property {number} x
|
||||||
|
* @property {number} y
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class DrawerBase
|
* @class DrawerBase
|
||||||
* @memberof OpenSeadragon
|
* @memberof OpenSeadragon
|
||||||
@ -297,7 +303,7 @@ $.DrawerBase = class DrawerBase{
|
|||||||
* @inner
|
* @inner
|
||||||
* Calculate width and height of the canvas based on viewport dimensions
|
* Calculate width and height of the canvas based on viewport dimensions
|
||||||
* and pixelDensityRatio
|
* and pixelDensityRatio
|
||||||
* @returns {Dictionary} {x, y} size of the canvas
|
* @returns {Point} {x, y} size of the canvas
|
||||||
*/
|
*/
|
||||||
_calculateCanvasSize() {
|
_calculateCanvasSize() {
|
||||||
var pixelDensityRatio = $.pixelDensityRatio;
|
var pixelDensityRatio = $.pixelDensityRatio;
|
||||||
|
@ -272,7 +272,8 @@ $.TileCache.prototype = {
|
|||||||
* @property {CanvasRenderingContext2D} context2D - The context that is being unloaded
|
* @property {CanvasRenderingContext2D} context2D - The context that is being unloaded
|
||||||
*/
|
*/
|
||||||
tiledImage.viewer.raiseEvent("image-unloaded", {
|
tiledImage.viewer.raiseEvent("image-unloaded", {
|
||||||
context2D: context2D
|
context2D: context2D,
|
||||||
|
tile: tile
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,9 +125,12 @@ $.WebGLModule.IDataLoader = class {
|
|||||||
* @param id
|
* @param id
|
||||||
*/
|
*/
|
||||||
free(renderer, id) {
|
free(renderer, id) {
|
||||||
|
const loaded = this.getLoaded(id);
|
||||||
|
if (loaded) {
|
||||||
this.unloadTexture(renderer, id, this.getLoaded(id));
|
this.unloadTexture(renderer, id, this.getLoaded(id));
|
||||||
this.setUnloaded(id);
|
this.setUnloaded(id);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the program is being loaded (set as active)
|
* Called when the program is being loaded (set as active)
|
@ -41,6 +41,7 @@
|
|||||||
* @param {Object} options - Options for this Drawer.
|
* @param {Object} options - Options for this Drawer.
|
||||||
* @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this Drawer.
|
* @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this Drawer.
|
||||||
* @param {OpenSeadragon.Viewport} options.viewport - Reference to Viewer viewport.
|
* @param {OpenSeadragon.Viewport} options.viewport - Reference to Viewer viewport.
|
||||||
|
* @param {boolean} options.twoPassRendering
|
||||||
* @param {Element} options.element - Parent element.
|
* @param {Element} options.element - Parent element.
|
||||||
* @param {Number} [options.debugGridColor] - See debugGridColor in {@link OpenSeadragon.Options} for details.
|
* @param {Number} [options.debugGridColor] - See debugGridColor in {@link OpenSeadragon.Options} for details.
|
||||||
*/
|
*/
|
||||||
@ -49,10 +50,56 @@ $.WebGL = class WebGL extends OpenSeadragon.DrawerBase {
|
|||||||
constructor(options){
|
constructor(options){
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
|
const gl = this.renderer.gl;
|
||||||
|
this.maxTextureUnits = 4 || gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
|
||||||
|
this.maxDrawBufferUnits = gl.getParameter(gl.MAX_DRAW_BUFFERS);
|
||||||
|
|
||||||
|
this._createSinglePassShader('TEXTURE_2D');
|
||||||
|
|
||||||
|
const size = this._calculateCanvasSize();
|
||||||
|
this.renderer.init(size.x, size.y);
|
||||||
|
this._size = size;
|
||||||
|
this.renderer.setDataBlendingEnabled(true);
|
||||||
|
|
||||||
this.destroyed = false;
|
this.destroyed = false;
|
||||||
|
this._textureMap = {};
|
||||||
|
this._renderOffScreenBuffer = gl.createFramebuffer();
|
||||||
|
this._renderOffScreenTextures = [];
|
||||||
|
//batch rendering (artifacts)
|
||||||
|
// this._tileTexturePositions = new Float32Array(this.maxTextureUnits * 8);
|
||||||
|
// this._transformMatrices = new Float32Array(this.maxTextureUnits * 9);
|
||||||
|
|
||||||
|
|
||||||
|
this.viewer.addHandler("resize", this._resizeRenderer.bind(this));
|
||||||
// Add listeners for events that require modifying the scene or camera
|
// Add listeners for events that require modifying the scene or camera
|
||||||
this.viewer.addHandler("tile-ready", this._tileReadyHandler.bind(this));
|
this.viewer.addHandler("tile-ready", this._tileReadyHandler.bind(this));
|
||||||
this.viewer.addHandler("image-unloaded", this.renderer.freeData.bind(this.renderer));
|
this.viewer.addHandler("image-unloaded", (e) => {
|
||||||
|
const tileData = this._textureMap[e.tile.cacheKey];
|
||||||
|
if (tileData.texture) {
|
||||||
|
this.renderer.gl.deleteTexture(tileData.texture);
|
||||||
|
delete this._textureMap[e.tile.cacheKey];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.viewer.world.addHandler("add-item", (e) => {
|
||||||
|
let shader = e.item.source.shader;
|
||||||
|
if (shader) {
|
||||||
|
const targetIndex = this.renderer.getSpecificationsCount();
|
||||||
|
if (this.renderer.addRenderingSpecifications(shader)) {
|
||||||
|
shader._programIndexTarget = targetIndex;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
e.item.source.shader = shader = this.defaultRenderingSpecification;
|
||||||
|
}
|
||||||
|
//set default program: identity
|
||||||
|
shader._programIndexTarget = 0;
|
||||||
|
});
|
||||||
|
this.viewer.world.addHandler("remove-item", (e) => {
|
||||||
|
const tIndex = e.item.source.shader._programIndexTarget;
|
||||||
|
if (tIndex > 0) {
|
||||||
|
this.renderer.setRenderingSpecification(tIndex, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public API required by all Drawer implementations
|
// Public API required by all Drawer implementations
|
||||||
@ -64,6 +111,17 @@ $.WebGL = class WebGL extends OpenSeadragon.DrawerBase {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//todo
|
//todo
|
||||||
|
const gl = this.renderer.gl;
|
||||||
|
this._renderOffScreenTextures.forEach(t => {
|
||||||
|
if (t) {
|
||||||
|
gl.deleteTexture(t);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._renderOffScreenTextures = [];
|
||||||
|
|
||||||
|
if (this._renderOffScreenBuffer) {
|
||||||
|
gl.deleteFramebuffer(this._renderOffScreenBuffer);
|
||||||
|
}
|
||||||
this.destroyed = true;
|
this.destroyed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,8 +152,7 @@ $.WebGL = class WebGL extends OpenSeadragon.DrawerBase {
|
|||||||
* @returns {Element} the canvas to draw into
|
* @returns {Element} the canvas to draw into
|
||||||
*/
|
*/
|
||||||
createDrawingElement(){
|
createDrawingElement(){
|
||||||
|
this.renderer = new $.WebGLModule($.extend(this.options, {
|
||||||
const engine = new $.WebGLModule($.extend(this.options, {
|
|
||||||
uniqueId: "openseadragon",
|
uniqueId: "openseadragon",
|
||||||
"2.0": {
|
"2.0": {
|
||||||
canvasOptions: {
|
canvasOptions: {
|
||||||
@ -103,51 +160,26 @@ $.WebGL = class WebGL extends OpenSeadragon.DrawerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
return this.renderer.canvas;
|
||||||
engine.addRenderingSpecifications({
|
|
||||||
shaders: {
|
|
||||||
renderShader: {
|
|
||||||
type: "identity",
|
|
||||||
dataReferences: [0],
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
engine.prepare();
|
|
||||||
|
|
||||||
const size = this._calculateCanvasSize();
|
|
||||||
engine.init(size.x, size.y);
|
|
||||||
this.viewer.addHandler("resize", this._resizeRenderer.bind(this));
|
|
||||||
this.renderer = engine;
|
|
||||||
this.renderer.setDataBlendingEnabled(true);
|
|
||||||
|
|
||||||
|
enableStencilTest(enabled) {
|
||||||
|
if (enabled) {
|
||||||
|
if (!this._stencilTestEnabled) {
|
||||||
const gl = this.renderer.gl;
|
const gl = this.renderer.gl;
|
||||||
// this._renderToTexture = gl.createTexture();
|
|
||||||
// gl.activeTexture(gl.TEXTURE0);
|
|
||||||
// gl.bindTexture(gl.TEXTURE_2D, this._renderToTexture);
|
|
||||||
// gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, size.x, size.y, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
||||||
// gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
||||||
// 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);
|
|
||||||
//
|
|
||||||
// // set up the framebuffer for render-to-texture
|
|
||||||
// this._glFrameBuffer = gl.createFramebuffer();
|
|
||||||
// gl.bindFramebuffer(gl.FRAMEBUFFER, this._glFrameBuffer);
|
|
||||||
// gl.framebufferTexture2D(
|
|
||||||
// gl.FRAMEBUFFER,
|
|
||||||
// gl.COLOR_ATTACHMENT0, // attach texture as COLOR_ATTACHMENT0
|
|
||||||
// gl.TEXTURE_2D, // attach a 2D texture
|
|
||||||
// this._renderToTexture, // the texture to attach
|
|
||||||
// 0
|
|
||||||
// );
|
|
||||||
// gl.bindFramebuffer(gl.FRAMEBUFFER, this._glFrameBuffer);
|
|
||||||
// gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this._renderToTexture, 0);
|
|
||||||
|
|
||||||
gl.enable(gl.STENCIL_TEST);
|
gl.enable(gl.STENCIL_TEST);
|
||||||
gl.stencilMask(0xff);
|
gl.stencilMask(0xff);
|
||||||
gl.stencilFunc(gl.GREATER, 1, 0xff);
|
gl.stencilFunc(gl.GREATER, 1, 0xff);
|
||||||
gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE);
|
gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE);
|
||||||
return engine.canvas;
|
this._stencilTestEnabled = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this._stencilTestEnabled) {
|
||||||
|
this._stencilTestEnabled = false;
|
||||||
|
const gl = this.renderer.gl;
|
||||||
|
gl.disable(gl.STENCIL_TEST);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -155,6 +187,15 @@ $.WebGL = class WebGL extends OpenSeadragon.DrawerBase {
|
|||||||
* @param {Array} tiledImages Array of TiledImage objects to draw
|
* @param {Array} tiledImages Array of TiledImage objects to draw
|
||||||
*/
|
*/
|
||||||
draw(tiledImages){
|
draw(tiledImages){
|
||||||
|
let twoPassRendering = this.options.twoPassRendering;
|
||||||
|
if (!twoPassRendering) {
|
||||||
|
for (const tiledImage of tiledImages) {
|
||||||
|
if (tiledImage.blendTime > 0) {
|
||||||
|
twoPassRendering = false; //todo set true, now we debug single pass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let viewport = {
|
let viewport = {
|
||||||
bounds: this.viewport.getBoundsNoRotate(true),
|
bounds: this.viewport.getBoundsNoRotate(true),
|
||||||
center: this.viewport.getCenter(true),
|
center: this.viewport.getCenter(true),
|
||||||
@ -162,20 +203,39 @@ $.WebGL = class WebGL extends OpenSeadragon.DrawerBase {
|
|||||||
zoom: this.viewport.getZoom(true)
|
zoom: this.viewport.getZoom(true)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let flipMultiplier = this.viewport.flipped ? -1 : 1;
|
||||||
// let flipMultiplier = this.viewport.flipped ? -1 : 1;
|
|
||||||
// calculate view matrix for viewer
|
// calculate view matrix for viewer
|
||||||
let posMatrix = $.Mat3.makeTranslation(-viewport.center.x, -viewport.center.y);
|
let posMatrix = $.Mat3.makeTranslation(-viewport.center.x, -viewport.center.y);
|
||||||
let scaleMatrix = $.Mat3.makeScaling(2 / viewport.bounds.width, -2 / viewport.bounds.height);
|
let scaleMatrix = $.Mat3.makeScaling(2 / viewport.bounds.width * flipMultiplier, -2 / viewport.bounds.height);
|
||||||
let rotMatrix = $.Mat3.makeRotation(-viewport.rotation);
|
let rotMatrix = $.Mat3.makeRotation(-viewport.rotation);
|
||||||
let viewMatrix = scaleMatrix.multiply(rotMatrix).multiply(posMatrix);
|
let viewMatrix = scaleMatrix.multiply(rotMatrix).multiply(posMatrix);
|
||||||
|
this._batchTextures = Array(this.maxTextureUnits);
|
||||||
|
|
||||||
|
if (twoPassRendering) {
|
||||||
|
this._resizeOffScreenTextures(0);
|
||||||
|
this.enableStencilTest(true);
|
||||||
|
this._drawTwoPass(tiledImages, viewport, viewMatrix);
|
||||||
|
} else {
|
||||||
|
this._resizeOffScreenTextures(tiledImages.length);
|
||||||
|
this.enableStencilTest(false);
|
||||||
|
this._drawSinglePass(tiledImages, viewport, viewMatrix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
tiledImageViewportToImageZoom(tiledImage, viewportZoom) {
|
||||||
|
var ratio = tiledImage._scaleSpring.current.value *
|
||||||
|
tiledImage.viewport._containerInnerSize.x /
|
||||||
|
tiledImage.source.dimensions.x;
|
||||||
|
return ratio * viewportZoom;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_drawSinglePass(tiledImages, viewport, viewMatrix) {
|
||||||
const gl = this.renderer.gl;
|
const gl = this.renderer.gl;
|
||||||
// gl.bindFramebuffer(gl.FRAMEBUFFER, this._glFrameBuffer);
|
|
||||||
// clear the buffer to draw a new image
|
|
||||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||||
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
||||||
|
|
||||||
//iterate over tiled images and draw each one using a two-pass rendering pipeline if needed
|
|
||||||
for (const tiledImage of tiledImages) {
|
for (const tiledImage of tiledImages) {
|
||||||
let tilesToDraw = tiledImage.getTilesToDraw();
|
let tilesToDraw = tiledImage.getTilesToDraw();
|
||||||
|
|
||||||
@ -183,6 +243,20 @@ $.WebGL = class WebGL extends OpenSeadragon.DrawerBase {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//todo better access to the rendering context
|
||||||
|
const shader = this.renderer.specification(0).shaders.renderShader._renderContext;
|
||||||
|
shader.setBlendMode(tiledImage.index === 0 ?
|
||||||
|
"source-over" : tiledImage.compositeOperation || this.viewer.compositeOperation);
|
||||||
|
|
||||||
|
const sourceShader = tiledImage.source.shader;
|
||||||
|
if (tiledImage.debugMode !== this.renderer.getCompiled("debug", sourceShader._programIndexTarget)) {
|
||||||
|
this.buildOptions.debug = tiledImage.debugMode;
|
||||||
|
//todo per image-level debug info :/
|
||||||
|
this.renderer.buildProgram(sourceShader._programIndexTarget, null, true, this.buildOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.renderer.useProgram(sourceShader._programIndexTarget);
|
||||||
gl.clear(gl.STENCIL_BUFFER_BIT);
|
gl.clear(gl.STENCIL_BUFFER_BIT);
|
||||||
|
|
||||||
let overallMatrix = viewMatrix;
|
let overallMatrix = viewMatrix;
|
||||||
@ -198,22 +272,144 @@ $.WebGL = class WebGL extends OpenSeadragon.DrawerBase {
|
|||||||
let localMatrix = t1.multiply(imageRotationMatrix).multiply(t2);
|
let localMatrix = t1.multiply(imageRotationMatrix).multiply(t2);
|
||||||
overallMatrix = viewMatrix.multiply(localMatrix);
|
overallMatrix = viewMatrix.multiply(localMatrix);
|
||||||
}
|
}
|
||||||
|
let pixelSize = this.tiledImageViewportToImageZoom(tiledImage, viewport.zoom);
|
||||||
|
|
||||||
|
//tile level opacity not supported with single pass rendering
|
||||||
|
shader.opacity.set(tiledImage.opacity);
|
||||||
|
|
||||||
|
//batch rendering (artifacts)
|
||||||
|
//let batchSize = 0;
|
||||||
|
|
||||||
|
// iterate over tiles and add data for each one to the buffers
|
||||||
|
for (let tileIndex = tilesToDraw.length - 1; tileIndex >= 0; tileIndex--){
|
||||||
|
const tile = tilesToDraw[tileIndex].tile;
|
||||||
|
const matrix = this._getTileMatrix(tile, tiledImage, overallMatrix);
|
||||||
|
const tileData = this._textureMap[tile.cacheKey];
|
||||||
|
|
||||||
|
this.renderer.processData(tileData.texture, {
|
||||||
|
transform: matrix,
|
||||||
|
zoom: viewport.zoom,
|
||||||
|
pixelSize: pixelSize,
|
||||||
|
textureCoords: tileData.position,
|
||||||
|
});
|
||||||
|
|
||||||
|
//batch rendering (artifacts)
|
||||||
|
// this._transformMatrices.set(matrix, batchSize * 9);
|
||||||
|
// this._tileTexturePositions.set(tileData.position, batchSize * 8);
|
||||||
|
// this._batchTextures[batchSize] = tileData.texture;
|
||||||
|
// batchSize++;
|
||||||
|
// if (batchSize === this.maxTextureUnits) {
|
||||||
|
// console.log("tiles inside", this._tileTexturePositions);
|
||||||
|
// this.renderer.processData(this._batchTextures, {
|
||||||
|
// transform: this._transformMatrices,
|
||||||
|
// zoom: viewport.zoom,
|
||||||
|
// pixelSize: pixelSize,
|
||||||
|
// textureCoords: this._tileTexturePositions,
|
||||||
|
// instanceCount: batchSize
|
||||||
|
// });
|
||||||
|
// batchSize = 0;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
//batch rendering (artifacts)
|
||||||
|
// if (batchSize > 0) {
|
||||||
|
// console.log("tiles outside", this._tileTexturePositions);
|
||||||
|
//
|
||||||
|
// //todo possibly zero out unused, or limit drawing size
|
||||||
|
// this.renderer.processData(this._batchTextures, {
|
||||||
|
// transform: this._transformMatrices,
|
||||||
|
// zoom: viewport.zoom,
|
||||||
|
// pixelSize: pixelSize,
|
||||||
|
// textureCoords: this._tileTexturePositions,
|
||||||
|
// instanceCount: batchSize
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Fire tiled-image-drawn event.
|
||||||
|
// TODO: the image data may not be on the output canvas yet!!
|
||||||
|
if( this.viewer ){
|
||||||
|
/**
|
||||||
|
* Raised when a tiled image is drawn to the canvas. Only valid
|
||||||
|
* for webgl drawer.
|
||||||
|
*
|
||||||
|
* @event tiled-image-drawn
|
||||||
|
* @memberof OpenSeadragon.Viewer
|
||||||
|
* @type {object}
|
||||||
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
||||||
|
* @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.
|
||||||
|
* @property {Array} tiles - An array of Tile objects that were drawn.
|
||||||
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
||||||
|
*/
|
||||||
|
this.viewer.raiseEvent( 'tiled-image-drawn', {
|
||||||
|
tiledImage: tiledImage,
|
||||||
|
tiles: tilesToDraw.map(info => info.tile),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_drawTwoPass(tiledImages, viewport, viewMatrix) {
|
||||||
|
const gl = this.renderer.gl;
|
||||||
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
let drawnItems = 0;
|
||||||
|
|
||||||
|
for (const tiledImage of tiledImages) {
|
||||||
|
let tilesToDraw = tiledImage.getTilesToDraw();
|
||||||
|
|
||||||
|
if (tilesToDraw.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//second pass first: check whether next render won't overflow batch size
|
||||||
//todo better access to the rendering context
|
//todo better access to the rendering context
|
||||||
const shader = this.renderer.specification(0).shaders.renderShader._renderContext;
|
const shader = this.renderer.specification(0).shaders.renderShader._renderContext;
|
||||||
|
shader.setBlendMode(tiledImage.index === 0 ?
|
||||||
|
"source-over" : tiledImage.compositeOperation || this.viewer.compositeOperation);
|
||||||
|
// const willDraw = drawnItems + shader.dataReferences.length;
|
||||||
|
// if (willDraw > this.maxTextureUnits) {
|
||||||
|
// //merge to the output screen
|
||||||
|
// this._bindOffScreenTexture(-1);
|
||||||
|
//
|
||||||
|
// //todo
|
||||||
|
//
|
||||||
|
// drawnItems = 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
this.renderer.useProgram(0); //todo use program based on texture used, e.g. drawing multi output
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
this._bindOffScreenTexture(drawnItems);
|
||||||
|
|
||||||
|
let overallMatrix = viewMatrix;
|
||||||
|
let imageRotation = tiledImage.getRotation(true);
|
||||||
|
// if needed, handle the tiledImage being rotated
|
||||||
|
if( imageRotation % 360 !== 0){
|
||||||
|
let imageRotationMatrix = $.Mat3.makeRotation(-imageRotation * Math.PI / 180);
|
||||||
|
let imageCenter = tiledImage.getBoundsNoRotate(true).getCenter();
|
||||||
|
let t1 = $.Mat3.makeTranslation(imageCenter.x, imageCenter.y);
|
||||||
|
let t2 = $.Mat3.makeTranslation(-imageCenter.x, -imageCenter.y);
|
||||||
|
|
||||||
|
// update the view matrix to account for this image's rotation
|
||||||
|
let localMatrix = t1.multiply(imageRotationMatrix).multiply(t2);
|
||||||
|
overallMatrix = viewMatrix.multiply(localMatrix);
|
||||||
|
}
|
||||||
|
|
||||||
// iterate over tiles and add data for each one to the buffers
|
// iterate over tiles and add data for each one to the buffers
|
||||||
for (let tileIndex = tilesToDraw.length - 1; tileIndex >= 0; tileIndex--){
|
for (let tileIndex = tilesToDraw.length - 1; tileIndex >= 0; tileIndex--){
|
||||||
const tile = tilesToDraw[tileIndex].tile;
|
const tile = tilesToDraw[tileIndex].tile;
|
||||||
|
|
||||||
const matrix = this._getTileMatrix(tile, tiledImage, overallMatrix);
|
const matrix = this._getTileMatrix(tile, tiledImage, overallMatrix);
|
||||||
shader.opacity.set(tile.opacity * tiledImage.opacity);
|
shader.opacity.set(tile.opacity * tiledImage.opacity);
|
||||||
|
const tileData = this._textureMap[tile.cacheKey];
|
||||||
|
|
||||||
//todo pixelSize value (not yet memoized)
|
//todo pixelSize value (not yet memoized)
|
||||||
this.renderer.processData(tile.cacheKey, {
|
this.renderer.processData(tileData.texture, {
|
||||||
transform: matrix,
|
transform: matrix,
|
||||||
zoom: viewport.zoom,
|
zoom: viewport.zoom,
|
||||||
pixelSize: 0
|
pixelSize: 0,
|
||||||
|
textureCoords: tileData.position
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,6 +436,54 @@ $.WebGL = class WebGL extends OpenSeadragon.DrawerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//single pass shaders are built-in shaders compiled from JSON
|
||||||
|
_createSinglePassShader(textureType) {
|
||||||
|
this.defaultRenderingSpecification = {
|
||||||
|
shaders: {
|
||||||
|
renderShader: {
|
||||||
|
type: "identity",
|
||||||
|
dataReferences: [0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.buildOptions = {
|
||||||
|
textureType: textureType,
|
||||||
|
//batch rendering (artifacts)
|
||||||
|
//instanceCount: this.maxTextureUnits,
|
||||||
|
debug: false
|
||||||
|
};
|
||||||
|
const index = this.renderer.getSpecificationsCount();
|
||||||
|
this.renderer.addRenderingSpecifications(this.defaultRenderingSpecification);
|
||||||
|
this.renderer.buildProgram(index, null, true, this.buildOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
//two pass shaders are special
|
||||||
|
_createTwoPassShaderForFirstPass(textureType) {
|
||||||
|
//custom program for two pass processing
|
||||||
|
const gl = this.renderer.gl;
|
||||||
|
const program = gl.createProgram();
|
||||||
|
|
||||||
|
//works only in version dependent matter!
|
||||||
|
const glContext = this.renderer.webglContext;
|
||||||
|
const options = {
|
||||||
|
textureType: textureType
|
||||||
|
};
|
||||||
|
|
||||||
|
glContext.compileVertexShader(program, `
|
||||||
|
uniform mat3 transform_matrix;
|
||||||
|
const vec3 quad[4] = vec3[4] (
|
||||||
|
vec3(0.0, 1.0, 1.0),
|
||||||
|
vec3(0.0, 0.0, 1.0),
|
||||||
|
vec3(1.0, 1.0, 1.0),
|
||||||
|
vec3(1.0, 0.0, 1.0)
|
||||||
|
);`, `
|
||||||
|
gl_Position = vec4(transform_matrix * quad[gl_VertexID], 1);`, options);
|
||||||
|
glContext.compileFragmentShader(program, `
|
||||||
|
uniform int texture_location;`, `
|
||||||
|
blend(osd_texture(texture_location, osd_texture_coords), 0, false)`, options);
|
||||||
|
return program;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the context2d imageSmoothingEnabled parameter
|
* Set the context2d imageSmoothingEnabled parameter
|
||||||
* @param {Boolean} enabled
|
* @param {Boolean} enabled
|
||||||
@ -253,11 +497,12 @@ $.WebGL = class WebGL extends OpenSeadragon.DrawerBase {
|
|||||||
// private
|
// private
|
||||||
_getTileMatrix(tile, tiledImage, viewMatrix){
|
_getTileMatrix(tile, tiledImage, viewMatrix){
|
||||||
// compute offsets that account for tile overlap; needed for calculating the transform matrix appropriately
|
// compute offsets that account for tile overlap; needed for calculating the transform matrix appropriately
|
||||||
|
// x, y, w, h in viewport coords
|
||||||
|
|
||||||
let overlapFraction = this._calculateOverlapFraction(tile, tiledImage);
|
let overlapFraction = this._calculateOverlapFraction(tile, tiledImage);
|
||||||
let xOffset = tile.positionedBounds.width * overlapFraction.x;
|
let xOffset = tile.positionedBounds.width * overlapFraction.x;
|
||||||
let yOffset = tile.positionedBounds.height * overlapFraction.y;
|
let yOffset = tile.positionedBounds.height * overlapFraction.y;
|
||||||
|
|
||||||
// x, y, w, h in viewport coords
|
|
||||||
let x = tile.positionedBounds.x + (tile.x === 0 ? 0 : xOffset);
|
let x = tile.positionedBounds.x + (tile.x === 0 ? 0 : xOffset);
|
||||||
let y = tile.positionedBounds.y + (tile.y === 0 ? 0 : yOffset);
|
let y = tile.positionedBounds.y + (tile.y === 0 ? 0 : yOffset);
|
||||||
let right = tile.positionedBounds.x + tile.positionedBounds.width - (tile.isRightMost ? 0 : xOffset);
|
let right = tile.positionedBounds.x + tile.positionedBounds.width - (tile.isRightMost ? 0 : xOffset);
|
||||||
@ -288,19 +533,116 @@ $.WebGL = class WebGL extends OpenSeadragon.DrawerBase {
|
|||||||
_resizeRenderer(){
|
_resizeRenderer(){
|
||||||
const size = this._calculateCanvasSize();
|
const size = this._calculateCanvasSize();
|
||||||
this.renderer.setDimensions(0, 0, size.x, size.y);
|
this.renderer.setDimensions(0, 0, size.x, size.y);
|
||||||
|
this._size = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
_imageUnloadedHandler(event){
|
_bindOffScreenTexture(index) {
|
||||||
this.renderer.freeData(event.tile.cacheKey);
|
const gl = this.renderer.gl;
|
||||||
|
if (index < 0) {
|
||||||
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
||||||
|
} else {
|
||||||
|
let texture = this._renderOffScreenTextures[index];
|
||||||
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this._renderOffScreenBuffer);
|
||||||
|
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_resizeOffScreenTextures(count) {
|
||||||
|
//create at most count textures, with max texturing units constraint
|
||||||
|
const gl = this.renderer.gl;
|
||||||
|
|
||||||
|
count = Math.min(count, this.maxTextureUnits);
|
||||||
|
|
||||||
|
if (count > 0) {
|
||||||
|
//append or reinitialize textures
|
||||||
|
const rebuildStartIndex =
|
||||||
|
this._renderBufferSize === this._size ?
|
||||||
|
this._renderOffScreenTextures.length : 0;
|
||||||
|
|
||||||
|
let i;
|
||||||
|
for (i = rebuildStartIndex; i < count; i++) {
|
||||||
|
let texture = this._renderOffScreenTextures[i];
|
||||||
|
if (!texture) {
|
||||||
|
this._renderOffScreenTextures[i] = texture = gl.createTexture();
|
||||||
|
}
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||||||
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8,
|
||||||
|
this._size.x, this._size.y, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.MIRRORED_REPEAT);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.MIRRORED_REPEAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
//destroy any textures that we don't need todo maybe just keep dont bother?
|
||||||
|
for (let j = this._renderOffScreenTextures.length - 1; j >= i; j--) {
|
||||||
|
let texture = this._renderOffScreenTextures.pop();
|
||||||
|
gl.deleteTexture(texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._renderBufferSize = this._size;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
//just leave the textures be, freeing consumes time
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
_tileReadyHandler(event){
|
_tileReadyHandler(event){
|
||||||
//todo tile overlap
|
//todo tile overlap
|
||||||
let tile = event.tile;
|
let tile = event.tile;
|
||||||
//todo fix cache system and then this line
|
let tiledImage = event.tiledImage;
|
||||||
//access by default raw tile data, and only access canvas if not cache set
|
if (this._textureMap[tile.cacheKey]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let position,
|
||||||
|
overlap = tiledImage.source.tileOverlap;
|
||||||
|
if( overlap > 0){
|
||||||
|
// calculate the normalized position of the rect to actually draw
|
||||||
|
// discarding overlap.
|
||||||
|
let overlapFraction = this._calculateOverlapFraction(tile, tiledImage);
|
||||||
|
|
||||||
|
let left = tile.x === 0 ? 0 : overlapFraction.x;
|
||||||
|
let top = tile.y === 0 ? 0 : overlapFraction.y;
|
||||||
|
let right = tile.isRightMost ? 1 : 1 - overlapFraction.x;
|
||||||
|
let bottom = tile.isBottomMost ? 1 : 1 - overlapFraction.y;
|
||||||
|
position = new Float32Array([
|
||||||
|
left, bottom,
|
||||||
|
left, top,
|
||||||
|
right, bottom,
|
||||||
|
right, top
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
// no overlap: this texture can use the unit quad as it's position data
|
||||||
|
position = new Float32Array([
|
||||||
|
0, 1,
|
||||||
|
0, 0,
|
||||||
|
1, 1,
|
||||||
|
1, 0
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
//todo rewrite with new cache api, support data arrays
|
||||||
let data = tile.cacheImageRecord ? tile.cacheImageRecord.getData() : tile.getCanvasContext().canvas;
|
let data = tile.cacheImageRecord ? tile.cacheImageRecord.getData() : tile.getCanvasContext().canvas;
|
||||||
this.renderer.loadData(tile.cacheKey, data, tile.sourceBounds.width, tile.sourceBounds.height);
|
|
||||||
|
const options = this.renderer.webglContext.options;
|
||||||
|
const gl = this.renderer.gl;
|
||||||
|
const texture = gl.createTexture();
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, options.wrap);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, options.wrap);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, options.minFilter);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, options.magFilter);
|
||||||
|
gl.texImage2D(gl.TEXTURE_2D,
|
||||||
|
0,
|
||||||
|
gl.RGBA,
|
||||||
|
gl.RGBA,
|
||||||
|
gl.UNSIGNED_BYTE,
|
||||||
|
data);
|
||||||
|
this._textureMap[tile.cacheKey] = {
|
||||||
|
texture: texture,
|
||||||
|
position: position,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
_calculateOverlapFraction(tile, tiledImage){
|
_calculateOverlapFraction(tile, tiledImage){
|
||||||
|
@ -26,7 +26,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
getFragmentShaderExecution() {
|
getFragmentShaderExecution() {
|
||||||
return `return ${this.sampleChannel("tile_texture_coords")};`;
|
return `return ${this.sampleChannel("osd_texture_coords")};`;
|
||||||
|
//return `return vec4(osd_texture_coords, .0, 1.0);`;
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -12,8 +12,8 @@
|
|||||||
$.WebGLModule = class extends $.EventSource {
|
$.WebGLModule = class extends $.EventSource {
|
||||||
/**
|
/**
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
* name: string,
|
* name?: string,
|
||||||
* lossless: boolean,
|
* lossless?: boolean,
|
||||||
* shaders: Object.<string, OpenSeadragon.WebGLModule.ShaderLayerConfig>
|
* shaders: Object.<string, OpenSeadragon.WebGLModule.ShaderLayerConfig>
|
||||||
* }} OpenSeadragon.WebGLModule.RenderingConfig
|
* }} OpenSeadragon.WebGLModule.RenderingConfig
|
||||||
*
|
*
|
||||||
@ -26,11 +26,11 @@ $.WebGLModule = class extends $.EventSource {
|
|||||||
* @type {{TUseChannel,TUseFilter,TIControlConfig}}
|
* @type {{TUseChannel,TUseFilter,TIControlConfig}}
|
||||||
*
|
*
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
* name: string,
|
* name?: string,
|
||||||
* type: string,
|
* type: string,
|
||||||
* visible: boolean,
|
* visible?: boolean,
|
||||||
* dataReferences: number[],
|
* dataReferences: number[],
|
||||||
* params: OpenSeadragon.WebGLModule.ShaderLayerParams
|
* params?: OpenSeadragon.WebGLModule.ShaderLayerParams,
|
||||||
* }} OpenSeadragon.WebGLModule.ShaderLayerConfig
|
* }} OpenSeadragon.WebGLModule.ShaderLayerConfig
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
@ -53,8 +53,6 @@ $.WebGLModule = class extends $.EventSource {
|
|||||||
* @param {boolean} incomingOptions.debug debug mode default false
|
* @param {boolean} incomingOptions.debug debug mode default false
|
||||||
* @param {function} incomingOptions.ready function called when ready
|
* @param {function} incomingOptions.ready function called when ready
|
||||||
* @param {function} incomingOptions.resetCallback function called when user input changed, e.g. changed output of the current rendering
|
* @param {function} incomingOptions.resetCallback function called when user input changed, e.g. changed output of the current rendering
|
||||||
* @param {function} incomingOptions.visualisationInUse function called when a specification is initialized and run
|
|
||||||
* @param {function} incomingOptions.visualisationChanged function called when a visualization swap is performed:
|
|
||||||
* signature f({Visualization} oldVisualisation,{Visualization} newVisualisation)
|
* signature f({Visualization} oldVisualisation,{Visualization} newVisualisation)
|
||||||
* @constructor
|
* @constructor
|
||||||
* @memberOf OpenSeadragon.WebGLModule
|
* @memberOf OpenSeadragon.WebGLModule
|
||||||
@ -77,9 +75,6 @@ $.WebGLModule = class extends $.EventSource {
|
|||||||
this.resetCallback = function() { };
|
this.resetCallback = function() { };
|
||||||
//called once a visualisation is compiled and linked (might not happen)
|
//called once a visualisation is compiled and linked (might not happen)
|
||||||
this.visualisationReady = function(i, visualisation) { };
|
this.visualisationReady = function(i, visualisation) { };
|
||||||
//called once a visualisation is switched to (including first run)
|
|
||||||
this.visualisationInUse = function(visualisation) { };
|
|
||||||
this.visualisationChanged = function(oldVis, newVis) { };
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Debug mode.
|
* Debug mode.
|
||||||
@ -110,7 +105,7 @@ $.WebGLModule = class extends $.EventSource {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* WebGL context
|
* WebGL context
|
||||||
* @member {WebGLRenderingContextBase}
|
* @member {WebGLRenderingContext|WebGL2RenderingContext}
|
||||||
*/
|
*/
|
||||||
this.gl = null;
|
this.gl = null;
|
||||||
|
|
||||||
@ -141,13 +136,11 @@ $.WebGLModule = class extends $.EventSource {
|
|||||||
* @param {string} options.wrap texture wrap parameteri
|
* @param {string} options.wrap texture wrap parameteri
|
||||||
* @param {string} options.magFilter texture filter parameteri
|
* @param {string} options.magFilter texture filter parameteri
|
||||||
* @param {string} options.minFilter texture filter parameteri
|
* @param {string} options.minFilter texture filter parameteri
|
||||||
* @param {string|WebGLModule.IDataLoader} options.dataLoader class name or implementation of a given loader
|
|
||||||
*/
|
*/
|
||||||
const options = {
|
const options = {
|
||||||
wrap: readGlProp("wrap", "MIRRORED_REPEAT"),
|
wrap: readGlProp("wrap", "MIRRORED_REPEAT"),
|
||||||
magFilter: readGlProp("magFilter", "LINEAR"),
|
magFilter: readGlProp("magFilter", "LINEAR"),
|
||||||
minFilter: readGlProp("minFilter", "LINEAR"),
|
minFilter: readGlProp("minFilter", "LINEAR"),
|
||||||
dataLoader: contextOpts.dataLoader || "TEXTURE_2D"
|
|
||||||
};
|
};
|
||||||
this.webglContext = new Context(this, glContext, options);
|
this.webglContext = new Context(this, glContext, options);
|
||||||
}
|
}
|
||||||
@ -159,10 +152,10 @@ $.WebGLModule = class extends $.EventSource {
|
|||||||
*/
|
*/
|
||||||
this.raiseEvent('fatal-error', {message: "Unable to initialize the WebGL renderer.",
|
this.raiseEvent('fatal-error', {message: "Unable to initialize the WebGL renderer.",
|
||||||
details: e});
|
details: e});
|
||||||
console.error(e);
|
$.console.error(e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log(`WebGL ${this.webglContext.getVersion()} Rendering module (ID ${this.uniqueId})`);
|
$.console.log(`WebGL ${this.webglContext.getVersion()} Rendering module (ID ${this.uniqueId})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -171,28 +164,18 @@ $.WebGLModule = class extends $.EventSource {
|
|||||||
* @memberOf OpenSeadragon.WebGLModule
|
* @memberOf OpenSeadragon.WebGLModule
|
||||||
*/
|
*/
|
||||||
reset() {
|
reset() {
|
||||||
this._unloadCurrentProgram();
|
if (this._programs) {
|
||||||
this._programConfigurations = [];
|
Object.values(this._programs).forEach(p => this._unloadProgram(p));
|
||||||
|
}
|
||||||
|
this._programSpecifications = [];
|
||||||
this._dataSources = [];
|
this._dataSources = [];
|
||||||
this._shaderDataIndexToGlobalDataIndex = [];
|
|
||||||
this._origDataSources = [];
|
this._origDataSources = [];
|
||||||
this._programs = {};
|
this._programs = {};
|
||||||
this._program = -1;
|
this._program = -1;
|
||||||
this._prepared = false;
|
|
||||||
this.running = false;
|
this.running = false;
|
||||||
this._initialized = false;
|
this._initialized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if prepare() was called.
|
|
||||||
* @return {boolean}
|
|
||||||
* @instance
|
|
||||||
* @memberOf OpenSeadragon.WebGLModule
|
|
||||||
*/
|
|
||||||
get isPrepared() {
|
|
||||||
return this._prepared;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WebGL target canvas
|
* WebGL target canvas
|
||||||
* @return {HTMLCanvasElement}
|
* @return {HTMLCanvasElement}
|
||||||
@ -236,27 +219,59 @@ $.WebGLModule = class extends $.EventSource {
|
|||||||
this.gl.viewport(x, y, width, height);
|
this.gl.viewport(x, y, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
getCompiled(name, programIndex = this._program) {
|
||||||
|
return this.webglContext.getCompiled(this._programs[programIndex], name);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set program shaders. Vertex shader is set by default a square.
|
* Set program shaders. Vertex shader is set by default a square.
|
||||||
* @param {RenderingConfig} configurations - objects that define the what to render (see Readme)
|
* @param {RenderingConfig} specifications - objects that define the what to render (see Readme)
|
||||||
* @return {boolean} true if loaded successfully
|
* @return {boolean} true if loaded successfully
|
||||||
* @instance
|
* @instance
|
||||||
* @memberOf OpenSeadragon.WebGLModule
|
* @memberOf OpenSeadragon.WebGLModule
|
||||||
*/
|
*/
|
||||||
addRenderingSpecifications(...configurations) {
|
addRenderingSpecifications(...specifications) {
|
||||||
if (this._prepared) {
|
for (let spec of specifications) {
|
||||||
console.error("New specification cannot be introduced after the visualiser was prepared.");
|
const parsed = this._parseSpec(spec);
|
||||||
|
if (parsed) {
|
||||||
|
this._programSpecifications.push(parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
setRenderingSpecification(i, spec) {
|
||||||
|
if (!spec) {
|
||||||
|
const program = this._programs[i];
|
||||||
|
if (program) {
|
||||||
|
this._unloadProgram();
|
||||||
|
}
|
||||||
|
delete this._programs[i];
|
||||||
|
delete this._programSpecifications[i];
|
||||||
|
this.getCurrentProgramIndex();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
const parsed = this._parseSpec(spec);
|
||||||
|
if (parsed) {
|
||||||
|
this._programSpecifications[i] = parsed;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (let config of configurations) {
|
|
||||||
if (!config.shaders) {
|
_parseSpec(spec) {
|
||||||
console.warn("Invalid visualization: no shaders defined", config);
|
if (!spec.shaders) {
|
||||||
continue;
|
$.console.warn("Invalid visualization: no shaders defined", spec);
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
for (let sid in config.shaders) {
|
for (let sid in spec.shaders) {
|
||||||
const shader = config.shaders[sid];
|
const shader = spec.shaders[sid];
|
||||||
if (!shader.params) {
|
if (!shader.params) {
|
||||||
shader.params = {};
|
shader.params = {};
|
||||||
}
|
}
|
||||||
@ -264,22 +279,48 @@ $.WebGLModule = class extends $.EventSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (count < 0) {
|
if (count < 0) {
|
||||||
console.warn("Invalid configualization: no shader configuration present!", config);
|
$.console.warn("Invalid rendering specs: no shader configuration present!", spec);
|
||||||
continue;
|
return undefined;
|
||||||
}
|
}
|
||||||
this._programConfigurations.push(config);
|
return spec;
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs a callback on each specification
|
*
|
||||||
* @param {function} call callback to perform on each specification (its object given as the only parameter)
|
* @param i
|
||||||
* @instance
|
* @param order
|
||||||
* @memberOf OpenSeadragon.WebGLModule
|
* @param force
|
||||||
|
* @param {object} options
|
||||||
|
* @param {boolean} options.withHtml whether html should be also created (false if no UI controls are desired)
|
||||||
|
* @param {string} options.textureType id of texture to be used, supported are TEXTURE_2D, TEXTURE_2D_ARRAY, TEXTURE_3D
|
||||||
|
* @param {string} options.instanceCount number of instances to draw at once
|
||||||
|
* @param {boolean} options.debug draw debugging info
|
||||||
|
* @return {boolean}
|
||||||
*/
|
*/
|
||||||
foreachRenderingSpecification(call) {
|
buildProgram(i, order, force, options) {
|
||||||
this._programConfigurations.forEach(vis => call(vis));
|
let vis = this._programSpecifications[i];
|
||||||
|
|
||||||
|
if (!vis) {
|
||||||
|
$.console.error("Invalid rendering program target!", i);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order) {
|
||||||
|
vis.order = order;
|
||||||
|
}
|
||||||
|
|
||||||
|
let program = this._programs && this._programs[i];
|
||||||
|
force = force || (program && !program['VERTEX_SHADER']);
|
||||||
|
if (force) {
|
||||||
|
this._unloadProgram(program);
|
||||||
|
this._specificationToProgram(vis, i, options);
|
||||||
|
|
||||||
|
if (i === this._program) {
|
||||||
|
this._forceSwitchShader(this._program);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -289,16 +330,12 @@ $.WebGLModule = class extends $.EventSource {
|
|||||||
* @instance
|
* @instance
|
||||||
* @memberOf OpenSeadragon.WebGLModule
|
* @memberOf OpenSeadragon.WebGLModule
|
||||||
*/
|
*/
|
||||||
rebuildSpecification(order = undefined) {
|
rebuildCurrentProgram(order = undefined) {
|
||||||
let vis = this._programConfigurations[this._program];
|
const program = this._programs[this._program];
|
||||||
|
if (this.buildProgram(this._program, order, true, program && program._osdOptions)) {
|
||||||
if (order) {
|
|
||||||
vis.order = order;
|
|
||||||
}
|
|
||||||
this._unloadCurrentProgram();
|
|
||||||
this._specificationToProgram(vis, this._program);
|
|
||||||
this._forceSwitchShader(this._program);
|
this._forceSwitchShader(this._program);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get currently used specification
|
* Get currently used specification
|
||||||
@ -307,7 +344,7 @@ $.WebGLModule = class extends $.EventSource {
|
|||||||
* @memberOf OpenSeadragon.WebGLModule
|
* @memberOf OpenSeadragon.WebGLModule
|
||||||
*/
|
*/
|
||||||
specification(index) {
|
specification(index) {
|
||||||
return this._programConfigurations[Math.min(index, this._programConfigurations.length - 1)];
|
return this._programSpecifications[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -328,17 +365,25 @@ $.WebGLModule = class extends $.EventSource {
|
|||||||
* @instance
|
* @instance
|
||||||
* @memberOf OpenSeadragon.WebGLModule
|
* @memberOf OpenSeadragon.WebGLModule
|
||||||
*/
|
*/
|
||||||
useSpecification(i) {
|
useProgram(i) {
|
||||||
if (!this._initialized) {
|
if (!this._initialized) {
|
||||||
console.warn("$.WebGLModule::useSpecification(): not initialized.");
|
$.console.warn("$.WebGLModule::useSpecification(): not initialized.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._program === i) {
|
if (this._program === i) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let oldIndex = this._program;
|
|
||||||
this._forceSwitchShader(i);
|
this._forceSwitchShader(i);
|
||||||
this.visualisationChanged(this._programConfigurations[oldIndex], this._programConfigurations[i]);
|
}
|
||||||
|
|
||||||
|
useCustomProgram(program) {
|
||||||
|
this._program = -1;
|
||||||
|
this.webglContext.programLoaded(program, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSpecificationsCount() {
|
||||||
|
return this._programSpecifications.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -347,57 +392,53 @@ $.WebGLModule = class extends $.EventSource {
|
|||||||
* @memberOf WebGLModule
|
* @memberOf WebGLModule
|
||||||
*/
|
*/
|
||||||
getSources() {
|
getSources() {
|
||||||
//return this._programConfigurations[this._program].dziExtendedUrl;
|
|
||||||
return this._dataSources;
|
return this._dataSources;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Supported: 'wrap', 'minFilter', 'magFilter'
|
* Set data srouces
|
||||||
* @param {string} name WebGL name of the parameter
|
|
||||||
* @param {GLuint} value
|
|
||||||
*/
|
*/
|
||||||
setTextureParam(name, value) {
|
setSources(sources) {
|
||||||
this.webglContext.texture.setTextureParam(name, value);
|
if (!this._initialized) {
|
||||||
|
$.console.warn("$.WebGLModule::useSpecification(): not initialized.");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
this._origDataSources = sources || [];
|
||||||
/**
|
|
||||||
* @param id
|
|
||||||
* @param data
|
|
||||||
* @param width
|
|
||||||
* @param height
|
|
||||||
*/
|
|
||||||
loadData(id, data, width, height) {
|
|
||||||
this.webglContext.texture.load(this, id, data, width, height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders data using WebGL
|
* Renders data using WebGL
|
||||||
* @param {string} id used in loadImage()
|
* @param {GLuint|[GLuint]} texture or texture array for instanced drawing
|
||||||
*
|
*
|
||||||
* @param {object} tileOpts
|
* @param {object} tileOpts
|
||||||
* @param {number} tileOpts.zoom value passed to the shaders as zoom_level
|
* @param {number} tileOpts.zoom value passed to the shaders as zoom_level
|
||||||
* @param {number} tileOpts.pixelSize value passed to the shaders as pixel_size_in_fragments
|
* @param {number} tileOpts.pixelSize value passed to the shaders as pixel_size_in_fragments
|
||||||
* @param {OpenSeadragon.Mat3} tileOpts.transform position of the rendered tile
|
* @param {OpenSeadragon.Mat3|[OpenSeadragon.Mat3]} tileOpts.transform position transform
|
||||||
|
* matrix or flat matrix array (instance drawing)
|
||||||
|
* @param {number?} tileOpts.instanceCount how many instances to draw in case instanced drawing is enabled
|
||||||
*
|
*
|
||||||
* @instance
|
* @instance
|
||||||
* @memberOf WebGLModule
|
* @memberOf WebGLModule
|
||||||
*/
|
*/
|
||||||
processData(id, tileOpts) {
|
processData(texture, tileOpts) {
|
||||||
this.webglContext.programUsed(
|
const spec = this._programSpecifications[this._program];
|
||||||
this.program,
|
if (!spec) {
|
||||||
this._programConfigurations[this._program],
|
$.console.error("Cannot render using invalid specification: did you call useCustomProgram?", this._program);
|
||||||
id,
|
} else {
|
||||||
tileOpts
|
this.webglContext.programUsed(this.program, spec, texture, tileOpts);
|
||||||
);
|
|
||||||
|
|
||||||
// if (this.debug) {
|
// if (this.debug) {
|
||||||
// //todo
|
// //todo
|
||||||
// this._renderDebugIO(data, result);
|
// this._renderDebugIO(data, result);
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
freeData(id) {
|
processCustomData(texture, tileOpts) {
|
||||||
|
this.webglContext.programUsed(this.program, null, texture, tileOpts);
|
||||||
|
// if (this.debug) {
|
||||||
|
// //todo
|
||||||
|
// this._renderDebugIO(data, result);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -431,9 +472,12 @@ $.WebGLModule = class extends $.EventSource {
|
|||||||
static eachValidShaderLayer(vis, callback,
|
static eachValidShaderLayer(vis, callback,
|
||||||
onFail = (layer, e) => {
|
onFail = (layer, e) => {
|
||||||
layer.error = e.message;
|
layer.error = e.message;
|
||||||
console.error(e);
|
$.console.error(e);
|
||||||
}) {
|
}) {
|
||||||
let shaders = vis.shaders;
|
let shaders = vis.shaders;
|
||||||
|
if (!shaders) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
let noError = true;
|
let noError = true;
|
||||||
for (let key in shaders) {
|
for (let key in shaders) {
|
||||||
let shader = shaders[key];
|
let shader = shaders[key];
|
||||||
@ -466,10 +510,13 @@ $.WebGLModule = class extends $.EventSource {
|
|||||||
static eachVisibleShaderLayer(vis, callback,
|
static eachVisibleShaderLayer(vis, callback,
|
||||||
onFail = (layer, e) => {
|
onFail = (layer, e) => {
|
||||||
layer.error = e.message;
|
layer.error = e.message;
|
||||||
console.error(e);
|
$.console.error(e);
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
let shaders = vis.shaders;
|
let shaders = vis.shaders;
|
||||||
|
if (!shaders) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
let noError = true;
|
let noError = true;
|
||||||
for (let key in shaders) {
|
for (let key in shaders) {
|
||||||
//rendering == true means no error
|
//rendering == true means no error
|
||||||
@ -499,7 +546,7 @@ $.WebGLModule = class extends $.EventSource {
|
|||||||
* @return {number} program index
|
* @return {number} program index
|
||||||
*/
|
*/
|
||||||
getCurrentProgramIndex() {
|
getCurrentProgramIndex() {
|
||||||
if (this._program < 0 || this._program >= this._programConfigurations.length) {
|
if (this._program < 0 || this._program >= this._programSpecifications.length) {
|
||||||
this._program = 0;
|
this._program = 0;
|
||||||
}
|
}
|
||||||
return this._program;
|
return this._program;
|
||||||
@ -516,36 +563,21 @@ $.WebGLModule = class extends $.EventSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For easy initialization, do both in once call.
|
* Initialization. It is separated from preparation as this actually initiates the rendering,
|
||||||
* For separate initialization (prepare|init), see functions below.
|
* sometimes this can happen only when other things are ready. Must be performed after
|
||||||
* @param {string[]|undefined} dataSources a list of data identifiers available to the specifications
|
* all the prepare() strategy finished: e.g. as onPrepared. Or use prepareAndInit();
|
||||||
* - specification configurations should not reference data not present in this array
|
|
||||||
* - the module gives you current list of required subset of this list for particular active visualization goal
|
|
||||||
* @param width initialization width
|
|
||||||
* @param height initialization height
|
|
||||||
*/
|
|
||||||
prepareAndInit(dataSources = undefined, width = 1, height = 1) {
|
|
||||||
this.prepare(dataSources);
|
|
||||||
this.init(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepares the WebGL wrapper for being initialized. It is separated from
|
|
||||||
* initialization as this must be finished before OSD is ready (we must be ready to draw when the data comes).
|
|
||||||
* The idea is to open the protocol for OSD in onPrepared.
|
|
||||||
* Shaders are fetched from `specification.url` parameter.
|
|
||||||
*
|
*
|
||||||
* @param {string[]|undefined} dataSources id's of data such that server can understand which image to send (usually paths)
|
* @param {int} width width of the first tile going to be drawn
|
||||||
* @param {number} visIndex index of the initial specification
|
* @param {int} height height of the first tile going to be drawn
|
||||||
|
* @param firstProgram
|
||||||
*/
|
*/
|
||||||
prepare(dataSources = undefined, visIndex = 0) {
|
init(width = 1, height = 1, firstProgram = 0) {
|
||||||
if (this._prepared) {
|
if (this._initialized) {
|
||||||
console.error("Already prepared!");
|
$.console.error("Already initialized!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (this._programSpecifications.length < 1) {
|
||||||
if (this._programConfigurations.length < 1) {
|
$.console.error("No specification specified!");
|
||||||
console.error("No specification specified!");
|
|
||||||
/**
|
/**
|
||||||
* @event fatal-error
|
* @event fatal-error
|
||||||
*/
|
*/
|
||||||
@ -553,35 +585,12 @@ $.WebGLModule = class extends $.EventSource {
|
|||||||
details: "::prepare() called with no specification set."});
|
details: "::prepare() called with no specification set."});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._origDataSources = dataSources || [];
|
this._program = firstProgram;
|
||||||
this._program = visIndex;
|
this.getCurrentProgramIndex(); //validates index
|
||||||
|
|
||||||
this._prepared = true;
|
|
||||||
this.getCurrentProgramIndex(); //resets index
|
|
||||||
this._specificationToProgram(this._programConfigurations[this._program], this._program);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialization. It is separated from preparation as this actually initiates the rendering,
|
|
||||||
* sometimes this can happen only when other things are ready. Must be performed after
|
|
||||||
* all the prepare() strategy finished: e.g. as onPrepared. Or use prepareAndInit();
|
|
||||||
*
|
|
||||||
* @param {int} width width of the first tile going to be drawn
|
|
||||||
* @param {int} height height of the first tile going to be drawn
|
|
||||||
*/
|
|
||||||
init(width = 1, height = 1) {
|
|
||||||
if (!this._prepared) {
|
|
||||||
console.error("The viaGL was not yet prepared. Call prepare() before init()!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this._initialized) {
|
|
||||||
console.error("Already initialized!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._initialized = true;
|
this._initialized = true;
|
||||||
this.setDimensions(width, height);
|
this.setDimensions(width, height);
|
||||||
|
|
||||||
|
|
||||||
//todo rotate anticlockwise to cull backfaces
|
//todo rotate anticlockwise to cull backfaces
|
||||||
this.gl.enable(this.gl.CULL_FACE);
|
this.gl.enable(this.gl.CULL_FACE);
|
||||||
this.gl.cullFace(this.gl.FRONT);
|
this.gl.cullFace(this.gl.FRONT);
|
||||||
@ -604,23 +613,6 @@ $.WebGLModule = class extends $.EventSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Supported are two modes: show and blend
|
|
||||||
* show is the default option, stacking layers by generalized alpha blending
|
|
||||||
* blend is a custom alternative, default is a mask (remove background where foreground.a > 0.001)
|
|
||||||
*
|
|
||||||
* vec4 my_blend(vec4 foreground, vec4 background) {
|
|
||||||
* <<code>> //here goes your blending code
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* @param code GLSL code to blend - must return vec4() and can use
|
|
||||||
* two variables: background, foreground
|
|
||||||
*/
|
|
||||||
setLayerBlending(code) {
|
|
||||||
this.webglContext.setBlendEquation(code);
|
|
||||||
this.rebuildSpecification();
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
///////////// YOU PROBABLY DON'T WANT TO READ/CHANGE FUNCTIONS BELOW
|
///////////// YOU PROBABLY DON'T WANT TO READ/CHANGE FUNCTIONS BELOW
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
@ -659,16 +651,14 @@ $.WebGLModule = class extends $.EventSource {
|
|||||||
i = this._program;
|
i = this._program;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i >= this._programConfigurations.length) {
|
let target = this._programSpecifications[i];
|
||||||
console.error("Invalid specification index ", i, "trying to use index 0...");
|
if (!target) {
|
||||||
if (i === 0) {
|
$.console.error("Invalid rendering target index!", i);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
i = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let target = this._programConfigurations[i];
|
const program = this._programs[i];
|
||||||
if (!this._programs[i]) {
|
if (!program) {
|
||||||
this._specificationToProgram(target, i);
|
this._specificationToProgram(target, i);
|
||||||
} else if (i !== this._program) {
|
} else if (i !== this._program) {
|
||||||
this._updateRequiredDataSources(target);
|
this._updateRequiredDataSources(target);
|
||||||
@ -677,11 +667,11 @@ $.WebGLModule = class extends $.EventSource {
|
|||||||
this._program = i;
|
this._program = i;
|
||||||
if (target.error) {
|
if (target.error) {
|
||||||
if (this.supportsHtmlControls()) {
|
if (this.supportsHtmlControls()) {
|
||||||
this._loadHtml(i, this._program);
|
this._loadHtml(i, program);
|
||||||
}
|
}
|
||||||
this._loadScript(i, this._program);
|
this._loadScript(i);
|
||||||
this.running = false;
|
this.running = false;
|
||||||
if (this._programConfigurations.length < 2) {
|
if (this._programSpecifications.length < 2) {
|
||||||
/**
|
/**
|
||||||
* @event fatal-error
|
* @event fatal-error
|
||||||
*/
|
*/
|
||||||
@ -695,22 +685,21 @@ $.WebGLModule = class extends $.EventSource {
|
|||||||
} else {
|
} else {
|
||||||
this.running = true;
|
this.running = true;
|
||||||
if (this.supportsHtmlControls()) {
|
if (this.supportsHtmlControls()) {
|
||||||
this._loadHtml(i, this._program);
|
this._loadHtml(program);
|
||||||
}
|
}
|
||||||
this._loadDebugInfo();
|
this._loadDebugInfo();
|
||||||
if (!this._loadScript(i, this._program)) {
|
if (!this._loadScript(i)) {
|
||||||
if (!_reset) {
|
if (!_reset) {
|
||||||
throw "Could not build visualization";
|
throw "Could not build visualization";
|
||||||
}
|
}
|
||||||
this._forceSwitchShader(i, false); //force reset in errors
|
this._forceSwitchShader(i, false); //force reset in errors
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.webglContext.programLoaded(this._programs[i], target);
|
this.webglContext.programLoaded(program, target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_unloadCurrentProgram() {
|
_unloadProgram(program) {
|
||||||
let program = this._programs && this._programs[this._program];
|
|
||||||
if (program) {
|
if (program) {
|
||||||
//must remove before attaching new
|
//must remove before attaching new
|
||||||
this._detachShader(program, "VERTEX_SHADER");
|
this._detachShader(program, "VERTEX_SHADER");
|
||||||
@ -718,13 +707,13 @@ $.WebGLModule = class extends $.EventSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_loadHtml(visId) {
|
_loadHtml(program) {
|
||||||
let htmlControls = document.getElementById(this.htmlControlsId);
|
let htmlControls = document.getElementById(this.htmlControlsId);
|
||||||
htmlControls.innerHTML = this._programConfigurations[visId]._built["html"];
|
htmlControls.innerHTML = this.webglContext.getCompiled(program, "html") || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
_loadScript(visId) {
|
_loadScript(visId) {
|
||||||
return $.WebGLModule.eachValidShaderLayer(this._programConfigurations[visId], layer => layer._renderContext.init());
|
return $.WebGLModule.eachValidShaderLayer(this._programSpecifications[visId], layer => layer._renderContext.init());
|
||||||
}
|
}
|
||||||
|
|
||||||
_getDebugInfoPanel() {
|
_getDebugInfoPanel() {
|
||||||
@ -774,33 +763,29 @@ Output:<br><div style="border: 1px solid;display: inline-block; overflow: auto;"
|
|||||||
}
|
}
|
||||||
|
|
||||||
_buildFailed(specification, error) {
|
_buildFailed(specification, error) {
|
||||||
console.error(error);
|
$.console.error(error);
|
||||||
specification.error = "Failed to compose this specification.";
|
specification.error = "Failed to compose this specification.";
|
||||||
specification.desc = error;
|
specification.desc = error;
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildSpecification(order, specification) {
|
_buildSpecification(program, order, specification, options) {
|
||||||
try {
|
try {
|
||||||
let data = this.webglContext.compileSpecification(order, specification,
|
options.withHtml = this.supportsHtmlControls();
|
||||||
this._shaderDataIndexToGlobalDataIndex, this.supportsHtmlControls());
|
const usableShaderCount = this.webglContext.compileSpecification(
|
||||||
|
program, order, specification, options);
|
||||||
|
|
||||||
if (data.usableShaders < 1) {
|
if (usableShaderCount < 1) {
|
||||||
this._buildFailed(specification, `Empty specification: no valid specification has been specified.
|
this._buildFailed(specification, `Empty specification: no valid specification has been specified.
|
||||||
<br><b>Specification setup:</b></br> <code>${JSON.stringify(specification, $.WebGLModule.jsonReplacer)}</code>
|
<br><b>Specification setup:</b></br> <code>${JSON.stringify(specification, $.WebGLModule.jsonReplacer)}</code>
|
||||||
<br><b>Dynamic shader data:</b></br><code>${JSON.stringify(specification.data)}</code>`);
|
<br><b>Dynamic shader data:</b></br><code>${JSON.stringify(specification.data)}</code>`);
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
data.dziExtendedUrl = data.dataUrls.join(",");
|
|
||||||
specification._built = data;
|
|
||||||
|
|
||||||
//preventive
|
//preventive
|
||||||
delete specification.error;
|
delete specification.error;
|
||||||
delete specification.desc;
|
delete specification.desc;
|
||||||
return data;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this._buildFailed(specification, error);
|
this._buildFailed(specification, error);
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_detachShader(program, type) {
|
_detachShader(program, type) {
|
||||||
@ -812,73 +797,9 @@ Output:<br><div style="border: 1px solid;display: inline-block; overflow: auto;"
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_specificationToProgram(vis, idx) {
|
_specificationToProgram(spec, idx, options) {
|
||||||
if (!vis._built) {
|
this._updateRequiredDataSources(spec);
|
||||||
vis._built = {};
|
let gl = this.gl;
|
||||||
}
|
|
||||||
|
|
||||||
this._updateRequiredDataSources(vis);
|
|
||||||
this._processSpecification(vis, idx);
|
|
||||||
return idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
_initializeShaderFactory(ShaderFactoryClass, layer, idx) {
|
|
||||||
if (!ShaderFactoryClass) {
|
|
||||||
layer.error = "Unknown layer type.";
|
|
||||||
layer.desc = `The layer type '${layer.type}' has no associated factory. Missing in 'shaderSources'.`;
|
|
||||||
console.warn("Skipping layer " + layer.name);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
layer._index = idx;
|
|
||||||
layer.visible = layer.visible === undefined ? true : layer.visible;
|
|
||||||
layer._renderContext = new ShaderFactoryClass(`${this.uniqueId}${idx}`, layer.params || {}, {
|
|
||||||
layer: layer,
|
|
||||||
webgl: this.webglContext,
|
|
||||||
invalidate: this.resetCallback,
|
|
||||||
rebuild: this.rebuildSpecification.bind(this, undefined)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_updateRequiredDataSources(specs) {
|
|
||||||
//for now just request all data, later decide in the context on what to really send
|
|
||||||
//might in the future decide to only request used data, now not supported
|
|
||||||
let usedIds = new Set();
|
|
||||||
for (let key in specs.shaders) {
|
|
||||||
let layer = specs.shaders[key];
|
|
||||||
if (layer) {
|
|
||||||
for (let x of layer.dataReferences) {
|
|
||||||
usedIds.add(x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
usedIds = [...usedIds].sort();
|
|
||||||
this._dataSources = [];
|
|
||||||
|
|
||||||
while (usedIds[usedIds.length - 1] >= this._origDataSources.length) {
|
|
||||||
//make sure values are set if user did not provide
|
|
||||||
this._origDataSources.push("__generated_do_not_use__");
|
|
||||||
}
|
|
||||||
|
|
||||||
this._shaderDataIndexToGlobalDataIndex = new Array(
|
|
||||||
Math.max(this._origDataSources.length, usedIds[usedIds.length - 1])
|
|
||||||
).fill(-1);
|
|
||||||
|
|
||||||
for (let id of usedIds) {
|
|
||||||
this._shaderDataIndexToGlobalDataIndex[id] = this._dataSources.length;
|
|
||||||
this._dataSources.push(this._origDataSources[id]);
|
|
||||||
while (id > this._shaderDataIndexToGlobalDataIndex.length) {
|
|
||||||
this._shaderDataIndexToGlobalDataIndex.push(-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_processSpecification(spec, idx) {
|
|
||||||
let gl = this.gl,
|
|
||||||
err = function(message, description) {
|
|
||||||
spec.error = message;
|
|
||||||
spec.desc = description;
|
|
||||||
};
|
|
||||||
|
|
||||||
let program;
|
let program;
|
||||||
|
|
||||||
if (!this._programs[idx]) {
|
if (!this._programs[idx]) {
|
||||||
@ -923,55 +844,50 @@ Output:<br><div style="border: 1px solid;display: inline-block; overflow: auto;"
|
|||||||
spec.order = Object.keys(spec.shaders);
|
spec.order = Object.keys(spec.shaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._buildSpecification(spec.order, spec);
|
this._buildSpecification(program, spec.order, spec, options);
|
||||||
|
|
||||||
if (spec.error) {
|
|
||||||
this.visualisationReady(idx, spec);
|
this.visualisationReady(idx, spec);
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
_initializeShaderFactory(ShaderFactoryClass, layer, idx) {
|
||||||
|
if (!ShaderFactoryClass) {
|
||||||
|
layer.error = "Unknown layer type.";
|
||||||
|
layer.desc = `The layer type '${layer.type}' has no associated factory. Missing in 'shaderSources'.`;
|
||||||
|
$.console.warn("Skipping layer " + layer.name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
layer._index = idx;
|
||||||
this.constructor.compileShader(gl, program,
|
layer.visible = layer.visible === undefined ? true : layer.visible;
|
||||||
spec._built.vertexShader, spec._built.fragmentShader, err, this.debug);
|
layer._renderContext = new ShaderFactoryClass(`${this.uniqueId}${idx}`, layer.params || {}, {
|
||||||
this.visualisationReady(idx, spec);
|
layer: layer,
|
||||||
|
webgl: this.webglContext,
|
||||||
|
invalidate: this.resetCallback,
|
||||||
|
rebuild: this.rebuildCurrentProgram.bind(this, undefined)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static compileShader(gl, program, VS, FS, onError, isDebugMode) {
|
_updateRequiredDataSources(specs) {
|
||||||
function ok (kind, status, value, sh) {
|
//for now just request all data, later decide in the context on what to really send
|
||||||
if (!gl['get' + kind + 'Parameter'](value, gl[status + '_STATUS'])) {
|
//might in the future decide to only request used data, now not supported
|
||||||
console.error((sh || 'LINK') + ':\n' + gl['get' + kind + 'InfoLog'](value));
|
let usedIds = new Set();
|
||||||
return false;
|
for (let key in specs.shaders) {
|
||||||
|
let layer = specs.shaders[key];
|
||||||
|
if (layer) {
|
||||||
|
for (let x of layer.dataReferences) {
|
||||||
|
usedIds.add(x);
|
||||||
}
|
}
|
||||||
return true;
|
}
|
||||||
|
}
|
||||||
|
usedIds = [...usedIds].sort();
|
||||||
|
this._dataSources = [];
|
||||||
|
|
||||||
|
while (usedIds[usedIds.length - 1] >= this._origDataSources.length) {
|
||||||
|
//make sure values are set if user did not provide
|
||||||
|
this._origDataSources.push("__generated_do_not_use__");
|
||||||
}
|
}
|
||||||
|
|
||||||
function useShader(gl, program, data, type) {
|
for (let id of usedIds) {
|
||||||
let shader = gl.createShader(gl[type]);
|
this._dataSources.push(this._origDataSources[id]);
|
||||||
gl.shaderSource(shader, data);
|
|
||||||
gl.compileShader(shader);
|
|
||||||
gl.attachShader(program, shader);
|
|
||||||
program[type] = shader;
|
|
||||||
return ok('Shader', 'COMPILE', shader, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
function numberLines(str) {
|
|
||||||
//https://stackoverflow.com/questions/49714971/how-to-add-line-numbers-to-beginning-of-each-line-in-string-in-javascript
|
|
||||||
return str.split('\n').map((line, index) => `${index + 1} ${line}`).join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!useShader(gl, program, VS, 'VERTEX_SHADER') ||
|
|
||||||
!useShader(gl, program, FS, 'FRAGMENT_SHADER')) {
|
|
||||||
onError("Unable to use this specification.",
|
|
||||||
"Compilation of shader failed. For more information, see logs in the console.");
|
|
||||||
console.warn("VERTEX SHADER\n", numberLines( VS ));
|
|
||||||
console.warn("FRAGMENT SHADER\n", numberLines( FS ));
|
|
||||||
} else {
|
|
||||||
gl.linkProgram(program);
|
|
||||||
if (!ok('Program', 'LINK', program)) {
|
|
||||||
onError("Unable to use this specification.",
|
|
||||||
"Linking of shader failed. For more information, see logs in the console.");
|
|
||||||
} else { //if (isDebugMode) { //todo testing
|
|
||||||
console.info("FRAGMENT SHADER\n", numberLines( FS ));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -8,7 +8,7 @@ $.WebGLModule.ShaderMediator = class {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Register shader
|
* Register shader
|
||||||
* @param {function} LayerRendererClass class extends OpenSeadragon.WebGLModule.ShaderLayer
|
* @param {typeof OpenSeadragon.WebGLModule.ShaderLayer} LayerRendererClass static class definition
|
||||||
*/
|
*/
|
||||||
static registerLayer(LayerRendererClass) {
|
static registerLayer(LayerRendererClass) {
|
||||||
//todo why not hasOwnProperty check allowed by syntax checker
|
//todo why not hasOwnProperty check allowed by syntax checker
|
||||||
@ -18,9 +18,16 @@ $.WebGLModule.ShaderMediator = class {
|
|||||||
// if (!$.WebGLModule.ShaderLayer.isPrototypeOf(LayerRendererClass)) {
|
// if (!$.WebGLModule.ShaderLayer.isPrototypeOf(LayerRendererClass)) {
|
||||||
// throw `${LayerRendererClass} does not inherit from ShaderLayer!`;
|
// throw `${LayerRendererClass} does not inherit from ShaderLayer!`;
|
||||||
// }
|
// }
|
||||||
|
if (!this.acceptsShaders) {
|
||||||
|
$.console.error("Registering layer renderer when registering disabled!", LayerRendererClass.type());
|
||||||
|
}
|
||||||
this._layers[LayerRendererClass.type()] = LayerRendererClass;
|
this._layers[LayerRendererClass.type()] = LayerRendererClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static setAcceptsRegistrations(accepts) {
|
||||||
|
this.acceptsShaders = accepts;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the shader class by type id
|
* Get the shader class by type id
|
||||||
* @param {string} id
|
* @param {string} id
|
||||||
@ -32,16 +39,52 @@ $.WebGLModule.ShaderMediator = class {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all available shaders
|
* Get all available shaders
|
||||||
* @return {function[]} classes that extend OpenSeadragon.WebGLModule.ShaderLayer
|
* @return {typeof OpenSeadragon.WebGLModule.ShaderLayer[]} classes that extend OpenSeadragon.WebGLModule.ShaderLayer
|
||||||
*/
|
*/
|
||||||
static availableShaders() {
|
static availableShaders() {
|
||||||
return Object.values(this._layers);
|
return Object.values(this._layers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all available shaders
|
||||||
|
* @return {string[]} classes that extend OpenSeadragon.WebGLModule.ShaderLayer
|
||||||
|
*/
|
||||||
|
static availableTypes() {
|
||||||
|
return Object.keys(this._layers);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
//todo why cannot be inside object :/
|
//todo why cannot be inside object :/
|
||||||
|
$.WebGLModule.ShaderMediator.acceptsShaders = true;
|
||||||
$.WebGLModule.ShaderMediator._layers = {};
|
$.WebGLModule.ShaderMediator._layers = {};
|
||||||
|
|
||||||
|
$.WebGLModule.BLEND_MODE = {
|
||||||
|
'source-over': 0,
|
||||||
|
'source-in': 1,
|
||||||
|
'source-out': 1,
|
||||||
|
'source-atop': 1,
|
||||||
|
'destination-over': 1,
|
||||||
|
'destination-in': 1,
|
||||||
|
'destination-out': 1,
|
||||||
|
'destination-atop': 1,
|
||||||
|
lighten: 1,
|
||||||
|
darken: 1,
|
||||||
|
copy: 1,
|
||||||
|
xor: 1,
|
||||||
|
multiply: 1,
|
||||||
|
screen: 1,
|
||||||
|
overlay: 1,
|
||||||
|
'color-dodge': 1,
|
||||||
|
'color-burn': 1,
|
||||||
|
'hard-light': 1,
|
||||||
|
'soft-light': 1,
|
||||||
|
difference: 1,
|
||||||
|
exclusion: 1,
|
||||||
|
hue: 1,
|
||||||
|
saturation: 1,
|
||||||
|
color: 1,
|
||||||
|
luminosity: 1
|
||||||
|
};
|
||||||
|
$.WebGLModule.BLEND_MODE_MULTIPLY = 1;
|
||||||
/**
|
/**
|
||||||
* Abstract interface to any Shader.
|
* Abstract interface to any Shader.
|
||||||
* @abstract
|
* @abstract
|
||||||
@ -103,6 +146,9 @@ $.WebGLModule.ShaderLayer = class {
|
|||||||
this._buildControls(options);
|
this._buildControls(options);
|
||||||
this.resetChannel(options);
|
this.resetChannel(options);
|
||||||
this.resetMode(options);
|
this.resetMode(options);
|
||||||
|
this._blendUniform = null;
|
||||||
|
this._clipUniform = null;
|
||||||
|
this.blendMode = $.WebGLModule.BLEND_MODE["source-over"];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -121,8 +167,10 @@ $.WebGLModule.ShaderLayer = class {
|
|||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
getFragmentShaderDefinition() {
|
getFragmentShaderDefinition() {
|
||||||
|
this._blendUniform = `${this.uid}_blend`;
|
||||||
|
this._clipUniform = `${this.uid}_clip`;
|
||||||
let controls = this.constructor.defaultControls,
|
let controls = this.constructor.defaultControls,
|
||||||
html = [];
|
glsl = [`uniform int ${this._blendUniform};`, `uniform bool ${this._clipUniform};`];
|
||||||
for (let control in controls) {
|
for (let control in controls) {
|
||||||
if (control.startsWith("use_")) {
|
if (control.startsWith("use_")) {
|
||||||
continue;
|
continue;
|
||||||
@ -133,11 +181,19 @@ $.WebGLModule.ShaderLayer = class {
|
|||||||
let code = controlObject.define();
|
let code = controlObject.define();
|
||||||
if (code) {
|
if (code) {
|
||||||
code = code.trim();
|
code = code.trim();
|
||||||
html.push(code);
|
glsl.push(code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return html.join("\n");
|
return glsl.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
setBlendMode(name) {
|
||||||
|
const modes = $.WebGLModule.BLEND_MODE;
|
||||||
|
this.blendMode = modes[name];
|
||||||
|
if (this.blendMode === undefined) {
|
||||||
|
this.blendMode = modes["source-over"];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -159,6 +215,11 @@ $.WebGLModule.ShaderLayer = class {
|
|||||||
* @param {WebGLRenderingContextBase} gl
|
* @param {WebGLRenderingContextBase} gl
|
||||||
*/
|
*/
|
||||||
glDrawing(program, gl) {
|
glDrawing(program, gl) {
|
||||||
|
if (this._blendUniform) {
|
||||||
|
gl.uniform1i(this._blendLoc, this.blendMode);
|
||||||
|
gl.uniform1i(this._clipLoc, 0); //todo
|
||||||
|
}
|
||||||
|
|
||||||
let controls = this.constructor.defaultControls;
|
let controls = this.constructor.defaultControls;
|
||||||
for (let control in controls) {
|
for (let control in controls) {
|
||||||
if (control.startsWith("use_")) {
|
if (control.startsWith("use_")) {
|
||||||
@ -178,6 +239,13 @@ $.WebGLModule.ShaderLayer = class {
|
|||||||
* @param {WebGLRenderingContextBase} gl WebGL Context
|
* @param {WebGLRenderingContextBase} gl WebGL Context
|
||||||
*/
|
*/
|
||||||
glLoaded(program, gl) {
|
glLoaded(program, gl) {
|
||||||
|
if (!this._blendUniform) {
|
||||||
|
$.console.warn("Shader layer has autoblending disabled: are you sure you call super.getFragmentShaderDefinition()?");
|
||||||
|
} else {
|
||||||
|
this._clipLoc = gl.getUniformLocation(program, this._clipUniform);
|
||||||
|
this._blendLoc = gl.getUniformLocation(program, this._blendUniform);
|
||||||
|
}
|
||||||
|
|
||||||
let controls = this.constructor.defaultControls;
|
let controls = this.constructor.defaultControls;
|
||||||
for (let control in controls) {
|
for (let control in controls) {
|
||||||
if (control.startsWith("use_")) {
|
if (control.startsWith("use_")) {
|
||||||
@ -295,7 +363,7 @@ $.WebGLModule.ShaderLayer = class {
|
|||||||
return 'vec4(0.0)';
|
return 'vec4(0.0)';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let sampled = `${this.webglContext.texture.sample(refs[otherDataIndex], textureCoords)}.${chan}`;
|
let sampled = `${this.webglContext.sampleTexture(refs[otherDataIndex], textureCoords)}.${chan}`;
|
||||||
// if (raw) return sampled;
|
// if (raw) return sampled;
|
||||||
// return this.filter(sampled);
|
// return this.filter(sampled);
|
||||||
return sampled;
|
return sampled;
|
||||||
|
@ -16,6 +16,15 @@ $.WebGLModule.determineContext = function( version ){
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function iterate(n) {
|
||||||
|
let result = Array(n),
|
||||||
|
it = 0;
|
||||||
|
while (it < n) {
|
||||||
|
result[it] = it++;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @interface OpenSeadragon.WebGLModule.webglContext
|
* @interface OpenSeadragon.WebGLModule.webglContext
|
||||||
* Interface for the visualisation rendering implementation which can run
|
* Interface for the visualisation rendering implementation which can run
|
||||||
@ -26,35 +35,18 @@ $.WebGLModule.WebGLImplementation = class {
|
|||||||
/**
|
/**
|
||||||
* Create a WebGL Renderer Context Implementation (version-dependent)
|
* Create a WebGL Renderer Context Implementation (version-dependent)
|
||||||
* @param {WebGLModule} renderer
|
* @param {WebGLModule} renderer
|
||||||
* @param {WebGLRenderingContextBase} gl
|
* @param {WebGLRenderingContext|WebGL2RenderingContext} gl
|
||||||
* @param webglVersion
|
* @param webglVersion
|
||||||
* @param {object} options
|
* @param {object} options
|
||||||
* @param {GLuint} options.wrap texture wrap parameteri
|
* @param {GLuint} options.wrap texture wrap parameteri
|
||||||
* @param {GLuint} options.magFilter texture filter parameteri
|
* @param {GLuint} options.magFilter texture filter parameteri
|
||||||
* @param {GLuint} options.minFilter texture filter parameteri
|
* @param {GLuint} options.minFilter texture filter parameteri
|
||||||
* @param {string|WebGLModule.IDataLoader} options.dataLoader class name or implementation of a given loader
|
|
||||||
*/
|
*/
|
||||||
constructor(renderer, gl, webglVersion, options) {
|
constructor(renderer, gl, webglVersion, options) {
|
||||||
//Set default blending to be MASK
|
//Set default blending to be MASK
|
||||||
this.renderer = renderer;
|
this.renderer = renderer;
|
||||||
this.gl = gl;
|
this.gl = gl;
|
||||||
this.glslBlendCode = "return background * (step(0.001, foreground.a));";
|
this.options = options;
|
||||||
|
|
||||||
let Loader = options.dataLoader;
|
|
||||||
if (typeof Loader === "string") {
|
|
||||||
Loader = $.WebGLModule.Loaders[Loader];
|
|
||||||
}
|
|
||||||
if (!Loader) {
|
|
||||||
throw("Unknown data loader: " + options.dataLoader);
|
|
||||||
}
|
|
||||||
if (!(Loader.prototype instanceof $.WebGLModule.IDataLoader)) {
|
|
||||||
throw("Incompatible texture loader used: " + options.dataLoader);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._texture = new Loader(gl, webglVersion, options);
|
|
||||||
if (!this.texture.supportsWebglVersion(this.getVersion())) {
|
|
||||||
throw("Incompatible texture loader version to the renderer context version! Context WebGL" + this.getVersion());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -82,49 +74,81 @@ $.WebGLModule.WebGLImplementation = class {
|
|||||||
return this._texture;
|
return this._texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCompiled(program, name) {
|
||||||
|
throw("::getCompiled() must be implemented!");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a visualisation from the given JSON params
|
* Create a visualisation from the given JSON params
|
||||||
|
* @param program
|
||||||
* @param {string[]} order keys of visualisation.shader in which order to build the visualization
|
* @param {string[]} order keys of visualisation.shader in which order to build the visualization
|
||||||
* the order: painter's algorithm: the last drawn is the most visible
|
* the order: painter's algorithm: the last drawn is the most visible
|
||||||
* @param {object} visualisation
|
* @param {object} visualisation
|
||||||
* @param {[number]} shaderDataIndexToGlobalDataIndex
|
* @param {object} options
|
||||||
* @param {boolean} withHtml whether html should be also created (false if no UI controls are desired)
|
* @param {boolean} options.withHtml whether html should be also created (false if no UI controls are desired)
|
||||||
* @return {object} compiled specification object ready to be used by the wrapper, with the following keys:
|
* @param {string} options.textureType id of texture to be used, supported are TEXTURE_2D, TEXTURE_2D_ARRAY, TEXTURE_3D
|
||||||
{string} object.vertexShader vertex shader code
|
* @param {string} options.instanceCount number of instances to draw at once
|
||||||
{string} object.fragmentShader fragment shader code
|
* @return {number} amount of usable shaders
|
||||||
{string} object.html html for the UI
|
|
||||||
{number} object.usableShaders how many layers are going to be visualised
|
|
||||||
{(array|string[])} object.dataUrls ID's of data in use (keys of visualisation.shaders object) in desired order
|
|
||||||
the data is guaranteed to arrive in this order (images stacked below each other in imageElement)
|
|
||||||
*/
|
*/
|
||||||
compileSpecification(order, visualisation, shaderDataIndexToGlobalDataIndex, withHtml) {
|
compileSpecification(program, order, visualisation, options) {
|
||||||
throw("::compileSpecification() must be implemented!");
|
throw("::compileSpecification() must be implemented!");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called once program is switched to: initialize all necessary items
|
* Called once program is switched to: initialize all necessary items
|
||||||
* @param {WebGLProgram} program used program
|
* @param {WebGLProgram} program used program
|
||||||
* @param {OpenSeadragon.WebGLModule.RenderingConfig} currentConfig JSON parameters used for this visualisation
|
* @param {OpenSeadragon.WebGLModule.RenderingConfig?} currentConfig JSON parameters used for this visualisation
|
||||||
*/
|
*/
|
||||||
programLoaded(program, currentConfig) {
|
programLoaded(program, currentConfig = null) {
|
||||||
throw("::programLoaded() must be implemented!");
|
throw("::programLoaded() must be implemented!");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draw on the canvas using given program
|
* Draw on the canvas using given program
|
||||||
* @param {WebGLProgram} program used program
|
* @param {WebGLProgram} program used program
|
||||||
* @param {OpenSeadragon.WebGLModule.RenderingConfig} currentConfig JSON parameters used for this visualisation
|
* @param {OpenSeadragon.WebGLModule.RenderingConfig?} currentConfig JSON parameters used for this visualisation
|
||||||
*
|
* @param {GLuint} texture
|
||||||
* @param {string} id dataId
|
|
||||||
* @param {object} tileOpts
|
* @param {object} tileOpts
|
||||||
* @param {number} tileOpts.zoom value passed to the shaders as zoom_level
|
* @param {number} tileOpts.zoom value passed to the shaders as zoom_level
|
||||||
* @param {number} tileOpts.pixelSize value passed to the shaders as pixel_size_in_fragments
|
* @param {number} tileOpts.pixelSize value passed to the shaders as pixel_size_in_fragments
|
||||||
* @param {OpenSeadragon.Mat3} tileOpts.transform position of the rendered tile
|
* @param {OpenSeadragon.Mat3|[OpenSeadragon.Mat3]} tileOpts.transform position transform
|
||||||
|
* @param {number?} tileOpts.instanceCount how many instances to draw in case instanced drawing is enabled
|
||||||
|
* matrix or flat matrix array (instance drawing)
|
||||||
*/
|
*/
|
||||||
programUsed(program, currentConfig, id, tileOpts) {
|
programUsed(program, currentConfig, texture, tileOpts = {}) {
|
||||||
throw("::programUsed() must be implemented!");
|
throw("::programUsed() must be implemented!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sampleTexture(index, vec2coords) {
|
||||||
|
throw("::sampleTexture() must be implemented!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {WebGLProgram} program
|
||||||
|
* @param definition
|
||||||
|
* @param execution
|
||||||
|
* @param {object} options
|
||||||
|
* @param {string} options.textureType id of texture to be used, supported are TEXTURE_2D, TEXTURE_2D_ARRAY, TEXTURE_3D
|
||||||
|
* @param {string} options.instanceCount number of instances to draw at once
|
||||||
|
*/
|
||||||
|
compileFragmentShader(program, definition, execution, options) {
|
||||||
|
throw("::compileFragmentShader() must be implemented!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {WebGLProgram} program
|
||||||
|
* @param definition
|
||||||
|
* @param execution
|
||||||
|
* @param {object} options
|
||||||
|
* @param {string} options.textureType id of texture to be used, supported are TEXTURE_2D, TEXTURE_2D_ARRAY, TEXTURE_3D
|
||||||
|
* @param {string} options.instanceCount number of instances to draw at once
|
||||||
|
*/
|
||||||
|
compileVertexShader(program, definition, execution, options) {
|
||||||
|
throw("::compileVertexShader() must be implemented!");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Code to be included only once, required by given shader type (keys are considered global)
|
* Code to be included only once, required by given shader type (keys are considered global)
|
||||||
* @param {string} type shader type
|
* @param {string} type shader type
|
||||||
@ -167,6 +191,55 @@ $.WebGLModule.WebGLImplementation = class {
|
|||||||
setBlendEquation(glslCode) {
|
setBlendEquation(glslCode) {
|
||||||
this.glslBlendCode = glslCode;
|
this.glslBlendCode = glslCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_compileProgram(program, onError) {
|
||||||
|
const gl = this.gl;
|
||||||
|
function ok (kind, status, value, sh) {
|
||||||
|
if (!gl['get' + kind + 'Parameter'](value, gl[status + '_STATUS'])) {
|
||||||
|
$.console.error((sh || 'LINK') + ':\n' + gl['get' + kind + 'InfoLog'](value));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function useShader(gl, program, data, type) {
|
||||||
|
let shader = gl.createShader(gl[type]);
|
||||||
|
gl.shaderSource(shader, data);
|
||||||
|
gl.compileShader(shader);
|
||||||
|
gl.attachShader(program, shader);
|
||||||
|
program[type] = shader;
|
||||||
|
return ok('Shader', 'COMPILE', shader, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
function numberLines(str) {
|
||||||
|
//https://stackoverflow.com/questions/49714971/how-to-add-line-numbers-to-beginning-of-each-line-in-string-in-javascript
|
||||||
|
return str.split('\n').map((line, index) => `${index + 1} ${line}`).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
const opts = program._osdOptions;
|
||||||
|
if (!opts) {
|
||||||
|
$.console.error("Invalid program compilation! Did you build shaders using compile[Type]Shader() methods?");
|
||||||
|
onError("Invalid program.", "Program not compatible with this renderer!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!useShader(gl, program, opts.vs, 'VERTEX_SHADER') ||
|
||||||
|
!useShader(gl, program, opts.fs, 'FRAGMENT_SHADER')) {
|
||||||
|
onError("Unable to use this specification.",
|
||||||
|
"Compilation of shader failed. For more information, see logs in the $.console.");
|
||||||
|
$.console.warn("VERTEX SHADER\n", numberLines( opts.vs ));
|
||||||
|
$.console.warn("FRAGMENT SHADER\n", numberLines( opts.fs ));
|
||||||
|
} else {
|
||||||
|
gl.linkProgram(program);
|
||||||
|
if (!ok('Program', 'LINK', program)) {
|
||||||
|
onError("Unable to use this specification.",
|
||||||
|
"Linking of shader failed. For more information, see logs in the $.console.");
|
||||||
|
} else { //if (this.renderer.debug) { //todo uncomment in production
|
||||||
|
$.console.info("VERTEX SHADER\n", numberLines( opts.vs ));
|
||||||
|
$.console.info("FRAGMENT SHADER\n", numberLines( opts.fs ));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$.WebGLModule.WebGL20 = class extends $.WebGLModule.WebGLImplementation {
|
$.WebGLModule.WebGL20 = class extends $.WebGLModule.WebGLImplementation {
|
||||||
@ -178,7 +251,32 @@ $.WebGLModule.WebGL20 = class extends $.WebGLModule.WebGLImplementation {
|
|||||||
*/
|
*/
|
||||||
constructor(renderer, gl, options) {
|
constructor(renderer, gl, options) {
|
||||||
super(renderer, gl, "2.0", options);
|
super(renderer, gl, "2.0", options);
|
||||||
this.emptyBuffer = gl.createBuffer();
|
|
||||||
|
// this.vao = gl.createVertexArray();
|
||||||
|
this._bufferTexturePosition = gl.createBuffer();
|
||||||
|
|
||||||
|
|
||||||
|
// Create a texture.
|
||||||
|
this.glyphTex = gl.createTexture();
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, this.glyphTex);
|
||||||
|
// Fill the texture with a 1x1 blue pixel.
|
||||||
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE,
|
||||||
|
new Uint8Array([0, 0, 255, 255]));
|
||||||
|
// Asynchronously load an image
|
||||||
|
var image = new Image();
|
||||||
|
image.src = "8x8-font.png";
|
||||||
|
|
||||||
|
const _this = this;
|
||||||
|
image.addEventListener('load', function() {
|
||||||
|
// Now that the image has loaded make copy it to the texture.
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, _this.glyphTex);
|
||||||
|
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
|
||||||
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
|
||||||
|
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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getVersion() {
|
getVersion() {
|
||||||
@ -191,33 +289,40 @@ $.WebGLModule.WebGL20 = class extends $.WebGLModule.WebGLImplementation {
|
|||||||
return canvas.getContext('webgl2', options);
|
return canvas.getContext('webgl2', options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCompiled(program, name) {
|
||||||
|
return program._osdOptions[name];
|
||||||
|
}
|
||||||
|
|
||||||
//todo try to implement on the global scope version-independntly
|
//todo try to implement on the global scope version-independntly
|
||||||
compileSpecification(order, visualisation, shaderDataIndexToGlobalDataIndex, withHtml) {
|
compileSpecification(program, order, specification, options) {
|
||||||
var definition = "",
|
var definition = "",
|
||||||
execution = "",
|
execution = "",
|
||||||
html = "",
|
html = "",
|
||||||
_this = this,
|
_this = this,
|
||||||
usableShaders = 0,
|
usableShaders = 0,
|
||||||
|
dataCount = 0,
|
||||||
globalScopeCode = {};
|
globalScopeCode = {};
|
||||||
|
|
||||||
order.forEach(dataId => {
|
order.forEach(dataId => {
|
||||||
let layer = visualisation.shaders[dataId];
|
let layer = specification.shaders[dataId];
|
||||||
layer.rendering = false;
|
layer.rendering = false;
|
||||||
|
|
||||||
if (layer.type === "none") {
|
if (layer.type === "none") {
|
||||||
//prevents the layer from being accounted for
|
//prevents the layer from being accounted for
|
||||||
layer.error = "Not an error - layer type none.";
|
layer.error = "Not an error - layer type none.";
|
||||||
} else if (layer.error) {
|
} else if (layer.error) {
|
||||||
if (withHtml) {
|
if (options.withHtml) {
|
||||||
html = _this.renderer.htmlShaderPartHeader(layer.name, layer.error, dataId, false, layer, false) + html;
|
html = _this.renderer.htmlShaderPartHeader(layer.name, layer.error, dataId, false, layer, false) + html;
|
||||||
}
|
}
|
||||||
console.warn(layer.error, layer["desc"]);
|
$.console.warn(layer.error, layer["desc"]);
|
||||||
|
|
||||||
} else if (layer._renderContext && (layer._index || layer._index === 0)) {
|
} else if (layer._renderContext && (layer._index || layer._index === 0)) {
|
||||||
|
//todo consider html generating in the renderer
|
||||||
let visible = false;
|
let visible = false;
|
||||||
usableShaders++;
|
usableShaders++;
|
||||||
|
|
||||||
//make visible textures if 'visible' flag set
|
//make visible textures if 'visible' flag set
|
||||||
|
//todo either allways visible or ensure textures do not get loaded
|
||||||
if (layer.visible) {
|
if (layer.visible) {
|
||||||
let renderCtx = layer._renderContext;
|
let renderCtx = layer._renderContext;
|
||||||
definition += renderCtx.getFragmentShaderDefinition() + `
|
definition += renderCtx.getFragmentShaderDefinition() + `
|
||||||
@ -228,137 +333,308 @@ vec4 lid_${layer._index}_xo() {
|
|||||||
execution += `
|
execution += `
|
||||||
vec4 l${layer._index}_out = lid_${layer._index}_xo();
|
vec4 l${layer._index}_out = lid_${layer._index}_xo();
|
||||||
l${layer._index}_out.a *= ${renderCtx.opacity.sample()};
|
l${layer._index}_out.a *= ${renderCtx.opacity.sample()};
|
||||||
${renderCtx.__mode}(l${layer._index}_out);`;
|
blend(l${layer._index}_out, ${renderCtx._blendUniform}, ${renderCtx._clipUniform});`;
|
||||||
} else {
|
} else {
|
||||||
execution += `
|
execution += `
|
||||||
${renderCtx.__mode}(lid_${layer._index}_xo());`;
|
blend(lid_${layer._index}_xo(), ${renderCtx._blendUniform}, ${renderCtx._clipUniform});`; //todo remove ${renderCtx.__mode}
|
||||||
}
|
}
|
||||||
|
|
||||||
layer.rendering = true;
|
layer.rendering = true;
|
||||||
visible = true;
|
visible = true;
|
||||||
OpenSeadragon.extend(globalScopeCode, _this.globalCodeRequiredByShaderType(layer.type));
|
$.extend(globalScopeCode, _this.globalCodeRequiredByShaderType(layer.type));
|
||||||
|
dataCount += layer.dataReferences.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
//reverse order append to show first the last drawn element (top)
|
//reverse order append to show first the last drawn element (top)
|
||||||
if (withHtml) {
|
if (options.withHtml) {
|
||||||
html = _this.renderer.htmlShaderPartHeader(layer.name,
|
html = _this.renderer.htmlShaderPartHeader(layer.name,
|
||||||
layer._renderContext.htmlControls(), dataId, visible, layer, true) + html;
|
layer._renderContext.htmlControls(), dataId, visible, layer, true) + html;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (withHtml) {
|
if (options.withHtml) {
|
||||||
html = _this.renderer.htmlShaderPartHeader(layer.name,
|
html = _this.renderer.htmlShaderPartHeader(layer.name,
|
||||||
`The requested visualisation type does not work properly.`, dataId, false, layer, false) + html;
|
`The requested specification type does not work properly.`, dataId, false, layer, false) + html;
|
||||||
}
|
}
|
||||||
console.warn("Invalid shader part.", "Missing one of the required elements.", layer);
|
$.console.warn("Invalid shader part.", "Missing one of the required elements.", layer);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
if (!options.textureType) {
|
||||||
vertexShader: this.getVertexShader(),
|
if (dataCount === 1) {
|
||||||
fragmentShader: this.getFragmentShader(definition, execution, shaderDataIndexToGlobalDataIndex, globalScopeCode),
|
options.textureType = "TEXTURE_2D";
|
||||||
html: html,
|
}
|
||||||
usableShaders: usableShaders,
|
if (dataCount > 1) {
|
||||||
dataUrls: this.renderer._dataSources
|
options.textureType = "TEXTURE_2D_ARRAY";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
options.html = html;
|
||||||
|
options.dataUrls = this.renderer._dataSources;
|
||||||
|
options.onError = function(message, description) {
|
||||||
|
specification.error = message;
|
||||||
|
specification.desc = description;
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
getFragmentShader(definition, execution, shaderDataIndexToGlobalDataIndex, globalScopeCode) {
|
const matrixType = options.instanceCount > 2 ? "in" : "uniform";
|
||||||
return `#version 300 es
|
|
||||||
precision mediump float;
|
|
||||||
precision mediump sampler2DArray;
|
|
||||||
precision mediump sampler2D;
|
|
||||||
|
|
||||||
${this.texture.declare(shaderDataIndexToGlobalDataIndex)}
|
//hack use 'invalid' key to attach item
|
||||||
uniform float pixel_size_in_fragments;
|
globalScopeCode[null] = definition;
|
||||||
uniform float zoom_level;
|
this.compileVertexShader(
|
||||||
uniform vec2 u_tile_size;
|
program, `
|
||||||
vec4 _last_rendered_color = vec4(.0);
|
${matrixType} mat3 osd_transform_matrix;
|
||||||
|
|
||||||
in vec2 tile_texture_coords;
|
|
||||||
|
|
||||||
out vec4 final_color;
|
|
||||||
|
|
||||||
bool close(float value, float target) {
|
|
||||||
return abs(target - value) < 0.001;
|
|
||||||
}
|
|
||||||
|
|
||||||
void show(vec4 color) {
|
|
||||||
//premultiplied alpha blending
|
|
||||||
vec4 fg = _last_rendered_color;
|
|
||||||
_last_rendered_color = color;
|
|
||||||
vec4 pre_fg = vec4(fg.rgb * fg.a, fg.a);
|
|
||||||
final_color = pre_fg + final_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
vec4 blend_equation(in vec4 foreground, in vec4 background) {
|
|
||||||
${this.glslBlendCode}
|
|
||||||
}
|
|
||||||
|
|
||||||
void blend_clip(vec4 foreground) {
|
|
||||||
_last_rendered_color = blend_equation(foreground, _last_rendered_color);
|
|
||||||
}
|
|
||||||
|
|
||||||
void blend(vec4 foreground) {
|
|
||||||
show(_last_rendered_color);
|
|
||||||
final_color = blend_equation(foreground, final_color);
|
|
||||||
_last_rendered_color = vec4(.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
${Object.values(globalScopeCode).join("\n")}
|
|
||||||
|
|
||||||
${definition}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
${execution}
|
|
||||||
|
|
||||||
//blend last level
|
|
||||||
show(vec4(.0));
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
getVertexShader() {
|
|
||||||
//UNPACK_FLIP_Y_WEBGL not supported with 3D textures so sample bottom up
|
|
||||||
return `#version 300 es
|
|
||||||
precision mediump float;
|
|
||||||
|
|
||||||
uniform mat3 transform_matrix;
|
|
||||||
out vec2 tile_texture_coords;
|
|
||||||
const vec3 quad[4] = vec3[4] (
|
const vec3 quad[4] = vec3[4] (
|
||||||
vec3(0.0, 1.0, 1.0),
|
vec3(0.0, 1.0, 1.0),
|
||||||
vec3(0.0, 0.0, 1.0),
|
vec3(0.0, 0.0, 1.0),
|
||||||
vec3(1.0, 1.0, 1.0),
|
vec3(1.0, 1.0, 1.0),
|
||||||
vec3(1.0, 0.0, 1.0)
|
vec3(1.0, 0.0, 1.0)
|
||||||
);
|
);`, `
|
||||||
|
gl_Position = vec4(osd_transform_matrix * quad[gl_VertexID], 1);`, options);
|
||||||
|
this.compileFragmentShader(
|
||||||
|
program,
|
||||||
|
Object.values(globalScopeCode).join("\n"),
|
||||||
|
execution,
|
||||||
|
options);
|
||||||
|
|
||||||
|
return usableShaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTextureSampling(options) {
|
||||||
|
const type = options.textureType;
|
||||||
|
if (!type) { //no texture is also allowed option todo test if valid, defined since we read its location
|
||||||
|
return `
|
||||||
|
ivec2 osd_texture_size() {
|
||||||
|
return ivec2(0);
|
||||||
|
}
|
||||||
|
uniform sampler2D _vis_data_sampler[0];
|
||||||
|
vec4 osd_texture(int index, vec2 coords) {
|
||||||
|
return vec(.0);
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
const numOfTextures = options.instanceCount =
|
||||||
|
Math.max(options.instanceCount || 0, 1);
|
||||||
|
|
||||||
|
function samplingCode(coords) {
|
||||||
|
if (numOfTextures === 1) {
|
||||||
|
return `return texture(_vis_data_sampler[0], ${coords});`;
|
||||||
|
}
|
||||||
|
//sampling hardcode switch to sample with constant indexes
|
||||||
|
return `switch(osd_texture_id) {
|
||||||
|
${iterate(options.instanceCount).map(i => `
|
||||||
|
case ${i}:
|
||||||
|
return texture(_vis_data_sampler[${i}], ${coords});`).join("")}
|
||||||
|
}
|
||||||
|
return vec4(1.0);`;
|
||||||
|
}
|
||||||
|
|
||||||
|
//todo consider sampling with vec3 for universality
|
||||||
|
if (type === "TEXTURE_2D") {
|
||||||
|
return `
|
||||||
|
uniform sampler2D _vis_data_sampler[${numOfTextures}];
|
||||||
|
ivec2 osd_texture_size() {
|
||||||
|
return textureSize(_vis_data_sampler[0], 0);
|
||||||
|
}
|
||||||
|
vec4 osd_texture(int index, vec2 coords) {
|
||||||
|
${samplingCode('coords')}
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
if (type === "TEXTURE_2D_ARRAY") {
|
||||||
|
return `
|
||||||
|
uniform sampler2DArray _vis_data_sampler[${numOfTextures}];
|
||||||
|
ivec2 osd_texture_size() {
|
||||||
|
return textureSize(_vis_data_sampler[0], 0).xy;
|
||||||
|
}
|
||||||
|
vec4 osd_texture(int index, vec2 coords) {
|
||||||
|
${samplingCode('vec3(coords, index)')}
|
||||||
|
}`;
|
||||||
|
} else if (type === "TEXTURE_3D") {
|
||||||
|
//todo broken api, but pointless sending vec2 with 3d tex
|
||||||
|
return `
|
||||||
|
uniform sampler3D _vis_data_sampler[${numOfTextures}];
|
||||||
|
ivec3 osd_texture_size() {
|
||||||
|
return textureSize(_vis_data_sampler[0], 0).xy;
|
||||||
|
}
|
||||||
|
vec4 osd_texture(int index, vec2 coords) {
|
||||||
|
${samplingCode('vec3(coords, index)')}
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
return 'Error: invalid texture: unsupported sampling type ' + type;
|
||||||
|
}
|
||||||
|
|
||||||
|
sampleTexture(index, vec2coords) {
|
||||||
|
return `osd_texture(${index}, ${vec2coords})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
compileFragmentShader(program, definition, execution, options) {
|
||||||
|
const debug = options.debug ? `
|
||||||
|
float twoPixels = 1.0 / float(osd_texture_size().x) * 2.0;
|
||||||
|
vec2 distance = abs(osd_texture_bounds - osd_texture_coords);
|
||||||
|
if (distance.x <= twoPixels || distance.y <= twoPixels) {
|
||||||
|
final_color = vec4(1.0, .0, .0, 1.0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
` : "";
|
||||||
|
|
||||||
|
options.fs = `#version 300 es
|
||||||
|
precision mediump float;
|
||||||
|
precision mediump sampler2DArray;
|
||||||
|
precision mediump sampler2D;
|
||||||
|
precision mediump sampler3D;
|
||||||
|
|
||||||
|
uniform float pixel_size_in_fragments;
|
||||||
|
uniform float zoom_level;
|
||||||
|
|
||||||
|
in vec2 osd_texture_coords;
|
||||||
|
flat in vec2 osd_texture_bounds;
|
||||||
|
flat in int osd_texture_id;
|
||||||
|
|
||||||
|
${this.getTextureSampling(options)}
|
||||||
|
|
||||||
|
out vec4 final_color;
|
||||||
|
|
||||||
|
vec4 _last_rendered_color = vec4(.0);
|
||||||
|
|
||||||
|
bool close(float value, float target) {
|
||||||
|
return abs(target - value) < 0.001;
|
||||||
|
}
|
||||||
|
|
||||||
|
int _last_mode = 0;
|
||||||
|
bool _last_clip = false;
|
||||||
|
void blend(vec4 color, int mode, bool clip) {
|
||||||
|
//premultiplied alpha blending
|
||||||
|
//if (_last_clip) {
|
||||||
|
// todo
|
||||||
|
//} else {
|
||||||
|
vec4 fg = _last_rendered_color;
|
||||||
|
vec4 pre_fg = vec4(fg.rgb * fg.a, fg.a);
|
||||||
|
|
||||||
|
if (_last_mode == 0) {
|
||||||
|
final_color = pre_fg + (1.0-fg.a)*final_color;
|
||||||
|
} else if (_last_mode == 1) {
|
||||||
|
final_color = vec4(pre_fg.rgb * final_color.rgb, pre_fg.a + final_color.a);
|
||||||
|
} else {
|
||||||
|
final_color = vec4(.0, .0, 1.0, 1.0);
|
||||||
|
}
|
||||||
|
//}
|
||||||
|
_last_rendered_color = color;
|
||||||
|
_last_mode = mode;
|
||||||
|
_last_clip = clip;
|
||||||
|
}
|
||||||
|
|
||||||
|
${definition}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec3 vertex = quad[gl_VertexID];
|
${debug}
|
||||||
tile_texture_coords = vec2(vertex.x, -vertex.y);
|
|
||||||
gl_Position = vec4(transform_matrix * vertex, 1);
|
${execution}
|
||||||
|
|
||||||
|
//blend last level
|
||||||
|
blend(vec4(.0), 0, false);
|
||||||
|
}`;
|
||||||
|
if (options.vs) {
|
||||||
|
program._osdOptions = options;
|
||||||
|
this._compileProgram(program, options.onError || $.console.error);
|
||||||
|
delete options.fs;
|
||||||
|
delete options.vs;
|
||||||
}
|
}
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
programLoaded(program, currentConfig) {
|
compileVertexShader(program, definition, execution, options) {
|
||||||
|
const textureId = options.instanceCount > 1 ? 'gl_InstanceID' : '0';
|
||||||
|
|
||||||
|
options.vs = `#version 300 es
|
||||||
|
precision mediump float;
|
||||||
|
in vec2 osd_tile_texture_position;
|
||||||
|
flat out int osd_texture_id;
|
||||||
|
out vec2 osd_texture_coords;
|
||||||
|
flat out vec2 osd_texture_bounds;
|
||||||
|
|
||||||
|
${definition}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
osd_texture_id = ${textureId};
|
||||||
|
// vec3 vertex = quad[gl_VertexID];
|
||||||
|
// vec2 texCoords = vec2(vertex.x, -vertex.y);
|
||||||
|
// osd_texture_coords = texCoords;
|
||||||
|
// osd_texture_bounds = texCoords;
|
||||||
|
|
||||||
|
osd_texture_coords = osd_tile_texture_position;
|
||||||
|
osd_texture_bounds = osd_tile_texture_position;
|
||||||
|
${execution}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
if (options.fs) {
|
||||||
|
program._osdOptions = options;
|
||||||
|
this._compileProgram(program, options.onError || $.console.error);
|
||||||
|
delete options.fs;
|
||||||
|
delete options.vs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
programLoaded(program, currentConfig = null) {
|
||||||
if (!this.renderer.running) {
|
if (!this.renderer.running) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let context = this.renderer,
|
const gl = this.gl;
|
||||||
gl = this.gl;
|
|
||||||
|
|
||||||
// Allow for custom loading
|
// Allow for custom loading
|
||||||
gl.useProgram(program);
|
gl.useProgram(program);
|
||||||
context.visualisationInUse(currentConfig);
|
if (currentConfig) {
|
||||||
context.glLoaded(gl, program, currentConfig);
|
this.renderer.glLoaded(gl, program, currentConfig);
|
||||||
|
|
||||||
//Note that the drawing strategy is not to resize canvas, and simply draw everyhing on squares
|
|
||||||
this.texture.programLoaded(context, gl, program, currentConfig);
|
|
||||||
|
|
||||||
//Empty ARRAY: get the vertices directly from the shader
|
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.emptyBuffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
programUsed(program, currentConfig, id, tileOpts) {
|
// gl.bindVertexArray(this.vao);
|
||||||
|
|
||||||
|
this._locationPixelSize = gl.getUniformLocation(program, "pixel_size_in_fragments");
|
||||||
|
this._locationZoomLevel = gl.getUniformLocation(program, "zoom_level");
|
||||||
|
|
||||||
|
const options = program._osdOptions;
|
||||||
|
if (options.instanceCount > 1) {
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this._bufferTexturePosition);
|
||||||
|
this._locationTexturePosition = gl.getAttribLocation(program, 'osd_tile_texture_position');
|
||||||
|
//vec2 * 4 bytes per element
|
||||||
|
const vertexSizeByte = 2 * 4;
|
||||||
|
gl.bufferData(gl.ARRAY_BUFFER, options.instanceCount * 4 * vertexSizeByte, gl.STREAM_DRAW);
|
||||||
|
gl.enableVertexAttribArray(this._locationTexturePosition);
|
||||||
|
gl.vertexAttribPointer(this._locationTexturePosition, 2, gl.FLOAT, false, 0, 0);
|
||||||
|
gl.vertexAttribDivisor(this._locationTexturePosition, 0);
|
||||||
|
|
||||||
|
this._bufferMatrices = this._bufferMatrices || gl.createBuffer();
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this._bufferMatrices);
|
||||||
|
this._locationMatrices = gl.getAttribLocation(program, "osd_transform_matrix");
|
||||||
|
gl.bufferData(gl.ARRAY_BUFFER, 4 * 9 * options.instanceCount, gl.STREAM_DRAW);
|
||||||
|
//matrix 3x3 (9) * 4 bytes per element
|
||||||
|
const bytesPerMatrix = 4 * 9;
|
||||||
|
for (let i = 0; i < 3; ++i) {
|
||||||
|
const loc = this._locationMatrices + i;
|
||||||
|
gl.enableVertexAttribArray(loc);
|
||||||
|
// note the stride and offset
|
||||||
|
const offset = i * 12; // 3 floats per row, 4 bytes per float
|
||||||
|
gl.vertexAttribPointer(
|
||||||
|
loc, // location
|
||||||
|
3, // size (num values to pull from buffer per iteration)
|
||||||
|
gl.FLOAT, // type of data in buffer
|
||||||
|
false, // normalize
|
||||||
|
bytesPerMatrix, // stride, num bytes to advance to get to next set of values
|
||||||
|
offset
|
||||||
|
);
|
||||||
|
// this line says this attribute only changes for each 1 instance
|
||||||
|
gl.vertexAttribDivisor(loc, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._textureLoc = gl.getUniformLocation(program, "_vis_data_sampler");
|
||||||
|
gl.uniform1iv(this._textureLoc, iterate(options.instanceCount));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this._bufferTexturePosition);
|
||||||
|
this._locationTexturePosition = gl.getAttribLocation(program, 'osd_tile_texture_position');
|
||||||
|
gl.enableVertexAttribArray(this._locationTexturePosition);
|
||||||
|
gl.vertexAttribPointer(this._locationTexturePosition, 2, gl.FLOAT, false, 0, 0);
|
||||||
|
|
||||||
|
this._locationMatrices = gl.getUniformLocation(program, "osd_transform_matrix");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
programUsed(program, currentConfig, texture, tileOpts = {}) {
|
||||||
if (!this.renderer.running) {
|
if (!this.renderer.running) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -367,20 +643,47 @@ void main() {
|
|||||||
let context = this.renderer,
|
let context = this.renderer,
|
||||||
gl = this.gl;
|
gl = this.gl;
|
||||||
|
|
||||||
|
if (currentConfig) {
|
||||||
context.glDrawing(gl, program, currentConfig, tileOpts);
|
context.glDrawing(gl, program, currentConfig, tileOpts);
|
||||||
|
}
|
||||||
|
|
||||||
// Set Attributes for GLSL
|
// Set Attributes for GLSL
|
||||||
gl.uniform1f(gl.getUniformLocation(program, "pixel_size_in_fragments"), tileOpts.pixelSize || 1);
|
gl.uniform1f(this._locationPixelSize, tileOpts.pixelSize || 1);
|
||||||
gl.uniform1f(gl.getUniformLocation(program, "zoom_level"), tileOpts.zoom || 1);
|
gl.uniform1f(this._locationZoomLevel, tileOpts.zoom || 1);
|
||||||
gl.uniformMatrix3fv(gl.getUniformLocation(program, "transform_matrix"), false,
|
|
||||||
tileOpts.transform || OpenSeadragon.Mat3.makeIdentity());
|
|
||||||
|
|
||||||
// Upload textures
|
const options = program._osdOptions;
|
||||||
this.texture.programUsed(context, currentConfig, id, program, gl);
|
//if compiled as instanced drawing
|
||||||
|
if (options.instanceCount > 1) {
|
||||||
|
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this._bufferTexturePosition);
|
||||||
|
gl.bufferSubData(gl.ARRAY_BUFFER, 0, tileOpts.textureCoords);
|
||||||
|
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this._bufferMatrices);
|
||||||
|
gl.bufferSubData(gl.ARRAY_BUFFER, 0, tileOpts.transform);
|
||||||
|
|
||||||
|
let drawInstanceCount = tileOpts.instanceCount || Infinity;
|
||||||
|
drawInstanceCount = Math.min(drawInstanceCount, options.instanceCount);
|
||||||
|
|
||||||
|
for (let i = 0; i <= drawInstanceCount; i++){
|
||||||
|
gl.activeTexture(gl.TEXTURE0 + i);
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, texture[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, drawInstanceCount);
|
||||||
|
} else {
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this._bufferTexturePosition);
|
||||||
|
gl.bufferData(gl.ARRAY_BUFFER, tileOpts.textureCoords, gl.STATIC_DRAW);
|
||||||
|
|
||||||
|
gl.uniformMatrix3fv(this._locationMatrices, false, tileOpts.transform || $.Mat3.makeIdentity());
|
||||||
|
|
||||||
|
// Upload texture, only one texture active, no preparation
|
||||||
|
gl.activeTexture(gl.TEXTURE0);
|
||||||
|
gl.bindTexture(gl[options.textureType], texture);
|
||||||
|
|
||||||
// Draw triangle strip (two triangles) from a static array defined in the vertex shader
|
// Draw triangle strip (two triangles) from a static array defined in the vertex shader
|
||||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
})(OpenSeadragon);
|
})(OpenSeadragon);
|
||||||
|
@ -129,6 +129,9 @@ $('#image-picker').sortable({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#image-picker').append(`<div class="option-grid">
|
||||||
|
<label>Blend Time (s): <input type="number" value="0" data-field="blend-time" min="0" max="1" step="0.2"> </label>
|
||||||
|
</div>`);
|
||||||
Object.keys(sources).forEach((key, index)=>{
|
Object.keys(sources).forEach((key, index)=>{
|
||||||
let element = makeImagePickerElement(key, labels[key])
|
let element = makeImagePickerElement(key, labels[key])
|
||||||
$('#image-picker').append(element);
|
$('#image-picker').append(element);
|
||||||
@ -163,9 +166,10 @@ $('#image-picker input:not(.toggle)').on('change',function(){
|
|||||||
});
|
});
|
||||||
|
|
||||||
function updateTiledImage(tiledImage, data, value, item){
|
function updateTiledImage(tiledImage, data, value, item){
|
||||||
|
let field = data.field;
|
||||||
|
|
||||||
if(tiledImage){
|
if(tiledImage){
|
||||||
//item = tiledImage
|
//item = tiledImage
|
||||||
let field = data.field;
|
|
||||||
if(field == 'x'){
|
if(field == 'x'){
|
||||||
let bounds = tiledImage.getBoundsNoRotate();
|
let bounds = tiledImage.getBoundsNoRotate();
|
||||||
let position = new OpenSeadragon.Point(Number(value), bounds.y);
|
let position = new OpenSeadragon.Point(Number(value), bounds.y);
|
||||||
@ -198,14 +202,18 @@ function updateTiledImage(tiledImage, data, value, item){
|
|||||||
} else {
|
} else {
|
||||||
tiledImage.setClip(null);
|
tiledImage.setClip(null);
|
||||||
}
|
}
|
||||||
}
|
} else if (field == 'debug'){
|
||||||
else if (field == 'debug'){
|
|
||||||
if( $(item).prop('checked') ){
|
if( $(item).prop('checked') ){
|
||||||
tiledImage.debugMode = true;
|
tiledImage.debugMode = true;
|
||||||
} else {
|
} else {
|
||||||
tiledImage.debugMode = false;
|
tiledImage.debugMode = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
//viewer-level option
|
||||||
|
if (field == "blend-time") {
|
||||||
|
//todo
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,6 +347,7 @@ function makeImagePickerElement(key, label){
|
|||||||
<label>Flipped: <input type="checkbox" data-image="" data-field="flipped"></label>
|
<label>Flipped: <input type="checkbox" data-image="" data-field="flipped"></label>
|
||||||
<label>Cropped: <input type="checkbox" data-image="" data-field="cropped"></label>
|
<label>Cropped: <input type="checkbox" data-image="" data-field="cropped"></label>
|
||||||
<label>Clipped: <input type="checkbox" data-image="" data-field="clipped"></label>
|
<label>Clipped: <input type="checkbox" data-image="" data-field="clipped"></label>
|
||||||
|
<label>Chess Tile Opacity: <input type="checkbox" data-image="" data-field="tile-level-opecity"></label>
|
||||||
<label>Debug: <input type="checkbox" data-image="" data-field="debug"></label>
|
<label>Debug: <input type="checkbox" data-image="" data-field="debug"></label>
|
||||||
<label>Composite: <select data-image="" data-field="composite"></select></label>
|
<label>Composite: <select data-image="" data-field="composite"></select></label>
|
||||||
<label>Wrap: <select data-image="" data-field="wrapping"></select></label>
|
<label>Wrap: <select data-image="" data-field="wrapping"></select></label>
|
||||||
|
Loading…
Reference in New Issue
Block a user