diff --git a/Core/plottable/Hover_Info.h b/Core/plottable/Hover_Info.h index a94fe44..00ae6d8 100644 --- a/Core/plottable/Hover_Info.h +++ b/Core/plottable/Hover_Info.h @@ -16,6 +16,7 @@ struct LIB_DECL Hover_Info_Render_State { virtual ~Hover_Info_Render_State() = default; Hover_Info_Render_State() { hover_info_brush = Brush{Color::white(), Brush_Style::Solid}; + hover_info_pen = Pen{Color::black()}; } int hover_info_left = 4; int hover_info_top = 4; diff --git a/Core/plottable/Performance_Shower.cpp b/Core/plottable/Performance_Shower.cpp index 56d627f..ee9edf4 100644 --- a/Core/plottable/Performance_Shower.cpp +++ b/Core/plottable/Performance_Shower.cpp @@ -233,6 +233,18 @@ static std::function performance_update_callback(const std::shared_ptr

lock(ctx->performance_update_callback_mutex); return ctx->performance_update_callback; } + +static void request_performance_update(const std::shared_ptr& worker) { + if (!worker) + return; + std::function callback; + { + std::lock_guard lock(worker->update_callback_mutex); + callback = worker->update_callback; + } + if (callback) + callback(); +} } void schedule_performance_metrics_worker(const std::shared_ptr& state) { @@ -257,18 +269,36 @@ Performance_Shower_Private* Performance_Shower::d() { return private_data.get(); } -std::shared_ptr attach_performance_shower(Plot_Core& plot) { +static std::shared_ptr attach_performance_shower_impl(Plot_Core& plot, const Performance_Shower_Options* options) { auto ctx = plot.render_context(); if (!ctx) return {}; auto shower = performance_shower_owner(ctx); - if (shower) + if (shower) { + if (options) + shower->set_options(*options); return shower; + } auto created_shower = renderive::make_shared(); + if (options) + created_shower->set_options(*options); created_shower->set_update_callback(performance_update_callback(ctx)); std::atomic_store_explicit(&ctx->frame_lifecycle_observer, std::static_pointer_cast(created_shower), std::memory_order_release); return created_shower; } + +std::shared_ptr attach_performance_shower(Plot_Core& plot) { + return attach_performance_shower_impl(plot, nullptr); +} + +std::shared_ptr attach_performance_shower(Plot_Core& plot, const Performance_Shower_Options& options) { + return attach_performance_shower_impl(plot, &options); +} + +std::shared_ptr performance_shower(Plot_Core& plot) { + return performance_shower_owner(plot.render_context()); +} + void detach_performance_shower(Plot_Core& plot) { auto ctx = plot.render_context(); if (!ctx) @@ -284,7 +314,9 @@ bool performance_shower_enabled(const Plot_Core& plot) { } void set_performance_shower_enabled(Plot_Core& plot, bool enabled) { if (!enabled) { - detach_performance_shower(plot); + auto shower = performance_shower_owner(plot.render_context()); + if (shower) + shower->setEnabled(false); return; } auto shower = attach_performance_shower(plot); @@ -335,12 +367,17 @@ bool performance_shower_handle_pointer_release(Plot_Core& plot, PointF position) return shower && shower->enabled() && shower->handle_pointer_release(position); } void Performance_Shower::setEnabled(bool enabled) { + auto worker = d()->metrics_worker; d()->enabled_value.store(enabled, std::memory_order_release); - if (enabled) { - d()->metrics_worker->reset_requested.store(true, std::memory_order_release); - d()->metrics_worker->snapshot_rebuild_requested.store(true, std::memory_order_release); - schedule_performance_metrics_worker(d()->metrics_worker); + worker->enabled.store(enabled, std::memory_order_release); + if (!enabled) { + worker->display_snapshot_slot.store({}, std::memory_order_release); + request_performance_update(worker); + return; } + worker->reset_requested.store(true, std::memory_order_release); + worker->snapshot_rebuild_requested.store(true, std::memory_order_release); + schedule_performance_metrics_worker(worker); } bool Performance_Shower::enabled() const { return const_cast(this)->d()->enabled_value.load(std::memory_order_acquire); @@ -348,6 +385,36 @@ bool Performance_Shower::enabled() const { std::shared_ptr Performance_Shower::display_snapshot() const { return const_cast(this)->d()->metrics_worker->display_snapshot_slot.load(std::memory_order_acquire); } +void Performance_Shower::set_options(Performance_Shower_Options options) { + auto worker = d()->metrics_worker; + { + std::lock_guard lock(worker->options_mutex); + worker->options = std::move(options); + } + worker->snapshot_rebuild_requested.store(true, std::memory_order_release); + if (enabled()) + schedule_performance_metrics_worker(worker); +} +Performance_Shower_Options Performance_Shower::options() const { + auto worker = const_cast(this)->d()->metrics_worker; + std::lock_guard lock(worker->options_mutex); + return worker->options; +} +void Performance_Shower::set_font(Font font) { + Performance_Shower_Options value = options(); + value.font = std::move(font); + set_options(std::move(value)); +} +void Performance_Shower::set_foreground(Color color) { + Performance_Shower_Options value = options(); + value.foreground = color; + set_options(std::move(value)); +} +void Performance_Shower::set_background(Color color) { + Performance_Shower_Options value = options(); + value.background = color; + set_options(std::move(value)); +} void Performance_Shower::collect_frame(std::shared_ptr frame) { if (!enabled()) return; diff --git a/Core/plottable/Performance_Shower.h b/Core/plottable/Performance_Shower.h new file mode 100644 index 0000000..4c5e8d2 --- /dev/null +++ b/Core/plottable/Performance_Shower.h @@ -0,0 +1,58 @@ +#pragma once +#include +#include +#include +#include "../architecture/Frame_Scheduler.h" +#include "../base/Color.h" +#include "../base/Geometry.h" +#include "../base/Text.h" +#include "global.h" +#include "Performance_Snapshot.h" + +namespace renderive { + +struct Performance_Shower_Private; + +struct Performance_Shower_Options { + Font font; + int top_margin = 4; + int left_margin = 6; + int right_margin = 12; + int bottom_margin = 6; + int scroll_bar_width = 8; + int scroll_bar_margin = 2; + Color background = Color::white(); + Color foreground = Color::black(); + Color scroll_track = Color{220, 220, 220}; + Color scroll_thumb = Color{80, 80, 80}; +}; + +class LIB_DECL Performance_Shower : public Frame_Lifecycle_Observer { +public: + Performance_Shower(); + ~Performance_Shower(); + + void setEnabled(bool enabled); + bool enabled() const override; + void collect_frame(std::shared_ptr frame) override; + [[nodiscard]] std::shared_ptr display_snapshot() const; + + void set_options(Performance_Shower_Options options); + [[nodiscard]] Performance_Shower_Options options() const; + void set_font(Font font); + void set_foreground(Color color); + void set_background(Color color); + + void set_viewport(Size viewport, std::uint64_t version); + bool handle_wheel(PointF position, double angle_delta_y, double pixel_delta_y); + bool handle_pointer_press(PointF position); + bool handle_pointer_move(PointF position); + bool handle_pointer_release(PointF position); + void set_update_callback(std::function callback); + +private: + Performance_Shower_Private* d(); + std::unique_ptr private_data; +}; + +} // namespace renderive diff --git a/Core/plottable/Performance_Shower_p.h b/Core/plottable/Performance_Shower_p.h index 0fcad44..9076536 100644 --- a/Core/plottable/Performance_Shower_p.h +++ b/Core/plottable/Performance_Shower_p.h @@ -24,23 +24,11 @@ #include "../base/String_Format.h" #include "../base/Text.h" #include "../render/Canvas.h" -#include "Performance_Snapshot.h" +#include "Performance_Shower.h" #include "Psc_Cpp_Core/Statistics/Statistics.h" namespace renderive { void write_frame_performance_log(std::shared_ptr frame); -struct Performance_Shower_Render_State : Render_State { - Font font; - int top_margin = 4; - int left_margin = 6; - int right_margin = 12; - int bottom_margin = 6; - int scroll_bar_width = 8; - int scroll_bar_margin = 2; - Color background = Color::white(); - Color foreground = Color::black(); - Color scroll_track = Color{220, 220, 220}; - Color scroll_thumb = Color{80, 80, 80}; -}; +struct Performance_Shower_Render_State : Render_State, Performance_Shower_Options {}; struct Performance_Shower_Input_Data : Input_Data {}; struct Renderable_Cache_Aggregate_Stat { std::string object_name; @@ -267,6 +255,7 @@ enum class Performance_Field_Id : std::uint16_t { Frame_Peak_Parallelism, Frame_Wait_Max, Frame_Run_Total, + Outcome, Cache_Hit, Cache_Miss, Cache_Ratio, @@ -316,26 +305,6 @@ struct Performance_Render_Line { double width{}; }; -struct Performance_Shower_Private; -class Performance_Shower : public Frame_Lifecycle_Observer { -public: - Performance_Shower(); - ~Performance_Shower(); - void setEnabled(bool enabled); - bool enabled() const override; - void collect_frame(std::shared_ptr frame) override; - [[nodiscard]] std::shared_ptr display_snapshot() const; - void set_viewport(Size viewport, std::uint64_t version); - bool handle_wheel(PointF position, double angle_delta_y, double pixel_delta_y); - bool handle_pointer_press(PointF position); - bool handle_pointer_move(PointF position); - bool handle_pointer_release(PointF position); - void set_update_callback(std::function callback); -private: - Performance_Shower_Private* d(); - std::unique_ptr private_data; -}; - using Performance_Frame_Record = std::shared_ptr; struct Performance_Metrics_Worker_State { @@ -344,6 +313,7 @@ struct Performance_Metrics_Worker_State { : metric_queue(Capacity) {} rigtorp::MPMCQueue metric_queue; std::atomic metric_capacity_dropped{0}; + std::atomic_bool enabled{false}; std::atomic_bool worker_active{false}; std::atomic_bool work_requested{false}; std::atomic_bool cancelled{false}; @@ -368,6 +338,8 @@ struct Performance_Metrics_Worker_State { double last_line_height{1.0}; double last_max_scroll_offset{}; double held_content_width{}; + mutable std::mutex options_mutex; + Performance_Shower_Options options; std::mutex update_callback_mutex; std::function update_callback; std::atomic> display_snapshot_slot; @@ -445,7 +417,7 @@ struct Performance_Shower_Private { state->aggregate.update(*frame); changed = true; } - if (changed) { + if (changed && state->enabled.load(std::memory_order_acquire)) { state->display_snapshot_slot.store(build_display_snapshot(*state), std::memory_order_release); invoke_update_callback(*state); } @@ -476,6 +448,10 @@ struct Performance_Shower_Private { } Performance_Shower_Render_State state; + { + std::lock_guard lock(worker_state.options_mutex); + static_cast(state) = worker_state.options; + } Image measure_image(1, 1); measure_image.fill(Color::transparent()); Text_Metrics metrics; @@ -485,6 +461,7 @@ struct Performance_Shower_Private { metrics = measure_canvas.measure_text("0123456789 ABC \xE4\xB8\xAD\xE6\x96\x87"); if (metrics.line_height() <= 0.0) metrics = measure_canvas.measure_text("M"); + builder.seed_static_field_widths(measure_canvas); int available_text_width = viewport.width - state.left_margin - state.right_margin - state.scroll_bar_width - state.scroll_bar_margin * 2; builder.build_render_lines(measure_canvas, static_cast(std::max(1, available_text_width))); } @@ -698,6 +675,8 @@ private: return Performance_Field_Id::Frame_Wait_Max; if (key == "frame_worker_run_total") return Performance_Field_Id::Frame_Run_Total; + if (key == "frame_outcome") + return Performance_Field_Id::Outcome; if (key == "cache_hit_count") return Performance_Field_Id::Cache_Hit; if (key == "cache_miss_count") @@ -763,6 +742,26 @@ private: return Performance_Field_Id::Generic_Count; return Performance_Field_Id::Generic_Number; } + static constexpr std::array outcome_values() { + return { + Frame_Outcome::Presented, + Frame_Outcome::Superseded, + Frame_Outcome::Render_Acquire_Dropped, + Frame_Outcome::Publish_Dropped, + Frame_Outcome::Paint_Acquire_Dropped, + Frame_Outcome::Worker_Budget_Dropped, + Frame_Outcome::Cancelled + }; + } + void seed_static_field_widths(Canvas& canvas) { + for (Frame_Outcome outcome : outcome_values()) { + Performance_Text_Run run; + run.value = true; + run.field_id = Performance_Field_Id::Outcome; + run.text = outcome_text(outcome); + measure_run(canvas, run); + } + } static std::string marked_value(Performance_Field_Id field_id, std::string text) { std::string result; result.reserve(text.size() + 12); @@ -959,7 +958,7 @@ private: std::uint64_t latest_render_time = frame_render_time_ns(frame); double cache_hit_ratio = aggregate_cache_total ? static_cast(aggregate.cache_hit_count) * 100.0 / static_cast(aggregate_cache_total) : 0.0; lines.clear(); - lines.push_back("frame:" + count_text("frame_id", frame.frame_id) + " input_ver:" + count_text("input_version", frame.input_version) + " outcome:" + outcome_text(frame.outcome) + " limit:" + limit_source_text(refresh.source)); + lines.push_back("frame:" + count_text("frame_id", frame.frame_id) + " input_ver:" + count_text("input_version", frame.input_version) + " outcome:" + marked_value(Performance_Field_Id::Outcome, outcome_text(frame.outcome)) + " limit:" + limit_source_text(refresh.source)); lines.push_back("period render:" + period_text("render_period", refresh.render_period_ns) + " paint:" + period_text("paint_period", refresh.paint_period_ns) + " user:" + period_text("user_period", refresh.user_period_ns) + " target:" + period_text("target_period", refresh.target_period_ns)); lines.push_back("wait 21:" + ns_text("wait_21", frame.wait_21_ns) + "ms 12:" + ns_text("wait_12", frame.wait_12_ns) + "ms 23:" + ns_text("wait_23", frame.wait_23_ns) + "ms 32:" + ns_text("wait_32", frame.wait_32_ns) + "ms"); lines.push_back("taskflow workers:" + count_text("taskflow_workers", static_cast(executor.worker_count)) + " active:" + count_text("taskflow_active", static_cast(executor.active_workers)) + " queued_frames:" + count_text("taskflow_queued_frames", static_cast(executor.queued_frame_jobs)) + " active_frames:" + count_text("taskflow_active_frames", static_cast(executor.active_frame_jobs))); diff --git a/Core/plottable/export.h b/Core/plottable/export.h index db02fc1..4b6e037 100644 --- a/Core/plottable/export.h +++ b/Core/plottable/export.h @@ -7,15 +7,16 @@ #include "Interpolation.h" #include "Multi_Select_Rect.h" #include "Planisphere.h" -#include "Performance_Snapshot.h" +#include "Performance_Shower.h" #include "Spectrum.h" #include "Sweep_Frequency.h" #include "Waterfall.h" namespace renderive { class Plot_Core; -class Performance_Shower; LIB_DECL std::shared_ptr attach_performance_shower(Plot_Core& plot); +LIB_DECL std::shared_ptr attach_performance_shower(Plot_Core& plot, const Performance_Shower_Options& options); +LIB_DECL std::shared_ptr performance_shower(Plot_Core& plot); LIB_DECL void detach_performance_shower(Plot_Core& plot); LIB_DECL bool performance_shower_enabled(const Plot_Core& plot); LIB_DECL void set_performance_shower_enabled(Plot_Core& plot, bool enabled); diff --git a/test/Renderive_Core_Tests.cpp b/test/Renderive_Core_Tests.cpp index a0b994c..87a2a25 100644 --- a/test/Renderive_Core_Tests.cpp +++ b/test/Renderive_Core_Tests.cpp @@ -465,8 +465,38 @@ TEST(Renderive_Performance_Shower, ToggleDoesNotReenterRenderableTreeUnsafely) { EXPECT_TRUE(root->children_snapshot().empty()); } +TEST(Renderive_Performance_Shower, TogglePreservesAttachedOptions) { + Plot_Core plot(std::make_unique()); + plot.init(); + Performance_Shower_Options options; + options.font.size = 17.0; + options.background = Color{10, 20, 30, 255}; + options.foreground = Color{240, 240, 240, 255}; + + auto original = attach_performance_shower(plot, options); + ASSERT_TRUE(original); + set_performance_shower_enabled(plot, true); + EXPECT_TRUE(performance_shower_enabled(plot)); + set_performance_shower_enabled(plot, false); + EXPECT_FALSE(performance_shower_enabled(plot)); + set_performance_shower_enabled(plot, true); + + auto toggled = performance_shower(plot); + ASSERT_EQ(toggled, original); + EXPECT_EQ(toggled->options().font.size, options.font.size); + EXPECT_EQ(toggled->options().background, options.background); + EXPECT_EQ(toggled->options().foreground, options.foreground); +} + +TEST(Renderive_Hover_Info, DefaultTextPenContrastsWithDefaultBackground) { + Hover_Info_Render_State state; + EXPECT_EQ(state.hover_info_brush.color, Color::white()); + EXPECT_EQ(state.hover_info_pen.color, Color::black()); +} + TEST(Renderive_Performance_Shower, WorkerConsumesSharedFrameRecord) { auto state = std::make_shared(); + state->enabled.store(true, std::memory_order_release); auto frame = std::make_shared(); frame->frame_id = 7; frame->input_version = 3; @@ -499,8 +529,60 @@ TEST(Renderive_Performance_Shower, WorkerConsumesSharedFrameRecord) { EXPECT_EQ(state->aggregate.latest.frame_id, 7); } +TEST(Renderive_Performance_Shower, OptionsAffectSnapshotRendering) { + Performance_Shower shower; + Performance_Shower_Options options; + options.font.size = 18.0; + options.background = Color{10, 20, 30, 255}; + options.foreground = Color{240, 240, 240, 255}; + shower.set_options(options); + shower.setEnabled(true); + shower.set_viewport(Size{260, 120}, 1); + shower.collect_frame(performance_frame(1, 260, 120)); + + ASSERT_TRUE(wait_until([&]() { + auto snapshot = shower.display_snapshot(); + return snapshot && !snapshot->image.empty(); + })); + auto snapshot = shower.display_snapshot(); + ASSERT_TRUE(snapshot); + ASSERT_FALSE(snapshot->image.empty()); + EXPECT_EQ(shower.options().font.size, 18.0); + EXPECT_GT(snapshot->line_height, 12.0); + EXPECT_EQ(premultiplied_to_color(snapshot->image.row(1)[1]), options.background); +} + +TEST(Renderive_Performance_Shower, OutcomeWidthIsSeededWithLongestEnumText) { + auto state = std::make_shared(); + state->enabled.store(true, std::memory_order_release); + auto outcome_width = [&]() { + return state->field_layout_states[static_cast(Performance_Field_Id::Outcome)].max_value_width; + }; + + auto presented = performance_frame(1, 260, 120); + presented->outcome = Frame_Outcome::Presented; + ASSERT_TRUE(state->metric_queue.try_push(Performance_Frame_Record{presented})); + schedule_performance_metrics_worker(state); + ASSERT_TRUE(wait_until([&]() { + return state->aggregate.latest.frame_id == 1 && !state->worker_active.load(std::memory_order_acquire); + })); + double seeded_width = outcome_width(); + EXPECT_GT(seeded_width, 0.0); + + auto dropped = performance_frame(2, 260, 120); + dropped->outcome = Frame_Outcome::Worker_Budget_Dropped; + ASSERT_TRUE(state->metric_queue.try_push(Performance_Frame_Record{dropped})); + schedule_performance_metrics_worker(state); + ASSERT_TRUE(wait_until([&]() { + return state->aggregate.latest.frame_id == 2 && !state->worker_active.load(std::memory_order_acquire); + })); + EXPECT_EQ(outcome_width(), seeded_width); + state->cancelled.store(true, std::memory_order_release); +} + TEST(Renderive_Performance_Shower, NumericFieldWidthIsMonotonicAcrossResetAndResize) { auto state = std::make_shared(); + state->enabled.store(true, std::memory_order_release); auto frame_width = [&]() { return state->field_layout_states[static_cast(Performance_Field_Id::Frame_Id)].max_value_width; };