#include "../src/export.h" #include #include #include #include #include namespace renderive { namespace { TEST(Renderive_Core2_Frame_Pipeline, ManualLifecycleSeparatesPrepareRefreshAndRender) { Manual_Scene2D plot; plot.init(); plot.set_viewport_size({96, 54}); ASSERT_TRUE(plot.prepare_frame()); auto snapshot = plot.frame_observer_snapshot(); EXPECT_EQ(snapshot.mode, Frame_Control_Mode::Manual); EXPECT_EQ(snapshot.last_event, "prepared"); EXPECT_EQ(snapshot.limit_state, "not_applicable"); EXPECT_EQ(snapshot.produced_frame_count, 1u); EXPECT_EQ(snapshot.consumed_frame_count, 0u); EXPECT_EQ(snapshot.pending_frame_count, 1u); EXPECT_EQ(snapshot.latest_sequence, 1u); ASSERT_TRUE(plot.refresh_manual_frame()); snapshot = plot.frame_observer_snapshot(); EXPECT_EQ(snapshot.last_event, "refresh_succeeded"); EXPECT_EQ(snapshot.produced_frame_count, 1u); EXPECT_EQ(snapshot.consumed_frame_count, 0u); EXPECT_EQ(snapshot.pending_frame_count, 0u); EXPECT_EQ(snapshot.latest_sequence, 1u); ASSERT_TRUE(plot.render_prepared_frame()); snapshot = plot.frame_observer_snapshot(); EXPECT_EQ(snapshot.last_event, "rendered"); EXPECT_EQ(snapshot.produced_frame_count, 1u); EXPECT_EQ(snapshot.consumed_frame_count, 1u); EXPECT_EQ(snapshot.pending_frame_count, 0u); EXPECT_EQ(snapshot.latest_sequence, 1u); EXPECT_EQ(plot.diagnostics().refresh.frame_count, 1u); } TEST(Renderive_Core2_Frame_Pipeline, HighFrequencyLowLatencyLifecycleExposesKernelTiming) { Scene2D plot; plot.init(); plot.set_viewport_size({96, 54}); plot.set_max_render_fps(1'000'000'000.0); ASSERT_TRUE(plot.prepare_frame()); auto snapshot = plot.frame_observer_snapshot(); EXPECT_EQ(snapshot.last_event, "published"); EXPECT_EQ(snapshot.limit_state, "frequency_limited"); EXPECT_DOUBLE_EQ(snapshot.frequency_hz, 1'000'000'000.0); EXPECT_EQ(snapshot.target_interval_ns, 1u); EXPECT_EQ(snapshot.produced_frame_count, 1u); EXPECT_EQ(snapshot.consumed_frame_count, 0u); EXPECT_EQ(snapshot.pending_frame_count, 1u); ASSERT_TRUE(plot.render_prepared_frame()); snapshot = plot.frame_observer_snapshot(); EXPECT_EQ(snapshot.last_event, "lifecycle_completed"); EXPECT_DOUBLE_EQ(snapshot.frequency_hz, 1'000'000'000.0); EXPECT_EQ(snapshot.target_interval_ns, 1u); EXPECT_EQ(snapshot.produced_frame_count, 1u); EXPECT_EQ(snapshot.consumed_frame_count, 1u); EXPECT_EQ(snapshot.pending_frame_count, 0u); EXPECT_EQ(snapshot.bottleneck_duration_ns, std::max(snapshot.paint_duration_ns, snapshot.render_duration_ns)); EXPECT_EQ(snapshot.next_refresh_interval_ns, std::max(snapshot.target_interval_ns, snapshot.bottleneck_duration_ns)); EXPECT_GE(snapshot.end_to_end_ns, snapshot.paint_duration_ns); EXPECT_GE(snapshot.end_to_end_ns, snapshot.render_duration_ns); const auto refresh = plot.refresh_feedback_snapshot(); EXPECT_DOUBLE_EQ(refresh.frequency_hz, snapshot.frequency_hz); EXPECT_EQ(refresh.frame_count, snapshot.consumed_frame_count); EXPECT_EQ(refresh.next_refresh_interval_ns, snapshot.next_refresh_interval_ns); } TEST(Renderive_Core2_Frame_Pipeline, LowLatencyConsumerFeedbackFlowsThroughCore2IntoKernelScheduling) { Scene2D plot; plot.init(); plot.set_viewport_size({96, 54}); plot.set_max_render_fps(100.0); plot.set_consumer_feedback({40'000'000}); ASSERT_TRUE(plot.prepare_frame()); ASSERT_TRUE(plot.render_prepared_frame()); const auto snapshot = plot.frame_observer_snapshot(); EXPECT_TRUE(snapshot.frequency_limit_enabled); EXPECT_TRUE(snapshot.consumer_feedback_enabled); EXPECT_EQ(snapshot.limit_state, "consumer_limited"); EXPECT_EQ(snapshot.target_interval_ns, 10'000'000u); EXPECT_EQ(snapshot.consumer_sample_interval_ns, 40'000'000u); EXPECT_EQ(snapshot.consumer_smoothed_interval_ns, 40'000'000u); EXPECT_EQ(snapshot.consumer_variation_ns, 0u); EXPECT_EQ(snapshot.consumer_safety_interval_ns, 40'000'000u); EXPECT_EQ(snapshot.consumer_interval_ns, 40'000'000u); EXPECT_EQ(snapshot.next_refresh_interval_ns, 40'000'000u); EXPECT_EQ(plot.refresh_feedback_snapshot().next_refresh_interval_ns, 40'000'000u); } TEST(Renderive_Core2_Frame_Pipeline, LowLatencyLimitsCanBeClearedIndependently) { Scene2D plot; plot.init(); plot.set_viewport_size({96, 54}); plot.set_max_render_fps(100.0); plot.set_consumer_feedback({40'000'000}); plot.clear_consumer_feedback(); plot.clear_max_render_fps(); ASSERT_TRUE(plot.prepare_frame()); ASSERT_TRUE(plot.render_prepared_frame()); const auto snapshot = plot.frame_observer_snapshot(); EXPECT_FALSE(snapshot.frequency_limit_enabled); EXPECT_FALSE(snapshot.consumer_feedback_enabled); EXPECT_EQ(snapshot.target_interval_ns, 0u); EXPECT_EQ(snapshot.consumer_interval_ns, 0u); EXPECT_EQ(snapshot.next_refresh_interval_ns, snapshot.bottleneck_duration_ns); EXPECT_TRUE(snapshot.limit_state == "paint_limited" || snapshot.limit_state == "render_limited"); } TEST(Renderive_Core2_Frame_Pipeline, LowLatencyReplacementRetiresOldFrameWithoutPhantomPendingFrame) { Scene2D plot; plot.init(); plot.set_viewport_size({64, 40}); ASSERT_TRUE(plot.prepare_frame()); ASSERT_TRUE(plot.prepare_frame()); auto snapshot = plot.frame_observer_snapshot(); EXPECT_EQ(snapshot.last_event, "published"); EXPECT_EQ(snapshot.produced_frame_count, 2u); EXPECT_EQ(snapshot.dropped_frame_count, 1u); EXPECT_EQ(snapshot.pending_frame_count, 1u); EXPECT_EQ(snapshot.latest_sequence, 2u); ASSERT_TRUE(plot.render_prepared_frame()); snapshot = plot.frame_observer_snapshot(); EXPECT_EQ(snapshot.last_event, "lifecycle_completed"); EXPECT_EQ(snapshot.produced_frame_count, 2u); EXPECT_EQ(snapshot.consumed_frame_count, 1u); EXPECT_EQ(snapshot.dropped_frame_count, 1u); EXPECT_EQ(snapshot.pending_frame_count, 0u); EXPECT_EQ(snapshot.latest_sequence, 2u); } TEST(Renderive_Core2_Frame_Pipeline, LowLatencyDiscardAndEmptyRenderRemainObservable) { Scene2D plot; plot.init(); plot.set_viewport_size({64, 40}); ASSERT_TRUE(plot.prepare_frame()); ASSERT_TRUE(plot.discard_pending_frame()); auto snapshot = plot.frame_observer_snapshot(); EXPECT_EQ(snapshot.last_event, "manually_discarded"); EXPECT_EQ(snapshot.produced_frame_count, 1u); EXPECT_EQ(snapshot.consumed_frame_count, 0u); EXPECT_EQ(snapshot.dropped_frame_count, 1u); EXPECT_EQ(snapshot.pending_frame_count, 0u); EXPECT_EQ(snapshot.latest_sequence, 1u); EXPECT_FALSE(plot.render_prepared_frame()); snapshot = plot.frame_observer_snapshot(); EXPECT_EQ(snapshot.last_event, "swap_failed"); EXPECT_EQ(snapshot.failed_operation_count, 1u); EXPECT_EQ(snapshot.pending_frame_count, 0u); EXPECT_EQ(snapshot.latest_sequence, 1u); } TEST(Renderive_Core2_Frame_Pipeline, ManualDiscardAndFailedRefreshUpdateObserverState) { Manual_Scene2D plot; plot.init(); plot.set_viewport_size({64, 40}); ASSERT_TRUE(plot.prepare_frame()); ASSERT_TRUE(plot.discard_pending_frame()); auto snapshot = plot.frame_observer_snapshot(); EXPECT_EQ(snapshot.last_event, "manually_discarded"); EXPECT_EQ(snapshot.produced_frame_count, 1u); EXPECT_EQ(snapshot.dropped_frame_count, 1u); EXPECT_EQ(snapshot.pending_frame_count, 0u); EXPECT_EQ(snapshot.latest_sequence, 1u); const auto last_paint_duration_ns = snapshot.paint_duration_ns; EXPECT_FALSE(plot.refresh_manual_frame()); snapshot = plot.frame_observer_snapshot(); EXPECT_EQ(snapshot.last_event, "refresh_failed"); EXPECT_EQ(snapshot.failed_operation_count, 1u); EXPECT_EQ(snapshot.pending_frame_count, 0u); EXPECT_EQ(snapshot.latest_sequence, 1u); EXPECT_EQ(snapshot.paint_duration_ns, last_paint_duration_ns); } TEST(Renderive_Core2_Frame_Pipeline, PlaybackPreservesQueueDepthAndEmptyAcquireFailure) { Playback_Scene2D plot; plot.init(); plot.set_viewport_size({64, 40}); ASSERT_TRUE(plot.prepare_frame()); ASSERT_TRUE(plot.prepare_frame()); ASSERT_TRUE(plot.prepare_frame()); auto snapshot = plot.frame_observer_snapshot(); EXPECT_EQ(snapshot.produced_frame_count, 3u); EXPECT_EQ(snapshot.pending_frame_count, 3u); EXPECT_EQ(snapshot.latest_sequence, 3u); ASSERT_TRUE(plot.render_prepared_frame()); ASSERT_TRUE(plot.render_prepared_frame()); ASSERT_TRUE(plot.render_prepared_frame()); snapshot = plot.frame_observer_snapshot(); EXPECT_EQ(snapshot.last_event, "rendered"); EXPECT_EQ(snapshot.consumed_frame_count, 3u); EXPECT_EQ(snapshot.pending_frame_count, 0u); EXPECT_EQ(snapshot.latest_sequence, 3u); const auto last_paint_duration_ns = snapshot.paint_duration_ns; const auto last_render_duration_ns = snapshot.render_duration_ns; const auto last_queue_wait_ns = snapshot.queue_wait_ns; EXPECT_FALSE(plot.render_prepared_frame()); snapshot = plot.frame_observer_snapshot(); EXPECT_EQ(snapshot.last_event, "queue_empty"); EXPECT_EQ(snapshot.failed_operation_count, 1u); EXPECT_EQ(snapshot.pending_frame_count, 0u); EXPECT_EQ(snapshot.latest_sequence, 3u); EXPECT_EQ(snapshot.paint_duration_ns, last_paint_duration_ns); EXPECT_EQ(snapshot.render_duration_ns, last_render_duration_ns); EXPECT_EQ(snapshot.queue_wait_ns, last_queue_wait_ns); } TEST(Renderive_Core2_Frame_Pipeline, PlaybackFullCyclePreservesPreviouslyQueuedFrames) { Playback_Scene2D plot; plot.init(); plot.set_viewport_size({96, 54}); ASSERT_TRUE(plot.prepare_frame()); ASSERT_TRUE(plot.prepare_frame()); ASSERT_TRUE(plot.prepare_frame()); auto snapshot = plot.frame_observer_snapshot(); ASSERT_EQ(snapshot.pending_frame_count, 3u); ASSERT_EQ(snapshot.produced_frame_count, 3u); ASSERT_TRUE(plot.render_frame(true)); snapshot = plot.frame_observer_snapshot(); EXPECT_EQ(snapshot.mode, Frame_Control_Mode::Playback); EXPECT_EQ(snapshot.last_event, "rendered"); EXPECT_EQ(snapshot.produced_frame_count, 4u); EXPECT_EQ(snapshot.consumed_frame_count, 1u); EXPECT_EQ(snapshot.pending_frame_count, 3u); // The cycle enqueues sequence 4 but must consume the FIFO head (sequence 1). EXPECT_EQ(snapshot.latest_sequence, 1u); EXPECT_FALSE(plot.discard_pending_frame()); EXPECT_EQ(plot.frame_observer_snapshot().pending_frame_count, 3u); } TEST(Renderive_Core2_Frame_Pipeline, EveryModeHonorsActiveDirtyAndForceRenderGates) { const auto verify = [](Frame_Control_Mode mode) { Scene_Type scene; scene.init(); scene.set_viewport_size({80, 45}); EXPECT_FALSE(scene.view_active()); EXPECT_FALSE(scene.render_frame(false)); EXPECT_EQ(scene.frame_observer_snapshot().observation_count, 0u); scene.activate_view(); ASSERT_TRUE(scene.render_frame(false)); const auto completed = scene.frame_observer_snapshot(); EXPECT_EQ(completed.mode, mode); EXPECT_EQ(completed.consumed_frame_count, 1u); EXPECT_FALSE(scene.render_frame(false)); EXPECT_EQ(scene.frame_observer_snapshot().consumed_frame_count, 1u); scene.deactivate_view(); scene.set_background_color(Color{8, 16, 24, 255}); EXPECT_FALSE(scene.render_frame(false)); ASSERT_TRUE(scene.render_frame(true)); EXPECT_EQ(scene.frame_observer_snapshot().consumed_frame_count, 2u); }; verify.template operator()(Frame_Control_Mode::Manual); verify.template operator()(Frame_Control_Mode::Low_Latency); verify.template operator()(Frame_Control_Mode::Playback); } TEST(Renderive_Core2_Frame_Pipeline, FrequencyConfigurationRejectsInvalidValuesAndWrongModes) { Scene2D low_latency; EXPECT_THROW(low_latency.set_max_render_fps(0.0), std::invalid_argument); EXPECT_THROW(low_latency.set_max_render_fps(-1.0), std::invalid_argument); EXPECT_THROW(low_latency.set_max_render_fps(std::numeric_limits::quiet_NaN()), std::invalid_argument); EXPECT_THROW(low_latency.set_max_render_fps(std::numeric_limits::infinity()), std::invalid_argument); low_latency.set_max_render_fps(1'000.0); EXPECT_DOUBLE_EQ(low_latency.max_render_fps(), 1'000.0); Manual_Scene2D manual; Playback_Scene2D playback; EXPECT_THROW(manual.set_max_render_fps(60.0), std::logic_error); EXPECT_THROW(playback.set_max_render_fps(60.0), std::logic_error); EXPECT_FALSE(playback.refresh_manual_frame()); EXPECT_FALSE(playback.discard_pending_frame()); } } // namespace } // namespace renderive