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 lodFreeze = context->variables->getOrAdd(lodFreezeName, false);
108 renderer->renderFrustum = lodFreeze;
109
110 bool atlasLayoutLog = context->variables->getOrAdd(atlasLayoutLogName, false);
111
112 if (!lodFreeze) {
113 const glm::vec3 p[8] = {
114 glm::vec3(-1.f, -1.f, -1.f),
115 glm::vec3( 1.f, -1.f, -1.f),
116 glm::vec3( 1.f, 1.f, -1.f),
117 glm::vec3(-1.f, 1.f, -1.f),
118 glm::vec3(-1.f, -1.f, 1.f),
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 };
123
124 const CameraData& mainCamData = context->cameraSystem->getMainCameraData();
125 glm::mat4 PVinv = glm::inverse(mainCamData.rawViewProjection);
126 for (size_t i = 0; i < 8; i++) {
127 renderer->frustumCorners[i] = glm::vec4(euclidean(PVinv * glm::vec4(p[i], 1.f)), 1.f);
128 }
129 }
130
131 for (auto& item : trackedMaterials) {
132 TrackedMaterial& trackedMaterial = item.second;
133 for (TexAtlasComponent*& slot : trackedMaterial.slots) {
134 slot = nullptr;
135 }
136 }
137
138 TransformSystem* transformSystem = context->transformSystem;
139
140 for (TexAtlasComponent& texAtlasComp : pool) {
141
142 TexAtlasData& texAtlasData = getData(&texAtlasComp);
143 texAtlasData.levels = 0; // Set as disabled, will be set to actual max level when all sanity checks have passed
144
145 if (texAtlasData.inUse) {
146 texAtlasData.inUse = false;
147 }
148 else if (texAtlasComp.materials.empty()) continue;
149
150 if (4 <= texAtlasComp.index) continue;
151 if ((texAtlasComp.domainMax.x <= texAtlasComp.domainMin.x) ||
152 (texAtlasComp.domainMax.y <= texAtlasComp.domainMin.y)) continue;
153
154 TexAtlas::Geometry& geo = texAtlasData.geometry;
155
156 texAtlasComp.maxLevel = std::min(28u, texAtlasComp.maxLevel); // Sanity check. Tile x & y get 29 bits in tile key.
157 texAtlasComp.minLevel = std::min(texAtlasComp.minLevel, texAtlasComp.maxLevel); // enforce minlevel <= maxlevel
158
159 geo.domain[0] = texAtlasComp.domainMin;
160 geo.domain[1] = texAtlasComp.domainMax;
161 geo.invDomainMapShift = -glm::vec2(geo.domain[0]);
162 geo.invDomainMapScale = glm::vec2(1.f) / glm::vec2(geo.domain[1] - geo.domain[0]);
163
164
165 texAtlasData.layout = getLayout(texAtlasData.geometry.domain[0],
166 texAtlasData.geometry.domain[1],
167 texAtlasComp.minLevel,
168 texAtlasComp.maxLevel);
169
170 texAtlasData.fetcher.datasetExtentMin = texAtlasComp.domainExtentsMin;
171 texAtlasData.fetcher.datasetExtentMax = texAtlasComp.domainExtentsMax;
172
173 texAtlasData.fetcher.update(context, currentFrame, currentTime, texAtlasComp.timeout, texAtlasComp.path);
174 texAtlasData.cache.update(currentFrame, currentTime, texAtlasComp.minRetryDelay, std::min(texAtlasComp.maxItemCount, maxItemCount));
175 texAtlasData.tree.update(context, texAtlasData.geometry, texAtlasData.cache, texAtlasData.fetcher, texAtlasData.layout, std::max(1.f / 8192.f, texAtlasComp.tolerance), texAtlasComp.restrictBetweenNearAndFar, lodFreeze);
176 texAtlasData.fetcher.processLoadQueue(context, texAtlasData.cache);
177
178 // Only update tree texture when it has changed
179 {
180 std::vector<uint16_t>& encoded = texAtlasData.tree.encoded;
181 size_t treeHashValue = Cogs::hash(encoded.data(), sizeof(encoded[0]) * encoded.size());
182 if (!texAtlasData.treeTex ||
183 (texAtlasData.treeTex->description.width != encoded.size()) ||
184 (texAtlasData.treeHashValue != treeHashValue))
185 {
186 texAtlasData.treeTex = context->textureManager->loadTexture(texAtlasData.tree.encoded.data(),
187 ResourceDimensions::Texture2D,
188 static_cast<int>(texAtlasData.tree.encoded.size()), 1, 1, 1,
189 TextureFormat::R16_UINT, 0, texAtlasData.treeTex,
191 texAtlasData.treeHashValue = treeHashValue;
192 }
193 }
194
195
196 if (texAtlasData.fetcher.tileWidth != 0 && texAtlasData.fetcher.tileHeight != 0) {
197
198 uint32_t currentSlotCount = texAtlasData.tilesTex ? texAtlasData.tilesTex->description.layers : 0;
199 uint32_t newSlotCount = currentSlotCount;
200
201 if (currentSlotCount < texAtlasData.cache.slots.size()) {
202 newSlotCount = std::min(texAtlasData.cache.maxItemCount, std::max(static_cast<uint32_t>(texAtlasData.cache.slots.size()), (currentSlotCount * 3 + 1) / 2));
203 if (atlasLayoutLog) {
204 LOG_DEBUG(logger, "Growing to %u tiles", newSlotCount);
205 }
206 }
207
208 if (texAtlasData.cache.slots.size() < currentSlotCount / 2) {
209 newSlotCount = std::max(static_cast<uint32_t>(1), currentSlotCount / 2);
210 if (atlasLayoutLog) {
211 LOG_DEBUG(logger, "Shrinking to %u tiles", newSlotCount);
212 }
213 }
214
215 if (currentSlotCount != newSlotCount) {
216
217 if (texAtlasData.tilesOldTex) {
218 // If tilesOldTex contains a texture, renderer hasn't run and blitted existing tiles out of the old
219 // texture populating the new texture in tilesTex, so we just overwrite the previous new texture and
220 // keep the old texture.
221 }
222 else {
223 texAtlasData.tilesOldTex = texAtlasData.tilesTex;
224 }
225 texAtlasData.tilesTex = context->textureManager->loadTexture(nullptr,
226 ResourceDimensions::Texture2DArray,
227 texAtlasData.fetcher.tileWidth, texAtlasData.fetcher.tileHeight, 1, newSlotCount,
228 TextureFormat::R8G8B8A8_UNORM_SRGB, 0, TextureHandle::NoHandle,
230 }
231 }
232
233 switch (texAtlasComp.projection) {
235 if (texAtlasComp.coefficients.size() != 4) continue;
236
237 for (size_t i = 0; i < 4; i++) {
238 texAtlasData.geometry.coefficients[i] = transformSystem->engineFromWorldCoords(glm::dvec3(texAtlasComp.coefficients[i], 0.f));
239 }
240 texAtlasData.geometry.elevationMin = transformSystem->engineFromWorldCoords(glm::dvec3(0.0, 0.0, double(texAtlasComp.elevation))).z;
241 texAtlasData.geometry.elevationMax = transformSystem->engineFromWorldCoords(glm::dvec3(0.0, 0.0, double(texAtlasComp.elevation))).z;
242 break;
243
244 default:
245 continue;
246 }
247
248 // Shader constants
249 uint32_t tileScale = (1 << texAtlasData.layout.level);
250 glm::vec2 gridFromUnitScale = glm::vec2(double(tileScale) * (texAtlasData.geometry.domain[1] - texAtlasData.geometry.domain[0]));
251 glm::vec2 gridFromUnitOffset = glm::vec2(double(tileScale) * texAtlasData.geometry.domain[0] - glm::dvec2(texAtlasData.layout.offset));
252 texAtlasData.floatCoefficients = glm::mat4(glm::vec4(texAtlasData.geometry.coefficients[0], texAtlasData.geometry.coefficients[1]),
253 glm::vec4(texAtlasData.geometry.coefficients[2], texAtlasData.geometry.coefficients[3]),
254 glm::vec4(gridFromUnitScale, gridFromUnitOffset),
255 glm::vec4(tileScale, 0.f, 0.f, 0.f));
256 texAtlasData.intCoefficients = glm::ivec4(int(tileScale - 1u), texAtlasData.layout.size.x, 0, 0);
257 texAtlasData.levels = static_cast<int>(texAtlasData.layout.maxLevel + 1 - texAtlasData.layout.level);
258
259 const std::string indexString = std::to_string(texAtlasComp.index);
260 for (MaterialHandle handle : texAtlasComp.materials) {
261 if (!handle) continue;
262
263 if (texAtlasComp.index < slotCount) {
264 TrackedMaterial& tracked = trackedMaterials[handle.resolve()];
265 tracked.material = handle;
266 tracked.lastSeen = currentFrame;
267 tracked.slots[texAtlasComp.index] = &texAtlasComp;
268 }
269
270#if 0
271 if (texAtlasComp.index == 0) {
272 LOG_DEBUG(logger, "level=%u off=[%d %d], size=[%d %d]",
273 texAtlasData.layout.level,
274 texAtlasData.layout.offset.x,
275 texAtlasData.layout.offset.y,
276 texAtlasData.layout.size.x,
277 texAtlasData.layout.size.y);
278 }
279#endif
280
281 }
282 }
283
284 // Update slots in tracked materials
285 for (auto& item : trackedMaterials) {
286
287 TrackedMaterial& trackedMaterial = item.second;
288 Material* mat = trackedMaterial.material.resolve();
289 assert(mat);
290 for (size_t slotIndex = 0; slotIndex < slotCount; slotIndex++) {
291
292 const MaterialKeys& keys = materialKeys[slotIndex];
293 if (TexAtlasComponent* texAtlasComp = trackedMaterial.slots[slotIndex]; texAtlasComp) {
294 TexAtlasData& texAtlasData = getData(texAtlasComp);
295
296 mat->setVariant(keys.level, texAtlasData.levels);
297
298 if (VariableKey transformKey = mat->getMat4Key(keys.floatCoeffs); transformKey != NoProperty) {
299 mat->setMat4Property(transformKey, texAtlasData.floatCoefficients);
300 }
301
302 if (VariableKey key = mat->getInt4Key(keys.intCoeffs); key != NoProperty) {
303 mat->setInt4Property(key, texAtlasData.intCoefficients);
304 }
305
306 if (VariableKey key = mat->getTextureKey(keys.tile); key != NoProperty) {
307 mat->setTextureProperty(key, texAtlasData.tilesTex);
310 }
311
312 if (VariableKey key = mat->getTextureKey(keys.tree); key != NoProperty) {
313 mat->setTextureProperty(key, texAtlasData.treeTex);
316 }
317 }
318 else {
319 // Disable slot
320 mat->setVariant(keys.level, 0);
321 }
322 }
323 }
324
325 // Stop tracking materials that we haven't seen this frame. Already have all slot levels set to zero.
326 std::erase_if(trackedMaterials, [currentFrame](auto& item) { return item.second.lastSeen != currentFrame; });
327}
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