Introduce stable render instances and prepared LOD policy
This commit is contained in:
@@ -39,6 +39,11 @@ struct DrawItem {
|
||||
float metallic{0.0f};
|
||||
bool cast_shadow{true};
|
||||
std::shared_ptr<const Texture> texture{};
|
||||
// Empty means an ad-hoc draw without temporal visibility state. Scene extraction
|
||||
// derives this from persistent object and primitive identities, not draw order.
|
||||
std::string instance_key{};
|
||||
// Optional prepared coarser meshes: element zero is LOD 1; mesh is LOD 0.
|
||||
std::vector<std::shared_ptr<const Mesh>> lod_meshes{};
|
||||
};
|
||||
struct Sprite {
|
||||
Vec3 position{};
|
||||
@@ -88,6 +93,9 @@ struct Snapshot {
|
||||
std::vector<Quad> ui_quads;
|
||||
std::vector<Text> ui_text;
|
||||
std::vector<UiTriangles> ui_triangles;
|
||||
// Distinguishes temporal histories when one Renderer displays different views.
|
||||
std::string view_id{};
|
||||
bool camera_cut{};
|
||||
};
|
||||
// CPU-only validation used before publishing a game or creating Vulkan pipelines.
|
||||
void validate_shader_bundle(const std::filesystem::path& directory);
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
#include <faset/render/renderer.hpp>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace faset::render {
|
||||
struct Bounds {
|
||||
Vec3 min{};
|
||||
Vec3 max{};
|
||||
};
|
||||
|
||||
// Mesh bounds are computed from all vertices; world bounds transform all eight
|
||||
// corners of the local AABB, including rotation, reflection and nonuniform scale.
|
||||
// Empty or nonfinite geometry/matrices are rejected instead of being culled.
|
||||
Bounds local_bounds(const Mesh& mesh);
|
||||
Bounds transformed_bounds(const Mesh& mesh, const Mat4& model);
|
||||
|
||||
struct InstanceUpdate {
|
||||
std::uint32_t slot{};
|
||||
std::uint64_t generation{};
|
||||
Mat4 previous_model{identity};
|
||||
Bounds previous_bounds{};
|
||||
bool previous_valid{};
|
||||
};
|
||||
|
||||
// One update per logical instance per rendered frame. finish_frame() promotes the
|
||||
// current state to *rendered* history and retires keys absent from that frame.
|
||||
// Reordering the input never renumbers live instances. Mesh changes retain the
|
||||
// slot but advance its generation; slot reuse also advances its generation.
|
||||
class InstanceTracker {
|
||||
public:
|
||||
// Prefer this overload for engine-owned meshes. Retaining the owner prevents
|
||||
// allocator address reuse from impersonating the previously rendered mesh.
|
||||
InstanceUpdate update(std::string_view key, std::shared_ptr<const Mesh> mesh_identity,
|
||||
const Mat4& model, const Bounds& world_bounds,
|
||||
std::string_view view_id);
|
||||
// Non-owning overload for meshes whose lifetime is guaranteed by the caller.
|
||||
InstanceUpdate update(std::string_view key, const Mesh* mesh_identity, const Mat4& model,
|
||||
const Bounds& world_bounds, std::string_view view_id);
|
||||
void finish_frame();
|
||||
void invalidate_view(std::string_view view_id);
|
||||
|
||||
private:
|
||||
struct Record {
|
||||
std::uint32_t slot{};
|
||||
std::uint64_t generation{};
|
||||
const Mesh* mesh{};
|
||||
std::shared_ptr<const Mesh> mesh_owner{};
|
||||
Mat4 current_model{identity}, previous_model{identity};
|
||||
Bounds current_bounds{}, previous_bounds{};
|
||||
std::string current_view, previous_view;
|
||||
std::uint64_t seen_frame{};
|
||||
bool committed{};
|
||||
};
|
||||
std::unordered_map<std::string, Record> records_;
|
||||
std::vector<std::uint32_t> free_slots_;
|
||||
std::vector<std::uint64_t> slot_generations_;
|
||||
std::unordered_set<std::string> invalid_views_;
|
||||
std::uint64_t frame_{1};
|
||||
};
|
||||
|
||||
// thresholds[i] is the projected-pixel boundary between levels i and i+1,
|
||||
// ordered strictly high-to-low. Moving toward a coarser level requires size <
|
||||
// threshold*(1-hysteresis); toward a finer level requires size >
|
||||
// threshold*(1+hysteresis). SIZE_MAX means no prior level and uses raw thresholds.
|
||||
// available is optional; when given it has thresholds.size()+1 entries, with 0
|
||||
// marking missing prepared geometry. Nearest available wins; ties prefer finer.
|
||||
std::size_t select_lod(float projected_pixels, std::size_t previous_level,
|
||||
std::span<const float> thresholds, float hysteresis,
|
||||
std::span<const std::uint8_t> available = {});
|
||||
} // namespace faset::render
|
||||
Reference in New Issue
Block a user