#include #include #include #include namespace { using namespace aethera; using namespace aethera::render_2d; template std::unique_ptr build_object(Args&&... args) { typename Object::Builder builder(std::forward(args)...); auto result = builder.build(); if (!result) std::terminate(); return std::move(result).value(); } template std::unique_ptr build_scene(Renderables*... renderables) { typename Scene::Builder builder; (builder.add_renderable(renderables), ...); auto result = builder.build(); if (!result) std::terminate(); return std::move(result).value(); } bool contains_color(Image_View view) { for (int y = 0; y < view.height; ++y) { const auto* row = reinterpret_cast(view.data + static_cast(y) * view.stride); for (int x = 0; x < view.width; ++x) if (row[x] != 0) return true; } 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(view.data) + static_cast(y) * view.stride; for (std::ptrdiff_t index = 0; index < static_cast(view.width * sizeof(Pixel)); ++index) { value ^= static_cast(row[index]); value *= 1099511628211ULL; } } return value; } template std::unique_ptr render_frame(Scene* scene) { static std::uint64_t sequence{1}; auto frame = std::make_unique(Frame_Identity{sequence++, 0}); scene->set_frame_callback([](Frame_2D*) {}); EXPECT_TRUE(scene->render(frame.get()).has_value()); return frame; } } TEST(spectrum_data, publishes_samples_through_tagged_frame_buffer) { using Frequency = Impl; using Power = Impl; using Object = Impl; using Scene_Object = Impl; auto frequency = build_object(); auto power = build_object(); auto spectrum = build_object(frequency.get(), power.get()); spectrum->set<&Spectrum::Prop::frequency_range>(Axis_Range{0.0, 100.0}); const double samples[]{-100.0, -50.0, 0.0}; spectrum->pending_buffer() = Spectrum_Frame{std::vector(std::begin(samples), std::end(samples))}; spectrum->mark_dirty(); spectrum->advance(); EXPECT_EQ(spectrum->current_buffer().samples, (std::vector{-100.0, -50.0, 0.0})); } TEST(spectrum_markers, uses_tagged_properties_as_the_only_marker_interface) { using Frequency = Impl; using Power = Impl; using Object = Impl; using Scene_Object = Impl; auto frequency = build_object(); auto power = build_object(); auto spectrum = build_object(frequency.get(), power.get()); spectrum->set<&Spectrum::Prop::custom_markers>( std::vector{10.0, 20.0}); spectrum->advance(); EXPECT_EQ(spectrum->read_prop().custom_markers.size(), 2u); spectrum->set<&Spectrum::Prop::selected_marker>(1); spectrum->advance(); EXPECT_EQ(spectrum->read_prop().selected_marker, 1); spectrum->update_prop<&Spectrum::Prop::custom_markers>( [](Prop_Access props) { props.get().custom_markers[1] = 25.0; }); spectrum->advance(); EXPECT_DOUBLE_EQ( spectrum->read_prop().custom_markers[1], 25.0); spectrum->update_prop<&Spectrum::Prop::custom_markers, &Spectrum::Prop::selected_marker>( [](Prop_Access props) { auto& values = props.get(); values.custom_markers.erase(values.custom_markers.begin() + values.selected_marker); values.selected_marker = -1; }); spectrum->advance(); EXPECT_EQ(spectrum->read_prop().custom_markers.size(), 1u); EXPECT_EQ(spectrum->read_prop().selected_marker, -1); } TEST(render_scene_2d, composites_axes_and_spectrum_into_final_frame) { using Frequency = Impl; using Power = Impl; using Spectrum_Object = Impl; using Scene_Object = Impl; initialize_runtime({.workers = 2}); auto frequency = build_object(); auto power = build_object(); auto spectrum = build_object(frequency.get(), power.get(), 3u); auto scene = build_scene(spectrum.get()); const Size canvas{160, 120}; 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}); spectrum->set<&Spectrum::Prop::sweep_region_visible>(true); spectrum->set<&Spectrum::Prop::max_hold_visible>(true); const double samples[]{-90.0, -65.0, -20.0, -45.0, -75.0}; spectrum->pending_buffer() = Spectrum_Frame{std::vector(std::begin(samples), std::end(samples))}; spectrum->mark_dirty(); scene->set<&Render_Scene_2D::Prop::viewport>(canvas); scene->set<&Render_Scene_2D::Prop::background>(Color::transparent()); scene->activate_view(); const auto prepare_graph = scene->pending_dependency_graph(); EXPECT_TRUE(prepare_graph.depends_on(spectrum.get(), scene.get())); EXPECT_TRUE(prepare_graph.depends_on(spectrum.get(), frequency.get())); EXPECT_TRUE(prepare_graph.depends_on(spectrum.get(), power.get())); const auto paint_graph = scene->pending_dependency_graph(); EXPECT_TRUE(paint_graph.depends_on(frequency.get(), spectrum.get())); EXPECT_TRUE(paint_graph.depends_on(power.get(), spectrum.get())); const auto cache_graph = scene->pending_dependency_graph(); EXPECT_TRUE(cache_graph.depends_on(spectrum.get(), scene.get())); EXPECT_TRUE(cache_graph.depends_on(spectrum.get(), frequency.get())); EXPECT_TRUE(cache_graph.depends_on(spectrum.get(), power.get())); auto output_frame = render_frame(scene.get()); EXPECT_GT(spectrum->read_state().rendered_point_count, 0u); const auto& render_state = spectrum->read_state(); EXPECT_EQ(render_state.prepare_task_count, 4u); EXPECT_EQ(render_state.paint_task_count, 5u); const Image_View frame = output_frame->image(); ASSERT_FALSE(frame.empty()); EXPECT_TRUE(contains_color(frame)); EXPECT_FALSE(spectrum->dirty()); EXPECT_FALSE(spectrum->dirty()); EXPECT_FALSE(frequency->dirty()); EXPECT_FALSE(power->dirty()); spectrum->set<&Renderable_2D::Prop::cache_enabled>(false); output_frame = render_frame(scene.get()); EXPECT_TRUE(contains_color(output_frame->image())); spectrum->set<&Renderable_2D::Prop::cache_enabled>(true); output_frame = render_frame(scene.get()); EXPECT_TRUE(contains_color(output_frame->image())); frequency->set<&Numeric_Axis::Prop::precision>(3); EXPECT_FALSE(spectrum->dirty()); output_frame = render_frame(scene.get()); EXPECT_TRUE(spectrum->read_state().paint_executed); EXPECT_TRUE(frequency->read_state().paint_executed); EXPECT_TRUE(power->read_state().paint_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()); output_frame = render_frame(scene.get()); EXPECT_FALSE(spectrum->dirty()); EXPECT_TRUE(spectrum->read_state().prepare_executed); spectrum->set<&Spectrum::Prop::partition_count>(2u); spectrum->pending_buffer() = Spectrum_Frame{std::vector(std::begin(samples), std::end(samples))}; spectrum->mark_dirty(); output_frame = render_frame(scene.get()); const auto& rebuilt_state = spectrum->read_state(); EXPECT_TRUE(rebuilt_state.prepare_graph_rebuilt); EXPECT_EQ(rebuilt_state.prepare_task_count, 3u); EXPECT_TRUE(contains_color(output_frame->image())); const Size resized_canvas{200, 140}; scene->set<&Render_Scene_2D::Prop::viewport>(resized_canvas); EXPECT_TRUE(spectrum->dirty()); EXPECT_FALSE(frequency->dirty()); EXPECT_FALSE(power->dirty()); output_frame = render_frame(scene.get()); EXPECT_EQ(scene->read_prop().viewport, resized_canvas); EXPECT_FALSE(frequency->read_state().prepare_executed); EXPECT_TRUE(frequency->read_state().paint_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); EXPECT_TRUE(contains_color(resized_frame)); } TEST(spectrum_data, repeatedly_publishes_new_samples_during_long_running_render) { using Frequency = Impl; using Power = Impl; using Spectrum_Object = Impl; using Scene_Object = Impl; initialize_runtime({.workers = 4}); auto frequency = build_object(); auto power = build_object(); auto spectrum = build_object(frequency.get(), power.get()); auto scene = build_scene(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 samples(128, -95.0); const std::size_t peak = 8 + frame_index % 112; samples[peak] = -15.0; spectrum->pending_buffer() = Spectrum_Frame{std::move(samples)}; spectrum->mark_dirty(); 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().prepare_executed); EXPECT_TRUE(spectrum->read_state().paint_executed); EXPECT_EQ(spectrum->read_state().sample_count, 128u); } EXPECT_GT(changed_frames, 700u); }