diff --git a/Kernel/CMakeLists.txt b/Kernel/CMakeLists.txt index 119696f..af30f94 100644 --- a/Kernel/CMakeLists.txt +++ b/Kernel/CMakeLists.txt @@ -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 "$" ) 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 + "$" + "$" + 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}") diff --git a/Kernel/cmake/rely.cmake b/Kernel/cmake/rely.cmake index 67e7a22..92128b7 100644 --- a/Kernel/cmake/rely.cmake +++ b/Kernel/cmake/rely.cmake @@ -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" diff --git a/Kernel/src/renderive/frame_control/strategy/flow/Flow_Refresh_Strategy.hpp b/Kernel/src/renderive/frame_control/strategy/flow/Flow_Refresh_Strategy.hpp index b8e44fe..0b87789 100644 --- a/Kernel/src/renderive/frame_control/strategy/flow/Flow_Refresh_Strategy.hpp +++ b/Kernel/src/renderive/frame_control/strategy/flow/Flow_Refresh_Strategy.hpp @@ -1,8 +1,10 @@ #pragma once +#include #include #include -#include +#include #include +#include #include #include #include "renderive/base/Concepts.hpp" @@ -93,8 +95,9 @@ public: Memory_Resource_Unique_Ptr frame_; std::unique_lock 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> frames_; + using Frame_Ptr = Memory_Resource_Unique_Ptr; + oneapi::tbb::concurrent_bounded_queue frames_; std::uint64_t next_sequence_{}; std::uint64_t real_time_data_update_sequence_{}; Real_Time_Data_Update_State last_real_time_data_update_; diff --git a/Kernel/src/renderive/frame_control/strategy/flow/Flow_Refresh_Strategy.inl b/Kernel/src/renderive/frame_control/strategy/flow/Flow_Refresh_Strategy.inl index 0641f9a..c32cf85 100644 --- a/Kernel/src/renderive/frame_control/strategy/flow/Flow_Refresh_Strategy.inl +++ b/Kernel/src/renderive/frame_control/strategy/flow/Flow_Refresh_Strategy.inl @@ -19,13 +19,13 @@ Flow_Refresh_Strategy::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 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::max(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::Render_Lease::Render_Lease( lease_lock_ = std::unique_lock(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 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::max(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::Render_Lease::~Render_Lease { std::lock_guard 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::max(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::Render_Lease::get() co return frame_.get(); } template -Flow_Refresh_Strategy::Flow_Refresh_Strategy(Observer observer) - : Flow_Refresh_Strategy(*std::pmr::get_default_resource(), std::move(observer)) {} +Flow_Refresh_Strategy::Flow_Refresh_Strategy(Observer observer, std::size_t capacity) + : Flow_Refresh_Strategy(*std::pmr::get_default_resource(), std::move(observer), capacity) {} template -Flow_Refresh_Strategy::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::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(capacity)); +} template auto Flow_Refresh_Strategy::acquire_painter() -> Painter_Lease { return Painter_Lease(*this); @@ -179,14 +180,13 @@ auto Flow_Refresh_Strategy::frame_control_state() } template std::size_t Flow_Refresh_Strategy::pending_frame_count() const { - std::lock_guard lock(state_mutex_); - return frames_.size(); + return static_cast(std::max(0, frames_.size())); } template auto Flow_Refresh_Strategy::state() const -> State { std::lock_guard lock(state_mutex_); State result = state_; - result.pending_frame_count = frames_.size(); + result.pending_frame_count = static_cast(std::max(0, frames_.size())); return result; } template diff --git a/Kernel/src/renderive/render_graph/detail/Render_Graph_Runtime.cpp b/Kernel/src/renderive/render_graph/detail/Render_Graph_Runtime.cpp index e1adda5..a8bb033 100644 --- a/Kernel/src/renderive/render_graph/detail/Render_Graph_Runtime.cpp +++ b/Kernel/src/renderive/render_graph/detail/Render_Graph_Runtime.cpp @@ -1,6 +1,5 @@ #include "Render_Graph_Runtime.hpp" - -#include +#include #include #include #include @@ -8,347 +7,169 @@ #include #include #include - +#include +#include "renderive/scheduling/detail/OneTBB_Runtime.hpp" namespace renderive::render_graph::detail { - -struct Render_Graph_Runtime::State - : std::enable_shared_from_this { - 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 { + using Message = oneapi::tbb::flow::continue_msg; + using Ready_Node = oneapi::tbb::flow::continue_node; + using Execute_Node_Type = oneapi::tbb::flow::async_node; + struct Node { + std::unique_ptr ready; + std::unique_ptr execute; + bool root{}; }; - - State(const Render_Plan& plan, - std::span 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 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 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 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 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(graph, [this, index](const Message&) { + mark_ready(index); + return Message{}; + }); + nodes[index].execute = std::make_unique(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 remaining; - remaining.reserve(nodes.size()); - std::vector 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 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& 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& 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::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 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 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& 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 nodes; - std::vector> successors; + oneapi::tbb::flow::graph graph; + std::vector nodes; std::vector 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 executions, - Execute_Node execute_node, - Schedule schedule, - Current_Worker_Id current_worker_id) - : state_(std::make_shared( - 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 executions, Execute_Node execute_node) + : state_(std::make_shared(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 +} diff --git a/Kernel/src/renderive/render_graph/detail/Render_Graph_Runtime.hpp b/Kernel/src/renderive/render_graph/detail/Render_Graph_Runtime.hpp index cf00339..6c562b2 100644 --- a/Kernel/src/renderive/render_graph/detail/Render_Graph_Runtime.hpp +++ b/Kernel/src/renderive/render_graph/detail/Render_Graph_Runtime.hpp @@ -1,41 +1,24 @@ #pragma once - #include -#include #include #include #include - #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; - using Schedule = std::function)>; - using Current_Worker_Id = std::function; - - Render_Graph_Runtime(const Render_Plan& plan, - std::span executions, - Execute_Node execute_node, - Schedule schedule, - Current_Worker_Id current_worker_id); + using Execute_Node = std::function; + Render_Graph_Runtime(const Render_Plan& plan, std::span 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_; }; - -} // namespace renderive::render_graph::detail +} diff --git a/Kernel/src/renderive/scene/base/Scene_Base.cpp b/Kernel/src/renderive/scene/base/Scene_Base.cpp index 05a374a..0a201df 100644 --- a/Kernel/src/renderive/scene/base/Scene_Base.cpp +++ b/Kernel/src/renderive/scene/base/Scene_Base.cpp @@ -13,23 +13,62 @@ #include #include -#include +#include +#include #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; + 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 operations_; + oneapi::tbb::flow::function_node 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(*this)), memory_domain_(std::allocate_shared(std::pmr::polymorphic_allocator(&upstream_memory_resource), upstream_memory_resource)), impl_(std::move(impl)), + execution_context_(std::make_unique()), 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::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()}; - task.plan = compile_render_plan(*snapshot, task); - task.topology = - std::make_shared(topology_snapshot()); - task.completion = std::make_shared(); + auto task = std::make_shared(memory_resource()); + task->frame = frame ? Render_Task::Frame{std::ref(*frame)} : Render_Task::Frame{std::make_shared()}; + task->plan = compile_render_plan(*snapshot, *task); + task->topology = std::make_shared(topology_snapshot()); + task->completion = std::make_shared(); 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 lock(task_mutex_); + --pending_operations_; + } + render_completed_.notify_all(); + }); + } catch (...) { + { + std::lock_guard lock(task_mutex_); + --pending_operations_; + } + render_completed_.notify_all(); + throw; + } std::size_t deferred_render_count{}; { std::lock_guard 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 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 edit) { - std::lock_guard 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 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 operation) { + { + std::lock_guard 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 lock(task_mutex_); + --pending_operations_; + } + render_completed_.notify_all(); + }); + } catch (...) { + { + std::lock_guard 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 lock(task_mutex_); - if (!runtime_started_) { - owner->d_func().reset_render_graph(); - notify_model_dirty(); - return; + { + std::lock_guard 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 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 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 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 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(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 edits; - { - std::unique_lock 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 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 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 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 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 function) { - executor.silent_async(std::move(function)); - }, - [&executor] { - const int worker = executor.this_worker_id(); - return worker < 0 - ? std::numeric_limits::max() - : static_cast(worker); }); runtime.execute(); diff --git a/Kernel/src/renderive/scene/base/Scene_Base.hpp b/Kernel/src/renderive/scene/base/Scene_Base.hpp index 6d5bb0c..5a1e49d 100644 --- a/Kernel/src/renderive/scene/base/Scene_Base.hpp +++ b/Kernel/src/renderive/scene/base/Scene_Base.hpp @@ -4,14 +4,12 @@ #include #include #include -#include #include #include #include #include #include #include -#include #include #include #include @@ -174,7 +172,6 @@ public: [[nodiscard]] std::vector 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 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 completion; }; - struct Renderable_Edit_Command { - std::function 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 edit); + void submit_operation(std::function operation); void request_render_graph_rebuild(Renderable_Base& renderable); [[nodiscard]] std::shared_ptr snapshot_live_model(); [[nodiscard]] std::shared_ptr @@ -266,7 +259,8 @@ private: void publish_frame_state_locked(); std::shared_ptr compile_render_plan( const Frame_Render_Snapshot& snapshot, Render_Task& task); - void render_loop(); + void execute_render_task(std::shared_ptr task); + void execute_renderable_edit(std::function 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_; std::shared_ptr memory_domain_; std::unique_ptr impl_; + std::unique_ptr execution_context_; std::atomic_bool model_dirty_{true}; std::pmr::unordered_map renderables_; renderive::scene::dependency::Dependency_Resolver @@ -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_queue_; std::shared_ptr 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_; diff --git a/Kernel/src/renderive/scheduling/Scheduler.hpp b/Kernel/src/renderive/scheduling/Scheduler.hpp new file mode 100644 index 0000000..c6d4d78 --- /dev/null +++ b/Kernel/src/renderive/scheduling/Scheduler.hpp @@ -0,0 +1,18 @@ +#pragma once +#include +#include +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; +} diff --git a/Kernel/src/renderive/scheduling/detail/OneTBB_Runtime.cpp b/Kernel/src/renderive/scheduling/detail/OneTBB_Runtime.cpp new file mode 100644 index 0000000..24b13e2 --- /dev/null +++ b/Kernel/src/renderive/scheduling/detail/OneTBB_Runtime.cpp @@ -0,0 +1,98 @@ +#include "OneTBB_Runtime.hpp" +#include +#include +#include +#include +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()), arena_(oneapi::tbb::task_arena::automatic) { + arena_.initialize(); + observer_ = std::make_unique(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::max(); + return static_cast(index); +} +Scheduler_Statistics OneTBB_Runtime::statistics() const noexcept { + return { + static_cast(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(); +} +} diff --git a/Kernel/src/renderive/scheduling/detail/OneTBB_Runtime.hpp b/Kernel/src/renderive/scheduling/detail/OneTBB_Runtime.hpp new file mode 100644 index 0000000..b87ebc9 --- /dev/null +++ b/Kernel/src/renderive/scheduling/detail/OneTBB_Runtime.hpp @@ -0,0 +1,29 @@ +#pragma once +#include +#include +#include +#include +#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 + void enqueue(F&& task) { + arena_.enqueue(std::forward(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_; + oneapi::tbb::task_arena arena_; + std::unique_ptr observer_; +}; +} diff --git a/Kernel/tests/renderive/frame_control/strategy/flow/Flow_Refresh_Strategy_Test.cpp b/Kernel/tests/renderive/frame_control/strategy/flow/Flow_Refresh_Strategy_Test.cpp index 544dfe7..181371a 100644 --- a/Kernel/tests/renderive/frame_control/strategy/flow/Flow_Refresh_Strategy_Test.cpp +++ b/Kernel/tests/renderive/frame_control/strategy/flow/Flow_Refresh_Strategy_Test.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -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 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 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; diff --git a/Kernel/tests/renderive/render_graph/Render_Graph_Runtime_Test.cpp b/Kernel/tests/renderive/render_graph/Render_Graph_Runtime_Test.cpp index 88b4fe6..48a14f2 100644 --- a/Kernel/tests/renderive/render_graph/Render_Graph_Runtime_Test.cpp +++ b/Kernel/tests/renderive/render_graph/Render_Graph_Runtime_Test.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -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 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 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> queue_; - std::thread worker_; - bool stopping_{}; -}; - std::shared_ptr 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 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 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 submit_started{}; std::atomic 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 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 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 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; diff --git a/Kernel/tests/renderive/scheduling/Scheduler_Test.cpp b/Kernel/tests/renderive/scheduling/Scheduler_Test.cpp new file mode 100644 index 0000000..6726a73 --- /dev/null +++ b/Kernel/tests/renderive/scheduling/Scheduler_Test.cpp @@ -0,0 +1,28 @@ +#include +#include +#include +#include +#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); +} diff --git a/Qt/CMakeLists.txt b/Qt/CMakeLists.txt index 81809f0..75e297a 100644 --- a/Qt/CMakeLists.txt +++ b/Qt/CMakeLists.txt @@ -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}" diff --git a/render_2D/CMakeLists.txt b/render_2D/CMakeLists.txt index 884da06..ded4ad4 100644 --- a/render_2D/CMakeLists.txt +++ b/render_2D/CMakeLists.txt @@ -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}") diff --git a/render_2D/render_2D/plottable/Afterglow.cpp b/render_2D/render_2D/plottable/Afterglow.cpp index 1c1cabc..cedec42 100644 --- a/render_2D/render_2D/plottable/Afterglow.cpp +++ b/render_2D/render_2D/plottable/Afterglow.cpp @@ -8,6 +8,7 @@ #include #include #include +#include "renderive/scheduling/Scheduler.hpp" namespace renderive::detail { namespace { using Afterglow_History = Plottable_History_Real_Time_Data, std::deque>>; @@ -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(Scene_Base::task_executor_worker_count()), work_size, 4096); + static_cast(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(Scene_Base::task_executor_worker_count()), work_size, 4096); + static_cast(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(Scene_Base::task_executor_worker_count()), + static_cast(renderive::scheduling::scheduler_concurrency()), output.work_size, 4096)) render_graph_changed(); } diff --git a/render_2D/render_2D/plottable/Spectrum.cpp b/render_2D/render_2D/plottable/Spectrum.cpp index 75bf0a1..2739a43 100644 --- a/render_2D/render_2D/plottable/Spectrum.cpp +++ b/render_2D/render_2D/plottable/Spectrum.cpp @@ -8,6 +8,7 @@ #include #include #include +#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(Scene_Base::task_executor_worker_count()), work_size, 128); + static_cast(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(Scene_Base::task_executor_worker_count()), work_size, 128); + static_cast(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(Scene_Base::task_executor_worker_count()), + static_cast(renderive::scheduling::scheduler_concurrency()), output.work_size, 128)) render_graph_changed(); } diff --git a/render_2D/render_2D/plottable/Waterfall.cpp b/render_2D/render_2D/plottable/Waterfall.cpp index 95ba6e8..c2b1277 100644 --- a/render_2D/render_2D/plottable/Waterfall.cpp +++ b/render_2D/render_2D/plottable/Waterfall.cpp @@ -9,6 +9,7 @@ #include #include #include +#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(Scene_Base::task_executor_worker_count()), work_size, 4096); + static_cast(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(Scene_Base::task_executor_worker_count()), work_size, 4096); + static_cast(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(Scene_Base::task_executor_worker_count()), + static_cast(renderive::scheduling::scheduler_concurrency()), output.work_size, 4096)) render_graph_changed(); } diff --git a/render_2D/render_2D/scene/Scene_Context.hpp b/render_2D/render_2D/scene/Scene_Context.hpp index 5e7b500..760ef0b 100644 --- a/render_2D/render_2D/scene/Scene_Context.hpp +++ b/render_2D/render_2D/scene/Scene_Context.hpp @@ -59,7 +59,7 @@ public: Frame_Control frame_control; template void with_final_color_cache(Function&& function) { - if (this->is_render_worker_thread()) { + if (this->is_render_execution_context()) { function(static_cast(final_color_cache_)); return; } diff --git a/render_3D/CMakeLists.txt b/render_3D/CMakeLists.txt index 8b3b5b3..a277db7 100644 --- a/render_3D/CMakeLists.txt +++ b/render_3D/CMakeLists.txt @@ -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) diff --git a/render_3D/DATOVIZ_MIGRATION_AUDIT.md b/render_3D/DATOVIZ_MIGRATION_AUDIT.md index 85ed80e..7657e04 100644 --- a/render_3D/DATOVIZ_MIGRATION_AUDIT.md +++ b/render_3D/DATOVIZ_MIGRATION_AUDIT.md @@ -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()` | diff --git a/render_3D/env.ps1 b/render_3D/env.ps1 index 376eaf7..0fcda53 100644 --- a/render_3D/env.ps1 +++ b/render_3D/env.ps1 @@ -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" diff --git a/render_3D/render_3D/detail/Point_Core.h b/render_3D/render_3D/detail/Point_Core.h index 08e0140..ef40fda 100644 --- a/render_3D/render_3D/detail/Point_Core.h +++ b/render_3D/render_3D/detail/Point_Core.h @@ -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;