88 {
89 _pendingPyramidalOrigin = pyramidalOrigin;
90 _pendingViewportWidth = viewportWidth;
91 _pendingViewportHeight = viewportHeight;
92 _pendingResolution = resolution;
93 _pendingCoordinate = coordinate;
94 _hasPendingView = true;
95
96 if (_openSlideBase == null && _slideBase == null)
97 return;
98 if (viewportWidth <= 1 && viewportHeight <= 1)
99 return;
101 return;
102 if (!_renderSemaphore.Wait(0))
103 {
104 return;
105 }
106
107 try
108 {
109 var schema = _useOpenSlide ? _openSlideBase.Schema : _slideBase.Schema;
110 int level = TileUtil.GetLevel(schema.Resolutions, resolution);
111 var levelRes = schema.Resolutions[level];
112 double levelUnitsPerPixel = levelRes.UnitsPerPixel;
113
114 if (level != _currentLevel)
115 {
117 _currentLevel = level;
118 }
119
120 double minX = pyramidalOrigin.X;
121 double width = viewportWidth * resolution;
122 double height = viewportHeight * resolution;
123 var viewportExtent = new Extent(minX, -pyramidalOrigin.Y - height, minX + width, -pyramidalOrigin.Y);
124
125 double tileMarginX = levelRes.TileWidth * levelUnitsPerPixel;
126 double tileMarginY = levelRes.TileHeight * levelUnitsPerPixel;
127 var fetchExtent = new Extent(
128 viewportExtent.MinX - tileMarginX,
129 viewportExtent.MinY - tileMarginY,
130 viewportExtent.MaxX + tileMarginX,
131 viewportExtent.MaxY + tileMarginY);
132
133 var lookupExtent = BioLib.ExtentEx.WorldToPixelInvertedY(fetchExtent, levelUnitsPerPixel);
134 var tileInfos = schema.GetTileInfos(lookupExtent, level).ToList();
135 var fallbackInfos = schema.GetTileInfos(fetchExtent, level).ToList();
136 if (fallbackInfos.Count > 0)
137 {
138 var seen = new HashSet<TileIndex>(tileInfos.Select(t => t.Index));
139 foreach (var info in fallbackInfos)
140 {
141 if (seen.Add(info.Index))
142 tileInfos.Add(info);
143 }
144 }
145
146 var renderInfos = new List<TileRenderInfo>();
147 var pendingFetches = new List<(TileInfo Tile, Task<byte[]> FetchTask)>();
148 foreach (var tileInfo in tileInfos)
149 {
151 {
152 renderInfos.Add(CalculateScreenPosition(
153 tileInfo,
154 pyramidalOrigin,
155 resolution,
156 level,
157 schema));
158 continue;
159 }
160
161 bool alreadyPending;
162 lock (_pendingTileFetchLock)
163 {
164 alreadyPending = !_pendingTileFetches.Add(tileInfo.Index);
165 }
166
167 if (!alreadyPending)
168 {
169 pendingFetches.Add((tileInfo, FetchTileAsync(tileInfo, level, coordinate, RemoteTileFetchTimeout)));
170 renderInfos.Add(CalculateScreenPosition(
171 tileInfo,
172 pyramidalOrigin,
173 resolution,
174 level,
175 schema));
176 }
177 }
178
179 foreach (var pending in pendingFetches)
180 {
181 _ = ProcessPendingFetchAsync(
182 pending.Tile,
183 pending.FetchTask,
184 pyramidalOrigin,
185 resolution,
186 level,
187 schema);
188 }
189
192 }
193 finally
194 {
195 _renderSemaphore.Release();
196 }
197 }
void ReleaseLevelTextures(int level)
Release all textures for a specific pyramid level.
bool IsGLReady
True after the GL context has been fully initialized and is ready for texture uploads and rendering.
void SetTilesToRender(IEnumerable< TileRenderInfo > tiles)
Prepare tiles for rendering. Call before RequestRedraw().
void RequestRedraw()
Request a redraw of the GLArea.
bool HasTileTexture(TileIndex index)
Check if a tile texture is already in the GPU cache.