改进
This commit is contained in:
@@ -125,7 +125,7 @@ struct Basic_Point_Scene final
|
||||
Basic_Point_Scene(const Scene_Options& options,
|
||||
std::shared_ptr<Point_Visual> visual)
|
||||
requires (!std::same_as<Strategy, Low_Latency_Frame>)
|
||||
: Kernel_Scene() {
|
||||
: Kernel_Scene(), render_domain(detail::Render_Domain::acquire(options.gpu_index)) {
|
||||
initialize(options, std::move(visual));
|
||||
}
|
||||
|
||||
@@ -134,14 +134,14 @@ struct Basic_Point_Scene final
|
||||
requires std::same_as<Strategy, Low_Latency_Frame>
|
||||
: Kernel_Scene(Observer_State<>{}, typename Strategy::Configuration{
|
||||
.frequency_hz = options.maximum_frames_per_second,
|
||||
.replace_pending_frame = true}) {
|
||||
.replace_pending_frame = true}),
|
||||
render_domain(detail::Render_Domain::acquire(options.gpu_index)) {
|
||||
initialize(options, std::move(visual));
|
||||
}
|
||||
|
||||
~Basic_Point_Scene() override {
|
||||
this->shutdown();
|
||||
gpu_completion.shutdown();
|
||||
render_domain.invoke([this] { backend.reset(); });
|
||||
render_domain->invoke([this] { backend.reset(); });
|
||||
}
|
||||
|
||||
void initialize(const Scene_Options& options,
|
||||
@@ -162,7 +162,7 @@ struct Basic_Point_Scene final
|
||||
}
|
||||
const detail::Scene_State initial{
|
||||
options.viewport, options.clear_color, options.visual_family};
|
||||
render_domain.invoke([this, &options, &initial] {
|
||||
render_domain->invoke([this, &options, &initial] {
|
||||
backend = std::make_unique<detail::Datoviz_Visual_Backend>(
|
||||
options.gpu_index, options.validation_enabled, initial);
|
||||
});
|
||||
@@ -186,7 +186,7 @@ struct Basic_Point_Scene final
|
||||
::renderive::Keyboard_Modifier modifiers) override {
|
||||
const Extent viewport = this->Scene_State_Strategy::template get<
|
||||
&detail::Scene_State::viewport>();
|
||||
render_domain.invoke([this, type, x, y, button, modifiers, viewport] {
|
||||
render_domain->invoke([this, type, x, y, button, modifiers, viewport] {
|
||||
backend->dispatch_pointer(type, x, y, button, modifiers, viewport);
|
||||
});
|
||||
this->notify_model_dirty();
|
||||
@@ -197,7 +197,7 @@ struct Basic_Point_Scene final
|
||||
::renderive::Keyboard_Modifier modifiers) override {
|
||||
const Extent viewport = this->Scene_State_Strategy::template get<
|
||||
&detail::Scene_State::viewport>();
|
||||
render_domain.invoke(
|
||||
render_domain->invoke(
|
||||
[this, x, y, delta_x, delta_y, modifiers, viewport] {
|
||||
backend->dispatch_wheel(x, y, delta_x, delta_y, modifiers,
|
||||
viewport);
|
||||
@@ -206,7 +206,7 @@ struct Basic_Point_Scene final
|
||||
}
|
||||
|
||||
void dispatch_key(const ::renderive::Key_Event& event) override {
|
||||
render_domain.invoke([this, event] { backend->dispatch_key(event); });
|
||||
render_domain->invoke([this, event] { backend->dispatch_key(event); });
|
||||
this->notify_model_dirty();
|
||||
}
|
||||
|
||||
@@ -327,7 +327,7 @@ struct Basic_Point_Scene final
|
||||
auto async_frame = std::make_shared<Async_Frame_State>();
|
||||
auto render_completion = std::make_shared<
|
||||
detail::Render_Domain::Prepared_Task>(
|
||||
render_domain.prepare(
|
||||
render_domain->prepare(
|
||||
[this, async_frame, metrics, diagnostics, source]() mutable {
|
||||
if (!async_frame->pending) {
|
||||
static_cast<void>(source->fail(
|
||||
@@ -375,11 +375,11 @@ struct Basic_Point_Scene final
|
||||
}));
|
||||
auto completion = std::make_shared<
|
||||
detail::Gpu_Completion_Service::Reservation>(
|
||||
gpu_completion.prepare(
|
||||
detail::Gpu_Completion_Service::instance().prepare(
|
||||
[this, async_frame, render_completion](
|
||||
Completion_Result result) noexcept {
|
||||
async_frame->completion = std::move(result);
|
||||
render_domain.post(std::move(*render_completion));
|
||||
render_domain->post(std::move(*render_completion));
|
||||
},
|
||||
observe));
|
||||
const auto queued_at = observe
|
||||
@@ -387,7 +387,7 @@ struct Basic_Point_Scene final
|
||||
: std::chrono::steady_clock::time_point{};
|
||||
|
||||
try {
|
||||
render_domain.post(
|
||||
render_domain->post(
|
||||
[this, scene_state, scene_revision, frame_sequence, prepared,
|
||||
source, async_frame, completion, observe,
|
||||
queued_at] {
|
||||
@@ -432,8 +432,7 @@ struct Basic_Point_Scene final
|
||||
|
||||
std::shared_ptr<Point_Visual> visual;
|
||||
Renderable_Id point_id{};
|
||||
detail::Render_Domain render_domain;
|
||||
detail::Gpu_Completion_Service gpu_completion;
|
||||
std::shared_ptr<detail::Render_Domain> render_domain;
|
||||
std::unique_ptr<detail::Datoviz_Visual_Backend> backend;
|
||||
mutable std::mutex frame_mutex;
|
||||
std::shared_ptr<const Pixel_Frame> latest;
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
#include "Gpu_Completion_Service.h"
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
namespace renderive::render_3d::detail {
|
||||
Gpu_Completion_Service& Gpu_Completion_Service::instance() {
|
||||
static Gpu_Completion_Service service;
|
||||
return service;
|
||||
}
|
||||
Gpu_Completion_Service::Gpu_Completion_Service() {
|
||||
pending_.set_capacity(default_capacity);
|
||||
thread_ = std::thread([this] { run(); });
|
||||
}
|
||||
Gpu_Completion_Service::~Gpu_Completion_Service() {
|
||||
shutdown();
|
||||
stopping_.store(true, std::memory_order_release);
|
||||
wake();
|
||||
if (thread_.joinable())
|
||||
thread_.join();
|
||||
}
|
||||
Gpu_Completion_Service::Reservation::Reservation(std::shared_ptr<Pending_Fence> pending) noexcept : pending_(std::move(pending)) {}
|
||||
Gpu_Completion_Service::Reservation::~Reservation() {
|
||||
@@ -26,108 +34,131 @@ void Gpu_Completion_Service::Reservation::watch(VkDevice device, VkFence fence)
|
||||
std::terminate();
|
||||
pending->device = device;
|
||||
pending->fence = fence;
|
||||
if (pending->observe)
|
||||
pending->watched_at = std::chrono::steady_clock::now();
|
||||
pending->status = Pending_Fence::Status::watched;
|
||||
}
|
||||
pending->ready.notify_one();
|
||||
pending->service->wake();
|
||||
}
|
||||
void Gpu_Completion_Service::Reservation::cancel() noexcept {
|
||||
if (!pending_)
|
||||
return;
|
||||
auto pending = std::exchange(pending_, {});
|
||||
cancel_reserved(pending);
|
||||
pending->service->wake();
|
||||
}
|
||||
void Gpu_Completion_Service::cancel_reserved(const std::shared_ptr<Pending_Fence>& pending) noexcept {
|
||||
if (!pending)
|
||||
return;
|
||||
{
|
||||
std::lock_guard lock(pending->mutex);
|
||||
if (pending->status == Pending_Fence::Status::reserved)
|
||||
pending->status = Pending_Fence::Status::canceled;
|
||||
}
|
||||
pending->ready.notify_one();
|
||||
std::lock_guard lock(pending->mutex);
|
||||
if (pending->status == Pending_Fence::Status::reserved)
|
||||
pending->status = Pending_Fence::Status::canceled;
|
||||
}
|
||||
Gpu_Completion_Service::Reservation Gpu_Completion_Service::prepare(Completion completion, bool observe) {
|
||||
if (!completion)
|
||||
throw std::invalid_argument("GPU completion callback is empty");
|
||||
if (stopping_.load(std::memory_order_acquire))
|
||||
throw std::runtime_error("GPU completion service is stopping");
|
||||
auto pending = std::make_shared<Pending_Fence>();
|
||||
pending->completion = std::move(completion);
|
||||
pending->observe = observe;
|
||||
{
|
||||
std::lock_guard lock(mutex_);
|
||||
if (stopping_)
|
||||
throw std::runtime_error("GPU completion service is stopping");
|
||||
pending->service = this;
|
||||
slots_.acquire();
|
||||
try {
|
||||
pending_.push(pending);
|
||||
} catch (...) {
|
||||
slots_.release();
|
||||
throw;
|
||||
}
|
||||
wake();
|
||||
return Reservation(std::move(pending));
|
||||
}
|
||||
void Gpu_Completion_Service::shutdown() noexcept {
|
||||
{
|
||||
std::lock_guard lock(mutex_);
|
||||
if (stopping_ && !thread_.joinable())
|
||||
return;
|
||||
stopping_ = true;
|
||||
cancel_reserved(active_);
|
||||
std::shared_ptr<Pending_Fence> pending;
|
||||
while (pending_.try_pop(pending))
|
||||
cancel_reserved(pending);
|
||||
pending_.push(std::shared_ptr<Pending_Fence>{});
|
||||
}
|
||||
if (thread_.joinable())
|
||||
thread_.join();
|
||||
void Gpu_Completion_Service::wake() noexcept {
|
||||
wake_.notify_one();
|
||||
}
|
||||
void Gpu_Completion_Service::run() noexcept {
|
||||
std::vector<std::shared_ptr<Pending_Fence>> active;
|
||||
active.reserve(static_cast<std::size_t>(default_capacity));
|
||||
for (;;) {
|
||||
std::shared_ptr<Pending_Fence> pending;
|
||||
pending_.pop(pending);
|
||||
if (!pending)
|
||||
return;
|
||||
{
|
||||
std::lock_guard lock(mutex_);
|
||||
if (stopping_) {
|
||||
cancel_reserved(pending);
|
||||
std::shared_ptr<Pending_Fence> incoming;
|
||||
while (pending_.try_pop(incoming))
|
||||
active.push_back(std::move(incoming));
|
||||
bool progressed{};
|
||||
for (auto iterator = active.begin(); iterator != active.end();) {
|
||||
auto& pending = *iterator;
|
||||
VkDevice device{VK_NULL_HANDLE};
|
||||
VkFence fence{VK_NULL_HANDLE};
|
||||
Completion completion;
|
||||
std::chrono::steady_clock::time_point watched_at{};
|
||||
bool observe{};
|
||||
Pending_Fence::Status status;
|
||||
{
|
||||
std::lock_guard lock(pending->mutex);
|
||||
status = pending->status;
|
||||
if (status == Pending_Fence::Status::watched) {
|
||||
device = pending->device;
|
||||
fence = pending->fence;
|
||||
watched_at = pending->watched_at;
|
||||
observe = pending->observe;
|
||||
}
|
||||
}
|
||||
if (status == Pending_Fence::Status::canceled) {
|
||||
iterator = active.erase(iterator);
|
||||
slots_.release();
|
||||
progressed = true;
|
||||
continue;
|
||||
}
|
||||
active_ = pending;
|
||||
}
|
||||
VkDevice device{VK_NULL_HANDLE};
|
||||
VkFence fence{VK_NULL_HANDLE};
|
||||
Completion completion;
|
||||
bool observe{};
|
||||
{
|
||||
std::unique_lock lock(pending->mutex);
|
||||
pending->ready.wait(lock, [&pending] { return pending->status != Pending_Fence::Status::reserved; });
|
||||
if (pending->status == Pending_Fence::Status::canceled) {
|
||||
lock.unlock();
|
||||
std::lock_guard service_lock(mutex_);
|
||||
active_.reset();
|
||||
if (status == Pending_Fence::Status::reserved) {
|
||||
if (stopping_.load(std::memory_order_acquire)) {
|
||||
cancel_reserved(pending);
|
||||
iterator = active.erase(iterator);
|
||||
slots_.release();
|
||||
progressed = true;
|
||||
continue;
|
||||
}
|
||||
++iterator;
|
||||
continue;
|
||||
}
|
||||
device = pending->device;
|
||||
fence = pending->fence;
|
||||
completion = std::move(pending->completion);
|
||||
observe = pending->observe;
|
||||
}
|
||||
const auto wait_started = observe ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
|
||||
const VkResult result = vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);
|
||||
Result completion_result;
|
||||
if (observe) {
|
||||
const auto wait_duration = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now() - wait_started).count();
|
||||
completion_result.wait_duration_ns = wait_duration > 0 ? static_cast<std::uint64_t>(wait_duration) : 0;
|
||||
}
|
||||
if (result != VK_SUCCESS) {
|
||||
const VkResult result = vkGetFenceStatus(device, fence);
|
||||
if (result == VK_NOT_READY) {
|
||||
++iterator;
|
||||
continue;
|
||||
}
|
||||
{
|
||||
std::lock_guard lock(pending->mutex);
|
||||
completion = std::move(pending->completion);
|
||||
pending->status = Pending_Fence::Status::canceled;
|
||||
}
|
||||
Result completion_result;
|
||||
if (observe) {
|
||||
const auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now() - watched_at).count();
|
||||
completion_result.wait_duration_ns = duration > 0 ? static_cast<std::uint64_t>(duration) : 0;
|
||||
}
|
||||
if (result != VK_SUCCESS) {
|
||||
try {
|
||||
throw std::runtime_error("GPU fence wait failed with Vulkan result " + std::to_string(static_cast<int>(result)));
|
||||
} catch (...) {
|
||||
completion_result.error = std::current_exception();
|
||||
}
|
||||
}
|
||||
try {
|
||||
throw std::runtime_error("GPU fence wait failed with Vulkan result " + std::to_string(static_cast<int>(result)));
|
||||
completion(std::move(completion_result));
|
||||
} catch (...) {
|
||||
completion_result.error = std::current_exception();
|
||||
}
|
||||
iterator = active.erase(iterator);
|
||||
slots_.release();
|
||||
progressed = true;
|
||||
}
|
||||
try {
|
||||
completion(std::move(completion_result));
|
||||
} catch (...) {
|
||||
}
|
||||
{
|
||||
std::lock_guard lock(mutex_);
|
||||
active_.reset();
|
||||
if (stopping_.load(std::memory_order_acquire) && active.empty() && pending_.empty())
|
||||
return;
|
||||
if (!progressed) {
|
||||
std::unique_lock lock(wait_mutex_);
|
||||
if (active.empty())
|
||||
wake_.wait(lock, [this] {
|
||||
return stopping_.load(std::memory_order_acquire) || !pending_.empty();
|
||||
});
|
||||
else
|
||||
wake_.wait_for(lock, poll_interval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
#pragma once
|
||||
#include <volk.h>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <semaphore>
|
||||
#include <thread>
|
||||
#include <oneapi/tbb/concurrent_queue.h>
|
||||
namespace renderive::render_3d::detail {
|
||||
@@ -32,12 +36,10 @@ public:
|
||||
std::shared_ptr<Pending_Fence> pending_;
|
||||
friend class Gpu_Completion_Service;
|
||||
};
|
||||
Gpu_Completion_Service();
|
||||
~Gpu_Completion_Service();
|
||||
static Gpu_Completion_Service& instance();
|
||||
Gpu_Completion_Service(const Gpu_Completion_Service&) = delete;
|
||||
Gpu_Completion_Service& operator=(const Gpu_Completion_Service&) = delete;
|
||||
[[nodiscard]] Reservation prepare(Completion completion, bool observe);
|
||||
void shutdown() noexcept;
|
||||
private:
|
||||
struct Pending_Fence {
|
||||
enum class Status {
|
||||
@@ -46,20 +48,26 @@ private:
|
||||
canceled
|
||||
};
|
||||
std::mutex mutex;
|
||||
std::condition_variable ready;
|
||||
VkDevice device{VK_NULL_HANDLE};
|
||||
VkFence fence{VK_NULL_HANDLE};
|
||||
Completion completion;
|
||||
std::chrono::steady_clock::time_point watched_at{};
|
||||
Gpu_Completion_Service* service{};
|
||||
Status status{Status::reserved};
|
||||
bool observe{};
|
||||
};
|
||||
Gpu_Completion_Service();
|
||||
~Gpu_Completion_Service();
|
||||
static void cancel_reserved(const std::shared_ptr<Pending_Fence>& pending) noexcept;
|
||||
void wake() noexcept;
|
||||
void run() noexcept;
|
||||
static constexpr std::size_t default_capacity = 64;
|
||||
std::mutex mutex_;
|
||||
static constexpr std::ptrdiff_t default_capacity = 1024;
|
||||
static constexpr auto poll_interval = std::chrono::microseconds(200);
|
||||
std::counting_semaphore<default_capacity> slots_{default_capacity};
|
||||
oneapi::tbb::concurrent_bounded_queue<std::shared_ptr<Pending_Fence>> pending_;
|
||||
std::shared_ptr<Pending_Fence> active_;
|
||||
std::mutex wait_mutex_;
|
||||
std::condition_variable wake_;
|
||||
std::atomic_bool stopping_{};
|
||||
std::thread thread_;
|
||||
bool stopping_{};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
#include "Render_Domain.h"
|
||||
#include <unordered_map>
|
||||
namespace renderive::render_3d::detail {
|
||||
namespace {
|
||||
struct Render_Domain_Registry {
|
||||
std::mutex mutex;
|
||||
std::unordered_map<std::uint32_t, std::shared_ptr<Render_Domain>> domains;
|
||||
};
|
||||
Render_Domain_Registry& registry() {
|
||||
static Render_Domain_Registry value;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
Render_Domain::Prepared_Task::~Prepared_Task() {
|
||||
release();
|
||||
}
|
||||
Render_Domain::Prepared_Task::Prepared_Task(Prepared_Task&& other) noexcept
|
||||
: domain_(std::exchange(other.domain_, nullptr)), task_(std::move(other.task_)) {}
|
||||
Render_Domain::Prepared_Task& Render_Domain::Prepared_Task::operator=(Prepared_Task&& other) noexcept {
|
||||
if (this == &other)
|
||||
return *this;
|
||||
release();
|
||||
domain_ = std::exchange(other.domain_, nullptr);
|
||||
task_ = std::move(other.task_);
|
||||
return *this;
|
||||
}
|
||||
void Render_Domain::Prepared_Task::release() noexcept {
|
||||
if (!domain_)
|
||||
return;
|
||||
task_.reset();
|
||||
domain_->slots_.release();
|
||||
domain_ = nullptr;
|
||||
}
|
||||
std::shared_ptr<Render_Domain> Render_Domain::acquire(std::uint32_t gpu_index) {
|
||||
auto& storage = registry();
|
||||
std::lock_guard lock(storage.mutex);
|
||||
if (const auto found = storage.domains.find(gpu_index); found != storage.domains.end())
|
||||
return found->second;
|
||||
auto domain = std::shared_ptr<Render_Domain>(new Render_Domain());
|
||||
storage.domains.emplace(gpu_index, domain);
|
||||
return domain;
|
||||
}
|
||||
Render_Domain::Render_Domain() {
|
||||
tasks_.set_capacity(default_capacity);
|
||||
thread_ = std::thread([this] { run(); });
|
||||
}
|
||||
Render_Domain::~Render_Domain() {
|
||||
{
|
||||
std::lock_guard lock(mutex_);
|
||||
stopping_ = true;
|
||||
}
|
||||
slots_.acquire();
|
||||
if (!tasks_.try_push(std::unique_ptr<Task>{}))
|
||||
std::terminate();
|
||||
if (thread_.joinable())
|
||||
thread_.join();
|
||||
}
|
||||
void Render_Domain::post(std::function<void()> function) {
|
||||
post(prepare(std::move(function)));
|
||||
}
|
||||
void Render_Domain::post(Prepared_Task task) noexcept {
|
||||
if (!task.task_ || task.domain_ != this)
|
||||
std::terminate();
|
||||
std::lock_guard lock(mutex_);
|
||||
if (stopping_)
|
||||
std::terminate();
|
||||
if (!tasks_.try_push(std::move(task.task_)))
|
||||
std::terminate();
|
||||
task.domain_ = nullptr;
|
||||
}
|
||||
void Render_Domain::run() {
|
||||
for (;;) {
|
||||
std::unique_ptr<Task> task;
|
||||
tasks_.pop(task);
|
||||
slots_.release();
|
||||
if (!task)
|
||||
return;
|
||||
task->function();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <semaphore>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
@@ -18,51 +20,36 @@ public:
|
||||
class Prepared_Task final {
|
||||
public:
|
||||
Prepared_Task() = default;
|
||||
~Prepared_Task() = default;
|
||||
~Prepared_Task();
|
||||
Prepared_Task(const Prepared_Task&) = delete;
|
||||
Prepared_Task& operator=(const Prepared_Task&) = delete;
|
||||
Prepared_Task(Prepared_Task&&) noexcept = default;
|
||||
Prepared_Task& operator=(Prepared_Task&&) noexcept = default;
|
||||
Prepared_Task(Prepared_Task&& other) noexcept;
|
||||
Prepared_Task& operator=(Prepared_Task&& other) noexcept;
|
||||
private:
|
||||
explicit Prepared_Task(std::unique_ptr<Task> task) noexcept : task_(std::move(task)) {}
|
||||
Prepared_Task(Render_Domain* domain, std::unique_ptr<Task> task) noexcept
|
||||
: domain_(domain), task_(std::move(task)) {}
|
||||
void release() noexcept;
|
||||
Render_Domain* domain_{};
|
||||
std::unique_ptr<Task> task_;
|
||||
friend class Render_Domain;
|
||||
};
|
||||
Render_Domain() {
|
||||
tasks_.set_capacity(default_capacity);
|
||||
thread_ = std::thread([this] { run(); });
|
||||
}
|
||||
~Render_Domain() {
|
||||
{
|
||||
std::lock_guard lock(mutex_);
|
||||
stopping_ = true;
|
||||
tasks_.push(std::unique_ptr<Task>{});
|
||||
}
|
||||
if (thread_.joinable())
|
||||
thread_.join();
|
||||
}
|
||||
static std::shared_ptr<Render_Domain> acquire(std::uint32_t gpu_index);
|
||||
~Render_Domain();
|
||||
Render_Domain(const Render_Domain&) = delete;
|
||||
Render_Domain& operator=(const Render_Domain&) = delete;
|
||||
[[nodiscard]] Prepared_Task prepare(std::function<void()> function) {
|
||||
if (!function)
|
||||
throw std::invalid_argument("render domain task is empty");
|
||||
return Prepared_Task(std::make_unique<Task>(std::move(function)));
|
||||
}
|
||||
void post(std::function<void()> function) {
|
||||
auto task = prepare(std::move(function));
|
||||
std::lock_guard lock(mutex_);
|
||||
if (stopping_)
|
||||
throw std::runtime_error("Point_Scene render domain is stopping");
|
||||
tasks_.push(std::move(task.task_));
|
||||
}
|
||||
void post(Prepared_Task task) noexcept {
|
||||
if (!task.task_)
|
||||
std::terminate();
|
||||
std::lock_guard lock(mutex_);
|
||||
if (stopping_)
|
||||
std::terminate();
|
||||
tasks_.push(std::move(task.task_));
|
||||
slots_.acquire();
|
||||
try {
|
||||
return Prepared_Task(this, std::make_unique<Task>(std::move(function)));
|
||||
} catch (...) {
|
||||
slots_.release();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
void post(std::function<void()> function);
|
||||
void post(Prepared_Task task) noexcept;
|
||||
template <class Function>
|
||||
auto invoke(Function&& function) -> std::invoke_result_t<Function> {
|
||||
using Result = std::invoke_result_t<Function>;
|
||||
@@ -75,16 +62,10 @@ public:
|
||||
return result.get();
|
||||
}
|
||||
private:
|
||||
void run() {
|
||||
for (;;) {
|
||||
std::unique_ptr<Task> task;
|
||||
tasks_.pop(task);
|
||||
if (!task)
|
||||
return;
|
||||
task->function();
|
||||
}
|
||||
}
|
||||
static constexpr std::size_t default_capacity = 64;
|
||||
Render_Domain();
|
||||
void run();
|
||||
static constexpr std::ptrdiff_t default_capacity = 64;
|
||||
std::counting_semaphore<default_capacity> slots_{default_capacity};
|
||||
oneapi::tbb::concurrent_bounded_queue<std::unique_ptr<Task>> tasks_;
|
||||
std::mutex mutex_;
|
||||
bool stopping_{};
|
||||
|
||||
@@ -1,63 +1,48 @@
|
||||
#include "render_3D/detail/Gpu_Completion_Service.h"
|
||||
#include "render_3D/detail/Render_Domain.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
namespace renderive::render_3d::detail {
|
||||
namespace {
|
||||
|
||||
static_assert(noexcept(
|
||||
std::declval<Gpu_Completion_Service::Reservation&>().watch(
|
||||
VK_NULL_HANDLE, VK_NULL_HANDLE)));
|
||||
static_assert(noexcept(
|
||||
std::declval<Render_Domain&>().post(
|
||||
std::declval<Render_Domain::Prepared_Task>())));
|
||||
|
||||
TEST(GpuCompletionService, AbandonedReservationCancelsBeforeFenceWait) {
|
||||
Gpu_Completion_Service service;
|
||||
static_assert(noexcept(std::declval<Gpu_Completion_Service::Reservation&>().watch(VK_NULL_HANDLE, VK_NULL_HANDLE)));
|
||||
static_assert(noexcept(std::declval<Render_Domain&>().post(std::declval<Render_Domain::Prepared_Task>())));
|
||||
TEST(GpuCompletionService, UsesOneProcessWideService) {
|
||||
EXPECT_EQ(&Gpu_Completion_Service::instance(), &Gpu_Completion_Service::instance());
|
||||
}
|
||||
TEST(GpuCompletionService, AbandonedReservationDoesNotComplete) {
|
||||
std::atomic<int> completion_count{};
|
||||
{
|
||||
auto reservation = service.prepare(
|
||||
auto reservation = Gpu_Completion_Service::instance().prepare(
|
||||
[&](Gpu_Completion_Service::Result) {
|
||||
completion_count.fetch_add(1, std::memory_order_relaxed);
|
||||
},
|
||||
false);
|
||||
}, false);
|
||||
}
|
||||
service.shutdown();
|
||||
EXPECT_EQ(completion_count.load(std::memory_order_relaxed), 0);
|
||||
}
|
||||
|
||||
TEST(GpuCompletionService, PrepareRejectsAStoppedServiceBeforeSubmission) {
|
||||
Gpu_Completion_Service service;
|
||||
service.shutdown();
|
||||
EXPECT_THROW({
|
||||
auto reservation = service.prepare(
|
||||
[](Gpu_Completion_Service::Result) {}, false);
|
||||
static_cast<void>(reservation);
|
||||
}, std::runtime_error);
|
||||
TEST(RenderDomain, SharesDomainPerGpuIndex) {
|
||||
auto first = Render_Domain::acquire(0);
|
||||
auto second = Render_Domain::acquire(0);
|
||||
auto other = Render_Domain::acquire(1);
|
||||
EXPECT_EQ(first, second);
|
||||
EXPECT_NE(first, other);
|
||||
}
|
||||
|
||||
TEST(GpuCompletionService, ShutdownCancelsAnUnarmedReservation) {
|
||||
Gpu_Completion_Service service;
|
||||
auto reservation = service.prepare(
|
||||
[](Gpu_Completion_Service::Result) {}, false);
|
||||
service.shutdown();
|
||||
}
|
||||
|
||||
TEST(RenderDomain, PreparedTaskRunsAfterNoThrowHandoff) {
|
||||
Render_Domain domain;
|
||||
auto domain = Render_Domain::acquire(0);
|
||||
std::atomic<bool> executed{};
|
||||
auto task = domain.prepare([&] {
|
||||
auto task = domain->prepare([&] {
|
||||
executed.store(true, std::memory_order_release);
|
||||
});
|
||||
domain.post(std::move(task));
|
||||
domain.invoke([] {});
|
||||
domain->post(std::move(task));
|
||||
domain->invoke([] {});
|
||||
EXPECT_TRUE(executed.load(std::memory_order_acquire));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace renderive::render_3d::detail
|
||||
TEST(RenderDomain, AbandonedPreparedTasksReleaseReservedCapacity) {
|
||||
auto domain = Render_Domain::acquire(2);
|
||||
for (std::size_t index = 0; index < 128; ++index) {
|
||||
auto task = domain->prepare([] {});
|
||||
}
|
||||
domain->invoke([] {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user