DrawerBase API for internal cache.

This commit is contained in:
Aiosa 2025-01-07 19:56:21 +01:00
parent 4c2b0715af
commit 6315662078
4 changed files with 250 additions and 257 deletions

View File

@ -97,7 +97,8 @@ OpenSeadragon.DrawerBase = class DrawerBase{
*/ */
get defaultOptions() { get defaultOptions() {
return { return {
usePrivateCache: false usePrivateCache: false,
preloadCache: true,
}; };
} }
@ -161,7 +162,7 @@ OpenSeadragon.DrawerBase = class DrawerBase{
$.console.warn("Attempt to draw tile %s when not cached!", tile); $.console.warn("Attempt to draw tile %s when not cached!", tile);
return undefined; return undefined;
} }
const dataCache = cache.getCacheForRendering(this, tile); const dataCache = cache.getDataForRendering(this, tile);
return dataCache && dataCache.data; return dataCache && dataCache.data;
} }
@ -240,6 +241,31 @@ OpenSeadragon.DrawerBase = class DrawerBase{
$.console.warn('[drawer].clear() is deprecated. The drawer is responsible for clearing itself as needed before drawing tiles.'); $.console.warn('[drawer].clear() is deprecated. The drawer is responsible for clearing itself as needed before drawing tiles.');
} }
/**
* If options.usePrivateCache is true, this method MUST RETURN the private cache content
* @param {OpenSeadragon.CacheRecord} cache
* @param {OpenSeadragon.Tile} tile
* @return any
*/
dataCreate(cache, tile) {}
/**
* It is possible to perform any necessary cleanup on internal cache, necessary if you
* need to clean up some memory (e.g. destroy canvas by setting with & height to 0).
* @param {*} data object returned by dataCreate(...)
*/
dataFree(data) {}
/**
* Call to invalidate internal cache. It will be rebuilt. With synchronous converions,
* it will be rebuilt immediatelly. With asynchronous, it will be rebuilt once invalidation
* routine happens, e.g. you should call also requestInvalidate() if you need to happen
* it as soon as possible.
*/
setDataNeedsRefresh() {
this._dataNeedsRefresh = $.now();
}
// Private functions // Private functions
/** /**

View File

@ -79,7 +79,7 @@
*/ */
await() { await() {
if (!this._promise) { //if not cache loaded, do not fail if (!this._promise) { //if not cache loaded, do not fail
return $.Promise.resolve(); return $.Promise.resolve(this._data);
} }
return this._promise; return this._promise;
} }
@ -171,83 +171,133 @@
* @param {OpenSeadragon.Tile} tileToDraw reference to the tile that is in the process of drawing and * @param {OpenSeadragon.Tile} tileToDraw reference to the tile that is in the process of drawing and
* for which we request the data; if we attempt to draw such tile while main cache target is destroyed, * for which we request the data; if we attempt to draw such tile while main cache target is destroyed,
* attempt to reset the tile state to force system to re-download it again * attempt to reset the tile state to force system to re-download it again
* @returns {OpenSeadragon.CacheRecord|OpenSeadragon.SimpleCacheRecord|undefined} desired data if available, * @returns {OpenSeadragon.CacheRecord|OpenSeadragon.InternalCacheRecord|undefined} desired data if available,
* wrapped in the cache container. This data is guaranteed to be loaded & in the type supported by the drawer. * wrapped in the cache container. This data is guaranteed to be loaded & in the type supported by the drawer.
* Returns undefined if the data is not ready for rendering. * Returns undefined if the data is not ready for rendering.
* @private * @private
*/ */
getCacheForRendering(drawer, tileToDraw) { getDataForRendering(drawer, tileToDraw) {
const supportedTypes = drawer.getSupportedDataFormats(), const keepInternalCopy = drawer.options.usePrivateCache;
keepInternalCopy = drawer.options.usePrivateCache;
if (this.loaded && supportedTypes.includes(this.type)) {
return this;
}
if (!this.loaded) {
$.console.error("Attempt to draw tile when not loaded main cache!");
return undefined;
}
if (this._destroyed) { if (this._destroyed) {
$.console.error("Attempt to draw tile with destroyed main cache!"); $.console.error("Attempt to draw tile with destroyed main cache!");
tileToDraw._unload(); // try to restore the state so that the tile is later on fetched again tileToDraw._unload(); // try to restore the state so that the tile is later on fetched again
return undefined; return undefined;
} }
let internalCache = this[DRAWER_INTERNAL_CACHE]; const supportedTypes = drawer.getSupportedDataFormats();
internalCache = internalCache && internalCache[drawer.getId()]; if (!supportedTypes.includes(this.type)) {
if (keepInternalCopy && !internalCache) { $.console.error("Attempt to draw tile with unsupported target drawer type!");
$.console.warn("Attempt to render cache that is not prepared for current drawer " + this.prepareForRendering(drawer);
"supported format: the preparation should've happened after tile processing has finished.",
this, tileToDraw);
this.prepareForRendering(drawer.getId(), supportedTypes, keepInternalCopy)
.then(() => this._triggerNeedsDraw());
return undefined; return undefined;
} }
if (internalCache) { // If we support internal cache
internalCache.withTileReference(this._tRef); if (keepInternalCopy && !drawer.options.preloadCache) {
} else { // let sync preparation handle data
internalCache = this; if (!drawer.options.preloadCache) {
return this.prepareInternalCacheSync(drawer);
}
// or check that it was properly initiated before returning
const internalCache = this._getInternalCacheRef(drawer);
if (!internalCache || !internalCache.loaded) {
$.console.error("Attempt to draw tile with internal cache non-ready state!");
return undefined;
}
return internalCache;
} }
// Cache in the process of loading, no-op // If no internal cache support, we are ready - just return self reference
if (!internalCache.loaded) { return this;
$.console.warn("Attempt to render cache that is not prepared for current drawer: " +
"internal cache still loading: this should be awaited.",
this, tileToDraw);
this._triggerNeedsDraw();
return undefined;
}
if (!supportedTypes.includes(internalCache.type)) {
let logReference = this[DRAWER_INTERNAL_CACHE];
logReference = logReference ? Object.entries(logReference) : this;
$.console.warn("Attempt to render cache that is not prepared for current drawer " +
"supported format: the preparation should've happened after tile processing has finished.",
logReference, tileToDraw);
internalCache.transformTo(supportedTypes.length > 1 ? supportedTypes : supportedTypes[0])
.then(() => this._triggerNeedsDraw());
return undefined; // type is NOT compatible
}
return internalCache;
} }
/** /**
* Should not be called if cache type is already among supported types * Should not be called if cache type is already among supported types
* @private * @private
* @param drawerId * @param {OpenSeadragon.DrawerBase} drawer
* @param supportedTypes * @return {OpenSeadragon.Promise<*>} reference to the data,
* @param keepInternalCopy if a drawer requests internal copy, it means it can only use * or null if not data yet loaded/ready (usually due to error)
* given cache for itself, cannot be shared -> initialize privately
* @return {OpenSeadragon.Promise<OpenSeadragon.SimpleCacheRecord|OpenSeadragon.CacheRecord> | null}
* reference to the cache processed for drawer rendering requirements, or null on error
*/ */
prepareForRendering(drawerId, supportedTypes, keepInternalCopy = true) { prepareForRendering(drawer) {
// if not internal copy and we have no data, or we are ready to render, exit const supportedTypes = drawer.getRequiredDataFormats();
if (!this.loaded || supportedTypes.includes(this.type)) {
return $.Promise.resolve(this); if (drawer.options.usePrivateCache && drawer.options.preloadCache) {
return this.prepareInternalCacheAsync(drawer).then(_ => {
// if not internal copy and we have no data, or we are ready to render, exit
if (!this.loaded || supportedTypes.includes(this.type)) {
return this.await();
}
return this.transformTo(supportedTypes);
});
} }
if (!keepInternalCopy) { if (!this.loaded || supportedTypes.includes(this.type)) {
return this.transformTo(supportedTypes); return this.await();
}
return this.transformTo(supportedTypes);
}
/**
* Must not be called if drawer.options.usePrivateCache == false. Called inside prepareForRenderine
* by cache itself if preloadCache == true (supports async behavior).
*
* @private
* @param {OpenSeadragon.DrawerBase} drawer
* @return {OpenSeadragon.Promise<*>} reference to the data wrapped in a promise,
* or null if not data yet loaded/ready (usually due to error)
*/
prepareInternalCacheAsync(drawer) {
let internalCache = this._getInternalCacheRef(drawer);
if (this._checkInternalCacheUpToDate(internalCache, drawer)) {
return internalCache.await();
}
// Force reset
if (internalCache && !internalCache.loaded) {
internalCache.await().then(() => internalCache.destroy());
}
$.console.assert(this._tRef, "Data Create called from invalidation routine needs tile reference!");
const transformedData = drawer.dataCreate(this, this._tRef);
$.console.assert(transformedData !== undefined, "[DrawerBase.dataCreate] must return a value if usePrivateCache is enabled!");
internalCache = this[DRAWER_INTERNAL_CACHE][drawer.getId()] = new $.InternalCacheRecord(transformedData, (data) => drawer.dataFree(data));
return internalCache.await();
}
/**
* Must not be called if drawer.options.usePrivateCache == false. Called inside getDataForRendering
* by cache itself if preloadCache == false (without support for async behavior).
* @private
* @param {OpenSeadragon.DrawerBase} drawer
* @return {OpenSeadragon.InternalCacheRecord} reference to the cache
*/
prepareInternalCacheSync(drawer) {
let internalCache = this._getInternalCacheRef(drawer);
if (this._checkInternalCacheUpToDate(internalCache, drawer)) {
return internalCache;
}
// Force reset
if (internalCache) {
internalCache.destroy();
}
$.console.assert(this._tRef, "Data Create called from drawing loop needs tile reference!");
const transformedData = drawer.dataCreate(this, this._tRef);
$.console.assert(transformedData !== undefined, "[DrawerBase.dataCreate] must return a value if usePrivateCache is enabled!");
internalCache = this[DRAWER_INTERNAL_CACHE][drawer.getId()] = new $.InternalCacheRecord(transformedData, (data) => drawer.dataFree(data));
return internalCache;
}
_getInternalCacheRef(drawer) {
const options = drawer.options;
if (!options.usePrivateCache) {
return $.Promise.reject("[CacheRecord.prepareInternalCacheSync] must not be called when usePrivateCache is false.");
} }
// we can get here only if we want to render incompatible type // we can get here only if we want to render incompatible type
@ -255,32 +305,13 @@
if (!internalCache) { if (!internalCache) {
internalCache = this[DRAWER_INTERNAL_CACHE] = {}; internalCache = this[DRAWER_INTERNAL_CACHE] = {};
} }
return internalCache[drawer.getId()];
}
internalCache = internalCache[drawerId]; _checkInternalCacheUpToDate(internalCache, drawer) {
if (internalCache && supportedTypes.includes(internalCache.type)) { // We respect existing records, unless they are outdated. Invalidation routine by its nature
// already done // destroys internal cache, therefore we do not need to check if internal cache is consistent with its parent.
return $.Promise.resolve(this); return internalCache && internalCache.loaded && internalCache.tstamp >= drawer._dataNeedsRefresh;
}
const conversionPath = $.convertor.getConversionPath(this.type, supportedTypes);
if (!conversionPath) {
$.console.error(`[getCacheForRendering] Conversion ${this.type} ---> ${supportedTypes} cannot be done!`);
return $.Promise.resolve(this);
}
const newInternalCache = new $.SimpleCacheRecord();
newInternalCache.withTileReference(this._tRef);
const selectedFormat = conversionPath[conversionPath.length - 1].target.value;
return $.convertor.convert(this._tRef, this.data, this.type, selectedFormat).then(data => {
newInternalCache.setDataAs(data, selectedFormat); // synchronous, SimpleCacheRecord call
// if existed, delete
if (internalCache) {
internalCache.destroy();
}
this[DRAWER_INTERNAL_CACHE][drawerId] = newInternalCache;
return newInternalCache;
});
} }
/** /**
@ -623,7 +654,7 @@
}; };
/** /**
* @class SimpleCacheRecord * @class InternalCacheRecord
* @memberof OpenSeadragon * @memberof OpenSeadragon
* @classdesc Simple cache record without robust support for async access. Meant for internal use only. * @classdesc Simple cache record without robust support for async access. Meant for internal use only.
* *
@ -635,12 +666,22 @@
* It also does not record tiles nor allows cache/tile sharing. * It also does not record tiles nor allows cache/tile sharing.
* @private * @private
*/ */
$.SimpleCacheRecord = class { $.InternalCacheRecord = class {
constructor(preferredTypes) { constructor(data, onDestroy) {
this._data = null; this.tstamp = $.now();
this._type = null; this._ondestroy = onDestroy;
this.loaded = false;
this.format = Array.isArray(preferredTypes) ? preferredTypes : null; if (data instanceof $.Promise) {
this._promise = data;
data.then(data => {
this.loaded = true;
this._data = data;
});
} else {
this._promise = null;
this.loaded = true;
this._data = data;
}
} }
/** /**
@ -656,86 +697,42 @@
* @returns {string} * @returns {string}
*/ */
get type() { get type() {
return this._type; return "__internal_cache__";
}
/**
* Await ongoing process so that we get cache ready on callback.
* @returns {OpenSeadragon.Promise<?>}
*/
await() {
if (!this._promise) { //if not cache loaded, do not fail
return $.Promise.resolve(this._data);
}
return this._promise;
} }
/** /**
* Must be called before transformTo or setDataAs. To keep * Must be called before transformTo or setDataAs. To keep
* compatible api with CacheRecord where tile refs are known. * compatible api with CacheRecord where tile refs are known.
* @param {OpenSeadragon.Tile} referenceTile reference tile for conversion * @param {OpenSeadragon.Tile} referenceTile reference tile for conversion
* @return {OpenSeadragon.SimpleCacheRecord} self reference for builder pattern * @return {OpenSeadragon.InternalCacheRecord} self reference for builder pattern
*/ */
withTileReference(referenceTile) { withTileReference(referenceTile) {
this._temporaryTileRef = referenceTile; this._temporaryTileRef = referenceTile;
return this; return this;
} }
/**
* Transform cache to desired type and get the data after conversion.
* Does nothing if the type equals to the current type. Asynchronous.
* @param {string|string[]} type if array provided, the system will
* try to optimize for the best type to convert to.
* @returns {OpenSeadragon.Promise<?>}
*/
transformTo(type) {
$.console.assert(this._temporaryTileRef, "SimpleCacheRecord needs tile reference set before update operation!");
const convertor = $.convertor,
conversionPath = convertor.getConversionPath(this._type, type);
if (!conversionPath) {
$.console.error(`[SimpleCacheRecord.transformTo] Conversion ${this._type} ---> ${type} cannot be done!`);
return $.Promise.resolve(); //no-op
}
const stepCount = conversionPath.length,
_this = this,
convert = (x, i) => {
if (i >= stepCount) {
_this._data = x;
_this.loaded = true;
_this._temporaryTileRef = null;
return $.Promise.resolve(x);
}
let edge = conversionPath[i];
try {
// no test for y - less robust approach
let y = edge.transform(this._temporaryTileRef, x);
convertor.destroy(x, edge.origin.value);
const result = $.type(y) === "promise" ? y : $.Promise.resolve(y);
return result.then(res => convert(res, i + 1));
} catch (e) {
_this.loaded = false;
_this._temporaryTileRef = null;
throw e;
}
};
this.loaded = false;
// Read target type from the conversion path: [edge.target] = Vertex, its value=type
this._type = conversionPath[stepCount - 1].target.value;
const promise = convert(this._data, 0);
this._data = undefined;
return promise;
}
/** /**
* Free all the data and call data destructors if defined. * Free all the data and call data destructors if defined.
*/ */
destroy() { destroy() {
$.convertor.destroy(this._data, this._type); if (this.loaded) {
this._data = null; if (this._ondestroy) {
this._type = null; this._ondestroy(this._data);
} }
this._data = null;
/** this.loaded = false;
* Safely overwrite the cache data and return the old data }
* @private
*/
setDataAs(data, type) {
// no check for state, users must ensure compatibility manually
$.convertor.destroy(this._data, this._type);
this._type = type;
this._data = data;
this.loaded = true;
} }
}; };

View File

@ -100,10 +100,7 @@
this._setupCanvases(); this._setupCanvases();
this._setupRenderer(); this._setupRenderer();
this._setupCallCount = 1; this._supportedFormats = ["context2d", "image"];
this._supportedFormats = this._setupTextureHandlers();
this._requiredFormats = this._supportedFormats;
this.context = this._outputContext; // API required by tests this.context = this._outputContext; // API required by tests
} }
@ -247,9 +244,9 @@
$.console.warn("Attempt to draw tile %s when not cached!", tile); $.console.warn("Attempt to draw tile %s when not cached!", tile);
return undefined; return undefined;
} }
const dataCache = cache.getCacheForRendering(this, tile); const dataCache = cache.getDataForRendering(this, tile);
// Use CPU Data for the drawer instead // Use CPU Data for the drawer instead
return dataCache && dataCache.data.cpuData.getContext("2d"); return dataCache && dataCache.cpuData;
}; };
} }
@ -480,10 +477,6 @@
} }
getRequiredDataFormats() {
return this._requiredFormats;
}
// Public API required by all Drawer implementations // Public API required by all Drawer implementations
/** /**
* Sets whether image smoothing is enabled or disabled * Sets whether image smoothing is enabled or disabled
@ -492,15 +485,9 @@
setImageSmoothingEnabled(enabled){ setImageSmoothingEnabled(enabled){
if( this._imageSmoothingEnabled !== enabled ){ if( this._imageSmoothingEnabled !== enabled ){
this._imageSmoothingEnabled = enabled; this._imageSmoothingEnabled = enabled;
this.setDataNeedsRefresh();
// Todo consider removing old type handlers if _supportedFormats had already types defined, this.viewer.forceRedraw();
// and remove support for rendering old types...
const newFormats = this._setupTextureHandlers(); // re-sets the type to enforce re-initialization
this._supportedFormats.push(...newFormats);
this._requiredFormats = newFormats;
return this.viewer.requestInvalidate();
} }
return $.Promise.resolve();
} }
/** /**
@ -883,97 +870,83 @@
this.viewer.addHandler("resize", this._resizeHandler); this.viewer.addHandler("resize", this._resizeHandler);
} }
_setupTextureHandlers() { dataCreate(cache, tile) {
const tex2DCompatibleLoader = (tile, data) => { let tiledImage = tile.tiledImage;
let tiledImage = tile.tiledImage; let gl = this._gl;
let gl = this._gl; let texture;
let texture; let position;
let position;
if (!tiledImage.isTainted()) { const data = cache.data;
if((data instanceof CanvasRenderingContext2D) && $.isCanvasTainted(data.canvas)){
tiledImage.setTainted(true); if (!tiledImage.isTainted()) {
$.console.warn('WebGL cannot be used to draw this TiledImage because it has tainted data. Does crossOriginPolicy need to be set?'); if((data instanceof CanvasRenderingContext2D) && $.isCanvasTainted(data.canvas)){
this._raiseDrawerErrorEvent(tiledImage, 'Tainted data cannot be used by the WebGLDrawer. Falling back to CanvasDrawer for this TiledImage.'); tiledImage.setTainted(true);
$.console.warn('WebGL cannot be used to draw this TiledImage because it has tainted data. Does crossOriginPolicy need to be set?');
this._raiseDrawerErrorEvent(tiledImage, 'Tainted data cannot be used by the WebGLDrawer. Falling back to CanvasDrawer for this TiledImage.');
} else {
let sourceWidthFraction, sourceHeightFraction;
if (tile.sourceBounds) {
sourceWidthFraction = Math.min(tile.sourceBounds.width, data.width) / data.width;
sourceHeightFraction = Math.min(tile.sourceBounds.height, data.height) / data.height;
} else { } else {
let sourceWidthFraction, sourceHeightFraction; sourceWidthFraction = 1;
if (tile.sourceBounds) { sourceHeightFraction = 1;
sourceWidthFraction = Math.min(tile.sourceBounds.width, data.width) / data.width; }
sourceHeightFraction = Math.min(tile.sourceBounds.height, data.height) / data.height;
} else {
sourceWidthFraction = 1;
sourceHeightFraction = 1;
}
// create a gl Texture for this tile and bind the canvas with the image data // create a gl Texture for this tile and bind the canvas with the image data
texture = gl.createTexture(); texture = gl.createTexture();
let overlap = tiledImage.source.tileOverlap; let overlap = tiledImage.source.tileOverlap;
if( overlap > 0){ if( overlap > 0){
// calculate the normalized position of the rect to actually draw // calculate the normalized position of the rect to actually draw
// discarding overlap. // discarding overlap.
let overlapFraction = this._calculateOverlapFraction(tile, tiledImage); let overlapFraction = this._calculateOverlapFraction(tile, tiledImage);
let left = (tile.x === 0 ? 0 : overlapFraction.x) * sourceWidthFraction; let left = (tile.x === 0 ? 0 : overlapFraction.x) * sourceWidthFraction;
let top = (tile.y === 0 ? 0 : overlapFraction.y) * sourceHeightFraction; let top = (tile.y === 0 ? 0 : overlapFraction.y) * sourceHeightFraction;
let right = (tile.isRightMost ? 1 : 1 - overlapFraction.x) * sourceWidthFraction; let right = (tile.isRightMost ? 1 : 1 - overlapFraction.x) * sourceWidthFraction;
let bottom = (tile.isBottomMost ? 1 : 1 - overlapFraction.y) * sourceHeightFraction; let bottom = (tile.isBottomMost ? 1 : 1 - overlapFraction.y) * sourceHeightFraction;
position = this._makeQuadVertexBuffer(left, right, top, bottom); position = this._makeQuadVertexBuffer(left, right, top, bottom);
} else if (sourceWidthFraction === 1 && sourceHeightFraction === 1) { } else if (sourceWidthFraction === 1 && sourceHeightFraction === 1) {
// no overlap and no padding: this texture can use the unit quad as its position data // no overlap and no padding: this texture can use the unit quad as its position data
position = this._unitQuad; position = this._unitQuad;
} else { } else {
position = this._makeQuadVertexBuffer(0, sourceWidthFraction, 0, sourceHeightFraction); position = this._makeQuadVertexBuffer(0, sourceWidthFraction, 0, sourceHeightFraction);
} }
gl.activeTexture(gl.TEXTURE0); gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture); gl.bindTexture(gl.TEXTURE_2D, texture);
// Set the parameters so we can render any size image. // Set the parameters so we can render any size image.
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 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_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, this._textureFilter()); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, this._textureFilter());
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, this._textureFilter()); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, this._textureFilter());
try { try {
// This depends on gl.TEXTURE_2D being bound to the texture // This depends on gl.TEXTURE_2D being bound to the texture
// associated with this canvas before calling this function // associated with this canvas before calling this function
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, data); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, data);
} catch (e){ } catch (e){
// Todo a bit dirty re-use of the tainted flag, but makes the code more stable // Todo a bit dirty re-use of the tainted flag, but makes the code more stable
tiledImage.setTainted(true); tiledImage.setTainted(true);
$.console.error('Error uploading image data to WebGL. Falling back to canvas renderer.', e); $.console.error('Error uploading image data to WebGL. Falling back to canvas renderer.', e);
this._raiseDrawerErrorEvent(tiledImage, 'Unknown error when uploading texture. Falling back to CanvasDrawer for this TiledImage.'); this._raiseDrawerErrorEvent(tiledImage, 'Unknown error when uploading texture. Falling back to CanvasDrawer for this TiledImage.');
}
} }
} }
}
// TextureInfo stored in the cache // TextureInfo stored in the cache
return { return {
texture: texture, texture: texture,
position: position, position: position,
cpuData: data cpuData: data // Reference to the outer cache data, used to draw if webgl cannot be used
};
};
const tex2DCompatibleDestructor = textureInfo => {
if (textureInfo) {
this._gl.deleteTexture(textureInfo.texture);
}
}; };
}
const thisType = `${this.getId()}_${this._setupCallCount++}_TEX_2D`; dataFree(data) {
// Differentiate type also based on type used to upload data: we can support bidirectional conversion. if (data && data.texture) {
const c2dTexType = thisType + ":context2d", this._gl.deleteTexture(data.texture);
imageTexType = thisType + ":image"; data.texture = null;
}
// We should be OK uploading any of these types. The complexity is selected to be O(3n), should be
// more than linear pass over pixels
$.convertor.learn("context2d", c2dTexType, (t, d) => tex2DCompatibleLoader(t, d.canvas), 1, 3);
// TODO: lost support for image
// $.convertor.learn("image", imageTexType, tex2DCompatibleLoader, 1, 3);
$.convertor.learnDestroy(c2dTexType, tex2DCompatibleDestructor);
// TODO
// $.convertor.learnDestroy(imageTexType, tex2DCompatibleDestructor);
return [c2dTexType, imageTexType];
} }
// private // private

View File

@ -310,9 +310,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
// We call the event on the parent viewer window no matter what // We call the event on the parent viewer window no matter what
const eventTarget = this.viewer.viewer || this.viewer; const eventTarget = this.viewer.viewer || this.viewer;
// However, we must pick the correct drawer reference (navigator VS viewer) // However, we must pick the correct drawer reference (navigator VS viewer)
const supportedFormats = this.viewer.drawer.getRequiredDataFormats(); const drawer = this.viewer.drawer;
const keepInternalCacheCopy = this.viewer.drawer.options.usePrivateCache;
const drawerId = this.viewer.drawer.getId();
const jobList = tileList.map(tile => { const jobList = tileList.map(tile => {
const tiledImage = tile.tiledImage; const tiledImage = tile.tiledImage;
@ -360,7 +358,6 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
tiledImage._tileCache.restoreTilesThatShareOriginalCache(tile, tile.getCache(tile.originalCacheKey), true); tiledImage._tileCache.restoreTilesThatShareOriginalCache(tile, tile.getCache(tile.originalCacheKey), true);
} }
}; };
/** /**
* @event tile-invalidated * @event tile-invalidated
* @memberof OpenSeadragon.Viewer * @memberof OpenSeadragon.Viewer
@ -391,7 +388,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
}).then(_ => { }).then(_ => {
if (originalCache.__invStamp === tStamp && (tile.loaded || tile.loading)) { if (originalCache.__invStamp === tStamp && (tile.loaded || tile.loading)) {
if (workingCache) { if (workingCache) {
return workingCache.prepareForRendering(drawerId, supportedFormats, keepInternalCacheCopy).then(c => { return workingCache.prepareForRendering(drawer).then(c => {
if (c && originalCache.__invStamp === tStamp) { if (c && originalCache.__invStamp === tStamp) {
atomicCacheSwap(); atomicCacheSwap();
originalCache.__invStamp = null; originalCache.__invStamp = null;
@ -402,7 +399,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
// If we requested restore, perform now // If we requested restore, perform now
if (restoreTiles) { if (restoreTiles) {
const freshOriginalCacheRef = tile.getCache(tile.originalCacheKey); const freshOriginalCacheRef = tile.getCache(tile.originalCacheKey);
return freshOriginalCacheRef.prepareForRendering(drawerId, supportedFormats, keepInternalCacheCopy).then((c) => { return freshOriginalCacheRef.prepareForRendering(drawer).then((c) => {
if (c && originalCache.__invStamp === tStamp) { if (c && originalCache.__invStamp === tStamp) {
atomicCacheSwap(); atomicCacheSwap();
originalCache.__invStamp = null; originalCache.__invStamp = null;
@ -412,7 +409,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
// Preventive call to ensure we stay compatible // Preventive call to ensure we stay compatible
const freshMainCacheRef = tile.getCache(); const freshMainCacheRef = tile.getCache();
return freshMainCacheRef.prepareForRendering(drawerId, supportedFormats, keepInternalCacheCopy).then(() => { return freshMainCacheRef.prepareForRendering(drawer).then(() => {
atomicCacheSwap(); atomicCacheSwap();
originalCache.__invStamp = null; originalCache.__invStamp = null;
}); });