@vitessce/spatial-accelerated 3.8.5 → 3.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +139 -2
- package/dist-tsc/VolumeDataManager.d.ts +30 -0
- package/dist-tsc/VolumeDataManager.d.ts.map +1 -1
- package/dist-tsc/VolumeDataManager.js +160 -3
- package/dist-tsc/VolumeView.d.ts.map +1 -1
- package/dist-tsc/VolumeView.js +4 -0
- package/package.json +11 -11
- package/src/VolumeDataManager.js +182 -3
- package/src/VolumeView.js +4 -0
package/dist/index.js
CHANGED
|
@@ -49364,6 +49364,7 @@ function _requestBufferToRequestObjects(buffer, k) {
|
|
|
49364
49364
|
class VolumeDataManager {
|
|
49365
49365
|
constructor(glParam) {
|
|
49366
49366
|
logWithColor$2("CLASS INITIALIZING");
|
|
49367
|
+
log.debug("VolumeDataManager constructor", { glParam, glParamContext: glParam.getContext?.() });
|
|
49367
49368
|
const gl = glParam.getContext?.() || glParam;
|
|
49368
49369
|
const renderer = glParam;
|
|
49369
49370
|
if (gl.domElement && gl.getContext) {
|
|
@@ -49386,12 +49387,20 @@ class VolumeDataManager {
|
|
|
49386
49387
|
};
|
|
49387
49388
|
return defaults[param] || 0;
|
|
49388
49389
|
},
|
|
49390
|
+
isContextLost: () => false,
|
|
49389
49391
|
MAX_TEXTURE_SIZE: "MAX_TEXTURE_SIZE",
|
|
49390
49392
|
MAX_3D_TEXTURE_SIZE: "MAX_3D_TEXTURE_SIZE",
|
|
49391
49393
|
MAX_RENDERBUFFER_SIZE: "MAX_RENDERBUFFER_SIZE",
|
|
49392
49394
|
MAX_UNIFORM_BUFFER_BINDINGS: "MAX_UNIFORM_BUFFER_BINDINGS"
|
|
49393
49395
|
};
|
|
49394
49396
|
}
|
|
49397
|
+
this._originalGlParam = glParam;
|
|
49398
|
+
this._isContextLost = false;
|
|
49399
|
+
this._contextRestoredCallbacks = [];
|
|
49400
|
+
if (this.gl && this.gl.canvas) {
|
|
49401
|
+
this.gl.canvas.addEventListener("webglcontextlost", this._handleContextLost.bind(this));
|
|
49402
|
+
this.gl.canvas.addEventListener("webglcontextrestored", this._handleContextRestored.bind(this));
|
|
49403
|
+
}
|
|
49395
49404
|
log.debug("GL CONSTANTS");
|
|
49396
49405
|
log.debug(this.gl);
|
|
49397
49406
|
log.debug(this.gl.TEXTURE0);
|
|
@@ -49483,8 +49492,77 @@ class VolumeDataManager {
|
|
|
49483
49492
|
this.needsBailout = false;
|
|
49484
49493
|
this.initStatus = INIT_STATUS.NOT_STARTED;
|
|
49485
49494
|
this.initError = null;
|
|
49495
|
+
this._lastChannelConfig = null;
|
|
49486
49496
|
logWithColor$2("VolumeDataManager constructor complete");
|
|
49487
49497
|
}
|
|
49498
|
+
/**
|
|
49499
|
+
* Handle WebGL context loss
|
|
49500
|
+
*/
|
|
49501
|
+
_handleContextLost(event) {
|
|
49502
|
+
logWithColor$2("CONTEXT LOST");
|
|
49503
|
+
log.warn("WebGL context lost, preventing default and setting flag");
|
|
49504
|
+
event.preventDefault();
|
|
49505
|
+
this._isContextLost = true;
|
|
49506
|
+
if (this.channels && this.channels.zarrMappings) {
|
|
49507
|
+
this._lastChannelConfig = {
|
|
49508
|
+
zarrMappings: [...this.channels.zarrMappings],
|
|
49509
|
+
colorMappings: [...this.channels.colorMappings],
|
|
49510
|
+
downsampleMin: [...this.channels.downsampleMin],
|
|
49511
|
+
downsampleMax: [...this.channels.downsampleMax]
|
|
49512
|
+
};
|
|
49513
|
+
}
|
|
49514
|
+
}
|
|
49515
|
+
/**
|
|
49516
|
+
* Handle WebGL context restoration
|
|
49517
|
+
*/
|
|
49518
|
+
_handleContextRestored(event) {
|
|
49519
|
+
logWithColor$2("CONTEXT RESTORED");
|
|
49520
|
+
log.warn("WebGL context restored, reinitializing textures");
|
|
49521
|
+
this._isContextLost = false;
|
|
49522
|
+
if (this._originalGlParam && this._originalGlParam.getContext) {
|
|
49523
|
+
this.gl = this._originalGlParam.getContext();
|
|
49524
|
+
}
|
|
49525
|
+
if (this._lastChannelConfig) {
|
|
49526
|
+
this.channels.zarrMappings = [...this._lastChannelConfig.zarrMappings];
|
|
49527
|
+
this.channels.colorMappings = [...this._lastChannelConfig.colorMappings];
|
|
49528
|
+
this.channels.downsampleMin = [...this._lastChannelConfig.downsampleMin];
|
|
49529
|
+
this.channels.downsampleMax = [...this._lastChannelConfig.downsampleMax];
|
|
49530
|
+
log.debug("Restored channel configuration after context loss");
|
|
49531
|
+
}
|
|
49532
|
+
if (this.PT && this.zarrStore && this.zarrStore.brickLayout) {
|
|
49533
|
+
try {
|
|
49534
|
+
this.initMRMCPT();
|
|
49535
|
+
log.debug("Successfully reinitialized MRMCPT after context restoration");
|
|
49536
|
+
} catch (error) {
|
|
49537
|
+
log.error("Failed to reinitialize MRMCPT after context restoration:", error);
|
|
49538
|
+
}
|
|
49539
|
+
}
|
|
49540
|
+
this._contextRestoredCallbacks.forEach((callback) => {
|
|
49541
|
+
try {
|
|
49542
|
+
callback();
|
|
49543
|
+
} catch (error) {
|
|
49544
|
+
log.error("Error in context restored callback:", error);
|
|
49545
|
+
}
|
|
49546
|
+
});
|
|
49547
|
+
}
|
|
49548
|
+
/**
|
|
49549
|
+
* Check if WebGL context is lost
|
|
49550
|
+
*/
|
|
49551
|
+
isContextLost() {
|
|
49552
|
+
if (this._isContextLost) return true;
|
|
49553
|
+
if (this.gl && typeof this.gl.isContextLost === "function") {
|
|
49554
|
+
return this.gl.isContextLost();
|
|
49555
|
+
}
|
|
49556
|
+
return false;
|
|
49557
|
+
}
|
|
49558
|
+
/**
|
|
49559
|
+
* Register a callback to be called when context is restored
|
|
49560
|
+
*/
|
|
49561
|
+
onContextRestored(callback) {
|
|
49562
|
+
if (typeof callback === "function") {
|
|
49563
|
+
this._contextRestoredCallbacks.push(callback);
|
|
49564
|
+
}
|
|
49565
|
+
}
|
|
49488
49566
|
initImages(images, imageLayerScopes) {
|
|
49489
49567
|
logWithColor$2("INIT IMAGES");
|
|
49490
49568
|
this.images = images;
|
|
@@ -49778,6 +49856,12 @@ class VolumeDataManager {
|
|
|
49778
49856
|
log.debug("newColorMappings", newColorMappings);
|
|
49779
49857
|
this.channels.colorMappings = newColorMappings;
|
|
49780
49858
|
log.debug("updatedChannels", this.channels);
|
|
49859
|
+
this._lastChannelConfig = {
|
|
49860
|
+
zarrMappings: [...this.channels.zarrMappings],
|
|
49861
|
+
colorMappings: [...this.channels.colorMappings],
|
|
49862
|
+
downsampleMin: [...this.channels.downsampleMin],
|
|
49863
|
+
downsampleMax: [...this.channels.downsampleMax]
|
|
49864
|
+
};
|
|
49781
49865
|
}
|
|
49782
49866
|
/**
|
|
49783
49867
|
* Try to load a resolution level
|
|
@@ -49895,6 +49979,10 @@ class VolumeDataManager {
|
|
|
49895
49979
|
log.debug("processRequestData: already busy, skipping");
|
|
49896
49980
|
return;
|
|
49897
49981
|
}
|
|
49982
|
+
if (this.isContextLost()) {
|
|
49983
|
+
log.debug("processRequestData: WebGL context is lost, skipping");
|
|
49984
|
+
return;
|
|
49985
|
+
}
|
|
49898
49986
|
this.isBusy = true;
|
|
49899
49987
|
this.triggerRequest = false;
|
|
49900
49988
|
const { requests, origRequestCount } = _requestBufferToRequestObjects(buffer, this.k);
|
|
@@ -49912,6 +50000,10 @@ class VolumeDataManager {
|
|
|
49912
50000
|
this.needsBailout = true;
|
|
49913
50001
|
return;
|
|
49914
50002
|
}
|
|
50003
|
+
if (this.isContextLost()) {
|
|
50004
|
+
log.debug("processUsageData: WebGL context is lost, skipping");
|
|
50005
|
+
return;
|
|
50006
|
+
}
|
|
49915
50007
|
this.isBusy = true;
|
|
49916
50008
|
this.triggerUsage = false;
|
|
49917
50009
|
const now2 = ++this.timeStamp;
|
|
@@ -49951,6 +50043,10 @@ class VolumeDataManager {
|
|
|
49951
50043
|
log.error("pagetable texture not initialized");
|
|
49952
50044
|
return;
|
|
49953
50045
|
}
|
|
50046
|
+
if (this.isContextLost()) {
|
|
50047
|
+
log.warn("WebGL context is lost, skipping channel purge");
|
|
50048
|
+
return;
|
|
50049
|
+
}
|
|
49954
50050
|
this.channels.downsampleMin[ptChannelIndex] = void 0;
|
|
49955
50051
|
this.channels.downsampleMax[ptChannelIndex] = void 0;
|
|
49956
50052
|
this.channels.zarrMappings[ptChannelIndex] = void 0;
|
|
@@ -49991,6 +50087,10 @@ class VolumeDataManager {
|
|
|
49991
50087
|
// Update a PT entry
|
|
49992
50088
|
_updatePTEntry(ptX, ptY, ptZ, ptVal) {
|
|
49993
50089
|
if (!this.ptTHREE) return;
|
|
50090
|
+
if (this.isContextLost()) {
|
|
50091
|
+
log.warn("WebGL context is lost, skipping PT entry update");
|
|
50092
|
+
return;
|
|
50093
|
+
}
|
|
49994
50094
|
const { gl } = this;
|
|
49995
50095
|
const texPT = this.renderer.properties.get(this.ptTHREE).__webglTexture;
|
|
49996
50096
|
gl.activeTexture(gl.TEXTURE0);
|
|
@@ -50052,6 +50152,11 @@ class VolumeDataManager {
|
|
|
50052
50152
|
* 4. Upload one brick + PT entry *
|
|
50053
50153
|
* ------------------------------------------------------------- */
|
|
50054
50154
|
async _uploadBrick(ptCoord, bcSlot) {
|
|
50155
|
+
log.debug("uploading brick", ptCoord, bcSlot);
|
|
50156
|
+
if (this.isContextLost()) {
|
|
50157
|
+
log.warn("WebGL context is lost, skipping brick upload");
|
|
50158
|
+
return;
|
|
50159
|
+
}
|
|
50055
50160
|
if (ptCoord.x >= this.PT.xExtent || ptCoord.y >= this.PT.yExtent || ptCoord.z >= this.PT.zTotal || ptCoord.x < 0 || ptCoord.y < 0 || ptCoord.z < 0) {
|
|
50056
50161
|
log.error("this.PT", this.PT);
|
|
50057
50162
|
log.error("ptCoord out of bounds", ptCoord);
|
|
@@ -50063,10 +50168,40 @@ class VolumeDataManager {
|
|
|
50063
50168
|
ptCoord.z,
|
|
50064
50169
|
{ PT_zExtent: this.PT.zExtent, PT_z0Extent: this.PT.z0Extent, PT_anchors: this.PT.anchors }
|
|
50065
50170
|
);
|
|
50171
|
+
if (!this.channels || !this.channels.zarrMappings || this.channels.zarrMappings.length === 0) {
|
|
50172
|
+
log.error("Channel mappings not initialized, skipping brick upload");
|
|
50173
|
+
return;
|
|
50174
|
+
}
|
|
50175
|
+
if (channel < 0 || channel >= this.channels.zarrMappings.length) {
|
|
50176
|
+
log.error("Channel index out of bounds", { channel, mappingsLength: this.channels.zarrMappings.length });
|
|
50177
|
+
return;
|
|
50178
|
+
}
|
|
50066
50179
|
const zarrChannel = this.channels.zarrMappings[channel];
|
|
50067
50180
|
if (zarrChannel === void 0 || zarrChannel === -1) {
|
|
50068
|
-
log.
|
|
50069
|
-
|
|
50181
|
+
log.warn("zarrChannel is undefined or -1", {
|
|
50182
|
+
zarrChannel,
|
|
50183
|
+
channel,
|
|
50184
|
+
ptCoord,
|
|
50185
|
+
channelMappings: this.channels.zarrMappings,
|
|
50186
|
+
contextLost: this.isContextLost()
|
|
50187
|
+
});
|
|
50188
|
+
if (this._lastChannelConfig && this._lastChannelConfig.zarrMappings[channel] !== void 0) {
|
|
50189
|
+
log.warn("Attempting to use last known channel config");
|
|
50190
|
+
this.channels.zarrMappings = [...this._lastChannelConfig.zarrMappings];
|
|
50191
|
+
this.channels.colorMappings = [...this._lastChannelConfig.colorMappings];
|
|
50192
|
+
this.channels.downsampleMin = [...this._lastChannelConfig.downsampleMin];
|
|
50193
|
+
this.channels.downsampleMax = [...this._lastChannelConfig.downsampleMax];
|
|
50194
|
+
const restoredZarrChannel = this.channels.zarrMappings[channel];
|
|
50195
|
+
if (restoredZarrChannel !== void 0 && restoredZarrChannel !== -1) {
|
|
50196
|
+
log.debug("Successfully restored channel mapping, continuing with upload");
|
|
50197
|
+
} else {
|
|
50198
|
+
log.error("Could not restore valid channel mapping, aborting brick upload");
|
|
50199
|
+
return;
|
|
50200
|
+
}
|
|
50201
|
+
} else {
|
|
50202
|
+
log.error("No fallback channel config available, aborting brick upload");
|
|
50203
|
+
return;
|
|
50204
|
+
}
|
|
50070
50205
|
}
|
|
50071
50206
|
log.debug("starting to load zarr chunk", { resolution, z, y, x, zarrChannel });
|
|
50072
50207
|
let chunk = await this.loadZarrChunk(0, zarrChannel, z, y, x, resolution);
|
|
@@ -50157,6 +50292,7 @@ class VolumeDataManager {
|
|
|
50157
50292
|
async handleBrickRequests(ptRequests) {
|
|
50158
50293
|
if (ptRequests.length === 0) return;
|
|
50159
50294
|
const slots = this._allocateBCSlots(ptRequests.length);
|
|
50295
|
+
log.debug("Handling brick requests:", { requestCount: ptRequests.length, slotCount: slots.length });
|
|
50160
50296
|
log.debug("handleBrickRequests: starting for loop");
|
|
50161
50297
|
for (let i2 = 0; i2 < ptRequests.length; ++i2) {
|
|
50162
50298
|
log.debug("uploading brick", ptRequests[i2], slots[i2]);
|
|
@@ -52815,6 +52951,7 @@ function VolumeView(props) {
|
|
|
52815
52951
|
const RENDER_PRIORITY = 1;
|
|
52816
52952
|
useFrame((state, delta, xrFrame) => {
|
|
52817
52953
|
if (!mrtRef.current || !dataManager || !renderManager) return;
|
|
52954
|
+
if (!renderState.shader) return;
|
|
52818
52955
|
const {
|
|
52819
52956
|
gl: frameGl,
|
|
52820
52957
|
camera: frameCamera,
|
|
@@ -39,6 +39,9 @@ export class VolumeDataManager {
|
|
|
39
39
|
constructor(glParam: any);
|
|
40
40
|
gl: any;
|
|
41
41
|
renderer: any;
|
|
42
|
+
_originalGlParam: any;
|
|
43
|
+
_isContextLost: boolean;
|
|
44
|
+
_contextRestoredCallbacks: any[];
|
|
42
45
|
deviceLimits: {
|
|
43
46
|
maxTextureSize: any;
|
|
44
47
|
max3DTextureSize: any;
|
|
@@ -93,6 +96,33 @@ export class VolumeDataManager {
|
|
|
93
96
|
needsBailout: boolean;
|
|
94
97
|
initStatus: string;
|
|
95
98
|
initError: any;
|
|
99
|
+
_lastChannelConfig: {
|
|
100
|
+
zarrMappings: never[];
|
|
101
|
+
colorMappings: never[];
|
|
102
|
+
downsampleMin: never[];
|
|
103
|
+
downsampleMax: never[];
|
|
104
|
+
} | {
|
|
105
|
+
zarrMappings: never[];
|
|
106
|
+
colorMappings: never[];
|
|
107
|
+
downsampleMin: never[];
|
|
108
|
+
downsampleMax: never[];
|
|
109
|
+
} | null;
|
|
110
|
+
/**
|
|
111
|
+
* Handle WebGL context loss
|
|
112
|
+
*/
|
|
113
|
+
_handleContextLost(event: any): void;
|
|
114
|
+
/**
|
|
115
|
+
* Handle WebGL context restoration
|
|
116
|
+
*/
|
|
117
|
+
_handleContextRestored(event: any): void;
|
|
118
|
+
/**
|
|
119
|
+
* Check if WebGL context is lost
|
|
120
|
+
*/
|
|
121
|
+
isContextLost(): any;
|
|
122
|
+
/**
|
|
123
|
+
* Register a callback to be called when context is restored
|
|
124
|
+
*/
|
|
125
|
+
onContextRestored(callback: any): void;
|
|
96
126
|
initImages(images: any, imageLayerScopes: any): void;
|
|
97
127
|
images: any;
|
|
98
128
|
imageLayerScopes: any;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VolumeDataManager.d.ts","sourceRoot":"","sources":["../src/VolumeDataManager.js"],"names":[],"mappings":"AA8DA,yEAaC;AACD,8EAeC;AAED;;;;GAIG;AACH,kDAHW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,8BAC1B,MAAM;;;;;;;;;;;;;EAyHhB;AAMD,kFAaC;AAqBD;;;;;;EA2DC;AAED;;;;;;;EA0BC;AAID;IACE,
|
|
1
|
+
{"version":3,"file":"VolumeDataManager.d.ts","sourceRoot":"","sources":["../src/VolumeDataManager.js"],"names":[],"mappings":"AA8DA,yEAaC;AACD,8EAeC;AAED;;;;GAIG;AACH,kDAHW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,8BAC1B,MAAM;;;;;;;;;;;;;EAyHhB;AAMD,kFAaC;AAqBD;;;;;;EA2DC;AAED;;;;;;;EA0BC;AAID;IACE,0BA2KC;IA5JG,QAAyB;IAa3B,cAAwB;IAyBxB,sBAA+B;IAC/B,wBAA2B;IAC3B,iCAAmC;IAenC;;;;;MAKC;IAED;;;;;;;;;;;;MAcC;IAED,8BAAmB;IACnB,8BAAmB;IAEnB;;;;;;MAMC;IAED;;;;;;;;;MAiBC;IAQD,2BAAiC;IAYjC,gBAAmB;IAGnB,oBAAuD;IACvD,gBAAwD;IACxD,gBAAmB;IACnB,sBAAsB;IAGtB,aAAmD;IAGnD,gBAAkB;IAElB,sBAAwB;IACxB,wBAA2B;IAC3B,kBAAkB;IAClB,UAAW;IACX,uBAA0B;IAE1B,sBAAyB;IAGzB,mBAAyC;IACzC,eAAqB;IAGrB;;;;;;;;;;aAA8B;IAKhC;;OAEG;IACH,qCAeC;IAED;;OAEG;IACH,yCAqCC;IAED;;OAEG;IACH,qBAMC;IAED;;OAEG;IACH,uCAIC;IAED,qDAIC;IAFC,YAAoB;IACpB,sBAAwC;IAG1C;;;;;OAKG;IACH,mBAFa,OAAO,CAAC,MAAM,CAAC,CA8S3B;IAjQG,kBAAmD;IAmQvD;;;;;;;;OAQG;IACH,mBAqBC;IASD,6BAKC;IAED,wCAqHC;IAED;;;;;OAKG;IAqBH;;;OAGG;IACH,kCAQC;IAED;;;OAGG;IACH,uBAFa,MAAM,CAUlB;IAED,+BAQC;IAED,kCASC;IAED,gCASC;IAED;;;;;;;;;OASG;IACH,iBARW,MAAM,iBACN,MAAM,iBACN,MAAM,KACN,MAAM,KACN,MAAM,cACN,MAAM,GACJ,OAAO,CAAC,UAAU,CAAC,CAiC/B;IAED,+CA0BC;IAGD,6CA0DC;IAGD,gCAcC;IAED,yCAmDC;IAGD,+DAqBC;IAKD;;;;OAIG;IACH,oBAHW,MAAM,GACJ;QAAE,OAAO,MAAC;QAAC,CAAC,MAAC;QAAC,CAAC,MAAC;QAAC,CAAC,MAAA;KAAE,EAAE,CAyClC;IAKD,uDAqLC;IAKD,oDA2CC;IAKD,kBAWC;CACF;8BAhpDM,OAAO"}
|
|
@@ -300,6 +300,7 @@ export class VolumeDataManager {
|
|
|
300
300
|
constructor(glParam) {
|
|
301
301
|
logWithColor('CLASS INITIALIZING');
|
|
302
302
|
// this.store = new zarrita.FetchStore(url); // TODO: use this.images instead
|
|
303
|
+
log.debug('VolumeDataManager constructor', { glParam, glParamContext: glParam.getContext?.() });
|
|
303
304
|
const gl = glParam.getContext?.() || glParam;
|
|
304
305
|
const renderer = glParam;
|
|
305
306
|
// this.images = images;
|
|
@@ -335,12 +336,22 @@ export class VolumeDataManager {
|
|
|
335
336
|
};
|
|
336
337
|
return defaults[param] || 0;
|
|
337
338
|
},
|
|
339
|
+
isContextLost: () => false,
|
|
338
340
|
MAX_TEXTURE_SIZE: 'MAX_TEXTURE_SIZE',
|
|
339
341
|
MAX_3D_TEXTURE_SIZE: 'MAX_3D_TEXTURE_SIZE',
|
|
340
342
|
MAX_RENDERBUFFER_SIZE: 'MAX_RENDERBUFFER_SIZE',
|
|
341
343
|
MAX_UNIFORM_BUFFER_BINDINGS: 'MAX_UNIFORM_BUFFER_BINDINGS',
|
|
342
344
|
};
|
|
343
345
|
}
|
|
346
|
+
// Store references for context loss handling
|
|
347
|
+
this._originalGlParam = glParam;
|
|
348
|
+
this._isContextLost = false;
|
|
349
|
+
this._contextRestoredCallbacks = [];
|
|
350
|
+
// Add context loss event listeners if we have a real WebGL context
|
|
351
|
+
if (this.gl && this.gl.canvas) {
|
|
352
|
+
this.gl.canvas.addEventListener('webglcontextlost', this._handleContextLost.bind(this));
|
|
353
|
+
this.gl.canvas.addEventListener('webglcontextrestored', this._handleContextRestored.bind(this));
|
|
354
|
+
}
|
|
344
355
|
log.debug('GL CONSTANTS');
|
|
345
356
|
log.debug(this.gl);
|
|
346
357
|
log.debug(this.gl.TEXTURE0);
|
|
@@ -430,8 +441,86 @@ export class VolumeDataManager {
|
|
|
430
441
|
// Add initialization status
|
|
431
442
|
this.initStatus = INIT_STATUS.NOT_STARTED;
|
|
432
443
|
this.initError = null;
|
|
444
|
+
// Store the last known configuration for context restoration
|
|
445
|
+
this._lastChannelConfig = null;
|
|
433
446
|
logWithColor('VolumeDataManager constructor complete');
|
|
434
447
|
}
|
|
448
|
+
/**
|
|
449
|
+
* Handle WebGL context loss
|
|
450
|
+
*/
|
|
451
|
+
_handleContextLost(event) {
|
|
452
|
+
logWithColor('CONTEXT LOST');
|
|
453
|
+
log.warn('WebGL context lost, preventing default and setting flag');
|
|
454
|
+
event.preventDefault();
|
|
455
|
+
this._isContextLost = true;
|
|
456
|
+
// Store current channel configuration for restoration
|
|
457
|
+
if (this.channels && this.channels.zarrMappings) {
|
|
458
|
+
this._lastChannelConfig = {
|
|
459
|
+
zarrMappings: [...this.channels.zarrMappings],
|
|
460
|
+
colorMappings: [...this.channels.colorMappings],
|
|
461
|
+
downsampleMin: [...this.channels.downsampleMin],
|
|
462
|
+
downsampleMax: [...this.channels.downsampleMax],
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Handle WebGL context restoration
|
|
468
|
+
*/
|
|
469
|
+
_handleContextRestored(event) {
|
|
470
|
+
logWithColor('CONTEXT RESTORED');
|
|
471
|
+
log.warn('WebGL context restored, reinitializing textures');
|
|
472
|
+
this._isContextLost = false;
|
|
473
|
+
// Reinitialize WebGL context reference
|
|
474
|
+
if (this._originalGlParam && this._originalGlParam.getContext) {
|
|
475
|
+
this.gl = this._originalGlParam.getContext();
|
|
476
|
+
}
|
|
477
|
+
// Restore channel configuration if available
|
|
478
|
+
if (this._lastChannelConfig) {
|
|
479
|
+
this.channels.zarrMappings = [...this._lastChannelConfig.zarrMappings];
|
|
480
|
+
this.channels.colorMappings = [...this._lastChannelConfig.colorMappings];
|
|
481
|
+
this.channels.downsampleMin = [...this._lastChannelConfig.downsampleMin];
|
|
482
|
+
this.channels.downsampleMax = [...this._lastChannelConfig.downsampleMax];
|
|
483
|
+
log.debug('Restored channel configuration after context loss');
|
|
484
|
+
}
|
|
485
|
+
// Reinitialize textures if they were previously created
|
|
486
|
+
if (this.PT && this.zarrStore && this.zarrStore.brickLayout) {
|
|
487
|
+
try {
|
|
488
|
+
this.initMRMCPT();
|
|
489
|
+
log.debug('Successfully reinitialized MRMCPT after context restoration');
|
|
490
|
+
}
|
|
491
|
+
catch (error) {
|
|
492
|
+
log.error('Failed to reinitialize MRMCPT after context restoration:', error);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
// Call any registered restoration callbacks
|
|
496
|
+
this._contextRestoredCallbacks.forEach((callback) => {
|
|
497
|
+
try {
|
|
498
|
+
callback();
|
|
499
|
+
}
|
|
500
|
+
catch (error) {
|
|
501
|
+
log.error('Error in context restored callback:', error);
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Check if WebGL context is lost
|
|
507
|
+
*/
|
|
508
|
+
isContextLost() {
|
|
509
|
+
if (this._isContextLost)
|
|
510
|
+
return true;
|
|
511
|
+
if (this.gl && typeof this.gl.isContextLost === 'function') {
|
|
512
|
+
return this.gl.isContextLost();
|
|
513
|
+
}
|
|
514
|
+
return false;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Register a callback to be called when context is restored
|
|
518
|
+
*/
|
|
519
|
+
onContextRestored(callback) {
|
|
520
|
+
if (typeof callback === 'function') {
|
|
521
|
+
this._contextRestoredCallbacks.push(callback);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
435
524
|
initImages(images, imageLayerScopes) {
|
|
436
525
|
logWithColor('INIT IMAGES');
|
|
437
526
|
this.images = images;
|
|
@@ -823,6 +912,13 @@ export class VolumeDataManager {
|
|
|
823
912
|
log.debug('newColorMappings', newColorMappings);
|
|
824
913
|
this.channels.colorMappings = newColorMappings;
|
|
825
914
|
log.debug('updatedChannels', this.channels);
|
|
915
|
+
// Store the updated configuration for context restoration
|
|
916
|
+
this._lastChannelConfig = {
|
|
917
|
+
zarrMappings: [...this.channels.zarrMappings],
|
|
918
|
+
colorMappings: [...this.channels.colorMappings],
|
|
919
|
+
downsampleMin: [...this.channels.downsampleMin],
|
|
920
|
+
downsampleMax: [...this.channels.downsampleMax],
|
|
921
|
+
};
|
|
826
922
|
}
|
|
827
923
|
/**
|
|
828
924
|
* Try to load a resolution level
|
|
@@ -943,6 +1039,11 @@ export class VolumeDataManager {
|
|
|
943
1039
|
log.debug('processRequestData: already busy, skipping');
|
|
944
1040
|
return;
|
|
945
1041
|
}
|
|
1042
|
+
// Check for context loss
|
|
1043
|
+
if (this.isContextLost()) {
|
|
1044
|
+
log.debug('processRequestData: WebGL context is lost, skipping');
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
946
1047
|
this.isBusy = true;
|
|
947
1048
|
this.triggerRequest = false;
|
|
948
1049
|
const { requests, origRequestCount } = _requestBufferToRequestObjects(buffer, this.k);
|
|
@@ -961,6 +1062,11 @@ export class VolumeDataManager {
|
|
|
961
1062
|
this.needsBailout = true; // Set a flag to indicate we need to bail out of processing requests
|
|
962
1063
|
return;
|
|
963
1064
|
}
|
|
1065
|
+
// Check for context loss
|
|
1066
|
+
if (this.isContextLost()) {
|
|
1067
|
+
log.debug('processUsageData: WebGL context is lost, skipping');
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
964
1070
|
this.isBusy = true;
|
|
965
1071
|
this.triggerUsage = false;
|
|
966
1072
|
const now = ++this.timeStamp;
|
|
@@ -1019,6 +1125,11 @@ export class VolumeDataManager {
|
|
|
1019
1125
|
log.error('pagetable texture not initialized');
|
|
1020
1126
|
return;
|
|
1021
1127
|
}
|
|
1128
|
+
// Check for context loss before proceeding
|
|
1129
|
+
if (this.isContextLost()) {
|
|
1130
|
+
log.warn('WebGL context is lost, skipping channel purge');
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1022
1133
|
this.channels.downsampleMin[ptChannelIndex] = undefined;
|
|
1023
1134
|
this.channels.downsampleMax[ptChannelIndex] = undefined;
|
|
1024
1135
|
this.channels.zarrMappings[ptChannelIndex] = undefined;
|
|
@@ -1050,6 +1161,11 @@ export class VolumeDataManager {
|
|
|
1050
1161
|
_updatePTEntry(ptX, ptY, ptZ, ptVal) {
|
|
1051
1162
|
if (!this.ptTHREE)
|
|
1052
1163
|
return;
|
|
1164
|
+
// Check for context loss before proceeding
|
|
1165
|
+
if (this.isContextLost()) {
|
|
1166
|
+
log.warn('WebGL context is lost, skipping PT entry update');
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1053
1169
|
const { gl } = this;
|
|
1054
1170
|
const texPT = this.renderer.properties.get(this.ptTHREE).__webglTexture;
|
|
1055
1171
|
gl.activeTexture(gl.TEXTURE0);
|
|
@@ -1109,7 +1225,12 @@ export class VolumeDataManager {
|
|
|
1109
1225
|
* 4. Upload one brick + PT entry *
|
|
1110
1226
|
* ------------------------------------------------------------- */
|
|
1111
1227
|
async _uploadBrick(ptCoord, bcSlot) {
|
|
1112
|
-
|
|
1228
|
+
log.debug('uploading brick', ptCoord, bcSlot);
|
|
1229
|
+
// Check for context loss before proceeding
|
|
1230
|
+
if (this.isContextLost()) {
|
|
1231
|
+
log.warn('WebGL context is lost, skipping brick upload');
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1113
1234
|
if (ptCoord.x >= this.PT.xExtent
|
|
1114
1235
|
|| ptCoord.y >= this.PT.yExtent
|
|
1115
1236
|
|| ptCoord.z >= this.PT.zTotal
|
|
@@ -1124,10 +1245,45 @@ export class VolumeDataManager {
|
|
|
1124
1245
|
// log.debug('this.PT', this.PT);
|
|
1125
1246
|
/* 4.1 fetch chunk from Zarr */
|
|
1126
1247
|
const { channel, resolution, x, y, z } = _ptToZarr(ptCoord.x, ptCoord.y, ptCoord.z, { PT_zExtent: this.PT.zExtent, PT_z0Extent: this.PT.z0Extent, PT_anchors: this.PT.anchors });
|
|
1248
|
+
// Check if channel mappings are valid
|
|
1249
|
+
if (!this.channels || !this.channels.zarrMappings || this.channels.zarrMappings.length === 0) {
|
|
1250
|
+
log.error('Channel mappings not initialized, skipping brick upload');
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
if (channel < 0 || channel >= this.channels.zarrMappings.length) {
|
|
1254
|
+
log.error('Channel index out of bounds', { channel, mappingsLength: this.channels.zarrMappings.length });
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1127
1257
|
const zarrChannel = this.channels.zarrMappings[channel];
|
|
1128
1258
|
if (zarrChannel === undefined || zarrChannel === -1) {
|
|
1129
|
-
log.
|
|
1130
|
-
|
|
1259
|
+
log.warn('zarrChannel is undefined or -1', {
|
|
1260
|
+
zarrChannel,
|
|
1261
|
+
channel,
|
|
1262
|
+
ptCoord,
|
|
1263
|
+
channelMappings: this.channels.zarrMappings,
|
|
1264
|
+
contextLost: this.isContextLost(),
|
|
1265
|
+
});
|
|
1266
|
+
// If this happens due to context loss, try to restore from last known config
|
|
1267
|
+
if (this._lastChannelConfig && this._lastChannelConfig.zarrMappings[channel] !== undefined) {
|
|
1268
|
+
log.warn('Attempting to use last known channel config');
|
|
1269
|
+
this.channels.zarrMappings = [...this._lastChannelConfig.zarrMappings];
|
|
1270
|
+
this.channels.colorMappings = [...this._lastChannelConfig.colorMappings];
|
|
1271
|
+
this.channels.downsampleMin = [...this._lastChannelConfig.downsampleMin];
|
|
1272
|
+
this.channels.downsampleMax = [...this._lastChannelConfig.downsampleMax];
|
|
1273
|
+
// Retry with restored config
|
|
1274
|
+
const restoredZarrChannel = this.channels.zarrMappings[channel];
|
|
1275
|
+
if (restoredZarrChannel !== undefined && restoredZarrChannel !== -1) {
|
|
1276
|
+
log.debug('Successfully restored channel mapping, continuing with upload');
|
|
1277
|
+
}
|
|
1278
|
+
else {
|
|
1279
|
+
log.error('Could not restore valid channel mapping, aborting brick upload');
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
else {
|
|
1284
|
+
log.error('No fallback channel config available, aborting brick upload');
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1131
1287
|
}
|
|
1132
1288
|
log.debug('starting to load zarr chunk', { resolution, z, y, x, zarrChannel });
|
|
1133
1289
|
let chunk = await this.loadZarrChunk(0, zarrChannel, z, y, x, resolution);
|
|
@@ -1229,6 +1385,7 @@ export class VolumeDataManager {
|
|
|
1229
1385
|
/* <= k requests, allocate same number of bricks */
|
|
1230
1386
|
const slots = this._allocateBCSlots(ptRequests.length);
|
|
1231
1387
|
// log.debug('slots', slots);
|
|
1388
|
+
log.debug('Handling brick requests:', { requestCount: ptRequests.length, slotCount: slots.length });
|
|
1232
1389
|
/* upload each (sequentially or Promise.all if you prefer IO overlap) */
|
|
1233
1390
|
log.debug('handleBrickRequests: starting for loop');
|
|
1234
1391
|
for (let i = 0; i < ptRequests.length; ++i) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VolumeView.d.ts","sourceRoot":"","sources":["../src/VolumeView.js"],"names":[],"mappings":"AAkLA,
|
|
1
|
+
{"version":3,"file":"VolumeView.d.ts","sourceRoot":"","sources":["../src/VolumeView.js"],"names":[],"mappings":"AAkLA,2DA4dC"}
|
package/dist-tsc/VolumeView.js
CHANGED
|
@@ -407,6 +407,10 @@ export function VolumeView(props) {
|
|
|
407
407
|
// Reference: https://r3f.docs.pmnd.rs/advanced/pitfalls#%E2%9D%8C-setstate-in-useframe-is-bad
|
|
408
408
|
if (!mrtRef.current || !dataManager || !renderManager)
|
|
409
409
|
return;
|
|
410
|
+
// Don't render to MRT if shader is not ready - this prevents WebGL warnings
|
|
411
|
+
// about missing fragment shader outputs for multiple render targets
|
|
412
|
+
if (!renderState.shader)
|
|
413
|
+
return;
|
|
410
414
|
// Receive the same state as the useThree hook.
|
|
411
415
|
const { gl: frameGl, camera: frameCamera, scene: frameScene, clock, } = state;
|
|
412
416
|
if (!stillRef.current) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitessce/spatial-accelerated",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.7",
|
|
4
4
|
"author": "HIDIVE Lab at HMS",
|
|
5
5
|
"homepage": "http://vitessce.io",
|
|
6
6
|
"repository": {
|
|
@@ -28,16 +28,16 @@
|
|
|
28
28
|
"short-number": "^1.0.6",
|
|
29
29
|
"three": "^0.154.0",
|
|
30
30
|
"zarrita": "0.5.4",
|
|
31
|
-
"@vitessce/constants-internal": "3.8.
|
|
32
|
-
"@vitessce/gl": "3.8.
|
|
33
|
-
"@vitessce/legend": "3.8.
|
|
34
|
-
"@vitessce/sets-utils": "3.8.
|
|
35
|
-
"@vitessce/spatial-utils": "3.8.
|
|
36
|
-
"@vitessce/tooltip": "3.8.
|
|
37
|
-
"@vitessce/utils": "3.8.
|
|
38
|
-
"@vitessce/vit-s": "3.8.
|
|
39
|
-
"@vitessce/styles": "3.8.
|
|
40
|
-
"@vitessce/globals": "3.8.
|
|
31
|
+
"@vitessce/constants-internal": "3.8.7",
|
|
32
|
+
"@vitessce/gl": "3.8.7",
|
|
33
|
+
"@vitessce/legend": "3.8.7",
|
|
34
|
+
"@vitessce/sets-utils": "3.8.7",
|
|
35
|
+
"@vitessce/spatial-utils": "3.8.7",
|
|
36
|
+
"@vitessce/tooltip": "3.8.7",
|
|
37
|
+
"@vitessce/utils": "3.8.7",
|
|
38
|
+
"@vitessce/vit-s": "3.8.7",
|
|
39
|
+
"@vitessce/styles": "3.8.7",
|
|
40
|
+
"@vitessce/globals": "3.8.7"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/three": "^0.154.0",
|
package/src/VolumeDataManager.js
CHANGED
|
@@ -351,6 +351,8 @@ export class VolumeDataManager {
|
|
|
351
351
|
logWithColor('CLASS INITIALIZING');
|
|
352
352
|
// this.store = new zarrita.FetchStore(url); // TODO: use this.images instead
|
|
353
353
|
|
|
354
|
+
log.debug('VolumeDataManager constructor', { glParam, glParamContext: glParam.getContext?.() });
|
|
355
|
+
|
|
354
356
|
const gl = glParam.getContext?.() || glParam;
|
|
355
357
|
const renderer = glParam;
|
|
356
358
|
|
|
@@ -389,6 +391,7 @@ export class VolumeDataManager {
|
|
|
389
391
|
};
|
|
390
392
|
return defaults[param] || 0;
|
|
391
393
|
},
|
|
394
|
+
isContextLost: () => false,
|
|
392
395
|
MAX_TEXTURE_SIZE: 'MAX_TEXTURE_SIZE',
|
|
393
396
|
MAX_3D_TEXTURE_SIZE: 'MAX_3D_TEXTURE_SIZE',
|
|
394
397
|
MAX_RENDERBUFFER_SIZE: 'MAX_RENDERBUFFER_SIZE',
|
|
@@ -396,6 +399,17 @@ export class VolumeDataManager {
|
|
|
396
399
|
};
|
|
397
400
|
}
|
|
398
401
|
|
|
402
|
+
// Store references for context loss handling
|
|
403
|
+
this._originalGlParam = glParam;
|
|
404
|
+
this._isContextLost = false;
|
|
405
|
+
this._contextRestoredCallbacks = [];
|
|
406
|
+
|
|
407
|
+
// Add context loss event listeners if we have a real WebGL context
|
|
408
|
+
if (this.gl && this.gl.canvas) {
|
|
409
|
+
this.gl.canvas.addEventListener('webglcontextlost', this._handleContextLost.bind(this));
|
|
410
|
+
this.gl.canvas.addEventListener('webglcontextrestored', this._handleContextRestored.bind(this));
|
|
411
|
+
}
|
|
412
|
+
|
|
399
413
|
log.debug('GL CONSTANTS');
|
|
400
414
|
log.debug(this.gl);
|
|
401
415
|
log.debug(this.gl.TEXTURE0);
|
|
@@ -499,9 +513,95 @@ export class VolumeDataManager {
|
|
|
499
513
|
// Add initialization status
|
|
500
514
|
this.initStatus = INIT_STATUS.NOT_STARTED;
|
|
501
515
|
this.initError = null;
|
|
516
|
+
|
|
517
|
+
// Store the last known configuration for context restoration
|
|
518
|
+
this._lastChannelConfig = null;
|
|
519
|
+
|
|
502
520
|
logWithColor('VolumeDataManager constructor complete');
|
|
503
521
|
}
|
|
504
522
|
|
|
523
|
+
/**
|
|
524
|
+
* Handle WebGL context loss
|
|
525
|
+
*/
|
|
526
|
+
_handleContextLost(event) {
|
|
527
|
+
logWithColor('CONTEXT LOST');
|
|
528
|
+
log.warn('WebGL context lost, preventing default and setting flag');
|
|
529
|
+
event.preventDefault();
|
|
530
|
+
this._isContextLost = true;
|
|
531
|
+
|
|
532
|
+
// Store current channel configuration for restoration
|
|
533
|
+
if (this.channels && this.channels.zarrMappings) {
|
|
534
|
+
this._lastChannelConfig = {
|
|
535
|
+
zarrMappings: [...this.channels.zarrMappings],
|
|
536
|
+
colorMappings: [...this.channels.colorMappings],
|
|
537
|
+
downsampleMin: [...this.channels.downsampleMin],
|
|
538
|
+
downsampleMax: [...this.channels.downsampleMax],
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Handle WebGL context restoration
|
|
545
|
+
*/
|
|
546
|
+
_handleContextRestored(event) {
|
|
547
|
+
logWithColor('CONTEXT RESTORED');
|
|
548
|
+
log.warn('WebGL context restored, reinitializing textures');
|
|
549
|
+
this._isContextLost = false;
|
|
550
|
+
|
|
551
|
+
// Reinitialize WebGL context reference
|
|
552
|
+
if (this._originalGlParam && this._originalGlParam.getContext) {
|
|
553
|
+
this.gl = this._originalGlParam.getContext();
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Restore channel configuration if available
|
|
557
|
+
if (this._lastChannelConfig) {
|
|
558
|
+
this.channels.zarrMappings = [...this._lastChannelConfig.zarrMappings];
|
|
559
|
+
this.channels.colorMappings = [...this._lastChannelConfig.colorMappings];
|
|
560
|
+
this.channels.downsampleMin = [...this._lastChannelConfig.downsampleMin];
|
|
561
|
+
this.channels.downsampleMax = [...this._lastChannelConfig.downsampleMax];
|
|
562
|
+
log.debug('Restored channel configuration after context loss');
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Reinitialize textures if they were previously created
|
|
566
|
+
if (this.PT && this.zarrStore && this.zarrStore.brickLayout) {
|
|
567
|
+
try {
|
|
568
|
+
this.initMRMCPT();
|
|
569
|
+
log.debug('Successfully reinitialized MRMCPT after context restoration');
|
|
570
|
+
} catch (error) {
|
|
571
|
+
log.error('Failed to reinitialize MRMCPT after context restoration:', error);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// Call any registered restoration callbacks
|
|
576
|
+
this._contextRestoredCallbacks.forEach((callback) => {
|
|
577
|
+
try {
|
|
578
|
+
callback();
|
|
579
|
+
} catch (error) {
|
|
580
|
+
log.error('Error in context restored callback:', error);
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Check if WebGL context is lost
|
|
587
|
+
*/
|
|
588
|
+
isContextLost() {
|
|
589
|
+
if (this._isContextLost) return true;
|
|
590
|
+
if (this.gl && typeof this.gl.isContextLost === 'function') {
|
|
591
|
+
return this.gl.isContextLost();
|
|
592
|
+
}
|
|
593
|
+
return false;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Register a callback to be called when context is restored
|
|
598
|
+
*/
|
|
599
|
+
onContextRestored(callback) {
|
|
600
|
+
if (typeof callback === 'function') {
|
|
601
|
+
this._contextRestoredCallbacks.push(callback);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
505
605
|
initImages(images, imageLayerScopes) {
|
|
506
606
|
logWithColor('INIT IMAGES');
|
|
507
607
|
this.images = images;
|
|
@@ -971,6 +1071,14 @@ export class VolumeDataManager {
|
|
|
971
1071
|
this.channels.colorMappings = newColorMappings;
|
|
972
1072
|
|
|
973
1073
|
log.debug('updatedChannels', this.channels);
|
|
1074
|
+
|
|
1075
|
+
// Store the updated configuration for context restoration
|
|
1076
|
+
this._lastChannelConfig = {
|
|
1077
|
+
zarrMappings: [...this.channels.zarrMappings],
|
|
1078
|
+
colorMappings: [...this.channels.colorMappings],
|
|
1079
|
+
downsampleMin: [...this.channels.downsampleMin],
|
|
1080
|
+
downsampleMax: [...this.channels.downsampleMax],
|
|
1081
|
+
};
|
|
974
1082
|
}
|
|
975
1083
|
|
|
976
1084
|
/**
|
|
@@ -1107,6 +1215,13 @@ export class VolumeDataManager {
|
|
|
1107
1215
|
log.debug('processRequestData: already busy, skipping');
|
|
1108
1216
|
return;
|
|
1109
1217
|
}
|
|
1218
|
+
|
|
1219
|
+
// Check for context loss
|
|
1220
|
+
if (this.isContextLost()) {
|
|
1221
|
+
log.debug('processRequestData: WebGL context is lost, skipping');
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1110
1225
|
this.isBusy = true;
|
|
1111
1226
|
this.triggerRequest = false;
|
|
1112
1227
|
|
|
@@ -1130,6 +1245,13 @@ export class VolumeDataManager {
|
|
|
1130
1245
|
this.needsBailout = true; // Set a flag to indicate we need to bail out of processing requests
|
|
1131
1246
|
return;
|
|
1132
1247
|
}
|
|
1248
|
+
|
|
1249
|
+
// Check for context loss
|
|
1250
|
+
if (this.isContextLost()) {
|
|
1251
|
+
log.debug('processUsageData: WebGL context is lost, skipping');
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1133
1255
|
this.isBusy = true;
|
|
1134
1256
|
this.triggerUsage = false;
|
|
1135
1257
|
|
|
@@ -1203,6 +1325,12 @@ export class VolumeDataManager {
|
|
|
1203
1325
|
return;
|
|
1204
1326
|
}
|
|
1205
1327
|
|
|
1328
|
+
// Check for context loss before proceeding
|
|
1329
|
+
if (this.isContextLost()) {
|
|
1330
|
+
log.warn('WebGL context is lost, skipping channel purge');
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1206
1334
|
this.channels.downsampleMin[ptChannelIndex] = undefined;
|
|
1207
1335
|
this.channels.downsampleMax[ptChannelIndex] = undefined;
|
|
1208
1336
|
this.channels.zarrMappings[ptChannelIndex] = undefined;
|
|
@@ -1245,6 +1373,12 @@ export class VolumeDataManager {
|
|
|
1245
1373
|
_updatePTEntry(ptX, ptY, ptZ, ptVal) {
|
|
1246
1374
|
if (!this.ptTHREE) return;
|
|
1247
1375
|
|
|
1376
|
+
// Check for context loss before proceeding
|
|
1377
|
+
if (this.isContextLost()) {
|
|
1378
|
+
log.warn('WebGL context is lost, skipping PT entry update');
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1248
1382
|
const { gl } = this;
|
|
1249
1383
|
const texPT = this.renderer.properties.get(this.ptTHREE).__webglTexture;
|
|
1250
1384
|
gl.activeTexture(gl.TEXTURE0);
|
|
@@ -1312,7 +1446,13 @@ export class VolumeDataManager {
|
|
|
1312
1446
|
* 4. Upload one brick + PT entry *
|
|
1313
1447
|
* ------------------------------------------------------------- */
|
|
1314
1448
|
async _uploadBrick(ptCoord, bcSlot) {
|
|
1315
|
-
|
|
1449
|
+
log.debug('uploading brick', ptCoord, bcSlot);
|
|
1450
|
+
|
|
1451
|
+
// Check for context loss before proceeding
|
|
1452
|
+
if (this.isContextLost()) {
|
|
1453
|
+
log.warn('WebGL context is lost, skipping brick upload');
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
1316
1456
|
|
|
1317
1457
|
if (ptCoord.x >= this.PT.xExtent
|
|
1318
1458
|
|| ptCoord.y >= this.PT.yExtent
|
|
@@ -1336,11 +1476,48 @@ export class VolumeDataManager {
|
|
|
1336
1476
|
{ PT_zExtent: this.PT.zExtent, PT_z0Extent: this.PT.z0Extent, PT_anchors: this.PT.anchors },
|
|
1337
1477
|
);
|
|
1338
1478
|
|
|
1479
|
+
// Check if channel mappings are valid
|
|
1480
|
+
if (!this.channels || !this.channels.zarrMappings || this.channels.zarrMappings.length === 0) {
|
|
1481
|
+
log.error('Channel mappings not initialized, skipping brick upload');
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
if (channel < 0 || channel >= this.channels.zarrMappings.length) {
|
|
1486
|
+
log.error('Channel index out of bounds', { channel, mappingsLength: this.channels.zarrMappings.length });
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1339
1490
|
const zarrChannel = this.channels.zarrMappings[channel];
|
|
1340
1491
|
|
|
1341
1492
|
if (zarrChannel === undefined || zarrChannel === -1) {
|
|
1342
|
-
log.
|
|
1343
|
-
|
|
1493
|
+
log.warn('zarrChannel is undefined or -1', {
|
|
1494
|
+
zarrChannel,
|
|
1495
|
+
channel,
|
|
1496
|
+
ptCoord,
|
|
1497
|
+
channelMappings: this.channels.zarrMappings,
|
|
1498
|
+
contextLost: this.isContextLost(),
|
|
1499
|
+
});
|
|
1500
|
+
|
|
1501
|
+
// If this happens due to context loss, try to restore from last known config
|
|
1502
|
+
if (this._lastChannelConfig && this._lastChannelConfig.zarrMappings[channel] !== undefined) {
|
|
1503
|
+
log.warn('Attempting to use last known channel config');
|
|
1504
|
+
this.channels.zarrMappings = [...this._lastChannelConfig.zarrMappings];
|
|
1505
|
+
this.channels.colorMappings = [...this._lastChannelConfig.colorMappings];
|
|
1506
|
+
this.channels.downsampleMin = [...this._lastChannelConfig.downsampleMin];
|
|
1507
|
+
this.channels.downsampleMax = [...this._lastChannelConfig.downsampleMax];
|
|
1508
|
+
|
|
1509
|
+
// Retry with restored config
|
|
1510
|
+
const restoredZarrChannel = this.channels.zarrMappings[channel];
|
|
1511
|
+
if (restoredZarrChannel !== undefined && restoredZarrChannel !== -1) {
|
|
1512
|
+
log.debug('Successfully restored channel mapping, continuing with upload');
|
|
1513
|
+
} else {
|
|
1514
|
+
log.error('Could not restore valid channel mapping, aborting brick upload');
|
|
1515
|
+
return;
|
|
1516
|
+
}
|
|
1517
|
+
} else {
|
|
1518
|
+
log.error('No fallback channel config available, aborting brick upload');
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1344
1521
|
}
|
|
1345
1522
|
|
|
1346
1523
|
log.debug('starting to load zarr chunk', { resolution, z, y, x, zarrChannel });
|
|
@@ -1467,6 +1644,8 @@ export class VolumeDataManager {
|
|
|
1467
1644
|
const slots = this._allocateBCSlots(ptRequests.length);
|
|
1468
1645
|
// log.debug('slots', slots);
|
|
1469
1646
|
|
|
1647
|
+
log.debug('Handling brick requests:', { requestCount: ptRequests.length, slotCount: slots.length });
|
|
1648
|
+
|
|
1470
1649
|
/* upload each (sequentially or Promise.all if you prefer IO overlap) */
|
|
1471
1650
|
log.debug('handleBrickRequests: starting for loop');
|
|
1472
1651
|
for (let i = 0; i < ptRequests.length; ++i) {
|
package/src/VolumeView.js
CHANGED
|
@@ -495,6 +495,10 @@ export function VolumeView(props) {
|
|
|
495
495
|
// Reference: https://r3f.docs.pmnd.rs/advanced/pitfalls#%E2%9D%8C-setstate-in-useframe-is-bad
|
|
496
496
|
if (!mrtRef.current || !dataManager || !renderManager) return;
|
|
497
497
|
|
|
498
|
+
// Don't render to MRT if shader is not ready - this prevents WebGL warnings
|
|
499
|
+
// about missing fragment shader outputs for multiple render targets
|
|
500
|
+
if (!renderState.shader) return;
|
|
501
|
+
|
|
498
502
|
// Receive the same state as the useThree hook.
|
|
499
503
|
const {
|
|
500
504
|
gl: frameGl,
|