Cogs.Core
Fetcher.cpp
1#include "Foundation/Logging/Logger.h"
2
3#include "zstd.h"
4
5#include "Context.h"
6#include "ExtensionRegistry.h"
7#include "Resources/DataFetcherManager.h"
8#include "Resources/TextureManager.h"
9
10#include "Image360System.h"
11
12namespace {
13 using namespace Cogs::Core;
14 using namespace Cogs::Core::Image360;
15
17
18 bool lookupAndCheckForStaleness(Context* context, Image360System*& system, Image360Component*& comp, Image360Data*& data, const uint32_t instanceId)
19 {
20 system = ExtensionRegistry::getExtensionSystem<Image360System>(context);
21 for (Image360Component& comp_ : system->pool) {
22 Image360Data& data_ = system->getData(&comp_);
23 if (data_.config.instanceId == instanceId) {
24 comp = &comp_;
25 data = &data_;
26 return true;
27 }
28 }
29 LOG_DEBUG(logger, "[instance=%u] Failed to lookup instance.", instanceId);
30 return false;
31 }
32
33}
34
35
36
37void Cogs::Core::Image360::Fetcher::issueChannelFetch(Context* context,
38 const Image360Component& im360Comp,
39 const Config& config,
40 const MapIx mapIx,
41 const SlotIx slotIx,
42 const uint8_t channelIx,
43 const uint8_t revision,
44 const size_t cacheLevel,
45 const size_t cacheLevelIndex)
46{
47 assert(channelIx < config.channels.size());
48 const Config::Channel& valueChannel = config.channels[channelIx];
49
50 std::vector<char> prefix(config.treeDepth + 1, 0);
51 size_t t = cacheLevelIndex;
52 for (size_t i = cacheLevel; i; --i) {
53 prefix[i] = '0' + (t % 4);
54 t = t / 4;
55 }
56 prefix[0] = '0' + char(t);
57
58 std::string path = valueChannel.prefix;
59 path.append(prefix.data());
60
61 bool loadAsData = false;
62 switch (valueChannel.dataType) {
64 path.append(".jpg");
65 break;
66
67 case Config::Channel::DataType::SRGB8_PNG: [[fallthrough]];
69 path.append(".png");
70 break;
72 path.append(".u16");
73 loadAsData = true;
74 break;
76 path.append(".u16.zst");
77 loadAsData = true;
78 break;
79 default:
80 assert(false && "Invalid enum");
81 break;
82 }
83
84 LOG_TRACE(logger, "Map=%zu slot=%d rev=%u channel=%u: Issue fetching %s", mapIx, slotIx, revision, channelIx, path.c_str());
85 for (const Fetcher::LoadItem& loadItem : itemsLoading) {
86 assert(!(loadItem.mapIx == mapIx && loadItem.slotIx == slotIx && loadItem.channelIx == channelIx && loadItem.revision == revision));
87 }
88
89 if (itemsLoading.empty()) {
90 // Loading queue transitions from empty to non-empty, notify
91 context->engine->invokeComponentNotifyCallback(im360Comp, (int)Image360Notification::FetchingBegin, nullptr, 0);
92 }
93
94 Fetcher::LoadItem& loadItem = itemsLoading.emplace_back();
95 loadItem.mapIx = mapIx;
96 loadItem.slotIx = slotIx;
97 loadItem.channelIx = channelIx;
98 loadItem.revision = revision;
99
100 if (loadAsData) {
101 auto handler = [context, instanceId = config.instanceId, w = config.baseSize, channelIx, mapIx, slotIx, revision, dataType = valueChannel.dataType](std::unique_ptr<Cogs::FileContents> contents1)
102 {
103 LOG_TRACE(logger, "Map=%zu slot=%d rev=%u channel=%u Got data", mapIx, slotIx, revision, channelIx);
104
105 bool decompress = false;
106 size_t elementSize = 0;
107 Cogs::TextureFormat format = Cogs::TextureFormat::Unknown;
108 switch (dataType) {
110 format = Cogs::DataFormat::R8G8B8_UNORM_SRGB;
111 elementSize = 3 * sizeof(uint8_t);
112 break;
114 format = Cogs::DataFormat::R8G8B8A8_UNORM_SRGB;
115 elementSize = 4 * sizeof(uint8_t);
116 break;
118 format = Cogs::TextureFormat::R16_UINT;
119 elementSize = sizeof(uint16_t);
120 break;
122 format = Cogs::TextureFormat::R16_UINT;
123 elementSize = sizeof(uint16_t);
124 decompress = true;
125 break;
126 default:
127 assert(false && "Invalid enum");
128 break;
129 }
130
131 // Decompress data or try to move the file contents into a memory buffer
132 size_t expectedSize = elementSize * w * w;
133 std::unique_ptr<Cogs::Memory::MemoryBuffer> contents2 = std::make_unique<Cogs::Memory::MemoryBuffer>();
134 if (contents1) {
135 if (decompress) {
136 contents2->resize(expectedSize);
137 size_t outSize = ZSTD_decompress(contents2->data(), contents2->size(), contents1->ptr, contents1->size);
138 if (outSize != expectedSize) {
139 LOG_ERROR(logger, "Error decompressing zstd data: %zxu", outSize);
140 contents2->clear();
141 }
142 }
143 else {
144 Cogs::Memory::MemoryBuffer data_ = contents1->take();
145 contents2->swap(data_);
146 }
147 }
148
149 // Try to upload the texture
151 if (contents2->size() == expectedSize) {
152 TextureLoadInfo* loadInfo = context->textureManager->createLoadInfo();
153 loadInfo->target = Cogs::ResourceDimensions::Texture2D;
154 loadInfo->width = w;
155 loadInfo->height = w;
156 loadInfo->format = format;
158 loadInfo->mipMaps = false;
159 loadInfo->resourceData.assign((const char*)contents2->data(), (const char*)contents2->data() + contents2->size());
160 texture = context->textureManager->loadTexture(loadInfo);
161 }
162 else if(!contents2->empty()) {
163 LOG_ERROR_ONCE(logger, "Unexpected size of data (got=%zu, expected=%zu)", contents2->size(), expectedSize);
164 }
165
166 // Update system in main thread
167 auto task = [context, instanceId, channelIx, mapIx, slotIx, revision, texture, contents3 = contents2.release()]
168 {
169 std::unique_ptr<Memory::MemoryBuffer> contents4(contents3);
170
171 Image360System* system = nullptr;
172 Image360Component* comp = nullptr;
173 Image360Data* data = nullptr;
174 if (!lookupAndCheckForStaleness(context, system, comp, data, instanceId)) {
175 LOG_WARNING(logger, "Map=%zu slot=%d rev=%u channel=%u: Ignoring stale tile data file", mapIx, slotIx, revision, channelIx);
176 return;
177 }
178
179 for (Fetcher::LoadItem& loadItem : data->fetcher.itemsLoading) {
180 if ((loadItem.mapIx == mapIx) &&
181 (loadItem.slotIx == slotIx) &&
182 (loadItem.channelIx == channelIx) &&
183 (loadItem.revision == revision))
184 {
185 if (texture) {
186 loadItem.texture = texture;
187 loadItem.buffer = std::move(contents4);
188 }
189 else {
190 loadItem.failed = true;
191 }
192 context->engine->setDirty();
193 return;
194 }
195 }
196
197 LOG_WARNING(logger, "Map=%zu slot=%d rev=%u channel=%u: Failed to find matching loadItem", mapIx, slotIx, revision, channelIx);
198 };
199
200 context->engine->runTaskInMainThread(std::move(task));
201 };
202 loadItem.fetchId = DataFetcherManager::fetchAsync(context, path, handler, 0, 0, true, Cogs::FileContentsHints::None);
203 }
204 else {
205 loadItem.texture = context->textureManager->loadTexture(path, NoResourceId, TextureLoadFlags::None);
206 }
207
208}
209
210
211bool Cogs::Core::Image360::Fetcher::canLoadAnyItems(const Config& config) const
212{
213 return itemsLoading.size() < config.maxConcurrent;
214}
215
216
217void Cogs::Core::Image360::Fetcher::processLoadItems(Context* context,
218 const Image360Component& im360Comp,
219 Cache& cache,
220 const Config& config,
221 const uint32_t currentFrame)
222{
223 itemsLoadingNext.clear();
224 for (LoadItem& loadItem : itemsLoading) {
225
226 SlotIx slotIx = cache.map[loadItem.mapIx];
227 if (0 <= slotIx && loadItem.slotIx == slotIx && cache.items[slotIx].revision == loadItem.revision) {
228
229 Cache::Item& cacheItem = cache.items[slotIx];
230
231 bool isValue = loadItem.channelIx == config.valueChannel;
232 bool isDepth = config.hasDepth && loadItem.channelIx == config.depthChannel;
233 const uint32_t age = currentFrame - cacheItem.lastTouched;
234
235 if (!isValue && !isDepth) {
236 LOG_ERROR(logger, "Map=%zu Slot=%d channel=%u rev=%u channel is neither value nor depth", loadItem.mapIx, slotIx, loadItem.channelIx, loadItem.revision);
237 continue;
238 }
239
240 if (isValue) { assert(cacheItem.value.state == Cache::Item::State::Loading); }
241 if (isDepth) { assert(cacheItem.depth.state == Cache::Item::State::Loading); }
242
243 if (loadItem.failed || (loadItem.texture && loadItem.texture->hasFailedLoad())) {
244 if (isValue) { cacheItem.value.state = Cache::Item::State::Failed; }
245 if (isDepth) { cacheItem.depth.state = Cache::Item::State::Failed; }
246 LOG_TRACE(logger, "Map=%zu Slot=%d rev=%u failed load item", loadItem.mapIx, slotIx, loadItem.revision);
247 continue;
248 }
249
250 if (loadItem.texture && loadItem.texture->isResident()) {
251
252 if (loadItem.texture->description.width != loadItem.texture->description.height) {
253 if (isValue) { cacheItem.value.state = Cache::Item::State::Failed; }
254 if (isDepth) { cacheItem.depth.state = Cache::Item::State::Failed; }
255 LOG_ERROR(logger, "Map=%zu Slot=%d rev=%u texture is not square w=%u h=%u",
256 loadItem.mapIx, slotIx, loadItem.revision, loadItem.texture->description.width, loadItem.texture->description.height);
257 }
258 else if (loadItem.texture->description.width != config.baseSize) {
259 if (isValue) { cacheItem.value.state = Cache::Item::State::Failed; }
260 if (isDepth) { cacheItem.depth.state = Cache::Item::State::Failed; }
261 LOG_ERROR(logger, "Map=%zu Slot=%d rev=%u texture size (=%u) does not match base size (=%u)",
262 loadItem.mapIx, slotIx, loadItem.revision, loadItem.texture->description.width, config.baseSize);
263 }
264 else {
265 if (isValue) { cacheItem.value.state = Cache::Item::State::Loaded; }
266 if (isDepth) { cacheItem.depth.state = Cache::Item::State::Loaded; }
267 LOG_TRACE(logger, "Map=%zu Slot=%d rev=%u loaded load item", loadItem.mapIx, slotIx, loadItem.revision);
268 itemsUploading.push_back(std::move(loadItem));
269 }
270
271 context->engine->triggerUpdate();
272 }
273 else if (3 < age) {
274 LOG_DEBUG(logger, "Map=%zu Slot=%d rev=%u: Cancelling loadItem isValue=%u isDepth=%u tex=%u fetch=%u",
275 loadItem.mapIx, slotIx, loadItem.revision,
276 isValue ? 1 : 0, isDepth ? 1 : 0,
277 loadItem.texture ? 1 : 0,
278 loadItem.fetchId != DataFetcherManager::NoFetchId ? 1 : 0);
279
280 // Hasn't been requested for two frames, just cancel it.
281 if (isValue) { cacheItem.value.state = Cache::Item::State::None; }
282 if (isDepth) { cacheItem.depth.state = Cache::Item::State::None; }
283 if (loadItem.texture) {
284 context->textureManager->cancelTextureLoad(loadItem.texture);
285 loadItem.texture = TextureHandle::NoHandle;
286 }
287 if (loadItem.fetchId != DataFetcherManager::NoFetchId) {
288 DataFetcherManager::cancelAsyncFetch(context, loadItem.fetchId);
289 loadItem.fetchId = DataFetcherManager::NoFetchId;
290 }
291
292 }
293 else {
294 itemsLoadingNext.push_back(std::move(loadItem));
295 }
296 }
297 else {
298 LOG_TRACE(logger, "Map=%zu Slot=%d rev=%u: Stale load item", loadItem.mapIx, slotIx, loadItem.revision);
299 }
300 }
301 if (!itemsLoading.empty() && itemsLoadingNext.empty()) {
302 // Loading queue transitions from non-empty to empty, notify
303 context->engine->invokeComponentNotifyCallback(im360Comp, (int)Image360Notification::FetchingEnd, nullptr, 0);
304 }
305
306 itemsLoading.swap(itemsLoadingNext);
307}
ComponentPool< ComponentType > pool
Pool of components managed by the system.
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
std::unique_ptr< class Engine > engine
Engine instance.
Definition: Context.h:222
Log implementation class.
Definition: LogManager.h:139
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
@ DoNotCache
Do not cache this resource for later retrieval.
@ FetchingBegin
Loading queue transitions from empty to non-empty, that is, instance needs data.
@ FetchingEnd
Loading queue transitions from non-empty to empty, that is, instance has all data it currently needs.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180
@ SRGB8_JPEG
8-bit colors in SRGB color space stored as JPEG (.jpg).
@ U16_ZST
16-bit unsigned values stored as little endian raw values that are subsequently zstd compressed.
@ SRGBA8_PNG
8-bit colors in SRGB color space and a alpha channel (zero is transparent) stored as PNG (....
@ SRGB8_PNG
8-bit colors in SRGB color space stored as PNG (.png).
@ U16
16-bit unsigned values stored as little endian raw values.
uint32_t instanceId
Component instance id.
Definition: Image360.h:44
uint32_t baseSize
Base image size of a cached tile. From json.
Definition: Image360.h:46
uint8_t valueChannel
Data channel to be used as value data. From component.
Definition: Image360.h:49
uint32_t treeDepth
Depth of tile hierarchy. From json.
Definition: Image360.h:45
uint8_t depthChannel
Data channel that contains depth data. From json.
Definition: Image360.h:50
std::vector< Channel > channels
Data channels to use. From json.
Definition: Image360.h:51
bool hasDepth
If data has depth and component wants depth.
Definition: Image360.h:58
static const ResourceHandle_t NoHandle
Handle representing a default (or none if default not present) resource.
std::vector< uint8_t > resourceData
Resource load data.
ResourceLoadFlags loadFlags
Desired loading flags. Used to specify how the resource will be loaded.