Cogs.Core
BridgeRasterSource.cpp
1#include "BridgeRasterSource.h"
2#include "RasterConversion.h"
3#include "RasterSourceSubscription.h"
4
5#include "../ClipmapTerrainTypes.h"
6
7#include "Rendering/ITextures.h"
8#include "Rendering/IContext.h"
9
10#include "Foundation/Logging/Logger.h"
11
12#include <memory>
13
14namespace
15{
16 Cogs::Logging::Log logger = Cogs::Logging::getLogger("BridgeRasterSource");
17}
18
19namespace Cogs
20{
22 {
23 RequestClosure(size_t generation, size_t tileGeneration) : generation(generation), tileGeneration(tileGeneration) {}
24
25 uint64_t providerId = 0;
26 BridgeRasterSource * source = nullptr;
27 size_t generation = 0;
28 size_t id = 0;
29 size_t rasterLevel = 0;
30 size_t tileGeneration = 0;
31 };
32
33 void loadTileData(BridgeRasterSource * source,
34 RasterTile * tile,
35 const size_t requestGeneration,
36 const size_t rasterLevel,
37 const size_t /*id*/,
38 const uint8_t * data,
39 int width,
40 int height,
41 TextureFormat format,
42 bool isHeight = false,
43 float minZ = 0,
44 float maxZ = 0)
45 {
46#ifdef _WIN32
47 source->device->initializeThread();
48#endif
49
50 const size_t cacheLimit = source->cacheSize;
51
52 while (source->textureBufferSize > cacheLimit) {
53 WriteLock sourceLock(*source);
54
55 source->evictLruTile();
56 }
57
58 size_t textureSize;
59 TextureHandle textureHandle;
60
61 const bool supportQuery = (source->getFlags() & RasterSourceFlags::Queryable) != 0;
62 bool compressCache = (source->getFlags() & RasterSourceFlags::CompressCache) != 0;
63 const bool isCompressible = Cogs::isCompressible(format);
64
65 if (compressCache && !isCompressible) {
66 if (!source->suppressCompressionError) {
67 source->suppressCompressionError = true;
68 LOG_ERROR(logger, "Compression only supported for R8G8B8A8_UNORM or R8G8B8A8_UNORM_SRGB format.");
69 }
70
71 compressCache = false;
72 } else if (compressCache && (width % 4 != 0 || height % 4 != 0)) {
73 if (!source->suppressDimensionError) {
74 LOG_ERROR(logger, "Tile dimensions must be a multiple of 4 to support block compression.");
75 source->suppressDimensionError = true;
76 }
77
78 compressCache = false;
79 }
80
81 if (compressCache) {
82 textureSize = Cogs::getTextureSize(width, height, format) / 4;
83
84 std::vector<uint8_t> compressed(textureSize);
85 auto compressedFormat = compressTileData(compressed, data, format, width, height);
86
87 textureHandle = source->loadTexture(compressed.data(), width, height, compressedFormat, textureSize);
88 } else {
89 textureSize = Cogs::getTextureSize(width, height, format);
90 textureHandle = source->loadTexture(data, width, height, format, textureSize);
91 }
92
93 source->device->waitForCommandSync();
94
95 TileLoadResponse response{};
96 response.rasterLevel = static_cast<int>(rasterLevel);
97 response.tile = tile;
98
99 std::vector<uint8_t> buffer;
100
101 if (supportQuery) {
102 source->allocateTileStorage(textureSize, buffer);
103 buffer.assign(data, data + textureSize);
104 }
105
106 {
107 WriteLock lock(*source);
108
109 source->loadTileData(tile, requestGeneration, buffer, textureHandle, format, textureSize);
110
111 if (isHeight) {
112 source->isHeight = true;
113
114 tile->data->minZ = minZ;
115 tile->data->maxZ = maxZ;
116
117 source->minZ = std::min(source->minZ, minZ);
118 source->maxZ = std::max(source->maxZ, maxZ);
119 }
120 }
121
122 std::lock_guard<std::mutex> lock(source->subscriberMutex);
123
124 for (auto handler : source->subscribers) {
125 handler->postResponse(response);
126 }
127 }
128
129 void loadCallbackInternal(void * userData, int /*level*/, int /*x*/, int /*y*/, TileData * dataPtr)
130 {
131 assert(userData && "User data must point to valid closure.");
132 assert(dataPtr && "Tile data must point to valid structure.");
133
134 std::unique_ptr<RequestClosure> closure(static_cast<RequestClosure *>(userData));
135 BridgeRasterSource * source = closure->source;
136
137 // Enter processing state for this request. Ensures the tile data etc. is not deleted while the lock
138 // is in scope.
139 std::lock_guard<RasterSource::Processing> processingLock(source->processing);
140 --source->closures;
141
142 if (closure->generation < source->generation) {
143 // Drop responses to requests made before source was invalidated.
144 return;
145 }
146
147 RasterTile * tile = nullptr;
148 {
149 ReadLock lock(*source);
150
151 tile = source->getTile(closure->id);
152 }
153
154 if (!tile || (tile->data && (closure->tileGeneration < tile->data->generation))) {
155 // Drop responses to requests that have been preempted by later requests.
156 return;
157 }
158
159 auto tileData = reinterpret_cast<const TileData *>(dataPtr);
160 auto data = reinterpret_cast<const uint8_t *>(tileData->imageData);
161 auto format = tileData->format;
162
163 if (data) {
164 if (tileData->shouldConvert() || tileData->shouldFlip()) {
165 std::vector<uint8_t> convertedData;
166 auto targetFormat = convertBitmapData(data, tileData, convertedData);
167
168 if (targetFormat == TextureFormat::Unknown) {
169 LOG_ERROR(logger, "Texture format %hu not convertible.", tileData->format);
170 return;
171 }
172
173 loadTileData(closure->source, tile, closure->tileGeneration, closure->rasterLevel, closure->id, convertedData.data(), tileData->width, tileData->height, targetFormat, tileData->isHeight(), tileData->minZ, tileData->maxZ);
174 } else {
175 loadTileData(closure->source, tile, closure->tileGeneration, closure->rasterLevel, closure->id, data, tileData->width, tileData->height, format, tileData->isHeight(), tileData->minZ, tileData->maxZ);
176 }
177 } else {
178 WriteLock lock(*source);
179
180 source->loadEmptyTileData(tile);
181 }
182 }
183
184#define CHECKED(c, p) p
185
186 void loadCallback(void * userData, int level, int x, int y, TileData * dataPtr)
187 {
188 RequestClosure * closure = reinterpret_cast<RequestClosure *>(userData);
189 BridgeRasterSource * source = closure->source;
190
191 assert(closure && "Invalid closure in tile load callback.");
192 assert(source && "Invalid raster source in tile load closure.");
193
194 // loadCallbackInternal will take ownership of the closure and destroy it.
195 CHECKED(source->context, loadCallbackInternal(userData, level, x, y, dataPtr));
196 }
197}
198
199Cogs::BridgeRasterSource::BridgeRasterSource(CogsContext * context, RasterSourceParameters * parameters) :
200 context(context),
201 cacheSize(parameters->cacheSize),
202 providerId(parameters->id),
203 flags(parameters->flags),
204 requestCallback(parameters->tileRequestCallback),
205 allocationCallback(parameters->allocationCallback),
206 deallocationCallback(parameters->deallocationCallback)
207{
208 const bool supportQuery = (flags & RasterSourceFlags::Queryable) != 0;
209 const bool compressCache = (flags & RasterSourceFlags::CompressCache) != 0;
210
211 if (supportQuery && compressCache) {
212 LOG_ERROR(logger, "Raster source cannot support queries on compressed data.");
213 }
214
215 format = static_cast<TextureFormat>(parameters->format);
216 name = std::string(parameters->name);
217
218 GeodeticExtent geodeticExtent(parameters->minX, parameters->minY, parameters->maxX, parameters->maxY);
219
220 tileLongitudePosts = parameters->tileWidth;
221 tileLatitudePosts = parameters->tileHeight;
222
223 const size_t numLevels = parameters->numLevels;
224
225 double deltaLongitude = parameters->deltaX;
226 double deltaLatitude = parameters->deltaY;
227
228 const double extentX = geodeticExtent.getEast() - geodeticExtent.getWest();
229 const double extentY = geodeticExtent.getNorth() - geodeticExtent.getSouth();
230
231 double tilePostDeltaLongitude = deltaLongitude / (double)tileLongitudePosts;
232 double tilePostDeltaLatitude = deltaLatitude / (double)tileLatitudePosts;
233
234 for (size_t i = 0; i < numLevels; ++i) {
235 const int longitudePosts = static_cast<int>(std::ceil(extentX / deltaLongitude)) * tileLongitudePosts;
236 const int latitudePosts = static_cast<int>(std::ceil(extentY / deltaLatitude)) * tileLatitudePosts;
237
238 assert(longitudePosts > 0 && "Number of longitude posts must be a positive integer.");
239 assert(latitudePosts > 0 && "Number of latitude posts must be a positive integer.");
240
241 levels.emplace_back(this,
242 static_cast<int>(i),
243 geodeticExtent,
244 parameters->noData,
245 longitudePosts,
246 latitudePosts,
247 tileLongitudePosts,
248 tileLatitudePosts,
249 tilePostDeltaLongitude,
250 tilePostDeltaLatitude);
251
252 deltaLongitude /= 2.0;
253 deltaLatitude /= 2.0;
254
255 tilePostDeltaLongitude /= 2.0;
256 tilePostDeltaLatitude /= 2.0;
257 }
258}
259
260Cogs::BridgeRasterSource::~BridgeRasterSource()
261{
262 assert(!processing.getCount() && "Request processing should be completed before destruction.");
263}
264
265void Cogs::BridgeRasterSource::requestTile(const TileLoadRequest & request)
266{
267 const RasterTileIdentifier & identifier = request.tile->identifier;
268
269 if (request.tile->isResident()) {
270 return;
271 }
272
273 if (request.tile->isOOB()) {
274 loadInvalidTile(request.tile);
275
276 return;
277 }
278
279 auto closure = std::make_unique<RequestClosure>(generation, request.tile->generation);
280 closure->providerId = this->providerId;
281 closure->source = this;
282 closure->rasterLevel = request.tile->identifier.level;
283 closure->id = identifier.getHashCode();
284
285 ++closures;
286
287 if (!requestCallback(closure.get(), loadCallback, identifier.level, identifier.x, identifier.y)) {
288 // The request will not be processed, in most cases due to an identical request already being processed.
289 --closures;
290 } else {
291 closure.release();
292 }
293}
294
295void Cogs::BridgeRasterSource::allocateTileStorage(const size_t textureSize, std::vector<uint8_t> & buffer)
296{
297 if (allocationCallback) {
298 allocationCallback(nullptr, static_cast<unsigned int>(textureSize));
299 }
300
301 buffer.resize(textureSize);
302}
303
304void Cogs::BridgeRasterSource::deallocateTileStorage(std::vector<uint8_t> & buffer)
305{
306 if (buffer.size()) {
307 if (deallocationCallback) {
308 deallocationCallback(nullptr, static_cast<unsigned int>(buffer.size()));
309 }
310
311 buffer.clear();
312 buffer.shrink_to_fit();
313 }
314}
315
316namespace
317{
318 static std::vector<uint8_t> emptyBuffer;
319}
320
321void Cogs::BridgeRasterSource::loadInvalidTile(RasterTile * tile)
322{
323 if (invalidTileHandle == TextureHandle::InvalidHandle) {
324 uint8_t bytes[] = {
325 110, 110, 110, 0,
326 127, 127, 127, 0,
327 127, 127, 127, 0,
328 110, 110, 110, 0,
329 };
330
331 invalidTileHandle = device->getTextures()->loadTexture(bytes, 2, 2, TextureFormat::R8G8B8A8_UNORM);
332 }
333
334 loadTileData(tile, tile->generation, emptyBuffer, invalidTileHandle, TextureFormat::R8G8B8A8_UNORM, 0);
335}
336
337void Cogs::BridgeRasterSource::loadEmptyTileData(RasterTile * tile)
338{
339 loadTileData(tile, tile->generation, emptyBuffer, TextureHandle::NoHandle, TextureFormat::R8G8B8A8_UNORM, 0);
340}
Log implementation class.
Definition: LogManager.h:140
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:181
Contains all Cogs related functionality.
Definition: FieldSetter.h:23
static const Handle_t NoHandle
Represents a handle to nothing.
Definition: Common.h:78
static const Handle_t InvalidHandle
Represents an invalid handle.
Definition: Common.h:81