SleakEngine 1.0.0
C++23 multi-backend game engine
Loading...
Searching...
No Matches
CullingSystem.hpp
Go to the documentation of this file.
1#ifndef _CULLING_SYSTEM_HPP_
2#define _CULLING_SYSTEM_HPP_
3
5#include <Core/OSDef.hpp>
6#include <Math/AABB.hpp>
7#include <Math/Matrix.hpp>
8#include <Math/Vector.hpp>
9#include <cstdint>
10
11namespace Sleak {
12
13/// CPU visibility system: view-frustum culling plus software occlusion
14/// culling against a low-resolution depth buffer rasterized from
15/// game-submitted occluder volumes. Backend-agnostic (no GPU work).
16///
17/// Frame protocol:
18/// 1. BeginFrame(...) once per frame after camera update
19/// (the engine calls this automatically from the main camera)
20/// 2. SubmitOccluderBox/Triangles any number of world-space occluders
21/// 3. FinalizeOccluders() sort by distance, rasterize budget
22/// 4. IsVisible(aabb) frustum + occlusion query
23/// Steps 2-3 are optional; IsVisible degrades to frustum-only.
24///
25/// Everything here is static and lives for the process. The main camera
26/// calls BeginFrame() for you during its update, so a game that only wants
27/// frustum culling can call IsVisible() and stop reading here.
28///
29/// Occlusion culling is the part you opt into. Submit occluder volumes
30/// every frame, call FinalizeOccluders() once, then test your objects.
31/// Occluders must be fully solid volumes: a box submitted over a cave or
32/// an open doorway will hide geometry that should be visible. The bounds
33/// you test with should be tight around the real vertex extent, since
34/// bounds that overshoot into empty space pass the depth test and cull
35/// nothing while still costing you the test.
36///
37/// The occlusion pass adapts. When a rasterized frame culls nothing, it
38/// stops rasterizing and probes again every `probeInterval` frames, with
39/// queries falling back to frustum-only in between. GetStats() reports
40/// what the last pass actually did.
41///
42/// @code{.cpp}
43/// // Optional tuning, once at startup
44/// Sleak::CullingSystem::SetOcclusionCullingEnabled(true);
45/// Sleak::CullingSystem::SetOcclusionBufferSize(256, 144);
46/// Sleak::CullingSystem::SetMaxOccluders(192);
47/// Sleak::CullingSystem::SetAdaptiveOcclusion(true, 20);
48///
49/// // Every frame, after the camera has updated
50/// for (const auto& chunk : loadedChunks) {
51/// for (const auto& solid : chunk.solidVolumes) {
52/// Sleak::CullingSystem::SubmitOccluderBox(solid);
53/// }
54/// }
55/// Sleak::CullingSystem::FinalizeOccluders();
56///
57/// for (auto& chunk : loadedChunks) {
58/// chunk.visible = Sleak::CullingSystem::IsVisible(chunk.bounds);
59/// }
60///
61/// const auto& stats = Sleak::CullingSystem::GetStats();
62/// SLEAK_LOG("culled {} by frustum, {} by occlusion",
63/// stats.frustumCulled, stats.occlusionCulled);
64/// @endcode
65///
66/// @see ViewFrustum, Camera, Math::AABB
67/// @ingroup culling
68class ENGINE_API CullingSystem {
69public:
70 /// Per-frame counters for the last completed culling pass.
71 struct Stats {
72 uint32_t occludersSubmitted = 0;
73 uint32_t occludersRasterized = 0;
74 uint32_t tested = 0;
75 uint32_t frustumCulled = 0;
76 uint32_t occlusionCulled = 0;
77 bool occlusionSkipped = false; // adaptive pass idle this frame
78 float rasterizeMs = 0.0f;
79 };
80
81 static void SetFrustumCullingEnabled(bool enabled);
82 static void SetOcclusionCullingEnabled(bool enabled);
83 static bool IsFrustumCullingEnabled();
84 static bool IsOcclusionCullingEnabled();
85
86 /// Occlusion depth buffer resolution (default 256x144).
87 static void SetOcclusionBufferSize(uint32_t width, uint32_t height);
88 /// Max occluders rasterized per frame after the distance sort
89 /// (default 192).
90 static void SetMaxOccluders(uint32_t count);
91
92 /// Adaptive occlusion (default on, interval 20): when a rasterized
93 /// frame culls nothing, skip rasterization for `probeInterval` frames
94 /// and probe again. Queries degrade to frustum-only while skipping.
95 static void SetAdaptiveOcclusion(bool enabled, uint32_t probeInterval);
96
97 /// viewProj uses the engine row-vector convention: clip = point * VP,
98 /// depth range [0, w]. cameraPos is world-space.
99 static void BeginFrame(const ViewFrustum& frustum,
100 const Math::Matrix4& viewProj,
101 const Math::Vector3D& cameraPos);
102
103 /// World-space occluders. Boxes must be fully solid volumes.
104 static void SubmitOccluderBox(const Math::AABB& box);
105 /// World-space triangle occluder; same fully-solid-volume requirement as SubmitOccluderBox.
106 static void SubmitOccluderTriangles(const Math::Vector3D* vertices,
107 uint32_t vertexCount,
108 const uint32_t* indices,
109 uint32_t indexCount);
110 /// Sorts submitted occluders by distance and rasterizes them into the depth buffer up to the max-occluder budget.
111 static void FinalizeOccluders();
112
113 /// Frustum test, then conservative depth test against the occlusion
114 /// buffer. Never falsely culls a visible box (given valid occluders).
115 static bool IsVisible(const Math::AABB& box);
116 /// Frustum-only visibility test, skipping the occlusion buffer entirely.
117 static bool IsVisibleFrustumOnly(const Math::AABB& box);
118
119 static const Stats& GetStats();
120
121 /// Debug: row-major width*height floats, NDC depth (0 near, 1 far).
122 /// Returns nullptr if occlusion has never rasterized.
123 static const float* GetDepthBuffer(uint32_t& width, uint32_t& height);
124
125 static void Shutdown();
126};
127
128} // namespace Sleak
129
130#endif // _CULLING_SYSTEM_HPP_
int width
int height
static bool IsVisibleFrustumOnly(const Math::AABB &box)
Frustum-only visibility test, skipping the occlusion buffer entirely.
static const Stats & GetStats()
static void SetAdaptiveOcclusion(bool enabled, uint32_t probeInterval)
static void SetOcclusionBufferSize(uint32_t width, uint32_t height)
Occlusion depth buffer resolution (default 256x144).
static void SetOcclusionCullingEnabled(bool enabled)
static const float * GetDepthBuffer(uint32_t &width, uint32_t &height)
static void SubmitOccluderTriangles(const Math::Vector3D *vertices, uint32_t vertexCount, const uint32_t *indices, uint32_t indexCount)
World-space triangle occluder; same fully-solid-volume requirement as SubmitOccluderBox.
static void SubmitOccluderBox(const Math::AABB &box)
World-space occluders. Boxes must be fully solid volumes.
static void BeginFrame(const ViewFrustum &frustum, const Math::Matrix4 &viewProj, const Math::Vector3D &cameraPos)
static bool IsVisible(const Math::AABB &box)
static void FinalizeOccluders()
Sorts submitted occluders by distance and rasterizes them into the depth buffer up to the max-occlude...
static bool IsFrustumCullingEnabled()
static bool IsOcclusionCullingEnabled()
static void SetMaxOccluders(uint32_t count)
static void SetFrustumCullingEnabled(bool enabled)
Matrix< float, 4, 4 > Matrix4
Definition Matrix.hpp:413
Root namespace for everything the engine exposes.
Definition Camera.hpp:10
Per-frame counters for the last completed culling pass.