BioGTK  6.5.0
A .NET library & program for annotating, editing various microscopy imaging formats using Bioformats supported images. Including whole slide, pyramidal, and series.
Loading...
Searching...
No Matches
BioGTK.SlideRenderer Class Reference

Public Member Functions

 SlideRenderer (SlideGLArea glArea)
 
void SetSource (OpenSlideBase source)
 
void SetSource (SlideBase source)
 
void ClearCache (bool resetLevel=true)
 
void InvalidateRenderState ()
 Marks all in-flight tile work as stale. Call this before changing threshold/range state so old tiles cannot overwrite the current view.
 
async Task UpdateViewAsync (PointD pyramidalOrigin, int viewportWidth, int viewportHeight, double resolution, ZCT coordinate)
 

Properties

bool LastRenderSkipped [get]
 
int CurrentLevel [get]
 
int CachedTextureCount [get]
 

Detailed Description

Definition at line 13 of file SlideRenderer.cs.

Constructor & Destructor Documentation

◆ SlideRenderer()

BioGTK.SlideRenderer.SlideRenderer ( SlideGLArea glArea)

Definition at line 47 of file SlideRenderer.cs.

48 {
49 _glArea = glArea;
50 }

Member Function Documentation

◆ ClearCache()

void BioGTK.SlideRenderer.ClearCache ( bool resetLevel = true)

Definition at line 88 of file SlideRenderer.cs.

89 {
90 // Clearing the visible tile cache must also invalidate any in-flight
91 // fetches. Otherwise an old Z/C/T request can finish after the
92 // coordinate changes and repopulate the GPU cache with stale tiles.
93 Interlocked.Increment(ref _renderStateVersion);
94
95 _glArea.ClearTextureCache();
96 _uploadedTiles.Clear();
97 _uploadedTileInfos.Clear();
98 if (resetLevel)
99 _currentLevel = -1;
100 _tileCache?.Clear();
101 _openTileCache?.cache.cacheMap.Clear();
102
103 _slideBase?.cache?.Clear();
104 _openSlideBase?.cache?.cache.cacheMap.Clear();
105 _displayRangeSeedLevel = -1;
106
107 lock (_pendingLevelSwitchLock)
108 {
109 _pendingLevelRenderInfos = null;
110 _pendingLevel = -1;
111 _pendingPreviousLevel = -1;
112 }
113
114 lock (_pendingTileFetchLock)
115 {
116 _pendingTileFetches.Clear();
117 }
118 }
void ClearTextureCache()
Clear all cached textures.

◆ InvalidateRenderState()

void BioGTK.SlideRenderer.InvalidateRenderState ( )

Marks all in-flight tile work as stale. Call this before changing threshold/range state so old tiles cannot overwrite the current view.

Definition at line 124 of file SlideRenderer.cs.

125 {
126 Interlocked.Increment(ref _renderStateVersion);
127 }

◆ SetSource() [1/2]

void BioGTK.SlideRenderer.SetSource ( OpenSlideBase source)

Definition at line 70 of file SlideRenderer.cs.

71 {
72 _openSlideBase = source;
73 _slideBase = null;
74 _useOpenSlide = true;
75 _openTileCache = new OpenSlideGTK.TileCache(source, 200);
76 ClearCache();
77 }

◆ SetSource() [2/2]

void BioGTK.SlideRenderer.SetSource ( SlideBase source)

Definition at line 79 of file SlideRenderer.cs.

80 {
81 _slideBase = source;
82 _openSlideBase = null;
83 _useOpenSlide = false;
84 _tileCache = new BioLib.TileCache(source, 200);
85 ClearCache();
86 }
Definition Zarr.cs:20

◆ UpdateViewAsync()

async Task BioGTK.SlideRenderer.UpdateViewAsync ( PointD pyramidalOrigin,
int viewportWidth,
int viewportHeight,
double resolution,
ZCT coordinate )

Definition at line 129 of file SlideRenderer.cs.

