This commit is contained in:
2026-08-27 16:36:29 +08:00
parent 63bb406a27
commit 8ffb642c36
36 changed files with 633 additions and 868 deletions
+52 -35
View File
@@ -1,6 +1,7 @@
#include <render_2D/axis/Axis.hpp>
#include <render_2D/base/Frame_2D.hpp>
#include <render_2D/plottable/Constellation_Diagram.hpp>
#include <render_2D/plottable/Afterglow.hpp>
#include <render_2D/plottable/Spectrum.hpp>
#include <render_2D/plottable/Waterfall.hpp>
#include <render_2D/scene/Render_Scene_2D.hpp>
@@ -8,6 +9,7 @@
#include <atomic>
#include <cstdlib>
#include <memory>
#include <string_view>
#include <vector>
namespace {
@@ -40,6 +42,7 @@ int main() {
using Spectrum_Object = Spectrum;
using Constellation_Object = Constellation_Diagram;
using Waterfall_Object = Waterfall;
using Afterglow_Object = Afterglow;
using Scene_Object = Render_Scene_2D;
initialize_runtime({.workers = 4});
@@ -49,11 +52,15 @@ int main() {
auto* time = build_object<Time>();
auto* waterfall = build_object<Waterfall_Object>(
frequency, time, Plot_Partition_Grid{2, 2});
auto* afterglow = build_object<Afterglow_Object>(
frequency, power, Plot_Partition_Grid{2, 2});
auto* q_axis = build_object<Power>();
auto* constellation = build_object<Constellation_Object>(
power, q_axis, 3u);
auto* spectrum_painted = new std::atomic_bool{};
auto* topology_valid = new std::atomic_bool{true};
auto* second_frame_valid = new std::atomic_bool{};
auto* trace_valid = new std::atomic_bool{true};
spectrum->taskflow().add("test.spectrum.render.complete", [spectrum_painted] {
spectrum_painted->store(true, std::memory_order_release);
});
@@ -70,6 +77,7 @@ int main() {
typename Scene_Object::template Builder<Scene_Object> scene_builder;
scene_builder.add_renderable(spectrum);
scene_builder.add_renderable(waterfall);
scene_builder.add_renderable(afterglow);
scene_builder.add_renderable(constellation);
auto scene_result = scene_builder.build();
if (!scene_result) return 2;
@@ -110,6 +118,13 @@ int main() {
std::make_shared<const Waterfall_Row>(Waterfall_Row{
second_tick, std::vector<Plot_Value>(64, -30.0)}));
waterfall->mark_dirty<Render_Graph_Tag>();
afterglow->set<&Afterglow::Prop::frequency_range>(
Axis_Range{0.0, 100.0});
afterglow->set<&Afterglow::Prop::power_range>(Axis_Range{-100.0, 0.0});
afterglow->set<&Afterglow::Prop::power_point_size>(32u);
afterglow->submit_stream<Afterglow_Stream_Tag>(
std::make_shared<const std::vector<Plot_Value>>(64, -40.0));
afterglow->mark_dirty<Render_Graph_Tag>();
constellation->set<&Constellation_Diagram::Prop::i_range>(
Axis_Range{-1.0, 1.0});
constellation->set<&Constellation_Diagram::Prop::q_range>(
@@ -122,27 +137,40 @@ int main() {
scene->activate_view();
auto* frame = new Frame_2D(Frame_Identity{1, 0});
auto* resized_partition_frame = new Frame_2D(Frame_Identity{2, 0});
frame->request_taskflow_trace();
resized_partition_frame->request_taskflow_trace();
scene->set_frame_retired_callback(
[resized_partition_frame, second_frame_valid, trace_valid](
Frame_2D* retired) {
const auto trace = retired->take_taskflow_trace();
bool spectrum_hold{};
bool afterglow_reduce{};
bool afterglow_color_barrier{};
for (const auto& graph : trace.graphs)
for (const auto& node : graph.nodes) {
spectrum_hold = spectrum_hold ||
node.name == "prepare.hold.partition";
afterglow_reduce = afterglow_reduce ||
node.name == "prepare.normalize";
afterglow_color_barrier = afterglow_color_barrier ||
node.name == "prepare.color.complete";
}
if (!spectrum_hold || !afterglow_reduce ||
!afterglow_color_barrier)
trace_valid->store(false, std::memory_order_relaxed);
if (retired == resized_partition_frame)
std::_Exit(second_frame_valid->load(std::memory_order_relaxed) &&
trace_valid->load(std::memory_order_relaxed)
? 0
: 1);
});
scene->set_frame_callback(
[scene, spectrum, waterfall, constellation, frequency, power, frame,
[scene, spectrum, waterfall, afterglow, constellation, frame,
resized_partition_frame, spectrum_painted,
topology_valid](Frame_2D* completed) {
const auto& spectrum_state =
spectrum->read_state<Renderable::Base_Tag>();
const auto& waterfall_state =
waterfall->read_state<Renderable::Base_Tag>();
const auto& constellation_state =
constellation->read_state<Renderable::Base_Tag>();
topology_valid, second_frame_valid](Frame_2D* completed) {
const bool valid = completed == frame &&
spectrum_state.render_executed &&
spectrum_state.task_count == 11 &&
waterfall_state.render_executed &&
waterfall_state.task_count == 10 &&
constellation_state.render_executed &&
constellation_state.task_count == 5 &&
spectrum->read_state<Spectrum::Base_Tag>().sample_count ==
512 &&
frequency->read_state<Renderable::Base_Tag>().render_executed &&
power->read_state<Renderable::Base_Tag>().render_executed &&
topology_valid->load(std::memory_order_relaxed) &&
contains_color(completed->image());
if (!valid) std::_Exit(1);
@@ -150,34 +178,23 @@ int main() {
spectrum->set<&Spectrum::Prop::partition_count>(2u);
waterfall->set<&Waterfall::Prop::partition_grid>(
Plot_Partition_Grid{3, 2});
afterglow->set<&Afterglow::Prop::partition_grid>(
Plot_Partition_Grid{3, 2});
afterglow->submit_stream<Afterglow_Stream_Tag>(
std::make_shared<const std::vector<Plot_Value>>(64, -20.0));
afterglow->mark_dirty<Render_Graph_Tag>();
constellation->set<
&Constellation_Diagram::Prop::partition_count>(2u);
spectrum_painted->store(false, std::memory_order_relaxed);
scene->set_frame_callback(
[spectrum, waterfall, constellation, frequency, power,
resized_partition_frame,
[resized_partition_frame, second_frame_valid,
topology_valid](Frame_2D* next) {
const auto& next_spectrum =
spectrum->read_state<Renderable::Base_Tag>();
const auto& next_waterfall =
waterfall->read_state<Renderable::Base_Tag>();
const auto& next_constellation =
constellation->read_state<Renderable::Base_Tag>();
const bool resized_valid =
next == resized_partition_frame &&
next_spectrum.graph_rebuilt &&
next_spectrum.task_count == 7 &&
next_waterfall.graph_rebuilt &&
next_waterfall.task_count == 14 &&
next_constellation.graph_rebuilt &&
next_constellation.task_count == 4 &&
frequency->read_state<Renderable::Base_Tag>()
.render_executed &&
power->read_state<Renderable::Base_Tag>()
.render_executed &&
topology_valid->load(std::memory_order_relaxed) &&
contains_color(next->image());
std::_Exit(resized_valid ? 0 : 1);
second_frame_valid->store(resized_valid,
std::memory_order_relaxed);
});
if (!scene->render(resized_partition_frame)) std::_Exit(2);
});
@@ -141,6 +141,81 @@ TEST(partition_configuration,
}
}
TEST(partition_configuration,
bilinear_tiles_with_source_halo_match_one_complete_heatmap) {
const Size canvas{137, 93};
const aethera::render_2d::detail::Raster_Layout layout{
19, 13, Rect_F{3.25, 5.5, 128.5, 80.25}, false, false, true};
std::vector<Pixel> pixels(
static_cast<std::size_t>(layout.width) * layout.height);
for (int y = 0; y < layout.height; ++y)
for (int x = 0; x < layout.width; ++x)
pixels[static_cast<std::size_t>(y) * layout.width + x] =
0xff000000u | (static_cast<Pixel>(x * 13) << 16u) |
(static_cast<Pixel>(y * 17) << 8u);
Blend2D_Cache expected_cache;
expected_cache.ensure_size(canvas);
expected_cache.clear();
{
aethera::render_2d::detail::Painter painter(expected_cache, canvas);
aethera::render_2d::detail::paint_raster(
painter, layout, pixels, Image_Interpolation_Mode::bilinear);
}
Blend2D_Cache actual_cache;
actual_cache.ensure_size(canvas);
actual_cache.clear();
constexpr Plot_Partition_Grid grid{4, 3};
const Rect_F target = layout.target.normalized();
for (Plot_Partition_Count index = 0;
index < aethera::render_2d::detail::raster_partition_count(grid);
++index) {
const auto core = aethera::render_2d::detail::raster_partition(
layout, index, grid);
const aethera::render_2d::detail::Raster_Partition source{
std::max(0, core.first_x - 1),
std::min(layout.width, core.last_x + 1),
std::max(0, core.first_y - 1),
std::min(layout.height, core.last_y + 1)};
const int width = source.last_x - source.first_x;
const int height = source.last_y - source.first_y;
std::vector<Pixel> tile(static_cast<std::size_t>(width) * height);
for (int y = source.first_y; y < source.last_y; ++y)
std::copy_n(pixels.begin() + static_cast<std::ptrdiff_t>(y) *
layout.width + source.first_x,
width,
tile.begin() + static_cast<std::ptrdiff_t>(
y - source.first_y) * width);
const auto project_x = [&](int boundary) {
return target.x + target.width * boundary / layout.width;
};
const auto project_y = [&](int boundary) {
return target.y + target.height * boundary / layout.height;
};
const double left = project_x(source.first_x);
const double top = project_y(source.first_y);
aethera::render_2d::detail::Painter painter(
actual_cache, canvas,
aethera::render_2d::detail::raster_paint_region(layout, index, grid));
painter.heatmap(
{left, top, project_x(source.last_x) - left,
project_y(source.last_y) - top},
width, height, tile, Image_Interpolation_Mode::bilinear);
}
const Image_View expected = expected_cache.view();
const Image_View actual = actual_cache.view();
for (int y = 0; y < expected.height; ++y) {
const auto* expected_row = reinterpret_cast<const Pixel*>(
expected.data + static_cast<std::ptrdiff_t>(y) * expected.stride);
const auto* actual_row = reinterpret_cast<const Pixel*>(
actual.data + static_cast<std::ptrdiff_t>(y) * actual.stride);
for (int x = 0; x < expected.width; ++x)
EXPECT_EQ(actual_row[x], expected_row[x])
<< "tile seam mismatch at " << x << ", " << y;
}
}
TEST(partition_configuration,
raster_grid_covers_each_matrix_cell_exactly_once) {
const aethera::render_2d::detail::Raster_Layout layout{
@@ -185,7 +185,6 @@ TEST(plottable_migration, direct_overlay_and_constellation_build_and_render) {
render_once(scene.get());
EXPECT_EQ(diagram->access_rendering_stream<Constellation_Stream_Tag>(
[](std::span<Constellation_Point> points) { return points.size(); }), 1u);
EXPECT_EQ(overlay->read_state<Renderable::Base_Tag>().task_count, 1u);
}
TEST(selection_overlay, control_extends_selection_and_plain_click_clears_it) {
-11
View File
@@ -142,8 +142,6 @@ TEST(render_scene_2d, composites_axes_and_spectrum_into_final_frame) {
EXPECT_TRUE(cache_graph.depends_on(spectrum.get(), power.get()));
auto output_frame = render_frame(scene.get());
EXPECT_GT(spectrum->read_state<Spectrum::Base_Tag>().rendered_point_count, 0u);
const auto& render_state = spectrum->read_state<Renderable::Base_Tag>();
EXPECT_EQ(render_state.task_count, 9u);
const Image_View frame = output_frame->image();
ASSERT_FALSE(frame.empty());
EXPECT_TRUE(contains_color(frame));
@@ -158,23 +156,16 @@ TEST(render_scene_2d, composites_axes_and_spectrum_into_final_frame) {
frequency->set<&Numeric_Axis::Prop::precision>(3);
EXPECT_FALSE(spectrum->dirty<Render_Cache_Tag>());
output_frame = render_frame(scene.get());
EXPECT_TRUE(spectrum->read_state<Renderable::Base_Tag>().render_executed);
EXPECT_TRUE(frequency->read_state<Renderable::Base_Tag>().render_executed);
EXPECT_TRUE(power->read_state<Renderable::Base_Tag>().render_executed);
EXPECT_TRUE(contains_color(output_frame->image()));
frequency->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{0.0, 120.0});
EXPECT_TRUE(spectrum->dirty<Render_Cache_Tag>());
output_frame = render_frame(scene.get());
EXPECT_FALSE(spectrum->dirty<Render_Cache_Tag>());
EXPECT_TRUE(spectrum->read_state<Renderable::Base_Tag>().render_executed);
spectrum->set<&Spectrum::Prop::partition_count>(2u);
spectrum->pending_buffer<Spectrum_Frame_Tag>() =
Spectrum_Frame{std::vector<Spectrum_Power>(std::begin(samples), std::end(samples))};
spectrum->mark_dirty<Render_Graph_Tag>();
output_frame = render_frame(scene.get());
const auto& rebuilt_state = spectrum->read_state<Renderable::Base_Tag>();
EXPECT_TRUE(rebuilt_state.graph_rebuilt);
EXPECT_EQ(rebuilt_state.task_count, 7u);
EXPECT_TRUE(contains_color(output_frame->image()));
const Size resized_canvas{200, 140};
scene->set<&Render_Scene_2D::Prop::viewport>(resized_canvas);
@@ -183,7 +174,6 @@ TEST(render_scene_2d, composites_axes_and_spectrum_into_final_frame) {
EXPECT_FALSE(power->dirty<Render_Graph_Tag>());
output_frame = render_frame(scene.get());
EXPECT_EQ(scene->read_prop<Render_Scene_2D::Base_Tag>().viewport, resized_canvas);
EXPECT_TRUE(frequency->read_state<Renderable::Base_Tag>().render_executed);
const Image_View resized_frame = output_frame->image();
EXPECT_EQ(resized_frame.width, resized_canvas.width);
EXPECT_EQ(resized_frame.height, resized_canvas.height);
@@ -225,7 +215,6 @@ TEST(spectrum_data, repeatedly_publishes_new_samples_during_long_running_render)
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>().render_executed);
EXPECT_EQ(spectrum->read_state<Spectrum::Base_Tag>().sample_count, 128u);
}
EXPECT_GT(changed_frames, 700u);