chrono_batch_render/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
303 / 333
91.0%
Branches:
244 / 316
77.2%
Functions:
28 / 29
96.6%

texture/cache.cpp
Line Branch Exec Source
1 // SPDX-License-Identifier: BSD-3-Clause
2 #include "chrono_batch_render/texture/cache.h"
3
4 #include <algorithm>
5 #include <bit>
6 #include <cstring>
7 #include <exception>
8 #include <fstream>
9 #include <span>
10 #include <sstream>
11
12 #include "chrono_batch_render/device/kernels/shared.h"
13 #include "chrono_batch_render/device/queue.h"
14 #include "chrono_batch_render/io/ies.h"
15 #include "chrono_batch_render/io/image.h"
16 #include "chrono_batch_render/io/kernels/encoding.h"
17 #include "chrono_batch_render/texture/kernels.h"
18 #include "chrono_batch_render/texture/texels.h"
19 #include "chrono_batch_render/util/error.h"
20 #include "chrono_batch_render/util/log.h"
21 #include "chrono_batch_render/util/profiler.h"
22
23 namespace chrono::batch_render {
24 namespace {
25
26 96 std::array<float, 4> ReadTexel(const TexelHeader& header, const std::byte* texels, size_t index) {
27 96 std::array<float, 4> values{};
28
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 96 times.
96 const uint32_t channels = std::min(header.channels, uint32_t(values.size()));
29
2/2
✓ Branch 0 taken 128 times.
✓ Branch 1 taken 96 times.
224 for (uint32_t c = 0; c < channels; ++c) {
30 128 const size_t at = index * header.channels + c;
31
2/2
✓ Branch 0 taken 64 times.
✓ Branch 1 taken 64 times.
128 if (header.floats != 0) {
32 64 std::memcpy(&values[c], texels + at * sizeof(float), sizeof(float));
33 } else {
34 64 values[c] = float(uint8_t(texels[at]));
35 }
36 }
37
2/2
✓ Branch 0 taken 64 times.
✓ Branch 1 taken 32 times.
96 const float opaque = header.floats != 0 ? 1.0f : 255.0f;
38
2/4
✓ Branch 0 taken 80 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
96 switch (header.channels) {
39 80 case 1:
40 80 return {values[0], values[0], values[0], opaque};
41 ✗ case 2:
42 ✗ return {values[0], values[0], values[0], values[1]};
43 16 case 3:
44 16 return {values[0], values[1], values[2], opaque};
45 ✗ default:
46 ✗ return values;
47 }
48 }
49
50 3 bool ReadTexels(const TexelHeader& header, Span<const std::byte> bytes, Image& floats,
51 std::vector<uint32_t>& words) {
52 3 const size_t count = size_t(header.width) * header.height;
53
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 2 times.
3 const size_t size = header.floats != 0 ? sizeof(float) : 1;
54
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (header.channels == 0 || header.channels > 4 ||
55
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 bytes.size() != sizeof(TexelHeader) + count * header.channels * size) {
56 return false;
57 }
58
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 time.
3 const std::byte* texels = bytes.data() + sizeof(TexelHeader);
59
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 time.
3 if (header.floats != 0) {
60 2 floats = Image(header.width, header.height);
61
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 2 times.
34 for (size_t i = 0; i < count; ++i) {
62 32 const std::array<float, 4> v = ReadTexel(header, texels, i);
63 32 floats.pixels[i] = {v[0], v[1], v[2], v[3]};
64 }
65 return true;
66 }
67 1 words.resize(count);
68
2/2
✓ Branch 1 taken 64 times.
✓ Branch 2 taken 1 time.
65 for (size_t i = 0; i < count; ++i) {
69 64 const std::array<float, 4> v = ReadTexel(header, texels, i);
70 64 words[i] =
71 64 uint32_t(v[0]) | uint32_t(v[1]) << 8 | uint32_t(v[2]) << 16 | uint32_t(v[3]) << 24;
72 }
73 return true;
74 }
75
76 constexpr uint32_t kDescsPerChunk = 256;
77 constexpr uint32_t kCheckerSide = 512;
78
79 6 Image Checkerboard() {
80 6 Image image(kCheckerSide, kCheckerSide);
81 6 const uint32_t half = kCheckerSide / 2;
82
2/2
✓ Branch 0 taken 3072 times.
✓ Branch 1 taken 6 times.
3078 for (uint32_t y = 0; y < kCheckerSide; ++y) {
83
2/2
✓ Branch 0 taken 1572864 times.
✓ Branch 1 taken 3072 times.
1575936 for (uint32_t x = 0; x < kCheckerSide; ++x) {
84
2/2
✓ Branch 0 taken 786432 times.
✓ Branch 1 taken 786432 times.
1572864 const float value = (x / half + y / half + 1) % 2 == 0 ? 0.0f : 1.0f;
85 1572864 image.Pixel(x, y) = {value, value, value, 1};
86 }
87 }
88 6 return image;
89 }
90
91 10 void Encode(const Image& level, TexelFormat format, TexelColorSpace space,
92 std::vector<uint32_t>& words) {
93
2/2
✓ Branch 0 taken 1572928 times.
✓ Branch 1 taken 10 times.
1572938 for (const Rgba& texel : level.pixels) {
94
2/2
✓ Branch 0 taken 64 times.
✓ Branch 1 taken 1572864 times.
1572928 if (format == TexelFormat::Rgba16f) {
95 64 words.push_back(FloatToHalf(texel.r) | FloatToHalf(texel.g) << 16);
96 64 words.push_back(FloatToHalf(texel.b) | FloatToHalf(texel.a) << 16);
97 64 continue;
98 }
99 6291456 const auto color = [&](float value) {
100
2/2
✓ Branch 0 taken 1572864 times.
✓ Branch 1 taken 3145728 times.
4718592 return EncodeUnorm8(space == TexelColorSpace::Srgb ? EncodeSrgb(value) : value);
101 1572864 };
102 3145728 words.push_back(color(texel.r) | color(texel.g) << 8 | color(texel.b) << 16 |
103 1572864 EncodeUnorm8(texel.a) << 24);
104 }
105 10 }
106
107 8 std::optional<Image> ReadPixels(const std::string& path, bool srgb, const FileBytes& bytes) {
108
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
8 const ImageEncoding encoding = srgb ? ImageEncoding::Srgb : ImageEncoding::Linear;
109 8 try {
110
5/8
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
✓ Branch 3 taken 6 times.
✓ Branch 6 taken 2 times.
✗ Branch 9 not taken.
✗ Branch 12 not taken.
✗ Branch 14 not taken.
✓ Branch 15 taken 8 times.
18 return path == kCheckerboardTexture ? Checkerboard()
111
2/3
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 2 times.
2 : bytes ? LoadImageBytes(*bytes, path, encoding)
112 8 : LoadImageFile(path, encoding);
113
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ } catch (const std::exception& error) {
114
0/1
✗ Branch 2 not taken.
✗ Log(LogLevel::Warning, "texture {} can't be read: {}", path, error.what());
115 ✗ return std::nullopt;
116 ✗ }
117 }
118
119 ✗ std::optional<Image> ReadProfile(const std::string& path, const FileBytes& bytes) {
120 ✗ try {
121
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ std::string text;
122
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (bytes) {
123
0/1
✗ Branch 1 not taken.
✗ text.assign(reinterpret_cast<const char*>(bytes->data()), bytes->size());
124 } else {
125
0/1
✗ Branch 1 not taken.
✗ std::ifstream file(path, std::ios::binary);
126
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (!file) {
127
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ throw Error("it can't be opened");
128 }
129
0/1
✗ Branch 1 not taken.
✗ std::ostringstream contents;
130
0/1
✗ Branch 1 not taken.
✗ contents << file.rdbuf();
131
0/1
✗ Branch 1 not taken.
✗ text = contents.str();
132 ✗ }
133
0/1
✗ Branch 1 not taken.
✗ return ReadIes(text);
134
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ } catch (const std::exception& error) {
135
0/1
✗ Branch 2 not taken.
✗ Log(LogLevel::Warning, "IES profile {} can't be read: {}", path, error.what());
136 ✗ return std::nullopt;
137 ✗ }
138 }
139
140 33 uint32_t LevelCount(uint32_t width, uint32_t height) {
141 33 uint32_t levels = 1;
142
1/2
✓ Branch 0 taken 155 times.
✗ Branch 1 not taken.
155 while (levels < kTextureMaxLevels &&
143
6/8
✗ Branch 0 not taken.
✓ Branch 1 taken 155 times.
✓ Branch 2 taken 33 times.
✓ Branch 3 taken 122 times.
✓ Branch 4 taken 8 times.
✓ Branch 5 taken 25 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 33 times.
163 (TextureLevelSide(width, levels - 1) > 1 || TextureLevelSide(height, levels - 1) > 1)) {
144 122 ++levels;
145 }
146 33 return levels;
147 }
148
149 }
150
151 366 TextureCache::TextureCache(device::Device& device, FileLookup files, unsigned threads)
152
2/6
✓ Branch 2 taken 366 times.
✓ Branch 6 taken 366 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
366 : m_device(device), m_files(std::move(files)), m_pool(threads) {}
153
154
4/4
✓ Branch 1 taken 23 times.
✓ Branch 2 taken 343 times.
✓ Branch 5 taken 361 times.
✓ Branch 6 taken 5 times.
750 TextureCache::~TextureCache() = default;
155
156 8681 bool TextureCache::Pending() const {
157 8681 const std::lock_guard lock(m_mutex);
158
3/4
✓ Branch 0 taken 8611 times.
✓ Branch 1 taken 70 times.
✓ Branch 2 taken 8611 times.
✗ Branch 3 not taken.
17292 return !m_done.empty() || m_inFlight > 0;
159 8681 }
160
161 698 void TextureCache::Finish() {
162 698 std::unique_lock lock(m_mutex);
163
3/4
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 698 times.
✓ Branch 2 taken 698 times.
✗ Branch 3 not taken.
713 m_settled.wait(lock, [&] { return m_inFlight == 0; });
164 698 }
165
166 504 uint32_t TextureCache::Loading() const {
167 504 uint32_t loading = 0;
168
4/4
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 36 times.
✓ Branch 2 taken 68 times.
✓ Branch 3 taken 504 times.
572 for (const auto& [key, entry] : m_images) {
169
4/4
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 36 times.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 26 times.
110 loading += entry.ready || entry.failed ? 0 : 1;
170 }
171 504 return loading;
172 }
173
174 7 std::optional<std::pair<uint32_t, uint32_t>> TextureCache::Size(const TextureKey& key) const {
175 7 const auto found = m_images.find(key);
176
3/4
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 6 times.
7 if (found == m_images.end() || !found->second.ready) {
177 1 return std::nullopt;
178 }
179 6 return std::pair{found->second.width, found->second.height};
180 }
181
182 24 uint32_t TextureCache::AllocateBindings(uint32_t count) {
183 24 std::vector<uint32_t>& free = m_freeBindings[count];
184
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 19 times.
24 if (!free.empty()) {
185 5 const uint32_t start = free.back();
186 5 free.pop_back();
187 5 return start;
188 }
189 19 const uint32_t start = uint32_t(m_bindings.size());
190 19 m_bindings.resize(m_bindings.size() + count, 0);
191 19 return start;
192 }
193
194 6 void TextureCache::FreeBindings(uint32_t start, uint32_t count) {
195 6 m_freeBindings[count].push_back(start);
196 6 }
197
198 34 void TextureCache::SetBinding(uint32_t index, uint64_t handle) {
199 34 m_bindings[index] = handle;
200 34 m_bindingsChanged = true;
201 34 }
202
203 863 uint64_t TextureCache::UploadBindings() {
204
2/2
✓ Branch 0 taken 389 times.
✓ Branch 1 taken 474 times.
863 if (m_bindingsChanged) {
205 389 m_bindingsChanged = false;
206
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 361 times.
389 const uint64_t bytes = m_bindings.size() * sizeof(uint64_t);
207
3/4
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 361 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 28 times.
389 if (!m_bindingBuffer || m_bindingBuffer->Bytes() < bytes) {
208
1/1
✓ Branch 1 taken 361 times.
722 m_bindingBuffer = m_device.CreateBuffer(
209
1/2
✓ Branch 0 taken 361 times.
✗ Branch 1 not taken.
361 {.bytes = std::bit_ceil(std::max<uint64_t>(bytes, 64 * sizeof(uint64_t))),
210
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 361 times.
722 .label = "texture bindings"});
211 }
212 389 m_device.GetQueue().Write(m_bindingBuffer->Slice(0, bytes), m_bindings.data());
213 }
214 863 return m_bindingBuffer->Address();
215 1083 }
216
217 6 uint32_t TextureCache::AllocateDesc() {
218
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 5 times.
6 if (!m_freeDescs.empty()) {
219 1 const uint32_t desc = m_freeDescs.back();
220 1 m_freeDescs.pop_back();
221 1 return desc;
222 }
223
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3 times.
5 if (m_descCount == m_descChunks.size() * kDescsPerChunk) {
224
2/3
✓ Branch 1 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
4 m_descChunks.push_back(m_device.CreateBuffer(
225 2 {.bytes = kDescsPerChunk * sizeof(TextureDesc), .label = "texture descs"}));
226 }
227 5 return m_descCount++;
228 4 }
229
230 6 uint64_t TextureCache::DescAddress(uint32_t desc) const {
231 6 return m_descChunks[desc / kDescsPerChunk]->Address() +
232 6 uint64_t(desc % kDescsPerChunk) * sizeof(TextureDesc);
233 }
234
235 50 void TextureCache::Acquire(const TextureKey& key) {
236 50 auto [found, added] = m_images.try_emplace(key);
237
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 40 times.
50 ++found->second.users;
238
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 40 times.
50 if (!added) {
239 10 return;
240 }
241
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 34 times.
40 const FileBytes bytes = key.first == kCheckerboardTexture ? nullptr : m_files(key.first);
242 40 {
243
1/1
✓ Branch 1 taken 40 times.
40 const std::lock_guard lock(m_mutex);
244 40 ++m_inFlight;
245 40 }
246
7/9
✓ Branch 1 taken 40 times.
✓ Branch 3 taken 14 times.
✓ Branch 4 taken 26 times.
✓ Branch 6 taken 40 times.
✓ Branch 9 taken 40 times.
✓ Branch 12 taken 14 times.
✓ Branch 13 taken 26 times.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
94 m_pool.Add(this, [this, key, bytes] {
247 40 Decoded decoded;
248 40 const std::optional<TexelHeader> raw =
249
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 26 times.
40 bytes ? ReadTexelHeader(*bytes) : std::optional<TexelHeader>();
250
4/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 37 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 2 times.
40 decoded.format = raw ? (raw->floats != 0 ? TexelFormat::Rgba16f : TexelFormat::Rgba8)
251
6/6
✓ Branch 1 taken 37 times.
✓ Branch 4 taken 37 times.
✓ Branch 6 taken 6 times.
✓ Branch 7 taken 2 times.
✓ Branch 8 taken 37 times.
✓ Branch 9 taken 3 times.
120 : IsPngOrJpeg(key.first) || key.first == kCheckerboardTexture
252
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 29 times.
37 ? TexelFormat::Rgba8
253 : TexelFormat::Rgba16f;
254
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 29 times.
31 decoded.colorSpace = key.second && decoded.format == TexelFormat::Rgba8
255
2/2
✓ Branch 0 taken 31 times.
✓ Branch 1 taken 9 times.
40 ? TexelColorSpace::Srgb
256 : TexelColorSpace::Linear;
257
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 37 times.
40 if (raw) {
258 3 Image floats;
259
2/3
✓ Branch 1 taken 3 times.
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
3 if (ReadTexels(*raw, *bytes, floats, decoded.words)) {
260
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 time.
3 decoded.width = raw->width;
261 3 decoded.height = raw->height;
262
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 time.
3 if (raw->floats != 0) {
263
1/1
✓ Branch 1 taken 2 times.
2 Encode(floats, decoded.format, decoded.colorSpace, decoded.words);
264 }
265 } else {
266
0/1
✗ Branch 1 not taken.
✗ Log(LogLevel::Warning, "texture {} has malformed texels", key.first);
267 ✗ decoded.failed = true;
268 }
269
4/4
✓ Branch 1 taken 37 times.
✓ Branch 4 taken 37 times.
✓ Branch 7 taken 29 times.
✓ Branch 8 taken 8 times.
77 } else if (IsPngOrJpeg(key.first)) {
270 29 try {
271 29 Rgba8Image pixels =
272
5/5
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 20 times.
✓ Branch 3 taken 8 times.
✓ Branch 6 taken 20 times.
✓ Branch 9 taken 14 times.
49 bytes ? LoadRgba8Bytes(*bytes, key.first) : LoadRgba8File(key.first);
273 22 decoded.width = pixels.width;
274 22 decoded.height = pixels.height;
275 22 decoded.words = std::move(pixels.texels);
276
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
29 } catch (const std::exception& error) {
277
1/1
✓ Branch 2 taken 7 times.
7 Log(LogLevel::Warning, "texture {} can't be read: {}", key.first, error.what());
278 7 decoded.failed = true;
279 7 }
280
3/4
✓ Branch 1 taken 8 times.
✓ Branch 4 taken 8 times.
✓ Branch 6 taken 8 times.
✗ Branch 7 not taken.
16 } else if (std::optional<Image> pixels = IsIes(key.first)
281
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 ? ReadProfile(key.first, bytes)
282
2/4
✗ Branch 1 not taken.
✓ Branch 4 taken 8 times.
✓ Branch 6 taken 8 times.
✗ Branch 7 not taken.
16 : ReadPixels(key.first, key.second, bytes)) {
283
1/1
✓ Branch 1 taken 8 times.
8 decoded.width = pixels->width;
284 8 decoded.height = pixels->height;
285
1/1
✓ Branch 1 taken 8 times.
8 Encode(*pixels, decoded.format, decoded.colorSpace, decoded.words);
286 } else {
287 ✗ decoded.failed = true;
288 8 }
289
2/2
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 7 times.
40 decoded.levels = decoded.failed ? 0 : LevelCount(decoded.width, decoded.height);
290
1/1
✓ Branch 1 taken 40 times.
40 const std::lock_guard lock(m_mutex);
291
1/1
✓ Branch 1 taken 40 times.
40 m_done.emplace_back(key, std::move(decoded));
292 40 --m_inFlight;
293 40 m_settled.notify_all();
294 40 });
295 40 }
296
297 40 void TextureCache::Upload(const TextureKey& key, Entry& entry, Decoded decoded, Mips& mips) {
298
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 33 times.
40 if (decoded.failed) {
299 7 entry.failed = true;
300 7 ++m_failed;
301 7 return;
302 }
303 33 entry.format = decoded.format;
304 33 entry.colorSpace = decoded.colorSpace;
305 33 entry.width = decoded.width;
306 33 entry.height = decoded.height;
307 33 entry.levels = decoded.levels;
308 33 uint32_t bytes = 0;
309
2/2
✓ Branch 0 taken 155 times.
✓ Branch 1 taken 33 times.
188 for (uint32_t level = 0; level < entry.levels; ++level) {
310 155 entry.offsets[level] = bytes;
311 155 bytes += uint32_t(
312 155 RoundUp(TextureLevelBytes(entry.format, entry.width, entry.height, level), 16));
313 }
314
1/1
✓ Branch 1 taken 33 times.
33 const Ref<device::Buffer> texels = m_device.CreateBuffer({.bytes = bytes, .label = key.first});
315
2/2
✓ Branch 1 taken 33 times.
✓ Branch 4 taken 33 times.
33 m_device.GetQueue().Write(texels->Slice(0, decoded.words.size() * sizeof(uint32_t)),
316
1/1
✓ Branch 1 taken 33 times.
33 decoded.words.data());
317
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 3 times.
33 if (mips.levels.size() < entry.levels) {
318
1/1
✓ Branch 1 taken 30 times.
30 mips.levels.resize(entry.levels);
319 }
320
2/2
✓ Branch 0 taken 122 times.
✓ Branch 1 taken 33 times.
155 for (uint32_t level = 1; level < entry.levels; ++level) {
321
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 122 times.
244 mips.levels[level].push_back({.texels = {texels->Address()},
322
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 122 times.
122 .source = entry.offsets[level - 1] / 4,
323 122 .target = entry.offsets[level] / 4,
324
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 122 times.
122 .sourceWidth = TextureLevelSide(entry.width, level - 1),
325
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 122 times.
122 .sourceHeight = TextureLevelSide(entry.height, level - 1),
326
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 122 times.
122 .width = TextureLevelSide(entry.width, level),
327
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 114 times.
122 .height = TextureLevelSide(entry.height, level),
328 122 .format = entry.format,
329
1/1
✓ Branch 1 taken 122 times.
122 .colorSpace = entry.colorSpace});
330 }
331
2/2
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 6 times.
33 if (m_device.GetSampling() == device::Sampling::Hardware) {
332
1/1
✓ Branch 1 taken 27 times.
54 entry.texture = m_device.CreateTexture({.width = entry.width,
333 27 .height = entry.height,
334 27 .levels = entry.levels,
335 27 .format = entry.format,
336 27 .colorSpace = entry.colorSpace,
337
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
27 .label = key.first});
338
1/3
✓ Branch 1 taken 27 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
27 mips.copies.emplace_back(texels, &entry);
339 } else {
340
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
6 entry.texels = texels;
341 }
342 33 entry.ready = true;
343
1/2
✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
33 ++m_loaded;
344 93 }
345
346 507 void TextureCache::Downsample(const Mips& mips) {
347
3/4
✓ Branch 0 taken 481 times.
✓ Branch 1 taken 26 times.
✓ Branch 2 taken 481 times.
✗ Branch 3 not taken.
507 if (mips.levels.size() < 2 && mips.copies.empty()) {
348 481 return;
349 }
350
4/5
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 22 times.
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 4 times.
26 static const profiler::ZoneId zone = profiler::ZoneId::Named("Textures/Mips");
351 26 device::Queue& queue = m_device.GetQueue();
352 26 device::CommandEncoder encoder = queue.CreateEncoder();
353
1/1
✓ Branch 1 taken 26 times.
26 encoder.BeginZone(zone);
354
2/2
✓ Branch 0 taken 104 times.
✓ Branch 1 taken 26 times.
130 for (uint32_t level = 1; level < mips.levels.size(); ++level) {
355
1/1
✓ Branch 1 taken 104 times.
104 std::vector<MipJob> jobs = mips.levels[level];
356 104 uint32_t total = 0;
357
2/2
✓ Branch 0 taken 122 times.
✓ Branch 1 taken 104 times.
226 for (MipJob& job : jobs) {
358 122 job.first = total;
359 122 total += job.width * job.height;
360 }
361 104 const Ref<device::Buffer> table =
362
2/2
✓ Branch 1 taken 104 times.
✓ Branch 4 taken 104 times.
104 m_device.CreateBuffer({.label = "texture mips"}, std::span<const MipJob>(jobs));
363 104 encoder.Barrier();
364
3/3
✓ Branch 1 taken 104 times.
✓ Branch 4 taken 104 times.
✓ Branch 7 taken 104 times.
104 encoder.Dispatch(Kernels().Entry("DownsampleLevels"),
365
2/5
✓ Branch 1 taken 104 times.
✓ Branch 3 taken 104 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
208 MipParams{.jobs = {table->Address()},
366
1/1
✓ Branch 1 taken 104 times.
104 .jobCount = uint32_t(jobs.size()),
367 .total = total},
368
1/1
✓ Branch 1 taken 104 times.
104 {.x = device::GroupCount(total, kMipGroup)});
369 104 }
370
1/1
✓ Branch 1 taken 26 times.
26 encoder.Barrier();
371
2/2
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 26 times.
53 for (const auto& [texels, entry] : mips.copies) {
372
2/2
✓ Branch 0 taken 128 times.
✓ Branch 1 taken 27 times.
155 for (uint32_t level = 0; level < entry->levels; ++level) {
373
2/2
✓ Branch 1 taken 128 times.
✓ Branch 4 taken 128 times.
128 encoder.Copy(texels->Slice(entry->offsets[level], entry->texture->LevelBytes(level)),
374 *entry->texture, level);
375 }
376 }
377
1/1
✓ Branch 1 taken 26 times.
26 encoder.EndZone();
378
2/2
✓ Branch 1 taken 26 times.
✓ Branch 4 taken 26 times.
26 queue.Submit(encoder.Finish());
379 234 }
380
381 104 device::Program& TextureCache::Kernels() {
382
2/2
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 81 times.
104 if (!m_program) {
383
2/2
✓ Branch 1 taken 23 times.
✓ Branch 4 taken 23 times.
69 m_program = m_device.CreateProgram({.modules = {"chrono_batch_render.texture.kernels.mips"},
384 .library = &kTextureKernels,
385 .entries = {"DownsampleLevels"},
386
2/3
✓ Branch 1 taken 23 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 23 times.
46 .label = "textures"});
387 }
388 104 return *m_program;
389 46 }
390
391 507 std::set<TextureKey> TextureCache::Collect() {
392 507 std::vector<std::pair<TextureKey, Decoded>> done;
393 507 {
394
1/1
✓ Branch 1 taken 507 times.
507 const std::lock_guard lock(m_mutex);
395 507 done.swap(m_done);
396 507 }
397 507 m_loaded = 0;
398 507 m_failed = 0;
399 507 std::set<TextureKey> finished;
400 507 Mips mips;
401
2/2
✓ Branch 1 taken 40 times.
✓ Branch 2 taken 507 times.
547 for (auto& [key, decoded] : done) {
402 40 const auto entry = m_images.find(key);
403
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 40 times.
40 if (entry == m_images.end()) {
404 ✗ continue;
405 }
406
1/1
✓ Branch 2 taken 40 times.
40 Upload(key, entry->second, std::move(decoded), mips);
407
1/1
✓ Branch 1 taken 40 times.
40 finished.insert(key);
408 }
409
1/1
✓ Branch 1 taken 507 times.
507 Downsample(mips);
410 1014 return finished;
411 507 }
412
413 72 TextureBinding TextureCache::Bind(TextureKey key, TextureWrap wrapS, TextureWrap wrapT) {
414 72 TextureBinding binding{.key = std::move(key), .wrapS = wrapS, .wrapT = wrapT};
415
2/2
✓ Branch 0 taken 50 times.
✓ Branch 1 taken 22 times.
72 if (binding.key.first.empty()) {
416 return binding;
417 }
418
1/1
✓ Branch 1 taken 50 times.
50 Acquire(binding.key);
419
1/1
✓ Branch 1 taken 50 times.
50 Resolve(binding);
420 return binding;
421 ✗ }
422
423 84 void TextureCache::Resolve(TextureBinding& binding) {
424
2/4
✓ Branch 0 taken 84 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 84 times.
✗ Branch 3 not taken.
84 if (binding.handle != 0 || binding.key.first.empty()) {
425 return;
426 }
427 84 const Entry& entry = m_images.at(binding.key);
428
2/2
✓ Branch 0 taken 35 times.
✓ Branch 1 taken 49 times.
84 if (!entry.ready) {
429 return;
430 }
431
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 6 times.
35 if (entry.texture) {
432 29 binding.handle = entry.texture->KernelHandle(binding.wrapS, binding.wrapT);
433
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 } else if (entry.texels) {
434 6 TextureDesc desc{};
435 6 desc.texels.address = entry.texels->Address();
436 6 desc.width = entry.width;
437 6 desc.height = entry.height;
438 6 desc.numLevels = entry.levels;
439 6 desc.format = entry.format;
440 6 desc.colorSpace = entry.colorSpace;
441 6 desc.wrapS = binding.wrapS;
442 6 desc.wrapT = binding.wrapT;
443 6 std::copy(entry.offsets.begin(), entry.offsets.end(), desc.levelOffsets);
444 6 binding.desc = AllocateDesc();
445 6 m_device.GetQueue().Write(
446 6 m_descChunks[binding.desc / kDescsPerChunk]->Slice(
447 6 uint64_t(binding.desc % kDescsPerChunk) * sizeof(TextureDesc), sizeof(TextureDesc)),
448 &desc);
449 6 binding.handle = DescAddress(binding.desc);
450 }
451 }
452
453 36 void TextureCache::Unbind(const TextureBinding& binding) {
454
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 30 times.
36 if (binding.desc != ~0u) {
455 6 m_freeDescs.push_back(binding.desc);
456 }
457
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 8 times.
36 if (!binding.key.first.empty()) {
458 28 const auto entry = m_images.find(binding.key);
459
2/2
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 9 times.
28 if (--entry->second.users == 0) {
460 19 m_images.erase(entry);
461 }
462 }
463 36 }
464
465 }
466