仍有bug

This commit is contained in:
2026-08-24 16:27:27 +08:00
parent ca6d8f8e79
commit f463a756fc
27 changed files with 1201 additions and 898 deletions
@@ -4,7 +4,10 @@ Render_Scene_2D::Private::~Private() = default;
bool Render_Scene_2D::State::operator==(const State&) const = default;
bool Render_Scene_2D::Prop::operator==(const Prop&) const = default;
Render_Scene_2D::Render_Result Render_Scene_2D::render(Frame_2D* frame) { return static_cast<Private&>(*d).dispatch->render(this, frame); }
std::expected<void, Render_Scene_2D::Render_Result>
Render_Scene_2D::render(Frame_2D* frame) {
return static_cast<Private&>(*d).dispatch->render(this, frame);
}
void Render_Scene_2D::set_frame_callback(Frame_Callback callback) { static_cast<Private&>(*d).dispatch->set_frame_callback(this, std::move(callback)); }
tf::Taskflow& Render_Scene_2D::completion_taskflow() {
return static_cast<Private&>(*d).completion_graph;
@@ -15,4 +18,7 @@ void Render_Scene_2D::activate_view() {
void Render_Scene_2D::deactivate_view() {
static_cast<Private&>(*d).dispatch->set_active(this, false);
}
void Render_Scene_2D::reset_frame_statistics() {
static_cast<Private&>(*d).dispatch->reset_statistics(this);
}
}
@@ -3,6 +3,8 @@
#include "../base/Renderable_2D.hpp"
#include "../render/Blend2D_Cache.hpp"
#include <scene.hpp>
#include <frame_statistics.hpp>
#include <expected>
#include <functional>
#include <memory>
namespace aethera::render_2d {
@@ -17,6 +19,7 @@ struct Render_Scene_2D : Def<Render_Scene_2D, Scene,
bool operator==(const Prop&) const;
};
struct State : Prev_State {
Frame_Statistics_State frame_statistics{};
bool operator==(const State&) const;
};
/* 完整声明、合成顺序和 CRTP 分派见 Render_Scene_2D.ipp。 */
@@ -33,10 +36,10 @@ struct Render_Scene_2D : Def<Render_Scene_2D, Scene,
private:
std::vector<std::function<std::expected<void, Dependency_Graph_Error>(Object*)>> attachments{}; /* 仅在 build 期间绑定已构造 Renderable。 */
};
enum class Render_Result { completed, frame_in_flight, view_inactive, empty_viewport };
enum class Render_Result { frame_in_flight, view_inactive, empty_viewport };
using Frame_Callback = std::function<void(Frame_2D*)>;
/* 向调用方拥有的帧合成一次;必须先安装回调。回调返回前的并发请求返回 frame_in_flight。 */
[[nodiscard]] Render_Result render(Frame_2D* frame);
[[nodiscard]] std::expected<void, Render_Result> render(Frame_2D* frame);
/* 安装完成帧回调;回调收到对应 render(frame) 的对象,返回时 Scene 才释放下一帧准入。 */
void set_frame_callback(Frame_Callback callback);
/*
@@ -50,6 +53,7 @@ struct Render_Scene_2D : Def<Render_Scene_2D, Scene,
void activate_view();
/* 停止后续 render 调用,不清除最后一帧。 */
void deactivate_view();
void reset_frame_statistics();
};
}
#include "Render_Scene_2D.ipp"
+31 -6
View File
@@ -34,7 +34,7 @@ std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> Render_Scene_2D::
return scene;
}
struct Render_Scene_2D::Private : Prev_Private {
using Render_Run = Render_Result (*)(Root*, Frame_2D*);
using Render_Run = std::expected<void, Render_Result> (*)(Root*, Frame_2D*);
using Callback_Run = void (*)(Root*, Frame_Callback);
using Active_Run = void (*)(Root*, bool);
struct Paint_Node {
@@ -54,6 +54,7 @@ struct Render_Scene_2D::Private : Prev_Private {
Renderable_2D_Base::Private* private_data{}; /* 候选对象的二维能力层;仅在本次 Prepare 分发期间有效。 */
};
struct Dispatch {
void (*reset_statistics)(Root*);
Render_Run render; /* 向外部帧执行最终 Scene 并合成颜色层。 */
Callback_Run set_frame_callback; /* 安装最终完成帧回调。 */
Active_Run set_active; /* 修改最终 Scene 的视图活动状态。 */
@@ -62,6 +63,7 @@ struct Render_Scene_2D::Private : Prev_Private {
std::mutex render_mutex{}; /* 只保护完成回调和单帧准入。 */
bool frame_in_flight{}; /* render 准入到完成回调返回的唯一状态源。 */
Frame_Callback frame_callback{}; /* 合成完成后的唯一像素发布出口。 */
Frame_Statistics_Accumulator frame_statistics{}; /* Scene 内部增量计算;State 只发布定长统计结果。 */
tf::Taskflow completion_graph{}; /* 最终像素完成后、发布回调前执行的外部续写图。 */
std::unique_ptr<tf::Taskflow> paint_taskflow{}; /* 仅由二维 Paint 图构建的执行图。 */
~Private();
@@ -81,7 +83,9 @@ struct Render_Scene_2D::Private : Prev_Private {
template <Attached Object>
void dispatch_events(Object* object, Size viewport,
std::uint64_t frame_sequence);
template <Attached Object> [[nodiscard]] Render_Result render(Object* object, Frame_2D* frame);
template <Attached Object>
[[nodiscard]] std::expected<void, Render_Result> render(Object* object,
Frame_2D* frame);
template <Attached Object>
[[nodiscard]] static const Dispatch& dispatch_for();
/* CRTP 覆盖:Builder 挂接最终 Private 后安装二维 Scene 的无虚函数业务分派。 */
@@ -297,14 +301,16 @@ void Render_Scene_2D::Private::process(Object* object, Callback&& callback)
std::invoke(std::forward<Callback>(callback));
}
template <Attached Object>
Render_Scene_2D::Render_Result Render_Scene_2D::Private::render(Object* object, Frame_2D* frame) {
std::expected<void, Render_Scene_2D::Render_Result>
Render_Scene_2D::Private::render(Object* object, Frame_2D* frame) {
static_cast<void>(detail::Frame_2D_Access::render_target(frame));
Frame_Callback callback;
{
std::lock_guard lock(render_mutex);
if (!frame_callback)
throw std::logic_error("Render_Scene_2D requires a frame callback before render");
if (frame_in_flight) return Render_Result::frame_in_flight;
if (frame_in_flight)
return std::unexpected(Render_Result::frame_in_flight);
frame_in_flight = true;
callback = frame_callback;
}
@@ -331,10 +337,18 @@ Render_Scene_2D::Render_Result Render_Scene_2D::Private::render(Object* object,
frame->mark(Frame_Trace_Marker::callback_started);
callback(frame);
frame->mark(Frame_Trace_Marker::callback_finished);
frame->mark(Frame_Trace_Marker::frame_ready);
auto& private_data = static_cast<typename Object::Private&>(*this);
auto& scene_state = static_cast<State&>(*private_data.state.pending);
scene_state.frame_statistics = frame_statistics.submit(
*frame, Frame_Dimension::two_dimensional);
private_data.state.advance();
object->template notify_state<Render_Scene_2D::Base_Tag>();
});
if (completed) return Render_Result::completed;
if (completed) return {};
const auto& prop = object->template read_prop<Render_Scene_2D::Base_Tag>();
return prop.view_active ? Render_Result::empty_viewport : Render_Result::view_inactive;
return std::unexpected(prop.view_active ? Render_Result::empty_viewport
: Render_Result::view_inactive);
}
template <Attached Object>
void Render_Scene_2D::Private::dispatch_events(Object* object, Size viewport,
@@ -367,6 +381,17 @@ void Render_Scene_2D::Private::dispatch_events(Object* object, Size viewport,
template <Attached Object>
const Render_Scene_2D::Private::Dispatch& Render_Scene_2D::Private::dispatch_for() {
static const Dispatch value{
[](Root* root) {
auto* object = static_cast<Object*>(root);
auto& data = static_cast<typename Object::Private&>(*object->d);
object->template publish_state<Render_Scene_2D::Base_Tag,
&State::frame_statistics>(
[&data](State_Access<typename Object::State> states) {
data.frame_statistics.reset();
states.template get<Render_Scene_2D::Base_Tag>()
.frame_statistics = {};
});
},
[](Root* root, Frame_2D* frame) {
auto* object = static_cast<Object*>(root);
return static_cast<typename Object::Private&>(*object->d).render(object, frame);
+1 -1
View File
@@ -18,7 +18,7 @@ std::unique_ptr<Frame_2D> render_frame(Scene* scene) {
static std::uint64_t sequence{1};
auto frame = std::make_unique<Frame_2D>(Frame_Identity{sequence++, 0});
scene->set_frame_callback([](Frame_2D*) {});
EXPECT_EQ(scene->render(frame.get()), Render_Scene_2D::Render_Result::completed);
EXPECT_TRUE(scene->render(frame.get()).has_value());
return frame;
}
template <typename Scene>
+1 -1
View File
@@ -36,7 +36,7 @@ void render_once(Scene* scene) {
static std::uint64_t sequence{1};
Frame_2D frame{Frame_Identity{sequence++, 0}};
scene->set_frame_callback([](Frame_2D*) {});
EXPECT_EQ(scene->render(&frame), Render_Scene_2D::Render_Result::completed);
EXPECT_TRUE(scene->render(&frame).has_value());
}
}
+57 -1
View File
@@ -28,12 +28,26 @@ bool contains_color(Image_View view) {
}
return false;
}
std::uint64_t image_hash(Image_View view) {
std::uint64_t value{1469598103934665603ULL};
for (int y = 0; y < view.height; ++y) {
const auto* row = reinterpret_cast<const std::byte*>(view.data) +
static_cast<std::ptrdiff_t>(y) * view.stride;
for (std::ptrdiff_t index = 0;
index < static_cast<std::ptrdiff_t>(view.width * sizeof(Pixel));
++index) {
value ^= static_cast<std::uint8_t>(row[index]);
value *= 1099511628211ULL;
}
}
return value;
}
template <typename Scene>
std::unique_ptr<Frame_2D> render_frame(Scene* scene) {
static std::uint64_t sequence{1};
auto frame = std::make_unique<Frame_2D>(Frame_Identity{sequence++, 0});
scene->set_frame_callback([](Frame_2D*) {});
EXPECT_EQ(scene->render(frame.get()), Render_Scene_2D::Render_Result::completed);
EXPECT_TRUE(scene->render(frame.get()).has_value());
return frame;
}
}
@@ -182,3 +196,45 @@ TEST(render_scene_2d, composites_axes_and_spectrum_into_final_frame) {
EXPECT_EQ(resized_frame.height, resized_canvas.height);
EXPECT_TRUE(contains_color(resized_frame));
}
TEST(spectrum_data, repeatedly_publishes_new_samples_during_long_running_render) {
using Frequency = Impl<Frequency_Axis>;
using Power = Impl<Numeric_Axis>;
using Spectrum_Object = Impl<Spectrum>;
using Scene_Object = Impl<Render_Scene_2D>;
initialize_runtime(4);
auto frequency = build_object<Frequency>();
auto power = build_object<Power>();
auto spectrum = build_object<Spectrum_Object>(frequency.get(), power.get());
auto scene = build_scene<Scene_Object>(spectrum.get());
frequency->set<&Abs_Axis::Prop::position>(Point_F{20.0, 100.0});
frequency->set<&Abs_Axis::Prop::pixel_length>(120.0);
frequency->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{0.0, 100.0});
power->set<&Abs_Axis::Prop::position>(Point_F{20.0, 100.0});
power->set<&Abs_Axis::Prop::pixel_length>(-80.0);
power->set<&Abs_Axis::Prop::orientation>(Axis_Orientation::vertical);
power->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{-100.0, 0.0});
spectrum->set<&Spectrum::Prop::frequency_range>(Axis_Range{0.0, 100.0});
scene->set<&Render_Scene_2D::Prop::viewport>(Size{160, 120});
scene->set<&Render_Scene_2D::Prop::background>(Color::transparent());
scene->activate_view();
std::uint64_t previous_hash{};
std::size_t changed_frames{};
for (std::size_t frame_index = 0; frame_index < 720; ++frame_index) {
std::vector<Spectrum_Power> samples(128, -95.0);
const std::size_t peak = 8 + frame_index % 112;
samples[peak] = -15.0;
spectrum->pending_buffer<Spectrum_Frame_Tag>() =
Spectrum_Frame{std::move(samples)};
spectrum->mark_dirty<Prepare_Data_Tag>();
auto frame = render_frame(scene.get());
const auto hash = image_hash(frame->image());
if (frame_index != 0 && hash != previous_hash) ++changed_frames;
previous_hash = hash;
EXPECT_TRUE(spectrum->read_state<Renderable::Base_Tag>().prepare_executed);
EXPECT_TRUE(spectrum->read_state<Renderable::Base_Tag>().paint_executed);
EXPECT_EQ(spectrum->read_state<Spectrum::Base_Tag>().sample_count, 128u);
}
EXPECT_GT(changed_frames, 700u);
}