tess 1.0.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 DirtyMask dirty_mask{};
22 ContentVersion content_version{};
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 undefined for huge
31 // caller-supplied extents, so share chunk_meta's guarded helper.
32 return box_axis_end(origin, extent);
33}
34
35// Emits render-tile deltas for one chunk. Shared by the dense (all chunks) and
36// sparse (resident set) iteration in collect_render_tile_deltas, so the
37// per-chunk logic stays single-sourced.
38template <typename World>
39void emit_chunk_render_deltas(const World& world, ChunkKey chunk_key,
40 DirtyMask dirty_mask,
41 std::vector<RenderTileDelta>& out) {
42 using Shape = typename World::shape_type;
43 // One observation carries the selected mask, bounds, and content version --
44 // mask and bounds live in the world's SoA columns, not ChunkMeta.
45 const auto observed = world.observe_dirty(chunk_key, dirty_mask);
46 const auto mask = observed.mask;
47 if (!mask) {
48 return;
49 }
50 const auto& dirty_bounds = observed.bounds;
51
52 // Clip the dirty bounds to this chunk's own world-space box before the
53 // per-tile loop. Every tile in the clipped box is inside the shape and
54 // resolves to this chunk, so no per-tile filtering is needed.
55 using Traits = ShapeTraits<Shape>;
56 const auto chunk = chunk_coord<Shape>(chunk_key);
57 const auto chunk_begin_x =
58 static_cast<std::int64_t>(chunk.x * Traits::chunk.x);
59 const auto chunk_begin_y =
60 static_cast<std::int64_t>(chunk.y * Traits::chunk.y);
61 const auto chunk_begin_z =
62 static_cast<std::int64_t>(chunk.z * Traits::chunk.z);
63 const auto begin_x = std::max(dirty_bounds.origin.x, chunk_begin_x);
64 const auto begin_y = std::max(dirty_bounds.origin.y, chunk_begin_y);
65 const auto begin_z = std::max(dirty_bounds.origin.z, chunk_begin_z);
66 const auto end_x =
67 std::min(detail::axis_end(dirty_bounds.origin.x, dirty_bounds.extent.x),
68 chunk_begin_x + static_cast<std::int64_t>(Traits::chunk.x));
69 const auto end_y =
70 std::min(detail::axis_end(dirty_bounds.origin.y, dirty_bounds.extent.y),
71 chunk_begin_y + static_cast<std::int64_t>(Traits::chunk.y));
72 const auto end_z =
73 std::min(detail::axis_end(dirty_bounds.origin.z, dirty_bounds.extent.z),
74 chunk_begin_z + static_cast<std::int64_t>(Traits::chunk.z));
75 for (auto z = begin_z; z < end_z; ++z) {
76 for (auto y = begin_y; y < end_y; ++y) {
77 for (auto x = begin_x; x < end_x; ++x) {
78 const auto coord = Coord3{x, y, z};
79 out.push_back(RenderTileDelta{
80 coord,
81 chunk_key,
82 local_tile_id<Shape>(local_coord<Shape>(coord)),
83 mask,
84 observed.content_version,
85 });
86 }
87 }
88 }
89}
90
91} // namespace detail
92
97template <typename World>
98void collect_render_tile_deltas(std::vector<RenderTileDelta>& out,
99 const World& world, DirtyMask dirty_mask) {
100 if (!dirty_mask) {
101 return;
102 }
103
104 // Dense scans every chunk; sparse scans only the resident set (a non-resident
105 // chunk holds no data and cannot be dirty, so this misses no delta and never
106 // reads a non-resident slot / runs a full chunk_count scan).
107 if constexpr (std::is_same_v<typename World::residency_type,
109 for (std::uint64_t key = 0; key < World::chunk_count; ++key) {
110 detail::emit_chunk_render_deltas(world, ChunkKey{key}, dirty_mask, out);
111 }
112 } else {
113 for (const auto chunk_key : world.resident_chunk_keys()) {
114 detail::emit_chunk_render_deltas(world, chunk_key, dirty_mask, out);
115 }
116 }
117}
118
120template <typename World>
121[[nodiscard]] auto render_tile_deltas(const World& world, DirtyMask dirty_mask)
122 -> std::vector<RenderTileDelta> {
123 std::vector<RenderTileDelta> deltas;
124 collect_render_tile_deltas(deltas, world, dirty_mask);
125 return deltas;
126}
127
129template <typename World>
130void clear_render_delta_dirty(World& world, DirtyMask dirty_mask) noexcept {
131 if (!dirty_mask) {
132 return;
133 }
134 if constexpr (std::is_same_v<typename World::residency_type,
136 for (std::uint64_t key = 0; key < World::chunk_count; ++key) {
137 world.clear_dirty(ChunkKey{key}, dirty_mask);
138 }
139 } else {
140 // Only resident chunks can carry dirty state; clear_dirty on a non-resident
141 // key would assert.
142 for (const auto chunk_key : world.resident_chunk_keys()) {
143 world.clear_dirty(chunk_key, dirty_mask);
144 }
145 }
146}
147
148} // namespace tess
Definition world.h:22
Definition world.h:18
Definition shape.h:86
Definition metadata_types.h:86
Definition shape.h:46
Definition metadata_types.h:12
Definition shape.h:78
Identifies one dirty tile for a renderer to reread from the world.
Definition render_delta.h:17