策略抽离
This commit is contained in:
+230
-10
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user