tess 1.0.0
Performance-first tile and path simulation substrate
Loading...
Searching...
No Matches
descriptors.h
1#pragma once
2
3#include <tess/core/shape.h>
4#include <tess/storage/chunk_page.h>
5
6#include <cstddef>
7#include <cstdint>
8#include <limits>
9#include <type_traits>
10
11// GPU descriptors: dependency-free, byte-level storage facts derived from
12// the field schema and chunk layout for compile-time-polymorphic backends.
13// CPU results remain authoritative. No GPU API is included here; the optional
14// WebGPU implementation is separately gated in webgpu_backend.h, so CPU-only
15// consumers keep this descriptor surface without acquiring a GPU dependency.
16namespace tess::gpu {
17
23enum class GpuFieldFormat : std::uint8_t {
24 U8,
25 U16,
26 U32,
27 U64,
28 I8,
29 I16,
30 I32,
31 I64,
32 F32,
33};
34
35namespace detail {
36
37template <typename Value>
38[[nodiscard]] constexpr auto field_format() noexcept -> GpuFieldFormat {
39 // Integral and float32 only: `double` would otherwise fall through the
40 // is_signed branch into a lying I64 descriptor. Widen the enum before
41 // widening this gate.
42 static_assert(std::is_integral_v<Value> || std::is_same_v<Value, float>,
43 "GPU mirrors support integral and float32 field values only");
44 if constexpr (std::is_same_v<Value, float>) {
45 return GpuFieldFormat::F32;
46 } else if constexpr (std::is_signed_v<Value>) {
47 if constexpr (sizeof(Value) == 1) {
48 return GpuFieldFormat::I8;
49 } else if constexpr (sizeof(Value) == 2) {
50 return GpuFieldFormat::I16;
51 } else if constexpr (sizeof(Value) == 4) {
52 return GpuFieldFormat::I32;
53 } else {
54 return GpuFieldFormat::I64;
55 }
56 } else {
57 if constexpr (sizeof(Value) == 1) {
58 return GpuFieldFormat::U8;
59 } else if constexpr (sizeof(Value) == 2) {
60 return GpuFieldFormat::U16;
61 } else if constexpr (sizeof(Value) == 4) {
62 return GpuFieldFormat::U32;
63 } else {
64 return GpuFieldFormat::U64;
65 }
66 }
67}
68
69} // namespace detail
70
79 std::uint32_t field_index = 0;
80 GpuFieldFormat format = GpuFieldFormat::U8;
81 std::uint32_t value_bytes = 0;
82 std::uint64_t tiles_per_chunk = 0;
83 std::uint64_t bytes_per_chunk = 0;
84 std::uint64_t chunk_count = 0;
85
87 [[nodiscard]] constexpr auto total_bytes_fits() const noexcept -> bool {
88 return chunk_count == 0 ||
89 bytes_per_chunk <=
90 std::numeric_limits<std::uint64_t>::max() / chunk_count;
91 }
92
100 [[nodiscard]] constexpr auto total_bytes() const noexcept -> std::uint64_t {
101 return total_bytes_fits() ? bytes_per_chunk * chunk_count
102 : std::numeric_limits<std::uint64_t>::max();
103 }
104
105 friend constexpr auto operator==(const FieldMirrorDesc&,
106 const FieldMirrorDesc&) noexcept
107 -> bool = default;
108};
109
111template <typename World, typename Tag>
112[[nodiscard]] constexpr auto field_mirror_desc() noexcept -> FieldMirrorDesc {
113 using Schema = typename World::schema_type;
114 using Value = typename Schema::template value_type<Tag>;
116 // ShapeTraits deliberately does not bound total field bytes (sparse
117 // worlds may span trillions of chunks), so the dense-mirror byte
118 // counts this descriptor promises must be proven to fit u64 here --
119 // a wrapped total_bytes()/buffer_offset would be the exact lie this
120 // layer exists to prevent. Shapes whose dense mirror cannot be
121 // described fail to compile instead.
122 constexpr auto kMaxBytes = ~std::uint64_t{0};
123 static_assert(sizeof(Value) <= kMaxBytes / Traits::local_tile_count,
124 "per-chunk mirror bytes must fit std::uint64_t");
125 static_assert(
126 Traits::chunk_count <= 1 || Traits::local_tile_count * sizeof(Value) <=
127 kMaxBytes / Traits::chunk_count,
128 "chunk-key-major mirror byte size must fit std::uint64_t");
129 FieldMirrorDesc desc;
130 desc.field_index = static_cast<std::uint32_t>(Schema::template index<Tag>);
131 desc.format = detail::field_format<Value>();
132 desc.value_bytes = static_cast<std::uint32_t>(sizeof(Value));
133 desc.tiles_per_chunk = Traits::local_tile_count;
134 desc.bytes_per_chunk = Traits::local_tile_count * sizeof(Value);
135 desc.chunk_count = Traits::chunk_count;
136 return desc;
137}
138
146 ChunkKey chunk_key{};
147 std::uint32_t field_index = 0;
148 std::uint64_t buffer_offset = 0;
149 std::uint64_t byte_size = 0;
150 const void* data = nullptr;
151};
152
155 std::uint64_t key = 0;
156 std::uint64_t generation = 0;
157
158 friend constexpr auto operator==(GpuProductHandle, GpuProductHandle) noexcept
159 -> bool = default;
160};
161
167template <typename Tag, typename World>
168[[nodiscard]] auto upload_desc(const World& world, ChunkKey chunk_key) noexcept
169 -> UploadDesc {
170 const auto span = world.template field_span<Tag>(chunk_key);
171 constexpr auto desc = field_mirror_desc<World, Tag>();
172 UploadDesc upload;
173 upload.chunk_key = chunk_key;
174 upload.field_index = desc.field_index;
175 upload.buffer_offset = chunk_key.value * desc.bytes_per_chunk;
176 upload.byte_size = span.size_bytes();
177 upload.data = span.data();
178 return upload;
179}
180
183 GpuProductHandle handle{};
184 std::uint32_t input_field_index = 0;
185 std::uint64_t chunk_count = 0;
186 std::uint32_t workgroups_per_chunk = 1;
187};
188
190enum class ReadbackPolicy : std::uint8_t {
191 None,
192 Summary,
193 SelectedTiles,
194 SelectedPath,
195 // Debug/explicit only.
196 FullField,
197};
198
201 GpuProductHandle handle{};
202 ReadbackPolicy policy = ReadbackPolicy::None;
203 std::uint64_t byte_size = 0;
204};
205
206} // namespace tess::gpu
Definition world.h:22
Definition shape.h:86
Definition shape.h:320
Definition descriptors.h:182
Definition descriptors.h:78
constexpr auto total_bytes_fits() const noexcept -> bool
Definition descriptors.h:87
constexpr auto total_bytes() const noexcept -> std::uint64_t
Definition descriptors.h:100
Definition descriptors.h:154
Definition descriptors.h:200
Definition descriptors.h:145