tess 0.4.0
Performance-first tile and path simulation substrate
Loading...
Searching...
No Matches
render_delta.h
1#pragma once
2
3#include <tess/core/shape.h>
4#include <tess/storage/residency.h>
5#include <tess/storage/world.h>
6
7#include <algorithm>
8#include <cstddef>
9#include <cstdint>
10#include <span>
11#include <type_traits>
12#include <vector>
13
14namespace tess {
15
18 Coord3 coord{};
19 ChunkKey chunk_key{};
20 LocalTileId local_tile_id{};
21 std::uint32_t dirty_flags = 0;
22 std::uint32_t chunk_version = 0;
23};
24
25namespace detail {
26
27[[nodiscard]] constexpr auto axis_end(std::int64_t origin,
28 std::uint64_t extent) noexcept
29 -> std::int64_t {
30 // Saturating: an unguarded origin + int64(extent) is UB for huge
31 // caller-supplied extents (audit 2026-07-11 C1); share chunk_meta's
32 // guarded helper.
33 return box_axis_end(origin, extent);
34}
35
36// Emits render-tile deltas for one chunk. Shared by the dense (all chunks) and
37// sparse (resident set) iteration in collect_render_tile_deltas, so the
38// per-chunk logic stays single-sourced.
39template <typename World>
40void emit_chunk_render_deltas(const World& world, ChunkKey chunk_key,
41 std::uint32_t dirty_mask,
42 std::vector<RenderTileDelta>& out) {
43 using Shape = typename World::shape_type;
44 // One observation carries the masked flags, bounds, and version -- the
45 // flag word and bounds live in the world's SoA columns, not ChunkMeta
46 // (audit 2026-07-11 M5).
47 const auto observed = world.observe_dirty(chunk_key, dirty_mask);
48 const auto flags = observed.flags;
49 if (flags == 0) {
50 return;
51 }
52 const auto& dirty_bounds = observed.bounds;
53
54 // Clip the dirty bounds to this chunk's own world-space box before the
55 // per-tile loop. Every tile in the clipped box is inside the shape and
56 // resolves to this chunk, so no per-tile filtering is needed.
57 using Traits = ShapeTraits<Shape>;
58 const auto chunk = chunk_coord<Shape>(chunk_key);
59 const auto chunk_begin_x =
60 static_cast<std::int64_t>(chunk.x * Traits::chunk.x);
61 const auto chunk_begin_y =
62 static_cast<std::int64_t>(chunk.y * Traits::chunk.y);
63 const auto chunk_begin_z =
64 static_cast<std::int64_t>(chunk.z * Traits::chunk.z);
65 const auto begin_x = std::max(dirty_bounds.origin.x, chunk_begin_x);
66 const auto begin_y = std::max(dirty_bounds.origin.y, chunk_begin_y);
67 const auto begin_z = std::max(dirty_bounds.origin.z, chunk_begin_z);
68 const auto end_x =
69 std::min(detail::axis_end(dirty_bounds.origin.x, dirty_bounds.extent.x),
70 chunk_begin_x + static_cast<std::int64_t>(Traits::chunk.x));
71 const auto end_y =
72 std::min(detail::axis_end(dirty_bounds.origin.y, dirty_bounds.extent.y),
73 chunk_begin_y + static_cast<std::int64_t>(Traits::chunk.y));
74 const auto end_z =
75 std::min(detail::axis_end(dirty_bounds.origin.z, dirty_bounds.extent.z),
76 chunk_begin_z + static_cast<std::int64_t>(Traits::chunk.z));
77 for (auto z = begin_z; z < end_z; ++z) {
78 for (auto y = begin_y; y < end_y; ++y) {
79 for (auto x = begin_x; x < end_x; ++x) {
80 const auto coord = Coord3{x, y, z};
81 out.push_back(RenderTileDelta{
82 coord,
83 chunk_key,
84 local_tile_id<Shape>(local_coord<Shape>(coord)),
85 flags,
86 observed.version,
87 });
88 }
89 }
90 }
91}
92
93} // namespace detail
94
99template <typename World>
100void collect_render_tile_deltas(const World& world, std::uint32_t dirty_mask,
101 std::vector<RenderTileDelta>& out) {
102 if (dirty_mask == 0) {
103 return;
104 }
105
106 // Dense scans every chunk; sparse scans only the resident set (a non-resident
107 // chunk holds no data and cannot be dirty, so this misses no delta and never
108 // reads a non-resident slot / runs a full chunk_count scan).
109 if constexpr (std::is_same_v<typename World::residency_type,
111 for (std::uint64_t key = 0; key < World::chunk_count; ++key) {
112 detail::emit_chunk_render_deltas(world, ChunkKey{key}, dirty_mask, out);
113 }
114 } else {
115 for (const auto chunk_key : world.resident_chunk_keys()) {
116 detail::emit_chunk_render_deltas(world, chunk_key, dirty_mask, out);
117 }
118 }
119}
120
122template <typename World>
123[[nodiscard]] auto render_tile_deltas(const World& world,
124 std::uint32_t dirty_mask)
125 -> std::vector<RenderTileDelta> {
126 std::vector<RenderTileDelta> deltas;
127 collect_render_tile_deltas(world, dirty_mask, deltas);
128 return deltas;
129}
130
132template <typename World>
133void clear_render_delta_dirty(World& world, std::uint32_t dirty_mask) noexcept {
134 if (dirty_mask == 0) {
135 return;
136 }
137 if constexpr (std::is_same_v<typename World::residency_type,
139 for (std::uint64_t key = 0; key < World::chunk_count; ++key) {
140 world.clear_dirty(ChunkKey{key}, dirty_mask);
141 }
142 } else {
143 // Only resident chunks can carry dirty state; clear_dirty on a non-resident
144 // key would assert.
145 for (const auto chunk_key : world.resident_chunk_keys()) {
146 world.clear_dirty(chunk_key, dirty_mask);
147 }
148 }
149}
150
151} // namespace tess
Definition world.h:22
Definition world.h:18
Definition shape.h:67
Definition shape.h:30
Definition shape.h:59
Identifies one dirty tile for a renderer to reread from the world.
Definition render_delta.h:17