闪烁问题解决
This commit is contained in:
@@ -324,6 +324,9 @@ struct Render_Data {
|
||||
virtual Rect pixel_bounds(const Size& plot_size) {
|
||||
return {0, 0, plot_size.width, plot_size.height};
|
||||
}
|
||||
[[nodiscard]] virtual bool requires_frame_view() const {
|
||||
return false;
|
||||
}
|
||||
protected:
|
||||
virtual void copy_render_state_object(Render_State& target, const Render_State& source) {
|
||||
target.version = source.version;
|
||||
|
||||
@@ -188,7 +188,9 @@ void Renderable::append_tree_snapshot(
|
||||
node.cache_parent_node_id = cache_parent_node_id;
|
||||
node.cache_parent_object_name = cache_parent_object_name;
|
||||
node.cache_tree_depth = cache_tree_depth;
|
||||
if (d_ptr && should_prepare())
|
||||
bool should_capture_frame_view = d_ptr && should_prepare();
|
||||
node.frame_view_required = should_capture_frame_view && d_ptr->requires_frame_view();
|
||||
if (should_capture_frame_view)
|
||||
node.frame_view = d_ptr->capture_frame_view();
|
||||
snapshot.push_back(std::move(node));
|
||||
std::string child_cache_parent = cache_parent_object_name;
|
||||
|
||||
@@ -62,4 +62,8 @@ void Strategy_Render_Data::clear_render_input() {
|
||||
runtime->clear_render_input();
|
||||
}
|
||||
|
||||
bool Strategy_Render_Data::requires_frame_view() const {
|
||||
return static_cast<bool>(runtime);
|
||||
}
|
||||
|
||||
} // namespace renderive
|
||||
|
||||
@@ -27,6 +27,7 @@ struct LIB_DECL Strategy_Render_Data : Render_Data {
|
||||
Render_State* state(Renderable_State_Role role) override;
|
||||
Input_Data* input_data(Renderable_Input_Role role) override;
|
||||
void clear_render_input();
|
||||
[[nodiscard]] bool requires_frame_view() const override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<Renderable_Runtime> runtime;
|
||||
|
||||
@@ -84,6 +84,7 @@ Render_Frame_Surface Explicit_Frame_Flow::begin_render_frame() {
|
||||
if (render_job_active.exchange(true, std::memory_order_acq_rel))
|
||||
return {};
|
||||
auto output = std::make_shared<Render_Output>();
|
||||
output->render_complete.store(false, std::memory_order_release);
|
||||
auto handle = std::make_unique<Explicit_Render_Surface_Handle>(output);
|
||||
auto* output_pointer = output.get();
|
||||
std::uint64_t input_version = edit_version.load(std::memory_order_acquire);
|
||||
@@ -107,6 +108,17 @@ Render_Surface_Publish_Result Explicit_Frame_Flow::publish_rendered_frame(Render
|
||||
render_job_active.store(false, std::memory_order_release);
|
||||
return result;
|
||||
}
|
||||
if (!handle->output->render_complete.load(std::memory_order_acquire)
|
||||
|| !handle->output->metadata
|
||||
|| !handle->output->metadata->render_end_ns
|
||||
|| handle->output->image.empty()) {
|
||||
if (handle->output->metadata)
|
||||
handle->output->metadata->outcome = Frame_Outcome::Publish_Dropped;
|
||||
result.dropped_frame_record = handle->output->metadata;
|
||||
supersede_update_states(handle->output->presented_update_states);
|
||||
render_job_active.store(false, std::memory_order_release);
|
||||
return result;
|
||||
}
|
||||
if (handle->output->metadata) {
|
||||
handle->output->metadata->transition_12_ns = now_ns;
|
||||
handle->output->metadata->ready_queue_wait_ns = now_ns > handle->output->metadata->render_end_ns ? now_ns - handle->output->metadata->render_end_ns : 0;
|
||||
|
||||
@@ -162,6 +162,8 @@ Render_Frame_Surface Low_Latency_Frame_Flow::begin_render_frame() {
|
||||
d->pipeline.render_job_state = Frame_Job_State::Running;
|
||||
d->pipeline.rendered_edit_version = d->pipeline.edit_version.load(std::memory_order_acquire);
|
||||
auto* output = handle->lease.frame;
|
||||
output->render_complete.store(false, std::memory_order_release);
|
||||
output->image.clear();
|
||||
std::uint64_t input_version = d->pipeline.rendered_edit_version;
|
||||
return Render_Frame_Surface(output, input_version, std::move(handle));
|
||||
}
|
||||
@@ -288,4 +290,12 @@ void Low_Latency_Frame_Flow::shutdown() {
|
||||
d->host = nullptr;
|
||||
}
|
||||
|
||||
Low_Latency_Frame_Pipeline& Low_Latency_Frame_Flow::pipeline_for_test() {
|
||||
return d->pipeline;
|
||||
}
|
||||
|
||||
const Low_Latency_Frame_Pipeline& Low_Latency_Frame_Flow::pipeline_for_test() const {
|
||||
return d->pipeline;
|
||||
}
|
||||
|
||||
} // namespace renderive
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
|
||||
namespace renderive {
|
||||
|
||||
class Low_Latency_Frame_Pipeline;
|
||||
struct Low_Latency_Frame_Flow_Test_Probe;
|
||||
|
||||
class Low_Latency_Frame_Flow final : public Frame_Flow {
|
||||
public:
|
||||
Low_Latency_Frame_Flow();
|
||||
@@ -45,6 +48,10 @@ public:
|
||||
void shutdown() override;
|
||||
|
||||
private:
|
||||
friend struct Low_Latency_Frame_Flow_Test_Probe;
|
||||
[[nodiscard]] Low_Latency_Frame_Pipeline& pipeline_for_test();
|
||||
[[nodiscard]] const Low_Latency_Frame_Pipeline& pipeline_for_test() const;
|
||||
|
||||
struct Private;
|
||||
std::unique_ptr<Private> d;
|
||||
};
|
||||
|
||||
@@ -8,7 +8,10 @@ namespace renderive {
|
||||
namespace {
|
||||
|
||||
bool has_complete_frame(const Low_Latency_Frame* frame) {
|
||||
return frame && frame->version.load(std::memory_order_acquire) != 0 && !frame->image.empty();
|
||||
return frame && frame->version.load(std::memory_order_acquire) != 0
|
||||
&& frame->render_complete.load(std::memory_order_acquire)
|
||||
&& frame->metadata && frame->metadata->render_end_ns
|
||||
&& !frame->image.empty();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -93,11 +96,20 @@ Low_Latency_Frame_Pipeline::Frame_Publish_Result Low_Latency_Frame_Pipeline::try
|
||||
if (!rendering_lease)
|
||||
return result;
|
||||
Low_Latency_Frame* rendered = rendering_lease.frame;
|
||||
if (!has_complete_frame(rendered)) {
|
||||
if (rendered->metadata)
|
||||
rendered->metadata->outcome = Frame_Outcome::Publish_Dropped;
|
||||
result.dropped_frame_record = rendered->metadata;
|
||||
supersede_update_states(rendered->presented_update_states);
|
||||
++attempt_counters.publish_dropped_count;
|
||||
return result;
|
||||
}
|
||||
|
||||
auto swap = frame_control.try_swap_role_with_left_lease(Frame_Rendering, Frame_Middle, rendering_lease.lease, [this](const Triple_Buffer_View& view) {
|
||||
const Low_Latency_Frame* rendering = frame_by_index(view[Frame_Rendering]);
|
||||
const Low_Latency_Frame* middle = frame_by_index(view[Frame_Middle]);
|
||||
return buffer_version_newer(rendering->version.load(std::memory_order_acquire), middle->version.load(std::memory_order_acquire));
|
||||
return has_complete_frame(rendering)
|
||||
&& buffer_version_newer(rendering->version.load(std::memory_order_acquire), middle->version.load(std::memory_order_acquire));
|
||||
}, [this, &result, now_ns](const Triple_Buffer_View& old_view, const Triple_Buffer_View& new_view) {
|
||||
Low_Latency_Frame* rendered = frame_by_index(old_view[Frame_Rendering]);
|
||||
Low_Latency_Frame* old_middle = frame_by_index(old_view[Frame_Middle]);
|
||||
@@ -127,8 +139,7 @@ Low_Latency_Frame_Pipeline::Frame_Publish_Result Low_Latency_Frame_Pipeline::try
|
||||
}
|
||||
|
||||
rendering_lease.reset();
|
||||
if (has_complete_frame(rendered))
|
||||
has_published_frame.store(true, std::memory_order_release);
|
||||
has_published_frame.store(true, std::memory_order_release);
|
||||
Frame_Publish_Result update_result = request_present(now_ns);
|
||||
update_result.superseded_frame_record = result.superseded_frame_record;
|
||||
update_result.dropped_frame_record = result.dropped_frame_record;
|
||||
@@ -137,25 +148,17 @@ Low_Latency_Frame_Pipeline::Frame_Publish_Result Low_Latency_Frame_Pipeline::try
|
||||
|
||||
Low_Latency_Frame_Pipeline::Frame_Publish_Result Low_Latency_Frame_Pipeline::request_present(std::uint64_t now_ns) {
|
||||
Frame_Publish_Result result;
|
||||
auto record = frame_control.try_acquire_role(Frame_Middle);
|
||||
if (record.result != Triple_Buffer_Result::Success)
|
||||
Triple_Buffer_View view = frame_control.read_view();
|
||||
const Low_Latency_Frame* middle = frame_by_index(view[Frame_Middle]);
|
||||
const Low_Latency_Frame* painting = frame_by_index(view[Frame_Painting]);
|
||||
if (!has_complete_frame(middle)
|
||||
|| !buffer_version_newer(middle->version.load(std::memory_order_acquire), painting->version.load(std::memory_order_acquire)))
|
||||
return result;
|
||||
|
||||
Low_Latency_Frame_Lease middle_lease{frame_by_index(record.lease.index), &frame_control, record.lease};
|
||||
Low_Latency_Frame* middle = middle_lease.frame;
|
||||
const Low_Latency_Frame* painting = frame_by_role(Frame_Painting);
|
||||
if (!buffer_version_newer(middle->version.load(std::memory_order_acquire), painting->version.load(std::memory_order_acquire)))
|
||||
if (paint_request_pending.load(std::memory_order_acquire))
|
||||
return result;
|
||||
|
||||
if (paint_request_pending.load(std::memory_order_acquire)) {
|
||||
middle->metadata->update_request_id = pending_present_request_id.load(std::memory_order_acquire);
|
||||
middle->metadata->update_request_time_ns = pending_present_request_time_ns.load(std::memory_order_acquire);
|
||||
return result;
|
||||
}
|
||||
result.update_request_id = ++next_present_request_id;
|
||||
result.update_request_time = now_ns;
|
||||
middle->metadata->update_request_id = result.update_request_id;
|
||||
middle->metadata->update_request_time_ns = result.update_request_time;
|
||||
pending_present_request_id.store(result.update_request_id, std::memory_order_release);
|
||||
pending_present_request_time_ns.store(result.update_request_time, std::memory_order_release);
|
||||
paint_request_pending.store(true, std::memory_order_release);
|
||||
@@ -167,7 +170,7 @@ Low_Latency_Frame_Pipeline::Frame_Publish_Result Low_Latency_Frame_Pipeline::req
|
||||
|
||||
Low_Latency_Frame_Pipeline::Frame_Consume_Result Low_Latency_Frame_Pipeline::acquire_paint_frame(std::uint64_t now_ns) {
|
||||
Frame_Consume_Result result;
|
||||
bool pending = paint_request_pending.load(std::memory_order_acquire);
|
||||
result.present_request_was_pending = paint_request_pending.exchange(false, std::memory_order_acq_rel);
|
||||
|
||||
for (;;) {
|
||||
bool attempted_middle_swap = false;
|
||||
@@ -189,6 +192,11 @@ Low_Latency_Frame_Pipeline::Frame_Consume_Result Low_Latency_Frame_Pipeline::acq
|
||||
}
|
||||
new_painting->metadata->transition_23_ns = now_ns;
|
||||
new_painting->metadata->present_queue_wait_ns = now_ns > new_painting->metadata->transition_12_ns ? now_ns - new_painting->metadata->transition_12_ns : 0;
|
||||
std::uint64_t request_id = pending_present_request_id.load(std::memory_order_acquire);
|
||||
if (request_id) {
|
||||
new_painting->metadata->update_request_id = request_id;
|
||||
new_painting->metadata->update_request_time_ns = pending_present_request_time_ns.load(std::memory_order_acquire);
|
||||
}
|
||||
});
|
||||
middle_swap_result = swap.result;
|
||||
}
|
||||
@@ -199,8 +207,6 @@ Low_Latency_Frame_Pipeline::Frame_Consume_Result Low_Latency_Frame_Pipeline::acq
|
||||
result.has_valid_frame = has_complete_frame(result.lease.frame);
|
||||
if (result.has_valid_frame)
|
||||
{
|
||||
if (result.has_new_frame && pending)
|
||||
result.present_request_was_pending = paint_request_pending.exchange(false, std::memory_order_acq_rel);
|
||||
return result;
|
||||
}
|
||||
result.lease.reset();
|
||||
@@ -230,7 +236,8 @@ Low_Latency_Frame_Pipeline::Frame_Consume_Result Low_Latency_Frame_Pipeline::acq
|
||||
bool Low_Latency_Frame_Pipeline::middle_frame_ready() const {
|
||||
const Low_Latency_Frame* middle = frame_by_role(Frame_Middle);
|
||||
const Low_Latency_Frame* painting = frame_by_role(Frame_Painting);
|
||||
return buffer_version_newer(middle->version.load(std::memory_order_acquire), painting->version.load(std::memory_order_acquire));
|
||||
return has_complete_frame(middle)
|
||||
&& buffer_version_newer(middle->version.load(std::memory_order_acquire), painting->version.load(std::memory_order_acquire));
|
||||
}
|
||||
|
||||
bool Low_Latency_Frame_Pipeline::painting_frame_in_use() const {
|
||||
|
||||
+29
-6
@@ -50,6 +50,7 @@ public:
|
||||
if (!frame_surface)
|
||||
return false;
|
||||
Render_Output* buffer = frame_surface.output;
|
||||
buffer->render_complete.store(false, std::memory_order_release);
|
||||
cancel_update_states(buffer->presented_update_states);
|
||||
if (flow)
|
||||
flow->drain_frame_update_states(buffer->presented_update_states);
|
||||
@@ -87,6 +88,16 @@ public:
|
||||
previous_frame_time_ns = submit_time;
|
||||
job->renderables = renderable_tree_snapshot();
|
||||
job->render_snapshot.frame_tree = &job->renderables;
|
||||
if (!frame_tree_views_complete(job->renderables)) {
|
||||
frame.outcome = Frame_Outcome::Cancelled;
|
||||
if (flow)
|
||||
flow->cancel_render_frame(std::move(job->frame_surface));
|
||||
supersede_update_states(buffer->presented_update_states);
|
||||
release_frame_views(job);
|
||||
collect_frame(frame_record);
|
||||
notify_render_finished(frame);
|
||||
return false;
|
||||
}
|
||||
auto& scheduler = Global::instance()->render_scheduler();
|
||||
bool submitted = scheduler.try_submit_render_task(Render_Executor_Task{
|
||||
plot_execution_id,
|
||||
@@ -130,10 +141,22 @@ public:
|
||||
frame.worker.rejected_task_count++;
|
||||
if (flow)
|
||||
flow->discard_render_frame(std::move(job->frame_surface));
|
||||
release_frame_views(job);
|
||||
collect_frame(frame_record);
|
||||
notify_render_finished(frame);
|
||||
return false;
|
||||
}
|
||||
static bool frame_tree_views_complete(const Renderable_Frame_Tree& tree) {
|
||||
return std::all_of(tree.begin(), tree.end(), [](const Renderable_Frame_Node& node) {
|
||||
return !node.frame_view_required || static_cast<bool>(node.frame_view);
|
||||
});
|
||||
}
|
||||
static void release_frame_views(const std::shared_ptr<Frame_Render_Job>& job) {
|
||||
if (!job)
|
||||
return;
|
||||
job->render_snapshot.frame_tree = nullptr;
|
||||
job->renderables.clear();
|
||||
}
|
||||
static bool frame_job_valid(const std::shared_ptr<Frame_Render_Job>& job) {
|
||||
return job && job->buffer && job->lifecycle && !job->render_snapshot.destroying();
|
||||
}
|
||||
@@ -174,6 +197,7 @@ public:
|
||||
if (!frame_job_valid(job) || !job->lifecycle->prepare_end_ns)
|
||||
return;
|
||||
Frame_Lifecycle_Record& frame = *job->lifecycle;
|
||||
job->buffer->render_complete.store(false, std::memory_order_release);
|
||||
job->buffer->image.resize(job->render_snapshot.size.width, job->render_snapshot.size.height);
|
||||
job->buffer->image.fill(job->render_snapshot.background_color);
|
||||
Canvas canvas(job->buffer->image);
|
||||
@@ -191,6 +215,7 @@ public:
|
||||
if (update_state)
|
||||
update_state->complete_until(Update_Stage::Rendered);
|
||||
}
|
||||
job->buffer->render_complete.store(true, std::memory_order_release);
|
||||
}
|
||||
void execute_frame_job(const std::shared_ptr<Frame_Render_Job>& job) {
|
||||
if (!job || !job->buffer || !job->lifecycle || job->render_snapshot.destroying())
|
||||
@@ -210,15 +235,18 @@ public:
|
||||
frame.worker = *job->worker_stat;
|
||||
bool valid_generation = job->generation == generation.load(std::memory_order_acquire);
|
||||
bool valid_context = !destroying();
|
||||
if (!valid_generation || !valid_context || !frame.render_end_ns) {
|
||||
bool render_complete = job->buffer->render_complete.load(std::memory_order_acquire);
|
||||
if (!valid_generation || !valid_context || !frame.render_end_ns || !render_complete) {
|
||||
if (flow)
|
||||
flow->cancel_render_frame(std::move(job->frame_surface));
|
||||
frame.outcome = Frame_Outcome::Cancelled;
|
||||
supersede_update_states(job->buffer->presented_update_states);
|
||||
release_frame_views(job);
|
||||
notify_render_finished(frame);
|
||||
return;
|
||||
}
|
||||
auto publish_result = flow ? flow->publish_rendered_frame(std::move(job->frame_surface), steady_now_ns()) : Render_Surface_Publish_Result{};
|
||||
release_frame_views(job);
|
||||
if (publish_result.superseded_frame_record) {
|
||||
notify_frame_returned(publish_result.superseded_frame_record);
|
||||
}
|
||||
@@ -574,11 +602,6 @@ Present_Surface_Lease Plot_Core::begin_present() {
|
||||
data->notify_frame_returned(frame);
|
||||
});
|
||||
}
|
||||
if (!result.new_frame() && result.request_was_pending()) {
|
||||
d->post_scheduler_action([data = d]() {
|
||||
data->notify_paint_finished();
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
void Plot_Core::end_present(Present_Surface_Lease&& frame, std::uint64_t paint_begin_ns, std::uint64_t paint_end_ns) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "../architecture/Frame_Scheduler.h"
|
||||
#include "../architecture/Update_Completion.h"
|
||||
#include "Image.h"
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <memory_resource>
|
||||
|
||||
@@ -10,6 +11,7 @@ namespace renderive {
|
||||
struct Render_Output {
|
||||
Render_Output();
|
||||
Image image;
|
||||
std::atomic_bool render_complete{false};
|
||||
std::pmr::vector<std::shared_ptr<Update_State>> presented_update_states;
|
||||
std::shared_ptr<Frame_Lifecycle_Record> metadata;
|
||||
};
|
||||
|
||||
@@ -29,6 +29,7 @@ struct Renderable_Frame_Node {
|
||||
Renderable_Id cache_parent_node_id{};
|
||||
std::string cache_parent_object_name;
|
||||
int cache_tree_depth{};
|
||||
bool frame_view_required{};
|
||||
Renderable_Frame_View frame_view;
|
||||
};
|
||||
|
||||
|
||||
@@ -57,6 +57,12 @@ struct Low_Latency_Frame_Pipeline_Test_Probe {
|
||||
}
|
||||
};
|
||||
|
||||
struct Low_Latency_Frame_Flow_Test_Probe {
|
||||
static Low_Latency_Frame_Pipeline& pipeline(Low_Latency_Frame_Flow& flow) {
|
||||
return flow.pipeline_for_test();
|
||||
}
|
||||
};
|
||||
|
||||
namespace {
|
||||
bool unique_roles(const Triple_Buffer_View& view) {
|
||||
std::array<bool, 3> seen{};
|
||||
@@ -102,9 +108,24 @@ Low_Latency_Frame_Pipeline::Frame_Publish_Result publish_pipeline_frame(Low_Late
|
||||
lease.frame->metadata->render_end_ns = now_ns >= 5 ? now_ns - 5 : now_ns;
|
||||
lease.frame->image.resize(2, 2);
|
||||
lease.frame->image.fill(Color::black());
|
||||
lease.frame->render_complete.store(true, std::memory_order_release);
|
||||
lease.frame->version.store(frame_id, std::memory_order_release);
|
||||
return pipeline.try_publish_rendered_frame(std::move(lease), now_ns);
|
||||
}
|
||||
Render_Surface_Publish_Result publish_low_latency_flow_frame(Low_Latency_Frame_Flow& flow, std::uint64_t frame_id, std::uint64_t now_ns, int width = 2, int height = 2, Color color = Color::black()) {
|
||||
auto surface = flow.begin_render_frame();
|
||||
EXPECT_TRUE(static_cast<bool>(surface));
|
||||
if (!surface)
|
||||
return {};
|
||||
EXPECT_TRUE(surface.output && surface.output->metadata);
|
||||
surface.output->metadata->frame_id = frame_id;
|
||||
surface.output->metadata->render_begin_ns = now_ns >= 10 ? now_ns - 10 : now_ns;
|
||||
surface.output->metadata->render_end_ns = now_ns >= 5 ? now_ns - 5 : now_ns;
|
||||
surface.output->image.resize(width, height);
|
||||
surface.output->image.fill(color);
|
||||
surface.output->render_complete.store(true, std::memory_order_release);
|
||||
return flow.publish_rendered_frame(std::move(surface), now_ns);
|
||||
}
|
||||
Render_Surface_Publish_Result publish_explicit_frame(Explicit_Frame_Flow& flow, std::uint64_t frame_id, std::uint64_t now_ns, int width = 2, int height = 2, Color color = Color::green()) {
|
||||
auto surface = flow.begin_render_frame();
|
||||
EXPECT_TRUE(static_cast<bool>(surface));
|
||||
@@ -116,6 +137,7 @@ Render_Surface_Publish_Result publish_explicit_frame(Explicit_Frame_Flow& flow,
|
||||
surface.output->metadata->render_end_ns = now_ns >= 5 ? now_ns - 5 : now_ns;
|
||||
surface.output->image.resize(width, height);
|
||||
surface.output->image.fill(color);
|
||||
surface.output->render_complete.store(true, std::memory_order_release);
|
||||
return flow.publish_rendered_frame(std::move(surface), now_ns);
|
||||
}
|
||||
Frame_Lifecycle_Record feedback_frame(std::uint64_t frame_id, std::uint64_t base_ns, std::uint64_t render_ns, std::uint64_t paint_ns, std::uint64_t ready_wait_ns = 0, std::uint64_t gui_wait_ns = 0) {
|
||||
@@ -232,6 +254,16 @@ struct Runtime_Count_Owner : Renderable {
|
||||
}
|
||||
};
|
||||
struct Runtime_Count_Data : Typed_Render_Data<Runtime_Count_Owner, Lease_Test_State, Lease_Test_Input> {};
|
||||
struct Frame_View_Blocking_Renderable;
|
||||
struct Frame_View_Blocking_Data : Typed_Render_Data<Frame_View_Blocking_Renderable, Lease_Test_State, Lease_Test_Input> {};
|
||||
struct Frame_View_Blocking_Renderable : Renderable {
|
||||
Frame_View_Blocking_Renderable();
|
||||
Frame_View_Blocking_Data* d();
|
||||
Render_Object_Owner create_render_data() override;
|
||||
Render_Object_Owner create_render_state() override;
|
||||
Render_Object_Owner create_input_data() override;
|
||||
};
|
||||
RENDERIVE_DEFINE_RENDERABLE_BINDING(Frame_View_Blocking_Renderable, Frame_View_Blocking_Data, Lease_Test_State, Lease_Test_Input, "Frame_View_Blocking")
|
||||
struct Counting_Test_Input : Input_Data {
|
||||
int clear_count{};
|
||||
void clear() override {
|
||||
@@ -281,6 +313,28 @@ struct Flow_Test_Host : Frame_Flow_Host {
|
||||
++deadline_cancels;
|
||||
}
|
||||
};
|
||||
|
||||
struct Low_Latency_Present_Test_Host : Frame_Flow_Host {
|
||||
Low_Latency_Frame_Flow* flow{};
|
||||
std::uint64_t request_now_ns{};
|
||||
int paint_requests{};
|
||||
Render_Surface_Publish_Result last_present_request;
|
||||
|
||||
Frame_Flow_Runtime_Snapshot runtime_snapshot(std::uint64_t now_ns) const override {
|
||||
return flow ? flow->runtime_snapshot(now_ns, false) : Frame_Flow_Runtime_Snapshot{false, false, false, false, false, now_ns};
|
||||
}
|
||||
bool try_render_once() override {
|
||||
return false;
|
||||
}
|
||||
void request_paint_from_middle() override {
|
||||
++paint_requests;
|
||||
if (flow)
|
||||
last_present_request = flow->request_present(request_now_ns);
|
||||
}
|
||||
void schedule_render_deadline(std::uint64_t) override {}
|
||||
void cancel_render_deadline() override {}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
TEST(Renderive_Canvas, TextUsesPenAndFillUsesBrush) {
|
||||
auto diagnostics = Canvas::default_font_diagnostics();
|
||||
@@ -758,11 +812,140 @@ TEST(Renderive_Frame_Pipeline, PresentDuringMiddleBusyRepeatsPainting) {
|
||||
EXPECT_FALSE(repeated.has_new_frame);
|
||||
EXPECT_TRUE(repeated.has_valid_frame);
|
||||
EXPECT_FALSE(repeated.underrun);
|
||||
EXPECT_TRUE(Low_Latency_Frame_Pipeline_Test_Probe::paint_request_pending(pipeline));
|
||||
EXPECT_TRUE(repeated.present_request_was_pending);
|
||||
EXPECT_FALSE(Low_Latency_Frame_Pipeline_Test_Probe::paint_request_pending(pipeline));
|
||||
repeated.lease.reset();
|
||||
Low_Latency_Frame_Pipeline_Test_Probe::release_frame_role(pipeline, busy_middle.lease);
|
||||
}
|
||||
|
||||
TEST(Renderive_Frame_Pipeline, MiddleBusyPaintAcknowledgesPostedUpdate) {
|
||||
Low_Latency_Frame_Pipeline pipeline;
|
||||
ASSERT_TRUE(publish_pipeline_frame(pipeline, 1, 25).request_present);
|
||||
auto first = pipeline.acquire_paint_frame(30);
|
||||
ASSERT_TRUE(static_cast<bool>(first.lease));
|
||||
first.lease.reset();
|
||||
ASSERT_FALSE(Low_Latency_Frame_Pipeline_Test_Probe::paint_request_pending(pipeline));
|
||||
|
||||
ASSERT_TRUE(publish_pipeline_frame(pipeline, 2, 40).request_present);
|
||||
ASSERT_TRUE(Low_Latency_Frame_Pipeline_Test_Probe::paint_request_pending(pipeline));
|
||||
|
||||
auto busy_middle = Low_Latency_Frame_Pipeline_Test_Probe::acquire_frame_role(pipeline, Frame_Middle);
|
||||
ASSERT_EQ(busy_middle.result, Triple_Buffer_Result::Success);
|
||||
auto repeated = pipeline.acquire_paint_frame(45);
|
||||
|
||||
EXPECT_TRUE(static_cast<bool>(repeated.lease));
|
||||
EXPECT_FALSE(repeated.has_new_frame);
|
||||
EXPECT_TRUE(repeated.has_valid_frame);
|
||||
EXPECT_TRUE(repeated.present_request_was_pending);
|
||||
EXPECT_FALSE(Low_Latency_Frame_Pipeline_Test_Probe::paint_request_pending(pipeline));
|
||||
|
||||
repeated.lease.reset();
|
||||
Low_Latency_Frame_Pipeline_Test_Probe::release_frame_role(pipeline, busy_middle.lease);
|
||||
}
|
||||
|
||||
TEST(Renderive_Frame_Pipeline, MiddleBusyPaintRepostsAfterPaintFinished) {
|
||||
Low_Latency_Frame_Flow flow;
|
||||
Low_Latency_Present_Test_Host host;
|
||||
host.flow = &flow;
|
||||
flow.attach(host);
|
||||
flow.activate_view(20);
|
||||
ASSERT_TRUE(publish_low_latency_flow_frame(flow, 1, 25).request_present);
|
||||
std::shared_ptr<Frame_Lifecycle_Record> returned;
|
||||
auto first = flow.begin_present(30, returned);
|
||||
ASSERT_TRUE(static_cast<bool>(first));
|
||||
EXPECT_TRUE(first.new_frame());
|
||||
(void)flow.end_present(std::move(first), 31, 32);
|
||||
|
||||
ASSERT_TRUE(publish_low_latency_flow_frame(flow, 2, 40).request_present);
|
||||
Low_Latency_Frame_Pipeline& pipeline = Low_Latency_Frame_Flow_Test_Probe::pipeline(flow);
|
||||
auto busy_middle = Low_Latency_Frame_Pipeline_Test_Probe::acquire_frame_role(pipeline, Frame_Middle);
|
||||
ASSERT_EQ(busy_middle.result, Triple_Buffer_Result::Success);
|
||||
auto repeated = flow.begin_present(45, returned);
|
||||
ASSERT_TRUE(static_cast<bool>(repeated));
|
||||
EXPECT_FALSE(repeated.new_frame());
|
||||
EXPECT_TRUE(repeated.request_was_pending());
|
||||
(void)flow.end_present(std::move(repeated), 46, 47);
|
||||
|
||||
host.request_now_ns = 50;
|
||||
flow.notify_paint_finished(50);
|
||||
EXPECT_EQ(host.paint_requests, 1);
|
||||
EXPECT_TRUE(host.last_present_request.request_present);
|
||||
EXPECT_TRUE(Low_Latency_Frame_Pipeline_Test_Probe::paint_request_pending(pipeline));
|
||||
Low_Latency_Frame_Pipeline_Test_Probe::release_frame_role(pipeline, busy_middle.lease);
|
||||
|
||||
auto recovered = flow.begin_present(55, returned);
|
||||
EXPECT_TRUE(static_cast<bool>(recovered));
|
||||
EXPECT_TRUE(recovered.new_frame());
|
||||
EXPECT_TRUE(recovered.request_was_pending());
|
||||
EXPECT_FALSE(Low_Latency_Frame_Pipeline_Test_Probe::paint_request_pending(pipeline));
|
||||
}
|
||||
|
||||
TEST(Renderive_Frame_Pipeline, LostPresentWakeupDoesNotFreezeFlow) {
|
||||
Low_Latency_Frame_Flow flow;
|
||||
Low_Latency_Present_Test_Host host;
|
||||
host.flow = &flow;
|
||||
flow.attach(host);
|
||||
flow.activate_view(20);
|
||||
ASSERT_TRUE(publish_low_latency_flow_frame(flow, 1, 25).request_present);
|
||||
std::shared_ptr<Frame_Lifecycle_Record> returned;
|
||||
auto first = flow.begin_present(30, returned);
|
||||
ASSERT_TRUE(static_cast<bool>(first));
|
||||
(void)flow.end_present(std::move(first), 31, 32);
|
||||
|
||||
ASSERT_TRUE(publish_low_latency_flow_frame(flow, 2, 40).request_present);
|
||||
Low_Latency_Frame_Pipeline& pipeline = Low_Latency_Frame_Flow_Test_Probe::pipeline(flow);
|
||||
auto busy_middle = Low_Latency_Frame_Pipeline_Test_Probe::acquire_frame_role(pipeline, Frame_Middle);
|
||||
ASSERT_EQ(busy_middle.result, Triple_Buffer_Result::Success);
|
||||
auto repeated = flow.begin_present(45, returned);
|
||||
ASSERT_TRUE(static_cast<bool>(repeated));
|
||||
EXPECT_FALSE(repeated.new_frame());
|
||||
EXPECT_TRUE(repeated.request_was_pending());
|
||||
EXPECT_FALSE(Low_Latency_Frame_Pipeline_Test_Probe::paint_request_pending(pipeline));
|
||||
(void)flow.end_present(std::move(repeated), 46, 47);
|
||||
|
||||
EXPECT_TRUE(pipeline.middle_frame_ready());
|
||||
host.request_now_ns = 50;
|
||||
flow.notify_paint_finished(50);
|
||||
ASSERT_TRUE(host.last_present_request.request_present);
|
||||
Low_Latency_Frame_Pipeline_Test_Probe::release_frame_role(pipeline, busy_middle.lease);
|
||||
|
||||
auto consumed = flow.begin_present(55, returned);
|
||||
ASSERT_TRUE(static_cast<bool>(consumed));
|
||||
EXPECT_TRUE(consumed.new_frame());
|
||||
EXPECT_TRUE(consumed.request_was_pending());
|
||||
EXPECT_FALSE(pipeline.middle_frame_ready());
|
||||
}
|
||||
|
||||
TEST(Renderive_Frame_Pipeline, HoverUpdatesDoNotRequireExternalRepaint) {
|
||||
Low_Latency_Frame_Pipeline pipeline;
|
||||
ASSERT_TRUE(publish_pipeline_frame(pipeline, 1, 25).request_present);
|
||||
auto first = pipeline.acquire_paint_frame(30);
|
||||
ASSERT_TRUE(static_cast<bool>(first.lease));
|
||||
first.lease.reset();
|
||||
|
||||
ASSERT_TRUE(publish_pipeline_frame(pipeline, 2, 40).request_present);
|
||||
auto busy_middle = Low_Latency_Frame_Pipeline_Test_Probe::acquire_frame_role(pipeline, Frame_Middle);
|
||||
ASSERT_EQ(busy_middle.result, Triple_Buffer_Result::Success);
|
||||
auto repeated = pipeline.acquire_paint_frame(45);
|
||||
ASSERT_TRUE(static_cast<bool>(repeated.lease));
|
||||
EXPECT_TRUE(repeated.present_request_was_pending);
|
||||
EXPECT_FALSE(Low_Latency_Frame_Pipeline_Test_Probe::paint_request_pending(pipeline));
|
||||
repeated.lease.reset();
|
||||
Low_Latency_Frame_Pipeline_Test_Probe::release_frame_role(pipeline, busy_middle.lease);
|
||||
|
||||
auto hover_publish = publish_pipeline_frame(pipeline, 3, 60);
|
||||
ASSERT_TRUE(hover_publish.request_present);
|
||||
EXPECT_TRUE(Low_Latency_Frame_Pipeline_Test_Probe::paint_request_pending(pipeline));
|
||||
|
||||
auto consumed = pipeline.acquire_paint_frame(65);
|
||||
ASSERT_TRUE(static_cast<bool>(consumed.lease));
|
||||
EXPECT_TRUE(consumed.has_new_frame);
|
||||
EXPECT_TRUE(consumed.has_valid_frame);
|
||||
EXPECT_TRUE(consumed.present_request_was_pending);
|
||||
EXPECT_EQ(consumed.lease.frame->version.load(std::memory_order_acquire), 3u);
|
||||
EXPECT_FALSE(Low_Latency_Frame_Pipeline_Test_Probe::paint_request_pending(pipeline));
|
||||
}
|
||||
|
||||
TEST(Renderive_Frame_Pipeline, PaintingLeaseBlocksUntilAvailable) {
|
||||
Low_Latency_Frame_Pipeline pipeline;
|
||||
ASSERT_TRUE(publish_pipeline_frame(pipeline, 1, 25).request_present);
|
||||
@@ -902,6 +1085,35 @@ TEST(Renderive_Frame_Pipeline, PublishFailureKeepsPreviousPainting) {
|
||||
EXPECT_FALSE(repeated.lease.frame->image.empty());
|
||||
}
|
||||
|
||||
TEST(Renderive_Frame_Pipeline, IncompleteFrameDoesNotReplacePainting) {
|
||||
Low_Latency_Frame_Pipeline pipeline;
|
||||
ASSERT_TRUE(publish_pipeline_frame(pipeline, 1, 25).request_present);
|
||||
auto first = pipeline.acquire_paint_frame(30);
|
||||
ASSERT_TRUE(static_cast<bool>(first.lease));
|
||||
Low_Latency_Frame* first_frame = first.lease.frame;
|
||||
auto first_version = first_frame->version.load(std::memory_order_acquire);
|
||||
first.lease.reset();
|
||||
|
||||
auto render_lease = pipeline.try_acquire_render_frame();
|
||||
ASSERT_TRUE(static_cast<bool>(render_lease));
|
||||
render_lease.frame->metadata->frame_id = 2;
|
||||
render_lease.frame->metadata->render_begin_ns = 35;
|
||||
render_lease.frame->metadata->render_end_ns = 38;
|
||||
render_lease.frame->image.resize(2, 2);
|
||||
render_lease.frame->image.fill(Color::red());
|
||||
render_lease.frame->render_complete.store(false, std::memory_order_release);
|
||||
render_lease.frame->version.store(2, std::memory_order_release);
|
||||
auto failed_publish = pipeline.try_publish_rendered_frame(std::move(render_lease), 40);
|
||||
EXPECT_FALSE(failed_publish.request_present);
|
||||
EXPECT_NE(failed_publish.dropped_frame_record, nullptr);
|
||||
|
||||
auto repeated = pipeline.acquire_paint_frame(45);
|
||||
ASSERT_TRUE(static_cast<bool>(repeated.lease));
|
||||
EXPECT_FALSE(repeated.has_new_frame);
|
||||
EXPECT_EQ(repeated.lease.frame, first_frame);
|
||||
EXPECT_EQ(repeated.lease.frame->version.load(std::memory_order_acquire), first_version);
|
||||
}
|
||||
|
||||
TEST(Renderive_Frame_Pipeline, RepeatedPresentNeverReturnsEmpty) {
|
||||
Low_Latency_Frame_Pipeline pipeline;
|
||||
ASSERT_TRUE(publish_pipeline_frame(pipeline, 1, 25).request_present);
|
||||
@@ -919,17 +1131,24 @@ TEST(Renderive_Frame_Pipeline, RepeatedPresentNeverReturnsEmpty) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Renderive_Frame_Pipeline, RequestPresentDoesNotWriteBusyMiddle) {
|
||||
TEST(Renderive_Frame_Pipeline, RequestPresentDoesNotLeaseBusyMiddle) {
|
||||
Low_Latency_Frame_Pipeline pipeline;
|
||||
auto published = publish_pipeline_frame(pipeline, 1, 25);
|
||||
ASSERT_TRUE(published.request_present);
|
||||
Low_Latency_Frame_Pipeline_Test_Probe::clear_paint_request(pipeline);
|
||||
Low_Latency_Frame* middle = pipeline.frame_by_role(Frame_Middle);
|
||||
ASSERT_TRUE(middle);
|
||||
ASSERT_TRUE(middle->metadata);
|
||||
middle->metadata->update_request_id = 0;
|
||||
middle->metadata->update_request_time_ns = 0;
|
||||
|
||||
auto busy_middle = Low_Latency_Frame_Pipeline_Test_Probe::acquire_frame_role(pipeline, Frame_Middle);
|
||||
ASSERT_EQ(busy_middle.result, Triple_Buffer_Result::Success);
|
||||
auto blocked = pipeline.request_present(40);
|
||||
EXPECT_FALSE(blocked.request_present);
|
||||
EXPECT_FALSE(Low_Latency_Frame_Pipeline_Test_Probe::paint_request_pending(pipeline));
|
||||
auto posted = pipeline.request_present(40);
|
||||
EXPECT_TRUE(posted.request_present);
|
||||
EXPECT_TRUE(Low_Latency_Frame_Pipeline_Test_Probe::paint_request_pending(pipeline));
|
||||
EXPECT_EQ(middle->metadata->update_request_id, 0u);
|
||||
EXPECT_EQ(middle->metadata->update_request_time_ns, 0u);
|
||||
Low_Latency_Frame_Pipeline_Test_Probe::release_frame_role(pipeline, busy_middle.lease);
|
||||
}
|
||||
TEST(Renderive_Render_Model, NeutralSnapshotAndOutputTypesAreWired) {
|
||||
@@ -1197,6 +1416,33 @@ TEST(Renderive_Frame_Flow, ExplicitTicketsDrainIntoAcceptedFrame) {
|
||||
EXPECT_TRUE(ticket.is_complete());
|
||||
}
|
||||
|
||||
TEST(Renderive_Plot_Core, FrameViewCaptureFailureMarksTreeIncomplete) {
|
||||
Plot_Core plot(std::make_unique<Low_Latency_Frame_Flow>());
|
||||
plot.init();
|
||||
plot.set_viewport_size(Size{16, 16});
|
||||
auto root = plot.root_renderable();
|
||||
ASSERT_TRUE(root);
|
||||
auto blocker = std::make_shared<Frame_View_Blocking_Renderable>();
|
||||
blocker->init(root);
|
||||
auto held_view = blocker->d()->capture_frame_view();
|
||||
ASSERT_TRUE(static_cast<bool>(held_view));
|
||||
auto blocked_view = blocker->d()->capture_frame_view();
|
||||
ASSERT_FALSE(static_cast<bool>(blocked_view));
|
||||
|
||||
Renderable_Frame_Tree blocked_tree;
|
||||
root->append_tree_snapshot(blocked_tree);
|
||||
EXPECT_TRUE(std::any_of(blocked_tree.begin(), blocked_tree.end(), [](const Renderable_Frame_Node& node) {
|
||||
return node.frame_view_required && !node.frame_view;
|
||||
}));
|
||||
|
||||
held_view = {};
|
||||
Renderable_Frame_Tree recovered_tree;
|
||||
root->append_tree_snapshot(recovered_tree);
|
||||
EXPECT_FALSE(std::any_of(recovered_tree.begin(), recovered_tree.end(), [](const Renderable_Frame_Node& node) {
|
||||
return node.frame_view_required && !node.frame_view;
|
||||
}));
|
||||
}
|
||||
|
||||
TEST(Renderive_Explicit_Present, BeforeFirstRenderReturnsEmpty) {
|
||||
Explicit_Frame_Flow flow;
|
||||
std::shared_ptr<Frame_Lifecycle_Record> returned;
|
||||
@@ -1266,6 +1512,35 @@ TEST(Renderive_Explicit_Present, FailedRenderKeepsPreviousOutput) {
|
||||
(void)flow.end_present(std::move(repeated), 41, 42);
|
||||
}
|
||||
|
||||
TEST(Renderive_Explicit_Present, IncompleteFrameDoesNotReplaceOutput) {
|
||||
Explicit_Frame_Flow flow;
|
||||
ASSERT_TRUE(publish_explicit_frame(flow, 1, 25).request_present);
|
||||
std::shared_ptr<Frame_Lifecycle_Record> returned;
|
||||
auto first = flow.begin_present(30, returned);
|
||||
ASSERT_TRUE(static_cast<bool>(first));
|
||||
Render_Output* first_output = Present_Surface_Lease_Access::output(first);
|
||||
(void)flow.end_present(std::move(first), 31, 32);
|
||||
|
||||
auto incomplete = flow.begin_render_frame();
|
||||
ASSERT_TRUE(static_cast<bool>(incomplete));
|
||||
incomplete.output->metadata->frame_id = 2;
|
||||
incomplete.output->metadata->render_begin_ns = 35;
|
||||
incomplete.output->metadata->render_end_ns = 38;
|
||||
incomplete.output->image.resize(3, 3);
|
||||
incomplete.output->image.fill(Color::red());
|
||||
incomplete.output->render_complete.store(false, std::memory_order_release);
|
||||
auto failed_publish = flow.publish_rendered_frame(std::move(incomplete), 40);
|
||||
EXPECT_FALSE(failed_publish.request_present);
|
||||
EXPECT_NE(failed_publish.dropped_frame_record, nullptr);
|
||||
|
||||
auto repeated = flow.begin_present(45, returned);
|
||||
ASSERT_TRUE(static_cast<bool>(repeated));
|
||||
EXPECT_EQ(Present_Surface_Lease_Access::output(repeated), first_output);
|
||||
EXPECT_FALSE(repeated.new_frame());
|
||||
EXPECT_FALSE(repeated.image().empty());
|
||||
(void)flow.end_present(std::move(repeated), 46, 47);
|
||||
}
|
||||
|
||||
TEST(Renderive_Explicit_Present, NewOutputAtomicallyReplacesPreviousOutput) {
|
||||
Explicit_Frame_Flow flow;
|
||||
ASSERT_TRUE(publish_explicit_frame(flow, 1, 25).request_present);
|
||||
|
||||
Reference in New Issue
Block a user