修复若干bug

This commit is contained in:
2026-08-02 01:14:42 +08:00
parent 883df14d5e
commit d8e023cea7
6 changed files with 253 additions and 45 deletions
+1
View File
@@ -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;
+74 -7
View File
@@ -233,6 +233,18 @@ static std::function<void()> performance_update_callback(const std::shared_ptr<P
std::lock_guard<std::mutex> lock(ctx->performance_update_callback_mutex);
return ctx->performance_update_callback;
}
static void request_performance_update(const std::shared_ptr<Performance_Metrics_Worker_State>& worker) {
if (!worker)
return;
std::function<void()> callback;
{
std::lock_guard<std::mutex> lock(worker->update_callback_mutex);
callback = worker->update_callback;
}
if (callback)
callback();
}
}
void schedule_performance_metrics_worker(const std::shared_ptr<Performance_Metrics_Worker_State>& state) {
@@ -257,18 +269,36 @@ Performance_Shower_Private* Performance_Shower::d() {
return private_data.get();
}
std::shared_ptr<Performance_Shower> attach_performance_shower(Plot_Core& plot) {
static std::shared_ptr<Performance_Shower> 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<Performance_Shower>();
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<Frame_Lifecycle_Observer>(created_shower), std::memory_order_release);
return created_shower;
}
std::shared_ptr<Performance_Shower> attach_performance_shower(Plot_Core& plot) {
return attach_performance_shower_impl(plot, nullptr);
}
std::shared_ptr<Performance_Shower> attach_performance_shower(Plot_Core& plot, const Performance_Shower_Options& options) {
return attach_performance_shower_impl(plot, &options);
}
std::shared_ptr<Performance_Shower> 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<Performance_Shower*>(this)->d()->enabled_value.load(std::memory_order_acquire);
@@ -348,6 +385,36 @@ bool Performance_Shower::enabled() const {
std::shared_ptr<const Performance_Display_Snapshot> Performance_Shower::display_snapshot() const {
return const_cast<Performance_Shower*>(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<std::mutex> 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<Performance_Shower*>(this)->d()->metrics_worker;
std::lock_guard<std::mutex> 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<const Frame_Lifecycle_Record> frame) {
if (!enabled())
return;
+58
View File
@@ -0,0 +1,58 @@
#pragma once
#include <cstdint>
#include <functional>
#include <memory>
#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<const Frame_Lifecycle_Record> frame) override;
[[nodiscard]] std::shared_ptr<const Performance_Display_Snapshot> 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<void()> callback);
private:
Performance_Shower_Private* d();
std::unique_ptr<Performance_Shower_Private> private_data;
};
} // namespace renderive
+35 -36
View File
@@ -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<const Frame_Lifecycle_Record> 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<const Frame_Lifecycle_Record> frame) override;
[[nodiscard]] std::shared_ptr<const Performance_Display_Snapshot> 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<void()> callback);
private:
Performance_Shower_Private* d();
std::unique_ptr<Performance_Shower_Private> private_data;
};
using Performance_Frame_Record = std::shared_ptr<const Frame_Lifecycle_Record>;
struct Performance_Metrics_Worker_State {
@@ -344,6 +313,7 @@ struct Performance_Metrics_Worker_State {
: metric_queue(Capacity) {}
rigtorp::MPMCQueue<Performance_Frame_Record> metric_queue;
std::atomic<std::uint64_t> 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<void()> update_callback;
std::atomic<std::shared_ptr<const Performance_Display_Snapshot>> 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<std::mutex> lock(worker_state.options_mutex);
static_cast<Performance_Shower_Options&>(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<double>(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<Frame_Outcome, 7> 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<double>(aggregate.cache_hit_count) * 100.0 / static_cast<double>(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<std::uint64_t>(executor.worker_count)) + " active:" + count_text("taskflow_active", static_cast<std::uint64_t>(executor.active_workers)) + " queued_frames:" + count_text("taskflow_queued_frames", static_cast<std::uint64_t>(executor.queued_frame_jobs)) + " active_frames:" + count_text("taskflow_active_frames", static_cast<std::uint64_t>(executor.active_frame_jobs)));
+3 -2
View File
@@ -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<Performance_Shower> attach_performance_shower(Plot_Core& plot);
LIB_DECL std::shared_ptr<Performance_Shower> attach_performance_shower(Plot_Core& plot, const Performance_Shower_Options& options);
LIB_DECL std::shared_ptr<Performance_Shower> 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);
+82
View File
@@ -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<Latency_Eager_Refresh_Strategy>());
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<Performance_Metrics_Worker_State>();
state->enabled.store(true, std::memory_order_release);
auto frame = std::make_shared<Frame_Lifecycle_Record>();
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<Performance_Metrics_Worker_State>();
state->enabled.store(true, std::memory_order_release);
auto outcome_width = [&]() {
return state->field_layout_states[static_cast<std::size_t>(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<Performance_Metrics_Worker_State>();
state->enabled.store(true, std::memory_order_release);
auto frame_width = [&]() {
return state->field_layout_states[static_cast<std::size_t>(Performance_Field_Id::Frame_Id)].max_value_width;
};