改进
This commit is contained in:
@@ -85,18 +85,27 @@ void Capture_Repository::begin_session(Capture_Session_Id session_id,
|
||||
return session.session_id == session_id;
|
||||
}))
|
||||
throw std::invalid_argument("capture session already exists");
|
||||
while (sessions_.size() >= retained_session_count) {
|
||||
const auto completed = std::find_if(sessions_.begin(), sessions_.end(),
|
||||
[](const Capture_Session& session) { return !session.active(); });
|
||||
if (completed == sessions_.end())
|
||||
throw std::logic_error("capture repository retention is occupied by active sessions");
|
||||
sessions_.erase(completed);
|
||||
}
|
||||
sessions_.push_back({session_id, requested_count, {}});
|
||||
sessions_.back().frames.reserve(requested_count);
|
||||
}
|
||||
|
||||
void Capture_Repository::publish(Capture_Session_Id session_id,
|
||||
std::shared_ptr<const Frame_Snapshot> snapshot,
|
||||
std::shared_ptr<const Render_Plan> render_plan,
|
||||
Frame_Analysis analysis) {
|
||||
if (!snapshot)
|
||||
throw std::invalid_argument("captured frame snapshot is null");
|
||||
if (!snapshot || !render_plan)
|
||||
throw std::invalid_argument("captured frame snapshot or render plan is null");
|
||||
if (analysis.frame_id != snapshot->frame_id ||
|
||||
analysis.render_plan_version != snapshot->render_plan_version)
|
||||
throw std::invalid_argument("captured frame analysis does not match snapshot");
|
||||
analysis.render_plan_version != snapshot->render_plan_version ||
|
||||
render_plan->version != snapshot->render_plan_version)
|
||||
throw std::invalid_argument("captured frame analysis does not match snapshot or render plan");
|
||||
std::lock_guard lock(mutex_);
|
||||
const auto iterator = std::find_if(sessions_.begin(), sessions_.end(),
|
||||
[session_id](const auto& session) { return session.session_id == session_id; });
|
||||
@@ -104,7 +113,8 @@ void Capture_Repository::publish(Capture_Session_Id session_id,
|
||||
throw std::invalid_argument("capture session does not exist");
|
||||
if (!iterator->active())
|
||||
throw std::logic_error("capture session is complete");
|
||||
iterator->frames.push_back({std::move(snapshot), std::move(analysis)});
|
||||
iterator->frames.push_back({std::move(snapshot), std::move(render_plan),
|
||||
std::move(analysis)});
|
||||
}
|
||||
|
||||
std::optional<Capture_Session> Capture_Repository::session(
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <vector>
|
||||
#include "Capture_Types.hpp"
|
||||
#include "renderive/render_graph/Frame_Analysis.hpp"
|
||||
#include "renderive/render_graph/Render_Plan.hpp"
|
||||
|
||||
class Capture_Controller {
|
||||
public:
|
||||
@@ -39,6 +40,7 @@ private:
|
||||
|
||||
struct Captured_Frame {
|
||||
std::shared_ptr<const Frame_Snapshot> snapshot;
|
||||
std::shared_ptr<const Render_Plan> render_plan;
|
||||
Frame_Analysis analysis;
|
||||
};
|
||||
|
||||
@@ -59,10 +61,12 @@ public:
|
||||
void begin_session(Capture_Session_Id session_id, std::size_t requested_count);
|
||||
void publish(Capture_Session_Id session_id,
|
||||
std::shared_ptr<const Frame_Snapshot> snapshot,
|
||||
std::shared_ptr<const Render_Plan> render_plan,
|
||||
Frame_Analysis analysis);
|
||||
[[nodiscard]] std::optional<Capture_Session> session(Capture_Session_Id session_id) const;
|
||||
[[nodiscard]] std::vector<Capture_Session_Id> sessions() const;
|
||||
private:
|
||||
static constexpr std::size_t retained_session_count = 16;
|
||||
mutable std::mutex mutex_;
|
||||
std::vector<Capture_Session> sessions_;
|
||||
};
|
||||
|
||||
@@ -112,6 +112,8 @@ std::shared_ptr<const Render_Plan> Render_Plan_History::publish(Render_Graph gra
|
||||
plan->version = next_version_++;
|
||||
plan->graph = std::move(graph);
|
||||
plans_.push_back(plan);
|
||||
if (plans_.size() > retained_plan_count)
|
||||
plans_.erase(plans_.begin());
|
||||
return plan;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ public:
|
||||
[[nodiscard]] std::shared_ptr<const Render_Plan> current() const;
|
||||
[[nodiscard]] std::shared_ptr<const Render_Plan> find(Render_Plan_Version version) const;
|
||||
private:
|
||||
static constexpr std::size_t retained_plan_count = 64;
|
||||
mutable std::mutex mutex_;
|
||||
std::vector<std::shared_ptr<const Render_Plan>> plans_;
|
||||
Render_Plan_Version next_version_{1};
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#include "Scene_Base.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
#include <memory_resource>
|
||||
#include <queue>
|
||||
#include <semaphore>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
@@ -38,7 +40,19 @@ public:
|
||||
wait();
|
||||
}
|
||||
void submit(Operation operation) {
|
||||
operations_.push(std::move(operation));
|
||||
const auto started = std::chrono::steady_clock::now();
|
||||
if (!slots_.try_acquire()) {
|
||||
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();
|
||||
if (waited > 0)
|
||||
backpressure_wait_ns_.fetch_add(static_cast<std::uint64_t>(waited), std::memory_order_relaxed);
|
||||
}
|
||||
const std::size_t queued = queued_.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
update_peak(peak_queued_, queued);
|
||||
if (!operations_.try_push(std::move(operation)))
|
||||
std::terminate();
|
||||
schedule();
|
||||
}
|
||||
void wait() noexcept {
|
||||
@@ -48,7 +62,21 @@ public:
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
[[nodiscard]] Scene_Execution_Statistics statistics() const noexcept {
|
||||
return {
|
||||
static_cast<std::size_t>(default_capacity),
|
||||
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)
|
||||
};
|
||||
}
|
||||
private:
|
||||
static void 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)) {}
|
||||
}
|
||||
void schedule() {
|
||||
if (scheduled_.exchange(true, std::memory_order_acq_rel))
|
||||
return;
|
||||
@@ -57,19 +85,26 @@ private:
|
||||
}
|
||||
void execute_one() {
|
||||
Operation operation;
|
||||
if (operations_.try_pop(operation))
|
||||
if (operations_.try_pop(operation)) {
|
||||
queued_.fetch_sub(1, std::memory_order_relaxed);
|
||||
slots_.release();
|
||||
operation();
|
||||
}
|
||||
scheduled_.store(false, std::memory_order_release);
|
||||
if (!operations_.empty())
|
||||
schedule();
|
||||
}
|
||||
static constexpr std::size_t default_capacity = 64;
|
||||
static constexpr std::ptrdiff_t default_capacity = 64;
|
||||
std::counting_semaphore<default_capacity> slots_{default_capacity};
|
||||
oneapi::tbb::flow::graph graph_;
|
||||
oneapi::tbb::concurrent_bounded_queue<Operation> operations_;
|
||||
oneapi::tbb::flow::function_node<Message, Message> node_;
|
||||
std::atomic_size_t queued_{};
|
||||
std::atomic_size_t peak_queued_{};
|
||||
std::atomic_uint64_t backpressure_count_{};
|
||||
std::atomic_uint64_t backpressure_wait_ns_{};
|
||||
std::atomic_bool scheduled_{};
|
||||
};
|
||||
|
||||
class Scene_Base::Render_Execution_Scope {
|
||||
public:
|
||||
explicit Render_Execution_Scope(Scene_Base& scene) noexcept
|
||||
@@ -799,6 +834,10 @@ std::vector<Plan_Version_Statistics> Scene_Base::plan_version_statistics(
|
||||
return analyze_plan_versions(analyses);
|
||||
}
|
||||
|
||||
Scene_Execution_Statistics Scene_Base::execution_statistics() const noexcept {
|
||||
return execution_context_->statistics();
|
||||
}
|
||||
|
||||
std::pmr::memory_resource& Scene_Base::memory_resource() const noexcept {
|
||||
return memory_domain_->resource();
|
||||
}
|
||||
@@ -1273,7 +1312,7 @@ void Scene_Base::execute_render_graph(Render_Task& task) {
|
||||
}
|
||||
if (completed) {
|
||||
capture_repository_.publish(
|
||||
capture_ticket.session_id, completed,
|
||||
capture_ticket.session_id, completed, compiled.plan,
|
||||
analyze_frame(*compiled.plan, *completed));
|
||||
}
|
||||
capture_controller_.finish_frame(capture_ticket, true);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <concepts>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
@@ -59,6 +60,14 @@ public:
|
||||
const Scene_Render_Context& context) = 0;
|
||||
};
|
||||
|
||||
struct Scene_Execution_Statistics {
|
||||
std::size_t capacity{};
|
||||
std::size_t queued{};
|
||||
std::size_t peak_queued{};
|
||||
std::uint64_t backpressure_count{};
|
||||
std::uint64_t backpressure_wait_ns{};
|
||||
};
|
||||
|
||||
class Scene_Base {
|
||||
private:
|
||||
struct Render_Completion;
|
||||
@@ -171,6 +180,7 @@ public:
|
||||
Capture_Session_Id session_id) const;
|
||||
[[nodiscard]] std::vector<Plan_Version_Statistics> plan_version_statistics(
|
||||
Capture_Session_Id session_id) const;
|
||||
[[nodiscard]] Scene_Execution_Statistics execution_statistics() const noexcept;
|
||||
|
||||
[[nodiscard]] std::pmr::memory_resource& memory_resource() const noexcept;
|
||||
[[nodiscard]] std::pmr::memory_resource& upstream_memory_resource() const noexcept;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
namespace renderive::scheduling {
|
||||
struct Scheduler_Statistics {
|
||||
std::size_t concurrency{};
|
||||
@@ -15,4 +16,5 @@ struct Scheduler_Statistics {
|
||||
};
|
||||
[[nodiscard]] std::size_t scheduler_concurrency() noexcept;
|
||||
[[nodiscard]] Scheduler_Statistics scheduler_statistics() noexcept;
|
||||
void enqueue_task(std::function<void()> task);
|
||||
}
|
||||
|
||||
@@ -95,4 +95,7 @@ std::size_t scheduler_concurrency() noexcept {
|
||||
Scheduler_Statistics scheduler_statistics() noexcept {
|
||||
return detail::OneTBB_Runtime::instance().statistics();
|
||||
}
|
||||
void enqueue_task(std::function<void()> task) {
|
||||
detail::OneTBB_Runtime::instance().enqueue(std::move(task));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ TEST(render_dag_test, capture_controller_and_repository_capture_exact_requested_
|
||||
execution->worker_id = static_cast<std::uint32_t>(slot);
|
||||
execution->status = Node_Execution_Status::complete;
|
||||
}
|
||||
repository.publish(session_id, snapshot, analyze_frame(*plan, *snapshot));
|
||||
repository.publish(session_id, snapshot, plan, analyze_frame(*plan, *snapshot));
|
||||
}
|
||||
|
||||
const auto session = repository.session(session_id);
|
||||
@@ -232,3 +232,49 @@ TEST(render_dag_test, concurrent_workers_reserve_exactly_one_capture_slot_each)
|
||||
EXPECT_EQ(captured.load(std::memory_order_relaxed), requested);
|
||||
EXPECT_FALSE(controller.state().enabled());
|
||||
}
|
||||
|
||||
|
||||
TEST(render_dag_test, render_plan_history_has_bounded_retention) {
|
||||
Render_Plan_History history;
|
||||
std::vector<Render_Plan_Version> versions;
|
||||
for (std::uint64_t index = 0; index < 70; ++index) {
|
||||
Render_Graph_Builder builder;
|
||||
builder.emplace(1000 + index, 2000 + index, "Node", Render_Node_Kind::prepare);
|
||||
versions.push_back(history.publish(std::move(builder).finish())->version);
|
||||
}
|
||||
EXPECT_FALSE(history.find(versions.front()));
|
||||
EXPECT_TRUE(history.find(versions.back()));
|
||||
ASSERT_TRUE(history.current());
|
||||
EXPECT_EQ(history.current()->version, versions.back());
|
||||
}
|
||||
|
||||
TEST(render_dag_test, capture_repository_retains_only_recent_completed_sessions) {
|
||||
Capture_Repository repository;
|
||||
Render_Graph_Builder builder;
|
||||
builder.emplace(3000, 4000, "Node", Render_Node_Kind::prepare);
|
||||
Render_Plan_History history;
|
||||
const auto plan = history.publish(std::move(builder).finish());
|
||||
for (Capture_Session_Id session_id = 1; session_id <= 20; ++session_id) {
|
||||
repository.begin_session(session_id, 1);
|
||||
auto snapshot = std::make_shared<Frame_Snapshot>();
|
||||
snapshot->frame_id = session_id;
|
||||
snapshot->render_plan_version = plan->version;
|
||||
snapshot->render_start_ns = session_id * 100;
|
||||
snapshot->render_end_ns = snapshot->render_start_ns + 10;
|
||||
snapshot->node_executions.resize(1);
|
||||
auto& execution = snapshot->node_executions.front();
|
||||
execution.node_id = plan->graph.nodes.front().node_id;
|
||||
execution.ready_time_ns = snapshot->render_start_ns;
|
||||
execution.start_time_ns = snapshot->render_start_ns;
|
||||
execution.cpu_end_time_ns = snapshot->render_end_ns;
|
||||
execution.end_time_ns = snapshot->render_end_ns;
|
||||
execution.status = Node_Execution_Status::complete;
|
||||
repository.publish(session_id, snapshot, plan, analyze_frame(*plan, *snapshot));
|
||||
}
|
||||
const auto sessions = repository.sessions();
|
||||
ASSERT_EQ(sessions.size(), 16U);
|
||||
EXPECT_EQ(sessions.front(), 5U);
|
||||
EXPECT_EQ(sessions.back(), 20U);
|
||||
EXPECT_FALSE(repository.session(1));
|
||||
EXPECT_TRUE(repository.session(20));
|
||||
}
|
||||
|
||||
+8
-3
@@ -21,9 +21,14 @@ if (NOT TARGET Renderive_render_2D)
|
||||
rcl_log_append(${CMAKE_CURRENT_LIST_LINE} "[FATAL_ERROR] Renderive_Qt requires Renderive_render_2D")
|
||||
return()
|
||||
endif ()
|
||||
set(Renderive_Qt_source_dir "${CMAKE_CURRENT_LIST_DIR}")
|
||||
append_glob_source(Renderive_Qt_sources "${Renderive_Qt_source_dir}")
|
||||
list(FILTER Renderive_Qt_sources EXCLUDE REGEX "/tests/")
|
||||
set(Renderive_Qt_source_dirs
|
||||
"${CMAKE_CURRENT_LIST_DIR}/bridge"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/plot"
|
||||
)
|
||||
set(Renderive_Qt_sources)
|
||||
foreach (Renderive_Qt_source_dir IN LISTS Renderive_Qt_source_dirs)
|
||||
append_glob_source(Renderive_Qt_sources "${Renderive_Qt_source_dir}")
|
||||
endforeach ()
|
||||
add_library(Renderive_Qt STATIC ${Renderive_Qt_sources})
|
||||
set_target_properties(Renderive_Qt PROPERTIES AUTOMOC ON)
|
||||
target_include_directories(Renderive_Qt PUBLIC
|
||||
|
||||
@@ -19,9 +19,8 @@ if (NOT TARGET Renderive_Kernel)
|
||||
rcl_log_append(${CMAKE_CURRENT_LIST_LINE} "[FATAL_ERROR] Renderive_render_2D requires Renderive_Kernel")
|
||||
return()
|
||||
endif ()
|
||||
set(Renderive_render_2D_source_dir "${CMAKE_CURRENT_LIST_DIR}")
|
||||
set(Renderive_render_2D_source_dir "${CMAKE_CURRENT_LIST_DIR}/render_2D")
|
||||
append_glob_source(Renderive_render_2D_sources "${Renderive_render_2D_source_dir}")
|
||||
list(FILTER Renderive_render_2D_sources EXCLUDE REGEX "/tests/")
|
||||
add_library(Renderive_render_2D STATIC ${Renderive_render_2D_sources})
|
||||
target_include_directories(Renderive_render_2D PUBLIC
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# 加载 datoviz的依赖库
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/cmake/export.cmake)
|
||||
set(Renderive_render_3D_dependencies
|
||||
render_3D::PThreads4W
|
||||
render_3D::cglm
|
||||
render_3D::Vulkan-Hpp
|
||||
render_3D::VulkanMemoryAllocator
|
||||
@@ -13,6 +12,9 @@ set(Renderive_render_3D_dependencies
|
||||
render_3D::tinyxml2
|
||||
global::zlib
|
||||
)
|
||||
if (WIN32)
|
||||
list(APPEND Renderive_render_3D_dependencies render_3D::PThreads4W)
|
||||
endif ()
|
||||
if (RENDERIVE_BUILD_TESTS)
|
||||
list(APPEND Renderive_render_3D_dependencies global::GTest)
|
||||
endif ()
|
||||
@@ -25,10 +27,13 @@ if (Renderive_render_3D_missing)
|
||||
endif ()
|
||||
rcl_load_dependency_environment(${Renderive_render_3D_dependencies})
|
||||
find_package(Vulkan REQUIRED)
|
||||
find_package(PThreads4W QUIET)
|
||||
if (NOT PThreads4W_FOUND)
|
||||
rcl_log_append(${CMAKE_CURRENT_LIST_LINE} "[FATAL_ERROR] Renderive_render_3D PThreads4W installation is incomplete. Rebuild render_3D::PThreads4W")
|
||||
return()
|
||||
find_package(Threads REQUIRED)
|
||||
if (WIN32)
|
||||
find_package(PThreads4W QUIET)
|
||||
if (NOT PThreads4W_FOUND)
|
||||
rcl_log_append(${CMAKE_CURRENT_LIST_LINE} "[FATAL_ERROR] Renderive_render_3D PThreads4W installation is incomplete. Rebuild render_3D::PThreads4W")
|
||||
return()
|
||||
endif ()
|
||||
endif ()
|
||||
find_package(cglm CONFIG REQUIRED)
|
||||
find_package(volk CONFIG REQUIRED)
|
||||
@@ -89,10 +94,8 @@ if (NOT TARGET Renderive_Kernel)
|
||||
return()
|
||||
endif ()
|
||||
add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/datoviz" "${CMAKE_CURRENT_BINARY_DIR}/datoviz")
|
||||
set(Renderive_render_3D_source_dir "${CMAKE_CURRENT_LIST_DIR}")
|
||||
set(Renderive_render_3D_source_dir "${CMAKE_CURRENT_LIST_DIR}/render_3D")
|
||||
append_glob_source(Renderive_render_3D_sources "${Renderive_render_3D_source_dir}")
|
||||
list(FILTER Renderive_render_3D_sources EXCLUDE REGEX "/tests/")
|
||||
list(FILTER Renderive_render_3D_sources EXCLUDE REGEX "/datoviz/")
|
||||
add_library(Renderive_render_3D STATIC ${Renderive_render_3D_sources})
|
||||
target_include_directories(Renderive_render_3D PUBLIC
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>"
|
||||
@@ -100,7 +103,7 @@ target_include_directories(Renderive_render_3D PUBLIC
|
||||
target_compile_features(Renderive_render_3D PUBLIC cxx_std_20)
|
||||
target_link_libraries(Renderive_render_3D
|
||||
PUBLIC Renderive_Kernel
|
||||
PRIVATE ${Renderive_render_3D_datoviz_targets} tinyobjloader::tinyobjloader
|
||||
PRIVATE ${Renderive_render_3D_datoviz_targets} tinyobjloader::tinyobjloader Threads::Threads
|
||||
)
|
||||
if (MSVC)
|
||||
# Datoviz is embedded from OBJECT libraries in this static archive. Its Windows headers
|
||||
|
||||
@@ -138,11 +138,20 @@ if(TARGET Vulkan::Headers)
|
||||
else()
|
||||
set(Renderive_render_3D_datoviz_vulkan_target Vulkan::Vulkan)
|
||||
endif()
|
||||
# CORE/common:Datoviz 通用基础设施,线程实现沿用当前 PThreads4W 环境。
|
||||
if(WIN32)
|
||||
if(NOT TARGET PThreads4W::PThreads4W)
|
||||
message(FATAL_ERROR "Datoviz requires PThreads4W::PThreads4W on Windows")
|
||||
endif()
|
||||
set(Renderive_render_3D_datoviz_thread_target PThreads4W::PThreads4W)
|
||||
else()
|
||||
find_package(Threads REQUIRED)
|
||||
set(Renderive_render_3D_datoviz_thread_target Threads::Threads)
|
||||
endif()
|
||||
# CORE/common:Datoviz 通用基础设施使用平台线程运行库。
|
||||
Renderive_render_3D_datoviz_collect_sources(Renderive_render_3D_datoviz_common_sources "${Renderive_render_3D_datoviz_src_dir}/common")
|
||||
add_library(Renderive_render_3D_datoviz_common OBJECT ${Renderive_render_3D_datoviz_common_sources})
|
||||
target_include_directories(Renderive_render_3D_datoviz_common PUBLIC "${Renderive_render_3D_datoviz_include_dir}" INTERFACE "${Renderive_render_3D_datoviz_src_dir}/common")
|
||||
target_link_libraries(Renderive_render_3D_datoviz_common PUBLIC PThreads4W::PThreads4W)
|
||||
target_link_libraries(Renderive_render_3D_datoviz_common PUBLIC ${Renderive_render_3D_datoviz_thread_target})
|
||||
Renderive_render_3D_datoviz_configure_target(Renderive_render_3D_datoviz_common)
|
||||
# CORE/fileio:保留 Datoviz fileio + fpng 的边界,压缩统一使用工程已经安装的 ZLIB。
|
||||
Renderive_render_3D_datoviz_collect_sources(Renderive_render_3D_datoviz_fileio_sources "${Renderive_render_3D_datoviz_src_dir}/fileio")
|
||||
@@ -162,13 +171,13 @@ Renderive_render_3D_datoviz_configure_target(Renderive_render_3D_datoviz_geom)
|
||||
Renderive_render_3D_datoviz_collect_sources(Renderive_render_3D_datoviz_math_sources "${Renderive_render_3D_datoviz_src_dir}/math")
|
||||
add_library(Renderive_render_3D_datoviz_math OBJECT ${Renderive_render_3D_datoviz_math_sources})
|
||||
target_include_directories(Renderive_render_3D_datoviz_math PUBLIC "${Renderive_render_3D_datoviz_include_dir}" "${Renderive_render_3D_datoviz_src_dir}/common")
|
||||
target_link_libraries(Renderive_render_3D_datoviz_math PUBLIC cglm::cglm PThreads4W::PThreads4W)
|
||||
target_link_libraries(Renderive_render_3D_datoviz_math PUBLIC cglm::cglm ${Renderive_render_3D_datoviz_thread_target})
|
||||
Renderive_render_3D_datoviz_configure_target(Renderive_render_3D_datoviz_math)
|
||||
# CORE/thread:线程模块只依赖线程运行库,不引入 Datoviz 其他 runtime。
|
||||
Renderive_render_3D_datoviz_collect_sources(Renderive_render_3D_datoviz_thread_sources "${Renderive_render_3D_datoviz_src_dir}/thread")
|
||||
add_library(Renderive_render_3D_datoviz_thread OBJECT ${Renderive_render_3D_datoviz_thread_sources})
|
||||
target_include_directories(Renderive_render_3D_datoviz_thread PUBLIC "${Renderive_render_3D_datoviz_include_dir}" "${Renderive_render_3D_datoviz_src_dir}/common")
|
||||
target_link_libraries(Renderive_render_3D_datoviz_thread PUBLIC PThreads4W::PThreads4W)
|
||||
target_link_libraries(Renderive_render_3D_datoviz_thread PUBLIC ${Renderive_render_3D_datoviz_thread_target})
|
||||
Renderive_render_3D_datoviz_configure_target(Renderive_render_3D_datoviz_thread)
|
||||
# CONTROLLER/input:先保留 Datoviz 输入数据结构,不接 window/glfw。
|
||||
Renderive_render_3D_datoviz_collect_sources(Renderive_render_3D_datoviz_input_sources "${Renderive_render_3D_datoviz_src_dir}/input")
|
||||
|
||||
@@ -53,7 +53,7 @@ function(Renderive_render_3D_datoviz_add_test_runner target source_var component
|
||||
target_link_libraries(${target} PRIVATE
|
||||
${ARGN}
|
||||
Renderive_render_3D_datoviz_testing
|
||||
PThreads4W::PThreads4W)
|
||||
${Renderive_render_3D_datoviz_thread_target})
|
||||
if(Renderive_render_3D_datoviz_msvc_system_library_dirs)
|
||||
target_link_directories(${target} PRIVATE
|
||||
${Renderive_render_3D_datoviz_msvc_system_library_dirs})
|
||||
|
||||
@@ -106,6 +106,7 @@ struct Scene_Model {
|
||||
[[nodiscard]] virtual std::shared_ptr<const Pixel_Frame> latest_frame()
|
||||
const = 0;
|
||||
[[nodiscard]] virtual Frame_Status frame_status() const = 0;
|
||||
[[nodiscard]] virtual Runtime_Statistics runtime_statistics() const noexcept = 0;
|
||||
[[nodiscard]] virtual Scene_Base& scene() noexcept = 0;
|
||||
[[nodiscard]] virtual const Scene_Base& scene() const noexcept = 0;
|
||||
};
|
||||
@@ -295,6 +296,18 @@ struct Basic_Point_Scene final
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] Runtime_Statistics runtime_statistics() const noexcept override {
|
||||
const auto render = render_domain->statistics();
|
||||
const auto completion = detail::Gpu_Completion_Service::instance().statistics();
|
||||
return {
|
||||
{render.capacity, render.admitted, render.peak_admitted, render.queued,
|
||||
render.peak_queued, render.backpressure_count, render.backpressure_wait_ns},
|
||||
{completion.capacity, completion.in_flight, completion.peak_in_flight,
|
||||
completion.watched, completion.peak_watched, completion.backpressure_count,
|
||||
completion.backpressure_wait_ns}
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] Scene_Base& scene() noexcept override { return *this; }
|
||||
[[nodiscard]] const Scene_Base& scene() const noexcept override {
|
||||
return *this;
|
||||
@@ -544,6 +557,10 @@ Frame_Status Point_Scene::frame_status() const {
|
||||
return impl_->model->frame_status();
|
||||
}
|
||||
|
||||
Runtime_Statistics Point_Scene::runtime_statistics() const noexcept {
|
||||
return impl_->model->runtime_statistics();
|
||||
}
|
||||
|
||||
Scene_Base& Point_Scene::render_scene() noexcept {
|
||||
return impl_->model->scene();
|
||||
}
|
||||
|
||||
@@ -68,6 +68,21 @@ struct Pixel_Frame {
|
||||
std::vector<std::byte> rgba8;
|
||||
};
|
||||
|
||||
struct Runtime_Admission_Statistics {
|
||||
std::size_t capacity{};
|
||||
std::size_t active{};
|
||||
std::size_t peak_active{};
|
||||
std::size_t queued{};
|
||||
std::size_t peak_queued{};
|
||||
std::uint64_t backpressure_count{};
|
||||
std::uint64_t backpressure_wait_ns{};
|
||||
};
|
||||
|
||||
struct Runtime_Statistics {
|
||||
Runtime_Admission_Statistics render_domain;
|
||||
Runtime_Admission_Statistics gpu_completion;
|
||||
};
|
||||
|
||||
struct Frame_Status {
|
||||
Frame_Mode mode{Frame_Mode::Low_Latency};
|
||||
double frequency_hz{};
|
||||
@@ -123,6 +138,7 @@ public:
|
||||
|
||||
[[nodiscard]] std::shared_ptr<const Pixel_Frame> latest_frame() const;
|
||||
[[nodiscard]] Frame_Status frame_status() const;
|
||||
[[nodiscard]] Runtime_Statistics runtime_statistics() const noexcept;
|
||||
[[nodiscard]] Scene_Base& render_scene() noexcept;
|
||||
[[nodiscard]] const Scene_Base& render_scene() const noexcept;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "Gpu_Completion_Service.h"
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -28,6 +27,7 @@ void Gpu_Completion_Service::Reservation::watch(VkDevice device, VkFence fence)
|
||||
if (!pending_ || device == VK_NULL_HANDLE || fence == VK_NULL_HANDLE)
|
||||
std::terminate();
|
||||
auto pending = std::exchange(pending_, {});
|
||||
auto* const service = pending->service;
|
||||
{
|
||||
std::lock_guard lock(pending->mutex);
|
||||
if (pending->status != Pending_Fence::Status::reserved)
|
||||
@@ -36,9 +36,11 @@ void Gpu_Completion_Service::Reservation::watch(VkDevice device, VkFence fence)
|
||||
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;
|
||||
update_peak(service->peak_watched_, watched);
|
||||
pending->status = Pending_Fence::Status::watched;
|
||||
}
|
||||
pending->service->wake();
|
||||
service->wake();
|
||||
}
|
||||
void Gpu_Completion_Service::Reservation::cancel() noexcept {
|
||||
if (!pending_)
|
||||
@@ -47,6 +49,10 @@ 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 {
|
||||
std::size_t current = peak.load(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 {
|
||||
if (!pending)
|
||||
return;
|
||||
@@ -54,6 +60,23 @@ void Gpu_Completion_Service::cancel_reserved(const std::shared_ptr<Pending_Fence
|
||||
if (pending->status == Pending_Fence::Status::reserved)
|
||||
pending->status = Pending_Fence::Status::canceled;
|
||||
}
|
||||
void Gpu_Completion_Service::acquire_slot() {
|
||||
const auto started = std::chrono::steady_clock::now();
|
||||
if (!slots_.try_acquire()) {
|
||||
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();
|
||||
if (waited > 0)
|
||||
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;
|
||||
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) {
|
||||
if (!completion)
|
||||
throw std::invalid_argument("GPU completion callback is empty");
|
||||
@@ -63,27 +86,39 @@ Gpu_Completion_Service::Reservation Gpu_Completion_Service::prepare(Completion c
|
||||
pending->completion = std::move(completion);
|
||||
pending->observe = observe;
|
||||
pending->service = this;
|
||||
slots_.acquire();
|
||||
try {
|
||||
pending_.push(pending);
|
||||
} catch (...) {
|
||||
slots_.release();
|
||||
throw;
|
||||
acquire_slot();
|
||||
if (!pending_.try_push(pending)) {
|
||||
release_slot();
|
||||
throw std::logic_error("GPU completion admission invariant violated");
|
||||
}
|
||||
wake();
|
||||
return Reservation(std::move(pending));
|
||||
}
|
||||
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),
|
||||
peak_in_flight_.load(std::memory_order_relaxed),
|
||||
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)
|
||||
};
|
||||
}
|
||||
void Gpu_Completion_Service::wake() noexcept {
|
||||
wake_.notify_one();
|
||||
wake_generation_.fetch_add(1, std::memory_order_release);
|
||||
wake_condition_.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 (;;) {
|
||||
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));
|
||||
bool progressed{};
|
||||
bool has_watched{};
|
||||
for (auto iterator = active.begin(); iterator != active.end();) {
|
||||
auto& pending = *iterator;
|
||||
VkDevice device{VK_NULL_HANDLE};
|
||||
@@ -104,7 +139,7 @@ void Gpu_Completion_Service::run() noexcept {
|
||||
}
|
||||
if (status == Pending_Fence::Status::canceled) {
|
||||
iterator = active.erase(iterator);
|
||||
slots_.release();
|
||||
release_slot();
|
||||
progressed = true;
|
||||
continue;
|
||||
}
|
||||
@@ -112,13 +147,14 @@ void Gpu_Completion_Service::run() noexcept {
|
||||
if (stopping_.load(std::memory_order_acquire)) {
|
||||
cancel_reserved(pending);
|
||||
iterator = active.erase(iterator);
|
||||
slots_.release();
|
||||
release_slot();
|
||||
progressed = true;
|
||||
continue;
|
||||
}
|
||||
++iterator;
|
||||
continue;
|
||||
}
|
||||
has_watched = true;
|
||||
const VkResult result = vkGetFenceStatus(device, fence);
|
||||
if (result == VK_NOT_READY) {
|
||||
++iterator;
|
||||
@@ -129,9 +165,11 @@ void Gpu_Completion_Service::run() noexcept {
|
||||
completion = std::move(pending->completion);
|
||||
pending->status = Pending_Fence::Status::canceled;
|
||||
}
|
||||
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();
|
||||
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) {
|
||||
@@ -146,19 +184,24 @@ void Gpu_Completion_Service::run() noexcept {
|
||||
} catch (...) {
|
||||
}
|
||||
iterator = active.erase(iterator);
|
||||
slots_.release();
|
||||
release_slot();
|
||||
progressed = true;
|
||||
}
|
||||
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);
|
||||
if (progressed)
|
||||
continue;
|
||||
std::unique_lock lock(wait_mutex_);
|
||||
if (wake_generation_.load(std::memory_order_acquire) != wake_generation)
|
||||
continue;
|
||||
if (has_watched) {
|
||||
wake_condition_.wait_for(lock, poll_interval, [this, wake_generation] {
|
||||
return wake_generation_.load(std::memory_order_acquire) != wake_generation;
|
||||
});
|
||||
} else {
|
||||
wake_condition_.wait(lock, [this, wake_generation] {
|
||||
return wake_generation_.load(std::memory_order_acquire) != wake_generation;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,15 @@ public:
|
||||
std::exception_ptr error;
|
||||
std::uint64_t wait_duration_ns{};
|
||||
};
|
||||
struct Statistics {
|
||||
std::size_t capacity{};
|
||||
std::size_t in_flight{};
|
||||
std::size_t peak_in_flight{};
|
||||
std::size_t watched{};
|
||||
std::size_t peak_watched{};
|
||||
std::uint64_t backpressure_count{};
|
||||
std::uint64_t backpressure_wait_ns{};
|
||||
};
|
||||
using Completion = std::function<void(Result)>;
|
||||
class Reservation final {
|
||||
public:
|
||||
@@ -40,6 +49,7 @@ public:
|
||||
Gpu_Completion_Service(const Gpu_Completion_Service&) = delete;
|
||||
Gpu_Completion_Service& operator=(const Gpu_Completion_Service&) = delete;
|
||||
[[nodiscard]] Reservation prepare(Completion completion, bool observe);
|
||||
[[nodiscard]] Statistics statistics() const noexcept;
|
||||
private:
|
||||
struct Pending_Fence {
|
||||
enum class Status {
|
||||
@@ -58,7 +68,10 @@ private:
|
||||
};
|
||||
Gpu_Completion_Service();
|
||||
~Gpu_Completion_Service();
|
||||
static void update_peak(std::atomic_size_t& peak, std::size_t value) noexcept;
|
||||
static void cancel_reserved(const std::shared_ptr<Pending_Fence>& pending) noexcept;
|
||||
void acquire_slot();
|
||||
void release_slot() noexcept;
|
||||
void wake() noexcept;
|
||||
void run() noexcept;
|
||||
static constexpr std::ptrdiff_t default_capacity = 1024;
|
||||
@@ -66,7 +79,14 @@ private:
|
||||
std::counting_semaphore<default_capacity> slots_{default_capacity};
|
||||
oneapi::tbb::concurrent_bounded_queue<std::shared_ptr<Pending_Fence>> pending_;
|
||||
std::mutex wait_mutex_;
|
||||
std::condition_variable wake_;
|
||||
std::condition_variable wake_condition_;
|
||||
std::atomic_uint64_t wake_generation_{};
|
||||
std::atomic_size_t in_flight_{};
|
||||
std::atomic_size_t peak_in_flight_{};
|
||||
std::atomic_size_t watched_{};
|
||||
std::atomic_size_t peak_watched_{};
|
||||
std::atomic_uint64_t backpressure_count_{};
|
||||
std::atomic_uint64_t backpressure_wait_ns_{};
|
||||
std::atomic_bool stopping_{};
|
||||
std::thread thread_;
|
||||
};
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#include "Render_Domain.h"
|
||||
#include <mutex>
|
||||
#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;
|
||||
std::unordered_map<std::uint32_t, std::weak_ptr<Render_Domain>> domains;
|
||||
};
|
||||
Render_Domain_Registry& registry() {
|
||||
static Render_Domain_Registry value;
|
||||
@@ -15,12 +16,12 @@ 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_)) {}
|
||||
: domain_(std::move(other.domain_)), 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);
|
||||
domain_ = std::move(other.domain_);
|
||||
task_ = std::move(other.task_);
|
||||
return *this;
|
||||
}
|
||||
@@ -28,16 +29,17 @@ void Render_Domain::Prepared_Task::release() noexcept {
|
||||
if (!domain_)
|
||||
return;
|
||||
task_.reset();
|
||||
domain_->slots_.release();
|
||||
domain_ = nullptr;
|
||||
domain_->release_admission();
|
||||
domain_.reset();
|
||||
}
|
||||
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& entry = storage.domains[gpu_index];
|
||||
if (auto domain = entry.lock())
|
||||
return domain;
|
||||
auto domain = std::shared_ptr<Render_Domain>(new Render_Domain());
|
||||
storage.domains.emplace(gpu_index, domain);
|
||||
entry = domain;
|
||||
return domain;
|
||||
}
|
||||
Render_Domain::Render_Domain() {
|
||||
@@ -45,36 +47,82 @@ Render_Domain::Render_Domain() {
|
||||
thread_ = std::thread([this] { run(); });
|
||||
}
|
||||
Render_Domain::~Render_Domain() {
|
||||
{
|
||||
std::lock_guard lock(mutex_);
|
||||
stopping_ = true;
|
||||
}
|
||||
stopping_.store(true, std::memory_order_release);
|
||||
slots_.acquire();
|
||||
if (!tasks_.try_push(std::unique_ptr<Task>{}))
|
||||
std::terminate();
|
||||
if (thread_.joinable())
|
||||
thread_.join();
|
||||
}
|
||||
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);
|
||||
while (current < value && !peak.compare_exchange_weak(current, value, std::memory_order_relaxed)) {}
|
||||
}
|
||||
void Render_Domain::acquire_admission() {
|
||||
const auto started = std::chrono::steady_clock::now();
|
||||
if (!slots_.try_acquire()) {
|
||||
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();
|
||||
if (waited > 0)
|
||||
backpressure_wait_ns_.fetch_add(static_cast<std::uint64_t>(waited), std::memory_order_relaxed);
|
||||
}
|
||||
const std::size_t admitted = admitted_.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
update_peak(peak_admitted_, admitted);
|
||||
}
|
||||
void Render_Domain::release_admission() noexcept {
|
||||
admitted_.fetch_sub(1, std::memory_order_relaxed);
|
||||
slots_.release();
|
||||
}
|
||||
Render_Domain::Prepared_Task Render_Domain::prepare(std::function<void()> function) {
|
||||
if (!function)
|
||||
throw std::invalid_argument("render domain task is empty");
|
||||
if (stopping_.load(std::memory_order_acquire))
|
||||
throw std::runtime_error("render domain is stopping");
|
||||
acquire_admission();
|
||||
try {
|
||||
return Prepared_Task(shared_from_this(), std::make_unique<Task>(std::move(function)));
|
||||
} catch (...) {
|
||||
release_admission();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
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_)
|
||||
if (!task.task_ || task.domain_.get() != this || stopping_.load(std::memory_order_acquire))
|
||||
std::terminate();
|
||||
if (!tasks_.try_push(std::move(task.task_)))
|
||||
std::terminate();
|
||||
task.domain_ = nullptr;
|
||||
const std::size_t queued = queued_.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
update_peak(peak_queued_, queued);
|
||||
task.domain_.reset();
|
||||
}
|
||||
Render_Domain::Statistics Render_Domain::statistics() const noexcept {
|
||||
return {
|
||||
static_cast<std::size_t>(default_capacity),
|
||||
admitted_.load(std::memory_order_relaxed),
|
||||
peak_admitted_.load(std::memory_order_relaxed),
|
||||
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)
|
||||
};
|
||||
}
|
||||
void Render_Domain::run() {
|
||||
current_domain_ = this;
|
||||
for (;;) {
|
||||
std::unique_ptr<Task> task;
|
||||
tasks_.pop(task);
|
||||
slots_.release();
|
||||
if (!task)
|
||||
if (!task) {
|
||||
slots_.release();
|
||||
current_domain_ = nullptr;
|
||||
return;
|
||||
}
|
||||
queued_.fetch_sub(1, std::memory_order_relaxed);
|
||||
release_admission();
|
||||
task->function();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#pragma once
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <semaphore>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
@@ -11,12 +13,21 @@
|
||||
#include <utility>
|
||||
#include <oneapi/tbb/concurrent_queue.h>
|
||||
namespace renderive::render_3d::detail {
|
||||
class Render_Domain final {
|
||||
class Render_Domain final : public std::enable_shared_from_this<Render_Domain> {
|
||||
struct Task {
|
||||
explicit Task(std::function<void()> value) : function(std::move(value)) {}
|
||||
std::function<void()> function;
|
||||
};
|
||||
public:
|
||||
struct Statistics {
|
||||
std::size_t capacity{};
|
||||
std::size_t admitted{};
|
||||
std::size_t peak_admitted{};
|
||||
std::size_t queued{};
|
||||
std::size_t peak_queued{};
|
||||
std::uint64_t backpressure_count{};
|
||||
std::uint64_t backpressure_wait_ns{};
|
||||
};
|
||||
class Prepared_Task final {
|
||||
public:
|
||||
Prepared_Task() = default;
|
||||
@@ -26,10 +37,10 @@ public:
|
||||
Prepared_Task(Prepared_Task&& other) noexcept;
|
||||
Prepared_Task& operator=(Prepared_Task&& other) noexcept;
|
||||
private:
|
||||
Prepared_Task(Render_Domain* domain, std::unique_ptr<Task> task) noexcept
|
||||
: domain_(domain), task_(std::move(task)) {}
|
||||
Prepared_Task(std::shared_ptr<Render_Domain> domain, std::unique_ptr<Task> task) noexcept
|
||||
: domain_(std::move(domain)), task_(std::move(task)) {}
|
||||
void release() noexcept;
|
||||
Render_Domain* domain_{};
|
||||
std::shared_ptr<Render_Domain> domain_;
|
||||
std::unique_ptr<Task> task_;
|
||||
friend class Render_Domain;
|
||||
};
|
||||
@@ -37,22 +48,20 @@ public:
|
||||
~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");
|
||||
slots_.acquire();
|
||||
try {
|
||||
return Prepared_Task(this, std::make_unique<Task>(std::move(function)));
|
||||
} catch (...) {
|
||||
slots_.release();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
[[nodiscard]] Prepared_Task prepare(std::function<void()> function);
|
||||
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>;
|
||||
if (current_domain_ == this) {
|
||||
if constexpr (std::is_void_v<Result>) {
|
||||
std::invoke(std::forward<Function>(function));
|
||||
return;
|
||||
} else {
|
||||
return std::invoke(std::forward<Function>(function));
|
||||
}
|
||||
}
|
||||
auto task = std::make_shared<std::packaged_task<Result()>>(std::forward<Function>(function));
|
||||
auto result = task->get_future();
|
||||
post([task] { (*task)(); });
|
||||
@@ -61,14 +70,24 @@ public:
|
||||
else
|
||||
return result.get();
|
||||
}
|
||||
[[nodiscard]] Statistics statistics() const noexcept;
|
||||
private:
|
||||
Render_Domain();
|
||||
static void update_peak(std::atomic_size_t& peak, std::size_t value) noexcept;
|
||||
void acquire_admission();
|
||||
void release_admission() noexcept;
|
||||
void run();
|
||||
static constexpr std::ptrdiff_t default_capacity = 64;
|
||||
inline static thread_local Render_Domain* current_domain_{};
|
||||
std::counting_semaphore<default_capacity> slots_{default_capacity};
|
||||
oneapi::tbb::concurrent_bounded_queue<std::unique_ptr<Task>> tasks_;
|
||||
std::mutex mutex_;
|
||||
bool stopping_{};
|
||||
std::atomic_size_t admitted_{};
|
||||
std::atomic_size_t peak_admitted_{};
|
||||
std::atomic_size_t queued_{};
|
||||
std::atomic_size_t peak_queued_{};
|
||||
std::atomic_uint64_t backpressure_count_{};
|
||||
std::atomic_uint64_t backpressure_wait_ns_{};
|
||||
std::atomic_bool stopping_{};
|
||||
std::thread thread_;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#include "render_3D/detail/Gpu_Completion_Service.h"
|
||||
#include "render_3D/detail/Render_Domain.h"
|
||||
#include <gtest/gtest.h>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#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, UsesOneProcessWideService) {
|
||||
EXPECT_EQ(&Gpu_Completion_Service::instance(), &Gpu_Completion_Service::instance());
|
||||
}
|
||||
@@ -20,29 +20,18 @@ TEST(GpuCompletionService, AbandonedReservationDoesNotComplete) {
|
||||
}
|
||||
EXPECT_EQ(completion_count.load(std::memory_order_relaxed), 0);
|
||||
}
|
||||
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(RenderDomain, PreparedTaskRunsAfterNoThrowHandoff) {
|
||||
auto domain = Render_Domain::acquire(0);
|
||||
std::atomic<bool> executed{};
|
||||
auto task = domain->prepare([&] {
|
||||
executed.store(true, std::memory_order_release);
|
||||
});
|
||||
domain->post(std::move(task));
|
||||
domain->invoke([] {});
|
||||
EXPECT_TRUE(executed.load(std::memory_order_acquire));
|
||||
}
|
||||
TEST(RenderDomain, AbandonedPreparedTasksReleaseReservedCapacity) {
|
||||
auto domain = Render_Domain::acquire(2);
|
||||
for (std::size_t index = 0; index < 128; ++index) {
|
||||
auto task = domain->prepare([] {});
|
||||
TEST(GpuCompletionService, CanceledReservationWakesIdleService) {
|
||||
auto& service = Gpu_Completion_Service::instance();
|
||||
const auto baseline = service.statistics().in_flight;
|
||||
{
|
||||
auto reservation = service.prepare([](Gpu_Completion_Service::Result) {}, false);
|
||||
EXPECT_EQ(service.statistics().in_flight, baseline + 1);
|
||||
}
|
||||
domain->invoke([] {});
|
||||
const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(100);
|
||||
while (service.statistics().in_flight != baseline &&
|
||||
std::chrono::steady_clock::now() < deadline)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
EXPECT_EQ(service.statistics().in_flight, baseline);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#include "render_3D/detail/Render_Domain.h"
|
||||
#include <gtest/gtest.h>
|
||||
#include <atomic>
|
||||
#include <utility>
|
||||
namespace renderive::render_3d::detail {
|
||||
namespace {
|
||||
static_assert(noexcept(std::declval<Render_Domain&>().post(std::declval<Render_Domain::Prepared_Task>())));
|
||||
TEST(RenderDomain, SharesDomainPerGpuIndex) {
|
||||
auto first = Render_Domain::acquire(0x7ffffffcU);
|
||||
auto second = Render_Domain::acquire(0x7ffffffcU);
|
||||
auto other = Render_Domain::acquire(0x7ffffffbU);
|
||||
EXPECT_EQ(first, second);
|
||||
EXPECT_NE(first, other);
|
||||
}
|
||||
TEST(RenderDomain, PreparedTaskRunsAfterNoThrowHandoff) {
|
||||
auto domain = Render_Domain::acquire(0x7ffffffaU);
|
||||
std::atomic<bool> executed{};
|
||||
auto task = domain->prepare([&] {
|
||||
executed.store(true, std::memory_order_release);
|
||||
});
|
||||
domain->post(std::move(task));
|
||||
domain->invoke([] {});
|
||||
EXPECT_TRUE(executed.load(std::memory_order_acquire));
|
||||
}
|
||||
TEST(RenderDomain, AbandonedPreparedTasksReleaseReservedCapacity) {
|
||||
auto domain = Render_Domain::acquire(0x7ffffff9U);
|
||||
for (std::size_t index = 0; index < 128; ++index) {
|
||||
auto task = domain->prepare([] {});
|
||||
}
|
||||
domain->invoke([] {});
|
||||
}
|
||||
TEST(RenderDomain, NestedInvokeExecutesInlineOnTheAffinityThread) {
|
||||
auto domain = Render_Domain::acquire(0x7ffffff8U);
|
||||
const int value = domain->invoke([domain] {
|
||||
return domain->invoke([] { return 42; });
|
||||
});
|
||||
EXPECT_EQ(value, 42);
|
||||
}
|
||||
TEST(RenderDomain, ReportsBoundedAdmissionStatistics) {
|
||||
auto domain = Render_Domain::acquire(0x7ffffff7U);
|
||||
const auto statistics = domain->statistics();
|
||||
EXPECT_EQ(statistics.capacity, 64U);
|
||||
EXPECT_TRUE(statistics.admitted <= statistics.capacity);
|
||||
EXPECT_TRUE(statistics.queued <= statistics.capacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -348,7 +348,7 @@ inline nlohmann::json gallery_performance_capture_json(const Scene_Base& scene)
|
||||
using namespace gallery_capture_detail;
|
||||
const auto controller = scene.capture_state();
|
||||
Json sessions = Json::array();
|
||||
std::set<Render_Plan_Version> referenced_plans;
|
||||
std::map<Render_Plan_Version, std::shared_ptr<const Render_Plan>> referenced_plans;
|
||||
for (Capture_Session_Id id : scene.capture_sessions()) {
|
||||
const auto session = scene.capture_session(id);
|
||||
if (!session)
|
||||
@@ -356,7 +356,7 @@ inline nlohmann::json gallery_performance_capture_json(const Scene_Base& scene)
|
||||
Json frames = Json::array();
|
||||
for (const auto& frame : session->frames) {
|
||||
frames.push_back(frame_json(frame));
|
||||
referenced_plans.insert(frame.snapshot->render_plan_version);
|
||||
referenced_plans[frame.snapshot->render_plan_version] = frame.render_plan;
|
||||
}
|
||||
Json node_statistics = Json::array();
|
||||
for (const auto& node : scene.node_statistics(id))
|
||||
@@ -376,11 +376,11 @@ inline nlohmann::json gallery_performance_capture_json(const Scene_Base& scene)
|
||||
});
|
||||
}
|
||||
if (const auto current = scene.render_plan_snapshot())
|
||||
referenced_plans.insert(current->version);
|
||||
referenced_plans[current->version] = current;
|
||||
Json plans = Json::array();
|
||||
std::shared_ptr<const Render_Plan> previous;
|
||||
for (Render_Plan_Version version : referenced_plans) {
|
||||
const auto plan = scene.find_render_plan(version);
|
||||
for (const auto& [version, captured_plan] : referenced_plans) {
|
||||
auto plan = captured_plan ? captured_plan : scene.find_render_plan(version);
|
||||
if (!plan)
|
||||
continue;
|
||||
Json value = plan_json(scene, *plan);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "common/Gallery_Scene_Interface.h"
|
||||
#include "render_2D/Gallery_Scene2D.h"
|
||||
#include "render_3D/Gallery_Scene3D.h"
|
||||
#include <renderive/scheduling/Scheduler.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
@@ -10,12 +11,13 @@
|
||||
#include <cmath>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
namespace renderive::web {
|
||||
@@ -123,26 +125,84 @@ std::unique_ptr<Gallery_Scene_Interface> make_gallery_scene(std::uint64_t sessio
|
||||
return make_gallery_scene_2d(session_id, std::move(case_id), mode, automatic_low_latency);
|
||||
}
|
||||
}
|
||||
struct Gallery_Plot_Session::Impl {
|
||||
struct Gallery_Plot_Session::Impl : std::enable_shared_from_this<Gallery_Plot_Session::Impl> {
|
||||
using Clock = std::chrono::steady_clock;
|
||||
class Automatic_Render_Scheduler final {
|
||||
public:
|
||||
static Automatic_Render_Scheduler& instance() {
|
||||
static Automatic_Render_Scheduler scheduler;
|
||||
return scheduler;
|
||||
}
|
||||
void arm(std::uint64_t session_id, std::weak_ptr<Impl> session, Clock::time_point deadline) {
|
||||
std::lock_guard lock(mutex_);
|
||||
if (const auto current = entries_.find(session_id); current != entries_.end()) {
|
||||
schedule_.erase(current->second);
|
||||
entries_.erase(current);
|
||||
}
|
||||
const auto iterator = schedule_.emplace(deadline, Entry{session_id, std::move(session)});
|
||||
entries_.emplace(session_id, iterator);
|
||||
condition_.notify_one();
|
||||
}
|
||||
void disarm(std::uint64_t session_id) noexcept {
|
||||
std::lock_guard lock(mutex_);
|
||||
const auto current = entries_.find(session_id);
|
||||
if (current == entries_.end())
|
||||
return;
|
||||
schedule_.erase(current->second);
|
||||
entries_.erase(current);
|
||||
condition_.notify_one();
|
||||
}
|
||||
private:
|
||||
struct Entry {
|
||||
std::uint64_t session_id{};
|
||||
std::weak_ptr<Impl> session;
|
||||
};
|
||||
using Schedule = std::multimap<Clock::time_point, Entry>;
|
||||
Automatic_Render_Scheduler() : thread_([this](std::stop_token stop) { run(stop); }) {}
|
||||
~Automatic_Render_Scheduler() {
|
||||
thread_.request_stop();
|
||||
condition_.notify_all();
|
||||
}
|
||||
void run(std::stop_token stop) noexcept {
|
||||
std::unique_lock lock(mutex_);
|
||||
while (!stop.stop_requested()) {
|
||||
if (schedule_.empty()) {
|
||||
condition_.wait(lock, [this, &stop] {
|
||||
return stop.stop_requested() || !schedule_.empty();
|
||||
});
|
||||
continue;
|
||||
}
|
||||
const auto deadline = schedule_.begin()->first;
|
||||
if (condition_.wait_until(lock, deadline) != std::cv_status::timeout)
|
||||
continue;
|
||||
if (schedule_.empty() || schedule_.begin()->first > Clock::now())
|
||||
continue;
|
||||
auto iterator = schedule_.begin();
|
||||
Entry entry = std::move(iterator->second);
|
||||
entries_.erase(entry.session_id);
|
||||
schedule_.erase(iterator);
|
||||
lock.unlock();
|
||||
if (auto session = entry.session.lock())
|
||||
session->automatic_render_due();
|
||||
lock.lock();
|
||||
}
|
||||
}
|
||||
std::mutex mutex_;
|
||||
std::condition_variable condition_;
|
||||
Schedule schedule_;
|
||||
std::unordered_map<std::uint64_t, Schedule::iterator> entries_;
|
||||
std::jthread thread_;
|
||||
};
|
||||
explicit Impl(bool enable_automatic_low_latency)
|
||||
: automatic_low_latency(enable_automatic_low_latency),
|
||||
session_id(next_session_id()) {}
|
||||
: automatic_low_latency(enable_automatic_low_latency), session_id(next_session_id()) {}
|
||||
~Impl() {
|
||||
render_worker.request_stop();
|
||||
scheduler_condition.notify_all();
|
||||
if (automatic_low_latency)
|
||||
Automatic_Render_Scheduler::instance().disarm(session_id);
|
||||
}
|
||||
static std::uint64_t next_session_id() noexcept {
|
||||
static std::atomic<std::uint64_t> next{1};
|
||||
return next.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
void ensure_render_worker() {
|
||||
if (!automatic_low_latency || render_worker.joinable())
|
||||
return;
|
||||
render_worker = std::jthread(
|
||||
[this](std::stop_token stop) {
|
||||
run_automatic_renderer(stop);
|
||||
});
|
||||
}
|
||||
bool update_client_metrics(std::string_view message) {
|
||||
if (!scene)
|
||||
return false;
|
||||
@@ -204,57 +264,59 @@ struct Gallery_Plot_Session::Impl {
|
||||
return true;
|
||||
}
|
||||
[[nodiscard]] std::unique_lock<std::mutex> acquire_foreground_lock() {
|
||||
foreground_waiters.fetch_add(1, std::memory_order_release);
|
||||
scheduler_condition.notify_all();
|
||||
std::unique_lock lock(mutex);
|
||||
foreground_waiters.fetch_sub(1, std::memory_order_release);
|
||||
scheduler_condition.notify_all();
|
||||
return lock;
|
||||
return std::unique_lock<std::mutex>(mutex);
|
||||
}
|
||||
void run_automatic_renderer(std::stop_token stop) {
|
||||
using Clock = std::chrono::steady_clock;
|
||||
std::unique_lock lock(mutex);
|
||||
std::optional<Clock::time_point> last_render_started;
|
||||
std::uint64_t active_generation = std::numeric_limits<std::uint64_t>::max();
|
||||
while (!stop.stop_requested()) {
|
||||
scheduler_condition.wait(lock, [this, &stop] {
|
||||
return stop.stop_requested() || (scene && scene->can_render_automatically());
|
||||
void arm_automatic_render_locked() noexcept {
|
||||
if (!automatic_low_latency)
|
||||
return;
|
||||
auto& scheduler = Automatic_Render_Scheduler::instance();
|
||||
if (!scene || !scene->can_render_automatically() || render_task_pending) {
|
||||
scheduler.disarm(session_id);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const auto interval = std::chrono::nanoseconds(
|
||||
std::max<std::uint64_t>(1, scene->kernel_refresh_interval_ns()));
|
||||
const auto deadline = last_render_started ? *last_render_started + interval : Clock::now();
|
||||
scheduler.arm(session_id, weak_from_this(), deadline);
|
||||
} catch (...) {
|
||||
scheduler.disarm(session_id);
|
||||
}
|
||||
}
|
||||
void automatic_render_due() {
|
||||
std::uint64_t generation{};
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
if (!automatic_low_latency || !scene || !scene->can_render_automatically() || render_task_pending)
|
||||
return;
|
||||
render_task_pending = true;
|
||||
generation = scene_generation;
|
||||
}
|
||||
auto self = shared_from_this();
|
||||
try {
|
||||
renderive::scheduling::enqueue_task([self = std::move(self), generation] {
|
||||
self->run_automatic_render(generation);
|
||||
});
|
||||
if (stop.stop_requested())
|
||||
break;
|
||||
if (active_generation != scene_generation) {
|
||||
active_generation = scene_generation;
|
||||
last_render_started.reset();
|
||||
} catch (...) {
|
||||
std::lock_guard lock(mutex);
|
||||
render_task_pending = false;
|
||||
arm_automatic_render_locked();
|
||||
}
|
||||
}
|
||||
void run_automatic_render(std::uint64_t generation) noexcept {
|
||||
const auto started = Clock::now();
|
||||
try {
|
||||
std::lock_guard lock(mutex);
|
||||
if (generation == scene_generation && scene && scene->can_render_automatically()) {
|
||||
(void)scene->render_latest_frame();
|
||||
last_render_started = started;
|
||||
}
|
||||
if (foreground_waiters.load(std::memory_order_acquire) != 0) {
|
||||
scheduler_condition.wait(lock, [this, &stop] {
|
||||
return stop.stop_requested() || foreground_waiters.load(std::memory_order_acquire) == 0;
|
||||
});
|
||||
continue;
|
||||
}
|
||||
const auto revision = scheduler_revision;
|
||||
const auto interval = std::chrono::nanoseconds(std::max<std::uint64_t>(1, scene->kernel_refresh_interval_ns()));
|
||||
const auto now = Clock::now();
|
||||
const auto deadline = last_render_started ? *last_render_started + interval : now;
|
||||
if (now < deadline) {
|
||||
const auto spin_window = std::min(interval / 4, std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::microseconds(250)));
|
||||
const auto coarse_deadline = deadline > now + spin_window ? deadline - spin_window : now;
|
||||
if (now < coarse_deadline) {
|
||||
scheduler_condition.wait_until(lock, coarse_deadline, [this, &stop, revision, active_generation] {
|
||||
return stop.stop_requested() || scheduler_revision != revision || scene_generation != active_generation ||
|
||||
foreground_waiters.load(std::memory_order_acquire) != 0 || !scene || !scene->can_render_automatically();
|
||||
});
|
||||
continue;
|
||||
}
|
||||
lock.unlock();
|
||||
while (!stop.stop_requested() && Clock::now() < deadline)
|
||||
std::this_thread::yield();
|
||||
lock.lock();
|
||||
continue;
|
||||
}
|
||||
const auto started = Clock::now();
|
||||
(void)scene->render_latest_frame();
|
||||
last_render_started = started;
|
||||
render_task_pending = false;
|
||||
arm_automatic_render_locked();
|
||||
} catch (...) {
|
||||
std::lock_guard lock(mutex);
|
||||
render_task_pending = false;
|
||||
arm_automatic_render_locked();
|
||||
}
|
||||
}
|
||||
static bool affects_render_schedule(const Web_Event& event) {
|
||||
@@ -278,13 +340,11 @@ struct Gallery_Plot_Session::Impl {
|
||||
}
|
||||
std::unique_ptr<Gallery_Scene_Interface> scene;
|
||||
std::mutex mutex;
|
||||
std::condition_variable scheduler_condition;
|
||||
std::uint64_t scheduler_revision{};
|
||||
std::optional<Clock::time_point> last_render_started;
|
||||
std::uint64_t scene_generation{};
|
||||
bool automatic_low_latency{};
|
||||
bool render_task_pending{};
|
||||
std::uint64_t session_id{};
|
||||
std::atomic<std::uint32_t> foreground_waiters{};
|
||||
std::jthread render_worker;
|
||||
std::optional<Web_Response> handle_gallery(const Gallery_Request& request) {
|
||||
if (request.kind == Gallery_Request_Kind::Catalog)
|
||||
return Web_Response{Web_Response_Type::Json, Gallery_Protocol::catalog_json()};
|
||||
@@ -297,8 +357,8 @@ struct Gallery_Plot_Session::Impl {
|
||||
};
|
||||
scene = make_gallery_scene(session_id, open->case_id, open->frame_mode, automatic_low_latency);
|
||||
++scene_generation;
|
||||
if (scene->frame_mode() == Gallery_Frame_Mode::Low_Latency)
|
||||
ensure_render_worker();
|
||||
last_render_started.reset();
|
||||
arm_automatic_render_locked();
|
||||
return Web_Response{
|
||||
Web_Response_Type::Json,
|
||||
Gallery_Protocol::case_json_from_controls(scene->case_id(), scene->controls().dump(),
|
||||
@@ -313,10 +373,8 @@ struct Gallery_Plot_Session::Impl {
|
||||
Gallery_Protocol::error_json("请先发送 gallery_open")
|
||||
};
|
||||
if (request.kind == Gallery_Request_Kind::Observe) {
|
||||
if (update_client_metrics(request.message)) {
|
||||
++scheduler_revision;
|
||||
scheduler_condition.notify_all();
|
||||
}
|
||||
if (update_client_metrics(request.message))
|
||||
arm_automatic_render_locked();
|
||||
return Web_Response{
|
||||
Web_Response_Type::Json,
|
||||
Gallery_Protocol::observer_json(
|
||||
@@ -324,10 +382,8 @@ struct Gallery_Plot_Session::Impl {
|
||||
};
|
||||
}
|
||||
if (request.kind == Gallery_Request_Kind::Refresh) {
|
||||
if (update_client_metrics(request.message)) {
|
||||
++scheduler_revision;
|
||||
scheduler_condition.notify_all();
|
||||
}
|
||||
if (update_client_metrics(request.message))
|
||||
arm_automatic_render_locked();
|
||||
return Web_Response{
|
||||
Web_Response_Type::Json,
|
||||
Gallery_Protocol::case_json_from_controls(
|
||||
@@ -382,6 +438,8 @@ struct Gallery_Plot_Session::Impl {
|
||||
const auto mode = scene->frame_mode();
|
||||
scene = make_gallery_scene(session_id, id, mode, automatic_low_latency);
|
||||
++scene_generation;
|
||||
last_render_started.reset();
|
||||
arm_automatic_render_locked();
|
||||
return Web_Response{
|
||||
Web_Response_Type::Json,
|
||||
Gallery_Protocol::case_json_from_controls(id, scene->controls().dump(),
|
||||
@@ -455,16 +513,14 @@ struct Gallery_Plot_Session::Impl {
|
||||
},
|
||||
event);
|
||||
if (reschedule)
|
||||
++scheduler_revision;
|
||||
arm_automatic_render_locked();
|
||||
}
|
||||
if (reschedule)
|
||||
scheduler_condition.notify_all();
|
||||
return response;
|
||||
}
|
||||
};
|
||||
Gallery_Plot_Session::Gallery_Plot_Session() : Gallery_Plot_Session(false) {}
|
||||
Gallery_Plot_Session::Gallery_Plot_Session(bool automatic_low_latency)
|
||||
: impl_(std::make_unique<Impl>(automatic_low_latency)) {}
|
||||
: impl_(std::make_shared<Impl>(automatic_low_latency)) {}
|
||||
Gallery_Plot_Session::~Gallery_Plot_Session() = default;
|
||||
std::optional<Web_Response> Gallery_Plot_Session::handle(const Web_Event& event) {
|
||||
return impl_->handle(event);
|
||||
|
||||
@@ -19,7 +19,7 @@ public:
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
std::shared_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
} // namespace renderive::web
|
||||
|
||||
@@ -630,6 +630,7 @@ public:
|
||||
render_performance.automatic_low_latency_scheduler =
|
||||
automatic_low_latency_ && frame_mode_ == Gallery_Frame_Mode::Low_Latency;
|
||||
const Gallery_Client_Performance client_performance = client_performance_;
|
||||
const auto scene_execution = plot_.render_scene().execution_statistics();
|
||||
nlohmann::json observer_data =
|
||||
adminive::to_frontend_json<nlohmann::json>(observer);
|
||||
nlohmann::json consumer_feedback_data =
|
||||
@@ -656,6 +657,13 @@ public:
|
||||
{"client_performance", std::move(client_performance_data)},
|
||||
{"scheduler", adminive::to_frontend_json<nlohmann::json>(
|
||||
renderive::scheduling::scheduler_statistics())},
|
||||
{"queue_pressure",
|
||||
{{"scene_execution",
|
||||
{{"capacity", scene_execution.capacity},
|
||||
{"queued", scene_execution.queued},
|
||||
{"peak_queued", scene_execution.peak_queued},
|
||||
{"backpressure_count", scene_execution.backpressure_count},
|
||||
{"backpressure_wait_ns", scene_execution.backpressure_wait_ns}}}}},
|
||||
{"renderable_observers", controls_.observers(plot_.render_scene())},
|
||||
{"performance_capture", gallery_performance_capture_json(plot_.render_scene())},
|
||||
{"last_action_result", last_action_result_}
|
||||
|
||||
@@ -440,6 +440,8 @@ public:
|
||||
[[nodiscard]] std::string telemetry_json() const override {
|
||||
const auto status = scene_.frame_status();
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto scene_execution = scene_.render_scene().execution_statistics();
|
||||
const auto runtime = scene_.runtime_statistics();
|
||||
return nlohmann::json{
|
||||
{"kernel_observer",
|
||||
{{"mode", mode_id(frame_mode_)},
|
||||
@@ -464,6 +466,29 @@ public:
|
||||
client_performance_.websocket_buffered_bytes}}},
|
||||
{"scheduler", adminive::to_frontend_json<nlohmann::json>(
|
||||
renderive::scheduling::scheduler_statistics())},
|
||||
{"queue_pressure",
|
||||
{{"scene_execution",
|
||||
{{"capacity", scene_execution.capacity},
|
||||
{"queued", scene_execution.queued},
|
||||
{"peak_queued", scene_execution.peak_queued},
|
||||
{"backpressure_count", scene_execution.backpressure_count},
|
||||
{"backpressure_wait_ns", scene_execution.backpressure_wait_ns}}},
|
||||
{"render_domain",
|
||||
{{"capacity", runtime.render_domain.capacity},
|
||||
{"active", runtime.render_domain.active},
|
||||
{"peak_active", runtime.render_domain.peak_active},
|
||||
{"queued", runtime.render_domain.queued},
|
||||
{"peak_queued", runtime.render_domain.peak_queued},
|
||||
{"backpressure_count", runtime.render_domain.backpressure_count},
|
||||
{"backpressure_wait_ns", runtime.render_domain.backpressure_wait_ns}}},
|
||||
{"gpu_completion",
|
||||
{{"capacity", runtime.gpu_completion.capacity},
|
||||
{"active", runtime.gpu_completion.active},
|
||||
{"peak_active", runtime.gpu_completion.peak_active},
|
||||
{"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}}}}},
|
||||
{"session_id", session_id_},
|
||||
{"performance_capture",
|
||||
gallery_performance_capture_json(scene_.render_scene())},
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
import {Stack,Typography} from "@mui/material";import type {Gallery_Telemetry} from "../protocol/gallery_types";import {Json_Viewer} from "../common/json_viewer";
|
||||
export function Performance_Panel({telemetry}:{telemetry:Gallery_Telemetry}) {return <Stack spacing={1}><Typography variant="h6">性能滑动窗口</Typography><Json_Viewer value={{performance:telemetry.performance,scheduler:telemetry.scheduler,client_performance:telemetry.client_performance,consumer_feedback:telemetry.consumer_feedback}}/></Stack>;}
|
||||
export function Performance_Panel({telemetry}:{telemetry:Gallery_Telemetry}) {return <Stack spacing={1}><Typography variant="h6">性能滑动窗口</Typography><Json_Viewer value={{performance:telemetry.performance,scheduler:telemetry.scheduler,queue_pressure:telemetry.queue_pressure,client_performance:telemetry.client_performance,consumer_feedback:telemetry.consumer_feedback}}/></Stack>;}
|
||||
|
||||
@@ -26,7 +26,7 @@ export interface Gallery_Performance extends Record<string, Json_Value> {}
|
||||
export interface Gallery_Kernel_Observer extends Record<string, Json_Value> {}
|
||||
export interface Gallery_Scheduler_Statistics {concurrency: number; active_workers: number; peak_workers: number; active_external_threads: number; peak_external_threads: number; worker_entry_count: number; worker_exit_count: number; external_entry_count: number; external_exit_count: number;}
|
||||
export interface Gallery_Renderable_Observer extends Gallery_Resource {}
|
||||
export interface Gallery_Telemetry {[key: string]: unknown; scheduler?: Gallery_Scheduler_Statistics; renderable_observers?: Gallery_Renderable_Observer[]; performance_capture?: Gallery_Performance_Capture;}
|
||||
export interface Gallery_Telemetry {[key: string]: unknown; scheduler?: Gallery_Scheduler_Statistics; queue_pressure?: Record<string, unknown>; renderable_observers?: Gallery_Renderable_Observer[]; performance_capture?: Gallery_Performance_Capture;}
|
||||
|
||||
export interface Gallery_Render_Node {node_id: number; id?: string | number; name: string; label?: string; owner: string; kind: string;}
|
||||
export interface Gallery_Render_Edge {from: number | string; to: number | string;}
|
||||
|
||||
Reference in New Issue
Block a user