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 | 96 | const uint32_t channels = std::min(header.channels, uint32_t(values.size())); | |
| 29 | 224 | for (uint32_t c = 0; c < channels; ++c) { | |
| 30 | 128 | const size_t at = index * header.channels + c; | |
| 31 | 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 | 96 | const float opaque = header.floats != 0 ? 1.0f : 255.0f; | |
| 38 | 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 | 3 | const size_t size = header.floats != 0 ? sizeof(float) : 1; | |
| 54 | 3 | if (header.channels == 0 || header.channels > 4 || | |
| 55 | 3 | bytes.size() != sizeof(TexelHeader) + count * header.channels * size) { | |
| 56 | return false; | ||
| 57 | } | ||
| 58 | 3 | const std::byte* texels = bytes.data() + sizeof(TexelHeader); | |
| 59 | 3 | if (header.floats != 0) { | |
| 60 | 2 | floats = Image(header.width, header.height); | |
| 61 | 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 | 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 | 3078 | for (uint32_t y = 0; y < kCheckerSide; ++y) { | |
| 83 | 1575936 | for (uint32_t x = 0; x < kCheckerSide; ++x) { | |
| 84 | 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 | 1572938 | for (const Rgba& texel : level.pixels) { | |
| 94 | 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 | 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 | 8 | const ImageEncoding encoding = srgb ? ImageEncoding::Srgb : ImageEncoding::Linear; | |
| 109 | 8 | try { | |
| 110 | 18 | return path == kCheckerboardTexture ? Checkerboard() | |
| 111 | 2 | : bytes ? LoadImageBytes(*bytes, path, encoding) | |
| 112 | 8 | : LoadImageFile(path, encoding); | |
| 113 | ✗ | } catch (const std::exception& error) { | |
| 114 | ✗ | 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 | ✗ | std::string text; | |
| 122 | ✗ | if (bytes) { | |
| 123 | ✗ | text.assign(reinterpret_cast<const char*>(bytes->data()), bytes->size()); | |
| 124 | } else { | ||
| 125 | ✗ | std::ifstream file(path, std::ios::binary); | |
| 126 | ✗ | if (!file) { | |
| 127 | ✗ | throw Error("it can't be opened"); | |
| 128 | } | ||
| 129 | ✗ | std::ostringstream contents; | |
| 130 | ✗ | contents << file.rdbuf(); | |
| 131 | ✗ | text = contents.str(); | |
| 132 | ✗ | } | |
| 133 | ✗ | return ReadIes(text); | |
| 134 | ✗ | } catch (const std::exception& error) { | |
| 135 | ✗ | 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 | 155 | while (levels < kTextureMaxLevels && | |
| 143 | 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 | 366 | : m_device(device), m_files(std::move(files)), m_pool(threads) {} | |
| 153 | |||
| 154 | 750 | TextureCache::~TextureCache() = default; | |
| 155 | |||
| 156 | 8681 | bool TextureCache::Pending() const { | |
| 157 | 8681 | const std::lock_guard lock(m_mutex); | |
| 158 | 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 | 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 | 572 | for (const auto& [key, entry] : m_images) { | |
| 169 | 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 | 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 | 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 | 863 | if (m_bindingsChanged) { | |
| 205 | 389 | m_bindingsChanged = false; | |
| 206 | 389 | const uint64_t bytes = m_bindings.size() * sizeof(uint64_t); | |
| 207 | 389 | if (!m_bindingBuffer || m_bindingBuffer->Bytes() < bytes) { | |
| 208 | 722 | m_bindingBuffer = m_device.CreateBuffer( | |
| 209 | 361 | {.bytes = std::bit_ceil(std::max<uint64_t>(bytes, 64 * sizeof(uint64_t))), | |
| 210 | 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 | 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 | 5 | if (m_descCount == m_descChunks.size() * kDescsPerChunk) { | |
| 224 | 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 | 50 | ++found->second.users; | |
| 238 | 50 | if (!added) { | |
| 239 | 10 | return; | |
| 240 | } | ||
| 241 | 40 | const FileBytes bytes = key.first == kCheckerboardTexture ? nullptr : m_files(key.first); | |
| 242 | 40 | { | |
| 243 | 40 | const std::lock_guard lock(m_mutex); | |
| 244 | 40 | ++m_inFlight; | |
| 245 | 40 | } | |
| 246 | 94 | m_pool.Add(this, [this, key, bytes] { | |
| 247 | 40 | Decoded decoded; | |
| 248 | 40 | const std::optional<TexelHeader> raw = | |
| 249 | 40 | bytes ? ReadTexelHeader(*bytes) : std::optional<TexelHeader>(); | |
| 250 | 40 | decoded.format = raw ? (raw->floats != 0 ? TexelFormat::Rgba16f : TexelFormat::Rgba8) | |
| 251 | 120 | : IsPngOrJpeg(key.first) || key.first == kCheckerboardTexture | |
| 252 | 37 | ? TexelFormat::Rgba8 | |
| 253 | : TexelFormat::Rgba16f; | ||
| 254 | 31 | decoded.colorSpace = key.second && decoded.format == TexelFormat::Rgba8 | |
| 255 | 40 | ? TexelColorSpace::Srgb | |
| 256 | : TexelColorSpace::Linear; | ||
| 257 | 40 | if (raw) { | |
| 258 | 3 | Image floats; | |
| 259 | 3 | if (ReadTexels(*raw, *bytes, floats, decoded.words)) { | |
| 260 | 3 | decoded.width = raw->width; | |
| 261 | 3 | decoded.height = raw->height; | |
| 262 | 3 | if (raw->floats != 0) { | |
| 263 | 2 | Encode(floats, decoded.format, decoded.colorSpace, decoded.words); | |
| 264 | } | ||
| 265 | } else { | ||
| 266 | ✗ | Log(LogLevel::Warning, "texture {} has malformed texels", key.first); | |
| 267 | ✗ | decoded.failed = true; | |
| 268 | } | ||
| 269 | 77 | } else if (IsPngOrJpeg(key.first)) { | |
| 270 | 29 | try { | |
| 271 | 29 | Rgba8Image pixels = | |
| 272 | 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 | 29 | } catch (const std::exception& error) { | |
| 277 | 7 | Log(LogLevel::Warning, "texture {} can't be read: {}", key.first, error.what()); | |
| 278 | 7 | decoded.failed = true; | |
| 279 | 7 | } | |
| 280 | 16 | } else if (std::optional<Image> pixels = IsIes(key.first) | |
| 281 | 8 | ? ReadProfile(key.first, bytes) | |
| 282 | 16 | : ReadPixels(key.first, key.second, bytes)) { | |
| 283 | 8 | decoded.width = pixels->width; | |
| 284 | 8 | decoded.height = pixels->height; | |
| 285 | 8 | Encode(*pixels, decoded.format, decoded.colorSpace, decoded.words); | |
| 286 | } else { | ||
| 287 | ✗ | decoded.failed = true; | |
| 288 | 8 | } | |
| 289 | 40 | decoded.levels = decoded.failed ? 0 : LevelCount(decoded.width, decoded.height); | |
| 290 | 40 | const std::lock_guard lock(m_mutex); | |
| 291 | 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 | 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 | 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 | 33 | const Ref<device::Buffer> texels = m_device.CreateBuffer({.bytes = bytes, .label = key.first}); | |
| 315 | 33 | m_device.GetQueue().Write(texels->Slice(0, decoded.words.size() * sizeof(uint32_t)), | |
| 316 | 33 | decoded.words.data()); | |
| 317 | 33 | if (mips.levels.size() < entry.levels) { | |
| 318 | 30 | mips.levels.resize(entry.levels); | |
| 319 | } | ||
| 320 | 155 | for (uint32_t level = 1; level < entry.levels; ++level) { | |
| 321 | 244 | mips.levels[level].push_back({.texels = {texels->Address()}, | |
| 322 | 122 | .source = entry.offsets[level - 1] / 4, | |
| 323 | 122 | .target = entry.offsets[level] / 4, | |
| 324 | 122 | .sourceWidth = TextureLevelSide(entry.width, level - 1), | |
| 325 | 122 | .sourceHeight = TextureLevelSide(entry.height, level - 1), | |
| 326 | 122 | .width = TextureLevelSide(entry.width, level), | |
| 327 | 122 | .height = TextureLevelSide(entry.height, level), | |
| 328 | 122 | .format = entry.format, | |
| 329 | 122 | .colorSpace = entry.colorSpace}); | |
| 330 | } | ||
| 331 | 33 | if (m_device.GetSampling() == device::Sampling::Hardware) { | |
| 332 | 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 | 27 | .label = key.first}); | |
| 338 | 27 | mips.copies.emplace_back(texels, &entry); | |
| 339 | } else { | ||
| 340 | 6 | entry.texels = texels; | |
| 341 | } | ||
| 342 | 33 | entry.ready = true; | |
| 343 | 33 | ++m_loaded; | |
| 344 | 93 | } | |
| 345 | |||
| 346 | 507 | void TextureCache::Downsample(const Mips& mips) { | |
| 347 | 507 | if (mips.levels.size() < 2 && mips.copies.empty()) { | |
| 348 | 481 | return; | |
| 349 | } | ||
| 350 | 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 | 26 | encoder.BeginZone(zone); | |
| 354 | 130 | for (uint32_t level = 1; level < mips.levels.size(); ++level) { | |
| 355 | 104 | std::vector<MipJob> jobs = mips.levels[level]; | |
| 356 | 104 | uint32_t total = 0; | |
| 357 | 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 | 104 | m_device.CreateBuffer({.label = "texture mips"}, std::span<const MipJob>(jobs)); | |
| 363 | 104 | encoder.Barrier(); | |
| 364 | 104 | encoder.Dispatch(Kernels().Entry("DownsampleLevels"), | |
| 365 | 208 | MipParams{.jobs = {table->Address()}, | |
| 366 | 104 | .jobCount = uint32_t(jobs.size()), | |
| 367 | .total = total}, | ||
| 368 | 104 | {.x = device::GroupCount(total, kMipGroup)}); | |
| 369 | 104 | } | |
| 370 | 26 | encoder.Barrier(); | |
| 371 | 53 | for (const auto& [texels, entry] : mips.copies) { | |
| 372 | 155 | for (uint32_t level = 0; level < entry->levels; ++level) { | |
| 373 | 128 | encoder.Copy(texels->Slice(entry->offsets[level], entry->texture->LevelBytes(level)), | |
| 374 | *entry->texture, level); | ||
| 375 | } | ||
| 376 | } | ||
| 377 | 26 | encoder.EndZone(); | |
| 378 | 26 | queue.Submit(encoder.Finish()); | |
| 379 | 234 | } | |
| 380 | |||
| 381 | 104 | device::Program& TextureCache::Kernels() { | |
| 382 | 104 | if (!m_program) { | |
| 383 | 69 | m_program = m_device.CreateProgram({.modules = {"chrono_batch_render.texture.kernels.mips"}, | |
| 384 | .library = &kTextureKernels, | ||
| 385 | .entries = {"DownsampleLevels"}, | ||
| 386 | 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 | 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 | 547 | for (auto& [key, decoded] : done) { | |
| 402 | 40 | const auto entry = m_images.find(key); | |
| 403 | 40 | if (entry == m_images.end()) { | |
| 404 | ✗ | continue; | |
| 405 | } | ||
| 406 | 40 | Upload(key, entry->second, std::move(decoded), mips); | |
| 407 | 40 | finished.insert(key); | |
| 408 | } | ||
| 409 | 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 | 72 | if (binding.key.first.empty()) { | |
| 416 | return binding; | ||
| 417 | } | ||
| 418 | 50 | Acquire(binding.key); | |
| 419 | 50 | Resolve(binding); | |
| 420 | return binding; | ||
| 421 | ✗ | } | |
| 422 | |||
| 423 | 84 | void TextureCache::Resolve(TextureBinding& binding) { | |
| 424 | 84 | if (binding.handle != 0 || binding.key.first.empty()) { | |
| 425 | return; | ||
| 426 | } | ||
| 427 | 84 | const Entry& entry = m_images.at(binding.key); | |
| 428 | 84 | if (!entry.ready) { | |
| 429 | return; | ||
| 430 | } | ||
| 431 | 35 | if (entry.texture) { | |
| 432 | 29 | binding.handle = entry.texture->KernelHandle(binding.wrapS, binding.wrapT); | |
| 433 | 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 | 36 | if (binding.desc != ~0u) { | |
| 455 | 6 | m_freeDescs.push_back(binding.desc); | |
| 456 | } | ||
| 457 | 36 | if (!binding.key.first.empty()) { | |
| 458 | 28 | const auto entry = m_images.find(binding.key); | |
| 459 | 28 | if (--entry->second.users == 0) { | |
| 460 | 19 | m_images.erase(entry); | |
| 461 | } | ||
| 462 | } | ||
| 463 | 36 | } | |
| 464 | |||
| 465 | } | ||
| 466 |