135 {
136 if (_openSlideBase == null && _slideBase == null)
137 return;
138 if (viewportWidth <= 1 && viewportHeight <= 1)
139 return;
140
141
142
143
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
168
169
170
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
180
181
182 var viewportExtent = new Extent(minX, -originY - height, minX + width, -originY);
183
184
185
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
196
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
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
227
228
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
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;
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 {
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().