Cogs.Core
TexAtlasSystem.cpp
1#include "Context.h"
2#include "Foundation/Logging/Logger.h"
3
4#include "Rendering/ICapabilities.h"
5
6#include "Utilities/Math.h"
7#include "Systems/Core/CameraSystem.h"
8#include "Systems/Core/TransformSystem.h"
9#include "Resources/Material.h"
10#include "Resources/MaterialInstance.h"
11#include "Resources/TextureManager.h"
12#include "Services/Time.h"
13#include "TexAtlasSystem.h"
14#include "TexAtlasRenderer.h"
15
16using namespace Cogs::Core::TexAtlas;
17
18namespace {
19 using namespace Cogs::Core;
20
22
23 const Cogs::StringView lodFreezeName = "texAtlas.lodFreeze";
24 const Cogs::StringView atlasLayoutLogName = "texAtlas.atlasLayout.log";
25
26 const size_t slotCount = 4;
27
28 Layout getLayout(const glm::dvec2& domainMin, const glm::dvec2& domainMax, const uint32_t minLevel, const uint32_t maxLevel)
29 {
30 // Base level s.t. a tile roughly matches the domain, snapped to valid range
31 double domainSize = std::max(domainMax.x - domainMin.x,
32 domainMax.y - domainMax.y);
33 uint32_t level = static_cast<uint32_t>(std::max(0.0, std::round(-std::log2(domainSize))));
34 if (level < minLevel) level = minLevel;
35 if (maxLevel < level) level = maxLevel;
36
37 const uint32_t tileScale = 1<<level;
38
39
40 // Figure out the grid size at the level. We restrict it to the maximum number of
41 // grids that is defined, and rather do wrap-around in the shader.
42 glm::ivec2 maxGridSizeAtLevel = glm::ivec2(1 << level);
43 glm::ivec2 minGridCell = glm::ivec2(glm::floor(static_cast<float>(tileScale) * glm::vec2(domainMin)));
44 glm::ivec2 maxGridCell = glm::ivec2(glm::floor(static_cast<float>(tileScale) * glm::vec2(domainMax))) + glm::ivec2(1);
45 glm::uvec2 size = glm::max(glm::ivec2(0), glm::min(maxGridSizeAtLevel, maxGridCell - minGridCell));
46
47 return Layout{
48 .offset = minGridCell,
49 .size = size,
50 .tileScale = tileScale,
51 .level = level,
52 .maxLevel = maxLevel
53 };
54 }
55}
56
58{
60
61 materialKeys.resize(slotCount);
62 for (size_t slot = 0; slot < slotCount; slot++) {
63 const std::string indexString = std::to_string(slot);
64 materialKeys[slot].level = "TexAtlasLevels" + indexString;
65 materialKeys[slot].floatCoeffs = "texAtlasFloatCoeffs" + indexString;
66 materialKeys[slot].intCoeffs = "texAtlasIntCoeffs" + indexString;
67 materialKeys[slot].tile = "texAtlasTiles" + indexString;
68 materialKeys[slot].tree = "texAtlasTree" + indexString;
69 }
70}
71
73{
74 if (pool.size() == 0) {
75 LOG_DEBUG(logger, "Creating first component, creating renderer.");
76 assert(renderer == nullptr);
77 renderer = new TexAtlasRenderer(this);
78 context->renderer->registerExtension(renderer);
79 }
80 return base::createComponent();
81}
82
84{
85 base::destroyComponent(component);
86 if (pool.size() == 0) {
87 LOG_DEBUG(logger, "Destroying last component, destroying renderer.");
88 trackedMaterials.clear();
89 assert(renderer);
90 context->renderer->unregisterExtension(renderer);
91 delete renderer;
92 renderer = nullptr;
93 }
94}
95
96
98{
99 if (pool.size() == 0) return;
100
101
102 const uint32_t maxItemCount = context->renderer->getDevice()->getCapabilities()->getDeviceCapabilities().MaxTextureArrayLayers;
103 const uint32_t currentFrame = context->time->getFrame();
104 const uint32_t currentTime = static_cast<uint32_t>(std::floor(context->time->getAnimationTime())); // Time in seconds in 32-bits should be sufficent for our uses.
105
106
107 bool userLodFreeze = context->variables->getOrAdd(lodFreezeName, false);
108 renderer->renderFrustum = userLodFreeze;
109
110 // Also freeze the LOD tree (and thus all cache slot allocation, resize, etc.) until the
111 // renderer's atlasBlit effect (used only on ES30/WebGPU) is ready, so we never grow the
112 // atlas cache - and thus never trigger an atlas resize/blit - before it can be serviced.
113 bool lodFreeze = userLodFreeze || !renderer->isReady();
114
115 bool atlasLayoutLog = context->variables->getOrAdd(atlasLayoutLogName, false);
116
117 if (!lodFreeze) {
118 const glm::vec3 p[8] = {
119 glm::vec3(-1.f, -1.f, -1.f),
120 glm::vec3( 1.f, -1.f, -1.f),
121 glm::vec3( 1.f, 1.f, -1.f),
122 glm::vec3(-1.f, 1.f, -1.f),
123 glm::vec3(-1.f, -1.f, 1.f),
124 glm::vec3( 1.f, -1.f, 1.f),
125 glm::vec3( 1.f, 1.f, 1.f),
126 glm::vec3(-1.f, 1.f, 1.f)
127 };
128
129 const CameraData& mainCamData = context->cameraSystem->getMainCameraData();
130 glm::mat4 PVinv = glm::inverse(mainCamData.rawViewProjection);
131 for (size_t i = 0; i < 8; i++) {
132 renderer->frustumCorners[i] = glm::vec4(euclidean(PVinv * glm::vec4(p[i], 1.f)), 1.f);
133 }
134 }
135
136 for (auto& item : trackedMaterials) {
137 TrackedMaterial& trackedMaterial = item.second;
138 for (TexAtlasComponent*& slot : trackedMaterial.slots) {
139 slot = nullptr;
140 }
141 }
142
143 TransformSystem* transformSystem = context->transformSystem;
144
145 for (TexAtlasComponent& texAtlasComp : pool) {
146
147 TexAtlasData& texAtlasData = getData(&texAtlasComp);
148 texAtlasData.levels = 0; // Set as disabled, will be set to actual max level when all sanity checks have passed
149
150 if (texAtlasData.inUse) {
151 texAtlasData.inUse = false;
152 }
153 else if (texAtlasComp.materials.empty()) continue;
154
155 if (4 <= texAtlasComp.index) continue;
156 if ((texAtlasComp.domainMax.x <= texAtlasComp.domainMin.x) ||
157 (texAtlasComp.domainMax.y <= texAtlasComp.domainMin.y)) continue;
158
159 TexAtlas::Geometry& geo = texAtlasData.geometry;
160
161 texAtlasComp.maxLevel = std::min(28u, texAtlasComp.maxLevel); // Sanity check. Tile x & y get 29 bits in tile key.
162 texAtlasComp.minLevel = std::min(texAtlasComp.minLevel, texAtlasComp.maxLevel); // enforce minlevel <= maxlevel
163
164 geo.domain[0] = texAtlasComp.domainMin;
165 geo.domain[1] = texAtlasComp.domainMax;
166 geo.invDomainMapShift = -glm::vec2(geo.domain[0]);
167 geo.invDomainMapScale = glm::vec2(1.f) / glm::vec2(geo.domain[1] - geo.domain[0]);
168
169
170 texAtlasData.layout = getLayout(texAtlasData.geometry.domain[0],
171 texAtlasData.geometry.domain[1],
172 texAtlasComp.minLevel,
173 texAtlasComp.maxLevel);
174
175 texAtlasData.fetcher.datasetExtentMin = texAtlasComp.domainExtentsMin;
176 texAtlasData.fetcher.datasetExtentMax = texAtlasComp.domainExtentsMax;
177
178 texAtlasData.fetcher.update(context, currentFrame, currentTime, texAtlasComp.timeout, texAtlasComp.path);
179 texAtlasData.cache.update(currentFrame, currentTime, texAtlasComp.minRetryDelay, std::min(texAtlasComp.maxItemCount, maxItemCount));
180 texAtlasData.tree.update(context, texAtlasData.geometry, texAtlasData.cache, texAtlasData.fetcher, texAtlasData.layout, std::max(1.f / 8192.f, texAtlasComp.tolerance), texAtlasComp.restrictBetweenNearAndFar, lodFreeze);
181 texAtlasData.fetcher.processLoadQueue(context, texAtlasData.cache);
182
183 // Only update tree texture when it has changed
184 {
185 std::vector<uint16_t>& encoded = texAtlasData.tree.encoded;
186 size_t treeHashValue = Cogs::hash(encoded.data(), sizeof(encoded[0]) * encoded.size());
187 if (!texAtlasData.treeTex ||
188 (texAtlasData.treeTex->description.width != encoded.size()) ||
189 (texAtlasData.treeHashValue != treeHashValue))
190 {
191 texAtlasData.treeTex = context->textureManager->loadTexture(texAtlasData.tree.encoded.data(),
192 ResourceDimensions::Texture2D,
193 static_cast<int>(texAtlasData.tree.encoded.size()), 1, 1, 1,
194 TextureFormat::R16_UINT, 0, texAtlasData.treeTex,
196 texAtlasData.treeHashValue = treeHashValue;
197 }
198 }
199
200
201 if (texAtlasData.fetcher.tileWidth != 0 && texAtlasData.fetcher.tileHeight != 0) {
202
203 uint32_t currentSlotCount = texAtlasData.tilesTex ? texAtlasData.tilesTex->description.layers : 0;
204 uint32_t newSlotCount = currentSlotCount;
205
206 if (currentSlotCount < texAtlasData.cache.slots.size()) {
207 newSlotCount = std::min(texAtlasData.cache.maxItemCount, std::max(static_cast<uint32_t>(texAtlasData.cache.slots.size()), (currentSlotCount * 3 + 1) / 2));
208 if (atlasLayoutLog) {
209 LOG_DEBUG(logger, "Growing to %u tiles", newSlotCount);
210 }
211 }
212
213 if (texAtlasData.cache.slots.size() < currentSlotCount / 2) {
214 newSlotCount = std::max(static_cast<uint32_t>(1), currentSlotCount / 2);
215 if (atlasLayoutLog) {
216 LOG_DEBUG(logger, "Shrinking to %u tiles", newSlotCount);
217 }
218 }
219
220 if (currentSlotCount != newSlotCount) {
221
222 if (texAtlasData.tilesOldTex) {
223 // If tilesOldTex contains a texture, renderer hasn't run and blitted existing tiles out of the old
224 // texture populating the new texture in tilesTex, so we just overwrite the previous new texture and
225 // keep the old texture.
226 }
227 else {
228 texAtlasData.tilesOldTex = texAtlasData.tilesTex;
229 }
230 texAtlasData.tilesTex = context->textureManager->loadTexture(nullptr,
231 ResourceDimensions::Texture2DArray,
232 texAtlasData.fetcher.tileWidth, texAtlasData.fetcher.tileHeight, 1, newSlotCount,
233 TextureFormat::R8G8B8A8_UNORM_SRGB, 0, TextureHandle::NoHandle,
235 }
236 }
237
238 switch (texAtlasComp.projection) {
240 if (texAtlasComp.coefficients.size() != 4) continue;
241
242 for (size_t i = 0; i < 4; i++) {
243 texAtlasData.geometry.coefficients[i] = transformSystem->engineFromWorldCoords(glm::dvec3(texAtlasComp.coefficients[i], 0.f));
244 }
245 texAtlasData.geometry.elevationMin = transformSystem->engineFromWorldCoords(glm::dvec3(0.0, 0.0, double(texAtlasComp.elevation))).z;
246 texAtlasData.geometry.elevationMax = transformSystem->engineFromWorldCoords(glm::dvec3(0.0, 0.0, double(texAtlasComp.elevation))).z;
247 break;
248
249 default:
250 continue;
251 }
252
253 // Shader constants
254 uint32_t tileScale = (1 << texAtlasData.layout.level);
255 glm::vec2 gridFromUnitScale = glm::vec2(double(tileScale) * (texAtlasData.geometry.domain[1] - texAtlasData.geometry.domain[0]));
256 glm::vec2 gridFromUnitOffset = glm::vec2(double(tileScale) * texAtlasData.geometry.domain[0] - glm::dvec2(texAtlasData.layout.offset));
257 texAtlasData.floatCoefficients = glm::mat4(glm::vec4(texAtlasData.geometry.coefficients[0], texAtlasData.geometry.coefficients[1]),
258 glm::vec4(texAtlasData.geometry.coefficients[2], texAtlasData.geometry.coefficients[3]),
259 glm::vec4(gridFromUnitScale, gridFromUnitOffset),
260 glm::vec4(tileScale, 0.f, 0.f, 0.f));
261 texAtlasData.intCoefficients = glm::ivec4(int(tileScale - 1u), texAtlasData.layout.size.x, 0, 0);
262 texAtlasData.levels = static_cast<int>(texAtlasData.layout.maxLevel + 1 - texAtlasData.layout.level);
263
264 const std::string indexString = std::to_string(texAtlasComp.index);
265 for (MaterialHandle handle : texAtlasComp.materials) {
266 if (!handle) continue;
267
268 if (texAtlasComp.index < slotCount) {
269 TrackedMaterial& tracked = trackedMaterials[handle.resolve()];
270 tracked.material = handle;
271 tracked.lastSeen = currentFrame;
272 tracked.slots[texAtlasComp.index] = &texAtlasComp;
273 }
274
275#if 0
276 if (texAtlasComp.index == 0) {
277 LOG_DEBUG(logger, "level=%u off=[%d %d], size=[%d %d]",
278 texAtlasData.layout.level,
279 texAtlasData.layout.offset.x,
280 texAtlasData.layout.offset.y,
281 texAtlasData.layout.size.x,
282 texAtlasData.layout.size.y);
283 }
284#endif
285
286 }
287 }
288
289 // Update slots in tracked materials
290 for (auto& item : trackedMaterials) {
291
292 TrackedMaterial& trackedMaterial = item.second;
293 Material* mat = trackedMaterial.material.resolve();
294 assert(mat);
295 for (size_t slotIndex = 0; slotIndex < slotCount; slotIndex++) {
296
297 const MaterialKeys& keys = materialKeys[slotIndex];
298 if (TexAtlasComponent* texAtlasComp = trackedMaterial.slots[slotIndex]; texAtlasComp) {
299 TexAtlasData& texAtlasData = getData(texAtlasComp);
300
301 mat->setVariant(keys.level, texAtlasData.levels);
302
303 if (VariableKey transformKey = mat->getMat4Key(keys.floatCoeffs); transformKey != NoProperty) {
304 mat->setMat4Property(transformKey, texAtlasData.floatCoefficients);
305 }
306
307 if (VariableKey key = mat->getInt4Key(keys.intCoeffs); key != NoProperty) {
308 mat->setInt4Property(key, texAtlasData.intCoefficients);
309 }
310
311 if (VariableKey key = mat->getTextureKey(keys.tile); key != NoProperty) {
312 mat->setTextureProperty(key, texAtlasData.tilesTex);
315 }
316
317 if (VariableKey key = mat->getTextureKey(keys.tree); key != NoProperty) {
318 mat->setTextureProperty(key, texAtlasData.treeTex);
321 }
322 }
323 else {
324 // Disable slot
325 mat->setVariant(keys.level, 0);
326 }
327 }
328 }
329
330 // Stop tracking materials that we haven't seen this frame. Already have all slot levels set to zero.
331 std::erase_if(trackedMaterials, [currentFrame](auto& item) { return item.second.lastSeen != currentFrame; });
332}
Context * context
Pointer to the Context instance the system lives in.
virtual void initialize(Context *context)
Initialize the system.
void update()
Updates the system state to that of the current frame.
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
class IRenderer * renderer
Renderer.
Definition: Context.h:228
std::unique_ptr< class Variables > variables
Variables service instance.
Definition: Context.h:180
std::unique_ptr< class Time > time
Time service instance.
Definition: Context.h:198
virtual IGraphicsDevice * getDevice()=0
Get the graphics device used by the renderer.
The transform system handles TransformComponent instances, calculating local and global transform dat...
virtual ICapabilities * getCapabilities()=0
Get a pointer to the capability management interface used to query the graphics device capability fla...
Log implementation class.
Definition: LogManager.h:140
Provides a weakly referenced view over the contents of a string.
Definition: StringView.h:50
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
@ RenderTarget
Set the usage flag of the texture to RenderTarget.
@ NoMipMaps
Do not generate mipmaps.
@ ForceSynchronous
Force loading the resource synchronously.
uint16_t VariableKey
Used to lookup material properties.
Definition: Resources.h:46
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:181
constexpr size_t hash() noexcept
Simple getter function that returns the initial value for fnv1a hashing.
Definition: HashFunctions.h:62
COGSFOUNDATION_API Time currentTime()
High resolution clock time (NTP / UTC time). Returns an implementation defined absolute timestamp,...
Handle to a Component instance.
Definition: Component.h:67
Contains data describing a Camera instance and its derived data structured such as matrix data and vi...
Definition: CameraSystem.h:67
Material resources define the how of geometry rendering (the what is defined by Mesh and Texture reso...
Definition: Material.h:82
void setMat4Property(const VariableKey key, glm::mat4 value)
Set the mat4 property with the given key to value.
Definition: Material.h:229
void setTextureFilterMode(const VariableKey key, SamplerState::FilterMode filterMode)
Set filtermode used for the texture property.
Definition: Material.cpp:164
void setInt4Property(const VariableKey key, glm::ivec4 value)
Set the ivec4 property with the given key to value.
Definition: Material.h:218
void setTextureProperty(const VariableKey key, TextureHandle value)
Set the texture property with the given key to the texture resource held by value.
Definition: Material.cpp:116
void setTextureAddressMode(const VariableKey key, SamplerState::AddressMode mode)
Set the address mode used for the texture property with the given key to mode.
Definition: Material.cpp:142
static const ResourceHandle_t NoHandle
Handle representing a default (or none if default not present) resource.
ResourceType * resolve() const
Resolve the handle, returning a pointer to the actual resource.
void destroyComponent(ComponentHandle component) override
void initialize(Context *context) override
Initialize the system.
ComponentHandle createComponent() override
uint32_t maxItemCount
Number of tiles in cache.
void update(Context *context, uint32_t currentFrame, uint32_t currentTime, uint32_t timeout, std::string_view urlTemplate)
void processLoadQueue(Context *context, Cache &cache)
glm::vec2 coefficients[4]
Coefficients wrt engine origin.
uint32_t level
Base level.
uint32_t maxLevel
Maximum level.
glm::uvec2 size
Size of grid.
glm::ivec2 offset
Indices of the grid cell with smallest indices.
uint32_t MaxTextureArrayLayers
Using D3D11_REQ_TEXTURE2D_ARRAY_AXIS_DIMENSION as default.
Definition: ICapabilities.h:83
virtual const GraphicsDeviceCapabilities & getDeviceCapabilities() const
Gets the device capabilities in a structure.
@ Clamp
Texture coordinates are clamped to the [0, 1] range.
Definition: SamplerState.h:17
@ MinMagMipPoint
Point sampling for both minification and magnification.
Definition: SamplerState.h:33
@ MinMagMipLinear
Linear sampling for both minification and magnification.
Definition: SamplerState.h:35