#include #include "render_3D/Render_3D.h" #include "render_3D/detail/Datoviz_Backend.h" #include "render_3D/detail/Ordered_Backend_Shutdown.h" #include #include #include #include #include namespace renderive { namespace { std::unique_ptr build_or_skip(Frame_3D_Control_Mode mode) { try { Render_3D::Builder builder; return builder .frame_control(mode) .viewport({96, 64}) .frame_slots(2) .gpu(0, false) .clear_color({0.02F, 0.03F, 0.04F, 1.0F}) .build(); } catch (const std::exception&) { return {}; } return {}; } bool publish_and_render(Render_3D& renderer, Frame_3D_Control_Mode mode) { if (!renderer.prepare_frame()) return false; if (mode == Frame_3D_Control_Mode::Manual && !renderer.refresh_manual_frame()) return false; return renderer.render_prepared_frame(); } struct Backend_Harness { explicit Backend_Harness(const detail::Render_3D_State& state) { backend = std::make_unique(0, false, state); targets = backend->create_target_pool(2); } ~Backend_Harness() { if (backend == nullptr) return; if (targets != nullptr) targets->wait_all(); detail::ordered_backend_shutdown( [&] { backend->destroy_runtime(); }, [&] { targets.reset(); }, [&] { backend->destroy_scene(); }, [&] { backend->destroy_gpu_context(); }); } std::unique_ptr backend; std::unique_ptr targets; }; class Render3DStrategyIntegration : public testing::TestWithParam {}; TEST_P(Render3DStrategyIntegration, UsesExternalTargetsAcrossResizeGenerations) { const auto mode = GetParam(); auto renderer = build_or_skip(mode); if (renderer == nullptr) GTEST_SKIP() << "Vulkan/Datoviz backend unavailable"; ASSERT_NE(renderer, nullptr); Sphere_3D sphere; sphere.radius = 0.35F; sphere.color = {220, 80, 40, 255}; const Object_3D_Id id = renderer->add_sphere(sphere); ASSERT_TRUE(publish_and_render(*renderer, mode)); for (int frame = 0; frame < 2; ++frame) { sphere.position.x += 0.02F; ASSERT_TRUE(renderer->replace_sphere(id, sphere)); ASSERT_TRUE(publish_and_render(*renderer, mode)); } for (const Extent_2D extent : {Extent_2D{128, 72}, Extent_2D{80, 80}, Extent_2D{160, 90}}) { renderer->resize(extent); sphere.position.x += 0.05F; ASSERT_TRUE(renderer->replace_sphere(id, sphere)); ASSERT_TRUE(publish_and_render(*renderer, mode)); } renderer->wait_for_render(); } INSTANTIATE_TEST_SUITE_P( AllKernelStrategies, Render3DStrategyIntegration, testing::Values( Frame_3D_Control_Mode::Low_Latency, Frame_3D_Control_Mode::Manual, Frame_3D_Control_Mode::Playback)); TEST(Render3DIntegration, BuiltObjectOutlivesBuilderConfiguration) { std::unique_ptr renderer; try { { Render_3D::Builder builder; renderer = builder.viewport({64, 64}).frame_slots(2).build(); } } catch (const std::exception& error) { GTEST_SKIP() << "Vulkan/Datoviz backend unavailable: " << error.what(); } ASSERT_NE(renderer, nullptr); (void)renderer->add_sphere({}); ASSERT_TRUE(renderer->prepare_frame()); EXPECT_TRUE(renderer->render_prepared_frame()); } TEST(Render3DIntegration, RepeatedCreateRenderResizeDestroyIsStable) { for (int iteration = 0; iteration < 3; ++iteration) { auto renderer = build_or_skip(Frame_3D_Control_Mode::Low_Latency); if (renderer == nullptr) GTEST_SKIP() << "Vulkan/Datoviz backend unavailable"; ASSERT_NE(renderer, nullptr); (void)renderer->add_sphere({}); ASSERT_TRUE(publish_and_render(*renderer, Frame_3D_Control_Mode::Low_Latency)); renderer->resize({static_cast(100 + iteration * 8), 72}); ASSERT_TRUE(publish_and_render(*renderer, Frame_3D_Control_Mode::Low_Latency)); } } TEST(Render3DIntegration, SlotTargetsRotateAndBackendRebuildsFromLogicalState) { detail::Render_3D_State state; state.viewport = {64, 64}; std::unique_ptr harness; try { harness = std::make_unique(state); } catch (const std::exception& error) { GTEST_SKIP() << "Vulkan/Datoviz backend unavailable: " << error.what(); } detail::Frame_Target& slot_0 = harness->targets->acquire(1, state.viewport); ASSERT_TRUE(slot_0.begin()); const DvzStreamFrame slot_0_frame = slot_0.stream_frame(); EXPECT_TRUE(slot_0_frame.command_buffer_recording); EXPECT_TRUE(slot_0_frame.image_borrowed); EXPECT_TRUE(slot_0_frame.image_view_borrowed); EXPECT_TRUE(slot_0_frame.command_buffer_borrowed); slot_0.abort(); detail::Frame_Target& slot_1 = harness->targets->acquire(2, state.viewport); const DvzStreamFrame slot_1_frame = slot_1.stream_frame(); EXPECT_NE(slot_0_frame.image, slot_1_frame.image); EXPECT_EQ(slot_0_frame.resource_generation, slot_1_frame.resource_generation); detail::Frame_Target& reused_slot_0 = harness->targets->acquire(3, state.viewport); const DvzStreamFrame reused_slot_0_frame = reused_slot_0.stream_frame(); EXPECT_EQ(slot_0_frame.image, reused_slot_0_frame.image); EXPECT_EQ(slot_0_frame.command_buffer, reused_slot_0_frame.command_buffer); detail::Render_3D_Frame_Data frame{state, 1, 4}; ASSERT_TRUE(harness->backend->render(frame, *harness->targets)); harness->backend->destroy_runtime(); frame.state.clear_color.red = 0.25F; ++frame.revision; ++frame.frame_sequence; EXPECT_TRUE(harness->backend->render(frame, *harness->targets)); } TEST(Render3DIntegration, ConcurrentStateUpdatesAndRenderingStopBeforeDestroy) { auto renderer = build_or_skip(Frame_3D_Control_Mode::Low_Latency); if (renderer == nullptr) GTEST_SKIP() << "Vulkan/Datoviz backend unavailable"; Sphere_3D sphere; const Object_3D_Id id = renderer->add_sphere(sphere); std::atomic first_frame_prepared{}; std::atomic failed{}; std::thread producer([&] { try { for (int update = 0; update < 6; ++update) { sphere.position.x = static_cast(update) * 0.03F; if (!renderer->replace_sphere(id, sphere) || !renderer->prepare_frame()) { failed.store(true, std::memory_order_release); return; } if (update == 0) first_frame_prepared.store(true, std::memory_order_release); std::this_thread::yield(); } } catch (...) { failed.store(true, std::memory_order_release); } }); std::thread consumer([&] { while (!first_frame_prepared.load(std::memory_order_acquire) && !failed.load(std::memory_order_acquire)) std::this_thread::yield(); try { for (int frame = 0; frame < 6; ++frame) (void)renderer->render_prepared_frame(); } catch (...) { failed.store(true, std::memory_order_release); } }); producer.join(); consumer.join(); ASSERT_FALSE(failed.load(std::memory_order_acquire)); ASSERT_TRUE(renderer->prepare_frame()); ASSERT_TRUE(renderer->render_prepared_frame()); renderer->wait_for_render(); } } // namespace } // namespace renderive