stage/columns/array_pool.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | #include "chrono_batch_render/stage/columns/array_pool.h" | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <numeric> | ||
| 6 | #include <utility> | ||
| 7 | |||
| 8 | #include "chrono_batch_render/device/kernels/shared.h" | ||
| 9 | #include "chrono_batch_render/device/queue.h" | ||
| 10 | #include "chrono_batch_render/util/error.h" | ||
| 11 | |||
| 12 | namespace chrono::batch_render { | ||
| 13 | |||
| 14 | namespace { | ||
| 15 | |||
| 16 | constexpr uint64_t kArrayAlignment = 16; | ||
| 17 | |||
| 18 | } | ||
| 19 | |||
| 20 | 563 | ArrayPool::ArrayPool(DeviceHeap& heap, std::string label, uint32_t itemBytes) | |
| 21 | 563 | : m_heap(heap), m_label(std::move(label)), m_itemBytes(itemBytes) { | |
| 22 | 563 | if (itemBytes == 0 || itemBytes % 4 != 0) { | |
| 23 | ✗ | throw Error("{} holds items of {} bytes; items are whole words", m_label, itemBytes); | |
| 24 | } | ||
| 25 | 563 | } | |
| 26 | |||
| 27 | 3053 | uint32_t ArrayPool::AddRun(HeapBlock block, uint32_t live) { | |
| 28 | 3053 | if (!m_freeRuns.empty()) { | |
| 29 | 201 | const uint32_t run = m_freeRuns.back(); | |
| 30 | 201 | m_freeRuns.pop_back(); | |
| 31 | 201 | m_runs[run] = {block, live, {}}; | |
| 32 | 201 | return run; | |
| 33 | } | ||
| 34 | 2852 | m_runs.push_back({block, live, {}}); | |
| 35 | 2852 | return uint32_t(m_runs.size() - 1); | |
| 36 | 3053 | } | |
| 37 | |||
| 38 | 3099 | ArrayRef ArrayPool::Allocate(uint32_t count, uint32_t capacity) { | |
| 39 | 3099 | capacity = std::max(capacity, count); | |
| 40 | 3099 | if (capacity == 0) { | |
| 41 | 55 | return {}; | |
| 42 | } | ||
| 43 | 3044 | const uint64_t bytes = RoundUp(uint64_t(capacity) * m_itemBytes, kArrayAlignment); | |
| 44 | 3044 | const HeapBlock block = m_heap.Allocate(bytes, kArrayAlignment); | |
| 45 | 3044 | const uint32_t run = AddRun(block, 1); | |
| 46 | 3044 | m_arrays.emplace(block.Address(), Entry{run, 1}); | |
| 47 | 3044 | return {block.Address(), count, capacity}; | |
| 48 | } | ||
| 49 | |||
| 50 | 9 | std::vector<ArrayRef> ArrayPool::AllocateRun(std::span<const uint32_t> counts) { | |
| 51 | 9 | std::vector<ArrayRef> refs(counts.size()); | |
| 52 | 9 | uint64_t items = 0; | |
| 53 | 9 | uint32_t live = 0; | |
| 54 | 34 | for (const uint32_t count : counts) { | |
| 55 | 25 | items += count; | |
| 56 | 27 | live += count > 0 ? 1 : 0; | |
| 57 | } | ||
| 58 | 9 | if (items == 0) { | |
| 59 | return refs; | ||
| 60 | } | ||
| 61 | 9 | const uint64_t unit = std::lcm(uint64_t(m_itemBytes), kArrayAlignment); | |
| 62 | 9 | const HeapBlock block = m_heap.Allocate(RoundUp(items * m_itemBytes, unit), unit); | |
| 63 | 9 | const uint32_t run = AddRun(block, live); | |
| 64 | 9 | uint64_t address = block.Address(); | |
| 65 | 34 | for (uint32_t i = 0; i < counts.size(); ++i) { | |
| 66 | 25 | if (counts[i] == 0) { | |
| 67 | 2 | continue; | |
| 68 | } | ||
| 69 | 23 | m_arrays.emplace(address, Entry{run, 1}); | |
| 70 | 23 | refs[i] = {address, counts[i], counts[i]}; | |
| 71 | 23 | address += uint64_t(counts[i]) * m_itemBytes; | |
| 72 | } | ||
| 73 | return refs; | ||
| 74 | ✗ | } | |
| 75 | |||
| 76 | ✗ | std::vector<ArrayRef> ArrayPool::Borrow(Ref<device::Buffer> buffer, uint32_t arrays, | |
| 77 | uint32_t count) { | ||
| 78 | ✗ | const uint64_t stride = uint64_t(count) * m_itemBytes; | |
| 79 | ✗ | if (arrays == 0 || count == 0 || buffer->Bytes() < stride * arrays) { | |
| 80 | ✗ | throw Error("{} borrows {} arrays of {} items from {} bytes", m_label, arrays, count, | |
| 81 | ✗ | buffer->Bytes()); | |
| 82 | } | ||
| 83 | ✗ | const uint32_t run = AddRun({buffer.Get(), 0, buffer->Bytes()}, arrays); | |
| 84 | ✗ | m_runs[run].borrowed = std::move(buffer); | |
| 85 | ✗ | std::vector<ArrayRef> refs; | |
| 86 | ✗ | for (uint32_t i = 0; i < arrays; ++i) { | |
| 87 | ✗ | const uint64_t address = m_runs[run].block.Address() + i * stride; | |
| 88 | ✗ | if (!m_arrays.emplace(address, Entry{run, 1}).second) { | |
| 89 | ✗ | throw Error("{} already holds an array at {:#x}", m_label, address); | |
| 90 | } | ||
| 91 | ✗ | refs.push_back({address, count, count}); | |
| 92 | } | ||
| 93 | ✗ | return refs; | |
| 94 | ✗ | } | |
| 95 | |||
| 96 | 201 | bool ArrayPool::Borrowed(const ArrayRef& array) const { | |
| 97 | 201 | return array.address != 0 && m_runs[Lookup(array).run].borrowed; | |
| 98 | } | ||
| 99 | |||
| 100 | 6293 | const ArrayPool::Entry& ArrayPool::Lookup(const ArrayRef& array) const { | |
| 101 | 6293 | const auto found = m_arrays.find(array.address); | |
| 102 | 6293 | if (found == m_arrays.end()) { | |
| 103 | 1 | throw Error("{} holds no array at {:#x}", m_label, array.address); | |
| 104 | } | ||
| 105 | 6292 | return found->second; | |
| 106 | } | ||
| 107 | |||
| 108 | 164 | ArrayRef ArrayPool::Share(const ArrayRef& array) { | |
| 109 | 164 | if (array.address != 0) { | |
| 110 | 164 | Lookup(array); | |
| 111 | 164 | ++m_arrays.at(array.address).references; | |
| 112 | } | ||
| 113 | 164 | return array; | |
| 114 | } | ||
| 115 | |||
| 116 | 116 | uint32_t ArrayPool::References(const ArrayRef& array) const { | |
| 117 | 116 | return array.address == 0 ? 0 : Lookup(array).references; | |
| 118 | } | ||
| 119 | |||
| 120 | 466 | void ArrayPool::Free(const ArrayRef& array) { | |
| 121 | 466 | if (array.address == 0) { | |
| 122 | return; | ||
| 123 | } | ||
| 124 | 382 | Lookup(array); | |
| 125 | 382 | Entry& entry = m_arrays.at(array.address); | |
| 126 | 382 | if (--entry.references > 0) { | |
| 127 | return; | ||
| 128 | } | ||
| 129 | 347 | if (const auto staged = m_stagedIndices.find(array.address); staged != m_stagedIndices.end()) { | |
| 130 | ✗ | m_staged[staged->second].bytes = 0; | |
| 131 | ✗ | m_stagedIndices.erase(staged); | |
| 132 | } | ||
| 133 | 347 | Run& run = m_runs[entry.run]; | |
| 134 | 347 | if (--run.live == 0) { | |
| 135 | 343 | if (run.borrowed) { | |
| 136 | ✗ | run.borrowed = {}; | |
| 137 | } else { | ||
| 138 | 343 | m_heap.Free(run.block); | |
| 139 | } | ||
| 140 | 343 | m_freeRuns.push_back(entry.run); | |
| 141 | } | ||
| 142 | 347 | m_arrays.erase(array.address); | |
| 143 | } | ||
| 144 | |||
| 145 | 4218 | device::BufferRange ArrayPool::Range(const ArrayRef& array) const { | |
| 146 | 4218 | const Run& run = m_runs[Lookup(array).run]; | |
| 147 | 4217 | return run.block.buffer->Slice(array.address - run.block.buffer->Address(), | |
| 148 | 4217 | uint64_t(array.capacity) * m_itemBytes); | |
| 149 | } | ||
| 150 | |||
| 151 | 1656 | void ArrayPool::Write(const ArrayRef& array, std::span<const std::byte> bytes) { | |
| 152 | 1656 | if (bytes.empty()) { | |
| 153 | return; | ||
| 154 | } | ||
| 155 | 1642 | if (bytes.size() > uint64_t(array.capacity) * m_itemBytes) { | |
| 156 | ✗ | throw Error("{} bytes for an array of {} {}-byte items", bytes.size(), array.capacity, | |
| 157 | ✗ | m_itemBytes); | |
| 158 | } | ||
| 159 | 1642 | m_heap.GetDevice().GetQueue().Write(Range(array).Slice(0, bytes.size()), bytes.data()); | |
| 160 | } | ||
| 161 | |||
| 162 | 732 | void ArrayPool::Stage(const ArrayRef& array, std::span<const std::byte> bytes) { | |
| 163 | 732 | if (bytes.size() % sizeof(uint32_t) != 0) { | |
| 164 | ✗ | Write(array, bytes); | |
| 165 | ✗ | return; | |
| 166 | } | ||
| 167 | 732 | if (bytes.size() > uint64_t(array.capacity) * m_itemBytes) { | |
| 168 | ✗ | throw Error("{} bytes for an array of {} {}-byte items", bytes.size(), array.capacity, | |
| 169 | ✗ | m_itemBytes); | |
| 170 | } | ||
| 171 | 732 | if (const auto staged = m_stagedIndices.find(array.address); staged != m_stagedIndices.end()) { | |
| 172 | 17 | m_staged[staged->second].bytes = 0; | |
| 173 | } | ||
| 174 | 732 | m_stagedIndices[array.address] = m_staged.size(); | |
| 175 | 732 | m_staged.push_back({array, m_stagedBytes.size(), bytes.size()}); | |
| 176 | 732 | m_stagedBytes.insert(m_stagedBytes.end(), bytes.begin(), bytes.end()); | |
| 177 | } | ||
| 178 | |||
| 179 | 376 | std::vector<ArrayPool::StagedCopy> ArrayPool::TakeStaged(uint64_t base) { | |
| 180 | 376 | std::vector<StagedCopy> copies; | |
| 181 | 1069 | for (const Staged& staged : m_staged) { | |
| 182 | 693 | if (staged.bytes > 0) { | |
| 183 | 1370 | copies.push_back({Range(staged.array).Address(), base + staged.offset, | |
| 184 | 685 | uint32_t(staged.bytes / sizeof(uint32_t))}); | |
| 185 | } | ||
| 186 | } | ||
| 187 | 376 | m_staged.clear(); | |
| 188 | 376 | m_stagedBytes.clear(); | |
| 189 | 376 | m_stagedIndices.clear(); | |
| 190 | 376 | return copies; | |
| 191 | ✗ | } | |
| 192 | |||
| 193 | 3114 | void ArrayPool::FlushStaged() { | |
| 194 | 3136 | for (const Staged& staged : m_staged) { | |
| 195 | 22 | if (staged.bytes > 0) { | |
| 196 | ✗ | Write(staged.array, std::span(m_stagedBytes).subspan(staged.offset, staged.bytes)); | |
| 197 | } | ||
| 198 | } | ||
| 199 | 3114 | m_staged.clear(); | |
| 200 | 3114 | m_stagedBytes.clear(); | |
| 201 | 3114 | m_stagedIndices.clear(); | |
| 202 | 3114 | } | |
| 203 | |||
| 204 | 1536 | std::vector<std::byte> ArrayPool::Read(const ArrayRef& array) { | |
| 205 | 1536 | FlushStaged(); | |
| 206 | 1536 | std::vector<std::byte> bytes(size_t(array.count) * m_itemBytes); | |
| 207 | 1536 | if (!bytes.empty()) { | |
| 208 | 1536 | m_heap.GetDevice().GetQueue().Read(Range(array).Slice(0, bytes.size()), bytes.data()); | |
| 209 | } | ||
| 210 | 1535 | return bytes; | |
| 211 | 1 | } | |
| 212 | |||
| 213 | 1578 | std::vector<std::vector<std::byte>> ArrayPool::Read(std::span<const ArrayRef> arrays) { | |
| 214 | 1578 | FlushStaged(); | |
| 215 | |||
| 216 | 500 | struct Extent { | |
| 217 | device::Buffer* buffer = nullptr; | ||
| 218 | uint64_t first = ~uint64_t(0); | ||
| 219 | uint64_t last = 0; | ||
| 220 | device::Download download; | ||
| 221 | }; | ||
| 222 | |||
| 223 | 1578 | std::vector<Extent> spans; | |
| 224 | 1578 | std::vector<std::pair<size_t, uint64_t>> located(arrays.size(), {0, 0}); | |
| 225 | 3309 | for (size_t i = 0; i < arrays.size(); ++i) { | |
| 226 | 1731 | if (arrays[i].address == 0 || arrays[i].count == 0) { | |
| 227 | 519 | continue; | |
| 228 | } | ||
| 229 | 1212 | device::Buffer* buffer = m_runs[Lookup(arrays[i]).run].block.buffer; | |
| 230 | 1212 | auto found = std::ranges::find(spans, buffer, &Extent::buffer); | |
| 231 | 1212 | if (found == spans.end()) { | |
| 232 | 250 | found = spans.insert(spans.end(), Extent{.buffer = buffer}); | |
| 233 | } | ||
| 234 | 1212 | const uint64_t offset = arrays[i].address - buffer->Address(); | |
| 235 | 1212 | found->first = std::min(found->first, offset); | |
| 236 | 1212 | found->last = std::max(found->last, offset + uint64_t(arrays[i].count) * m_itemBytes); | |
| 237 | 1212 | located[i] = {size_t(found - spans.begin()), offset}; | |
| 238 | } | ||
| 239 | 1578 | device::Queue& queue = m_heap.GetDevice().GetQueue(); | |
| 240 | 1828 | for (Extent& span : spans) { | |
| 241 | 250 | span.download = queue.ReadAsync(span.buffer->Slice(span.first, span.last - span.first)); | |
| 242 | } | ||
| 243 | 1828 | for (Extent& span : spans) { | |
| 244 | 250 | span.download.Wait(); | |
| 245 | } | ||
| 246 | 1578 | std::vector<std::vector<std::byte>> bytes(arrays.size()); | |
| 247 | 3309 | for (size_t i = 0; i < arrays.size(); ++i) { | |
| 248 | 1731 | if (arrays[i].address == 0 || arrays[i].count == 0) { | |
| 249 | 519 | continue; | |
| 250 | } | ||
| 251 | 1212 | const Extent& span = spans[located[i].first]; | |
| 252 | 2424 | const std::span<const std::byte> read = span.download.Bytes().subspan( | |
| 253 | 1212 | located[i].second - span.first, uint64_t(arrays[i].count) * m_itemBytes); | |
| 254 | 1212 | bytes[i].assign(read.begin(), read.end()); | |
| 255 | } | ||
| 256 | 3156 | return bytes; | |
| 257 | 1578 | } | |
| 258 | |||
| 259 | } | ||
| 260 |