更完
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
set(Renderive_Web_dependencies global::drogon global::spdlog)
|
||||
set(Renderive_Web_dependencies global::drogon global::spdlog global::magic_enum)
|
||||
if (RENDERIVE_BUILD_TESTS)
|
||||
list(APPEND Renderive_Web_dependencies global::GTest)
|
||||
endif ()
|
||||
@@ -16,6 +16,7 @@ function(renderive_find_web_dependencies)
|
||||
rcl_load_dependency_environment(${Renderive_Web_dependencies})
|
||||
find_package(Drogon CONFIG REQUIRED)
|
||||
find_package(spdlog CONFIG REQUIRED)
|
||||
find_package(magic_enum CONFIG REQUIRED)
|
||||
if (RENDERIVE_BUILD_TESTS)
|
||||
find_package(GTest CONFIG REQUIRED)
|
||||
endif ()
|
||||
@@ -45,7 +46,7 @@ target_include_directories(Renderive_Web PUBLIC
|
||||
target_compile_features(Renderive_Web PUBLIC cxx_std_20)
|
||||
target_compile_definitions(Renderive_Web PRIVATE NOMINMAX)
|
||||
target_link_libraries(Renderive_Web
|
||||
PUBLIC Renderive_render_2D Drogon::Drogon
|
||||
PUBLIC Renderive_render_2D Drogon::Drogon Adminive::MagicEnum magic_enum::magic_enum
|
||||
PRIVATE Adminive::Nlohmann spdlog::spdlog
|
||||
)
|
||||
set(Renderive_Web_Server_source_dir "${CMAKE_CURRENT_LIST_DIR}/server")
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
namespace renderive::web {
|
||||
|
||||
template <class Enum>
|
||||
requires std::is_enum_v<Enum>
|
||||
std::string gallery_enum_id(Enum value) {
|
||||
std::string result(magic_enum::enum_name(value));
|
||||
std::ranges::transform(result, result.begin(), [](unsigned char character) {
|
||||
return static_cast<char>(std::tolower(character));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
template <class Enum>
|
||||
requires std::is_enum_v<Enum>
|
||||
std::optional<Enum> gallery_enum_cast(std::string_view identifier) {
|
||||
for (const Enum value : magic_enum::enum_values<Enum>()) {
|
||||
if (gallery_enum_id(value) == identifier)
|
||||
return value;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace renderive::web
|
||||
@@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
|
||||
#include "Gallery_Enum.h"
|
||||
#include "render_2D/plot/Plot_Core.h"
|
||||
|
||||
#include "adminive/adminive.hpp"
|
||||
#include "adminive/adapters/magic_enum.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace renderive::web {
|
||||
|
||||
struct Gallery_Consumer_Feedback_Snapshot {
|
||||
bool master_enabled{};
|
||||
bool pixel_enabled{};
|
||||
bool presentation_enabled{};
|
||||
bool manual_enabled{};
|
||||
double manual_fps{};
|
||||
std::string source;
|
||||
std::uint64_t pixel_interval_ns{};
|
||||
std::uint64_t presentation_interval_ns{};
|
||||
std::uint64_t manual_interval_ns{};
|
||||
};
|
||||
|
||||
} // namespace renderive::web
|
||||
|
||||
namespace adminive {
|
||||
|
||||
template <class Json>
|
||||
struct Value_Adapter<renderive::Frame_Control_Mode, Json> {
|
||||
using value_type = std::string;
|
||||
|
||||
static std::string read(renderive::Frame_Control_Mode value) {
|
||||
return renderive::web::gallery_enum_id(value);
|
||||
}
|
||||
static void write(renderive::Frame_Control_Mode& target, std::string value) {
|
||||
const auto parsed =
|
||||
renderive::web::gallery_enum_cast<renderive::Frame_Control_Mode>(value);
|
||||
if (!parsed)
|
||||
throw std::invalid_argument("unknown frame control mode: " + value);
|
||||
target = *parsed;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Type_Descriptor<renderive::Frame_Observer_Snapshot> {
|
||||
static auto get() {
|
||||
using T = renderive::Frame_Observer_Snapshot;
|
||||
return object<T>("kernel_observer",
|
||||
ADMINIVE_FIELD_LABEL(T, mode, "模式"),
|
||||
ADMINIVE_FIELD_LABEL(T, last_event, "最近事件"),
|
||||
ADMINIVE_FIELD_LABEL(T, limit_state, "当前瓶颈"),
|
||||
ADMINIVE_FIELD_LABEL(T, frequency_hz, "配置频率"),
|
||||
ADMINIVE_FIELD_LABEL(T, observation_count, "观察次数"),
|
||||
ADMINIVE_FIELD_LABEL(T, produced_frame_count, "发布"),
|
||||
ADMINIVE_FIELD_LABEL(T, consumed_frame_count, "完成"),
|
||||
ADMINIVE_FIELD_LABEL(T, dropped_frame_count, "丢弃"),
|
||||
ADMINIVE_FIELD_LABEL(T, failed_operation_count, "失败"),
|
||||
ADMINIVE_FIELD_LABEL(T, pending_frame_count, "待处理"),
|
||||
ADMINIVE_FIELD_LABEL(T, latest_sequence, "最新序号"),
|
||||
ADMINIVE_FIELD_LABEL(T, paint_duration_ns, "PaintEvent"),
|
||||
ADMINIVE_FIELD_LABEL(T, render_duration_ns, "后台渲染"),
|
||||
ADMINIVE_FIELD_LABEL(T, target_interval_ns, "目标间隔"),
|
||||
ADMINIVE_FIELD_LABEL(T, frequency_limit_enabled, "频率限制"),
|
||||
ADMINIVE_FIELD_LABEL(T, consumer_feedback_enabled, "Kernel 反馈有效"),
|
||||
ADMINIVE_FIELD_LABEL(T, bottleneck_duration_ns, "内部瓶颈"),
|
||||
ADMINIVE_FIELD_LABEL(T, consumer_sample_interval_ns, "消费者原始采样"),
|
||||
ADMINIVE_FIELD_LABEL(T, consumer_smoothed_interval_ns, "消费者平滑周期"),
|
||||
ADMINIVE_FIELD_LABEL(T, consumer_variation_ns, "消费者抖动"),
|
||||
ADMINIVE_FIELD_LABEL(T, consumer_safety_interval_ns, "消费者安全期限"),
|
||||
ADMINIVE_FIELD_LABEL(T, consumer_interval_ns, "消费者限速周期"),
|
||||
ADMINIVE_FIELD_LABEL(T, next_refresh_interval_ns, "下次刷新"),
|
||||
ADMINIVE_FIELD_LABEL(T, paint_lease_wait_ns, "Painter Lease 等待"),
|
||||
ADMINIVE_FIELD_LABEL(T, paint_state_wait_ns, "Paint State 等待"),
|
||||
ADMINIVE_FIELD_LABEL(T, publish_state_wait_ns, "Publish State 等待"),
|
||||
ADMINIVE_FIELD_LABEL(T, ready_wait_ns, "Ready 等待"),
|
||||
ADMINIVE_FIELD_LABEL(T, frame_age_at_render_ns, "开始渲染时帧龄"),
|
||||
ADMINIVE_FIELD_LABEL(T, render_lease_wait_ns, "Render Lease 等待"),
|
||||
ADMINIVE_FIELD_LABEL(T, render_state_wait_ns, "Render State 等待"),
|
||||
ADMINIVE_FIELD_LABEL(T, render_finish_state_wait_ns, "Render Finish 等待"),
|
||||
ADMINIVE_FIELD_LABEL(T, queue_wait_ns, "回放队列等待"),
|
||||
ADMINIVE_FIELD_LABEL(T, end_to_end_ns, "端到端延迟"))
|
||||
.label("Kernel Observer");
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Type_Descriptor<renderive::web::Gallery_Consumer_Feedback_Snapshot> {
|
||||
static auto get() {
|
||||
using T = renderive::web::Gallery_Consumer_Feedback_Snapshot;
|
||||
return object<T>("consumer_feedback",
|
||||
ADMINIVE_FIELD_LABEL(T, master_enabled, "消费者反馈总开关"),
|
||||
ADMINIVE_FIELD_LABEL(T, pixel_enabled, "像素响应反馈"),
|
||||
ADMINIVE_FIELD_LABEL(T, presentation_enabled, "浏览器呈现反馈"),
|
||||
ADMINIVE_FIELD_LABEL(T, manual_enabled, "手动消费者反馈"),
|
||||
ADMINIVE_FIELD_LABEL(T, manual_fps, "手动消费者 FPS"),
|
||||
ADMINIVE_FIELD_LABEL(T, source, "生效反馈来源"),
|
||||
ADMINIVE_FIELD_LABEL(T, pixel_interval_ns, "像素响应周期"),
|
||||
ADMINIVE_FIELD_LABEL(T, presentation_interval_ns, "浏览器呈现周期"),
|
||||
ADMINIVE_FIELD_LABEL(T, manual_interval_ns, "手动消费者周期"))
|
||||
.label("Consumer Feedback");
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(Described_Type<renderive::Frame_Observer_Snapshot>);
|
||||
static_assert(Described_Type<renderive::web::Gallery_Consumer_Feedback_Snapshot>);
|
||||
|
||||
} // namespace adminive
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "Gallery_Plot_Session.h"
|
||||
#include "Gallery_Controls.h"
|
||||
#include "Gallery_Enum.h"
|
||||
#include "Gallery_Observer_Adminive.h"
|
||||
#include "Gallery_Protocol.h"
|
||||
#include "Gallery_Session_Control_Adminive.h"
|
||||
#include "Pixel_Frame.h"
|
||||
@@ -75,26 +77,11 @@ std::optional<double> action_number(const Gallery_Action_Request& request) {
|
||||
return std::get<double>(*request.argument);
|
||||
}
|
||||
Frame_Control_Mode core_frame_mode(Gallery_Frame_Mode mode) {
|
||||
switch (mode) {
|
||||
case Gallery_Frame_Mode::Manual:
|
||||
return Frame_Control_Mode::Manual;
|
||||
case Gallery_Frame_Mode::Playback:
|
||||
return Frame_Control_Mode::Playback;
|
||||
case Gallery_Frame_Mode::Low_Latency:
|
||||
return Frame_Control_Mode::Low_Latency;
|
||||
}
|
||||
return Frame_Control_Mode::Low_Latency;
|
||||
}
|
||||
std::string_view frame_mode_name(Gallery_Frame_Mode mode) {
|
||||
switch (mode) {
|
||||
case Gallery_Frame_Mode::Manual:
|
||||
return "manual";
|
||||
case Gallery_Frame_Mode::Playback:
|
||||
return "playback";
|
||||
case Gallery_Frame_Mode::Low_Latency:
|
||||
return "low_latency";
|
||||
}
|
||||
return "low_latency";
|
||||
const auto result = magic_enum::enum_cast<Frame_Control_Mode>(
|
||||
magic_enum::enum_name(mode));
|
||||
if (!result)
|
||||
throw std::logic_error("Gallery frame mode has no Kernel strategy");
|
||||
return *result;
|
||||
}
|
||||
} // namespace
|
||||
class Gallery_Scene final {
|
||||
@@ -118,7 +105,7 @@ public:
|
||||
session_control_ = std::make_unique<Gallery_Session_Control>(
|
||||
plot_, *primary_, feedback_policy_);
|
||||
register_controls();
|
||||
set_performance_plot_name(plot_, case_id_ + "/" + std::string(frame_mode_name(frame_mode_)));
|
||||
set_performance_plot_name(plot_, case_id_ + "/" + gallery_enum_id(frame_mode_));
|
||||
if (frame_mode_ == Gallery_Frame_Mode::Low_Latency)
|
||||
plot_.set_max_render_fps(30.0);
|
||||
plot_.activate_view();
|
||||
@@ -434,9 +421,24 @@ public:
|
||||
const double pixel_fps = recent_pixel_rate(pixel_history_, telemetry_now);
|
||||
const double pixel_megabytes_per_second = recent_pixel_megabytes_per_second(
|
||||
pixel_history_, telemetry_now);
|
||||
const bool low_latency = frame_mode_ == Gallery_Frame_Mode::Low_Latency;
|
||||
const Gallery_Consumer_Feedback_Snapshot consumer_feedback{
|
||||
low_latency && feedback_policy_.enabled,
|
||||
low_latency && feedback_policy_.pixel,
|
||||
low_latency && feedback_policy_.presentation,
|
||||
low_latency && feedback_policy_.manual,
|
||||
low_latency ? feedback_policy_.manual_fps : 0.0,
|
||||
consumer_feedback_source_,
|
||||
consumer_pixel_interval_ns_,
|
||||
consumer_presentation_interval_ns_,
|
||||
consumer_manual_interval_ns_};
|
||||
nlohmann::json observer_data =
|
||||
adminive::to_frontend_json<nlohmann::json>(observer);
|
||||
nlohmann::json consumer_feedback_data =
|
||||
adminive::to_frontend_json<nlohmann::json>(consumer_feedback);
|
||||
nlohmann::json telemetry{
|
||||
{"case", case_id_},
|
||||
{"frame_mode", frame_mode_name(frame_mode_)},
|
||||
{"frame_mode", gallery_enum_id(frame_mode_)},
|
||||
{"frame_index", frame_index_},
|
||||
{
|
||||
"viewport", {
|
||||
@@ -467,75 +469,11 @@ public:
|
||||
{
|
||||
"automatic_low_latency_scheduler",
|
||||
automatic_low_latency_ && frame_mode_ == Gallery_Frame_Mode::Low_Latency
|
||||
},
|
||||
{"kernel_paint_ms", static_cast<double>(observer.paint_duration_ns) / 1e6},
|
||||
{"kernel_render_ms", static_cast<double>(observer.render_duration_ns) / 1e6},
|
||||
{"kernel_bottleneck_ms", static_cast<double>(observer.bottleneck_duration_ns) / 1e6},
|
||||
{"kernel_end_to_end_ms", static_cast<double>(observer.end_to_end_ns) / 1e6}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kernel_observer", {
|
||||
{"mode", frame_mode_name(frame_mode_)},
|
||||
{"last_event", observer.last_event},
|
||||
{"limit_state", observer.limit_state},
|
||||
{"configured_frequency_hz", observer.frequency_hz},
|
||||
{"observation_count", observer.observation_count},
|
||||
{"produced_frame_count", observer.produced_frame_count},
|
||||
{"consumed_frame_count", observer.consumed_frame_count},
|
||||
{"dropped_frame_count", observer.dropped_frame_count},
|
||||
{"failed_operation_count", observer.failed_operation_count},
|
||||
{"pending_frame_count", observer.pending_frame_count},
|
||||
{"latest_sequence", observer.latest_sequence},
|
||||
{"paint_duration_ns", observer.paint_duration_ns},
|
||||
{"render_duration_ns", observer.render_duration_ns},
|
||||
{"target_interval_ns", observer.target_interval_ns},
|
||||
{"frequency_limit_enabled", observer.frequency_limit_enabled},
|
||||
{"consumer_feedback_enabled", observer.consumer_feedback_enabled},
|
||||
{
|
||||
"consumer_feedback_master_enabled",
|
||||
frame_mode_ == Gallery_Frame_Mode::Low_Latency &&
|
||||
feedback_policy_.enabled
|
||||
},
|
||||
{
|
||||
"consumer_pixel_feedback_enabled",
|
||||
frame_mode_ == Gallery_Frame_Mode::Low_Latency &&
|
||||
feedback_policy_.pixel
|
||||
},
|
||||
{
|
||||
"consumer_presentation_feedback_enabled",
|
||||
frame_mode_ == Gallery_Frame_Mode::Low_Latency &&
|
||||
feedback_policy_.presentation
|
||||
},
|
||||
{
|
||||
"consumer_manual_feedback_enabled",
|
||||
frame_mode_ == Gallery_Frame_Mode::Low_Latency &&
|
||||
feedback_policy_.manual
|
||||
},
|
||||
{"consumer_manual_fps", frame_mode_ == Gallery_Frame_Mode::Low_Latency ? feedback_policy_.manual_fps : 0.0},
|
||||
{"consumer_feedback_source", consumer_feedback_source_},
|
||||
{"consumer_pixel_interval_ns", consumer_pixel_interval_ns_},
|
||||
{"consumer_presentation_interval_ns", consumer_presentation_interval_ns_},
|
||||
{"consumer_manual_interval_ns", consumer_manual_interval_ns_},
|
||||
{"bottleneck_duration_ns", observer.bottleneck_duration_ns},
|
||||
{"consumer_sample_interval_ns", observer.consumer_sample_interval_ns},
|
||||
{"consumer_smoothed_interval_ns", observer.consumer_smoothed_interval_ns},
|
||||
{"consumer_variation_ns", observer.consumer_variation_ns},
|
||||
{"consumer_safety_interval_ns", observer.consumer_safety_interval_ns},
|
||||
{"consumer_interval_ns", observer.consumer_interval_ns},
|
||||
{"next_refresh_interval_ns", observer.next_refresh_interval_ns},
|
||||
{"paint_lease_wait_ns", observer.paint_lease_wait_ns},
|
||||
{"paint_state_wait_ns", observer.paint_state_wait_ns},
|
||||
{"publish_state_wait_ns", observer.publish_state_wait_ns},
|
||||
{"ready_wait_ns", observer.ready_wait_ns},
|
||||
{"frame_age_at_render_ns", observer.frame_age_at_render_ns},
|
||||
{"render_lease_wait_ns", observer.render_lease_wait_ns},
|
||||
{"render_state_wait_ns", observer.render_state_wait_ns},
|
||||
{"render_finish_state_wait_ns", observer.render_finish_state_wait_ns},
|
||||
{"queue_wait_ns", observer.queue_wait_ns},
|
||||
{"end_to_end_ns", observer.end_to_end_ns}
|
||||
}
|
||||
}
|
||||
},
|
||||
{"kernel_observer", std::move(observer_data)},
|
||||
{"consumer_feedback", std::move(consumer_feedback_data)},
|
||||
{
|
||||
"client_performance", {
|
||||
{"transport_fps", client_transport_fps_},
|
||||
@@ -556,49 +494,12 @@ public:
|
||||
},
|
||||
{"last_action_result", last_action_result_}
|
||||
};
|
||||
if (frame_mode_ == Gallery_Frame_Mode::Low_Latency) {
|
||||
telemetry["render_fps"] = plot_.max_render_fps();
|
||||
telemetry["refresh_feedback_hz"] = plot_.refresh_feedback_snapshot().frequency_hz;
|
||||
const auto scheduler_interval_ns = kernel_refresh_interval_ns();
|
||||
telemetry["low_latency_limit"] = {
|
||||
{"current", observer.limit_state},
|
||||
{"frequency_limited", observer.limit_state == "frequency_limited"},
|
||||
{"paint_limited", observer.limit_state == "paint_limited"},
|
||||
{"render_limited", observer.limit_state == "render_limited"},
|
||||
{"consumer_limited", observer.limit_state == "consumer_limited"},
|
||||
{"configured_frequency_hz", observer.frequency_hz},
|
||||
{"frequency_limit_enabled", observer.frequency_limit_enabled},
|
||||
{"consumer_feedback_enabled", observer.consumer_feedback_enabled},
|
||||
{"consumer_feedback_master_enabled", feedback_policy_.enabled},
|
||||
{"consumer_pixel_feedback_enabled", feedback_policy_.pixel},
|
||||
{"consumer_presentation_feedback_enabled", feedback_policy_.presentation},
|
||||
{"consumer_manual_feedback_enabled", feedback_policy_.manual},
|
||||
{"consumer_manual_fps", feedback_policy_.manual_fps},
|
||||
{"consumer_feedback_source", consumer_feedback_source_},
|
||||
{"consumer_pixel_interval_ns", consumer_pixel_interval_ns_},
|
||||
{"consumer_presentation_interval_ns", consumer_presentation_interval_ns_},
|
||||
{"consumer_manual_interval_ns", consumer_manual_interval_ns_},
|
||||
{"target_interval_ns", observer.target_interval_ns},
|
||||
{"paint_duration_ns", observer.paint_duration_ns},
|
||||
{"render_duration_ns", observer.render_duration_ns},
|
||||
{"bottleneck_duration_ns", observer.bottleneck_duration_ns},
|
||||
{"consumer_sample_interval_ns", observer.consumer_sample_interval_ns},
|
||||
{"consumer_smoothed_interval_ns", observer.consumer_smoothed_interval_ns},
|
||||
{"consumer_variation_ns", observer.consumer_variation_ns},
|
||||
{"consumer_safety_interval_ns", observer.consumer_safety_interval_ns},
|
||||
{"consumer_interval_ns", observer.consumer_interval_ns},
|
||||
{"scheduler_interval_ns", scheduler_interval_ns},
|
||||
{"scheduler_frequency_hz", scheduler_interval_ns == 0 ? 0.0 : 1'000'000'000.0 / static_cast<double>(scheduler_interval_ns)}
|
||||
};
|
||||
}
|
||||
if (primary_) {
|
||||
telemetry["renderable"] = {
|
||||
{"object_name", primary_->object_name()},
|
||||
{"visible", primary_->is_visible()},
|
||||
{
|
||||
"cache_mode", primary_->get_cache_mode() == Renderable_Cache_Mode::Direct
|
||||
? "Direct"
|
||||
: "Local_Pixel"
|
||||
"cache_mode", magic_enum::enum_name(primary_->get_cache_mode())
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -887,7 +788,7 @@ private:
|
||||
{"unix_ms", unix_ms},
|
||||
{"session_id", session_id_},
|
||||
{"case", case_id_},
|
||||
{"frame_mode", frame_mode_name(frame_mode_)},
|
||||
{"frame_mode", gallery_enum_id(frame_mode_)},
|
||||
{"configured_fps", low_latency ? plot_.max_render_fps() : 0.0},
|
||||
{"automatic_scheduler_interval_ns", scheduler_interval_ns},
|
||||
{"automatic_scheduler_fps", scheduler_interval_ns == 0 ? 0.0 : 1'000'000'000.0 / static_cast<double>(scheduler_interval_ns)},
|
||||
|
||||
+162
-105
@@ -1,4 +1,6 @@
|
||||
#include "Gallery_Protocol.h"
|
||||
#include "Gallery_Enum.h"
|
||||
#include "Gallery_Observer_Adminive.h"
|
||||
#include "Gallery_Renderables.h"
|
||||
#include "Gallery_Session_Control_Adminive.h"
|
||||
|
||||
@@ -148,28 +150,6 @@ Json case_contract(std::string_view case_id) {
|
||||
return item ? adminive::to_frontend_json<Json>(*item) : Json::object();
|
||||
}
|
||||
|
||||
std::string_view frame_mode_id(Gallery_Frame_Mode mode) {
|
||||
switch (mode) {
|
||||
case Gallery_Frame_Mode::Manual:
|
||||
return "manual";
|
||||
case Gallery_Frame_Mode::Low_Latency:
|
||||
return "low_latency";
|
||||
case Gallery_Frame_Mode::Playback:
|
||||
return "playback";
|
||||
}
|
||||
return "low_latency";
|
||||
}
|
||||
|
||||
std::optional<Gallery_Frame_Mode> parse_frame_mode(std::string_view value) {
|
||||
if (value == "manual")
|
||||
return Gallery_Frame_Mode::Manual;
|
||||
if (value == "low_latency")
|
||||
return Gallery_Frame_Mode::Low_Latency;
|
||||
if (value == "playback")
|
||||
return Gallery_Frame_Mode::Playback;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Json dashboard_field(std::string_view label, std::string_view source,
|
||||
std::string_view format = "integer", int digits = -1) {
|
||||
Json result{{"label", label}, {"source", source}, {"format", format}};
|
||||
@@ -178,10 +158,110 @@ Json dashboard_field(std::string_view label, std::string_view source,
|
||||
return result;
|
||||
}
|
||||
|
||||
Json mapped_dashboard_field(std::string_view label, std::string_view source,
|
||||
std::string_view format, std::string_view value_map) {
|
||||
Json result = dashboard_field(label, source, format);
|
||||
result["value_map"] = value_map;
|
||||
template <class Object>
|
||||
const Json& described_field(std::string_view name) {
|
||||
static const Json descriptor = adminive::to_descriptor_json<Json, Object>();
|
||||
const auto& fields = descriptor.at("fields");
|
||||
const auto found = std::find_if(fields.begin(), fields.end(), [name](const Json& field) {
|
||||
return field.at("name").template get<std::string>() == name;
|
||||
});
|
||||
if (found == fields.end())
|
||||
throw std::logic_error("missing Adminive dashboard field: " + std::string(name));
|
||||
return *found;
|
||||
}
|
||||
|
||||
template <class Object>
|
||||
std::string described_source(std::string_view prefix, std::string_view name) {
|
||||
static_cast<void>(described_field<Object>(name));
|
||||
return std::string(prefix) + "." + std::string(name);
|
||||
}
|
||||
|
||||
template <class Object>
|
||||
Json described_dashboard_field(std::string_view prefix, std::string_view name) {
|
||||
const Json& field = described_field<Object>(name);
|
||||
const std::string value_type = field.at("value_type");
|
||||
std::string format = "integer";
|
||||
if (name.ends_with("_ns"))
|
||||
format = "nanoseconds";
|
||||
else if (name.ends_with("_fps"))
|
||||
format = "fps";
|
||||
else if (name.ends_with("frequency_hz"))
|
||||
format = "frequency";
|
||||
else if (name == "source")
|
||||
format = "flags";
|
||||
else if (value_type == "boolean" || name == "limit_state")
|
||||
format = "enum";
|
||||
else if (value_type == "string" || value_type == "enum")
|
||||
format = "text";
|
||||
Json result = dashboard_field(
|
||||
field.at("presentation").at("label").template get<std::string>(),
|
||||
described_source<Object>(prefix, name), format,
|
||||
name.ends_with("_fps") ? 2 : -1);
|
||||
if (value_type == "boolean")
|
||||
result["value_map"] = "enabled";
|
||||
else if (name == "limit_state")
|
||||
result["value_map"] = "limit_state";
|
||||
else if (name == "source")
|
||||
result["value_map"] = "consumer_feedback_source";
|
||||
return result;
|
||||
}
|
||||
|
||||
bool observer_header_field(std::string_view name) {
|
||||
return name == "mode" || name == "last_event" || name == "limit_state";
|
||||
}
|
||||
|
||||
bool observer_counter_field(std::string_view name) {
|
||||
return name == "frequency_hz" || name == "latest_sequence" ||
|
||||
name.ends_with("_count");
|
||||
}
|
||||
|
||||
bool observer_detail_field(std::string_view name) {
|
||||
return name.ends_with("_wait_ns") || name == "frame_age_at_render_ns";
|
||||
}
|
||||
|
||||
template <class Predicate>
|
||||
Json observer_fields(Predicate&& predicate) {
|
||||
const Json descriptor =
|
||||
adminive::to_descriptor_json<Json, renderive::Frame_Observer_Snapshot>();
|
||||
Json result = Json::array();
|
||||
for (const Json& field : descriptor.at("fields")) {
|
||||
const std::string name = field.at("name");
|
||||
if (predicate(name)) {
|
||||
Json field = described_dashboard_field<renderive::Frame_Observer_Snapshot>(
|
||||
"kernel_observer", name);
|
||||
if (name == "frequency_hz") {
|
||||
field["enabled_source"] =
|
||||
described_source<renderive::Frame_Observer_Snapshot>(
|
||||
"kernel_observer", "frequency_limit_enabled");
|
||||
field["disabled_label"] = "已关闭";
|
||||
} else if (name == "end_to_end_ns") {
|
||||
field["cell_class"] = "critical";
|
||||
}
|
||||
result.push_back(std::move(field));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Json consumer_feedback_fields() {
|
||||
const Json descriptor =
|
||||
adminive::to_descriptor_json<Json, Gallery_Consumer_Feedback_Snapshot>();
|
||||
Json result = Json::array();
|
||||
for (const Json& field : descriptor.at("fields")) {
|
||||
const std::string name = field.at("name");
|
||||
result.push_back(described_dashboard_field<Gallery_Consumer_Feedback_Snapshot>(
|
||||
"consumer_feedback", name));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Json observer_summary_fields() {
|
||||
Json result = observer_fields([](std::string_view name) {
|
||||
return !observer_header_field(name) && !observer_counter_field(name) &&
|
||||
!observer_detail_field(name);
|
||||
});
|
||||
for (Json& field : consumer_feedback_fields())
|
||||
result.push_back(std::move(field));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -189,18 +269,19 @@ Json dashboard_contract() {
|
||||
Json point_pair{{"label", "输入→绘制"}, {"format", "pair"},
|
||||
{"sources", Json::array({"data_shape.input_points", "data_shape.rendered_elements"})},
|
||||
{"separator", "→"}};
|
||||
Json bottleneck = mapped_dashboard_field("当前瓶颈", "low_latency_limit.current",
|
||||
"duration_enum", "limit_state");
|
||||
Json bottleneck = described_dashboard_field<renderive::Frame_Observer_Snapshot>(
|
||||
"kernel_observer", "limit_state");
|
||||
bottleneck["format"] = "duration_enum";
|
||||
bottleneck["duration_sources"] = {
|
||||
{"frequency_limited", "kernel_observer.target_interval_ns"},
|
||||
{"paint_limited", "kernel_observer.paint_duration_ns"},
|
||||
{"render_limited", "kernel_observer.render_duration_ns"},
|
||||
{"consumer_limited", "kernel_observer.consumer_interval_ns"}
|
||||
{"frequency_limited", described_source<renderive::Frame_Observer_Snapshot>(
|
||||
"kernel_observer", "target_interval_ns")},
|
||||
{"paint_limited", described_source<renderive::Frame_Observer_Snapshot>(
|
||||
"kernel_observer", "paint_duration_ns")},
|
||||
{"render_limited", described_source<renderive::Frame_Observer_Snapshot>(
|
||||
"kernel_observer", "render_duration_ns")},
|
||||
{"consumer_limited", described_source<renderive::Frame_Observer_Snapshot>(
|
||||
"kernel_observer", "consumer_interval_ns")}
|
||||
};
|
||||
Json configured_frequency = dashboard_field("配置频率", "kernel_observer.configured_frequency_hz",
|
||||
"frequency");
|
||||
configured_frequency["enabled_source"] = "kernel_observer.frequency_limit_enabled";
|
||||
configured_frequency["disabled_label"] = "已关闭";
|
||||
Json dashboard{
|
||||
{"value_maps", {
|
||||
{"enabled", {{"true", "启用"}, {"false", "关闭"}}},
|
||||
@@ -224,10 +305,13 @@ Json dashboard_contract() {
|
||||
dashboard_field("Core 渲染 ms", "performance.last_render_ms", "fixed", 2),
|
||||
dashboard_field("像素编码 ms", "performance.last_pixel_encode_ms", "fixed", 2),
|
||||
dashboard_field("响应负载 MB/s", "performance.pixel_payload_megabytes_per_second", "fixed", 1),
|
||||
dashboard_field("Kernel 待处理", "kernel_observer.pending_frame_count"),
|
||||
dashboard_field("Kernel 丢弃", "kernel_observer.dropped_frame_count"),
|
||||
described_dashboard_field<renderive::Frame_Observer_Snapshot>(
|
||||
"kernel_observer", "pending_frame_count"),
|
||||
described_dashboard_field<renderive::Frame_Observer_Snapshot>(
|
||||
"kernel_observer", "dropped_frame_count"),
|
||||
std::move(point_pair), std::move(bottleneck),
|
||||
dashboard_field("观察事件", "kernel_observer.last_event", "text"),
|
||||
described_dashboard_field<renderive::Frame_Observer_Snapshot>(
|
||||
"kernel_observer", "last_event"),
|
||||
dashboard_field("像素超时", "client_performance.frame_request_timeout_count"),
|
||||
dashboard_field("最近像素龄", "client_performance.last_pixel_receive_age_ms", "milliseconds", 0)
|
||||
})}
|
||||
@@ -238,66 +322,45 @@ Json dashboard_contract() {
|
||||
}},
|
||||
{"limits", {
|
||||
{"aria_label", "低延迟限速来源"}, {"title", "限速来源"},
|
||||
{"current_source", "low_latency_limit.current"},
|
||||
{"current_source", described_source<renderive::Frame_Observer_Snapshot>(
|
||||
"kernel_observer", "limit_state")},
|
||||
{"active_label", "当前瓶颈"}, {"inactive_label", "未受限"},
|
||||
{"disabled_label", "已关闭"},
|
||||
{"fields", Json::array({
|
||||
{{"label", "Kernel 用户频率"}, {"active_value", "frequency_limited"},
|
||||
{"duration_source", "kernel_observer.target_interval_ns"},
|
||||
{"enabled_source", "kernel_observer.frequency_limit_enabled"}},
|
||||
{"duration_source", described_source<renderive::Frame_Observer_Snapshot>(
|
||||
"kernel_observer", "target_interval_ns")},
|
||||
{"enabled_source", described_source<renderive::Frame_Observer_Snapshot>(
|
||||
"kernel_observer", "frequency_limit_enabled")}},
|
||||
{{"label", "Kernel PaintEvent"}, {"active_value", "paint_limited"},
|
||||
{"duration_source", "kernel_observer.paint_duration_ns"}},
|
||||
{"duration_source", described_source<renderive::Frame_Observer_Snapshot>(
|
||||
"kernel_observer", "paint_duration_ns")}},
|
||||
{{"label", "Kernel 后台渲染"}, {"active_value", "render_limited"},
|
||||
{"duration_source", "kernel_observer.render_duration_ns"}},
|
||||
{"duration_source", described_source<renderive::Frame_Observer_Snapshot>(
|
||||
"kernel_observer", "render_duration_ns")}},
|
||||
{{"label", "消费者反馈"}, {"active_value", "consumer_limited"},
|
||||
{"duration_source", "kernel_observer.consumer_interval_ns"},
|
||||
{"enabled_source", "kernel_observer.consumer_feedback_enabled"}}
|
||||
{"duration_source", described_source<renderive::Frame_Observer_Snapshot>(
|
||||
"kernel_observer", "consumer_interval_ns")},
|
||||
{"enabled_source", described_source<renderive::Frame_Observer_Snapshot>(
|
||||
"kernel_observer", "consumer_feedback_enabled")}}
|
||||
})}
|
||||
}},
|
||||
{"observer", {
|
||||
{"aria_label", "Kernel 低延迟全量统计"},
|
||||
{"header", {
|
||||
{"prefix", "KERNEL"}, {"suffix", "OBSERVER"}, {"event_label", "事件"},
|
||||
{"mode", dashboard_field("", "kernel_observer.mode", "text")},
|
||||
{"limit", mapped_dashboard_field("", "kernel_observer.limit_state", "enum", "limit_state")},
|
||||
{"event", dashboard_field("", "kernel_observer.last_event", "text")}
|
||||
{"mode", described_dashboard_field<renderive::Frame_Observer_Snapshot>(
|
||||
"kernel_observer", "mode")},
|
||||
{"limit", described_dashboard_field<renderive::Frame_Observer_Snapshot>(
|
||||
"kernel_observer", "limit_state")},
|
||||
{"event", described_dashboard_field<renderive::Frame_Observer_Snapshot>(
|
||||
"kernel_observer", "last_event")}
|
||||
}},
|
||||
{"sections", Json::array({
|
||||
{{"class_name", "observer-counters"}, {"fields", Json::array({
|
||||
std::move(configured_frequency),
|
||||
dashboard_field("观察次数", "kernel_observer.observation_count"),
|
||||
dashboard_field("最新序号", "kernel_observer.latest_sequence"),
|
||||
dashboard_field("发布", "kernel_observer.produced_frame_count"),
|
||||
dashboard_field("完成", "kernel_observer.consumed_frame_count"),
|
||||
dashboard_field("待处理", "kernel_observer.pending_frame_count"),
|
||||
dashboard_field("丢弃", "kernel_observer.dropped_frame_count"),
|
||||
dashboard_field("失败", "kernel_observer.failed_operation_count")
|
||||
})}},
|
||||
{{"class_name", "latency-summary"}, {"fields", Json::array({
|
||||
dashboard_field("目标间隔", "kernel_observer.target_interval_ns", "nanoseconds"),
|
||||
dashboard_field("PaintEvent", "kernel_observer.paint_duration_ns", "nanoseconds"),
|
||||
dashboard_field("后台渲染", "kernel_observer.render_duration_ns", "nanoseconds"),
|
||||
dashboard_field("内部瓶颈", "kernel_observer.bottleneck_duration_ns", "nanoseconds"),
|
||||
mapped_dashboard_field("消费者总开关", "kernel_observer.consumer_feedback_master_enabled", "enum", "enabled"),
|
||||
mapped_dashboard_field("Kernel 反馈有效", "kernel_observer.consumer_feedback_enabled", "enum", "enabled"),
|
||||
mapped_dashboard_field("像素响应反馈", "kernel_observer.consumer_pixel_feedback_enabled", "enum", "enabled"),
|
||||
mapped_dashboard_field("浏览器呈现反馈", "kernel_observer.consumer_presentation_feedback_enabled", "enum", "enabled"),
|
||||
mapped_dashboard_field("手动消费者反馈", "kernel_observer.consumer_manual_feedback_enabled", "enum", "enabled"),
|
||||
dashboard_field("手动消费者 FPS", "kernel_observer.consumer_manual_fps", "fps", 2),
|
||||
mapped_dashboard_field("生效反馈来源", "kernel_observer.consumer_feedback_source", "flags", "consumer_feedback_source"),
|
||||
dashboard_field("像素响应周期", "kernel_observer.consumer_pixel_interval_ns", "nanoseconds"),
|
||||
dashboard_field("浏览器呈现周期", "kernel_observer.consumer_presentation_interval_ns", "nanoseconds"),
|
||||
dashboard_field("手动消费者周期", "kernel_observer.consumer_manual_interval_ns", "nanoseconds"),
|
||||
dashboard_field("消费者原始采样", "kernel_observer.consumer_sample_interval_ns", "nanoseconds"),
|
||||
dashboard_field("消费者平滑周期", "kernel_observer.consumer_smoothed_interval_ns", "nanoseconds"),
|
||||
dashboard_field("消费者抖动", "kernel_observer.consumer_variation_ns", "nanoseconds"),
|
||||
dashboard_field("消费者安全期限", "kernel_observer.consumer_safety_interval_ns", "nanoseconds"),
|
||||
dashboard_field("消费者限速周期", "kernel_observer.consumer_interval_ns", "nanoseconds"),
|
||||
dashboard_field("消费者限速 FPS", "kernel_observer.consumer_interval_ns", "inverse_fps", 2),
|
||||
dashboard_field("下次刷新", "kernel_observer.next_refresh_interval_ns", "nanoseconds"),
|
||||
Json{{"label", "端到端延迟"}, {"source", "kernel_observer.end_to_end_ns"},
|
||||
{"format", "nanoseconds"}, {"cell_class", "critical"}}
|
||||
})}},
|
||||
{{"class_name", "observer-counters"},
|
||||
{"fields", observer_fields(observer_counter_field)}},
|
||||
{{"class_name", "latency-summary"},
|
||||
{"fields", observer_summary_fields()}},
|
||||
{{"class_name", "client-summary"}, {"aria_label", "浏览器消费者反馈全量统计"},
|
||||
{"fields", Json::array({
|
||||
dashboard_field("RAF 最新周期", "client_performance.display_interval_latest_ms", "milliseconds", 3),
|
||||
@@ -316,41 +379,35 @@ Json dashboard_contract() {
|
||||
dashboard_field("最近变化龄", "client_performance.last_pixel_change_age_ms", "milliseconds", 3)
|
||||
})}},
|
||||
{{"class_name", "latency-details"}, {"aria_label", "Kernel 各阶段等待耗时"},
|
||||
{"fields", Json::array({
|
||||
dashboard_field("Painter Lease 等待", "kernel_observer.paint_lease_wait_ns", "nanoseconds"),
|
||||
dashboard_field("Paint State 等待", "kernel_observer.paint_state_wait_ns", "nanoseconds"),
|
||||
dashboard_field("Publish State 等待", "kernel_observer.publish_state_wait_ns", "nanoseconds"),
|
||||
dashboard_field("Ready 等待", "kernel_observer.ready_wait_ns", "nanoseconds"),
|
||||
dashboard_field("开始渲染时帧龄", "kernel_observer.frame_age_at_render_ns", "nanoseconds"),
|
||||
dashboard_field("Render Lease 等待", "kernel_observer.render_lease_wait_ns", "nanoseconds"),
|
||||
dashboard_field("Render State 等待", "kernel_observer.render_state_wait_ns", "nanoseconds"),
|
||||
dashboard_field("Render Finish 等待", "kernel_observer.render_finish_state_wait_ns", "nanoseconds"),
|
||||
dashboard_field("回放队列等待", "kernel_observer.queue_wait_ns", "nanoseconds")
|
||||
})}}
|
||||
{"fields", observer_fields(observer_detail_field)}}
|
||||
})}
|
||||
}}
|
||||
};
|
||||
dashboard["observer"]["descriptor"] =
|
||||
adminive::to_descriptor_json<Json, renderive::Frame_Observer_Snapshot>();
|
||||
dashboard["observer"]["consumer_feedback_descriptor"] =
|
||||
adminive::to_descriptor_json<Json, Gallery_Consumer_Feedback_Snapshot>();
|
||||
return dashboard;
|
||||
}
|
||||
|
||||
Json frame_mode_contract(Gallery_Frame_Mode mode) {
|
||||
switch (mode) {
|
||||
case Gallery_Frame_Mode::Manual:
|
||||
return {{"id", "manual"}, {"title", "手动刷新"},
|
||||
return {{"id", gallery_enum_id(mode)}, {"title", "手动刷新"},
|
||||
{"strategy", "Manual_Refresh_Strategy"},
|
||||
{"description", "显式准备、刷新和渲染;页面不会自动拉取像素。"},
|
||||
{"automatic", false}, {"request_after_response", false},
|
||||
{"request_on_animation_frame", false}, {"observer_visible", false},
|
||||
{"frame_button_label", "手动刷新一帧"}, {"accent", "#ffd166aa"}, {"order", 10}};
|
||||
case Gallery_Frame_Mode::Low_Latency:
|
||||
return {{"id", "low_latency"}, {"title", "低延迟"},
|
||||
return {{"id", gallery_enum_id(mode)}, {"title", "低延迟"},
|
||||
{"strategy", "Low_Latency_Strategy"},
|
||||
{"description", "以最大 FPS 自动发布并消费最新帧,可观测丢帧与端到端延迟。"},
|
||||
{"automatic", true}, {"request_after_response", true},
|
||||
{"request_on_animation_frame", false}, {"observer_visible", true},
|
||||
{"frame_button_label", "立即刷新"}, {"accent", "#45ddbeaa"}, {"order", 20}};
|
||||
case Gallery_Frame_Mode::Playback:
|
||||
return {{"id", "playback"}, {"title", "回放队列"},
|
||||
return {{"id", gallery_enum_id(mode)}, {"title", "回放队列"},
|
||||
{"strategy", "Flow_Refresh_Strategy"},
|
||||
{"description", "所有帧按队列顺序入队和消费,可观测队深与排队时间。"},
|
||||
{"automatic", true}, {"request_after_response", true},
|
||||
@@ -368,7 +425,8 @@ std::string Gallery_Protocol::catalog_json() {
|
||||
result["transport"] = {{"events", "websocket-text"}, {"pixels", "websocket-binary-rvp1"},
|
||||
{"http_api", false}, {"socket_per_canvas", true}};
|
||||
result["navigation"] = {
|
||||
{"default_mode", "low_latency"}, {"all_categories_label", "全部"},
|
||||
{"default_mode", gallery_enum_id(Gallery_Frame_Mode::Low_Latency)},
|
||||
{"all_categories_label", "全部"},
|
||||
{"catalog_loaded_text", "Kernel 策略目录已加载"},
|
||||
{"hero_eyebrow", "REAL KERNEL STRATEGIES"},
|
||||
{"hero_title", "对称策略页面,同一批控件,直接比较"}
|
||||
@@ -377,9 +435,7 @@ std::string Gallery_Protocol::catalog_json() {
|
||||
result["case_descriptor"] =
|
||||
adminive::to_descriptor_json<Json, gallery_detail::Case_Model>();
|
||||
result["frame_modes"] = Json::array();
|
||||
constexpr Gallery_Frame_Mode frame_modes[]{Gallery_Frame_Mode::Manual,
|
||||
Gallery_Frame_Mode::Low_Latency,
|
||||
Gallery_Frame_Mode::Playback};
|
||||
constexpr auto frame_modes = magic_enum::enum_values<Gallery_Frame_Mode>();
|
||||
for (const auto mode : frame_modes)
|
||||
result["frame_modes"].push_back(frame_mode_contract(mode));
|
||||
result["cases"] = Json::array();
|
||||
@@ -392,7 +448,7 @@ std::string Gallery_Protocol::catalog_json() {
|
||||
const std::size_t controls = gallery_control_count<Gallery_Session_Control>() +
|
||||
gallery_renderable_control_count(item.id);
|
||||
for (const auto mode : frame_modes) {
|
||||
const std::string key(frame_mode_id(mode));
|
||||
const std::string key = gallery_enum_id(mode);
|
||||
const std::size_t actions =
|
||||
gallery_detail::registered_actions(item.id, mode).size();
|
||||
entry["control_count_by_mode"][key] = controls;
|
||||
@@ -412,7 +468,7 @@ std::string Gallery_Protocol::catalog_json() {
|
||||
{"manual_control_count", controls_total},
|
||||
{"manual_action_count", actions_total},
|
||||
{"frequency_modes", Json::array({"spectrum", "afterglow", "sweep_spectrum"})},
|
||||
{"image_interpolation_modes", Json::array({"Nearest", "Bilinear", "Bicubic"})}};
|
||||
{"image_interpolation_modes", magic_enum::enum_names<Image_Interpolation_Mode>()}};
|
||||
return result.dump();
|
||||
}
|
||||
|
||||
@@ -493,7 +549,8 @@ std::optional<Gallery_Open_Request> Gallery_Protocol::open_request(std::string_v
|
||||
if (!is_case(value) || !request.contains("frame_mode") ||
|
||||
!request.at("frame_mode").is_string())
|
||||
return std::nullopt;
|
||||
const auto mode = parse_frame_mode(request.at("frame_mode").get<std::string>());
|
||||
const auto mode = gallery_enum_cast<Gallery_Frame_Mode>(
|
||||
request.at("frame_mode").get<std::string>());
|
||||
return mode ? std::optional<Gallery_Open_Request>(Gallery_Open_Request{value, *mode})
|
||||
: std::nullopt;
|
||||
} catch (const std::exception&) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "Gallery_Renderable_Types.h"
|
||||
|
||||
#include "adminive/adminive.hpp"
|
||||
#include "adminive/adapters/magic_enum.hpp"
|
||||
#include "adminive/adapters/nlohmann_json.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -11,7 +12,6 @@
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
@@ -22,86 +22,6 @@ namespace renderive::web::gallery_adminive {
|
||||
enum class Number_Locale_Choice { Point, Comma };
|
||||
enum class Color_Map_Choice { Spectrum, Ember, Grayscale };
|
||||
|
||||
template <class Enum>
|
||||
struct Enum_Metadata;
|
||||
|
||||
#define RENDERIVE_ENUM_METADATA(Type, ...) \
|
||||
template <> struct Enum_Metadata<Type> { static constexpr auto values = std::array{__VA_ARGS__}; }
|
||||
|
||||
RENDERIVE_ENUM_METADATA(Orientation,
|
||||
std::pair{Orientation::Horizontal, std::string_view{"Horizontal"}},
|
||||
std::pair{Orientation::Vertical, std::string_view{"Vertical"}});
|
||||
RENDERIVE_ENUM_METADATA(Renderable_Cache_Mode,
|
||||
std::pair{Renderable_Cache_Mode::Direct, std::string_view{"Direct"}},
|
||||
std::pair{Renderable_Cache_Mode::Local_Pixel, std::string_view{"Local_Pixel"}});
|
||||
RENDERIVE_ENUM_METADATA(Line_Interpolation_Mode,
|
||||
std::pair{Line_Interpolation_Mode::Nearest_Sample, std::string_view{"Nearest_Sample"}},
|
||||
std::pair{Line_Interpolation_Mode::Linear_Value, std::string_view{"Linear_Value"}},
|
||||
std::pair{Line_Interpolation_Mode::Linear_Power_Domain, std::string_view{"Linear_Power_Domain"}},
|
||||
std::pair{Line_Interpolation_Mode::Step_Left, std::string_view{"Step_Left"}},
|
||||
std::pair{Line_Interpolation_Mode::Step_Right, std::string_view{"Step_Right"}},
|
||||
std::pair{Line_Interpolation_Mode::Cubic_Value, std::string_view{"Cubic_Value"}});
|
||||
RENDERIVE_ENUM_METADATA(Image_Interpolation_Mode,
|
||||
std::pair{Image_Interpolation_Mode::Nearest, std::string_view{"Nearest"}},
|
||||
std::pair{Image_Interpolation_Mode::Bilinear, std::string_view{"Bilinear"}},
|
||||
std::pair{Image_Interpolation_Mode::Bicubic, std::string_view{"Bicubic"}});
|
||||
RENDERIVE_ENUM_METADATA(Constellation_Diagram_Type,
|
||||
std::pair{Constellation_Diagram_Type::Psk4, std::string_view{"PSK4"}},
|
||||
std::pair{Constellation_Diagram_Type::Psk8, std::string_view{"PSK8"}},
|
||||
std::pair{Constellation_Diagram_Type::Psk16, std::string_view{"PSK16"}});
|
||||
RENDERIVE_ENUM_METADATA(Line_Style,
|
||||
std::pair{Line_Style::None, std::string_view{"None"}},
|
||||
std::pair{Line_Style::Solid, std::string_view{"Solid"}},
|
||||
std::pair{Line_Style::Dash, std::string_view{"Dash"}},
|
||||
std::pair{Line_Style::Dot, std::string_view{"Dot"}});
|
||||
RENDERIVE_ENUM_METADATA(Line_Cap,
|
||||
std::pair{Line_Cap::Butt, std::string_view{"Butt"}},
|
||||
std::pair{Line_Cap::Square, std::string_view{"Square"}},
|
||||
std::pair{Line_Cap::Round, std::string_view{"Round"}});
|
||||
RENDERIVE_ENUM_METADATA(Line_Join,
|
||||
std::pair{Line_Join::Miter, std::string_view{"Miter"}},
|
||||
std::pair{Line_Join::Bevel, std::string_view{"Bevel"}},
|
||||
std::pair{Line_Join::Round, std::string_view{"Round"}});
|
||||
RENDERIVE_ENUM_METADATA(Brush_Style,
|
||||
std::pair{Brush_Style::None, std::string_view{"None"}},
|
||||
std::pair{Brush_Style::Solid, std::string_view{"Solid"}});
|
||||
RENDERIVE_ENUM_METADATA(Number_Locale_Choice,
|
||||
std::pair{Number_Locale_Choice::Point, std::string_view{"."}},
|
||||
std::pair{Number_Locale_Choice::Comma, std::string_view{","}});
|
||||
RENDERIVE_ENUM_METADATA(Color_Map_Choice,
|
||||
std::pair{Color_Map_Choice::Spectrum, std::string_view{"Spectrum"}},
|
||||
std::pair{Color_Map_Choice::Ember, std::string_view{"Ember"}},
|
||||
std::pair{Color_Map_Choice::Grayscale, std::string_view{"Grayscale"}});
|
||||
|
||||
#undef RENDERIVE_ENUM_METADATA
|
||||
|
||||
template <class Enum>
|
||||
struct Actual_Enum_Adapter {
|
||||
static std::vector<Enum> values() {
|
||||
std::vector<Enum> result;
|
||||
result.reserve(Enum_Metadata<Enum>::values.size());
|
||||
for (const auto& [value, name] : Enum_Metadata<Enum>::values)
|
||||
result.push_back(value);
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::string_view name(Enum value) {
|
||||
for (const auto& [candidate, name] : Enum_Metadata<Enum>::values) {
|
||||
if (candidate == value)
|
||||
return name;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static std::optional<Enum> cast(std::string_view name) {
|
||||
for (const auto& [value, candidate] : Enum_Metadata<Enum>::values) {
|
||||
if (candidate == name)
|
||||
return value;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
inline Color blend(Color first, Color second, double amount) {
|
||||
const auto channel = [amount](std::uint8_t a, std::uint8_t b) {
|
||||
return static_cast<std::uint8_t>(std::clamp(
|
||||
@@ -111,15 +31,15 @@ inline Color blend(Color first, Color second, double amount) {
|
||||
channel(first.b, second.b), channel(first.a, second.a)};
|
||||
}
|
||||
|
||||
inline Color_Map color_map(std::string_view palette) {
|
||||
inline Color_Map color_map(Color_Map_Choice palette) {
|
||||
std::array<Color, 5> stops{
|
||||
Color{5, 10, 25, 255}, Color{20, 52, 112, 255}, Color{39, 196, 181, 255},
|
||||
Color{242, 201, 76, 255}, Color{255, 76, 106, 255}
|
||||
};
|
||||
if (palette == "Ember") {
|
||||
if (palette == Color_Map_Choice::Ember) {
|
||||
stops = {Color{10, 5, 8, 255}, Color{62, 18, 30, 255}, Color{153, 47, 39, 255},
|
||||
Color{244, 132, 48, 255}, Color{255, 238, 166, 255}};
|
||||
} else if (palette == "Grayscale") {
|
||||
} else if (palette == Color_Map_Choice::Grayscale) {
|
||||
stops = {Color{0, 0, 0, 255}, Color{52, 52, 52, 255}, Color{112, 112, 112, 255},
|
||||
Color{188, 188, 188, 255}, Color{255, 255, 255, 255}};
|
||||
}
|
||||
@@ -134,14 +54,12 @@ inline Color_Map color_map(std::string_view palette) {
|
||||
return Color_Map(std::move(colors));
|
||||
}
|
||||
|
||||
inline std::string color_map_name(const Color_Map& value) {
|
||||
for (const std::string_view name : {std::string_view{"Spectrum"},
|
||||
std::string_view{"Ember"},
|
||||
std::string_view{"Grayscale"}}) {
|
||||
if (value.colors() == color_map(name).colors())
|
||||
return std::string(name);
|
||||
inline Color_Map_Choice color_map_choice(const Color_Map& value) {
|
||||
for (const Color_Map_Choice choice : magic_enum::enum_values<Color_Map_Choice>()) {
|
||||
if (value.colors() == color_map(choice).colors())
|
||||
return choice;
|
||||
}
|
||||
return "Spectrum";
|
||||
return Color_Map_Choice::Spectrum;
|
||||
}
|
||||
|
||||
template <auto Member>
|
||||
@@ -243,31 +161,6 @@ struct Value_Adapter<::Validated_Value<T, Validator>, Json>
|
||||
static void write(Storage& target, T value) { target = std::move(value); }
|
||||
};
|
||||
|
||||
#define RENDERIVE_ENUM_ADAPTER(Type) \
|
||||
template <> struct Enum_Adapter<renderive::Type> \
|
||||
: renderive::web::gallery_adminive::Actual_Enum_Adapter<renderive::Type> {}
|
||||
|
||||
RENDERIVE_ENUM_ADAPTER(Orientation);
|
||||
RENDERIVE_ENUM_ADAPTER(Renderable_Cache_Mode);
|
||||
RENDERIVE_ENUM_ADAPTER(Line_Interpolation_Mode);
|
||||
RENDERIVE_ENUM_ADAPTER(Image_Interpolation_Mode);
|
||||
RENDERIVE_ENUM_ADAPTER(Constellation_Diagram_Type);
|
||||
RENDERIVE_ENUM_ADAPTER(Line_Style);
|
||||
RENDERIVE_ENUM_ADAPTER(Line_Cap);
|
||||
RENDERIVE_ENUM_ADAPTER(Line_Join);
|
||||
RENDERIVE_ENUM_ADAPTER(Brush_Style);
|
||||
#undef RENDERIVE_ENUM_ADAPTER
|
||||
|
||||
template <>
|
||||
struct Enum_Adapter<renderive::web::gallery_adminive::Number_Locale_Choice>
|
||||
: renderive::web::gallery_adminive::Actual_Enum_Adapter<
|
||||
renderive::web::gallery_adminive::Number_Locale_Choice> {};
|
||||
|
||||
template <>
|
||||
struct Enum_Adapter<renderive::web::gallery_adminive::Color_Map_Choice>
|
||||
: renderive::web::gallery_adminive::Actual_Enum_Adapter<
|
||||
renderive::web::gallery_adminive::Color_Map_Choice> {};
|
||||
|
||||
template <class Json>
|
||||
struct Value_Adapter<renderive::Color, Json> {
|
||||
using value_type = std::string;
|
||||
@@ -328,17 +221,10 @@ struct Value_Adapter<renderive::Color_Map, Json> {
|
||||
using Choice = renderive::web::gallery_adminive::Color_Map_Choice;
|
||||
using value_type = Choice;
|
||||
static Choice read(const renderive::Color_Map& value) {
|
||||
const auto name = renderive::web::gallery_adminive::color_map_name(value);
|
||||
if (name == "Ember")
|
||||
return Choice::Ember;
|
||||
if (name == "Grayscale")
|
||||
return Choice::Grayscale;
|
||||
return Choice::Spectrum;
|
||||
return renderive::web::gallery_adminive::color_map_choice(value);
|
||||
}
|
||||
static void write(renderive::Color_Map& target, Choice value) {
|
||||
const std::string_view name = value == Choice::Ember ? "Ember" :
|
||||
value == Choice::Grayscale ? "Grayscale" : "Spectrum";
|
||||
target = renderive::web::gallery_adminive::color_map(name);
|
||||
target = renderive::web::gallery_adminive::color_map(value);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "Web_Event_Adapter.h"
|
||||
#include "Gallery_Enum.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
@@ -28,27 +29,13 @@ Keyboard_Modifier modifiers(const Json::Value& root) {
|
||||
|
||||
Mouse_Button mouse_button(const Json::Value& root) {
|
||||
const std::string value = root["button"].isString() ? root["button"].asString() : "none";
|
||||
if (value == "left")
|
||||
return Mouse_Button::Left;
|
||||
if (value == "right")
|
||||
return Mouse_Button::Right;
|
||||
if (value == "middle")
|
||||
return Mouse_Button::Middle;
|
||||
return Mouse_Button::None;
|
||||
return gallery_enum_cast<Mouse_Button>(value).value_or(Mouse_Button::None);
|
||||
}
|
||||
|
||||
Key key(const Json::Value& root) {
|
||||
const std::string value = root["key"].isString() ? root["key"].asString() : "";
|
||||
if (value == "Escape")
|
||||
return Key::Escape;
|
||||
if (value == "Enter")
|
||||
return Key::Enter;
|
||||
if (value == "Space")
|
||||
return Key::Space;
|
||||
if (value == "Delete")
|
||||
return Key::Delete;
|
||||
if (value == "Backspace")
|
||||
return Key::Backspace;
|
||||
if (const auto direct = magic_enum::enum_cast<Key>(value))
|
||||
return *direct;
|
||||
if (value == "ArrowLeft")
|
||||
return Key::Left;
|
||||
if (value == "ArrowRight")
|
||||
@@ -136,20 +123,14 @@ std::optional<Web_Transport_Events> Web_Transport_Event_Decoder::decode(
|
||||
return Viewport_Resize{{static_cast<int>(std::clamp(*width, 240.0, 1920.0)),
|
||||
static_cast<int>(std::clamp(*height, 180.0, 1200.0))}};
|
||||
}
|
||||
Gallery_Request_Kind kind;
|
||||
if (type == "gallery_catalog")
|
||||
kind = Gallery_Request_Kind::Catalog;
|
||||
else if (type == "gallery_open")
|
||||
kind = Gallery_Request_Kind::Open;
|
||||
else if (type == "gallery_patch")
|
||||
kind = Gallery_Request_Kind::Patch;
|
||||
else if (type == "gallery_action")
|
||||
kind = Gallery_Request_Kind::Action;
|
||||
else if (type == "gallery_observe")
|
||||
kind = Gallery_Request_Kind::Observe;
|
||||
else
|
||||
constexpr std::string_view gallery_prefix = "gallery_";
|
||||
if (!std::string_view(type).starts_with(gallery_prefix))
|
||||
return std::nullopt;
|
||||
return Gallery_Request{kind, std::string(source)};
|
||||
const auto kind = gallery_enum_cast<Gallery_Request_Kind>(
|
||||
std::string_view(type).substr(gallery_prefix.size()));
|
||||
return kind ? std::optional<Web_Transport_Events>(
|
||||
Gallery_Request{*kind, std::string(source)})
|
||||
: std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<Render_2D_Web_Events> Render_2D_Web_Event_Decoder::decode(
|
||||
@@ -183,15 +164,10 @@ std::optional<Web_Demo_Control_Events> Web_Demo_Control_Decoder::decode(
|
||||
const std::string control = root["control"].asString();
|
||||
if (control == "mode" && root["value"].isString()) {
|
||||
const std::string value = root["value"].asString();
|
||||
if (value == "AM")
|
||||
return Set_Demo_Mode{Demo_Mode::Am};
|
||||
if (value == "FM")
|
||||
return Set_Demo_Mode{Demo_Mode::Fm};
|
||||
if (value == "USB")
|
||||
return Set_Demo_Mode{Demo_Mode::Usb};
|
||||
if (value == "LSB")
|
||||
return Set_Demo_Mode{Demo_Mode::Lsb};
|
||||
return std::nullopt;
|
||||
const auto mode = magic_enum::enum_cast<Demo_Mode>(
|
||||
value, magic_enum::case_insensitive);
|
||||
return mode ? std::optional<Web_Demo_Control_Events>(Set_Demo_Mode{*mode})
|
||||
: std::nullopt;
|
||||
}
|
||||
if (control == "center_frequency_mhz") {
|
||||
const auto value = finite_number(root, "value");
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "../app/Gallery_Plot_Session.h"
|
||||
#include "../app/Gallery_Observer_Adminive.h"
|
||||
#include "../app/Gallery_Protocol.h"
|
||||
#include "../app/Gallery_Renderables.h"
|
||||
#include "../app/Pixel_Frame.h"
|
||||
@@ -268,6 +269,30 @@ TEST(RenderiveWebGallery, CatalogOwnsDashboardFieldsAndDynamicBehavior) {
|
||||
EXPECT_TRUE(action->at("request_frame"));
|
||||
}
|
||||
|
||||
TEST(RenderiveWebGallery, ObserverTelemetryAndDashboardUseAdminiveDescriptors) {
|
||||
const auto catalog = parse_json(Gallery_Protocol::catalog_json());
|
||||
const auto& observer_contract = catalog.at("dashboard").at("observer");
|
||||
const auto& observer_descriptor = observer_contract.at("descriptor");
|
||||
const auto& feedback_descriptor =
|
||||
observer_contract.at("consumer_feedback_descriptor");
|
||||
|
||||
Gallery_Plot_Session session;
|
||||
const auto opened = response_json(session.handle(gallery_request(
|
||||
Gallery_Request_Kind::Open, open_message("spectrum", "manual"))));
|
||||
const auto& telemetry = opened.at("telemetry");
|
||||
const auto& observer = telemetry.at("kernel_observer");
|
||||
const auto& feedback = telemetry.at("consumer_feedback");
|
||||
|
||||
EXPECT_EQ(observer.size(), observer_descriptor.at("fields").size());
|
||||
for (const auto& field : observer_descriptor.at("fields"))
|
||||
EXPECT_TRUE(observer.contains(field.at("name").get<std::string>()));
|
||||
EXPECT_EQ(feedback.size(), feedback_descriptor.at("fields").size());
|
||||
for (const auto& field : feedback_descriptor.at("fields"))
|
||||
EXPECT_TRUE(feedback.contains(field.at("name").get<std::string>()));
|
||||
EXPECT_EQ(observer.at("mode"), "manual");
|
||||
EXPECT_FALSE(telemetry.contains("low_latency_limit"));
|
||||
}
|
||||
|
||||
TEST(RenderiveWebGallery, ActionsComeFromStructiveTypeMetadata) {
|
||||
using Spectrum_Schema = structive::type_descriptor_schema_t<Gallery_Spectrum>;
|
||||
using Time_Axis_Schema = structive::type_descriptor_schema_t<Gallery_Time_Axis>;
|
||||
@@ -719,20 +744,21 @@ TEST(RenderiveWebGallery, ThreeFrameStrategiesExposeTheirRealActionsAndObservers
|
||||
ASSERT_TRUE(low_latency.handle(Frame_Request{}));
|
||||
const auto low_telemetry = observe(low_latency);
|
||||
EXPECT_EQ(low_telemetry.at("frame_mode"), "low_latency");
|
||||
EXPECT_GT(low_telemetry.at("kernel_observer").at("observation_count").get<std::uint64_t>(), 0U);
|
||||
EXPECT_TRUE(low_telemetry.contains("render_fps"));
|
||||
ASSERT_TRUE(low_telemetry.contains("low_latency_limit"));
|
||||
const auto& limit = low_telemetry.at("low_latency_limit");
|
||||
EXPECT_FALSE(limit.at("consumer_limited").get<bool>());
|
||||
EXPECT_FALSE(limit.at("frequency_limited").get<bool>());
|
||||
EXPECT_TRUE(limit.at("paint_limited").get<bool>() || limit.at("render_limited").get<bool>());
|
||||
EXPECT_EQ(limit.at("target_interval_ns"), 1);
|
||||
EXPECT_EQ(limit.at("consumer_interval_ns"), 0);
|
||||
EXPECT_GT(limit.at("bottleneck_duration_ns").get<std::uint64_t>(), 1U);
|
||||
EXPECT_TRUE(low_telemetry.at("kernel_observer").contains("paint_lease_wait_ns"));
|
||||
EXPECT_TRUE(low_telemetry.at("kernel_observer").contains("render_finish_state_wait_ns"));
|
||||
const auto& low_observer = low_telemetry.at("kernel_observer");
|
||||
EXPECT_GT(low_observer.at("observation_count").get<std::uint64_t>(), 0U);
|
||||
EXPECT_FALSE(low_telemetry.contains("render_fps"));
|
||||
EXPECT_FALSE(low_telemetry.contains("low_latency_limit"));
|
||||
EXPECT_NE(low_observer.at("limit_state"), "consumer_limited");
|
||||
EXPECT_NE(low_observer.at("limit_state"), "frequency_limited");
|
||||
EXPECT_TRUE(low_observer.at("limit_state") == "paint_limited" ||
|
||||
low_observer.at("limit_state") == "render_limited");
|
||||
EXPECT_EQ(low_observer.at("target_interval_ns"), 1);
|
||||
EXPECT_EQ(low_observer.at("consumer_interval_ns"), 0);
|
||||
EXPECT_GT(low_observer.at("bottleneck_duration_ns").get<std::uint64_t>(), 1U);
|
||||
EXPECT_TRUE(low_observer.contains("paint_lease_wait_ns"));
|
||||
EXPECT_TRUE(low_observer.contains("render_finish_state_wait_ns"));
|
||||
constexpr std::array<std::string_view, 33> observer_fields{
|
||||
"mode", "last_event", "limit_state", "configured_frequency_hz", "observation_count",
|
||||
"mode", "last_event", "limit_state", "frequency_hz", "observation_count",
|
||||
"produced_frame_count", "consumed_frame_count", "dropped_frame_count",
|
||||
"failed_operation_count", "pending_frame_count", "latest_sequence",
|
||||
"paint_duration_ns", "render_duration_ns", "target_interval_ns", "frequency_limit_enabled",
|
||||
@@ -744,7 +770,7 @@ TEST(RenderiveWebGallery, ThreeFrameStrategiesExposeTheirRealActionsAndObservers
|
||||
"render_finish_state_wait_ns", "queue_wait_ns", "end_to_end_ns"
|
||||
};
|
||||
for (const auto field : observer_fields)
|
||||
EXPECT_TRUE(low_telemetry.at("kernel_observer").contains(field)) << field;
|
||||
EXPECT_TRUE(low_observer.contains(field)) << field;
|
||||
|
||||
Gallery_Plot_Session playback;
|
||||
ASSERT_TRUE(playback.handle(gallery_request(
|
||||
@@ -838,7 +864,7 @@ TEST(RenderiveWebGallery, LowLatencyStrategyKeepsForegroundResponsiveUnderAggres
|
||||
const auto telemetry = observe_telemetry(session);
|
||||
EXPECT_LT(std::chrono::steady_clock::now() - started,
|
||||
std::chrono::milliseconds(250)) << iteration;
|
||||
EXPECT_EQ(telemetry.at("kernel_observer").at("configured_frequency_hz"), 1000.0);
|
||||
EXPECT_EQ(telemetry.at("kernel_observer").at("frequency_hz"), 1000.0);
|
||||
if (iteration == 0)
|
||||
first_pixels = frame->payload;
|
||||
last_pixels = frame->payload;
|
||||
@@ -949,7 +975,7 @@ TEST(RenderiveWebGallery, WebRuntimeMetricsPublishKernelSmoothedConsumerFeedback
|
||||
Gallery_Request_Kind::Observe,
|
||||
R"({"category":"event","type":"gallery_observe","client_metrics":{"transport_fps":50,"presentation_fps":50,"websocket_buffered_bytes":0,"changed_pixel_frames":10,"duplicate_pixel_frames":0,"frame_request_timeout_count":0,"frame_round_trip_ms":80,"display_interval_ms":20,"overwritten_pixel_frames":0,"last_pixel_receive_age_ms":1,"last_pixel_change_age_ms":1}})")));
|
||||
auto observer = observed.at("telemetry").at("kernel_observer");
|
||||
auto limit = observed.at("telemetry").at("low_latency_limit");
|
||||
auto feedback = observed.at("telemetry").at("consumer_feedback");
|
||||
EXPECT_TRUE(observer.at("frequency_limit_enabled").get<bool>());
|
||||
EXPECT_TRUE(observer.at("consumer_feedback_enabled").get<bool>());
|
||||
EXPECT_EQ(observer.at("consumer_sample_interval_ns"), 20'000'000);
|
||||
@@ -958,7 +984,7 @@ TEST(RenderiveWebGallery, WebRuntimeMetricsPublishKernelSmoothedConsumerFeedback
|
||||
EXPECT_EQ(observer.at("consumer_safety_interval_ns"), 20'000'000);
|
||||
EXPECT_EQ(observer.at("consumer_interval_ns"), 20'000'000);
|
||||
EXPECT_EQ(observer.at("limit_state"), "consumer_limited");
|
||||
EXPECT_EQ(limit.at("consumer_feedback_source"), "presentation");
|
||||
EXPECT_EQ(feedback.at("source"), "presentation");
|
||||
observed = response_json(session.handle(gallery_request(
|
||||
Gallery_Request_Kind::Observe,
|
||||
R"({"category":"event","type":"gallery_observe","client_metrics":{"transport_fps":45,"presentation_fps":45,"websocket_buffered_bytes":0,"changed_pixel_frames":20,"duplicate_pixel_frames":0,"frame_request_timeout_count":0,"frame_round_trip_ms":4,"display_interval_ms":22,"overwritten_pixel_frames":0,"last_pixel_receive_age_ms":1,"last_pixel_change_age_ms":1}})")));
|
||||
@@ -999,31 +1025,35 @@ TEST(RenderiveWebGallery, ConsumerFeedbackSourcesCanBeSelectedIndependentlyAndMa
|
||||
Gallery_Request_Kind::Observe,
|
||||
R"({"category":"event","type":"gallery_observe","client_metrics":{"transport_fps":25,"presentation_fps":60,"websocket_buffered_bytes":0,"changed_pixel_frames":1,"duplicate_pixel_frames":0,"frame_request_timeout_count":0,"frame_round_trip_ms":5,"display_interval_ms":16,"display_interval_latest_ms":16,"display_interval_p95_ms":17,"display_jitter_ms":1,"overwritten_pixel_frames":0,"last_pixel_receive_age_ms":1,"last_pixel_change_age_ms":1}})")));
|
||||
auto observer = observed.at("telemetry").at("kernel_observer");
|
||||
EXPECT_EQ(observer.at("consumer_feedback_source"), "pixel");
|
||||
EXPECT_EQ(observer.at("consumer_pixel_interval_ns"), 40'000'000);
|
||||
EXPECT_EQ(observer.at("consumer_presentation_interval_ns"), 16'000'000);
|
||||
auto feedback = observed.at("telemetry").at("consumer_feedback");
|
||||
EXPECT_EQ(feedback.at("source"), "pixel");
|
||||
EXPECT_EQ(feedback.at("pixel_interval_ns"), 40'000'000);
|
||||
EXPECT_EQ(feedback.at("presentation_interval_ns"), 16'000'000);
|
||||
EXPECT_EQ(observer.at("consumer_sample_interval_ns"), 40'000'000);
|
||||
observed = patch_controls(session, {
|
||||
{"feedback", {{"pixel", false}, {"presentation", true}}}
|
||||
});
|
||||
observer = observed.at("telemetry").at("kernel_observer");
|
||||
EXPECT_EQ(observer.at("consumer_feedback_source"), "presentation");
|
||||
feedback = observed.at("telemetry").at("consumer_feedback");
|
||||
EXPECT_EQ(feedback.at("source"), "presentation");
|
||||
EXPECT_EQ(observer.at("consumer_sample_interval_ns"), 16'000'000);
|
||||
observed = patch_controls(session, {
|
||||
{"feedback", {{"presentation", false}, {"manual", true},
|
||||
{"manual_fps", 20.0}}}
|
||||
});
|
||||
observer = observed.at("telemetry").at("kernel_observer");
|
||||
EXPECT_EQ(observer.at("consumer_feedback_source"), "manual");
|
||||
EXPECT_EQ(observer.at("consumer_manual_interval_ns"), 50'000'000);
|
||||
feedback = observed.at("telemetry").at("consumer_feedback");
|
||||
EXPECT_EQ(feedback.at("source"), "manual");
|
||||
EXPECT_EQ(feedback.at("manual_interval_ns"), 50'000'000);
|
||||
EXPECT_EQ(observer.at("consumer_sample_interval_ns"), 50'000'000);
|
||||
observed = patch_controls(session, {
|
||||
{"feedback", {{"manual", false}}}
|
||||
});
|
||||
observer = observed.at("telemetry").at("kernel_observer");
|
||||
EXPECT_EQ(observer.at("consumer_feedback_source"), "none");
|
||||
feedback = observed.at("telemetry").at("consumer_feedback");
|
||||
EXPECT_EQ(feedback.at("source"), "none");
|
||||
EXPECT_FALSE(observer.at("consumer_feedback_enabled").get<bool>());
|
||||
EXPECT_TRUE(observer.at("consumer_feedback_master_enabled").get<bool>());
|
||||
EXPECT_TRUE(feedback.at("master_enabled").get<bool>());
|
||||
EXPECT_EQ(observer.at("consumer_interval_ns"), 0);
|
||||
}
|
||||
|
||||
@@ -1119,7 +1149,7 @@ TEST(RenderiveWebGallery, ProductionLowLatencySessionRendersWithoutPixelPulls) {
|
||||
EXPECT_TRUE(performance.at("automatic_low_latency_scheduler").get<bool>());
|
||||
EXPECT_GE(performance.at("successful_render_count").get<std::uint64_t>(), 4U);
|
||||
EXPECT_GT(performance.at("measured_fps").get<double>(), 0.0);
|
||||
EXPECT_DOUBLE_EQ(telemetry.at("kernel_observer").at("configured_frequency_hz"), 120.0);
|
||||
EXPECT_DOUBLE_EQ(telemetry.at("kernel_observer").at("frequency_hz"), 120.0);
|
||||
EXPECT_EQ(telemetry.at("kernel_observer").at("target_interval_ns"), 8'333'333);
|
||||
EXPECT_TRUE(telemetry.at("kernel_observer").at("limit_state") == "frequency_limited" ||
|
||||
telemetry.at("kernel_observer").at("limit_state") == "paint_limited" ||
|
||||
@@ -1273,11 +1303,11 @@ TEST(RenderiveWebGallery, DirectPropertyChangesAndAllImageModesRender) {
|
||||
open_message("constellation", "low_latency"))));
|
||||
const auto patched = constellation.handle(gallery_request(
|
||||
Gallery_Request_Kind::Patch,
|
||||
R"({"category":"event","type":"gallery_patch","target":"constellation","patch":{"type":"PSK16","phase_offset_radians":0.75}})"));
|
||||
R"({"category":"event","type":"gallery_patch","target":"constellation","patch":{"type":"Psk16","phase_offset_radians":0.75}})"));
|
||||
ASSERT_TRUE(patched.has_value());
|
||||
const auto patched_json = parse_json(patched->payload);
|
||||
EXPECT_EQ(patched_json.at("controls").at("resources").at(3).at("data").at("type"),
|
||||
"PSK16");
|
||||
"Psk16");
|
||||
ASSERT_TRUE(constellation.handle(Frame_Request{}).has_value());
|
||||
|
||||
Gallery_Plot_Session waterfall;
|
||||
|
||||
Reference in New Issue
Block a user