Files
Renderive/renderive_package/render_3D/tests/Render_3D_Integration_Tests.cpp
2026-08-13 23:10:04 +08:00

217 lines
7.5 KiB
C++

#include <gtest/gtest.h>
#include "render_3D/Render_3D.h"
#include "render_3D/detail/Datoviz_Backend.h"
#include "render_3D/detail/Ordered_Backend_Shutdown.h"
#include <atomic>
#include <cstdint>
#include <exception>
#include <memory>
#include <thread>
namespace renderive {
namespace {
std::unique_ptr<Render_3D> 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<detail::Datoviz_Backend>(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<detail::Datoviz_Backend> backend;
std::unique_ptr<detail::Frame_Target_Pool> targets;
};
class Render3DStrategyIntegration
: public testing::TestWithParam<Frame_3D_Control_Mode> {};
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<Render_3D> 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<std::uint32_t>(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<Backend_Harness> harness;
try {
harness = std::make_unique<Backend_Harness>(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<bool> first_frame_prepared{};
std::atomic<bool> failed{};
std::thread producer([&] {
try {
for (int update = 0; update < 6; ++update) {
sphere.position.x = static_cast<float>(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