改进
This commit is contained in:
@@ -49,7 +49,7 @@ install(TARGETS Renderive_Kernel
|
||||
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
|
||||
install(DIRECTORY "${Renderive_Kernel_source_dir}/renderive"
|
||||
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
|
||||
FILES_MATCHING PATTERN "*.h" PATTERN "*.hpp")
|
||||
FILES_MATCHING PATTERN "*.h" PATTERN "*.hpp" PATTERN "*.inl")
|
||||
if (RENDERIVE_BUILD_TESTS)
|
||||
set(Renderive_Kernel_test_dir "${CMAKE_CURRENT_LIST_DIR}/tests")
|
||||
append_glob_source(Renderive_Kernel_test_sources "${Renderive_Kernel_test_dir}")
|
||||
|
||||
@@ -224,7 +224,10 @@ Frame_Analysis analyze_frame(const Render_Plan& plan, const Frame_Snapshot& fram
|
||||
}
|
||||
auto& analysis = result.nodes[index];
|
||||
const auto& execution = frame.node_executions[index];
|
||||
analysis.dependency_ready_time_ns = execution.ready_time_ns;
|
||||
analysis.dependency_ready_offset_ns =
|
||||
execution.ready_time_ns >= frame.render_start_ns
|
||||
? execution.ready_time_ns - frame.render_start_ns
|
||||
: 0;
|
||||
analysis.scheduler_wait_ns = execution.start_time_ns >= execution.ready_time_ns
|
||||
? execution.start_time_ns - execution.ready_time_ns
|
||||
: 0;
|
||||
|
||||
@@ -23,7 +23,7 @@ struct Node_Frame_Analysis {
|
||||
std::uint64_t gpu_execution_duration_ns{};
|
||||
std::uint64_t start_offset_ns{};
|
||||
std::uint64_t end_offset_ns{};
|
||||
std::uint64_t dependency_ready_time_ns{};
|
||||
std::uint64_t dependency_ready_offset_ns{};
|
||||
std::uint64_t scheduler_wait_ns{};
|
||||
double work_contribution{};
|
||||
double critical_path_contribution{};
|
||||
|
||||
@@ -64,8 +64,17 @@ struct Render_Graph_Runtime::State
|
||||
}
|
||||
void execute(std::span<Node_Execution* const> execution_slots,
|
||||
Execute_Node execute) {
|
||||
if (running.exchange(true, std::memory_order_acq_rel))
|
||||
throw std::logic_error("render graph runtime is already executing");
|
||||
{
|
||||
std::lock_guard lock(lifecycle_mutex);
|
||||
if (running.load(std::memory_order_acquire))
|
||||
throw std::logic_error("render graph runtime is already executing");
|
||||
cancellation_requested.store(false, std::memory_order_release);
|
||||
{
|
||||
std::lock_guard cancellation_lock(cancellation_mutex);
|
||||
cancellation_exception = nullptr;
|
||||
}
|
||||
running.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
std::exception_ptr execution_error;
|
||||
try {
|
||||
@@ -119,23 +128,31 @@ struct Render_Graph_Runtime::State
|
||||
std::fill(external_operations.begin(),
|
||||
external_operations.end(), External_Operation{});
|
||||
}
|
||||
cancellation_requested.store(false, std::memory_order_release);
|
||||
{
|
||||
std::lock_guard lock(cancellation_mutex);
|
||||
cancellation_exception = nullptr;
|
||||
std::lock_guard lock(lifecycle_mutex);
|
||||
cancellation_requested.store(false, std::memory_order_release);
|
||||
{
|
||||
std::lock_guard cancellation_lock(cancellation_mutex);
|
||||
cancellation_exception = nullptr;
|
||||
}
|
||||
running.store(false, std::memory_order_release);
|
||||
}
|
||||
running.store(false, std::memory_order_release);
|
||||
}
|
||||
void cancel_pending(std::exception_ptr reason) noexcept {
|
||||
auto error = make_cancellation_error(std::move(reason));
|
||||
{
|
||||
std::lock_guard lock(cancellation_mutex);
|
||||
if (!cancellation_exception)
|
||||
cancellation_exception = error;
|
||||
else
|
||||
error = cancellation_exception;
|
||||
std::lock_guard lifecycle_lock(lifecycle_mutex);
|
||||
if (!running.load(std::memory_order_acquire))
|
||||
return;
|
||||
{
|
||||
std::lock_guard cancellation_lock(cancellation_mutex);
|
||||
if (!cancellation_exception)
|
||||
cancellation_exception = error;
|
||||
else
|
||||
error = cancellation_exception;
|
||||
}
|
||||
cancellation_requested.store(true, std::memory_order_release);
|
||||
}
|
||||
cancellation_requested.store(true, std::memory_order_release);
|
||||
cancel_external_operations(error);
|
||||
}
|
||||
std::exception_ptr cancellation_error() noexcept {
|
||||
@@ -291,6 +308,7 @@ struct Render_Graph_Runtime::State
|
||||
std::vector<External_Operation> external_operations;
|
||||
std::mutex cancellation_mutex;
|
||||
std::exception_ptr cancellation_exception;
|
||||
std::mutex lifecycle_mutex;
|
||||
std::atomic_bool running{};
|
||||
std::atomic_bool failed{};
|
||||
std::atomic_bool cancellation_requested{};
|
||||
|
||||
@@ -196,9 +196,11 @@ void Scene_Base::Renderable_Editor::attach(Renderable renderable) {
|
||||
}
|
||||
if (!scene_.dependency_resolver_.contains(id))
|
||||
scene_.dependency_resolver_.attach(id);
|
||||
if (auto* composition = scene_.composition_relationships())
|
||||
composition->attach(id);
|
||||
auto cache = scene_.make_renderable_color_cache();
|
||||
if (scene_.raster_capabilities_)
|
||||
scene_.raster_capabilities_->attach(id);
|
||||
auto cache = scene_.raster_capabilities_
|
||||
? scene_.raster_capabilities_->make_renderable_color_cache()
|
||||
: std::shared_ptr<Color_Cache>{};
|
||||
if (!scene_.renderables_.try_emplace(id, renderable).second)
|
||||
throw std::logic_error("renderable id is already attached");
|
||||
try {
|
||||
@@ -275,7 +277,9 @@ Scene_2D_Base::Scene_2D_Base(std::pmr::memory_resource& memory_resource)
|
||||
Scene_2D_Base::Scene_2D_Base(std::pmr::memory_resource& memory_resource,
|
||||
std::unique_ptr<Scene_Base::Impl> impl)
|
||||
: Scene_Base(memory_resource, std::move(impl)),
|
||||
display_resolver_(this->memory_resource()) {}
|
||||
display_resolver_(this->memory_resource()) {
|
||||
bind_raster_capabilities(*this);
|
||||
}
|
||||
|
||||
Scene_2D_Base::Attach_Builder Scene_2D_Base::attach_builder() {
|
||||
return Attach_Builder(*this);
|
||||
@@ -512,8 +516,8 @@ void Scene_Base::cleanup_detached_topology_locked() {
|
||||
std::to_string(id));
|
||||
dependency_resolver_.erase(id);
|
||||
}
|
||||
if (auto* composition = composition_relationships())
|
||||
composition->cleanup();
|
||||
if (raster_capabilities_)
|
||||
raster_capabilities_->cleanup();
|
||||
}
|
||||
void Scene_Base::validate_structure_locked() {
|
||||
for (const auto& [id, renderable] : renderables_) {
|
||||
@@ -536,8 +540,8 @@ void Scene_Base::validate_structure_locked() {
|
||||
"dependency graph references detached renderable " +
|
||||
std::to_string(id));
|
||||
}
|
||||
if (const auto* composition = composition_relationships())
|
||||
composition->validate();
|
||||
if (raster_capabilities_)
|
||||
raster_capabilities_->validate();
|
||||
}
|
||||
[[noreturn]] void Scene_Base::structure_fail_fast(const char* stage, std::exception_ptr exception) noexcept {
|
||||
std::fprintf(stderr, "Renderive scene structure failure during %s", stage ? stage : "unknown stage");
|
||||
@@ -641,9 +645,8 @@ Scene_Base::Topology_Snapshot Scene_Base::topology_snapshot() const {
|
||||
std::lock_guard lock(model_mutex_);
|
||||
Topology_Snapshot snapshot;
|
||||
const auto dependency = dependency_resolver_.resolve();
|
||||
const auto* composition = composition_relationships();
|
||||
const auto display = composition
|
||||
? composition->resolve()
|
||||
const auto display = raster_capabilities_
|
||||
? raster_capabilities_->resolve()
|
||||
: renderive::scene::dependency::Resolution<
|
||||
Renderable_Id>{};
|
||||
const auto& ids = dependency.order;
|
||||
@@ -675,9 +678,8 @@ Scene_Base::Topology_Snapshot Scene_Base::topology_snapshot() const {
|
||||
std::vector<Scene_Base::Const_Renderable> Scene_Base::paint_order_snapshot() const {
|
||||
std::lock_guard lock(model_mutex_);
|
||||
std::vector<Const_Renderable> result;
|
||||
const auto* composition = composition_relationships();
|
||||
auto display = composition
|
||||
? composition->resolve()
|
||||
auto display = raster_capabilities_
|
||||
? raster_capabilities_->resolve()
|
||||
: renderive::scene::dependency::Resolution<
|
||||
Renderable_Id>{};
|
||||
if (display.order.empty())
|
||||
@@ -691,9 +693,8 @@ std::shared_ptr<Frame_Render_Snapshot> Scene_Base::snapshot_live_model() {
|
||||
auto snapshot = std::make_shared<Frame_Render_Snapshot>();
|
||||
std::lock_guard lock(model_mutex_);
|
||||
const auto dependency = dependency_resolver_.resolve();
|
||||
const auto* composition = composition_relationships();
|
||||
const auto display = composition
|
||||
? composition->resolve()
|
||||
const auto display = raster_capabilities_
|
||||
? raster_capabilities_->resolve()
|
||||
: renderive::scene::dependency::Resolution<
|
||||
Renderable_Id>{};
|
||||
const auto& dependency_order = dependency.order;
|
||||
@@ -774,7 +775,8 @@ std::shared_ptr<Frame_Render_Snapshot> Scene_Base::capture_live_frame() {
|
||||
auto snapshot = snapshot_live_model();
|
||||
snapshot->scene_state_revision_ = acquire_scene_state();
|
||||
capture_scene_state(*snapshot);
|
||||
snapshot->viewport = frame_viewport();
|
||||
if (raster_capabilities_)
|
||||
snapshot->viewport = raster_capabilities_->frame_viewport();
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
@@ -855,44 +857,11 @@ const Frame_Control_Strategy_Base& Scene_Base::frame_control_strategy() const {
|
||||
return frame_control_strategy_impl();
|
||||
}
|
||||
|
||||
Frame_Control_Strategy_Base& Scene_Base::frame_control_strategy_impl() {
|
||||
throw std::logic_error("frame control strategy is not available");
|
||||
}
|
||||
|
||||
const Frame_Control_Strategy_Base& Scene_Base::frame_control_strategy_impl() const {
|
||||
throw std::logic_error("frame control strategy is not available");
|
||||
}
|
||||
|
||||
std::shared_ptr<Color_Cache> Scene_Base::make_renderable_color_cache() {
|
||||
return {};
|
||||
}
|
||||
|
||||
Frame_Viewport Scene_Base::frame_viewport() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::uint64_t Scene_Base::acquire_scene_state() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Scene_Base::capture_scene_state(Frame_Render_Snapshot&) const {}
|
||||
|
||||
renderive::scene::detail::Composition_Relationships*
|
||||
Scene_Base::composition_relationships() noexcept {
|
||||
auto* scene_2d = dynamic_cast<Scene_2D_Base*>(this);
|
||||
return scene_2d
|
||||
? static_cast<
|
||||
renderive::scene::detail::Composition_Relationships*>(
|
||||
scene_2d)
|
||||
: nullptr;
|
||||
}
|
||||
const renderive::scene::detail::Composition_Relationships*
|
||||
Scene_Base::composition_relationships() const noexcept {
|
||||
const auto* scene_2d = dynamic_cast<const Scene_2D_Base*>(this);
|
||||
return scene_2d
|
||||
? static_cast<const renderive::scene::detail::
|
||||
Composition_Relationships*>(scene_2d)
|
||||
: nullptr;
|
||||
void Scene_Base::bind_raster_capabilities(
|
||||
renderive::scene::detail::Raster_Capabilities& capabilities) noexcept {
|
||||
if (raster_capabilities_ != nullptr)
|
||||
std::terminate();
|
||||
raster_capabilities_ = &capabilities;
|
||||
}
|
||||
|
||||
void Scene_2D_Base::attach(Renderable_Id id) {
|
||||
|
||||
@@ -44,6 +44,11 @@ struct Composition_Relationships {
|
||||
virtual dependency::Resolution<Renderable_Id> resolve() const = 0;
|
||||
};
|
||||
|
||||
struct Raster_Capabilities : Composition_Relationships {
|
||||
virtual std::shared_ptr<Color_Cache> make_renderable_color_cache() = 0;
|
||||
virtual Frame_Viewport frame_viewport() const = 0;
|
||||
};
|
||||
|
||||
} // namespace renderive::scene::detail
|
||||
|
||||
class Scene_Compositor {
|
||||
@@ -204,12 +209,10 @@ protected:
|
||||
Impl& d_func() noexcept { return *impl_; }
|
||||
const Impl& d_func() const noexcept { return *impl_; }
|
||||
|
||||
virtual Frame_Control_Strategy_Base& frame_control_strategy_impl();
|
||||
virtual const Frame_Control_Strategy_Base& frame_control_strategy_impl() const;
|
||||
virtual std::shared_ptr<Color_Cache> make_renderable_color_cache();
|
||||
virtual Frame_Viewport frame_viewport() const;
|
||||
virtual std::uint64_t acquire_scene_state();
|
||||
virtual void capture_scene_state(Frame_Render_Snapshot& snapshot) const;
|
||||
virtual Frame_Control_Strategy_Base& frame_control_strategy_impl() = 0;
|
||||
virtual const Frame_Control_Strategy_Base& frame_control_strategy_impl() const = 0;
|
||||
virtual std::uint64_t acquire_scene_state() = 0;
|
||||
virtual void capture_scene_state(Frame_Render_Snapshot& snapshot) const = 0;
|
||||
std::unique_lock<std::recursive_mutex> lock_render_idle();
|
||||
[[nodiscard]] bool is_render_execution_context() const noexcept;
|
||||
bool consume_model_dirty() noexcept;
|
||||
@@ -271,10 +274,8 @@ private:
|
||||
const Renderable& renderable) const;
|
||||
void validate_structure_locked();
|
||||
void cleanup_detached_topology_locked();
|
||||
renderive::scene::detail::Composition_Relationships*
|
||||
composition_relationships() noexcept;
|
||||
const renderive::scene::detail::Composition_Relationships*
|
||||
composition_relationships() const noexcept;
|
||||
void bind_raster_capabilities(
|
||||
renderive::scene::detail::Raster_Capabilities& capabilities) noexcept;
|
||||
[[noreturn]] static void structure_fail_fast(const char* stage, std::exception_ptr exception = {}) noexcept;
|
||||
|
||||
inline static thread_local Scene_Base* active_execution_scene_{};
|
||||
@@ -289,6 +290,7 @@ private:
|
||||
std::pmr::unordered_map<Renderable_Id, Renderable> renderables_;
|
||||
renderive::scene::dependency::Dependency_Resolver<Renderable_Id>
|
||||
dependency_resolver_;
|
||||
renderive::scene::detail::Raster_Capabilities* raster_capabilities_{};
|
||||
std::pmr::unordered_map<Renderable_Id, std::shared_ptr<Color_Cache>> color_caches_;
|
||||
mutable std::mutex model_mutex_;
|
||||
|
||||
@@ -313,7 +315,7 @@ private:
|
||||
|
||||
class Scene_2D_Base
|
||||
: public Scene_Base,
|
||||
private renderive::scene::detail::Composition_Relationships {
|
||||
private renderive::scene::detail::Raster_Capabilities {
|
||||
public:
|
||||
class Renderable_Editor
|
||||
: public renderive::scene_inheritance::Editor_Node<
|
||||
@@ -366,6 +368,8 @@ protected:
|
||||
};
|
||||
Scene_2D_Base(std::pmr::memory_resource& memory_resource,
|
||||
std::unique_ptr<Scene_Base::Impl> impl);
|
||||
virtual std::shared_ptr<Color_Cache> make_renderable_color_cache() override = 0;
|
||||
virtual Frame_Viewport frame_viewport() const override = 0;
|
||||
|
||||
private:
|
||||
friend class Scene_Base;
|
||||
|
||||
@@ -89,7 +89,7 @@ TEST(render_dag_test, analysis_derives_wait_critical_path_and_parallel_overlap_f
|
||||
const auto analysis = analyze_frame(*plan, snapshot);
|
||||
|
||||
ASSERT_EQ(analysis.nodes.size(), 3u);
|
||||
EXPECT_EQ(analysis.nodes[2].dependency_ready_time_ns, 150u);
|
||||
EXPECT_EQ(analysis.nodes[2].dependency_ready_offset_ns, 50u);
|
||||
EXPECT_EQ(analysis.nodes[2].scheduler_wait_ns, 10u);
|
||||
EXPECT_EQ(analysis.total_render_duration_ns, 100u);
|
||||
EXPECT_EQ(analysis.total_work_duration_ns, 100u);
|
||||
|
||||
@@ -280,6 +280,25 @@ TEST(render_graph_runtime_test,
|
||||
EXPECT_EQ(source.operation().status(), External_Operation_Status::cancelled);
|
||||
}
|
||||
|
||||
TEST(render_graph_runtime_test, idle_cancellation_does_not_arm_next_execution) {
|
||||
const auto plan = two_node_plan();
|
||||
std::vector<Node_Execution> execution_storage;
|
||||
auto slots = execution_slots(*plan, execution_storage);
|
||||
renderive::render_graph::detail::Render_Graph_Runtime runtime(*plan);
|
||||
|
||||
runtime.cancel_pending();
|
||||
std::atomic<int> executed{};
|
||||
EXPECT_NO_THROW(runtime.execute(
|
||||
slots, [&](std::size_t, Node_Execution_Metrics*) {
|
||||
executed.fetch_add(1, std::memory_order_relaxed);
|
||||
return Node_Execution_Result::completed();
|
||||
}));
|
||||
|
||||
EXPECT_EQ(executed.load(std::memory_order_relaxed), 2);
|
||||
EXPECT_EQ(execution_storage[0].status, Node_Execution_Status::complete);
|
||||
EXPECT_EQ(execution_storage[1].status, Node_Execution_Status::complete);
|
||||
}
|
||||
|
||||
TEST(external_operation_test, completion_commit_is_exclusive_with_cancellation) {
|
||||
External_Operation_Source source;
|
||||
const auto operation = source.operation();
|
||||
|
||||
@@ -26,7 +26,11 @@ if (@RENDERIVE_INSTALL_3D@)
|
||||
find_dependency(tinyobjloader CONFIG)
|
||||
find_dependency(ZLIB)
|
||||
if (WIN32)
|
||||
set(_Renderive_saved_module_path "${CMAKE_MODULE_PATH}")
|
||||
list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/modules")
|
||||
find_dependency(PThreads4W)
|
||||
set(CMAKE_MODULE_PATH "${_Renderive_saved_module_path}")
|
||||
unset(_Renderive_saved_module_path)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
|
||||
@@ -34,6 +34,12 @@ install(FILES
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/RenderiveConfigVersion.cmake"
|
||||
DESTINATION "${Renderive_install_cmake_dir}")
|
||||
|
||||
if (RENDERIVE_INSTALL_3D AND WIN32)
|
||||
install(FILES
|
||||
"${PROJECT_SOURCE_DIR}/render_3D/cmake/module/FindPThreads4W.cmake"
|
||||
DESTINATION "${Renderive_install_cmake_dir}/modules")
|
||||
endif ()
|
||||
|
||||
if (RENDERIVE_INSTALL_QT)
|
||||
install(FILES "${PROJECT_SOURCE_DIR}/export.h"
|
||||
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
#pragma once
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <QBrush>
|
||||
#include <QByteArray>
|
||||
#include <QColor>
|
||||
@@ -147,37 +143,4 @@ inline Qt::Orientation to_qt_orientation(Orientation orientation) {
|
||||
inline Time_Of_Day qt_to_time_of_day(const QTime& time) {
|
||||
return {time.isValid() ? time.msecsSinceStartOfDay() : -1};
|
||||
}
|
||||
namespace detail {
|
||||
inline std::default_random_engine& mock_data_random_engine() {
|
||||
static thread_local std::default_random_engine engine(std::random_device{}());
|
||||
return engine;
|
||||
}
|
||||
} // namespace detail
|
||||
inline void fill_data(Range value_range, std::span<double> output) {
|
||||
if (value_range.origin > value_range.target)
|
||||
std::swap(value_range.origin, value_range.target);
|
||||
std::uniform_real_distribution<double> dist(value_range.origin, value_range.target);
|
||||
for (double& value : output)
|
||||
value = dist(detail::mock_data_random_engine());
|
||||
}
|
||||
inline std::vector<double> get_data(Range value_range, int size) {
|
||||
std::vector<double> ret(static_cast<std::size_t>(std::max(0, size)));
|
||||
fill_data(value_range, ret);
|
||||
return ret;
|
||||
}
|
||||
inline double get_data(Range value_range) {
|
||||
if (value_range.origin > value_range.target)
|
||||
std::swap(value_range.origin, value_range.target);
|
||||
std::uniform_real_distribution<double> dist(value_range.origin, value_range.target);
|
||||
return dist(detail::mock_data_random_engine());
|
||||
}
|
||||
inline std::vector<double> get_data(int value, int size) {
|
||||
return std::vector<double>(size, value);
|
||||
}
|
||||
inline std::vector<double> get_colored_data(int size) {
|
||||
std::vector<double> ret(size);
|
||||
for (int i = 0; i < size; ++i)
|
||||
ret[i] = i;
|
||||
return ret;
|
||||
}
|
||||
} // namespace renderive
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
include_guard(GLOBAL)
|
||||
rcl_get_effective_install_dir(render_3D::PThreads4W PTHREADS4W_ROOT)
|
||||
|
||||
# Self-contained PThreads4W discovery used both by the source tree and by the
|
||||
# installed Renderive package. The build infrastructure may define
|
||||
# PTHREADS4W_ROOT, but consumers only need to provide PThreads4W_ROOT,
|
||||
# PTHREADS4W_ROOT, the corresponding environment variable, or CMAKE_PREFIX_PATH.
|
||||
set(_PThreads4W_roots)
|
||||
foreach(_root IN ITEMS "${PThreads4W_ROOT}" "${PTHREADS4W_ROOT}" "$ENV{PThreads4W_ROOT}" "$ENV{PTHREADS4W_ROOT}")
|
||||
if (_root)
|
||||
list(APPEND _PThreads4W_roots "${_root}")
|
||||
endif ()
|
||||
endforeach()
|
||||
|
||||
if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64)$")
|
||||
set(PThreads4W_ARCH "x86_64")
|
||||
elseif (CMAKE_SYSTEM_PROCESSOR MATCHES "^(ARM64|arm64|aarch64)$")
|
||||
@@ -9,36 +20,77 @@ elseif (CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86|i[3-6]86)$")
|
||||
else ()
|
||||
set(PThreads4W_ARCH "${CMAKE_SYSTEM_PROCESSOR}")
|
||||
endif ()
|
||||
set(PThreads4W_INSTALL_DIR "${PTHREADS4W_ROOT}/${PThreads4W_ARCH}/${CMAKE_BUILD_TYPE}")
|
||||
|
||||
find_path(PThreads4W_INCLUDE_DIR
|
||||
NAMES pthread.h
|
||||
PATHS "${PThreads4W_INSTALL_DIR}/include"
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(PThreads4W_LIBRARY_NAME pthreadVC3d)
|
||||
HINTS ${_PThreads4W_roots}
|
||||
PATH_SUFFIXES
|
||||
"${PThreads4W_ARCH}/Debug/include"
|
||||
"${PThreads4W_ARCH}/Release/include"
|
||||
"${PThreads4W_ARCH}/RelWithDebInfo/include"
|
||||
"${PThreads4W_ARCH}/MinSizeRel/include"
|
||||
include)
|
||||
|
||||
find_library(PThreads4W_LIBRARY_DEBUG
|
||||
NAMES pthreadVC3d
|
||||
HINTS ${_PThreads4W_roots}
|
||||
PATH_SUFFIXES "${PThreads4W_ARCH}/Debug/lib" Debug/lib lib)
|
||||
find_library(PThreads4W_LIBRARY_RELEASE
|
||||
NAMES pthreadVC3
|
||||
HINTS ${_PThreads4W_roots}
|
||||
PATH_SUFFIXES
|
||||
"${PThreads4W_ARCH}/Release/lib"
|
||||
"${PThreads4W_ARCH}/RelWithDebInfo/lib"
|
||||
"${PThreads4W_ARCH}/MinSizeRel/lib"
|
||||
Release/lib RelWithDebInfo/lib MinSizeRel/lib lib)
|
||||
|
||||
# Keep the conventional singular variable available for callers that inspect it.
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug" AND PThreads4W_LIBRARY_DEBUG)
|
||||
set(PThreads4W_LIBRARY "${PThreads4W_LIBRARY_DEBUG}")
|
||||
elseif (PThreads4W_LIBRARY_RELEASE)
|
||||
set(PThreads4W_LIBRARY "${PThreads4W_LIBRARY_RELEASE}")
|
||||
else ()
|
||||
set(PThreads4W_LIBRARY_NAME pthreadVC3)
|
||||
set(PThreads4W_LIBRARY "${PThreads4W_LIBRARY_DEBUG}")
|
||||
endif ()
|
||||
find_library(PThreads4W_LIBRARY
|
||||
NAMES "${PThreads4W_LIBRARY_NAME}"
|
||||
PATHS "${PThreads4W_INSTALL_DIR}/lib"
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(PThreads4W
|
||||
REQUIRED_VARS
|
||||
PThreads4W_INCLUDE_DIR
|
||||
PThreads4W_LIBRARY
|
||||
)
|
||||
REQUIRED_VARS PThreads4W_INCLUDE_DIR PThreads4W_LIBRARY)
|
||||
|
||||
if (PThreads4W_FOUND AND NOT TARGET PThreads4W::PThreads4W)
|
||||
add_library(PThreads4W::PThreads4W UNKNOWN IMPORTED)
|
||||
set_target_properties(PThreads4W::PThreads4W PROPERTIES
|
||||
IMPORTED_LOCATION "${PThreads4W_LIBRARY}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${PThreads4W_INCLUDE_DIR}"
|
||||
)
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${PThreads4W_INCLUDE_DIR}")
|
||||
if (PThreads4W_LIBRARY_DEBUG)
|
||||
set_property(TARGET PThreads4W::PThreads4W APPEND PROPERTY
|
||||
IMPORTED_CONFIGURATIONS DEBUG)
|
||||
set_target_properties(PThreads4W::PThreads4W PROPERTIES
|
||||
IMPORTED_LOCATION_DEBUG "${PThreads4W_LIBRARY_DEBUG}")
|
||||
endif ()
|
||||
if (PThreads4W_LIBRARY_RELEASE)
|
||||
set_property(TARGET PThreads4W::PThreads4W APPEND PROPERTY
|
||||
IMPORTED_CONFIGURATIONS RELEASE RELWITHDEBINFO MINSIZEREL)
|
||||
set_target_properties(PThreads4W::PThreads4W PROPERTIES
|
||||
IMPORTED_LOCATION_RELEASE "${PThreads4W_LIBRARY_RELEASE}"
|
||||
IMPORTED_LOCATION_RELWITHDEBINFO "${PThreads4W_LIBRARY_RELEASE}"
|
||||
IMPORTED_LOCATION_MINSIZEREL "${PThreads4W_LIBRARY_RELEASE}")
|
||||
endif ()
|
||||
# A single available configuration remains usable for the other build types.
|
||||
if (NOT PThreads4W_LIBRARY_RELEASE AND PThreads4W_LIBRARY_DEBUG)
|
||||
set_target_properties(PThreads4W::PThreads4W PROPERTIES
|
||||
MAP_IMPORTED_CONFIG_RELEASE DEBUG
|
||||
MAP_IMPORTED_CONFIG_RELWITHDEBINFO DEBUG
|
||||
MAP_IMPORTED_CONFIG_MINSIZEREL DEBUG)
|
||||
elseif (NOT PThreads4W_LIBRARY_DEBUG AND PThreads4W_LIBRARY_RELEASE)
|
||||
set_target_properties(PThreads4W::PThreads4W PROPERTIES
|
||||
MAP_IMPORTED_CONFIG_DEBUG RELEASE)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
mark_as_advanced(
|
||||
PThreads4W_INCLUDE_DIR
|
||||
PThreads4W_LIBRARY
|
||||
)
|
||||
PThreads4W_LIBRARY_DEBUG
|
||||
PThreads4W_LIBRARY_RELEASE)
|
||||
unset(_PThreads4W_roots)
|
||||
unset(_root)
|
||||
|
||||
@@ -124,8 +124,36 @@ struct Point_Backend_State final {
|
||||
render_domain->invoke([this] { backend.reset(); });
|
||||
}
|
||||
|
||||
detail::Datoviz_Visual_Backend& require_backend() {
|
||||
if (backend)
|
||||
return *backend;
|
||||
if (backend_failure)
|
||||
std::rethrow_exception(backend_failure);
|
||||
throw std::runtime_error("Datoviz backend is unavailable");
|
||||
}
|
||||
|
||||
void quarantine_backend(std::exception_ptr failure) noexcept {
|
||||
if (!failure) {
|
||||
try {
|
||||
throw std::runtime_error("Datoviz backend was quarantined");
|
||||
} catch (...) {
|
||||
failure = std::current_exception();
|
||||
}
|
||||
}
|
||||
backend_failure = std::move(failure);
|
||||
// An abandoned fence means the driver never proved that the submitted
|
||||
// work stopped touching the backend resources. Destroying or reusing
|
||||
// them would be unsafe. Deliberately relinquish ownership of the whole
|
||||
// backend and let the OS/driver reclaim it at process teardown. This is
|
||||
// a fault-containment path only; all subsequent scene operations fail.
|
||||
static_cast<void>(backend.release());
|
||||
std::lock_guard lock(frame_mutex);
|
||||
latest.reset();
|
||||
}
|
||||
|
||||
std::shared_ptr<detail::Render_Domain> render_domain;
|
||||
std::unique_ptr<detail::Datoviz_Visual_Backend> backend;
|
||||
std::exception_ptr backend_failure;
|
||||
mutable std::mutex frame_mutex;
|
||||
std::shared_ptr<const Pixel_Frame> latest;
|
||||
};
|
||||
@@ -205,7 +233,7 @@ struct Basic_Point_Scene final
|
||||
&detail::Scene_State::viewport>();
|
||||
const auto state = backend_state;
|
||||
state->render_domain->invoke([state, type, x, y, button, modifiers, viewport] {
|
||||
state->backend->dispatch_pointer(type, x, y, button, modifiers, viewport);
|
||||
state->require_backend().dispatch_pointer(type, x, y, button, modifiers, viewport);
|
||||
});
|
||||
this->notify_model_dirty();
|
||||
}
|
||||
@@ -218,7 +246,7 @@ struct Basic_Point_Scene final
|
||||
const auto state = backend_state;
|
||||
state->render_domain->invoke(
|
||||
[state, x, y, delta_x, delta_y, modifiers, viewport] {
|
||||
state->backend->dispatch_wheel(x, y, delta_x, delta_y, modifiers,
|
||||
state->require_backend().dispatch_wheel(x, y, delta_x, delta_y, modifiers,
|
||||
viewport);
|
||||
});
|
||||
this->notify_model_dirty();
|
||||
@@ -227,7 +255,7 @@ struct Basic_Point_Scene final
|
||||
void dispatch_key(const ::renderive::Key_Event& event) override {
|
||||
const auto state = backend_state;
|
||||
state->render_domain->invoke([state, event] {
|
||||
state->backend->dispatch_key(event);
|
||||
state->require_backend().dispatch_key(event);
|
||||
});
|
||||
this->notify_model_dirty();
|
||||
}
|
||||
@@ -328,7 +356,9 @@ 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
|
||||
render.unhandled_exception_count,
|
||||
completion.fault_count,
|
||||
completion.abandoned_count
|
||||
};
|
||||
}
|
||||
|
||||
@@ -377,9 +407,15 @@ struct Basic_Point_Scene final
|
||||
async_frame->pending.reset();
|
||||
pending.trace.gpu_fence_wait_ns =
|
||||
async_frame->completion.wait_duration_ns;
|
||||
if (async_frame->completion.abandoned) {
|
||||
auto failure = std::move(async_frame->completion.error);
|
||||
state->quarantine_backend(failure);
|
||||
static_cast<void>(source->fail(std::move(failure)));
|
||||
return;
|
||||
}
|
||||
if (async_frame->completion.error) {
|
||||
try {
|
||||
state->backend->discard(std::move(pending));
|
||||
state->require_backend().discard(std::move(pending));
|
||||
static_cast<void>(source->fail(
|
||||
std::move(async_frame->completion.error)));
|
||||
} catch (...) {
|
||||
@@ -389,7 +425,7 @@ struct Basic_Point_Scene final
|
||||
return;
|
||||
}
|
||||
try {
|
||||
auto completed = state->backend->collect(
|
||||
auto completed = state->require_backend().collect(
|
||||
std::move(pending));
|
||||
auto frame = std::move(completed.frame);
|
||||
auto trace = std::make_shared<detail::Datoviz_Frame_Trace>(
|
||||
@@ -447,7 +483,7 @@ struct Basic_Point_Scene final
|
||||
? static_cast<std::uint64_t>(queue_wait)
|
||||
: 0;
|
||||
}
|
||||
auto pending = state->backend->submit(
|
||||
auto pending = state->require_backend().submit(
|
||||
scene_state, scene_revision, *prepared,
|
||||
frame_sequence, observe);
|
||||
if (!pending) {
|
||||
|
||||
@@ -82,6 +82,8 @@ 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{};
|
||||
};
|
||||
|
||||
struct Frame_Status {
|
||||
|
||||
@@ -19,12 +19,14 @@ Gpu_Completion_Service::~Gpu_Completion_Service() {
|
||||
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() {
|
||||
cancel();
|
||||
}
|
||||
Gpu_Completion_Service::Reservation::Reservation(Reservation&& other) noexcept : pending_(std::exchange(other.pending_, {})) {}
|
||||
void Gpu_Completion_Service::Reservation::watch(VkDevice device, VkFence fence) noexcept {
|
||||
Gpu_Completion_Service::Reservation::Reservation(
|
||||
std::shared_ptr<Pending_Fence> pending) noexcept
|
||||
: pending_(std::move(pending)) {}
|
||||
Gpu_Completion_Service::Reservation::~Reservation() { cancel(); }
|
||||
Gpu_Completion_Service::Reservation::Reservation(Reservation&& other) noexcept
|
||||
: pending_(std::exchange(other.pending_, {})) {}
|
||||
void Gpu_Completion_Service::Reservation::watch(VkDevice device,
|
||||
VkFence fence) noexcept {
|
||||
if (!pending_ || device == VK_NULL_HANDLE || fence == VK_NULL_HANDLE)
|
||||
std::terminate();
|
||||
auto pending = std::exchange(pending_, {});
|
||||
@@ -35,9 +37,11 @@ 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();
|
||||
const std::size_t watched = service->watched_.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
// This timestamp is part of correctness, not only observability: it
|
||||
// bounds the lifetime of a submission whose fence never signals.
|
||||
pending->watched_at = std::chrono::steady_clock::now();
|
||||
const std::size_t watched =
|
||||
service->watched_.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
update_peak(service->peak_watched_, watched);
|
||||
pending->status = Pending_Fence::Status::watched;
|
||||
}
|
||||
@@ -50,11 +54,15 @@ void Gpu_Completion_Service::Reservation::cancel() noexcept {
|
||||
cancel_reserved(pending);
|
||||
pending->service->wake();
|
||||
}
|
||||
void Gpu_Completion_Service::update_peak(std::atomic_size_t& peak, std::size_t value) noexcept {
|
||||
void Gpu_Completion_Service::update_peak(std::atomic_size_t& peak,
|
||||
std::size_t value) noexcept {
|
||||
std::size_t current = peak.load(std::memory_order_relaxed);
|
||||
while (current < value && !peak.compare_exchange_weak(current, value, std::memory_order_relaxed)) {}
|
||||
while (current < value &&
|
||||
!peak.compare_exchange_weak(current, value,
|
||||
std::memory_order_relaxed)) {}
|
||||
}
|
||||
void Gpu_Completion_Service::cancel_reserved(const std::shared_ptr<Pending_Fence>& pending) noexcept {
|
||||
void Gpu_Completion_Service::cancel_reserved(
|
||||
const std::shared_ptr<Pending_Fence>& pending) noexcept {
|
||||
if (!pending)
|
||||
return;
|
||||
std::lock_guard lock(pending->mutex);
|
||||
@@ -67,18 +75,23 @@ void Gpu_Completion_Service::acquire_slot() {
|
||||
backpressure_count_.fetch_add(1, std::memory_order_relaxed);
|
||||
slots_.acquire();
|
||||
const auto waited = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::steady_clock::now() - started).count();
|
||||
std::chrono::steady_clock::now() - started)
|
||||
.count();
|
||||
if (waited > 0)
|
||||
backpressure_wait_ns_.fetch_add(static_cast<std::uint64_t>(waited), std::memory_order_relaxed);
|
||||
backpressure_wait_ns_.fetch_add(
|
||||
static_cast<std::uint64_t>(waited),
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
const std::size_t in_flight = in_flight_.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
const std::size_t in_flight =
|
||||
in_flight_.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
update_peak(peak_in_flight_, in_flight);
|
||||
}
|
||||
void Gpu_Completion_Service::release_slot() noexcept {
|
||||
in_flight_.fetch_sub(1, std::memory_order_relaxed);
|
||||
slots_.release();
|
||||
}
|
||||
Gpu_Completion_Service::Reservation Gpu_Completion_Service::prepare(Completion completion, bool observe) {
|
||||
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))
|
||||
@@ -95,7 +108,8 @@ Gpu_Completion_Service::Reservation Gpu_Completion_Service::prepare(Completion c
|
||||
wake();
|
||||
return Reservation(std::move(pending));
|
||||
}
|
||||
Gpu_Completion_Service::Statistics Gpu_Completion_Service::statistics() const noexcept {
|
||||
Gpu_Completion_Service::Statistics
|
||||
Gpu_Completion_Service::statistics() const noexcept {
|
||||
return {
|
||||
static_cast<std::size_t>(default_capacity),
|
||||
in_flight_.load(std::memory_order_relaxed),
|
||||
@@ -103,8 +117,9 @@ Gpu_Completion_Service::Statistics Gpu_Completion_Service::statistics() const no
|
||||
watched_.load(std::memory_order_relaxed),
|
||||
peak_watched_.load(std::memory_order_relaxed),
|
||||
backpressure_count_.load(std::memory_order_relaxed),
|
||||
backpressure_wait_ns_.load(std::memory_order_relaxed)
|
||||
};
|
||||
backpressure_wait_ns_.load(std::memory_order_relaxed),
|
||||
fault_count_.load(std::memory_order_relaxed),
|
||||
abandoned_count_.load(std::memory_order_relaxed)};
|
||||
}
|
||||
void Gpu_Completion_Service::wake() noexcept {
|
||||
wake_generation_.fetch_add(1, std::memory_order_release);
|
||||
@@ -118,7 +133,17 @@ void Gpu_Completion_Service::run() noexcept {
|
||||
std::vector<std::shared_ptr<Pending_Fence>> active;
|
||||
active.reserve(static_cast<std::size_t>(default_capacity));
|
||||
std::size_t wait_group_index{};
|
||||
const auto finish = [this](const std::shared_ptr<Pending_Fence>& pending, VkResult result) {
|
||||
|
||||
const auto wait_age_ns = [](std::chrono::steady_clock::time_point started) {
|
||||
const auto elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::steady_clock::now() - started)
|
||||
.count();
|
||||
return elapsed > 0 ? static_cast<std::uint64_t>(elapsed) : 0ULL;
|
||||
};
|
||||
const auto finish = [this, &wait_age_ns](
|
||||
const std::shared_ptr<Pending_Fence>& pending,
|
||||
VkResult result, bool abandoned,
|
||||
const char* abandonment_reason = nullptr) {
|
||||
Completion completion;
|
||||
std::chrono::steady_clock::time_point watched_at{};
|
||||
bool observe{};
|
||||
@@ -131,14 +156,25 @@ void Gpu_Completion_Service::run() noexcept {
|
||||
}
|
||||
watched_.fetch_sub(1, std::memory_order_relaxed);
|
||||
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) {
|
||||
const std::uint64_t wait_ns = wait_age_ns(watched_at);
|
||||
if (observe)
|
||||
completion_result.wait_duration_ns = wait_ns;
|
||||
completion_result.abandoned = abandoned;
|
||||
if (abandoned || result != VK_SUCCESS) {
|
||||
fault_count_.fetch_add(1, std::memory_order_relaxed);
|
||||
if (abandoned)
|
||||
abandoned_count_.fetch_add(1, std::memory_order_relaxed);
|
||||
try {
|
||||
throw std::runtime_error("GPU fence wait failed with Vulkan result " + std::to_string(static_cast<int>(result)));
|
||||
if (abandoned) {
|
||||
std::string message = abandonment_reason != nullptr
|
||||
? abandonment_reason
|
||||
: "GPU fence was abandoned";
|
||||
message += " after " + std::to_string(wait_ns) + " ns";
|
||||
throw std::runtime_error(std::move(message));
|
||||
}
|
||||
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();
|
||||
}
|
||||
@@ -149,11 +185,14 @@ void Gpu_Completion_Service::run() noexcept {
|
||||
}
|
||||
release_slot();
|
||||
};
|
||||
|
||||
for (;;) {
|
||||
const std::uint64_t wake_generation = wake_generation_.load(std::memory_order_acquire);
|
||||
const std::uint64_t wake_generation =
|
||||
wake_generation_.load(std::memory_order_acquire);
|
||||
std::shared_ptr<Pending_Fence> incoming;
|
||||
while (pending_.try_pop(incoming))
|
||||
active.push_back(std::move(incoming));
|
||||
|
||||
std::vector<Device_Fences> groups;
|
||||
for (auto iterator = active.begin(); iterator != active.end();) {
|
||||
Pending_Fence::Status status;
|
||||
@@ -166,16 +205,19 @@ void Gpu_Completion_Service::run() noexcept {
|
||||
fence = (*iterator)->fence;
|
||||
}
|
||||
if (status == Pending_Fence::Status::canceled ||
|
||||
(status == Pending_Fence::Status::reserved && stopping_.load(std::memory_order_acquire))) {
|
||||
(status == Pending_Fence::Status::reserved &&
|
||||
stopping_.load(std::memory_order_acquire))) {
|
||||
cancel_reserved(*iterator);
|
||||
iterator = active.erase(iterator);
|
||||
release_slot();
|
||||
continue;
|
||||
}
|
||||
if (status == Pending_Fence::Status::watched) {
|
||||
auto group = std::find_if(groups.begin(), groups.end(), [device](const Device_Fences& item) {
|
||||
return item.device == device;
|
||||
});
|
||||
auto group = std::find_if(
|
||||
groups.begin(), groups.end(),
|
||||
[device](const Device_Fences& item) {
|
||||
return item.device == device;
|
||||
});
|
||||
if (group == groups.end()) {
|
||||
groups.push_back(Device_Fences{device, {}});
|
||||
group = groups.end() - 1;
|
||||
@@ -184,27 +226,47 @@ void Gpu_Completion_Service::run() noexcept {
|
||||
}
|
||||
++iterator;
|
||||
}
|
||||
if (stopping_.load(std::memory_order_acquire) && active.empty() && pending_.empty())
|
||||
if (stopping_.load(std::memory_order_acquire) && active.empty() &&
|
||||
pending_.empty())
|
||||
return;
|
||||
// Always probe every watched fence before entering a timed wait. The
|
||||
// timed wait still prevents busy-spinning, while this full probe keeps
|
||||
// completion latency bounded by one wait quantum instead of one quantum
|
||||
// per active device group.
|
||||
|
||||
// Probe every watched fence first. A permanently unsignaled fence is
|
||||
// converted into a logical failure after a bounded interval. The
|
||||
// submission is explicitly marked abandoned so its owner can
|
||||
// quarantine, rather than recycle, the referenced GPU resources.
|
||||
bool completed_any = false;
|
||||
for (auto iterator = active.begin(); iterator != active.end();) {
|
||||
VkDevice device{VK_NULL_HANDLE};
|
||||
VkFence fence{VK_NULL_HANDLE};
|
||||
Pending_Fence::Status status;
|
||||
std::chrono::steady_clock::time_point watched_at{};
|
||||
{
|
||||
std::lock_guard lock((*iterator)->mutex);
|
||||
status = (*iterator)->status;
|
||||
device = (*iterator)->device;
|
||||
fence = (*iterator)->fence;
|
||||
watched_at = (*iterator)->watched_at;
|
||||
}
|
||||
if (status != Pending_Fence::Status::watched) {
|
||||
++iterator;
|
||||
continue;
|
||||
}
|
||||
if (stopping_.load(std::memory_order_acquire)) {
|
||||
auto pending = *iterator;
|
||||
iterator = active.erase(iterator);
|
||||
finish(pending, VK_TIMEOUT, true,
|
||||
"GPU completion service stopped with an in-flight fence; submission quarantined");
|
||||
completed_any = true;
|
||||
continue;
|
||||
}
|
||||
if (wait_age_ns(watched_at) >= maximum_fence_age_ns) {
|
||||
auto pending = *iterator;
|
||||
iterator = active.erase(iterator);
|
||||
finish(pending, VK_TIMEOUT, true,
|
||||
"GPU fence exceeded the maximum completion age; submission quarantined");
|
||||
completed_any = true;
|
||||
continue;
|
||||
}
|
||||
const VkResult result = vkGetFenceStatus(device, fence);
|
||||
if (result == VK_NOT_READY) {
|
||||
++iterator;
|
||||
@@ -212,25 +274,31 @@ void Gpu_Completion_Service::run() noexcept {
|
||||
}
|
||||
auto pending = *iterator;
|
||||
iterator = active.erase(iterator);
|
||||
finish(pending, result);
|
||||
finish(pending, result, result != VK_SUCCESS,
|
||||
result == VK_SUCCESS
|
||||
? nullptr
|
||||
: "GPU fence query returned an error; submission quarantined");
|
||||
completed_any = true;
|
||||
}
|
||||
if (completed_any)
|
||||
continue;
|
||||
if (groups.empty()) {
|
||||
std::unique_lock lock(wait_mutex_);
|
||||
if (wake_generation_.load(std::memory_order_acquire) == wake_generation) {
|
||||
if (wake_generation_.load(std::memory_order_acquire) ==
|
||||
wake_generation) {
|
||||
wake_condition_.wait(lock, [this, wake_generation] {
|
||||
return wake_generation_.load(std::memory_order_acquire) != wake_generation;
|
||||
return wake_generation_.load(std::memory_order_acquire) !=
|
||||
wake_generation;
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
wait_group_index %= groups.size();
|
||||
const Device_Fences& group = groups[wait_group_index++];
|
||||
const VkResult wait_result = vkWaitForFences(
|
||||
group.device, static_cast<std::uint32_t>(group.fences.size()), group.fences.data(), VK_FALSE,
|
||||
fence_wait_timeout_ns);
|
||||
group.device, static_cast<std::uint32_t>(group.fences.size()),
|
||||
group.fences.data(), VK_FALSE, fence_wait_timeout_ns);
|
||||
for (auto iterator = active.begin(); iterator != active.end();) {
|
||||
VkDevice device{VK_NULL_HANDLE};
|
||||
VkFence fence{VK_NULL_HANDLE};
|
||||
@@ -241,7 +309,8 @@ void Gpu_Completion_Service::run() noexcept {
|
||||
device = (*iterator)->device;
|
||||
fence = (*iterator)->fence;
|
||||
}
|
||||
if (status != Pending_Fence::Status::watched || device != group.device) {
|
||||
if (status != Pending_Fence::Status::watched ||
|
||||
device != group.device) {
|
||||
++iterator;
|
||||
continue;
|
||||
}
|
||||
@@ -254,7 +323,10 @@ void Gpu_Completion_Service::run() noexcept {
|
||||
}
|
||||
auto pending = *iterator;
|
||||
iterator = active.erase(iterator);
|
||||
finish(pending, result);
|
||||
finish(pending, result, result != VK_SUCCESS,
|
||||
result == VK_SUCCESS
|
||||
? nullptr
|
||||
: "GPU fence wait returned an error; submission quarantined");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,11 @@ public:
|
||||
struct Result {
|
||||
std::exception_ptr error;
|
||||
std::uint64_t wait_duration_ns{};
|
||||
// True when the service cannot prove that the submission completed:
|
||||
// a fence exceeded the bounded wait policy, Vulkan returned an error,
|
||||
// or the service is shutting down. Callers must not recycle or destroy
|
||||
// resources referenced by that submission.
|
||||
bool abandoned{};
|
||||
};
|
||||
struct Statistics {
|
||||
std::size_t capacity{};
|
||||
@@ -28,6 +33,8 @@ public:
|
||||
std::size_t peak_watched{};
|
||||
std::uint64_t backpressure_count{};
|
||||
std::uint64_t backpressure_wait_ns{};
|
||||
std::uint64_t fault_count{};
|
||||
std::uint64_t abandoned_count{};
|
||||
};
|
||||
using Completion = std::function<void(Result)>;
|
||||
class Reservation final {
|
||||
@@ -76,6 +83,7 @@ private:
|
||||
void run() noexcept;
|
||||
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;
|
||||
std::counting_semaphore<default_capacity> slots_{default_capacity};
|
||||
oneapi::tbb::concurrent_bounded_queue<std::shared_ptr<Pending_Fence>> pending_;
|
||||
std::mutex wait_mutex_;
|
||||
@@ -87,6 +95,8 @@ private:
|
||||
std::atomic_size_t peak_watched_{};
|
||||
std::atomic_uint64_t backpressure_count_{};
|
||||
std::atomic_uint64_t backpressure_wait_ns_{};
|
||||
std::atomic_uint64_t fault_count_{};
|
||||
std::atomic_uint64_t abandoned_count_{};
|
||||
std::atomic_bool stopping_{};
|
||||
std::thread thread_;
|
||||
};
|
||||
|
||||
@@ -157,12 +157,6 @@ inline Json execution_json(const Node_Execution& execution,
|
||||
}
|
||||
return {
|
||||
{"node_id", execution.node_id},
|
||||
{"ready_time_ns", execution.ready_time_ns},
|
||||
{"start_time_ns", execution.start_time_ns},
|
||||
{"cpu_end_time_ns", execution.cpu_end_time_ns},
|
||||
{"external_start_time_ns", execution.external_start_time_ns},
|
||||
{"external_end_time_ns", execution.external_end_time_ns},
|
||||
{"end_time_ns", execution.end_time_ns},
|
||||
{"start_offset_ns", execution.start_time_ns >= frame.render_start_ns
|
||||
? execution.start_time_ns - frame.render_start_ns : 0},
|
||||
{"cpu_end_offset_ns", execution.cpu_end_time_ns >= frame.render_start_ns
|
||||
@@ -198,7 +192,7 @@ inline Json node_analysis_json(const Node_Frame_Analysis& node) {
|
||||
{"gpu_execution_duration_ns", node.gpu_execution_duration_ns},
|
||||
{"start_offset_ns", node.start_offset_ns},
|
||||
{"end_offset_ns", node.end_offset_ns},
|
||||
{"dependency_ready_time_ns", node.dependency_ready_time_ns},
|
||||
{"dependency_ready_offset_ns", node.dependency_ready_offset_ns},
|
||||
{"scheduler_wait_ns", node.scheduler_wait_ns},
|
||||
{"work_contribution", node.work_contribution},
|
||||
{"critical_path_contribution", node.critical_path_contribution},
|
||||
@@ -222,8 +216,6 @@ inline Json frame_json(const Captured_Frame& frame) {
|
||||
return {
|
||||
{"frame_id", frame.snapshot->frame_id},
|
||||
{"render_plan_version", frame.snapshot->render_plan_version},
|
||||
{"render_start_ns", frame.snapshot->render_start_ns},
|
||||
{"render_end_ns", frame.snapshot->render_end_ns},
|
||||
{"render_duration_ns", frame.snapshot->render_duration_ns()},
|
||||
{"node_executions", std::move(executions)},
|
||||
{"analysis", {
|
||||
|
||||
@@ -490,7 +490,9 @@ public:
|
||||
{"queued", runtime.gpu_completion.queued},
|
||||
{"peak_queued", runtime.gpu_completion.peak_queued},
|
||||
{"backpressure_count", runtime.gpu_completion.backpressure_count},
|
||||
{"backpressure_wait_ns", runtime.gpu_completion.backpressure_wait_ns}}}}},
|
||||
{"backpressure_wait_ns", runtime.gpu_completion.backpressure_wait_ns},
|
||||
{"fault_count", runtime.gpu_completion_fault_count},
|
||||
{"abandoned_count", runtime.gpu_completion_abandoned_count}}}}},
|
||||
{"session_id", session_id_},
|
||||
{"performance_capture",
|
||||
gallery_performance_capture_json(scene_.render_scene())},
|
||||
|
||||
@@ -544,12 +544,14 @@ TEST(RenderiveWebGallery, PerformanceCaptureReturnsPlanFrameAnalysisAndStatistic
|
||||
for (const auto& frame : captured.at("frames")) {
|
||||
EXPECT_TRUE(frame.contains("frame_id"));
|
||||
EXPECT_TRUE(frame.contains("render_plan_version"));
|
||||
EXPECT_TRUE(frame.contains("render_start_ns"));
|
||||
EXPECT_TRUE(frame.contains("render_end_ns"));
|
||||
EXPECT_FALSE(frame.contains("render_start_ns"));
|
||||
EXPECT_FALSE(frame.contains("render_end_ns"));
|
||||
ASSERT_FALSE(frame.at("node_executions").empty());
|
||||
const auto& execution = frame.at("node_executions").at(0);
|
||||
EXPECT_TRUE(execution.contains("start_time_ns"));
|
||||
EXPECT_TRUE(execution.contains("end_time_ns"));
|
||||
EXPECT_FALSE(execution.contains("start_time_ns"));
|
||||
EXPECT_FALSE(execution.contains("end_time_ns"));
|
||||
EXPECT_TRUE(execution.contains("start_offset_ns"));
|
||||
EXPECT_TRUE(execution.contains("end_offset_ns"));
|
||||
EXPECT_TRUE(execution.contains("worker_id"));
|
||||
EXPECT_TRUE(execution.contains("status"));
|
||||
EXPECT_TRUE(execution.contains("attachments"));
|
||||
|
||||
@@ -36,10 +36,10 @@ export interface Gallery_Renderable_Cache {owner_id: number; name: string; prepa
|
||||
export interface Gallery_Render_Plan {version: number; nodes: Gallery_Render_Node[]; edges: Gallery_Render_Edge[]; renderables: Gallery_Renderable_Cache[];}
|
||||
export interface Gallery_Node_Diagnostic_Attachment {type: string; content: string;}
|
||||
export interface Gallery_Node_Execution {node_id: number; worker_id: number; start_offset_ns: number; cpu_end_offset_ns: number; external_start_offset_ns: number; external_end_offset_ns: number; end_offset_ns: number; duration_ns: number; cpu_duration_ns: number; external_duration_ns: number; status: "pending"|"ready"|"running"|"waiting_external"|"complete"|"failed"|"cancelled"; metrics?: Record<string, Json_Primitive>; attachments: Gallery_Node_Diagnostic_Attachment[];}
|
||||
export interface Gallery_Node_Analysis {node_id: number; duration_ns: number; cpu_duration_ns: number; render_domain_cpu_duration_ns: number; total_cpu_work_duration_ns: number; external_duration_ns: number; render_domain_queue_wait_ns: number; gpu_completion_wait_ns: number; gpu_execution_duration_ns: number; start_offset_ns: number; end_offset_ns: number; dependency_ready_time_ns: number; scheduler_wait_ns: number; work_contribution: number; critical_path_contribution: number; on_critical_path: boolean;}
|
||||
export interface Gallery_Node_Analysis {node_id: number; duration_ns: number; cpu_duration_ns: number; render_domain_cpu_duration_ns: number; total_cpu_work_duration_ns: number; external_duration_ns: number; render_domain_queue_wait_ns: number; gpu_completion_wait_ns: number; gpu_execution_duration_ns: number; start_offset_ns: number; end_offset_ns: number; dependency_ready_offset_ns: number; scheduler_wait_ns: number; work_contribution: number; critical_path_contribution: number; on_critical_path: boolean;}
|
||||
export interface Gallery_Worker_Analysis {worker_id: number; work_duration_ns: number; utilization: number;}
|
||||
export interface Gallery_Frame_Analysis {total_render_duration_ns: number; critical_path_duration_ns: number; total_work_duration_ns: number; scheduler_work_duration_ns: number; render_domain_work_duration_ns: number; total_external_duration_ns: number; total_render_domain_queue_wait_ns: number; total_gpu_completion_wait_ns: number; total_gpu_execution_duration_ns: number; scheduler_parallel_overlap_ns: number; scheduler_peak_parallelism: number; average_cpu_concurrency: number; scheduler_average_parallelism: number; render_domain_utilization: number; critical_path: number[]; bottleneck_nodes: number[]; nodes: Gallery_Node_Analysis[]; workers: Gallery_Worker_Analysis[];}
|
||||
export interface Gallery_Captured_Frame {frame_id: number; render_plan_version: number; render_start_ns: number; render_end_ns: number; render_duration_ns: number; node_executions: Gallery_Node_Execution[]; analysis?: Gallery_Frame_Analysis;}
|
||||
export interface Gallery_Captured_Frame {frame_id: number; render_plan_version: number; render_duration_ns: number; node_executions: Gallery_Node_Execution[]; analysis?: Gallery_Frame_Analysis;}
|
||||
export interface Gallery_Node_Statistics {node_id: number; execution_count: number; average_ns: number; moving_average_ns: number; p50_ns: number; p95_ns: number; p99_ns: number; minimum_ns: number; maximum_ns: number; average_cpu_ns: number; average_render_domain_cpu_ns: number; average_total_cpu_work_ns: number; average_external_ns: number; average_render_domain_queue_wait_ns: number; average_gpu_completion_wait_ns: number; average_gpu_execution_ns: number; p95_cpu_ns: number; p95_render_domain_cpu_ns: number; p95_external_ns: number; p95_gpu_execution_ns: number; critical_path_frequency: number; average_scheduler_wait_ns: number;}
|
||||
export interface Gallery_Plan_Statistics {render_plan_version: number; frame_count: number; render_average_ns: number; render_p50_ns: number; render_p95_ns: number; render_maximum_ns: number; average_cpu_concurrency: number; scheduler_average_parallelism: number; render_domain_utilization: number; scheduler_peak_parallelism: number; scheduler_wait_average_ns: number; scheduler_wait_p95_ns: number; nodes: Gallery_Node_Statistics[];}
|
||||
export interface Gallery_Capture_Session {session_id: number; active: boolean; requested_count: number; captured_count: number; frames: Gallery_Captured_Frame[]; node_statistics: Gallery_Node_Statistics[]; plan_statistics: Gallery_Plan_Statistics[]; summary?: Record<string, Json_Value>;}
|
||||
|
||||
@@ -14,8 +14,6 @@ describe("DAG view model", () => {
|
||||
const frame: Gallery_Captured_Frame = {
|
||||
frame_id: 1,
|
||||
render_plan_version: 2,
|
||||
render_start_ns: 100,
|
||||
render_end_ns: 110,
|
||||
render_duration_ns: 10,
|
||||
node_executions: [{
|
||||
node_id: 7,
|
||||
@@ -60,7 +58,7 @@ describe("DAG view model", () => {
|
||||
gpu_execution_duration_ns: 0,
|
||||
start_offset_ns: 0,
|
||||
end_offset_ns: 10,
|
||||
dependency_ready_time_ns: 0,
|
||||
dependency_ready_offset_ns: 0,
|
||||
scheduler_wait_ns: 0,
|
||||
work_contribution: 1,
|
||||
critical_path_contribution: 1,
|
||||
|
||||
Reference in New Issue
Block a user