135 {
136 if (_openSlideBase == null && _slideBase == null)
137 return;
138 if (viewportWidth <= 1 && viewportHeight <= 1)
139 return;
140
141 // Only allow one render pass at a time. Without this gate, rapid pan
142 // events can pile up overlapping async tile fetches and keep large
143 // byte[] buffers alive until every stale render finishes.
144 if (!_renderSemaphore.Wait(0))
145 {
146 LastRenderSkipped = true;
147 return;
148 }
149
150 LastRenderSkipped = false;
151
152 try
153 {
154 int renderVersion = _renderStateVersion;
155 using var tileFetchCts = new CancellationTokenSource(RemoteTileFetchTimeout);
156
157 var schema = _useOpenSlide ? _openSlideBase.Schema : _slideBase.Schema;
158
159 int level = TileUtil.GetLevel(schema.Resolutions, resolution);
160 var levelRes = schema.Resolutions[level];
161 double levelUnitsPerPixel = levelRes.UnitsPerPixel;
162
163 await EnsureGlobalDisplayRangeAsync(coordinate, renderVersion, level).ConfigureAwait(false);
164
165 bool levelChanged = level != _currentLevel;
166
167 // Keep renderer-side bookkeeping scoped to the current viewport.
168 // The GL area already owns the actual texture cache; these collections
169 // are only used for the current render pass and should not accumulate
170 // every tile ever seen while panning.
171 _uploadedTiles.Clear();
172 _uploadedTileInfos.Clear();
173
174 double originX = pyramidalOrigin.X;
175 double originY = pyramidalOrigin.Y;
176 double minX = originX;
177 double width = viewportWidth * resolution;
178 double height = viewportHeight * resolution;
179 // BioLib pyramids use OSM-style inverted Y coordinates. Keep the
180 // renderer in the same convention rather than trying to infer a
181 // positive-down variant from the schema bounds.
182 var viewportExtent = new Extent(minX, -originY - height, minX + width, -originY);
183
184 // Keep the GL scissor aligned with the actual image boundary so
185 // edge tiles don't leak padded black pixels outside the image.
186 double imageMinX = schema.Extent.MinX;
187 double imageMaxX = schema.Extent.MaxX;
188 double imageMinY = schema.Extent.MinY;
189 double imageMaxY = schema.Extent.MaxY;
190 _glArea.ImageScreenX = (float)((imageMinX - pyramidalOrigin.X) / resolution);
191 _glArea.ImageScreenY = (float)((-imageMaxY - pyramidalOrigin.Y) / resolution);
192 _glArea.ImageScreenW = (float)((imageMaxX - imageMinX) / resolution);
193 _glArea.ImageScreenH = (float)((imageMaxY - imageMinY) / resolution);
194
195 // Prefetch one tile beyond each viewport edge so partially visible
196 // edge tiles are available without overfetching an arbitrary 2x area.
197 double tileMarginX = levelRes.TileWidth * levelUnitsPerPixel;
198 double tileMarginY = levelRes.TileHeight * levelUnitsPerPixel;
199 var fetchExtent = new Extent(
200 viewportExtent.MinX - tileMarginX,
201 viewportExtent.MinY - tileMarginY,
202 viewportExtent.MaxX + tileMarginX,
203 viewportExtent.MaxY + tileMarginY);
204
205 List<TileInfo> tileInfos;
206 List<TileInfo> fallbackInfos;
207 if (_useOpenSlide)
208 {
209 // OpenSlide schemas use their historical pixel-space lookup path.
210 var lookupExtent = BioLib.ExtentEx.WorldToPixelInvertedY(fetchExtent, levelUnitsPerPixel);
211 tileInfos = schema.GetTileInfos(lookupExtent, level).ToList();
212 fallbackInfos = schema.GetTileInfos(fetchExtent, level).ToList();
213
214 if (fallbackInfos.Count > 0)
215 {
216 var seen = new HashSet<TileIndex>(tileInfos.Select(t => t.Index));
217 foreach (var info in fallbackInfos)
218 {
219 if (seen.Add(info.Index))
220 tileInfos.Add(info);
221 }
222 }
223 }
224 else
225 {
226 // BioLib/Zarr SlideBase schemas already use the same inverted-Y
227 // world convention as BioLib's slice path. Keep lookups in that
228 // space so translated datasets stay aligned with the tile schema.
229 tileInfos = schema.GetTileInfos(fetchExtent, level).ToList();
230 fallbackInfos = new List<TileInfo>();
231
232 if (tileInfos.Count == 0)
233 {
234 var pixelToWorldExtent = BioLib.ExtentEx.PixelToWorldInvertedY(fetchExtent, levelUnitsPerPixel);
235 fallbackInfos = schema.GetTileInfos(pixelToWorldExtent, level).ToList();
236
237 if (fallbackInfos.Count == 0)
238 {
239 var worldToPixelExtent = BioLib.ExtentEx.WorldToPixelInvertedY(fetchExtent, levelUnitsPerPixel);
240 fallbackInfos = schema.GetTileInfos(worldToPixelExtent, level).ToList();
241 }
242
243 if (fallbackInfos.Count > 0)
244 tileInfos = fallbackInfos;
245 }
246 }
247
248 var renderInfos = new List<TileRenderInfo>();
249 var pendingFetches = new List<(TileInfo Tile, Task<byte[]> FetchTask)>();
250 foreach (var tileInfo in tileInfos)
251 {
252 if (renderVersion != _renderStateVersion)
253 return;
254
255 var fetchTileInfo = tileInfo;
256 var renderTileInfo = tileInfo;
257
258 var renderInfo = CalculateScreenPosition(
259 renderTileInfo,
260 pyramidalOrigin,
261 resolution,
262 level,
263 schema);
264 renderInfos.Add(renderInfo);
265
266 if (!_glArea.HasTileTexture(fetchTileInfo.Index))
267 {
268 bool alreadyPending;
269 lock (_pendingTileFetchLock)
270 {
271 alreadyPending = !_pendingTileFetches.Add(fetchTileInfo.Index);
272 }
273
274 if (!alreadyPending)
275 pendingFetches.Add((fetchTileInfo, FetchTileAsync(fetchTileInfo, level, coordinate, tileFetchCts.Token)));
276 }
277 }
278
279 if (pendingFetches.Count > 0)
280 {
281 foreach (var pending in pendingFetches)
282 _ = ProcessPendingFetchAsync(pending.Tile, pending.FetchTask, tileFetchCts.Token, renderVersion, pyramidalOrigin, resolution, level, schema);
283 }
284
285 if (renderVersion != _renderStateVersion)
286 return;
287
288 bool hasRenderableTexture = renderInfos.Any(tile => _glArea.HasTileTexture(tile.Index));
289 if (levelChanged)
290 {
291 if (hasRenderableTexture)
292 {
293 bool canSwapNow = renderInfos.All(tile => _glArea.HasTileTexture(tile.Index));
294 if (canSwapNow)
295 {
296 _glArea.PreservePreviousFrameWhileIncomplete = false;
297 _glArea.ReleaseLevelTextures(_currentLevel);
298 _currentLevel = level;
299 lock (_pendingLevelSwitchLock)
300 {
301 _pendingLevelRenderInfos = null;
302 _pendingLevel = -1;
303 _pendingPreviousLevel = -1;
304 }
305 }
306 else
307 {
308 _glArea.PreservePreviousFrameWhileIncomplete = true;
309 lock (_pendingLevelSwitchLock)
310 {
311 _pendingLevelRenderInfos = new List<TileRenderInfo>(renderInfos);
312 _pendingLevel = level;
313 _pendingPreviousLevel = _currentLevel;
314 }
315 return;
316 }
317 }
318 else
319 {
320 _glArea.PreservePreviousFrameWhileIncomplete = true;
321 lock (_pendingLevelSwitchLock)
322 {
323 _pendingLevelRenderInfos = new List<TileRenderInfo>(renderInfos);
324 _pendingLevel = level;
325 _pendingPreviousLevel = _currentLevel;
326 }
327 return;
328 }
329 }
330
331 if (renderVersion != _renderStateVersion)
332 return;
333
334 await RunOnGtkThreadAsync(() =>
335 {
336 _glArea.SetTilesToRender(renderInfos);
337 _glArea.RequestRedraw();
338 });
339 }
340 finally
341 {
342 _renderSemaphore.Release();
343
344 if (LastRenderSkipped)
345 {
346 LastRenderSkipped = false;
347 Gtk.Application.Invoke((s, a) => App.viewer?.RequestDeferredRender());
348 }
349 }
350 }
void RequestRedraw()
Request a redraw of the GLArea.
bool HasTileTexture(TileIndex index)
Check if a tile texture is already in the GPU cache.
void ReleaseLevelTextures(int level)
Release all textures for a specific pyramid level.
void SetTilesToRender(IEnumerable< TileRenderInfo > tiles)
Prepare tiles for rendering. Call before RequestRedraw().

Property Documentation

◆ CachedTextureCount

int BioGTK.SlideRenderer.CachedTextureCount
get

Definition at line 791 of file SlideRenderer.cs.

◆ CurrentLevel

int BioGTK.SlideRenderer.CurrentLevel
get

Definition at line 790 of file SlideRenderer.cs.

◆ LastRenderSkipped

bool BioGTK.SlideRenderer.LastRenderSkipped
get

Definition at line 36 of file SlideRenderer.cs.

36{ get; private set; }

The documentation for this class was generated from the following file: