策略抽离

This commit is contained in:
2026-08-02 14:20:27 +08:00
parent 6d000f43b4
commit 2df8959ed6
87 changed files with 2454 additions and 1390 deletions
+230 -10
View File
@@ -1,21 +1,32 @@
#include "Core/architecture/Frame_Scheduler.h"
#include "Core/architecture/Render_Lease.h"
#include "Core/architecture/Triple_Buffer.h"
#include "Core/plot/Latency_Eager_Refresh_Strategy.h"
#include "Core/plot/Plot_Frame_Pipeline.h"
#include "Core/flow/low_latency/Render_Lease.h"
#include "Core/flow/low_latency/Triple_Buffer.h"
#include "Core/flow/explicit/Explicit_Frame_Flow.h"
#include "Core/flow/low_latency/Low_Latency_Feedback_Controller.h"
#include "Core/flow/low_latency/Low_Latency_Frame_Flow.h"
#include "Core/flow/low_latency/Low_Latency_Frame_Pipeline.h"
#include "Core/flow/Present_Surface_Lease.h"
#include "Core/plot/Plot_Core.h"
#include "Core/execution/Render_Executor.h"
#include "Core/primitive/Curve_Utils_p.h"
#include "Core/plottable/Performance_Overlay_p.h"
#include "Core/plottable/export.h"
#include "Core/render/Image.h"
#include "Core/render/Render_Frame_Snapshot.h"
#include "Core/render/Render_Output.h"
#include "Core/render/Renderable_Frame_Node.h"
#include <array>
#include <chrono>
#include <cmath>
#include <functional>
#include <gtest/gtest.h>
#include <latch>
#include <limits>
#include <rigtorp/MPMCQueue.h>
#include <thread>
#include <type_traits>
#include <utility>
#include <variant>
namespace renderive {
namespace {
bool unique_roles(const Triple_Buffer_View& view) {
@@ -97,6 +108,8 @@ struct Lease_Test_State : Render_State {
};
struct Lease_Test_Input : Input_Data {};
struct Lease_Test_Data : Typed_Render_Data<Lease_Test_Owner, Lease_Test_State, Lease_Test_Input> {};
struct No_Input_Test_Owner : Renderable {};
struct No_Input_Test_Data : Typed_Render_Data<No_Input_Test_Owner, Lease_Test_State, Input_Data> {};
void init_lease_test_state(Lease_Test_Data& data) {
data.clear_state_buffers();
@@ -106,6 +119,36 @@ void init_lease_test_state(Lease_Test_Data& data) {
}
data.state_buffers[State_Edit]->version = 1;
}
struct Flow_Test_Host : Frame_Flow_Host {
Frame_Flow_Runtime_Snapshot snapshot;
int render_attempts{};
int paint_requests{};
int deadline_requests{};
int deadline_cancels{};
bool render_result = true;
Frame_Flow_Runtime_Snapshot runtime_snapshot(std::uint64_t now_ns) const override {
auto result = snapshot;
result.now_ns = now_ns;
return result;
}
bool try_render_once() override {
++render_attempts;
snapshot.render_job_active = true;
return render_result;
}
void request_paint_from_middle() override {
++paint_requests;
snapshot.middle_has_new_frame = false;
}
void schedule_render_deadline(std::uint64_t) override {
++deadline_requests;
}
void cancel_render_deadline() override {
++deadline_cancels;
}
};
} // namespace
TEST(Renderive_Canvas, TextUsesPenAndFillUsesBrush) {
auto diagnostics = Canvas::default_font_diagnostics();
@@ -186,6 +229,49 @@ TEST(Renderive_State, CloseWakesBlockedAcquire) {
control.unmark_use(held.lease);
}
TEST(Renderive_State, CloseDoesNotReleaseActiveLease) {
Triple_Role_Buffer_Control control;
control.reset();
auto held = control.acquire_role(State_Edit);
ASSERT_EQ(held.result, Triple_Buffer_Result::Success);
EXPECT_TRUE(control.role_busy(State_Edit));
control.close();
EXPECT_TRUE(control.role_busy(State_Edit));
auto blocked = control.try_acquire_role(State_Edit);
EXPECT_EQ(blocked.result, Triple_Buffer_Result::Cancelled);
control.unmark_use(held.lease);
control.wait_until_idle();
EXPECT_FALSE(control.role_busy(State_Edit));
}
TEST(Renderive_State, ClearBuffersWaitsForActiveLease) {
Lease_Test_Data data;
init_lease_test_state(data);
auto held = data.state_control.acquire_role(State_Edit);
ASSERT_EQ(held.result, Triple_Buffer_Result::Success);
std::atomic_bool clear_returned{false};
std::latch clear_started(1);
std::thread clearer([&]() {
clear_started.count_down();
data.clear_state_buffers();
clear_returned.store(true, std::memory_order_release);
});
clear_started.wait();
ASSERT_TRUE(wait_until([&]() {
return data.state_control.role_busy(State_Edit);
}));
EXPECT_FALSE(clear_returned.load(std::memory_order_acquire));
data.state_control.unmark_use(held.lease);
clearer.join();
EXPECT_TRUE(clear_returned.load(std::memory_order_acquire));
for (auto* state : data.state_buffers)
EXPECT_EQ(state, nullptr);
}
TEST(Renderive_State, BusyEditSetterWaitsAndCommits) {
Lease_Test_Data data;
init_lease_test_state(data);
@@ -205,9 +291,19 @@ TEST(Renderive_State, BusyEditSetterWaitsAndCommits) {
EXPECT_TRUE(setter_returned.load(std::memory_order_acquire));
EXPECT_EQ(data.edit_state_value(&Lease_Test_State::value), 42);
}
TEST(Renderive_Input_Runtime, ExactInputDataBindingSkipsAllocation) {
using No_Input_Binding = Renderable_Binding<No_Input_Test_Owner, No_Input_Test_Data, Lease_Test_State, Input_Data>;
using Custom_Input_Binding = Renderable_Binding<Lease_Test_Owner, Lease_Test_Data, Lease_Test_State, Lease_Test_Input>;
EXPECT_FALSE(static_cast<bool>(No_Input_Binding::create_input_data()));
EXPECT_TRUE(static_cast<bool>(Custom_Input_Binding::create_input_data()));
No_Input_Test_Data data;
Render_Input_Lease<No_Input_Test_Data, Input_Data> lease(&data);
EXPECT_FALSE(static_cast<bool>(lease));
}
TEST(Renderive_Frame_Pipeline, PublishAndPaintUseMiddleVersionOnly) {
Plot_Frame_Pipeline pipeline;
Low_Latency_Frame_Pipeline pipeline;
auto lease = pipeline.try_acquire_render_frame();
ASSERT_TRUE(static_cast<bool>(lease));
ASSERT_TRUE(lease.frame->metadata);
@@ -225,7 +321,7 @@ TEST(Renderive_Frame_Pipeline, PublishAndPaintUseMiddleVersionOnly) {
EXPECT_FALSE(pipeline.middle_frame_ready());
}
TEST(Renderive_Frame_Pipeline, PaintAcquireFailureKeepsPendingRequest) {
Plot_Frame_Pipeline pipeline;
Low_Latency_Frame_Pipeline pipeline;
auto lease = pipeline.try_acquire_render_frame();
ASSERT_TRUE(static_cast<bool>(lease));
ASSERT_TRUE(lease.frame->metadata);
@@ -248,7 +344,7 @@ TEST(Renderive_Frame_Pipeline, PaintAcquireFailureKeepsPendingRequest) {
EXPECT_TRUE(static_cast<bool>(consumed.lease));
}
TEST(Renderive_Frame_Pipeline, RequestPresentDoesNotWriteBusyMiddle) {
Plot_Frame_Pipeline pipeline;
Low_Latency_Frame_Pipeline pipeline;
auto lease = pipeline.try_acquire_render_frame();
ASSERT_TRUE(static_cast<bool>(lease));
ASSERT_TRUE(lease.frame->metadata);
@@ -267,6 +363,20 @@ TEST(Renderive_Frame_Pipeline, RequestPresentDoesNotWriteBusyMiddle) {
EXPECT_FALSE(pipeline.paint_request_pending.load(std::memory_order_acquire));
pipeline.frame_control.unmark_use(busy_middle.lease);
}
TEST(Renderive_Render_Model, NeutralSnapshotAndOutputTypesAreWired) {
static_assert(std::is_default_constructible_v<Render_Frame_Snapshot>);
static_assert(std::is_default_constructible_v<Renderable_Frame_Node>);
static_assert(std::is_default_constructible_v<Renderable_Frame_Tree>);
static_assert(std::is_base_of_v<Render_Output, Low_Latency_Frame>);
static_assert(std::is_move_constructible_v<Present_Surface_Lease>);
static_assert(!std::is_copy_constructible_v<Present_Surface_Lease>);
static_assert(!std::is_same_v<Present_Surface_Lease, Low_Latency_Frame_Lease>);
Low_Latency_Frame frame;
Render_Output& output = frame;
EXPECT_TRUE(output.metadata);
EXPECT_TRUE(output.presented_update_states.empty());
}
TEST(Renderive_MPMCQueue, UsesTryOnlyBoundedCapacity) {
rigtorp::MPMCQueue<int> queue(2);
EXPECT_TRUE(queue.try_push(1));
@@ -292,6 +402,60 @@ TEST(Renderive_Curve_Sampling, AllSamplesKeepsSourcePointCount) {
EXPECT_DOUBLE_EQ(points.front().x, 0.0);
EXPECT_DOUBLE_EQ(points.back().x, 100.0);
}
TEST(Renderive_Curve_Sampling, AllSamplesAddsVisibleBoundaryPoints) {
Axis_Mapping_2D mapping{
Axis_Frame_Snapshot{Orientation::Horizontal, Range{0.5, 3.5}, Range{0.0, 90.0}},
Axis_Frame_Snapshot{Orientation::Vertical, Range{0.0, 40.0}, Range{0.0, 40.0}}
};
std::array<double, 5> samples{0.0, 10.0, 20.0, 30.0, 40.0};
auto points = Curve_Utils::build_resampled_points(mapping, Range{0.0, 4.0}, 5, 5, true, Line_Interpolation_Mode::Linear_Value, [&samples](int i) {
return samples[static_cast<std::size_t>(i)];
});
ASSERT_EQ(points.size(), 5u);
EXPECT_DOUBLE_EQ(mapping.domain.pixel_to_coord(points.front().x), 0.5);
EXPECT_DOUBLE_EQ(points.front().y, 5.0);
EXPECT_DOUBLE_EQ(mapping.domain.pixel_to_coord(points.back().x), 3.5);
EXPECT_DOUBLE_EQ(points.back().y, 35.0);
}
TEST(Renderive_Curve_Sampling, AllSamplesUsesInterpolationModeForBoundaryPoints) {
Axis_Mapping_2D mapping{
Axis_Frame_Snapshot{Orientation::Horizontal, Range{0.5, 1.5}, Range{0.0, 100.0}},
Axis_Frame_Snapshot{Orientation::Vertical, Range{0.0, 20.0}, Range{0.0, 20.0}}
};
std::array<double, 3> samples{0.0, 10.0, 20.0};
auto points = Curve_Utils::build_resampled_points(mapping, Range{0.0, 2.0}, 3, 3, true, Line_Interpolation_Mode::Step_Left, [&samples](int i) {
return samples[static_cast<std::size_t>(i)];
});
ASSERT_EQ(points.size(), 3u);
EXPECT_DOUBLE_EQ(points.front().y, 0.0);
EXPECT_DOUBLE_EQ(points.back().y, 10.0);
}
TEST(Renderive_Curve_Sampling, PixelEnvelopeUsesPhysicalPixelBuckets) {
Axis_Mapping_2D mapping{
Axis_Frame_Snapshot{Orientation::Horizontal, Range{0.0, 5.0}, Range{10.1, 10.9}},
Axis_Frame_Snapshot{Orientation::Vertical, Range{-100.0, 100.0}, Range{-100.0, 100.0}}
};
std::array<double, 6> samples{0.0, 100.0, -100.0, 80.0, -80.0, 0.0};
auto points = Curve_Utils::build_pixel_envelope_points(mapping, Range{0.0, 5.0}, 6, 6, false, [&samples](int i) {
return samples[static_cast<std::size_t>(i)];
});
EXPECT_LE(points.size(), 4u);
}
TEST(Renderive_Curve_Sampling, PixelEnvelopeSkipsNonFiniteSamples) {
Axis_Mapping_2D mapping{
Axis_Frame_Snapshot{Orientation::Horizontal, Range{0.0, 4.0}, Range{0.0, 4.0}},
Axis_Frame_Snapshot{Orientation::Vertical, Range{-1.0, 1.0}, Range{-1.0, 1.0}}
};
std::array<double, 5> samples{0.0, std::numeric_limits<double>::quiet_NaN(), 1.0, std::numeric_limits<double>::infinity(), -1.0};
auto points = Curve_Utils::build_pixel_envelope_points(mapping, Range{0.0, 4.0}, 5, 5, false, [&samples](int i) {
return samples[static_cast<std::size_t>(i)];
});
ASSERT_FALSE(points.empty());
for (const PointF& point : points) {
EXPECT_TRUE(std::isfinite(point.x));
EXPECT_TRUE(std::isfinite(point.y));
}
}
TEST(Renderive_Curve_Sampling, ReverseAxisEnvelopeKeepsSourceOrderInsideBucket) {
Axis_Mapping_2D mapping{
Axis_Frame_Snapshot{Orientation::Horizontal, Range{0.0, 4.0}, Range{1.0, 0.0}},
@@ -310,7 +474,7 @@ TEST(Renderive_Curve_Sampling, ReverseAxisEnvelopeKeepsSourceOrderInsideBucket)
}
}
TEST(Renderive_Frame_Feedback, SelectsRenderPaintAndUserSources) {
Frame_Feedback_Controller controller;
Low_Latency_Feedback_Controller controller;
controller.set_max_render_fps(200.0);
for (int i = 0; i < 4; ++i) {
auto render_limited = render_frame(0, 20000000);
@@ -339,6 +503,62 @@ TEST(Renderive_Frame_Feedback, SelectsRenderPaintAndUserSources) {
}
EXPECT_EQ(controller.state().source, Refresh_Limit_Source::User);
}
TEST(Renderive_Frame_Flow, LowLatencyOwnsActiveState) {
Low_Latency_Frame_Flow flow;
Flow_Test_Host host;
host.snapshot.dirty = true;
flow.attach(host);
flow.notify_model_dirty(10);
EXPECT_FALSE(flow.view_active());
EXPECT_EQ(host.render_attempts, 0);
flow.activate_view(20);
EXPECT_TRUE(flow.view_active());
EXPECT_EQ(host.render_attempts, 1);
host.snapshot.render_job_active = false;
flow.deactivate_view();
flow.notify_model_dirty(30);
EXPECT_FALSE(flow.view_active());
EXPECT_EQ(host.render_attempts, 1);
EXPECT_GT(host.deadline_cancels, 0);
Flow_Diagnostics diagnostics = flow.diagnostics();
EXPECT_TRUE(std::holds_alternative<Low_Latency_Diagnostics>(diagnostics.strategy));
EXPECT_FALSE(diagnostics.common.active);
}
TEST(Renderive_Frame_Flow, ExplicitRequestDoesNotNeedActiveView) {
Explicit_Frame_Flow flow;
Flow_Test_Host host;
host.snapshot.dirty = true;
flow.attach(host);
auto ticket = flow.request_render();
flow.notify_model_dirty(10);
EXPECT_TRUE(static_cast<bool>(ticket));
EXPECT_FALSE(flow.view_active());
EXPECT_EQ(host.render_attempts, 1);
Flow_Diagnostics diagnostics = flow.diagnostics();
EXPECT_TRUE(std::holds_alternative<Explicit_Diagnostics>(diagnostics.strategy));
}
TEST(Renderive_Frame_Flow, ExplicitTicketsDrainIntoAcceptedFrame) {
Explicit_Frame_Flow flow;
Flow_Test_Host host;
flow.attach(host);
auto ticket = flow.request_render();
std::pmr::vector<std::shared_ptr<Update_State>> states(memory_resource(Memory_Domain::Update_Completion));
flow.drain_frame_update_states(states);
ASSERT_TRUE(static_cast<bool>(ticket));
ASSERT_EQ(states.size(), 1u);
EXPECT_EQ(ticket.id(), states.front()->id());
complete_update_states(states, Update_Stage::Presented);
EXPECT_TRUE(ticket.is_complete());
}
TEST(Renderive_Render_Executor, FrameAdmissionIsBounded) {
Render_Executor executor(Render_Runtime_Config{1});
std::atomic_bool entered{false};
@@ -575,7 +795,7 @@ TEST(Renderive_Render_Executor, ShutdownCompletesAcceptedQueuedFrameAsCancelled)
shutdown_thread.join();
}
TEST(Renderive_Performance_Shower, ToggleDoesNotReenterRenderableTreeUnsafely) {
Plot_Core plot(std::make_unique<Latency_Eager_Refresh_Strategy>());
Plot_Core plot(std::make_unique<Low_Latency_Frame_Flow>());
plot.init();
auto root = plot.root_renderable();
ASSERT_TRUE(root);
@@ -588,7 +808,7 @@ TEST(Renderive_Performance_Shower, ToggleDoesNotReenterRenderableTreeUnsafely) {
EXPECT_TRUE(root->children_snapshot().empty());
}
TEST(Renderive_Performance_Shower, TogglePreservesAttachedOptions) {
Plot_Core plot(std::make_unique<Latency_Eager_Refresh_Strategy>());
Plot_Core plot(std::make_unique<Low_Latency_Frame_Flow>());
plot.init();
Performance_Overlay_Options options;
options.font.size = 17.0;