380 {
381 if (_openSlideBase == null && _slideBase == null)
382 return;
383 if (viewportWidth <= 1 && viewportHeight <= 1)
384 return;
385
386
387
388
389 if (!_renderSemaphore.Wait(0))
390 {
391 LastRenderSkipped = true;
392 return;
393 }
394
395 LastRenderSkipped = false;
396
397 try
398 {
399 int renderVersion = _renderStateVersion;
400 int viewportVersion = _viewportVersion;
401 CancellationTokenSource tileFetchCts;
402 lock (_activeTileFetchLock)
403 {
404
405
406
407
408 if (_activeTileFetchCts == null || _activeTileFetchCts.IsCancellationRequested)
409 _activeTileFetchCts = new CancellationTokenSource();
410 tileFetchCts = _activeTileFetchCts;
411 }
412
413 var schema = _useOpenSlide ? _openSlideBase.Schema : _slideBase.Schema;
414
415 int level = TileUtil.GetLevel(schema.Resolutions, resolution);
416 var levelRes = schema.Resolutions[level];
417 double levelUnitsPerPixel = levelRes.UnitsPerPixel;
418
419 await EnsureGlobalDisplayRangeAsync(coordinate, renderVersion, level).ConfigureAwait(false);
420
421 bool levelChanged = level != _currentLevel;
422
423
424
425
426
427
428 double originX = pyramidalOrigin.X;
429 double originY = pyramidalOrigin.Y;
430 double minX = originX;
431 double width = viewportWidth * resolution;
432 double height = viewportHeight * resolution;
433
434
435
436 var viewportExtent = new Extent(minX, -originY - height, minX + width, -originY);
437
438
439
440 double imageMinX = schema.Extent.MinX;
441 double imageMaxX = schema.Extent.MaxX;
442 double imageMinY = schema.Extent.MinY;
443 double imageMaxY = schema.Extent.MaxY;
444 _glArea.ImageScreenX = (float)((imageMinX - pyramidalOrigin.X) / resolution);
445 _glArea.ImageScreenY = (float)((-imageMaxY - pyramidalOrigin.Y) / resolution);
446 _glArea.ImageScreenW = (float)((imageMaxX - imageMinX) / resolution);
447 _glArea.ImageScreenH = (float)((imageMaxY - imageMinY) / resolution);
448
449
450
451 double tileMarginX = levelRes.TileWidth * levelUnitsPerPixel;
452 double tileMarginY = levelRes.TileHeight * levelUnitsPerPixel;
453 var fetchExtent = new Extent(
454 viewportExtent.MinX - tileMarginX,
455 viewportExtent.MinY - tileMarginY,
456 viewportExtent.MaxX + tileMarginX,
457 viewportExtent.MaxY + tileMarginY);
458
459 List<TileInfo> tileInfos;
460 List<TileInfo> fallbackInfos;
461 if (_useOpenSlide)
462 {
463
464 var lookupExtent =
BioLib.ExtentEx.WorldToPixelInvertedY(fetchExtent, levelUnitsPerPixel);
465 tileInfos = schema.GetTileInfos(lookupExtent, level).ToList();
466 fallbackInfos = schema.GetTileInfos(fetchExtent, level).ToList();
467
468 if (fallbackInfos.Count > 0)
469 {
470 var seen = new HashSet<TileIndex>(tileInfos.Select(t => t.Index));
471 foreach (var info in fallbackInfos)
472 {
473 if (seen.Add(info.Index))
474 tileInfos.Add(info);
475 }
476 }
477 }
478 else
479 {
480
481
482
483 tileInfos = GetTileInfosIncludingBoundaries(schema, fetchExtent, level);
484 fallbackInfos = new List<TileInfo>();
485
486 if (tileInfos.Count == 0)
487 {
488 var pixelToWorldExtent =
BioLib.ExtentEx.PixelToWorldInvertedY(fetchExtent, levelUnitsPerPixel);
489 fallbackInfos = schema.GetTileInfos(pixelToWorldExtent, level).ToList();
490
491 if (fallbackInfos.Count == 0)
492 {
493 var worldToPixelExtent =
BioLib.ExtentEx.WorldToPixelInvertedY(fetchExtent, levelUnitsPerPixel);
494 fallbackInfos = schema.GetTileInfos(worldToPixelExtent, level).ToList();
495 }
496
497 if (fallbackInfos.Count > 0)
498 tileInfos = fallbackInfos;
499 }
500 }
501
502 var renderInfos = new List<TileRenderInfo>();
503 var pendingFetches = new List<(TileInfo Tile, Task<byte[]> FetchTask)>();
504 foreach (var tileInfo in tileInfos)
505 {
506 if (renderVersion != _renderStateVersion || viewportVersion != _viewportVersion)
507 return;
508
509 var fetchTileInfo = tileInfo;
510 var renderTileInfo = tileInfo;
511
512 var renderInfo = CalculateScreenPosition(
513 renderTileInfo,
514 pyramidalOrigin,
515 resolution,
516 level,
517 schema);
518 renderInfos.Add(renderInfo);
519
521 {
522 bool alreadyPending;
523 lock (_pendingTileFetchLock)
524 {
525 alreadyPending = !_pendingTileFetches.Add(fetchTileInfo.Index);
526 }
527
528 if (!alreadyPending)
529 pendingFetches.Add((fetchTileInfo, FetchTileAsync(fetchTileInfo, level, coordinate, tileFetchCts.Token)));
530 }
531 }
532
533
534
535
536 await RunOnGtkThreadAsync(() =>
538 .ConfigureAwait(false);
539
540 if (pendingFetches.Count > 0)
541 {
542 foreach (var pending in pendingFetches)
543 _ = ProcessPendingFetchAsync(pending.Tile, pending.FetchTask, tileFetchCts.Token, renderVersion, pyramidalOrigin, resolution, level, schema);
544 }
545
546 if (renderVersion != _renderStateVersion || viewportVersion != _viewportVersion)
547 return;
548
549 bool hasRenderableTexture = renderInfos.Any(tile => _glArea.
HasTileTexture(tile.Index));
550 if (levelChanged)
551 {
552 var fallbackRenderInfos = GetCachedLevelRenderInfos(
553 _currentLevel,
554 pyramidalOrigin,
555 resolution,
556 viewportWidth,
557 viewportHeight,
558 schema);
559
560 if (hasRenderableTexture)
561 {
562 bool canSwapNow = renderInfos.All(tile => _glArea.
HasTileTexture(tile.Index));
563 if (canSwapNow)
564 {
565 _glArea.PreservePreviousFrameWhileIncomplete = false;
567 _currentLevel = level;
568 lock (_pendingLevelSwitchLock)
569 {
570 _pendingLevelRenderInfos = null;
571 _pendingLevel = -1;
572 _pendingPreviousLevel = -1;
573 }
574 }
575 else
576 {
577 _glArea.PreservePreviousFrameWhileIncomplete = true;
578 lock (_pendingLevelSwitchLock)
579 {
580 _pendingLevelRenderInfos = new List<TileRenderInfo>(renderInfos);
581 _pendingLevel = level;
582 _pendingPreviousLevel = _currentLevel;
583 }
584 await ShowImmediateZoomFallbackAsync(fallbackRenderInfos).ConfigureAwait(false);
585 return;
586 }
587 }
588 else
589 {
590 _glArea.PreservePreviousFrameWhileIncomplete = true;
591 lock (_pendingLevelSwitchLock)
592 {
593 _pendingLevelRenderInfos = new List<TileRenderInfo>(renderInfos);
594 _pendingLevel = level;
595 _pendingPreviousLevel = _currentLevel;
596 }
597 await ShowImmediateZoomFallbackAsync(fallbackRenderInfos).ConfigureAwait(false);
598 return;
599 }
600 }
601
602 if (renderVersion != _renderStateVersion || viewportVersion != _viewportVersion)
603 return;
604
605 await RunOnGtkThreadAsync(() =>
606 {
609 });
610 }
611 catch (OperationCanceledException)
612 {
613
614
615 }
616 finally
617 {
618 _renderSemaphore.Release();
619
620 if (LastRenderSkipped)
621 {
622 LastRenderSkipped = false;
623 Gtk.Application.Invoke((s, a) => App.viewer?.RequestDeferredRender());
624 }
625 }
626 }
void RequestRedraw()
Request a redraw of the GLArea.
bool HasTileTexture(TileIndex index)
Check if a tile texture is already in the GPU cache.
void SetProtectedTileIndices(IEnumerable< TileIndex > indices)
Protects incoming visible tiles from FIFO eviction. The currently displayed set is included automatic...
void ReleaseLevelTextures(int level)
Release all textures for a specific pyramid level.
void SetTilesToRender(IEnumerable< TileRenderInfo > tiles)
Prepare tiles for rendering. Call before RequestRedraw().