This commit is contained in:
2026-08-12 09:45:18 +08:00
parent 9331b4cbf6
commit 0910b73ea4
22 changed files with 439 additions and 98 deletions
+119 -10
View File
@@ -1,5 +1,6 @@
#include "Scene_Base.hpp"
#include <algorithm>
#include <chrono>
#include <memory_resource>
#include <stdexcept>
#include <string>
@@ -11,6 +12,12 @@
#include "renderive/state/base/State_Strategy_Base.hpp"
static_assert(TF_VERSION == 400100, "Renderive requires Taskflow 4.1.0");
namespace {
std::uint64_t steady_now_ns() noexcept {
return static_cast<std::uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count());
}
template <class Node_Accessor>
Scene_Base::Renderable_List topological_order(
const Scene_Base::Renderable_List& renderables,
@@ -331,6 +338,50 @@ std::vector<Scene_Base::Const_Renderable> Scene_Base::paint_order_snapshot() con
const auto ordered = display_order(*cache_renderables_);
return {ordered.begin(), ordered.end()};
}
Task_Execution_Snapshot Scene_Base::task_execution_snapshot() const noexcept {
Task_Execution_Snapshot snapshot;
snapshot.worker_count = task_executor_worker_count();
snapshot.render_sequence =
task_execution_render_sequence_.load(std::memory_order_acquire);
snapshot.graph_node_count =
task_execution_node_count_.load(std::memory_order_acquire);
snapshot.completed_count =
task_execution_completed_count_.load(std::memory_order_acquire);
snapshot.running_count =
task_execution_running_count_.load(std::memory_order_acquire);
snapshot.peak_parallelism =
task_execution_peak_parallelism_.load(std::memory_order_acquire);
snapshot.failed_count =
task_execution_failed_count_.load(std::memory_order_acquire);
const std::size_t accounted = snapshot.completed_count + snapshot.running_count;
snapshot.pending_count = accounted < snapshot.graph_node_count
? snapshot.graph_node_count - accounted
: 0;
{
std::lock_guard lock(task_mutex_);
if (task_pending_)
snapshot.state = Task_Execution_State::Queued;
else if (rendering_)
snapshot.state = Task_Execution_State::Running;
else if (current_completion_ && current_completion_->completed)
snapshot.state = current_completion_->exception
? Task_Execution_State::Failed
: Task_Execution_State::Completed;
}
snapshot.duration_ns =
task_execution_duration_ns_.load(std::memory_order_acquire);
if (snapshot.state == Task_Execution_State::Running) {
const auto started =
task_execution_started_ns_.load(std::memory_order_acquire);
const auto now = steady_now_ns();
if (started != 0 && now >= started)
snapshot.duration_ns = now - started;
}
return snapshot;
}
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();
}
@@ -459,6 +510,20 @@ void Scene_Base::execute_taskflow(Render_Task& task) {
};
std::pmr::monotonic_buffer_resource scratch_resource(&memory_resource());
tf::Taskflow scene_graph;
std::size_t planned_task_count{};
const auto tracked = [this, &planned_task_count](auto function) {
++planned_task_count;
return [this, function = std::move(function)]() mutable {
task_execution_started();
try {
function();
task_execution_finished(false);
} catch (...) {
task_execution_finished(true);
throw;
}
};
};
std::pmr::unordered_map<Renderable_Base*, std::size_t> indices(&scratch_resource);
indices.reserve(task.render_order.size());
std::pmr::vector<bool> render_flags(task.render_order.size(), false, &scratch_resource);
@@ -500,21 +565,21 @@ void Scene_Base::execute_taskflow(Render_Task& task) {
std::pmr::vector<tf::Task> internal_tasks(&scratch_resource);
internal_tasks.reserve(nodes.size());
for (const auto& node : nodes) {
internal_tasks.push_back(module.graph.emplace([this, function = node.function, context] {
internal_tasks.push_back(module.graph.emplace(tracked([this, function = node.function, context] {
Render_Execution_Scope scope(*this);
function(context);
}).name(std::string(node.name)));
})).name(std::string(node.name)));
}
for (std::size_t node_index = 0; node_index < nodes.size(); ++node_index) {
for (std::size_t successor : nodes[node_index].successors) {
internal_tasks[node_index].precede(internal_tasks.at(successor));
}
}
tf::Task mark_rendered = module.graph.emplace([&renderable, render_revision] {
tf::Task mark_rendered = module.graph.emplace(tracked([&renderable, render_revision] {
renderable.mark_rendered(render_revision);
}).name("cache_publish");
})).name("cache_publish");
if (internal_tasks.empty()) {
module.graph.emplace([] {}).precede(mark_rendered);
module.graph.emplace(tracked([] {})).precede(mark_rendered);
} else {
for (std::size_t node_index = 0; node_index < nodes.size(); ++node_index) {
if (nodes[node_index].successors.empty()) {
@@ -523,7 +588,7 @@ void Scene_Base::execute_taskflow(Render_Task& task) {
}
}
} else {
module.graph.emplace([] {}).name("cache_hit");
module.graph.emplace(tracked([] {})).name("cache_hit");
}
module.module_task = scene_graph.composed_of(module.graph).name("renderable");
}
@@ -538,18 +603,62 @@ void Scene_Base::execute_taskflow(Render_Task& task) {
}
}
const Scene_Render_Context final_context{this, task.frame_control_state, task.scene_state_revision, task.render_sequence, nullptr, nullptr};
tf::Task final_task = scene_graph.emplace([this, final_context, &task] {
tf::Task final_task = scene_graph.emplace(tracked([this, final_context, &task] {
Render_Execution_Scope scope(*this);
generate_final_color_cache(final_context, task.display_order);
}).name("final_color_cache");
})).name("final_color_cache");
if (modules.empty()) {
scene_graph.emplace([] {}).precede(final_task);
scene_graph.emplace(tracked([] {})).precede(final_task);
} else {
for (Module& module : modules) {
module.module_task.precede(final_task);
}
}
Execution_Context::executor().run(scene_graph).get();
begin_task_execution(task.render_sequence);
set_task_execution_node_count(planned_task_count);
try {
Execution_Context::executor().run(scene_graph).get();
} catch (...) {
finish_task_execution();
throw;
}
finish_task_execution();
}
void Scene_Base::begin_task_execution(std::uint64_t render_sequence) noexcept {
task_execution_render_sequence_.store(render_sequence, std::memory_order_release);
task_execution_node_count_.store(0, std::memory_order_release);
task_execution_completed_count_.store(0, std::memory_order_release);
task_execution_running_count_.store(0, std::memory_order_release);
task_execution_peak_parallelism_.store(0, std::memory_order_release);
task_execution_failed_count_.store(0, std::memory_order_release);
task_execution_duration_ns_.store(0, std::memory_order_release);
task_execution_started_ns_.store(steady_now_ns(), std::memory_order_release);
}
void Scene_Base::set_task_execution_node_count(std::size_t count) noexcept {
task_execution_node_count_.store(count, std::memory_order_release);
}
void Scene_Base::task_execution_started() noexcept {
const std::size_t active =
task_execution_running_count_.fetch_add(1, std::memory_order_acq_rel) + 1;
std::size_t peak =
task_execution_peak_parallelism_.load(std::memory_order_acquire);
while (peak < active &&
!task_execution_peak_parallelism_.compare_exchange_weak(
peak, active, std::memory_order_acq_rel, std::memory_order_acquire)) {}
}
void Scene_Base::task_execution_finished(bool failed) noexcept {
if (failed)
task_execution_failed_count_.fetch_add(1, std::memory_order_acq_rel);
task_execution_completed_count_.fetch_add(1, std::memory_order_acq_rel);
task_execution_running_count_.fetch_sub(1, std::memory_order_acq_rel);
}
void Scene_Base::finish_task_execution() noexcept {
const auto started =
task_execution_started_ns_.load(std::memory_order_acquire);
const auto now = steady_now_ns();
task_execution_duration_ns_.store(
started != 0 && now >= started ? now - started : 0,
std::memory_order_release);
}
void Scene_Base::validate_renderable_scene(const Renderable_Base& renderable) const {
if (renderable.real_time_data_state_->scene_lifetime.get() != scene_lifetime_.get()) {
+18 -1
View File
@@ -1,5 +1,6 @@
#pragma once
#include <array>
#include <atomic>
#include <condition_variable>
#include <concepts>
#include <cstdint>
@@ -15,6 +16,7 @@
#include "renderive/renderable/base/Renderable_Base.hpp"
#include "Scene_Lifetime.hpp"
#include "Scene_Render_Context.hpp"
#include "Task_Execution_Snapshot.hpp"
class Color_Cache;
class Scene_Base {
private:
@@ -65,6 +67,8 @@ public:
std::size_t renderable_count() const;
Topology_Snapshot topology_snapshot() const;
std::vector<Const_Renderable> paint_order_snapshot() const;
[[nodiscard]] Task_Execution_Snapshot task_execution_snapshot() const noexcept;
[[nodiscard]] static std::size_t task_executor_worker_count() noexcept;
std::pmr::memory_resource& memory_resource() const noexcept;
std::pmr::memory_resource& upstream_memory_resource() const noexcept;
Frame_Control_Strategy_Base& frame_control_strategy();
@@ -114,6 +118,11 @@ private:
};
void render_loop();
void execute_taskflow(Render_Task& task);
void begin_task_execution(std::uint64_t render_sequence) noexcept;
void set_task_execution_node_count(std::size_t count) noexcept;
void task_execution_started() noexcept;
void task_execution_finished(bool failed) noexcept;
void finish_task_execution() noexcept;
void validate_renderable_scene(const Renderable_Base& renderable) const;
bool is_renderable_attached_locked(const Renderable_Base& renderable) const;
void validate_renderable_attached_locked(const Renderable_Base& renderable) const;
@@ -125,7 +134,7 @@ private:
Renderable_List* render_renderables_;
Renderable_List* cache_renderables_;
mutable std::mutex renderable_mutex_;
std::recursive_mutex task_mutex_;
mutable std::recursive_mutex task_mutex_;
std::condition_variable_any task_ready_;
std::condition_variable_any render_completed_;
std::thread worker_;
@@ -138,6 +147,14 @@ private:
bool task_pending_{};
bool rendering_{};
bool stop_{};
std::atomic<std::uint64_t> task_execution_render_sequence_{};
std::atomic<std::size_t> task_execution_node_count_{};
std::atomic<std::size_t> task_execution_completed_count_{};
std::atomic<std::size_t> task_execution_running_count_{};
std::atomic<std::size_t> task_execution_peak_parallelism_{};
std::atomic<std::size_t> task_execution_failed_count_{};
std::atomic<std::uint64_t> task_execution_started_ns_{};
std::atomic<std::uint64_t> task_execution_duration_ns_{};
};
class Scene_2D_Base : public Scene_Base {
public:
@@ -0,0 +1,25 @@
#pragma once
#include <cstddef>
#include <cstdint>
enum class Task_Execution_State : std::uint8_t {
Idle,
Queued,
Running,
Completed,
Failed
};
struct Task_Execution_Snapshot {
Task_Execution_State state{Task_Execution_State::Idle};
std::size_t worker_count{};
std::uint64_t render_sequence{};
std::size_t graph_node_count{};
std::size_t completed_count{};
std::size_t running_count{};
std::size_t pending_count{};
std::size_t peak_parallelism{};
std::size_t failed_count{};
std::uint64_t duration_ns{};
};
@@ -129,8 +129,25 @@ TEST(renderable_task_graph_concurrency_test, runs_independent_internal_tasks_con
auto renderable = std::make_shared<Renderable_Task_Graph_Concurrency_Test_Renderable>(scene, active, maximum);
scene.attach_renderable(renderable);
scene.render();
while (active.load(std::memory_order_acquire) == 0)
std::this_thread::yield();
const auto running = scene.task_execution_snapshot();
EXPECT_EQ(running.state, Task_Execution_State::Running);
EXPECT_EQ(running.graph_node_count, 4u);
EXPECT_GT(running.running_count, 0u);
EXPECT_LT(running.completed_count, running.graph_node_count);
scene.wait_for_render();
EXPECT_GE(maximum.load(std::memory_order_acquire), 2);
const auto execution = scene.task_execution_snapshot();
EXPECT_EQ(execution.state, Task_Execution_State::Completed);
EXPECT_EQ(execution.worker_count, Scene_Base::task_executor_worker_count());
EXPECT_EQ(execution.graph_node_count, 4u);
EXPECT_EQ(execution.completed_count, execution.graph_node_count);
EXPECT_EQ(execution.running_count, 0u);
EXPECT_EQ(execution.pending_count, 0u);
EXPECT_GE(execution.peak_parallelism, 2u);
EXPECT_EQ(execution.failed_count, 0u);
EXPECT_GT(execution.duration_ns, 0u);
}
#endif
TEST(renderable_task_graph_test, rejects_tasks_from_different_graphs) {
@@ -38,6 +38,12 @@ TEST(scene_base_test, wait_for_render_propagates_background_render_failure) {
scene.attach_renderable(renderable);
scene.render();
EXPECT_THROW(scene.wait_for_render(), std::runtime_error);
const auto execution = scene.task_execution_snapshot();
EXPECT_EQ(execution.state, Task_Execution_State::Failed);
EXPECT_EQ(execution.graph_node_count, 3u);
EXPECT_EQ(execution.failed_count, 1u);
EXPECT_EQ(execution.running_count, 0u);
EXPECT_GT(execution.duration_ns, 0u);
}
TEST(scene_base_test, scene_destruction_does_not_throw_after_background_render_failure) {
{