Files
Renderive/tests/Frame_Scheduler_Feedback.cpp
T
2026-07-30 11:21:30 +08:00

269 lines
11 KiB
C++

#include "../Renderive/plottable/Performance_Shower_p.h"
#include <QFile>
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <iostream>
#include <limits>
#include <optional>
#include <thread>
#include <vector>
namespace {
constexpr std::uint64_t Nanoseconds_Per_Millisecond = 1000000;
std::uint64_t to_nanoseconds(double milliseconds) {
return static_cast<std::uint64_t>(std::llround(milliseconds * static_cast<double>(Nanoseconds_Per_Millisecond)));
}
struct Scenario_Config {
const char* name{};
std::uint64_t id{};
double input_interval_ms{};
double render_duration_ms{};
double paint_duration_ms{};
double max_render_fps{};
renderive::Bottleneck_State expected_state = renderive::Bottleneck_State::Unknown;
double minimum_production_fps{};
double maximum_production_fps{};
double minimum_display_fps{};
double maximum_display_fps{};
};
struct Scenario_Result {
renderive::Frame_Feedback_State state;
std::uint64_t returned_frame_count{};
};
class Scenario_Simulation {
public:
explicit Scenario_Simulation(Scenario_Config config) : config(config) {
controller.set_max_render_fps(config.max_render_fps);
}
Scenario_Result run(double duration_ms) {
end_time = to_nanoseconds(duration_ms);
next_input_time = to_nanoseconds(config.input_interval_ms);
while (now < end_time) {
start_consumer();
start_render();
std::uint64_t event_time = next_event_time();
if (event_time > end_time)
event_time = end_time;
now = event_time;
if (now == next_input_time)
receive_input();
if (now == render_complete_time)
complete_render();
if (now == consume_complete_time)
complete_consume();
}
if (ready_frame) {
ready_frame->superseded_time = now;
controller.on_frame_superseded(*ready_frame, consuming_frame.has_value(), false);
return_frame(*ready_frame);
ready_frame.reset();
}
if (consuming_frame) {
consuming_frame->paint_end_time = now;
consuming_frame->consumer_release_time = now;
controller.on_frame_consumed(*consuming_frame, false);
return_frame(*consuming_frame);
consuming_frame.reset();
}
return {controller.state(), returned_frames.size()};
}
private:
bool input_pending() const {
return latest_input_version > published_input_version;
}
bool can_start_render() const {
if (rendering_frame || !input_pending())
return false;
return true;
}
void start_render() {
if (!can_start_render() || !controller.rate_limit_ready(now))
return;
renderive::Frame_Metadata frame;
frame.frame_id = config.id * 1000000 + ++next_frame_id;
frame.input_version = latest_input_version;
frame.snapshot_version = latest_input_version;
frame.frame_slot_generation = next_frame_id;
frame.core_data_time = latest_input_time;
frame.source_data_time_begin = latest_input_time;
frame.source_data_time_end = latest_input_time;
frame.render_request_time = latest_input_time;
frame.snapshot_capture_begin_time = now;
frame.snapshot_capture_end_time = now;
frame.render_queue_enter_time = now;
frame.prepare_begin_time = now;
frame.prepare_end_time = now;
frame.pixel_width = 1920;
frame.pixel_height = 1080;
controller.on_render_started(frame, now);
rendering_frame = frame;
render_complete_time = now + to_nanoseconds(config.render_duration_ms);
}
void complete_render() {
renderive::Frame_Metadata frame = *rendering_frame;
frame.draw_begin_time = frame.render_begin_time;
frame.draw_end_time = now;
frame.render_end_time = now;
frame.publish_time = now;
bool had_ready_frame = ready_frame.has_value();
controller.on_render_completed(frame, had_ready_frame, consuming_frame.has_value(), latest_input_version > frame.input_version);
published_input_version = frame.input_version;
if (ready_frame) {
ready_frame->superseded_time = now;
controller.on_frame_superseded(*ready_frame, consuming_frame.has_value(), input_pending());
return_frame(*ready_frame);
}
ready_frame = frame;
rendering_frame.reset();
render_complete_time = Infinite_Time;
}
void start_consumer() {
if (consuming_frame || !ready_frame)
return;
consuming_frame = *ready_frame;
ready_frame.reset();
consuming_frame->update_request_id = consuming_frame->frame_id;
consuming_frame->update_request_time = consuming_frame->publish_time;
consuming_frame->paint_begin_time = now;
consume_complete_time = now + to_nanoseconds(config.paint_duration_ms);
}
void complete_consume() {
consuming_frame->paint_end_time = now;
consuming_frame->consumer_release_time = now;
controller.on_frame_consumed(*consuming_frame, ready_frame.has_value());
return_frame(*consuming_frame);
consuming_frame.reset();
consume_complete_time = Infinite_Time;
}
void receive_input() {
++latest_input_version;
latest_input_time = now;
controller.on_input_changed(now, latest_input_version);
next_input_time += to_nanoseconds(config.input_interval_ms);
}
std::uint64_t next_event_time() const {
std::uint64_t event_time = std::min({next_input_time, render_complete_time, consume_complete_time, end_time});
if (can_start_render() && !controller.rate_limit_ready(now))
event_time = std::min(event_time, controller.next_render_time_ns());
return event_time;
}
void return_frame(renderive::Frame_Metadata& frame) {
renderive::write_frame_performance_log(frame);
returned_frames.push_back(frame);
}
static constexpr std::uint64_t Infinite_Time = std::numeric_limits<std::uint64_t>::max();
Scenario_Config config;
renderive::Frame_Feedback_Controller controller;
std::optional<renderive::Frame_Metadata> rendering_frame;
std::optional<renderive::Frame_Metadata> ready_frame;
std::optional<renderive::Frame_Metadata> consuming_frame;
std::vector<renderive::Frame_Metadata> returned_frames;
std::uint64_t now{};
std::uint64_t end_time{};
std::uint64_t next_input_time{};
std::uint64_t render_complete_time = Infinite_Time;
std::uint64_t consume_complete_time = Infinite_Time;
std::uint64_t latest_input_time{};
std::uint64_t latest_input_version{};
std::uint64_t published_input_version{};
std::uint64_t next_frame_id{};
};
bool in_range(double value, double minimum, double maximum) {
return value >= minimum && value <= maximum;
}
int validate_scenario(const Scenario_Config& config, const Scenario_Result& result, int index) {
const renderive::Frame_Feedback_State& state = result.state;
std::cout << config.name
<< " state=" << renderive::bottleneck_state_name(state.bottleneck_state)
<< " production_fps=" << state.production_rate_fps
<< " display_fps=" << state.display_rate_fps
<< " adaptive_interval_ms=" << state.adaptive_min_render_interval_ms
<< " superseded_ratio=" << state.superseded_ready_ratio
<< " display_efficiency=" << state.display_efficiency
<< " returned=" << result.returned_frame_count
<< '\n';
if (state.bottleneck_state != config.expected_state)
return index * 10 + 1;
if (!in_range(state.production_rate_fps, config.minimum_production_fps, config.maximum_production_fps))
return index * 10 + 2;
if (!in_range(state.display_rate_fps, config.minimum_display_fps, config.maximum_display_fps))
return index * 10 + 3;
if (config.expected_state == renderive::Bottleneck_State::Consumer_Limited) {
if (std::abs(state.production_rate_fps - state.display_rate_fps) > 6.0)
return index * 10 + 4;
if (state.adaptive_min_render_interval_ms < config.paint_duration_ms * 0.8)
return index * 10 + 5;
}
if (config.expected_state == renderive::Bottleneck_State::Manual_Limited) {
double expected_interval = 1000.0 / config.max_render_fps;
if (std::abs(state.effective_min_render_interval_ms - expected_interval) > 0.2)
return index * 10 + 6;
}
return 0;
}
int validate_frame_input_ring() {
renderive::Performance_Shower_Input_Data input;
for (std::uint64_t frame_id = 1; frame_id <= 72; ++frame_id) {
renderive::Frame_Metadata frame;
frame.frame_id = frame_id;
input.push(frame);
}
if (input.get_pending_count() != renderive::Performance_Shower_Input_Data::Capacity)
return 1;
if (input.get_capacity_dropped_count() != 8)
return 2;
std::vector<std::uint64_t> frame_ids;
input.read_all([&frame_ids](const renderive::Frame_Metadata& frame) {
frame_ids.push_back(frame.frame_id);
});
if (frame_ids.front() != 9 || frame_ids.back() != 72)
return 3;
input.request_reset();
if (input.get_pending_count() != 0 || !input.reset_pending)
return 4;
return 0;
}
int validate_renderable_cache_stats() {
renderive::Frame_Metadata frame;
frame.record_renderable_cache_hit("cache_a");
frame.record_renderable_cache_stale_hit("cache_a");
frame.record_renderable_cache_miss("cache_b");
if (frame.renderable_cache_hit_count != 1 || frame.renderable_cache_stale_hit_count != 1 || frame.renderable_cache_miss_count != 1)
return 1;
renderive::Frame_Performance_Aggregate aggregate;
aggregate.update(frame);
if (aggregate.cache_hit_count != 1 || aggregate.cache_stale_hit_count != 1 || aggregate.cache_miss_count != 1)
return 2;
if (aggregate.renderable_cache_stats.size() != 2)
return 3;
return 0;
}
}
int main() {
QString log_path = qEnvironmentVariable("RENDERIVE_PERFORMANCE_LOG_PATH");
if (!log_path.isEmpty())
QFile::remove(log_path);
int ring_result = validate_frame_input_ring();
if (ring_result)
return ring_result;
int cache_result = validate_renderable_cache_stats();
if (cache_result)
return 100 + cache_result;
const std::array<Scenario_Config, 4> scenarios{{
{"Input_Limited", 1, 50.0, 2.0, 2.0, 0.0, renderive::Bottleneck_State::Input_Limited, 18.0, 22.0, 18.0, 22.0},
{"Producer_Limited", 2, 1.0, 20.0, 2.0, 0.0, renderive::Bottleneck_State::Producer_Limited, 45.0, 55.0, 45.0, 55.0},
{"Consumer_Limited", 3, 1.0, 2.0, 25.0, 0.0, renderive::Bottleneck_State::Consumer_Limited, 34.0, 46.0, 36.0, 44.0},
{"Manual_Limited", 4, 1.0, 2.0, 2.0, 30.0, renderive::Bottleneck_State::Manual_Limited, 28.0, 32.0, 28.0, 32.0}
}};
for (int i = 0; i < scenarios.size(); ++i) {
Scenario_Simulation simulation(scenarios[i]);
Scenario_Result result = simulation.run(8000.0);
int validation_result = validate_scenario(scenarios[i], result, i + 1);
if (validation_result)
return validation_result;
}
std::this_thread::sleep_for(std::chrono::milliseconds(250));
return 0;
}