引入 oneTBB 删除taskflow

This commit is contained in:
2026-08-15 16:27:56 +08:00
parent 07c3937779
commit f9db768f72
24 changed files with 583 additions and 616 deletions
+15 -6
View File
@@ -1,5 +1,5 @@
include(${CMAKE_CURRENT_LIST_DIR}/cmake/rely.cmake)
set(Renderive_Kernel_dependencies global::taskflow render_kernel::oneTBB)
set(Renderive_Kernel_dependencies render_kernel::oneTBB)
if (RENDERIVE_BUILD_TESTS)
set(Renderive_Kernel_test_targets)
list(APPEND Renderive_Kernel_dependencies global::GTest)
@@ -12,7 +12,6 @@ if (Renderive_Kernel_dependencies_installed)
return()
endif ()
rcl_load_dependency_environment(${Renderive_Kernel_dependencies})
find_package(Taskflow CONFIG REQUIRED)
find_package(Threads REQUIRED)
find_package(TBB CONFIG REQUIRED)
if (RENDERIVE_BUILD_TESTS)
@@ -25,9 +24,18 @@ target_include_directories(Renderive_Kernel PUBLIC
"$<BUILD_INTERFACE:${Renderive_Kernel_source_dir}>"
)
target_compile_features(Renderive_Kernel PUBLIC cxx_std_20)
target_link_libraries(Renderive_Kernel PRIVATE Taskflow::Taskflow)
target_link_libraries(Renderive_Kernel PRIVATE TBB::tbb)
target_link_libraries(Renderive_Kernel PUBLIC TBB::tbb)
target_link_libraries(Renderive_Kernel PRIVATE Threads::Threads)
function(renderive_stage_kernel_runtime target)
if (WIN32)
add_custom_command(TARGET "${target}" POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
"$<TARGET_FILE:TBB::tbb>"
"$<TARGET_FILE_DIR:${target}>"
COMMENT "Staging oneTBB runtime for ${target}"
VERBATIM)
endif ()
endfunction()
if (MSVC)
target_compile_options(Renderive_Kernel PRIVATE /utf-8)
endif ()
@@ -47,10 +55,11 @@ if (RENDERIVE_BUILD_TESTS)
add_executable("${Renderive_Kernel_test_target}" "${Renderive_Kernel_test_source}")
target_include_directories("${Renderive_Kernel_test_target}" PRIVATE
"${Renderive_Kernel_test_dir}"
"${CMAKE_SOURCE_DIR}/render_2D"
"${CMAKE_SOURCE_DIR}/render_3D"
"${CMAKE_CURRENT_LIST_DIR}/../render_2D"
"${CMAKE_CURRENT_LIST_DIR}/../render_3D"
)
target_link_libraries("${Renderive_Kernel_test_target}" PRIVATE Renderive_Kernel GTest::gtest_main)
renderive_stage_kernel_runtime("${Renderive_Kernel_test_target}")
add_test(NAME "${Renderive_Kernel_test_target}" COMMAND "${Renderive_Kernel_test_target}")
set_tests_properties("${Renderive_Kernel_test_target}" PROPERTIES LABELS "Renderive_Kernel")
list(APPEND Renderive_Kernel_test_targets "${Renderive_Kernel_test_target}")
+12
View File
@@ -1,6 +1,18 @@
include_guard(GLOBAL)
block()
rcl_init(render_kernel)
set(oneTBB_option ${base_options})
list(APPEND oneTBB_option
-DBUILD_SHARED_LIBS=ON
-DTBB_BUILD=ON
-DTBB_INSTALL=ON
-DTBB_TEST=OFF
-DTBB_EXAMPLES=OFF
-DTBB_STRICT=OFF
-DTBB4PY_BUILD=OFF
-DTBBMALLOC_BUILD=OFF
-DTBBMALLOC_PROXY_BUILD=OFF
)
_register_git_cmake_library(render_kernel::oneTBB
"https://github.com/uxlfoundation/oneTBB.git"
"v2023.1.0"
@@ -1,8 +1,10 @@
#pragma once
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <deque>
#include <oneapi/tbb/concurrent_queue.h>
#include <memory_resource>
#include <stdexcept>
#include <mutex>
#include <utility>
#include "renderive/base/Concepts.hpp"
@@ -93,8 +95,9 @@ public:
Memory_Resource_Unique_Ptr<Frame> frame_;
std::unique_lock<Mutex> lease_lock_;
};
explicit Flow_Refresh_Strategy(Observer observer = {});
Flow_Refresh_Strategy(std::pmr::memory_resource& memory_resource, Observer observer = {});
static constexpr std::size_t default_capacity = 64;
explicit Flow_Refresh_Strategy(Observer observer = {}, std::size_t capacity = default_capacity);
Flow_Refresh_Strategy(std::pmr::memory_resource& memory_resource, Observer observer = {}, std::size_t capacity = default_capacity);
Painter_Lease acquire_painter();
Render_Lease acquire_renderer();
void swap() override;
@@ -110,7 +113,8 @@ private:
inline static thread_local Flow_Refresh_Strategy* rendering_strategy_{};
std::pmr::memory_resource* const memory_resource_;
Observer observer_;
std::pmr::deque<Memory_Resource_Unique_Ptr<Frame>> frames_;
using Frame_Ptr = Memory_Resource_Unique_Ptr<Frame>;
oneapi::tbb::concurrent_bounded_queue<Frame_Ptr> frames_;
std::uint64_t next_sequence_{};
std::uint64_t real_time_data_update_sequence_{};
Real_Time_Data_Update_State last_real_time_data_update_;
@@ -19,13 +19,13 @@ Flow_Refresh_Strategy<Scene_Frame, Mutex, Observer>::Painter_Lease::~Painter_Lea
frame_->statistics.paint_end_time_ns = strategy_->now_ns();
frame_->statistics.paint_duration_ns = frame_->statistics.paint_end_time_ns - frame_->statistics.paint_begin_time_ns;
frame_->statistics.queue_enter_time_ns = strategy_->now_ns();
const Statistics statistics = frame_->statistics;
strategy_->frames_.push(std::move(frame_));
Observation observation;
{
std::lock_guard<Mutex> lock(strategy_->state_mutex_);
const Statistics statistics = frame_->statistics;
strategy_->frames_.push_back(std::move(frame_));
++strategy_->state_.enqueued_frame_count;
strategy_->state_.pending_frame_count = strategy_->frames_.size();
strategy_->state_.pending_frame_count = static_cast<std::size_t>(std::max<std::ptrdiff_t>(0, strategy_->frames_.size()));
strategy_->update_frame_control_state(Frame_Control_Strategy_Base::invalid_frequency_hz(), 0);
observation = {Observation_Event::enqueued, statistics, strategy_->state_, strategy_->last_real_time_data_update_};
}
@@ -69,20 +69,17 @@ Flow_Refresh_Strategy<Scene_Frame, Mutex, Observer>::Render_Lease::Render_Lease(
lease_lock_ = std::unique_lock<Mutex>(strategy.render_mutex_);
previous_render_strategy_ = std::exchange(rendering_strategy_, &strategy);
Observation observation;
bool has_frame{};
const bool has_frame = strategy.frames_.try_pop(frame_);
{
std::lock_guard<Mutex> lock(strategy.state_mutex_);
if (!strategy.frames_.empty()) {
frame_ = std::move(strategy.frames_.front());
strategy.frames_.pop_front();
if (has_frame) {
frame_->statistics.queue_leave_time_ns = strategy.now_ns();
frame_->statistics.queue_wait_ns = frame_->statistics.queue_leave_time_ns - frame_->statistics.queue_enter_time_ns;
frame_->statistics.render_begin_time_ns = strategy.now_ns();
++strategy.state_.dequeued_frame_count;
strategy.state_.pending_frame_count = strategy.frames_.size();
strategy.state_.pending_frame_count = static_cast<std::size_t>(std::max<std::ptrdiff_t>(0, strategy.frames_.size()));
strategy.update_frame_control_state(Frame_Control_Strategy_Base::invalid_frequency_hz(), 0);
observation = {Observation_Event::dequeued, frame_->statistics, strategy.state_, strategy.last_real_time_data_update_};
has_frame = true;
} else {
++strategy.state_.empty_acquire_count;
strategy.state_.pending_frame_count = 0;
@@ -110,7 +107,7 @@ Flow_Refresh_Strategy<Scene_Frame, Mutex, Observer>::Render_Lease::~Render_Lease
{
std::lock_guard<Mutex> lock(strategy_->state_mutex_);
++strategy_->state_.rendered_frame_count;
strategy_->state_.pending_frame_count = strategy_->frames_.size();
strategy_->state_.pending_frame_count = static_cast<std::size_t>(std::max<std::ptrdiff_t>(0, strategy_->frames_.size()));
strategy_->update_frame_control_state(Frame_Control_Strategy_Base::invalid_frequency_hz(), 0);
observation = {Observation_Event::rendered, frame_->statistics, strategy_->state_, strategy_->last_real_time_data_update_};
frame_.reset();
@@ -148,11 +145,15 @@ auto Flow_Refresh_Strategy<Scene_Frame, Mutex, Observer>::Render_Lease::get() co
return frame_.get();
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
Flow_Refresh_Strategy<Scene_Frame, Mutex, Observer>::Flow_Refresh_Strategy(Observer observer)
: Flow_Refresh_Strategy(*std::pmr::get_default_resource(), std::move(observer)) {}
Flow_Refresh_Strategy<Scene_Frame, Mutex, Observer>::Flow_Refresh_Strategy(Observer observer, std::size_t capacity)
: Flow_Refresh_Strategy(*std::pmr::get_default_resource(), std::move(observer), capacity) {}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
Flow_Refresh_Strategy<Scene_Frame, Mutex, Observer>::Flow_Refresh_Strategy(std::pmr::memory_resource& memory_resource, Observer observer)
: Frame_Control_Strategy_Base(), memory_resource_(&memory_resource), observer_(std::move(observer)), frames_(&memory_resource) {}
Flow_Refresh_Strategy<Scene_Frame, Mutex, Observer>::Flow_Refresh_Strategy(std::pmr::memory_resource& memory_resource, Observer observer, std::size_t capacity)
: Frame_Control_Strategy_Base(), memory_resource_(&memory_resource), observer_(std::move(observer)) {
if (capacity == 0)
throw std::invalid_argument("flow refresh queue capacity must be greater than zero");
frames_.set_capacity(static_cast<typename decltype(frames_)::size_type>(capacity));
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
auto Flow_Refresh_Strategy<Scene_Frame, Mutex, Observer>::acquire_painter() -> Painter_Lease {
return Painter_Lease(*this);
@@ -179,14 +180,13 @@ auto Flow_Refresh_Strategy<Scene_Frame, Mutex, Observer>::frame_control_state()
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
std::size_t Flow_Refresh_Strategy<Scene_Frame, Mutex, Observer>::pending_frame_count() const {
std::lock_guard<Mutex> lock(state_mutex_);
return frames_.size();
return static_cast<std::size_t>(std::max<std::ptrdiff_t>(0, frames_.size()));
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
auto Flow_Refresh_Strategy<Scene_Frame, Mutex, Observer>::state() const -> State {
std::lock_guard<Mutex> lock(state_mutex_);
State result = state_;
result.pending_frame_count = frames_.size();
result.pending_frame_count = static_cast<std::size_t>(std::max<std::ptrdiff_t>(0, frames_.size()));
return result;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
@@ -1,6 +1,5 @@
#include "Render_Graph_Runtime.hpp"
#include <condition_variable>
#include <atomic>
#include <exception>
#include <limits>
#include <mutex>
@@ -8,347 +7,169 @@
#include <unordered_map>
#include <utility>
#include <vector>
#include <oneapi/tbb/flow_graph.h>
#include "renderive/scheduling/detail/OneTBB_Runtime.hpp"
namespace renderive::render_graph::detail {
struct Render_Graph_Runtime::State
: std::enable_shared_from_this<Render_Graph_Runtime::State> {
struct Node_State {
std::size_t remaining_predecessors{};
Node_Execution_Status status{Node_Execution_Status::pending};
struct Render_Graph_Runtime::State : std::enable_shared_from_this<Render_Graph_Runtime::State> {
using Message = oneapi::tbb::flow::continue_msg;
using Ready_Node = oneapi::tbb::flow::continue_node<Message>;
using Execute_Node_Type = oneapi::tbb::flow::async_node<Message, Message>;
struct Node {
std::unique_ptr<Ready_Node> ready;
std::unique_ptr<Execute_Node_Type> execute;
bool root{};
};
State(const Render_Plan& plan,
std::span<Node_Execution* const> execution_slots,
Execute_Node execute,
Schedule scheduler,
Current_Worker_Id worker_id)
: nodes(plan.graph.nodes.size()),
successors(plan.graph.nodes.size()),
executions(execution_slots.begin(), execution_slots.end()),
execute_node(std::move(execute)),
schedule(std::move(scheduler)),
current_worker_id(std::move(worker_id)),
unfinished_nodes(plan.graph.nodes.size()) {
State(const Render_Plan& plan, std::span<Node_Execution* const> execution_slots, Execute_Node execute)
: executions(execution_slots.begin(), execution_slots.end()), execute_node(std::move(execute)) {
if (!execute_node)
throw std::invalid_argument("render graph node executor is empty");
if (!schedule)
throw std::invalid_argument("render graph scheduler is empty");
if (!current_worker_id)
throw std::invalid_argument("render graph worker id source is empty");
if (!executions.empty() && executions.size() != nodes.size())
throw std::invalid_argument(
"render graph execution slot count differs from plan");
if (executions.empty())
executions.resize(nodes.size());
std::vector<bool> occupied(nodes.size());
executions.resize(plan.graph.nodes.size());
if (executions.size() != plan.graph.nodes.size())
throw std::invalid_argument("render graph execution slot count differs from plan");
nodes.resize(plan.graph.nodes.size());
std::unordered_map<Render_Node_Id, std::size_t> indices;
indices.reserve(nodes.size());
for (const auto& node : plan.graph.nodes) {
if (node.execution_index >= nodes.size() ||
occupied[node.execution_index])
throw std::invalid_argument(
"render plan execution indices are not dense and unique");
occupied[node.execution_index] = true;
if (!indices.emplace(node.node_id, node.execution_index).second)
throw std::invalid_argument(
"render plan contains duplicate node ids");
indices.reserve(plan.graph.nodes.size());
std::vector<std::size_t> predecessor_count(plan.graph.nodes.size());
for (const auto& node : plan.graph.nodes)
indices.emplace(node.node_id, node.execution_index);
for (const auto& edge : plan.graph.edges)
++predecessor_count.at(indices.at(edge.to));
for (std::size_t index = 0; index < nodes.size(); ++index) {
nodes[index].root = predecessor_count[index] == 0;
nodes[index].ready = std::make_unique<Ready_Node>(graph, [this, index](const Message&) {
mark_ready(index);
return Message{};
});
nodes[index].execute = std::make_unique<Execute_Node_Type>(graph, oneapi::tbb::flow::unlimited, [this, index](const Message&, Execute_Node_Type::gateway_type& gateway) {
run_node(index, gateway);
});
oneapi::tbb::flow::make_edge(*nodes[index].ready, *nodes[index].execute);
}
for (const auto& edge : plan.graph.edges) {
const auto from = indices.find(edge.from);
const auto to = indices.find(edge.to);
if (from == indices.end() || to == indices.end())
throw std::invalid_argument(
"render plan edge references an unknown node");
successors[from->second].push_back(to->second);
++nodes[to->second].remaining_predecessors;
const std::size_t from = indices.at(edge.from);
const std::size_t to = indices.at(edge.to);
oneapi::tbb::flow::make_edge(*nodes[from].execute, *nodes[to].ready);
}
std::vector<std::size_t> remaining;
remaining.reserve(nodes.size());
std::vector<std::size_t> topological;
topological.reserve(nodes.size());
for (const auto& node : nodes)
remaining.push_back(node.remaining_predecessors);
for (std::size_t index = 0; index < remaining.size(); ++index) {
if (remaining[index] == 0)
topological.push_back(index);
}
for (std::size_t cursor = 0; cursor < topological.size(); ++cursor) {
for (const std::size_t successor : successors[topological[cursor]]) {
if (--remaining[successor] == 0)
topological.push_back(successor);
}
}
if (topological.size() != nodes.size())
throw std::invalid_argument("render plan contains a cycle");
}
void execute() {
std::vector<std::size_t> ready;
{
std::lock_guard lock(mutex);
if (started)
throw std::logic_error("render graph runtime already executed");
started = true;
if (nodes.empty()) {
terminal = true;
} else {
for (std::size_t index = 0; index < nodes.size(); ++index) {
if (nodes[index].remaining_predecessors == 0)
make_ready_locked(index, ready);
}
if (ready.empty()) {
fail_graph_locked(std::make_exception_ptr(
std::logic_error("render plan contains a cycle")));
}
if (started.exchange(true, std::memory_order_acq_rel))
throw std::logic_error("render graph runtime already executed");
auto& arena = renderive::scheduling::detail::OneTBB_Runtime::instance().arena();
arena.execute([this] {
for (auto& node : nodes) {
if (node.root)
node.ready->try_put(Message{});
}
}
submit(ready);
graph.wait_for_all();
});
std::exception_ptr error;
{
std::unique_lock lock(mutex);
completion.wait(lock, [this] { return terminal; });
std::lock_guard lock(error_mutex);
error = first_exception;
}
if (error)
std::rethrow_exception(error);
}
void make_ready_locked(std::size_t index,
std::vector<std::size_t>& ready) {
auto& node = nodes.at(index);
if (failed || node.status != Node_Execution_Status::pending)
void mark_ready(std::size_t index) noexcept {
if (failed.load(std::memory_order_acquire))
return;
node.status = Node_Execution_Status::ready;
if (auto* execution = executions[index]) {
execution->ready_time_ns = render_clock_now_ns();
execution->status = Node_Execution_Status::ready;
}
ready.push_back(index);
}
void submit(const std::vector<std::size_t>& ready) noexcept {
for (const std::size_t index : ready)
submit(index);
}
void submit(std::size_t index) noexcept {
{
std::lock_guard lock(mutex);
if (failed || nodes[index].status != Node_Execution_Status::ready) {
finish_if_terminal_locked();
return;
}
++active_tasks;
}
try {
auto self = shared_from_this();
schedule([self = std::move(self), index] {
self->run_node(index);
});
} catch (...) {
std::lock_guard lock(mutex);
--active_tasks;
fail_node_locked(index, std::current_exception(),
render_clock_now_ns());
finish_if_terminal_locked();
}
}
void run_node(std::size_t index) noexcept {
void run_node(std::size_t index, Execute_Node_Type::gateway_type& gateway) noexcept {
if (failed.load(std::memory_order_acquire))
return;
Node_Execution_Metrics* metrics{};
{
std::lock_guard lock(mutex);
if (failed || nodes[index].status != Node_Execution_Status::ready) {
--active_tasks;
finish_if_terminal_locked();
return;
}
nodes[index].status = Node_Execution_Status::running;
if (auto* execution = executions[index]) {
execution->start_time_ns = render_clock_now_ns();
try {
execution->worker_id = current_worker_id();
} catch (...) {
execution->worker_id =
std::numeric_limits<std::uint32_t>::max();
}
execution->status = Node_Execution_Status::running;
metrics = &execution->metrics;
}
if (auto* execution = executions[index]) {
execution->start_time_ns = render_clock_now_ns();
execution->worker_id = renderive::scheduling::detail::OneTBB_Runtime::instance().current_worker_id();
execution->status = Node_Execution_Status::running;
metrics = &execution->metrics;
}
Node_Execution_Result result = Node_Execution_Result::completed();
std::exception_ptr error;
try {
result = execute_node(index, metrics);
} catch (...) {
error = std::current_exception();
fail(index, std::current_exception(), render_clock_now_ns());
return;
}
const std::uint64_t cpu_end = render_clock_now_ns();
if (error) {
std::lock_guard lock(mutex);
--active_tasks;
fail_node_locked(index, std::move(error), cpu_end);
finish_if_terminal_locked();
return;
}
if (!result.is_external()) {
complete_synchronous(index, cpu_end);
complete(index, cpu_end);
gateway.try_put(Message{});
return;
}
{
std::lock_guard lock(mutex);
--active_tasks;
auto& node = nodes[index];
node.status = Node_Execution_Status::waiting_external;
++waiting_external;
if (auto* execution = executions[index]) {
execution->cpu_end_time_ns = cpu_end;
execution->external_start_time_ns = cpu_end;
execution->status = Node_Execution_Status::waiting_external;
}
if (auto* execution = executions[index]) {
execution->cpu_end_time_ns = cpu_end;
execution->external_start_time_ns = cpu_end;
execution->status = Node_Execution_Status::waiting_external;
}
gateway.reserve_wait();
try {
auto self = shared_from_this();
result.operation().on_complete(
[self = std::move(self), index](std::exception_ptr completion_error) {
self->complete_external(index, std::move(completion_error));
});
auto* gateway_ptr = &gateway;
result.operation().on_complete([self = std::move(self), gateway_ptr, index](std::exception_ptr error) {
const std::uint64_t end = render_clock_now_ns();
if (error) {
self->fail(index, std::move(error), end);
} else {
self->complete_external(index, end);
gateway_ptr->try_put(Message{});
}
gateway_ptr->release_wait();
});
} catch (...) {
complete_external(index, std::current_exception());
fail(index, std::current_exception(), render_clock_now_ns());
gateway.release_wait();
}
}
void complete_synchronous(std::size_t index,
std::uint64_t cpu_end) noexcept {
std::vector<std::size_t> ready;
{
std::lock_guard lock(mutex);
--active_tasks;
auto& node = nodes[index];
node.status = Node_Execution_Status::complete;
if (auto* execution = executions[index]) {
execution->cpu_end_time_ns = cpu_end;
execution->end_time_ns = cpu_end;
execution->status = Node_Execution_Status::complete;
}
--unfinished_nodes;
unlock_successors_locked(index, ready);
finish_if_terminal_locked();
}
submit(ready);
}
void complete_external(std::size_t index,
std::exception_ptr error) noexcept {
std::vector<std::size_t> ready;
{
std::lock_guard lock(mutex);
auto& node = nodes[index];
if (node.status != Node_Execution_Status::waiting_external)
return;
--waiting_external;
const std::uint64_t end = render_clock_now_ns();
if (auto* execution = executions[index]) {
execution->external_end_time_ns = end;
execution->end_time_ns = end;
}
if (error) {
fail_node_locked(index, std::move(error), end);
} else {
node.status = Node_Execution_Status::complete;
if (auto* execution = executions[index])
execution->status = Node_Execution_Status::complete;
--unfinished_nodes;
unlock_successors_locked(index, ready);
}
finish_if_terminal_locked();
}
submit(ready);
}
void unlock_successors_locked(std::size_t index,
std::vector<std::size_t>& ready) {
if (failed)
return;
for (const std::size_t successor : successors[index]) {
auto& state = nodes[successor];
if (state.remaining_predecessors == 0)
continue;
--state.remaining_predecessors;
if (state.remaining_predecessors == 0)
make_ready_locked(successor, ready);
void complete(std::size_t index, std::uint64_t end) noexcept {
if (auto* execution = executions[index]) {
execution->cpu_end_time_ns = end;
execution->end_time_ns = end;
execution->status = Node_Execution_Status::complete;
}
}
void fail_node_locked(std::size_t index, std::exception_ptr error,
std::uint64_t end) {
auto& node = nodes[index];
node.status = Node_Execution_Status::failed;
void complete_external(std::size_t index, std::uint64_t end) noexcept {
if (auto* execution = executions[index]) {
execution->external_end_time_ns = end;
execution->end_time_ns = end;
execution->status = Node_Execution_Status::complete;
}
}
void fail(std::size_t index, std::exception_ptr error, std::uint64_t end) noexcept {
failed.store(true, std::memory_order_release);
if (auto* execution = executions[index]) {
if (execution->start_time_ns == 0)
execution->start_time_ns = end;
if (execution->cpu_end_time_ns == 0)
execution->cpu_end_time_ns = end;
if (execution->status == Node_Execution_Status::waiting_external)
execution->external_end_time_ns = end;
execution->end_time_ns = end;
execution->status = Node_Execution_Status::failed;
}
fail_graph_locked(std::move(error));
}
void fail_graph_locked(std::exception_ptr error) {
failed = true;
std::lock_guard lock(error_mutex);
if (!first_exception)
first_exception = std::move(error);
}
void finish_if_terminal_locked() {
if (terminal)
return;
if ((!failed && unfinished_nodes == 0) ||
(failed && active_tasks == 0 && waiting_external == 0)) {
terminal = true;
completion.notify_all();
}
}
std::vector<Node_State> nodes;
std::vector<std::vector<std::size_t>> successors;
oneapi::tbb::flow::graph graph;
std::vector<Node> nodes;
std::vector<Node_Execution*> executions;
Execute_Node execute_node;
Schedule schedule;
Current_Worker_Id current_worker_id;
std::mutex mutex;
std::condition_variable completion;
std::mutex error_mutex;
std::exception_ptr first_exception;
std::size_t unfinished_nodes{};
std::size_t active_tasks{};
std::size_t waiting_external{};
bool started{};
bool failed{};
bool terminal{};
std::atomic_bool started{};
std::atomic_bool failed{};
};
Render_Graph_Runtime::Render_Graph_Runtime(
const Render_Plan& plan,
std::span<Node_Execution* const> executions,
Execute_Node execute_node,
Schedule schedule,
Current_Worker_Id current_worker_id)
: state_(std::make_shared<State>(
plan, executions, std::move(execute_node), std::move(schedule),
std::move(current_worker_id))) {}
Render_Graph_Runtime::Render_Graph_Runtime(const Render_Plan& plan, std::span<Node_Execution* const> executions, Execute_Node execute_node)
: state_(std::make_shared<State>(plan, executions, std::move(execute_node))) {
}
Render_Graph_Runtime::~Render_Graph_Runtime() = default;
void Render_Graph_Runtime::execute() {
state_->execute();
}
} // namespace renderive::render_graph::detail
}
@@ -1,41 +1,24 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <span>
#include "renderive/render_graph/External_Operation.hpp"
#include "renderive/render_graph/Render_Plan.hpp"
#include "renderive/scene/base/Abstract_Frame.hpp"
namespace renderive::render_graph::detail {
class Render_Graph_Runtime final {
public:
using Execute_Node = std::function<Node_Execution_Result(
std::size_t execution_index, Node_Execution_Metrics* metrics)>;
using Schedule = std::function<void(std::function<void()>)>;
using Current_Worker_Id = std::function<std::uint32_t()>;
Render_Graph_Runtime(const Render_Plan& plan,
std::span<Node_Execution* const> executions,
Execute_Node execute_node,
Schedule schedule,
Current_Worker_Id current_worker_id);
using Execute_Node = std::function<Node_Execution_Result(std::size_t execution_index, Node_Execution_Metrics* metrics)>;
Render_Graph_Runtime(const Render_Plan& plan, std::span<Node_Execution* const> executions, Execute_Node execute_node);
~Render_Graph_Runtime();
Render_Graph_Runtime(const Render_Graph_Runtime&) = delete;
Render_Graph_Runtime& operator=(const Render_Graph_Runtime&) = delete;
Render_Graph_Runtime(Render_Graph_Runtime&&) = delete;
Render_Graph_Runtime& operator=(Render_Graph_Runtime&&) = delete;
void execute();
private:
struct State;
std::shared_ptr<State> state_;
};
} // namespace renderive::render_graph::detail
}
+164 -170
View File
@@ -13,23 +13,62 @@
#include <utility>
#include <vector>
#include <taskflow/taskflow.hpp>
#include <oneapi/tbb/concurrent_queue.h>
#include <oneapi/tbb/flow_graph.h>
#include "renderive/renderable/Render_Frame_Completion.hpp"
#include "renderive/renderable/Renderive_Id_Allocator.hpp"
#include "renderive/renderable/base/Renderable_Base_p.hpp"
#include "renderive/renderable/color/Color_Cache.hpp"
#include "renderive/render_graph/detail/Render_Graph_Runtime.hpp"
#include "renderive/scheduling/detail/OneTBB_Runtime.hpp"
#include "renderive/state/base/State_Strategy_Base.hpp"
static_assert(TF_VERSION == 400100, "Renderive requires Taskflow 4.1.0");
class Scene_Base::Execution_Context {
public:
static tf::Executor& executor() {
static tf::Executor executor;
return executor;
using Message = oneapi::tbb::flow::continue_msg;
using Operation = std::function<void()>;
Execution_Context()
: node_(graph_, oneapi::tbb::flow::serial, [this](const Message&) {
execute_one();
return Message{};
}) {
operations_.set_capacity(default_capacity);
}
~Execution_Context() {
wait();
}
void submit(Operation operation) {
operations_.push(std::move(operation));
schedule();
}
void wait() noexcept {
try {
auto& arena = renderive::scheduling::detail::OneTBB_Runtime::instance().arena();
arena.execute([this] { graph_.wait_for_all(); });
} catch (...) {
}
}
private:
void schedule() {
if (scheduled_.exchange(true, std::memory_order_acq_rel))
return;
auto& arena = renderive::scheduling::detail::OneTBB_Runtime::instance().arena();
arena.execute([this] { node_.try_put(Message{}); });
}
void execute_one() {
Operation operation;
if (operations_.try_pop(operation))
operation();
scheduled_.store(false, std::memory_order_release);
if (!operations_.empty())
schedule();
}
static constexpr std::size_t default_capacity = 64;
oneapi::tbb::flow::graph graph_;
oneapi::tbb::concurrent_bounded_queue<Operation> operations_;
oneapi::tbb::flow::function_node<Message, Message> node_;
std::atomic_bool scheduled_{};
};
class Scene_Base::Render_Execution_Scope {
@@ -53,14 +92,12 @@ Scene_Base::Scene_Base(std::pmr::memory_resource& upstream_memory_resource,
: scene_lifetime_(std::make_shared<Scene_Lifetime>(*this)),
memory_domain_(std::allocate_shared<Scene_Memory_Domain>(std::pmr::polymorphic_allocator<Scene_Memory_Domain>(&upstream_memory_resource), upstream_memory_resource)),
impl_(std::move(impl)),
execution_context_(std::make_unique<Execution_Context>()),
renderables_(&memory_domain_->resource()),
dependency_resolver_(memory_domain_->resource()),
color_caches_(&memory_domain_->resource()),
task_(memory_domain_->resource()),
renderable_edit_queue_(&memory_domain_->resource()),
composite_begin_node_id_(allocate_renderive_node_id()),
scene_render_node_id_(allocate_renderive_node_id()) {
worker_ = std::thread([this] { render_loop(); });
}
Scene_Base::~Scene_Base() {
shutdown();
@@ -310,50 +347,51 @@ void Scene_Base::submit_render(Abstract_Frame* frame) {
pending_exception_ = nullptr;
pending_exception_observed_ = false;
}
if (!pending_exception_ && current_completion_ &&
current_completion_->completed && current_completion_->exception &&
!current_completion_->observed) {
if (!pending_exception_ && current_completion_ && current_completion_->completed && current_completion_->exception && !current_completion_->observed)
pending_exception_ = current_completion_->exception;
}
auto& strategy = frame_control_strategy();
strategy.swap();
auto snapshot = frame ? std::exchange(frame->published_snapshot_, {})
: nullptr;
auto snapshot = frame ? std::exchange(frame->published_snapshot_, {}) : nullptr;
if (!snapshot)
snapshot = capture_live_frame();
snapshot->next_refresh_interval_ns_ =
strategy.frame_control_state().next_refresh_interval_ns;
snapshot->next_refresh_interval_ns_ = strategy.frame_control_state().next_refresh_interval_ns;
if (render_sequence_ == std::numeric_limits<std::uint64_t>::max())
throw std::overflow_error("render sequence exhausted");
snapshot->render_sequence = ++render_sequence_;
Render_Task task(memory_resource());
task.frame = frame
? Render_Task::Frame{std::ref(*frame)}
: Render_Task::Frame{std::make_shared<Abstract_Frame>()};
task.plan = compile_render_plan(*snapshot, task);
task.topology =
std::make_shared<Topology_Snapshot>(topology_snapshot());
task.completion = std::make_shared<Render_Completion>();
auto task = std::make_shared<Render_Task>(memory_resource());
task->frame = frame ? Render_Task::Frame{std::ref(*frame)} : Render_Task::Frame{std::make_shared<Abstract_Frame>()};
task->plan = compile_render_plan(*snapshot, *task);
task->topology = std::make_shared<Topology_Snapshot>(topology_snapshot());
task->completion = std::make_shared<Render_Completion>();
snapshot->capture_ticket_ = capture_controller_.begin_frame();
task.snapshot = std::move(snapshot);
current_completion_ = task.completion;
task->snapshot = std::move(snapshot);
current_completion_ = task->completion;
const Observation submitted_observation{
Observation_Event::render_submitted, d_func().now_ns(),
task.snapshot->render_sequence, task.snapshot->scene_state_revision_,
task.snapshot->renderables.size(), task.topology, task.snapshot,
task.plan};
task_ = std::move(task);
task_pending_ = true;
Observation_Event::render_submitted, d_func().now_ns(), task->snapshot->render_sequence,
task->snapshot->scene_state_revision_, task->snapshot->renderables.size(), task->topology,
task->snapshot, task->plan};
++pending_operations_;
task_lock.unlock();
Scene_Base* previous_observer_scene =
std::exchange(active_submitted_observer_scene_, this);
Scene_Base* previous_observer_scene = std::exchange(active_submitted_observer_scene_, this);
d_func().dispatch(submitted_observation);
active_submitted_observer_scene_ = previous_observer_scene;
task_ready_.notify_one();
try {
execution_context_->submit([this, task = std::move(task)] {
execute_render_task(task);
{
std::lock_guard<std::recursive_mutex> lock(task_mutex_);
--pending_operations_;
}
render_completed_.notify_all();
});
} catch (...) {
{
std::lock_guard<std::recursive_mutex> lock(task_mutex_);
--pending_operations_;
}
render_completed_.notify_all();
throw;
}
std::size_t deferred_render_count{};
{
std::lock_guard<std::recursive_mutex> lock(task_mutex_);
@@ -364,7 +402,7 @@ void Scene_Base::submit_render(Abstract_Frame* frame) {
}
void Scene_Base::wait_for_render() {
if (is_render_worker_thread() || active_submitted_observer_scene_ == this)
if (is_render_execution_context() || active_submitted_observer_scene_ == this)
return;
std::unique_lock<std::recursive_mutex> lock(task_mutex_);
const auto completion = current_completion_;
@@ -386,16 +424,37 @@ void Scene_Base::wait_for_render() {
std::rethrow_exception(exception);
}
void Scene_Base::enqueue_renderable_edit(std::function<void()> edit) {
std::lock_guard<std::recursive_mutex> lock(task_mutex_);
enqueue_renderable_edit_locked(std::move(edit));
submit_operation([this, edit = std::move(edit)]() mutable {
execute_renderable_edit(std::move(edit));
});
}
void Scene_Base::enqueue_renderable_edit_locked(std::function<void()> edit) {
if (stop_)
throw std::logic_error("scene is shutting down");
runtime_started_ = true;
renderable_edit_queue_.push_back({std::move(edit)});
task_ready_.notify_one();
void Scene_Base::submit_operation(std::function<void()> operation) {
{
std::lock_guard<std::recursive_mutex> lock(task_mutex_);
if (shutting_down_)
throw std::logic_error("scene is shutting down");
runtime_started_ = true;
++pending_operations_;
}
try {
execution_context_->submit([this, operation = std::move(operation)]() mutable {
operation();
{
std::lock_guard<std::recursive_mutex> lock(task_mutex_);
--pending_operations_;
}
render_completed_.notify_all();
});
} catch (...) {
{
std::lock_guard<std::recursive_mutex> lock(task_mutex_);
--pending_operations_;
}
render_completed_.notify_all();
throw;
}
}
void Scene_Base::cleanup_detached_topology_locked() {
for (const Renderable_Id id : dependency_resolver_.ids()) {
if (renderables_.contains(id))
@@ -469,13 +528,15 @@ void Scene_Base::request_render_graph_rebuild(Renderable_Base& renderable) {
return;
owner = iterator->second;
}
std::lock_guard<std::recursive_mutex> lock(task_mutex_);
if (!runtime_started_) {
owner->d_func().reset_render_graph();
notify_model_dirty();
return;
{
std::lock_guard<std::recursive_mutex> lock(task_mutex_);
if (!runtime_started_) {
owner->d_func().reset_render_graph();
notify_model_dirty();
return;
}
}
enqueue_renderable_edit_locked([owner = std::move(owner)] {
enqueue_renderable_edit([owner = std::move(owner)] {
owner->d_func().reset_render_graph();
owner->d_func().notify_scene_model_dirty();
});
@@ -727,10 +788,6 @@ std::vector<Plan_Version_Statistics> Scene_Base::plan_version_statistics(
return analyze_plan_versions(analyses);
}
std::size_t Scene_Base::task_executor_worker_count() noexcept {
return Execution_Context::executor().num_workers();
}
std::pmr::memory_resource& Scene_Base::memory_resource() const noexcept {
return memory_domain_->resource();
}
@@ -824,128 +881,75 @@ std::uint64_t Scene_Base::Impl::now_ns() const noexcept {
}
std::unique_lock<std::recursive_mutex> Scene_Base::lock_render_idle() {
if (is_render_worker_thread())
throw std::logic_error(
"render-idle operation is not allowed during render execution");
if (is_render_execution_context())
throw std::logic_error("render-idle operation is not allowed during render execution");
if (active_submitted_observer_scene_ == this)
throw std::logic_error(
"render-idle operation is not allowed during render submission observation");
throw std::logic_error("render-idle operation is not allowed during render submission observation");
std::unique_lock<std::recursive_mutex> lock(task_mutex_);
render_completed_.wait(lock, [this] {
return !task_pending_ && !rendering_ && renderable_edit_queue_.empty() && !editing_renderables_;
});
render_completed_.wait(lock, [this] { return pending_operations_ == 0; });
return lock;
}
bool Scene_Base::is_render_worker_thread() const noexcept {
return active_execution_scene_ == this ||
(worker_.joinable() &&
std::this_thread::get_id() == worker_.get_id());
bool Scene_Base::is_render_execution_context() const noexcept {
return active_execution_scene_ == this;
}
bool Scene_Base::consume_model_dirty() noexcept {
return model_dirty_.exchange(false, std::memory_order_acq_rel);
}
void Scene_Base::shutdown() noexcept {
{
std::unique_lock<std::recursive_mutex> lock(task_mutex_);
render_completed_.wait(lock, [this] {
return !task_pending_ && !rendering_ && renderable_edit_queue_.empty() && !editing_renderables_;
});
stop_ = true;
if (shutting_down_)
return;
shutting_down_ = true;
render_completed_.wait(lock, [this] { return pending_operations_ == 0; });
}
task_ready_.notify_one();
if (worker_.joinable())
worker_.join();
execution_context_->wait();
{
std::lock_guard lock(model_mutex_);
for (const auto& [id, renderable] : renderables_) {
static_cast<void>(id);
renderable->d_func().real_time_data_state->attached.store(
false, std::memory_order_release);
renderable->d_func().real_time_data_state->attached.store(false, std::memory_order_release);
}
}
scene_lifetime_->invalidate();
}
void Scene_Base::render_loop() {
for (;;) {
Render_Task task(memory_resource());
std::vector<Renderable_Edit_Command> edits;
{
std::unique_lock<std::recursive_mutex> lock(task_mutex_);
task_ready_.wait(lock, [this] {
return stop_ || task_pending_ || !renderable_edit_queue_.empty();
});
if (stop_)
return;
if (task_pending_) {
task = std::move(task_);
task_pending_ = false;
rendering_ = true;
} else {
editing_renderables_ = true;
edits.reserve(renderable_edit_queue_.size());
while (!renderable_edit_queue_.empty()) {
edits.push_back(std::move(renderable_edit_queue_.front()));
renderable_edit_queue_.pop_front();
}
}
}
if (!edits.empty()) {
{
std::lock_guard lock(model_mutex_);
Scene_Base* previous = std::exchange(active_renderable_edit_scene_, this);
for (auto& command : edits) {
try {
command.execute();
cleanup_detached_topology_locked();
validate_structure_locked();
} catch (...) {
active_renderable_edit_scene_ = previous;
structure_fail_fast("runtime renderable edit", std::current_exception());
}
}
active_renderable_edit_scene_ = previous;
}
{
std::lock_guard<std::recursive_mutex> lock(task_mutex_);
editing_renderables_ = false;
}
render_completed_.notify_all();
continue;
}
const auto& snapshot = *task.snapshot;
d_func().dispatch({Observation_Event::render_started, d_func().now_ns(),
snapshot.render_sequence,
snapshot.scene_state_revision_,
snapshot.renderables.size(), task.topology,
task.snapshot, task.plan});
std::exception_ptr exception;
try {
execute_render_graph(task);
d_func().dispatch({Observation_Event::render_completed, d_func().now_ns(),
snapshot.render_sequence,
snapshot.scene_state_revision_,
snapshot.renderables.size(), task.topology,
task.snapshot, task.plan});
} catch (...) {
exception = std::current_exception();
d_func().dispatch({Observation_Event::render_failed, d_func().now_ns(),
snapshot.render_sequence,
snapshot.scene_state_revision_,
snapshot.renderables.size(), task.topology,
task.snapshot, task.plan});
}
{
std::lock_guard lock(task_mutex_);
task.completion->exception = exception;
task.completion->completed = true;
rendering_ = false;
}
render_completed_.notify_all();
void Scene_Base::execute_renderable_edit(std::function<void()> edit) {
std::lock_guard lock(model_mutex_);
Scene_Base* previous = std::exchange(active_renderable_edit_scene_, this);
try {
edit();
cleanup_detached_topology_locked();
validate_structure_locked();
} catch (...) {
active_renderable_edit_scene_ = previous;
structure_fail_fast("runtime renderable edit", std::current_exception());
}
active_renderable_edit_scene_ = previous;
}
void Scene_Base::execute_render_task(std::shared_ptr<Render_Task> task) {
Render_Execution_Scope scope(*this);
const auto& snapshot = *task->snapshot;
d_func().dispatch({Observation_Event::render_started, d_func().now_ns(), snapshot.render_sequence,
snapshot.scene_state_revision_, snapshot.renderables.size(), task->topology,
task->snapshot, task->plan});
std::exception_ptr exception;
try {
execute_render_graph(*task);
d_func().dispatch({Observation_Event::render_completed, d_func().now_ns(), snapshot.render_sequence,
snapshot.scene_state_revision_, snapshot.renderables.size(), task->topology,
task->snapshot, task->plan});
} catch (...) {
exception = std::current_exception();
d_func().dispatch({Observation_Event::render_failed, d_func().now_ns(), snapshot.render_sequence,
snapshot.scene_state_revision_, snapshot.renderables.size(), task->topology,
task->snapshot, task->plan});
}
{
std::lock_guard lock(task_mutex_);
task->completion->exception = exception;
task->completion->completed = true;
}
render_completed_.notify_all();
}
std::shared_ptr<const Render_Plan> Scene_Base::compile_render_plan(
@@ -1159,7 +1163,6 @@ void Scene_Base::execute_render_graph(Render_Task& task) {
execution_slots[node.execution_index] =
frame->execution_slot(node.execution_index);
auto& executor = Execution_Context::executor();
renderive::render_graph::detail::Render_Graph_Runtime runtime(
*task.plan, execution_slots,
[this, &task, &snapshot, &execution_slots](
@@ -1212,15 +1215,6 @@ void Scene_Base::execute_render_graph(Render_Task& task) {
Scene_Render_Context{snapshot, metrics, diagnostics});
}
throw std::logic_error("unknown render node kind");
},
[&executor](std::function<void()> function) {
executor.silent_async(std::move(function));
},
[&executor] {
const int worker = executor.this_worker_id();
return worker < 0
? std::numeric_limits<std::uint32_t>::max()
: static_cast<std::uint32_t>(worker);
});
runtime.execute();
+7 -18
View File
@@ -4,14 +4,12 @@
#include <condition_variable>
#include <concepts>
#include <cstdint>
#include <deque>
#include <exception>
#include <functional>
#include <memory>
#include <memory_resource>
#include <mutex>
#include <optional>
#include <thread>
#include <unordered_map>
#include <utility>
#include <variant>
@@ -174,7 +172,6 @@ public:
[[nodiscard]] std::vector<Plan_Version_Statistics> plan_version_statistics(
Capture_Session_Id session_id) const;
[[nodiscard]] static std::size_t task_executor_worker_count() noexcept;
[[nodiscard]] std::pmr::memory_resource& memory_resource() const noexcept;
[[nodiscard]] std::pmr::memory_resource& upstream_memory_resource() const noexcept;
Frame_Control_Strategy_Base& frame_control_strategy();
@@ -204,7 +201,7 @@ protected:
virtual std::uint64_t acquire_scene_state();
virtual void capture_scene_state(Frame_Render_Snapshot& snapshot) const;
std::unique_lock<std::recursive_mutex> lock_render_idle();
[[nodiscard]] bool is_render_worker_thread() const noexcept;
[[nodiscard]] bool is_render_execution_context() const noexcept;
bool consume_model_dirty() noexcept;
void invalidate_renderables();
void shutdown() noexcept;
@@ -244,10 +241,6 @@ private:
std::shared_ptr<Render_Completion> completion;
};
struct Renderable_Edit_Command {
std::function<void()> execute;
};
class Execution_Context;
class Render_Execution_Scope;
@@ -258,7 +251,7 @@ private:
};
void submit_render(Abstract_Frame* frame);
void enqueue_renderable_edit_locked(std::function<void()> edit);
void submit_operation(std::function<void()> operation);
void request_render_graph_rebuild(Renderable_Base& renderable);
[[nodiscard]] std::shared_ptr<Frame_Render_Snapshot> snapshot_live_model();
[[nodiscard]] std::shared_ptr<Frame_Render_Snapshot>
@@ -266,7 +259,8 @@ private:
void publish_frame_state_locked();
std::shared_ptr<const Render_Plan> compile_render_plan(
const Frame_Render_Snapshot& snapshot, Render_Task& task);
void render_loop();
void execute_render_task(std::shared_ptr<Render_Task> task);
void execute_renderable_edit(std::function<void()> edit);
void execute_render_graph(Render_Task& task);
void validate_renderable_scene(const Renderable_Base& renderable) const;
[[nodiscard]] bool is_renderable_attached_locked(
@@ -286,6 +280,7 @@ private:
std::shared_ptr<Scene_Lifetime> scene_lifetime_;
std::shared_ptr<Scene_Memory_Domain> memory_domain_;
std::unique_ptr<Impl> impl_;
std::unique_ptr<Execution_Context> execution_context_;
std::atomic_bool model_dirty_{true};
std::pmr::unordered_map<Renderable_Id, Renderable> renderables_;
renderive::scene::dependency::Dependency_Resolver<Renderable_Id>
@@ -294,21 +289,15 @@ private:
mutable std::mutex model_mutex_;
mutable std::recursive_mutex task_mutex_;
std::condition_variable_any task_ready_;
std::condition_variable_any render_completed_;
std::thread worker_;
Render_Task task_;
std::pmr::deque<Renderable_Edit_Command> renderable_edit_queue_;
std::shared_ptr<Render_Completion> current_completion_;
std::exception_ptr pending_exception_;
bool pending_exception_observed_{};
std::size_t deferred_render_count_{};
std::uint64_t render_sequence_{};
bool task_pending_{};
bool rendering_{};
bool editing_renderables_{};
std::size_t pending_operations_{};
bool runtime_started_{};
bool stop_{};
bool shutting_down_{};
const Render_Node_Id composite_begin_node_id_;
const Render_Node_Id scene_render_node_id_;
@@ -0,0 +1,18 @@
#pragma once
#include <cstddef>
#include <cstdint>
namespace renderive::scheduling {
struct Scheduler_Statistics {
std::size_t concurrency{};
std::size_t active_workers{};
std::size_t peak_workers{};
std::size_t active_external_threads{};
std::size_t peak_external_threads{};
std::uint64_t worker_entry_count{};
std::uint64_t worker_exit_count{};
std::uint64_t external_entry_count{};
std::uint64_t external_exit_count{};
};
[[nodiscard]] std::size_t scheduler_concurrency() noexcept;
[[nodiscard]] Scheduler_Statistics scheduler_statistics() noexcept;
}
@@ -0,0 +1,98 @@
#include "OneTBB_Runtime.hpp"
#include <algorithm>
#include <atomic>
#include <limits>
#include <oneapi/tbb/task_scheduler_observer.h>
namespace renderive::scheduling::detail {
struct OneTBB_Runtime::Counters {
std::atomic_size_t active_workers{};
std::atomic_size_t peak_workers{};
std::atomic_size_t active_external_threads{};
std::atomic_size_t peak_external_threads{};
std::atomic_uint64_t worker_entry_count{};
std::atomic_uint64_t worker_exit_count{};
std::atomic_uint64_t external_entry_count{};
std::atomic_uint64_t external_exit_count{};
};
namespace {
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) {
if (peak.compare_exchange_weak(current, value, std::memory_order_relaxed))
break;
}
}
}
class OneTBB_Runtime::Observer final : public oneapi::tbb::task_scheduler_observer {
public:
Observer(oneapi::tbb::task_arena& arena, Counters& counters)
: oneapi::tbb::task_scheduler_observer(arena), counters_(counters) {
observe(true);
}
~Observer() override {
observe(false);
}
void on_scheduler_entry(bool is_worker) override {
if (is_worker) {
counters_.worker_entry_count.fetch_add(1, std::memory_order_relaxed);
const std::size_t active = counters_.active_workers.fetch_add(1, std::memory_order_relaxed) + 1;
update_peak(counters_.peak_workers, active);
return;
}
counters_.external_entry_count.fetch_add(1, std::memory_order_relaxed);
const std::size_t active = counters_.active_external_threads.fetch_add(1, std::memory_order_relaxed) + 1;
update_peak(counters_.peak_external_threads, active);
}
void on_scheduler_exit(bool is_worker) override {
if (is_worker) {
counters_.worker_exit_count.fetch_add(1, std::memory_order_relaxed);
counters_.active_workers.fetch_sub(1, std::memory_order_relaxed);
return;
}
counters_.external_exit_count.fetch_add(1, std::memory_order_relaxed);
counters_.active_external_threads.fetch_sub(1, std::memory_order_relaxed);
}
private:
Counters& counters_;
};
OneTBB_Runtime& OneTBB_Runtime::instance() {
static OneTBB_Runtime runtime;
return runtime;
}
OneTBB_Runtime::OneTBB_Runtime()
: counters_(std::make_unique<Counters>()), arena_(oneapi::tbb::task_arena::automatic) {
arena_.initialize();
observer_ = std::make_unique<Observer>(arena_, *counters_);
}
OneTBB_Runtime::~OneTBB_Runtime() = default;
oneapi::tbb::task_arena& OneTBB_Runtime::arena() noexcept {
return arena_;
}
std::uint32_t OneTBB_Runtime::current_worker_id() const noexcept {
const int index = oneapi::tbb::this_task_arena::current_thread_index();
if (index < 0)
return std::numeric_limits<std::uint32_t>::max();
return static_cast<std::uint32_t>(index);
}
Scheduler_Statistics OneTBB_Runtime::statistics() const noexcept {
return {
static_cast<std::size_t>(arena_.max_concurrency()),
counters_->active_workers.load(std::memory_order_relaxed),
counters_->peak_workers.load(std::memory_order_relaxed),
counters_->active_external_threads.load(std::memory_order_relaxed),
counters_->peak_external_threads.load(std::memory_order_relaxed),
counters_->worker_entry_count.load(std::memory_order_relaxed),
counters_->worker_exit_count.load(std::memory_order_relaxed),
counters_->external_entry_count.load(std::memory_order_relaxed),
counters_->external_exit_count.load(std::memory_order_relaxed)
};
}
}
namespace renderive::scheduling {
std::size_t scheduler_concurrency() noexcept {
return detail::OneTBB_Runtime::instance().statistics().concurrency;
}
Scheduler_Statistics scheduler_statistics() noexcept {
return detail::OneTBB_Runtime::instance().statistics();
}
}
@@ -0,0 +1,29 @@
#pragma once
#include <cstdint>
#include <memory>
#include <oneapi/tbb/task_arena.h>
#include <utility>
#include "renderive/scheduling/Scheduler.hpp"
namespace renderive::scheduling::detail {
class OneTBB_Runtime final {
public:
static OneTBB_Runtime& instance();
oneapi::tbb::task_arena& arena() noexcept;
template <class F>
void enqueue(F&& task) {
arena_.enqueue(std::forward<F>(task));
}
[[nodiscard]] std::uint32_t current_worker_id() const noexcept;
[[nodiscard]] Scheduler_Statistics statistics() const noexcept;
private:
class Observer;
OneTBB_Runtime();
~OneTBB_Runtime();
OneTBB_Runtime(const OneTBB_Runtime&) = delete;
OneTBB_Runtime& operator=(const OneTBB_Runtime&) = delete;
struct Counters;
std::unique_ptr<Counters> counters_;
oneapi::tbb::task_arena arena_;
std::unique_ptr<Observer> observer_;
};
}
@@ -1,6 +1,7 @@
#include <gtest/gtest.h>
#include <algorithm>
#include <atomic>
#include <chrono>
#include <functional>
#include <memory>
#include <memory_resource>
@@ -35,7 +36,7 @@ TEST(flow_refresh_strategy_test, returns_empty_lease_when_queue_is_empty) {
EXPECT_EQ(strategy.state().empty_acquire_count, 1);
}
TEST(flow_refresh_strategy_test, accepts_multiple_concurrent_producers_without_losing_frames) {
Flow_Refresh_Test_Strategy strategy;
Flow_Refresh_Test_Strategy strategy(Observer_State<>{}, 512);
constexpr int producer_count = 4;
constexpr int frames_per_producer = 100;
std::vector<std::thread> producers;
@@ -63,6 +64,35 @@ TEST(flow_refresh_strategy_test, accepts_multiple_concurrent_producers_without_l
}
}
}
TEST(flow_refresh_strategy_test, bounded_queue_applies_backpressure_until_consumer_releases_capacity) {
Flow_Refresh_Test_Strategy strategy(Observer_State<>{}, 1);
{
auto frame = strategy.acquire_painter();
frame->value = 1;
}
std::atomic<bool> second_enqueued{};
std::thread producer([&] {
{
auto frame = strategy.acquire_painter();
frame->value = 2;
}
second_enqueued.store(true, std::memory_order_release);
second_enqueued.notify_all();
});
std::this_thread::sleep_for(std::chrono::milliseconds(20));
EXPECT_FALSE(second_enqueued.load(std::memory_order_acquire));
{
auto frame = strategy.acquire_renderer();
ASSERT_TRUE(frame);
EXPECT_EQ(frame->value, 1);
}
second_enqueued.wait(false, std::memory_order_acquire);
producer.join();
auto frame = strategy.acquire_renderer();
ASSERT_TRUE(frame);
EXPECT_EQ(frame->value, 2);
}
TEST(flow_refresh_strategy_test, concurrent_consumer_does_not_underflow_pending_count) {
Flow_Refresh_Test_Strategy strategy;
constexpr int producer_count = 4;
@@ -4,7 +4,6 @@
#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <deque>
#include <exception>
#include <functional>
#include <memory>
@@ -16,58 +15,10 @@
#include "renderive/render_graph/External_Operation.hpp"
#include "renderive/render_graph/Render_Plan.hpp"
#include "renderive/render_graph/detail/Render_Graph_Runtime.hpp"
#include "renderive/scheduling/detail/OneTBB_Runtime.hpp"
namespace {
class Single_Worker_Scheduler final {
public:
Single_Worker_Scheduler()
: worker_([this] { run(); }) {}
~Single_Worker_Scheduler() {
{
std::lock_guard lock(mutex_);
stopping_ = true;
}
ready_.notify_all();
worker_.join();
}
void schedule(std::function<void()> function) {
{
std::lock_guard lock(mutex_);
if (stopping_)
throw std::logic_error("scheduler is stopping");
queue_.push_back(std::move(function));
}
ready_.notify_one();
}
private:
void run() {
for (;;) {
std::function<void()> function;
{
std::unique_lock lock(mutex_);
ready_.wait(lock, [this] {
return stopping_ || !queue_.empty();
});
if (stopping_ && queue_.empty())
return;
function = std::move(queue_.front());
queue_.pop_front();
}
function();
}
}
std::mutex mutex_;
std::condition_variable ready_;
std::deque<std::function<void()>> queue_;
std::thread worker_;
bool stopping_{};
};
std::shared_ptr<const Render_Plan> two_node_plan() {
Render_Graph_Builder builder;
const auto submit = builder.emplace(
@@ -124,7 +75,6 @@ TEST(external_operation_test,
TEST(render_graph_runtime_test,
external_successor_stays_blocked_until_operation_completes) {
const auto plan = two_node_plan();
Single_Worker_Scheduler scheduler;
External_Operation_Source source;
std::vector<Node_Execution> execution_storage;
auto slots = execution_slots(*plan, execution_storage);
@@ -141,17 +91,11 @@ TEST(render_graph_runtime_test,
}
publish_executed.store(true, std::memory_order_release);
return Node_Execution_Result::completed();
},
[&](std::function<void()> function) {
scheduler.schedule(std::move(function));
},
[] { return 0U; });
});
std::thread execution([&] { runtime.execute(); });
submit_started.wait(false, std::memory_order_acquire);
EXPECT_FALSE(publish_executed.load(std::memory_order_acquire));
EXPECT_EQ(execution_storage[0].status,
Node_Execution_Status::waiting_external);
EXPECT_TRUE(source.complete());
execution.join();
@@ -166,7 +110,6 @@ TEST(render_graph_runtime_test,
TEST(render_graph_runtime_test,
failed_external_node_prevents_its_successor_from_running) {
const auto plan = two_node_plan();
Single_Worker_Scheduler scheduler;
External_Operation_Source source;
std::atomic<bool> submit_started{};
std::atomic<bool> publish_executed{};
@@ -182,11 +125,7 @@ TEST(render_graph_runtime_test,
}
publish_executed.store(true, std::memory_order_release);
return Node_Execution_Result::completed();
},
[&](std::function<void()> function) {
scheduler.schedule(std::move(function));
},
[] { return 0U; });
});
std::thread execution([&] {
try {
@@ -211,7 +150,6 @@ TEST(render_graph_runtime_test,
builder.emplace(401, 40, "GPU Fence", Render_Node_Kind::render);
Render_Plan_History history;
const auto plan = history.publish(std::move(builder).finish());
Single_Worker_Scheduler scheduler;
External_Operation_Source source;
std::atomic<bool> external_started{};
std::mutex probe_mutex;
@@ -224,15 +162,11 @@ TEST(render_graph_runtime_test,
external_started.store(true, std::memory_order_release);
external_started.notify_all();
return Node_Execution_Result::external(source.operation());
},
[&](std::function<void()> function) {
scheduler.schedule(std::move(function));
},
[] { return 0U; });
});
std::thread execution([&] { runtime.execute(); });
external_started.wait(false, std::memory_order_acquire);
scheduler.schedule([&] {
renderive::scheduling::detail::OneTBB_Runtime::instance().enqueue([&] {
{
std::lock_guard lock(probe_mutex);
probe_ran = true;
@@ -0,0 +1,28 @@
#include <gtest/gtest.h>
#include <atomic>
#include <condition_variable>
#include <mutex>
#include "renderive/scheduling/Scheduler.hpp"
#include "renderive/scheduling/detail/OneTBB_Runtime.hpp"
TEST(scheduler_test, reports_oneTBB_runtime_activity) {
const auto before = renderive::scheduling::scheduler_statistics();
EXPECT_GT(before.concurrency, 0U);
std::mutex mutex;
std::condition_variable completed;
bool done{};
renderive::scheduling::detail::OneTBB_Runtime::instance().enqueue([&] {
{
std::lock_guard lock(mutex);
done = true;
}
completed.notify_one();
});
{
std::unique_lock lock(mutex);
completed.wait(lock, [&] { return done; });
}
const auto after = renderive::scheduling::scheduler_statistics();
EXPECT_EQ(after.concurrency, before.concurrency);
EXPECT_GE(after.worker_entry_count, before.worker_entry_count);
EXPECT_GE(after.peak_workers, 1U);
}
+1
View File
@@ -55,6 +55,7 @@ if (RENDERIVE_BUILD_TESTS)
set(Renderive_Qt_test_target "Renderive_Qt_${Renderive_Qt_test_name}_${Renderive_Qt_test_hash}")
add_executable("${Renderive_Qt_test_target}" "${Renderive_Qt_test_source}")
target_link_libraries("${Renderive_Qt_test_target}" PRIVATE Renderive_Qt GTest::gtest)
renderive_stage_kernel_runtime("${Renderive_Qt_test_target}")
add_test(NAME "${Renderive_Qt_test_target}"
COMMAND "${CMAKE_COMMAND}" -E env
"PATH=${Renderive_Qt_runtime_root}/bin\;$ENV{PATH}"
+1
View File
@@ -46,6 +46,7 @@ if (RENDERIVE_BUILD_TESTS)
set(Renderive_render_2D_test_target "Renderive_render_2D_${Renderive_render_2D_test_name}_${Renderive_render_2D_test_hash}")
add_executable("${Renderive_render_2D_test_target}" "${Renderive_render_2D_test_source}")
target_link_libraries("${Renderive_render_2D_test_target}" PRIVATE Renderive_render_2D GTest::gtest_main)
renderive_stage_kernel_runtime("${Renderive_render_2D_test_target}")
add_test(NAME "${Renderive_render_2D_test_target}" COMMAND "${Renderive_render_2D_test_target}")
set_tests_properties("${Renderive_render_2D_test_target}" PROPERTIES LABELS "Renderive_render_2D")
list(APPEND Renderive_render_2D_test_targets "${Renderive_render_2D_test_target}")
+4 -3
View File
@@ -8,6 +8,7 @@
#include <algorithm>
#include <deque>
#include <optional>
#include "renderive/scheduling/Scheduler.hpp"
namespace renderive::detail {
namespace {
using Afterglow_History = Plottable_History_Real_Time_Data<std::vector<double>, std::deque<std::vector<double>>>;
@@ -102,7 +103,7 @@ void Afterglow::Impl::build_prepare_graph(Renderable_Graph_Builder& builder) {
: 0;
const int partition_count = partitioner.graph_partition_count(
state.partition_mode, state.partition_count.get(),
static_cast<int>(Scene_Base::task_executor_worker_count()), work_size, 4096);
static_cast<int>(renderive::scheduling::scheduler_concurrency()), work_size, 4096);
const auto prepare = add_prepare_task(
builder, "prepare", "Prepare Afterglow",
[this, partition_count](const Prepare_Render_Context& context) {
@@ -171,7 +172,7 @@ void Afterglow::Impl::build_paint_graph(Renderable_Graph_Builder& builder) {
: 0;
const int partition_count = partitioner.graph_partition_count(
state.partition_mode, state.partition_count.get(),
static_cast<int>(Scene_Base::task_executor_worker_count()), work_size, 4096);
static_cast<int>(renderive::scheduling::scheduler_concurrency()), work_size, 4096);
const auto paint_image = add_paint_task(
builder, "paint", "Paint Afterglow",
[this](Painter& painter, const Paint_Render_Context& context) {
@@ -284,7 +285,7 @@ void Afterglow::Impl::render_frame_completed(
const auto& state = output.properties;
if (partitioner.finish(
state.partition_mode, output.active_partitions, target_interval_ns,
static_cast<int>(Scene_Base::task_executor_worker_count()),
static_cast<int>(renderive::scheduling::scheduler_concurrency()),
output.work_size, 4096))
render_graph_changed();
}
+4 -3
View File
@@ -8,6 +8,7 @@
#include <cmath>
#include <iomanip>
#include <sstream>
#include "renderive/scheduling/Scheduler.hpp"
namespace renderive::detail {
namespace {
struct Spectrum_Interaction {
@@ -353,7 +354,7 @@ void Spectrum::Impl::build_prepare_graph(
: 0;
const int partition_count = partitioner.graph_partition_count(
state.partition_mode, state.partition_count.get(),
static_cast<int>(Scene_Base::task_executor_worker_count()), work_size, 128);
static_cast<int>(renderive::scheduling::scheduler_concurrency()), work_size, 128);
const auto prepare = add_prepare_task(
builder, "prepare", "Prepare Spectrum",
[this, partition_count](const Prepare_Render_Context& context) {
@@ -394,7 +395,7 @@ void Spectrum::Impl::build_paint_graph(
: 0;
const int partition_count = partitioner.graph_partition_count(
state.partition_mode, state.partition_count.get(),
static_cast<int>(Scene_Base::task_executor_worker_count()), work_size, 128);
static_cast<int>(renderive::scheduling::scheduler_concurrency()), work_size, 128);
const auto paint = add_paint_task(
builder, "paint", "Paint Spectrum",
[this, partition_count](Painter& painter,
@@ -599,7 +600,7 @@ void Spectrum::Impl::render_frame_completed(
const auto& state = output.properties;
if (partitioner.finish(
state.partition_mode, output.active_partitions, target_interval_ns,
static_cast<int>(Scene_Base::task_executor_worker_count()),
static_cast<int>(renderive::scheduling::scheduler_concurrency()),
output.work_size, 128))
render_graph_changed();
}
+4 -3
View File
@@ -9,6 +9,7 @@
#include <iomanip>
#include <optional>
#include <sstream>
#include "renderive/scheduling/Scheduler.hpp"
namespace renderive::detail {
namespace {
struct Waterfall_Row {
@@ -164,7 +165,7 @@ void Waterfall::Impl::build_prepare_graph(Renderable_Graph_Builder& builder) {
state, published_rows, frequency_axis->transform(view));
const int partition_count = partitioner.graph_partition_count(
state.partition_mode, state.partition_count.get(),
static_cast<int>(Scene_Base::task_executor_worker_count()), work_size, 4096);
static_cast<int>(renderive::scheduling::scheduler_concurrency()), work_size, 4096);
const auto prepare = add_prepare_task(
builder, "prepare", "Prepare Waterfall",
[this, partition_count](const Prepare_Render_Context& context) {
@@ -214,7 +215,7 @@ void Waterfall::Impl::build_paint_graph(Renderable_Graph_Builder& builder) {
state, published_rows, frequency_axis->transform(view));
const int count = partitioner.graph_partition_count(
state.partition_mode, state.partition_count.get(),
static_cast<int>(Scene_Base::task_executor_worker_count()), work_size, 4096);
static_cast<int>(renderive::scheduling::scheduler_concurrency()), work_size, 4096);
if (count == 0) {
builder.precede(builder.find("prepare"), paint_image);
}
@@ -327,7 +328,7 @@ void Waterfall::Impl::render_frame_completed(
const auto& state = output.properties;
if (partitioner.finish(
state.partition_mode, output.active_partitions, target_interval_ns,
static_cast<int>(Scene_Base::task_executor_worker_count()),
static_cast<int>(renderive::scheduling::scheduler_concurrency()),
output.work_size, 4096))
render_graph_changed();
}
+1 -1
View File
@@ -59,7 +59,7 @@ public:
Frame_Control frame_control;
template <class Function>
void with_final_color_cache(Function&& function) {
if (this->is_render_worker_thread()) {
if (this->is_render_execution_context()) {
function(static_cast<const Cache&>(final_color_cache_));
return;
}
+1
View File
@@ -67,6 +67,7 @@ function(renderive_stage_render_3D_runtime target)
if (NOT TARGET "${target}")
message(FATAL_ERROR "Cannot stage render_3D runtime for missing target: ${target}")
endif ()
renderive_stage_kernel_runtime("${target}")
if (WIN32)
get_property(Renderive_render_3D_runtime_files GLOBAL PROPERTY
RENDERIVE_RENDER_3D_RUNTIME_FILES)
+6 -6
View File
@@ -2,7 +2,7 @@
## 线程与数据边界
Renderive 负责状态发布、实时数据交换、Taskflow CPU 数据准备、事件入口、帧调度和像素帧发布。Datoviz 的 scene/visual mutation、FramePlan emission、DRP2 execution、GPU submission 与 readback 全部归单一 `Render_Domain` 所有。
Renderive 负责状态发布、实时数据交换、oneTBB CPU 数据准备、事件入口、帧调度和像素帧发布。Datoviz 的 scene/visual mutation、FramePlan emission、DRP2 execution、GPU submission 与 readback 全部归单一 `Render_Domain` 所有。
当前帧数据流:
@@ -10,7 +10,7 @@ Renderive 负责状态发布、实时数据交换、Taskflow CPU 数据准备、
Scene / Renderable state ----+
Point bulk data -------------+-- frame publication snapshot
|
+-- Taskflow: per-renderable CPU prepare
+-- oneTBB Flow Graph: per-renderable CPU prepare
| |
Kernel Event -----------------+ +-- immutable Prepared_Point
|
@@ -22,14 +22,14 @@ Kernel Event -----------------+ +-- immutable Prepared_Point
immutable Pixel_Frame
```
Taskflow worker 不调用 `dvz_*`。Datoviz FramePlan 继续负责 GPU upload/render/copy/readback 依赖,Renderive RenderPlan 只负责应用级 CPU prepare 依赖和末端单一 render node。
oneTBB worker 不调用 `dvz_*`。Datoviz FramePlan 继续负责 GPU upload/render/copy/readback 依赖,Renderive RenderPlan 只负责应用级 CPU prepare 依赖和末端单一 render node。
## 帧快照
- `Scene_Base::publish_frame_state(Abstract_Frame&)` 在 frame-control 的发布边界交换 Scene、Renderable 与实时数据。
- `Frame_Render_Snapshot` 保存对应帧的 Scene 状态、Renderable 元数据和类型安全的大数据共享快照。
- Manual、Low Latency、Playback 保存同一类快照;Playback 出队不会读取后来版本的数据。
- Taskflow prepare 只从帧快照读取输入并发布不可变 prepared output。
- oneTBB prepare 只从帧快照读取输入并发布不可变 prepared output。
- Scene render node 等待所有末端 prepare 依赖,然后在 `Render_Domain` 串行消费 prepared output。
## Point 封装
@@ -37,7 +37,7 @@ Taskflow worker 不调用 `dvz_*`。Datoviz FramePlan 继续负责 GPU upload/re
- `Point_Visual` 的小状态由 Renderable 状态策略管理。
- 点集的唯一权威来源是 `Multi_Double_Buffer_Strategy`;帧只持有已发布版本的不可变共享快照。
- positions、colors、sizes、sigma、angles、radii、normals 等派生数组只存在于 `Prepared_Point`,不保存为 Visual 成员。
- `Point_Scene` 复用 Kernel `Scene3D_Context`、frame-control、RenderPlan、Taskflow、capture 和 observer。
- `Point_Scene` 复用 Kernel `Scene3D_Context`、frame-control、RenderPlan、oneTBB Flow Graph、capture 和 observer。
- Pointer、wheel、key 直接保留 Kernel Event 语义并投递到 `Render_Domain`,没有第二套输入命令或收集队列。
- 后端只应用 prepared data,并负责 Datoviz/Vulkan 资源和不可变 `Pixel_Frame` 输出。
@@ -49,7 +49,7 @@ Taskflow worker 不调用 `dvz_*`。Datoviz FramePlan 继续负责 GPU upload/re
| Point 大批量数据 | `Point_Visual` 双缓冲策略 | 不可变共享快照 |
| viewport、clear color、visual family | `Scene3D_Context` 状态策略 | 发布帧快照 |
| 输入 | Kernel Event | `Render_Domain` 直接处理 |
| CPU 派生数组 | 当前帧 Taskflow prepare | `Prepared_Point` |
| CPU 派生数组 | 当前帧 oneTBB prepare | `Prepared_Point` |
| Datoviz/Vulkan 资源 | `Datoviz_Visual_Backend` | 仅 `Render_Domain` |
| 输出像素 | 最近完成的 render node | `Point_Scene::latest_frame()` |
+21 -9
View File
@@ -1,6 +1,9 @@
param(
[string]$DatovizBuildDirectory,
[string]$CTestExecutable = 'ctest'
[string]$CTestExecutable = 'ctest',
[ValidateSet('Debug', 'Release', 'RelWithDebInfo', 'MinSizeRel')]
[string]$BuildType = 'Debug',
[string]$Toolchain = 'vs2022'
)
$ErrorActionPreference = 'Stop'
@@ -8,10 +11,14 @@ $ErrorActionPreference = 'Stop'
$render3DRoot = $PSScriptRoot
$workspaceRoot = Split-Path -Parent $render3DRoot
$aeRoot = Split-Path -Parent (Split-Path -Parent $workspaceRoot)
$cacheVariant = "${Toolchain}_${BuildType}"
$pthreadsRuntimeDirectory = Join-Path $aeRoot "cached_external_library\${cacheVariant}_render_3D\install\PThreads4W\x86_64\${BuildType}\bin"
$oneTBBRuntimeDirectory = Join-Path $aeRoot "cached_external_library\${cacheVariant}_render_kernel\install\oneTBB\bin"
$windowsSdkRuntimeDirectory = 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64'
$runtimeDirectories = @(
(Join-Path $aeRoot 'cached_external_library\vs2022_Debug_render_3D\install\PThreads4W\x86_64\Debug\bin')
'C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64'
$pthreadsRuntimeDirectory
$oneTBBRuntimeDirectory
$windowsSdkRuntimeDirectory
)
$vulkanSdkRoot = if ($env:VULKAN_SDK) {
@@ -20,9 +27,13 @@ $vulkanSdkRoot = if ($env:VULKAN_SDK) {
'C:\VulkanSDK\1.4.357.0'
}
$runtimeDirectories += Join-Path $vulkanSdkRoot 'Bin'
$pthreadRuntimeName = if ($BuildType -eq 'Debug') { 'pthreadVC3d.dll' } else { 'pthreadVC3.dll' }
$oneTBBRuntimeName = if ($BuildType -eq 'Debug') { 'tbb12_debug.dll' } else { 'tbb12.dll' }
$shadercRuntimeLibrary = Join-Path $vulkanSdkRoot 'Bin\shaderc_shared.dll'
$runtimeLibraries = @(
(Join-Path $runtimeDirectories[0] 'pthreadVC3d.dll')
(Join-Path $vulkanSdkRoot 'Bin\shaderc_shared.dll')
(Join-Path $pthreadsRuntimeDirectory $pthreadRuntimeName)
(Join-Path $oneTBBRuntimeDirectory $oneTBBRuntimeName)
$shadercRuntimeLibrary
)
$linkDirectories = @(
'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.44.35207\lib\x64'
@@ -60,7 +71,7 @@ foreach ($linkDirectory in $linkDirectories) {
$env:LIB = $libEntries -join ';'
$env:VULKAN_SDK = $vulkanSdkRoot
$env:DVZ_SHADERC_RUNTIME_LIBRARY = $runtimeLibraries[1]
$env:DVZ_SHADERC_RUNTIME_LIBRARY = $shadercRuntimeLibrary
$env:DVZ_WHEEL_RUNTIME_DIRS = Join-Path $vulkanSdkRoot 'Bin'
# Datoviz's upstream tests use POSIX-style absolute /tmp paths. On Windows those paths resolve
@@ -72,8 +83,9 @@ $env:TEMP = $windowsTempDirectory
$env:TMP = $windowsTempDirectory
Write-Host 'Renderive render_3D runtime environment is ready.'
Write-Host " PThreads4W: $($runtimeDirectories[0])"
Write-Host " Windows SDK: $($runtimeDirectories[1])"
Write-Host " PThreads4W: $pthreadsRuntimeDirectory"
Write-Host " oneTBB: $oneTBBRuntimeDirectory"
Write-Host " Windows SDK: $windowsSdkRuntimeDirectory"
Write-Host " Link roots: $($linkDirectories -join '; ')"
Write-Host " Vulkan SDK: $vulkanSdkRoot"
Write-Host " shaderc: $env:DVZ_SHADERC_RUNTIME_LIBRARY"
+1 -1
View File
@@ -25,7 +25,7 @@ struct Captured_Point {
std::uint64_t data_revision{};
};
// Immutable, frame-local CPU output. Taskflow workers create this data; only
// Immutable, frame-local CPU output. oneTBB workers create this data; only
// the Datoviz render-domain task consumes it.
struct Prepared_Point {
Point_State state;