错误处理
This commit is contained in:
@@ -96,11 +96,11 @@ struct Scene_Model {
|
||||
::renderive::Keyboard_Modifier modifiers) = 0;
|
||||
[[nodiscard]] virtual Scene_Control_Error dispatch_key(
|
||||
const ::renderive::Key_Event& event) = 0;
|
||||
[[nodiscard]] virtual bool prepare_frame() = 0;
|
||||
[[nodiscard]] virtual bool refresh_manual_frame() = 0;
|
||||
[[nodiscard]] virtual Frame_Request_Error prepare_frame() = 0;
|
||||
[[nodiscard]] virtual Frame_Request_Error refresh_manual_frame() = 0;
|
||||
[[nodiscard]] virtual bool discard_pending_frame() = 0;
|
||||
[[nodiscard]] virtual bool render_prepared_frame() = 0;
|
||||
[[nodiscard]] virtual bool request_frame() = 0;
|
||||
[[nodiscard]] virtual Frame_Request_Error render_prepared_frame() = 0;
|
||||
[[nodiscard]] virtual Frame_Request_Error request_frame() = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<const Pixel_Frame> latest_frame()
|
||||
const = 0;
|
||||
[[nodiscard]] virtual Frame_Status frame_status() const = 0;
|
||||
@@ -187,7 +187,9 @@ struct Basic_Point_Scene final
|
||||
&detail::Scene_State::visual_family>(options.visual_family);
|
||||
{
|
||||
auto builder = this->attach_builder();
|
||||
builder.attach(renderive_Owner<Point_Visual>(visual));
|
||||
if (builder.attach(renderive_Owner<Point_Visual>(visual)) !=
|
||||
Scene_Edit_Error::none)
|
||||
throw std::logic_error("validated point visual attachment failed");
|
||||
}
|
||||
const detail::Scene_State initial{
|
||||
options.viewport, options.clear_color, options.visual_family};
|
||||
@@ -278,18 +280,21 @@ struct Basic_Point_Scene final
|
||||
return error;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool prepare_frame() override {
|
||||
[[nodiscard]] Frame_Request_Error prepare_frame() override {
|
||||
auto painter = this->frame_control.acquire_painter();
|
||||
if (!painter)
|
||||
return false;
|
||||
return Frame_Request_Error::painter_unavailable;
|
||||
this->publish_frame_state(*painter);
|
||||
return true;
|
||||
return Frame_Request_Error::none;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool refresh_manual_frame() override {
|
||||
if constexpr (Mode == Frame_Mode::Manual)
|
||||
return this->frame_control.refresh();
|
||||
return false;
|
||||
[[nodiscard]] Frame_Request_Error refresh_manual_frame() override {
|
||||
if constexpr (Mode == Frame_Mode::Manual) {
|
||||
return this->frame_control.refresh() == Manual_Refresh_Error::none
|
||||
? Frame_Request_Error::none
|
||||
: Frame_Request_Error::no_pending_frame;
|
||||
}
|
||||
return Frame_Request_Error::manual_refresh_not_supported;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool discard_pending_frame() override {
|
||||
@@ -299,19 +304,31 @@ struct Basic_Point_Scene final
|
||||
return this->frame_control.discard_pending_frame();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool render_prepared_frame() override {
|
||||
[[nodiscard]] Frame_Request_Error render_prepared_frame() override {
|
||||
auto renderer = this->frame_control.acquire_renderer();
|
||||
if (!renderer)
|
||||
return false;
|
||||
return Scene_Base::render(*renderer) == Scene_Render_Error::none;
|
||||
return Frame_Request_Error::renderer_unavailable;
|
||||
switch (Scene_Base::render(*renderer)) {
|
||||
case Scene_Render_Error::none:
|
||||
return Frame_Request_Error::none;
|
||||
case Scene_Render_Error::cancelled:
|
||||
return Frame_Request_Error::cancelled;
|
||||
case Scene_Render_Error::deadline_exceeded:
|
||||
return Frame_Request_Error::deadline_exceeded;
|
||||
case Scene_Render_Error::external_failure:
|
||||
return Frame_Request_Error::external_failure;
|
||||
case Scene_Render_Error::shutting_down:
|
||||
return Frame_Request_Error::scene_shutting_down;
|
||||
}
|
||||
throw std::logic_error("unknown scene render error");
|
||||
}
|
||||
|
||||
[[nodiscard]] bool request_frame() override {
|
||||
if (!prepare_frame())
|
||||
return false;
|
||||
[[nodiscard]] Frame_Request_Error request_frame() override {
|
||||
if (const auto error = prepare_frame(); error != Frame_Request_Error::none)
|
||||
return error;
|
||||
if constexpr (Mode == Frame_Mode::Manual) {
|
||||
if (!refresh_manual_frame())
|
||||
return false;
|
||||
if (const auto error = refresh_manual_frame(); error != Frame_Request_Error::none)
|
||||
return error;
|
||||
}
|
||||
return render_prepared_frame();
|
||||
}
|
||||
@@ -372,7 +389,6 @@ struct Basic_Point_Scene final
|
||||
{completion.capacity, completion.in_flight, completion.peak_in_flight,
|
||||
completion.watched, completion.peak_watched, completion.backpressure_count,
|
||||
completion.backpressure_wait_ns},
|
||||
render.unhandled_exception_count,
|
||||
completion.fault_count,
|
||||
completion.abandoned_count
|
||||
};
|
||||
@@ -460,6 +476,8 @@ struct Basic_Point_Scene final
|
||||
static_cast<void>(source->fail(
|
||||
std::current_exception()));
|
||||
}
|
||||
}, [source](std::exception_ptr exception) {
|
||||
static_cast<void>(source->fail(std::move(exception)));
|
||||
});
|
||||
if (!prepared_completion) {
|
||||
static_cast<void>(source->cancel());
|
||||
@@ -483,6 +501,9 @@ struct Basic_Point_Scene final
|
||||
std::current_exception()));
|
||||
}
|
||||
},
|
||||
[source](std::exception_ptr exception) {
|
||||
static_cast<void>(source->fail(std::move(exception)));
|
||||
},
|
||||
observe);
|
||||
if (!prepared_gpu_completion) {
|
||||
static_cast<void>(source->cancel());
|
||||
@@ -538,6 +559,8 @@ struct Basic_Point_Scene final
|
||||
static_cast<void>(
|
||||
source->fail(std::current_exception()));
|
||||
}
|
||||
}, [source](std::exception_ptr exception) {
|
||||
static_cast<void>(source->fail(std::move(exception)));
|
||||
});
|
||||
if (post_error != detail::Render_Domain::Error::none)
|
||||
static_cast<void>(source->cancel());
|
||||
@@ -643,17 +666,17 @@ Scene_Control_Error Point_Scene::dispatch(const ::renderive::Key_Event& event) {
|
||||
return impl_->model->dispatch_key(event);
|
||||
}
|
||||
|
||||
bool Point_Scene::prepare_frame() { return impl_->model->prepare_frame(); }
|
||||
bool Point_Scene::refresh_manual_frame() {
|
||||
Frame_Request_Error Point_Scene::prepare_frame() { return impl_->model->prepare_frame(); }
|
||||
Frame_Request_Error Point_Scene::refresh_manual_frame() {
|
||||
return impl_->model->refresh_manual_frame();
|
||||
}
|
||||
bool Point_Scene::discard_pending_frame() {
|
||||
return impl_->model->discard_pending_frame();
|
||||
}
|
||||
bool Point_Scene::render_prepared_frame() {
|
||||
Frame_Request_Error Point_Scene::render_prepared_frame() {
|
||||
return impl_->model->render_prepared_frame();
|
||||
}
|
||||
bool Point_Scene::request_frame() { return impl_->model->request_frame(); }
|
||||
Frame_Request_Error Point_Scene::request_frame() { return impl_->model->request_frame(); }
|
||||
|
||||
std::shared_ptr<const Pixel_Frame> Point_Scene::latest_frame() const {
|
||||
return impl_->model->latest_frame();
|
||||
|
||||
@@ -34,6 +34,17 @@ enum class Scene_Control_Error : std::uint8_t {
|
||||
invalid_event,
|
||||
backend_unavailable
|
||||
};
|
||||
enum class Frame_Request_Error : std::uint8_t {
|
||||
none,
|
||||
painter_unavailable,
|
||||
manual_refresh_not_supported,
|
||||
no_pending_frame,
|
||||
renderer_unavailable,
|
||||
cancelled,
|
||||
deadline_exceeded,
|
||||
external_failure,
|
||||
scene_shutting_down
|
||||
};
|
||||
|
||||
enum class Frame_Mode : std::uint8_t {
|
||||
Manual,
|
||||
@@ -88,7 +99,6 @@ struct Runtime_Admission_Statistics {
|
||||
struct Runtime_Statistics {
|
||||
Runtime_Admission_Statistics render_domain;
|
||||
Runtime_Admission_Statistics gpu_completion;
|
||||
std::uint64_t render_domain_unhandled_exception_count{};
|
||||
std::uint64_t gpu_completion_fault_count{};
|
||||
std::uint64_t gpu_completion_abandoned_count{};
|
||||
};
|
||||
@@ -142,11 +152,11 @@ public:
|
||||
[[nodiscard]] Scene_Control_Error dispatch(const ::renderive::Key_Event& event);
|
||||
|
||||
// Frame strategy operations. request_frame() is the complete low-latency path.
|
||||
[[nodiscard]] bool prepare_frame();
|
||||
[[nodiscard]] bool refresh_manual_frame();
|
||||
[[nodiscard]] Frame_Request_Error prepare_frame();
|
||||
[[nodiscard]] Frame_Request_Error refresh_manual_frame();
|
||||
[[nodiscard]] bool discard_pending_frame();
|
||||
[[nodiscard]] bool render_prepared_frame();
|
||||
[[nodiscard]] bool request_frame();
|
||||
[[nodiscard]] Frame_Request_Error render_prepared_frame();
|
||||
[[nodiscard]] Frame_Request_Error request_frame();
|
||||
|
||||
[[nodiscard]] std::shared_ptr<const Pixel_Frame> latest_frame() const;
|
||||
[[nodiscard]] Frame_Status frame_status() const;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "Gpu_Completion_Service.h"
|
||||
#include "renderive/error/Error_Policy.hpp"
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
@@ -28,13 +27,13 @@ Gpu_Completion_Service::Reservation::Reservation(Reservation&& other) noexcept
|
||||
void Gpu_Completion_Service::Reservation::watch(VkDevice device,
|
||||
VkFence fence) {
|
||||
if (!pending_ || device == VK_NULL_HANDLE || fence == VK_NULL_HANDLE)
|
||||
renderive::error::unexpected<std::logic_error>("GPU completion reservation or fence is invalid");
|
||||
throw std::logic_error("GPU completion reservation or fence is invalid");
|
||||
auto pending = std::exchange(pending_, {});
|
||||
auto* const service = pending->service;
|
||||
{
|
||||
std::lock_guard lock(pending->mutex);
|
||||
if (pending->status != Pending_Fence::Status::reserved)
|
||||
renderive::error::unexpected<std::logic_error>("GPU completion reservation is not reserved");
|
||||
throw std::logic_error("GPU completion reservation is not reserved");
|
||||
pending->device = device;
|
||||
pending->fence = fence;
|
||||
// This timestamp is part of correctness, not only observability: it
|
||||
@@ -91,13 +90,16 @@ void Gpu_Completion_Service::release_slot() noexcept {
|
||||
slots_.release();
|
||||
}
|
||||
Gpu_Completion_Service::Prepare_Result Gpu_Completion_Service::prepare(
|
||||
Completion completion, bool observe) {
|
||||
Completion completion, Exception_Handler on_exception, bool observe) {
|
||||
if (!completion)
|
||||
renderive::error::unexpected<std::invalid_argument>("GPU completion callback is empty");
|
||||
throw std::invalid_argument("GPU completion callback is empty");
|
||||
if (!on_exception)
|
||||
throw std::invalid_argument("GPU completion exception handler is empty");
|
||||
if (stopping_.load(std::memory_order_acquire))
|
||||
return {{}, Error::stopping};
|
||||
auto pending = std::make_shared<Pending_Fence>();
|
||||
pending->completion = std::move(completion);
|
||||
pending->on_exception = std::move(on_exception);
|
||||
pending->observe = observe;
|
||||
pending->service = this;
|
||||
acquire_slot();
|
||||
@@ -107,7 +109,7 @@ Gpu_Completion_Service::Prepare_Result Gpu_Completion_Service::prepare(
|
||||
}
|
||||
if (!pending_.try_push(pending)) {
|
||||
release_slot();
|
||||
renderive::error::unexpected<std::logic_error>("GPU completion admission invariant violated");
|
||||
throw std::logic_error("GPU completion admission invariant violated");
|
||||
}
|
||||
wake();
|
||||
return {Reservation(std::move(pending)), Error::none};
|
||||
@@ -129,7 +131,7 @@ void Gpu_Completion_Service::wake() noexcept {
|
||||
wake_generation_.fetch_add(1, std::memory_order_release);
|
||||
wake_condition_.notify_one();
|
||||
}
|
||||
void Gpu_Completion_Service::run() noexcept {
|
||||
void Gpu_Completion_Service::run() {
|
||||
struct Device_Fences {
|
||||
VkDevice device{VK_NULL_HANDLE};
|
||||
std::vector<VkFence> fences;
|
||||
@@ -148,11 +150,13 @@ void Gpu_Completion_Service::run() noexcept {
|
||||
const std::shared_ptr<Pending_Fence>& pending,
|
||||
VkResult result, Completion_Error error) {
|
||||
Completion completion;
|
||||
Exception_Handler on_exception;
|
||||
std::chrono::steady_clock::time_point watched_at{};
|
||||
bool observe{};
|
||||
{
|
||||
std::lock_guard lock(pending->mutex);
|
||||
completion = std::move(pending->completion);
|
||||
on_exception = std::move(pending->on_exception);
|
||||
watched_at = pending->watched_at;
|
||||
observe = pending->observe;
|
||||
pending->status = Pending_Fence::Status::canceled;
|
||||
@@ -170,6 +174,7 @@ void Gpu_Completion_Service::run() noexcept {
|
||||
try {
|
||||
completion(std::move(completion_result));
|
||||
} catch (...) {
|
||||
on_exception(std::current_exception());
|
||||
}
|
||||
release_slot();
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <semaphore>
|
||||
@@ -41,6 +42,7 @@ public:
|
||||
std::uint64_t abandoned_count{};
|
||||
};
|
||||
using Completion = std::function<void(Result)>;
|
||||
using Exception_Handler = std::function<void(std::exception_ptr)>;
|
||||
class Reservation final {
|
||||
public:
|
||||
Reservation() = default;
|
||||
@@ -66,7 +68,9 @@ public:
|
||||
static Gpu_Completion_Service& instance();
|
||||
Gpu_Completion_Service(const Gpu_Completion_Service&) = delete;
|
||||
Gpu_Completion_Service& operator=(const Gpu_Completion_Service&) = delete;
|
||||
[[nodiscard]] Prepare_Result prepare(Completion completion, bool observe);
|
||||
[[nodiscard]] Prepare_Result prepare(Completion completion,
|
||||
Exception_Handler on_exception,
|
||||
bool observe);
|
||||
[[nodiscard]] Statistics statistics() const noexcept;
|
||||
private:
|
||||
struct Pending_Fence {
|
||||
@@ -79,6 +83,7 @@ private:
|
||||
VkDevice device{VK_NULL_HANDLE};
|
||||
VkFence fence{VK_NULL_HANDLE};
|
||||
Completion completion;
|
||||
Exception_Handler on_exception;
|
||||
std::chrono::steady_clock::time_point watched_at{};
|
||||
Gpu_Completion_Service* service{};
|
||||
Status status{Status::reserved};
|
||||
@@ -91,7 +96,7 @@ private:
|
||||
void acquire_slot();
|
||||
void release_slot() noexcept;
|
||||
void wake() noexcept;
|
||||
void run() noexcept;
|
||||
void run();
|
||||
static constexpr std::ptrdiff_t default_capacity = 1024;
|
||||
static constexpr std::uint64_t fence_wait_timeout_ns = 1'000'000;
|
||||
static constexpr std::uint64_t maximum_fence_age_ns = 30'000'000'000ULL;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "Render_Domain.h"
|
||||
#include "renderive/error/Error_Policy.hpp"
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
namespace renderive::render_3d::detail {
|
||||
namespace {
|
||||
@@ -69,8 +69,7 @@ void Render_Domain::request_stop() noexcept {
|
||||
expected, true, std::memory_order_acq_rel))
|
||||
return;
|
||||
slots_.acquire();
|
||||
if (!tasks_.try_push(std::unique_ptr<Task>{}))
|
||||
renderive::error::fast_fail("render domain stop marker queue rejected admitted task");
|
||||
tasks_.push(std::unique_ptr<Task>{});
|
||||
}
|
||||
void Render_Domain::update_peak(std::atomic_size_t& peak, std::size_t value) noexcept {
|
||||
std::size_t current = peak.load(std::memory_order_relaxed);
|
||||
@@ -93,9 +92,12 @@ void Render_Domain::release_admission() noexcept {
|
||||
admitted_.fetch_sub(1, std::memory_order_relaxed);
|
||||
slots_.release();
|
||||
}
|
||||
Render_Domain::Prepare_Result Render_Domain::prepare(std::function<void()> function) {
|
||||
Render_Domain::Prepare_Result Render_Domain::prepare(
|
||||
std::function<void()> function, Exception_Handler on_exception) {
|
||||
if (!function)
|
||||
renderive::error::unexpected<std::invalid_argument>("render domain task is empty");
|
||||
throw std::invalid_argument("render domain task is empty");
|
||||
if (!on_exception)
|
||||
throw std::invalid_argument("render domain exception handler is empty");
|
||||
if (stopping_.load(std::memory_order_acquire))
|
||||
return {{}, Error::stopping};
|
||||
acquire_admission();
|
||||
@@ -104,25 +106,27 @@ Render_Domain::Prepare_Result Render_Domain::prepare(std::function<void()> funct
|
||||
return {{}, Error::stopping};
|
||||
}
|
||||
try {
|
||||
return {Prepared_Task(shared_from_this(), std::make_unique<Task>(std::move(function))), Error::none};
|
||||
return {Prepared_Task(shared_from_this(), std::make_unique<Task>(
|
||||
std::move(function), std::move(on_exception))), Error::none};
|
||||
} catch (...) {
|
||||
release_admission();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
Render_Domain::Error Render_Domain::post(std::function<void()> function) {
|
||||
auto prepared = prepare(std::move(function));
|
||||
Render_Domain::Error Render_Domain::post(
|
||||
std::function<void()> function, Exception_Handler on_exception) {
|
||||
auto prepared = prepare(std::move(function), std::move(on_exception));
|
||||
if (!prepared)
|
||||
return prepared.error;
|
||||
return post(std::move(prepared.task));
|
||||
}
|
||||
Render_Domain::Error Render_Domain::post(Prepared_Task task) {
|
||||
if (!task.task_ || task.domain_.get() != this)
|
||||
renderive::error::unexpected<std::logic_error>("render domain prepared task is invalid");
|
||||
throw std::logic_error("render domain prepared task is invalid");
|
||||
if (stopping_.load(std::memory_order_acquire))
|
||||
return Error::stopping;
|
||||
if (!tasks_.try_push(std::move(task.task_)))
|
||||
renderive::error::unexpected<std::logic_error>("render domain admission invariant violated");
|
||||
throw std::logic_error("render domain admission invariant violated");
|
||||
const std::size_t queued = queued_.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
update_peak(peak_queued_, queued);
|
||||
task.domain_.reset();
|
||||
@@ -136,8 +140,7 @@ Render_Domain::Statistics Render_Domain::statistics() const noexcept {
|
||||
queued_.load(std::memory_order_relaxed),
|
||||
peak_queued_.load(std::memory_order_relaxed),
|
||||
backpressure_count_.load(std::memory_order_relaxed),
|
||||
backpressure_wait_ns_.load(std::memory_order_relaxed),
|
||||
unhandled_exception_count_.load(std::memory_order_relaxed)
|
||||
backpressure_wait_ns_.load(std::memory_order_relaxed)
|
||||
};
|
||||
}
|
||||
void Render_Domain::run() {
|
||||
@@ -155,7 +158,7 @@ void Render_Domain::run() {
|
||||
try {
|
||||
task->function();
|
||||
} catch (...) {
|
||||
unhandled_exception_count_.fetch_add(1, std::memory_order_relaxed);
|
||||
task->on_exception(std::current_exception());
|
||||
}
|
||||
task.reset();
|
||||
if (destroy_on_exit_.load(std::memory_order_acquire)) {
|
||||
|
||||
@@ -17,8 +17,12 @@
|
||||
namespace renderive::render_3d::detail {
|
||||
class Render_Domain final : public std::enable_shared_from_this<Render_Domain> {
|
||||
struct Task {
|
||||
explicit Task(std::function<void()> value) : function(std::move(value)) {}
|
||||
Task(std::function<void()> value,
|
||||
std::function<void(std::exception_ptr)> exception_handler)
|
||||
: function(std::move(value)),
|
||||
on_exception(std::move(exception_handler)) {}
|
||||
std::function<void()> function;
|
||||
std::function<void(std::exception_ptr)> on_exception;
|
||||
};
|
||||
public:
|
||||
enum class Error : std::uint8_t {
|
||||
@@ -33,7 +37,6 @@ public:
|
||||
std::size_t peak_queued{};
|
||||
std::uint64_t backpressure_count{};
|
||||
std::uint64_t backpressure_wait_ns{};
|
||||
std::uint64_t unhandled_exception_count{};
|
||||
};
|
||||
class Prepared_Task final {
|
||||
public:
|
||||
@@ -62,8 +65,11 @@ public:
|
||||
~Render_Domain();
|
||||
Render_Domain(const Render_Domain&) = delete;
|
||||
Render_Domain& operator=(const Render_Domain&) = delete;
|
||||
[[nodiscard]] Prepare_Result prepare(std::function<void()> function);
|
||||
[[nodiscard]] Error post(std::function<void()> function);
|
||||
using Exception_Handler = std::function<void(std::exception_ptr)>;
|
||||
[[nodiscard]] Prepare_Result prepare(std::function<void()> function,
|
||||
Exception_Handler on_exception);
|
||||
[[nodiscard]] Error post(std::function<void()> function,
|
||||
Exception_Handler on_exception);
|
||||
[[nodiscard]] Error post(Prepared_Task task);
|
||||
template <class Result>
|
||||
struct Invoke_Result {
|
||||
@@ -89,7 +95,10 @@ public:
|
||||
}
|
||||
auto task = std::make_shared<std::packaged_task<Result()>>(std::forward<Function>(function));
|
||||
auto result = task->get_future();
|
||||
output.error = post([task] { (*task)(); });
|
||||
output.error = post([task] { (*task)(); },
|
||||
[](std::exception_ptr exception) {
|
||||
std::rethrow_exception(exception);
|
||||
});
|
||||
if (output.error != Error::none)
|
||||
return output;
|
||||
if constexpr (std::is_void_v<Result>) {
|
||||
@@ -119,7 +128,6 @@ private:
|
||||
std::atomic_size_t peak_queued_{};
|
||||
std::atomic_uint64_t backpressure_count_{};
|
||||
std::atomic_uint64_t backpressure_wait_ns_{};
|
||||
std::atomic_uint64_t unhandled_exception_count_{};
|
||||
std::atomic_bool stopping_{};
|
||||
std::atomic_bool destroy_on_exit_{};
|
||||
std::thread thread_;
|
||||
|
||||
@@ -15,16 +15,23 @@ TEST(GpuCompletionService, AbandonedReservationDoesNotComplete) {
|
||||
auto prepared = Gpu_Completion_Service::instance().prepare(
|
||||
[&](Gpu_Completion_Service::Result) {
|
||||
completion_count.fetch_add(1, std::memory_order_relaxed);
|
||||
}, false);
|
||||
}, [](std::exception_ptr) {}, false);
|
||||
ASSERT_TRUE(prepared);
|
||||
}
|
||||
EXPECT_EQ(completion_count.load(std::memory_order_relaxed), 0);
|
||||
}
|
||||
TEST(GpuCompletionService, CanceledReservationWakesIdleService) {
|
||||
auto& service = Gpu_Completion_Service::instance();
|
||||
const auto previous_deadline =
|
||||
std::chrono::steady_clock::now() + std::chrono::milliseconds(100);
|
||||
while (service.statistics().in_flight != 0 &&
|
||||
std::chrono::steady_clock::now() < previous_deadline)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
const auto baseline = service.statistics().in_flight;
|
||||
ASSERT_EQ(baseline, 0U);
|
||||
{
|
||||
auto prepared = service.prepare([](Gpu_Completion_Service::Result) {}, false);
|
||||
auto prepared = service.prepare([](Gpu_Completion_Service::Result) {},
|
||||
[](std::exception_ptr) {}, false);
|
||||
ASSERT_TRUE(prepared);
|
||||
EXPECT_EQ(service.statistics().in_flight, baseline + 1);
|
||||
}
|
||||
|
||||
@@ -59,7 +59,9 @@ std::size_t colored_pixel_count(const Pixel_Frame& frame) {
|
||||
TEST(PointRenderIntegration, RendersRealRgbaAndResizes) {
|
||||
try {
|
||||
auto demo = make_test_scene(Scene_Options{.viewport = {320, 200}});
|
||||
ASSERT_TRUE(demo.scene->request_frame());
|
||||
ASSERT_EQ(demo.scene->request_frame(), Frame_Request_Error::none);
|
||||
ASSERT_EQ(demo.scene->render_scene().wait_for_render(),
|
||||
Scene_Render_Error::none);
|
||||
auto first = demo.scene->latest_frame();
|
||||
ASSERT_NE(first, nullptr);
|
||||
EXPECT_EQ(first->extent, (Extent{320, 200}));
|
||||
@@ -67,7 +69,9 @@ TEST(PointRenderIntegration, RendersRealRgbaAndResizes) {
|
||||
EXPECT_GT(colored_pixel_count(*first), 100U);
|
||||
|
||||
demo.scene->resize({480, 270});
|
||||
ASSERT_TRUE(demo.scene->request_frame());
|
||||
ASSERT_EQ(demo.scene->request_frame(), Frame_Request_Error::none);
|
||||
ASSERT_EQ(demo.scene->render_scene().wait_for_render(),
|
||||
Scene_Render_Error::none);
|
||||
auto resized = demo.scene->latest_frame();
|
||||
ASSERT_NE(resized, nullptr);
|
||||
EXPECT_EQ(resized->extent, (Extent{480, 270}));
|
||||
@@ -81,7 +85,9 @@ TEST(PointRenderIntegration, RendersRealRgbaAndResizes) {
|
||||
TEST(PointRenderIntegration, KernelEventsReachDatovizArcballOnItsDomain) {
|
||||
try {
|
||||
auto demo = make_test_scene(Scene_Options{.viewport = {320, 200}});
|
||||
ASSERT_TRUE(demo.scene->request_frame());
|
||||
ASSERT_EQ(demo.scene->request_frame(), Frame_Request_Error::none);
|
||||
ASSERT_EQ(demo.scene->render_scene().wait_for_render(),
|
||||
Scene_Render_Error::none);
|
||||
const auto before = demo.scene->latest_frame()->rgba8;
|
||||
|
||||
::renderive::Basic_Pointer_Event<Event_Point> press(
|
||||
@@ -102,7 +108,9 @@ TEST(PointRenderIntegration, KernelEventsReachDatovizArcballOnItsDomain) {
|
||||
release.button = ::renderive::Mouse_Button::Left;
|
||||
demo.scene->dispatch(release);
|
||||
|
||||
ASSERT_TRUE(demo.scene->request_frame());
|
||||
ASSERT_EQ(demo.scene->request_frame(), Frame_Request_Error::none);
|
||||
ASSERT_EQ(demo.scene->render_scene().wait_for_render(),
|
||||
Scene_Render_Error::none);
|
||||
const auto after_drag = demo.scene->latest_frame();
|
||||
ASSERT_NE(after_drag, nullptr);
|
||||
EXPECT_NE(after_drag->rgba8, before);
|
||||
@@ -113,7 +121,9 @@ TEST(PointRenderIntegration, KernelEventsReachDatovizArcballOnItsDomain) {
|
||||
wheel.angle_delta_y = -216.0;
|
||||
demo.scene->dispatch(wheel);
|
||||
|
||||
ASSERT_TRUE(demo.scene->request_frame());
|
||||
ASSERT_EQ(demo.scene->request_frame(), Frame_Request_Error::none);
|
||||
ASSERT_EQ(demo.scene->render_scene().wait_for_render(),
|
||||
Scene_Render_Error::none);
|
||||
const auto after_wheel = demo.scene->latest_frame();
|
||||
ASSERT_NE(after_wheel, nullptr);
|
||||
EXPECT_GT(colored_pixel_count(*after_wheel), 100U);
|
||||
@@ -131,7 +141,8 @@ TEST(PointRenderIntegration,
|
||||
const auto request = kernel_scene.capture_next_frame();
|
||||
ASSERT_TRUE(request);
|
||||
const Capture_Session_Id capture = request.session_id;
|
||||
ASSERT_TRUE(demo.scene->request_frame());
|
||||
ASSERT_EQ(demo.scene->request_frame(), Frame_Request_Error::none);
|
||||
ASSERT_EQ(kernel_scene.wait_for_render(), Scene_Render_Error::none);
|
||||
|
||||
const auto session = kernel_scene.capture_session(capture);
|
||||
ASSERT_TRUE(session);
|
||||
|
||||
@@ -28,7 +28,9 @@ struct Test_Scene final
|
||||
Extent{320, 180});
|
||||
point_id = visual->renderable_id();
|
||||
auto builder = attach_builder();
|
||||
builder.attach(renderive_Owner<Point_Visual>(visual));
|
||||
if (builder.attach(renderive_Owner<Point_Visual>(visual)) !=
|
||||
Scene_Edit_Error::none)
|
||||
throw std::logic_error("test visual attachment failed");
|
||||
}
|
||||
|
||||
~Test_Scene() override { shutdown(); }
|
||||
@@ -41,7 +43,8 @@ struct Test_Scene final
|
||||
}
|
||||
auto renderer = frame_control.acquire_renderer();
|
||||
ASSERT_TRUE(renderer);
|
||||
Scene_Base::render(*renderer);
|
||||
ASSERT_EQ(Scene_Base::render(*renderer), Scene_Render_Error::none);
|
||||
ASSERT_EQ(wait_for_render(), Scene_Render_Error::none);
|
||||
}
|
||||
|
||||
Node_Execution_Result render_scene(
|
||||
@@ -127,8 +130,8 @@ TEST(PointState, RejectsInvalidStateAndPayloadAtTheOwningBoundary) {
|
||||
invalid.style.stroke_width_px = -1.0F;
|
||||
EXPECT_THROW(visual->set<&Point_State::style>(invalid.style),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW(visual->update_points(std::vector<Point>{{{}, {}, 0.0F}}),
|
||||
std::invalid_argument);
|
||||
EXPECT_EQ(visual->update_points(std::vector<Point>{{{}, {}, 0.0F}}),
|
||||
Visual_Data_Error::invalid_data);
|
||||
}
|
||||
|
||||
TEST(PointRenderPlan, OrdersParallelPreparationBeforeSingleSceneRenderNode) {
|
||||
|
||||
@@ -20,7 +20,7 @@ TEST(RenderDomain, PreparedTaskRunsAfterNoThrowHandoff) {
|
||||
std::atomic<bool> executed{};
|
||||
auto prepared = domain->prepare([&] {
|
||||
executed.store(true, std::memory_order_release);
|
||||
});
|
||||
}, [](std::exception_ptr) {});
|
||||
ASSERT_TRUE(prepared);
|
||||
EXPECT_EQ(domain->post(std::move(prepared.task)), Render_Domain::Error::none);
|
||||
EXPECT_TRUE(domain->invoke([] {}));
|
||||
@@ -29,7 +29,7 @@ TEST(RenderDomain, PreparedTaskRunsAfterNoThrowHandoff) {
|
||||
TEST(RenderDomain, AbandonedPreparedTasksReleaseReservedCapacity) {
|
||||
auto domain = Render_Domain::acquire(0x7ffffff9U);
|
||||
for (std::size_t index = 0; index < 128; ++index) {
|
||||
auto prepared = domain->prepare([] {});
|
||||
auto prepared = domain->prepare([] {}, [](std::exception_ptr) {});
|
||||
ASSERT_TRUE(prepared);
|
||||
}
|
||||
EXPECT_TRUE(domain->invoke([] {}));
|
||||
@@ -52,14 +52,20 @@ TEST(RenderDomain, ReportsBoundedAdmissionStatistics) {
|
||||
EXPECT_TRUE(statistics.admitted <= statistics.capacity);
|
||||
EXPECT_TRUE(statistics.queued <= statistics.capacity);
|
||||
}
|
||||
TEST(RenderDomain, ContainsUnhandledFireAndForgetExceptionsAndKeepsRunning) {
|
||||
TEST(RenderDomain, DeliversFireAndForgetExceptionsAndKeepsRunning) {
|
||||
auto domain = Render_Domain::acquire(0x7ffffff6U);
|
||||
const auto before = domain->statistics().unhandled_exception_count;
|
||||
EXPECT_EQ(domain->post([] { throw std::runtime_error("unexpected render-domain failure"); }),
|
||||
std::promise<std::exception_ptr> delivered;
|
||||
auto exception = delivered.get_future();
|
||||
EXPECT_EQ(domain->post(
|
||||
[] { throw std::runtime_error("unexpected render-domain failure"); },
|
||||
[&delivered](std::exception_ptr value) {
|
||||
delivered.set_value(std::move(value));
|
||||
}),
|
||||
Render_Domain::Error::none);
|
||||
ASSERT_EQ(exception.wait_for(std::chrono::seconds(1)),
|
||||
std::future_status::ready);
|
||||
EXPECT_THROW(std::rethrow_exception(exception.get()), std::runtime_error);
|
||||
EXPECT_TRUE(domain->invoke([] {}));
|
||||
const auto after = domain->statistics().unhandled_exception_count;
|
||||
EXPECT_EQ(after, before + 1U);
|
||||
const auto value = domain->invoke([] { return 17; });
|
||||
ASSERT_TRUE(value);
|
||||
EXPECT_EQ(*value.value, 17);
|
||||
@@ -73,7 +79,7 @@ TEST(RenderDomain, FinalOwnerMayBeReleasedOnAffinityThread) {
|
||||
EXPECT_EQ(domain->post([owned = domain, &released]() mutable {
|
||||
owned.reset();
|
||||
released.set_value();
|
||||
}), Render_Domain::Error::none);
|
||||
}, [](std::exception_ptr) {}), Render_Domain::Error::none);
|
||||
domain.reset();
|
||||
EXPECT_EQ(finished.wait_for(std::chrono::seconds(1)),
|
||||
std::future_status::ready);
|
||||
|
||||
Reference in New Issue
Block a user