217 lines
7.5 KiB
C++
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
|