修复若干问题
This commit is contained in:
@@ -42,8 +42,12 @@ void External_Operation::on_complete(Completion completion) const {
|
|||||||
error = state_->error;
|
error = state_->error;
|
||||||
invoke = true;
|
invoke = true;
|
||||||
}
|
}
|
||||||
if (invoke)
|
if (invoke) {
|
||||||
completion(std::move(error));
|
try {
|
||||||
|
completion(std::move(error));
|
||||||
|
} catch (...) {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
External_Operation::operator bool() const noexcept {
|
External_Operation::operator bool() const noexcept {
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ public:
|
|||||||
|
|
||||||
External_Operation() = default;
|
External_Operation() = default;
|
||||||
|
|
||||||
|
// Completion callbacks are notification boundaries. Exceptions thrown by
|
||||||
|
// them are contained regardless of whether completion happened before or
|
||||||
|
// after subscription.
|
||||||
void on_complete(Completion completion) const;
|
void on_complete(Completion completion) const;
|
||||||
[[nodiscard]] explicit operator bool() const noexcept;
|
[[nodiscard]] explicit operator bool() const noexcept;
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ double percentile(std::vector<std::uint64_t> values, double probability) {
|
|||||||
|
|
||||||
struct Node_Samples {
|
struct Node_Samples {
|
||||||
std::vector<std::uint64_t> durations;
|
std::vector<std::uint64_t> durations;
|
||||||
|
std::vector<std::uint64_t> cpu_durations;
|
||||||
|
std::vector<std::uint64_t> external_durations;
|
||||||
std::vector<std::uint64_t> waits;
|
std::vector<std::uint64_t> waits;
|
||||||
std::size_t critical_count{};
|
std::size_t critical_count{};
|
||||||
};
|
};
|
||||||
@@ -50,6 +52,18 @@ Node_Statistics statistics_for(Render_Node_Id id, const Node_Samples& samples) {
|
|||||||
result.p99_ns = percentile(samples.durations, 0.99);
|
result.p99_ns = percentile(samples.durations, 0.99);
|
||||||
result.minimum_ns = *std::min_element(samples.durations.begin(), samples.durations.end());
|
result.minimum_ns = *std::min_element(samples.durations.begin(), samples.durations.end());
|
||||||
result.maximum_ns = *std::max_element(samples.durations.begin(), samples.durations.end());
|
result.maximum_ns = *std::max_element(samples.durations.begin(), samples.durations.end());
|
||||||
|
const auto cpu_sum = std::accumulate(
|
||||||
|
samples.cpu_durations.begin(), samples.cpu_durations.end(),
|
||||||
|
std::uint64_t{});
|
||||||
|
const auto external_sum = std::accumulate(
|
||||||
|
samples.external_durations.begin(), samples.external_durations.end(),
|
||||||
|
std::uint64_t{});
|
||||||
|
result.average_cpu_ns = static_cast<double>(cpu_sum) /
|
||||||
|
samples.cpu_durations.size();
|
||||||
|
result.average_external_ns = static_cast<double>(external_sum) /
|
||||||
|
samples.external_durations.size();
|
||||||
|
result.p95_cpu_ns = percentile(samples.cpu_durations, 0.95);
|
||||||
|
result.p95_external_ns = percentile(samples.external_durations, 0.95);
|
||||||
result.critical_path_frequency = samples.critical_count;
|
result.critical_path_frequency = samples.critical_count;
|
||||||
result.average_scheduler_wait_ns = samples.waits.empty()
|
result.average_scheduler_wait_ns = samples.waits.empty()
|
||||||
? 0.0
|
? 0.0
|
||||||
@@ -227,6 +241,8 @@ std::vector<Node_Statistics> analyze_node_statistics(
|
|||||||
for (const auto& node : frame.nodes) {
|
for (const auto& node : frame.nodes) {
|
||||||
auto& values = samples[node.node_id];
|
auto& values = samples[node.node_id];
|
||||||
values.durations.push_back(node.duration_ns);
|
values.durations.push_back(node.duration_ns);
|
||||||
|
values.cpu_durations.push_back(node.cpu_duration_ns);
|
||||||
|
values.external_durations.push_back(node.external_duration_ns);
|
||||||
values.waits.push_back(node.scheduler_wait_ns);
|
values.waits.push_back(node.scheduler_wait_ns);
|
||||||
values.critical_count += node.on_critical_path ? 1 : 0;
|
values.critical_count += node.on_critical_path ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,10 @@ struct Node_Statistics {
|
|||||||
double p99_ns{};
|
double p99_ns{};
|
||||||
std::uint64_t minimum_ns{};
|
std::uint64_t minimum_ns{};
|
||||||
std::uint64_t maximum_ns{};
|
std::uint64_t maximum_ns{};
|
||||||
|
double average_cpu_ns{};
|
||||||
|
double average_external_ns{};
|
||||||
|
double p95_cpu_ns{};
|
||||||
|
double p95_external_ns{};
|
||||||
std::size_t critical_path_frequency{};
|
std::size_t critical_path_frequency{};
|
||||||
double average_scheduler_wait_ns{};
|
double average_scheduler_wait_ns{};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct Node_Diagnostic_Attachment {
|
||||||
|
std::string type;
|
||||||
|
std::string content;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Scene_Base;
|
||||||
|
|
||||||
|
class Node_Diagnostic_Sink {
|
||||||
|
public:
|
||||||
|
Node_Diagnostic_Sink() = default;
|
||||||
|
|
||||||
|
[[nodiscard]] bool enabled() const noexcept {
|
||||||
|
return attachments_ != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void attach(std::string type, std::string content) const {
|
||||||
|
if (!attachments_)
|
||||||
|
return;
|
||||||
|
attachments_->push_back(
|
||||||
|
{std::move(type), std::move(content)});
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit Node_Diagnostic_Sink(
|
||||||
|
std::vector<Node_Diagnostic_Attachment>& attachments) noexcept
|
||||||
|
: attachments_(&attachments) {}
|
||||||
|
|
||||||
|
std::vector<Node_Diagnostic_Attachment>* attachments_{};
|
||||||
|
|
||||||
|
friend class Scene_Base;
|
||||||
|
};
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include "renderive/render_graph/Node_Diagnostics.hpp"
|
||||||
#include "renderive/render_graph/Render_Plan.hpp"
|
#include "renderive/render_graph/Render_Plan.hpp"
|
||||||
|
|
||||||
enum class Node_Execution_Status : std::uint8_t {
|
enum class Node_Execution_Status : std::uint8_t {
|
||||||
@@ -28,7 +29,9 @@ enum class Node_Metric_Kind : std::uint8_t {
|
|||||||
submit_duration_ns,
|
submit_duration_ns,
|
||||||
gpu_fence_wait_ns,
|
gpu_fence_wait_ns,
|
||||||
gpu_render_duration_ns,
|
gpu_render_duration_ns,
|
||||||
|
gpu_transition_duration_ns,
|
||||||
gpu_copy_duration_ns,
|
gpu_copy_duration_ns,
|
||||||
|
gpu_total_duration_ns,
|
||||||
readback_duration_ns,
|
readback_duration_ns,
|
||||||
backend_resource_version,
|
backend_resource_version,
|
||||||
backend_frame_index,
|
backend_frame_index,
|
||||||
@@ -60,6 +63,7 @@ struct Node_Execution {
|
|||||||
std::uint32_t worker_id{};
|
std::uint32_t worker_id{};
|
||||||
Node_Execution_Status status{Node_Execution_Status::pending};
|
Node_Execution_Status status{Node_Execution_Status::pending};
|
||||||
Node_Execution_Metrics metrics;
|
Node_Execution_Metrics metrics;
|
||||||
|
std::vector<Node_Diagnostic_Attachment> attachments;
|
||||||
[[nodiscard]] std::uint64_t duration_ns() const noexcept;
|
[[nodiscard]] std::uint64_t duration_ns() const noexcept;
|
||||||
[[nodiscard]] std::uint64_t cpu_duration_ns() const noexcept;
|
[[nodiscard]] std::uint64_t cpu_duration_ns() const noexcept;
|
||||||
[[nodiscard]] std::uint64_t external_duration_ns() const noexcept;
|
[[nodiscard]] std::uint64_t external_duration_ns() const noexcept;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include "renderive/render_graph/Node_Diagnostics.hpp"
|
||||||
#include "Frame_Render_Snapshot.hpp"
|
#include "Frame_Render_Snapshot.hpp"
|
||||||
|
|
||||||
class Color_Cache;
|
class Color_Cache;
|
||||||
@@ -10,6 +11,7 @@ struct Prepare_Render_Context {
|
|||||||
const Frame_Render_Snapshot& frame;
|
const Frame_Render_Snapshot& frame;
|
||||||
const Renderable_Frame_State& renderable;
|
const Renderable_Frame_State& renderable;
|
||||||
Node_Execution_Metrics* metrics{};
|
Node_Execution_Metrics* metrics{};
|
||||||
|
Node_Diagnostic_Sink diagnostics;
|
||||||
|
|
||||||
template <class Data>
|
template <class Data>
|
||||||
void publish(std::shared_ptr<const Data> data) const {
|
void publish(std::shared_ptr<const Data> data) const {
|
||||||
@@ -27,6 +29,7 @@ struct Paint_Render_Context {
|
|||||||
const Renderable_Frame_State& renderable;
|
const Renderable_Frame_State& renderable;
|
||||||
Color_Cache& color_cache;
|
Color_Cache& color_cache;
|
||||||
Node_Execution_Metrics* metrics{};
|
Node_Execution_Metrics* metrics{};
|
||||||
|
Node_Diagnostic_Sink diagnostics;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Composite_Render_Context {
|
struct Composite_Render_Context {
|
||||||
@@ -34,11 +37,13 @@ struct Composite_Render_Context {
|
|||||||
const Renderable_Frame_State* renderable{};
|
const Renderable_Frame_State* renderable{};
|
||||||
const Color_Cache* color_cache{};
|
const Color_Cache* color_cache{};
|
||||||
Node_Execution_Metrics* metrics{};
|
Node_Execution_Metrics* metrics{};
|
||||||
|
Node_Diagnostic_Sink diagnostics;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Scene_Render_Context {
|
struct Scene_Render_Context {
|
||||||
const Frame_Render_Snapshot& frame;
|
const Frame_Render_Snapshot& frame;
|
||||||
Node_Execution_Metrics* metrics{};
|
Node_Execution_Metrics* metrics{};
|
||||||
|
Node_Diagnostic_Sink diagnostics;
|
||||||
|
|
||||||
template <class Data>
|
template <class Data>
|
||||||
[[nodiscard]] std::shared_ptr<const Data> prepared(
|
[[nodiscard]] std::shared_ptr<const Data> prepared(
|
||||||
|
|||||||
@@ -1162,19 +1162,23 @@ void Scene_Base::execute_render_graph(Render_Task& task) {
|
|||||||
auto& executor = Execution_Context::executor();
|
auto& executor = Execution_Context::executor();
|
||||||
renderive::render_graph::detail::Render_Graph_Runtime runtime(
|
renderive::render_graph::detail::Render_Graph_Runtime runtime(
|
||||||
*task.plan, execution_slots,
|
*task.plan, execution_slots,
|
||||||
[this, &task, &snapshot](
|
[this, &task, &snapshot, &execution_slots](
|
||||||
std::size_t execution_index,
|
std::size_t execution_index,
|
||||||
Node_Execution_Metrics* metrics) -> Node_Execution_Result {
|
Node_Execution_Metrics* metrics) -> Node_Execution_Result {
|
||||||
const auto& node = task.plan->graph.nodes.at(execution_index);
|
const auto& node = task.plan->graph.nodes.at(execution_index);
|
||||||
const auto& binding =
|
const auto& binding =
|
||||||
task.execution_bindings.at(execution_index);
|
task.execution_bindings.at(execution_index);
|
||||||
|
Node_Diagnostic_Sink diagnostics;
|
||||||
|
if (auto* execution = execution_slots.at(execution_index))
|
||||||
|
diagnostics = Node_Diagnostic_Sink(execution->attachments);
|
||||||
Render_Execution_Scope scope(*this);
|
Render_Execution_Scope scope(*this);
|
||||||
switch (node.kind) {
|
switch (node.kind) {
|
||||||
case Render_Node_Kind::prepare: {
|
case Render_Node_Kind::prepare: {
|
||||||
const auto& state = snapshot.renderables.at(
|
const auto& state = snapshot.renderables.at(
|
||||||
binding.renderable_index.value());
|
binding.renderable_index.value());
|
||||||
std::get<Prepare_Render_Node_Function>(binding.function)(
|
std::get<Prepare_Render_Node_Function>(binding.function)(
|
||||||
Prepare_Render_Context{snapshot, state, metrics});
|
Prepare_Render_Context{snapshot, state, metrics,
|
||||||
|
diagnostics});
|
||||||
return Node_Execution_Result::completed();
|
return Node_Execution_Result::completed();
|
||||||
}
|
}
|
||||||
case Render_Node_Kind::paint: {
|
case Render_Node_Kind::paint: {
|
||||||
@@ -1185,7 +1189,8 @@ void Scene_Base::execute_render_graph(Render_Task& task) {
|
|||||||
"paint node has no frame color cache");
|
"paint node has no frame color cache");
|
||||||
std::get<Paint_Render_Node_Function>(binding.function)(
|
std::get<Paint_Render_Node_Function>(binding.function)(
|
||||||
Paint_Render_Context{snapshot, state,
|
Paint_Render_Context{snapshot, state,
|
||||||
*state.paint_buffer_, metrics});
|
*state.paint_buffer_, metrics,
|
||||||
|
diagnostics});
|
||||||
return Node_Execution_Result::completed();
|
return Node_Execution_Result::completed();
|
||||||
}
|
}
|
||||||
case Render_Node_Kind::composite: {
|
case Render_Node_Kind::composite: {
|
||||||
@@ -1199,12 +1204,12 @@ void Scene_Base::execute_render_graph(Render_Task& task) {
|
|||||||
: nullptr;
|
: nullptr;
|
||||||
std::get<Composite_Render_Node_Function>(binding.function)(
|
std::get<Composite_Render_Node_Function>(binding.function)(
|
||||||
Composite_Render_Context{snapshot, state, cache,
|
Composite_Render_Context{snapshot, state, cache,
|
||||||
metrics});
|
metrics, diagnostics});
|
||||||
return Node_Execution_Result::completed();
|
return Node_Execution_Result::completed();
|
||||||
}
|
}
|
||||||
case Render_Node_Kind::render:
|
case Render_Node_Kind::render:
|
||||||
return std::get<Scene_Render_Node_Function>(binding.function)(
|
return std::get<Scene_Render_Node_Function>(binding.function)(
|
||||||
Scene_Render_Context{snapshot, metrics});
|
Scene_Render_Context{snapshot, metrics, diagnostics});
|
||||||
}
|
}
|
||||||
throw std::logic_error("unknown render node kind");
|
throw std::logic_error("unknown render node kind");
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -181,6 +181,13 @@ TEST(render_dag_test,
|
|||||||
ASSERT_EQ(analysis.workers.size(), 1U);
|
ASSERT_EQ(analysis.workers.size(), 1U);
|
||||||
EXPECT_EQ(analysis.workers.front().work_duration_ns, 20U);
|
EXPECT_EQ(analysis.workers.front().work_duration_ns, 20U);
|
||||||
EXPECT_DOUBLE_EQ(analysis.workers.front().utilization, 0.1);
|
EXPECT_DOUBLE_EQ(analysis.workers.front().utilization, 0.1);
|
||||||
|
const auto statistics = analyze_node_statistics(
|
||||||
|
std::span<const Frame_Analysis>(&analysis, 1));
|
||||||
|
ASSERT_EQ(statistics.size(), 1U);
|
||||||
|
EXPECT_DOUBLE_EQ(statistics.front().average_cpu_ns, 20.0);
|
||||||
|
EXPECT_DOUBLE_EQ(statistics.front().average_external_ns, 180.0);
|
||||||
|
EXPECT_DOUBLE_EQ(statistics.front().p95_cpu_ns, 20.0);
|
||||||
|
EXPECT_DOUBLE_EQ(statistics.front().p95_external_ns, 180.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(render_dag_test, failed_capture_ticket_is_retried_until_the_requested_frame_completes) {
|
TEST(render_dag_test, failed_capture_ticket_is_retried_until_the_requested_frame_completes) {
|
||||||
|
|||||||
@@ -106,6 +106,21 @@ TEST(external_operation_test, completion_before_subscription_is_delivered_once)
|
|||||||
EXPECT_EQ(completion_count, 1);
|
EXPECT_EQ(completion_count, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(external_operation_test,
|
||||||
|
callback_exceptions_are_contained_for_both_completion_orders) {
|
||||||
|
External_Operation_Source completed_first;
|
||||||
|
const auto completed_operation = completed_first.operation();
|
||||||
|
ASSERT_TRUE(completed_first.complete());
|
||||||
|
EXPECT_NO_THROW(completed_operation.on_complete(
|
||||||
|
[](std::exception_ptr) { throw std::runtime_error("late callback"); }));
|
||||||
|
|
||||||
|
External_Operation_Source subscribed_first;
|
||||||
|
const auto subscribed_operation = subscribed_first.operation();
|
||||||
|
subscribed_operation.on_complete(
|
||||||
|
[](std::exception_ptr) { throw std::runtime_error("early callback"); });
|
||||||
|
EXPECT_TRUE(subscribed_first.complete());
|
||||||
|
}
|
||||||
|
|
||||||
TEST(render_graph_runtime_test,
|
TEST(render_graph_runtime_test,
|
||||||
external_successor_stays_blocked_until_operation_completes) {
|
external_successor_stays_blocked_until_operation_completes) {
|
||||||
const auto plan = two_node_plan();
|
const auto plan = two_node_plan();
|
||||||
|
|||||||
@@ -121,6 +121,9 @@ if (RENDERIVE_BUILD_TESTS)
|
|||||||
set(Renderive_render_3D_test_target "Renderive_render_3D_${Renderive_render_3D_test_name}_${Renderive_render_3D_test_hash}")
|
set(Renderive_render_3D_test_target "Renderive_render_3D_${Renderive_render_3D_test_name}_${Renderive_render_3D_test_hash}")
|
||||||
add_executable("${Renderive_render_3D_test_target}" "${Renderive_render_3D_test_source}")
|
add_executable("${Renderive_render_3D_test_target}" "${Renderive_render_3D_test_source}")
|
||||||
target_link_libraries("${Renderive_render_3D_test_target}" PRIVATE Renderive_render_3D GTest::gtest_main)
|
target_link_libraries("${Renderive_render_3D_test_target}" PRIVATE Renderive_render_3D GTest::gtest_main)
|
||||||
|
if (Renderive_render_3D_test_name STREQUAL "Gpu_Completion_Service_Tests")
|
||||||
|
target_link_libraries("${Renderive_render_3D_test_target}" PRIVATE volk::volk_headers)
|
||||||
|
endif ()
|
||||||
renderive_stage_render_3D_runtime("${Renderive_render_3D_test_target}")
|
renderive_stage_render_3D_runtime("${Renderive_render_3D_test_target}")
|
||||||
add_test(NAME "${Renderive_render_3D_test_target}" COMMAND "${Renderive_render_3D_test_target}")
|
add_test(NAME "${Renderive_render_3D_test_target}" COMMAND "${Renderive_render_3D_test_target}")
|
||||||
endforeach ()
|
endforeach ()
|
||||||
|
|||||||
+130
-102
@@ -15,7 +15,9 @@
|
|||||||
#include <exception>
|
#include <exception>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
namespace renderive::render_3d {
|
namespace renderive::render_3d {
|
||||||
@@ -41,38 +43,46 @@ float datoviz_wheel_step(float pixel_delta, float angle_delta) noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void publish_trace(Node_Execution_Metrics* metrics,
|
void publish_trace(Node_Execution_Metrics* metrics,
|
||||||
const detail::Datoviz_Frame_Trace& trace) {
|
Node_Diagnostic_Sink diagnostics,
|
||||||
if (!metrics)
|
detail::Datoviz_Frame_Trace trace) {
|
||||||
return;
|
if (metrics) {
|
||||||
metrics->set(Node_Metric_Kind::queue_wait_ns,
|
metrics->set(Node_Metric_Kind::queue_wait_ns,
|
||||||
trace.render_domain_queue_wait_ns);
|
trace.render_domain_queue_wait_ns);
|
||||||
metrics->set(Node_Metric_Kind::apply_duration_ns, trace.apply_ns);
|
metrics->set(Node_Metric_Kind::apply_duration_ns, trace.apply_ns);
|
||||||
metrics->set(Node_Metric_Kind::plan_emit_duration_ns, trace.emit_ns);
|
metrics->set(Node_Metric_Kind::plan_emit_duration_ns, trace.emit_ns);
|
||||||
metrics->set(Node_Metric_Kind::backend_execute_duration_ns,
|
metrics->set(Node_Metric_Kind::backend_execute_duration_ns,
|
||||||
trace.execute_ns);
|
trace.execute_ns);
|
||||||
metrics->set(Node_Metric_Kind::submit_duration_ns, trace.submit_ns);
|
metrics->set(Node_Metric_Kind::submit_duration_ns, trace.submit_ns);
|
||||||
metrics->set(Node_Metric_Kind::gpu_fence_wait_ns,
|
metrics->set(Node_Metric_Kind::gpu_fence_wait_ns,
|
||||||
trace.gpu_fence_wait_ns);
|
trace.gpu_fence_wait_ns);
|
||||||
metrics->set(Node_Metric_Kind::readback_duration_ns,
|
metrics->set(Node_Metric_Kind::readback_duration_ns,
|
||||||
trace.readback_ns);
|
trace.readback_ns);
|
||||||
if (trace.gpu) {
|
if (trace.gpu) {
|
||||||
metrics->set(Node_Metric_Kind::gpu_render_duration_ns,
|
metrics->set(Node_Metric_Kind::gpu_render_duration_ns,
|
||||||
trace.gpu->render_ns);
|
trace.gpu->render_ns);
|
||||||
metrics->set(Node_Metric_Kind::gpu_copy_duration_ns,
|
metrics->set(Node_Metric_Kind::gpu_transition_duration_ns,
|
||||||
trace.gpu->copy_ns);
|
trace.gpu->transition_ns);
|
||||||
|
metrics->set(Node_Metric_Kind::gpu_copy_duration_ns,
|
||||||
|
trace.gpu->copy_ns);
|
||||||
|
metrics->set(Node_Metric_Kind::gpu_total_duration_ns,
|
||||||
|
trace.gpu->total_ns);
|
||||||
|
}
|
||||||
|
metrics->set(Node_Metric_Kind::backend_resource_version,
|
||||||
|
trace.artifact_resource_version);
|
||||||
|
metrics->set(Node_Metric_Kind::backend_frame_index,
|
||||||
|
trace.artifact_frame_index);
|
||||||
|
metrics->set(Node_Metric_Kind::backend_artifact_status,
|
||||||
|
trace.artifact_status);
|
||||||
|
metrics->set(Node_Metric_Kind::backend_validation_code,
|
||||||
|
trace.validation_code);
|
||||||
|
metrics->set(Node_Metric_Kind::backend_validation_command,
|
||||||
|
trace.validation_command_index);
|
||||||
|
metrics->set(Node_Metric_Kind::backend_artifact_json_bytes,
|
||||||
|
trace.artifact_json.size());
|
||||||
}
|
}
|
||||||
metrics->set(Node_Metric_Kind::backend_resource_version,
|
if (!trace.artifact_json.empty())
|
||||||
trace.artifact_resource_version);
|
diagnostics.attach("datoviz.drp2.artifact.json",
|
||||||
metrics->set(Node_Metric_Kind::backend_frame_index,
|
std::move(trace.artifact_json));
|
||||||
trace.artifact_frame_index);
|
|
||||||
metrics->set(Node_Metric_Kind::backend_artifact_status,
|
|
||||||
trace.artifact_status);
|
|
||||||
metrics->set(Node_Metric_Kind::backend_validation_code,
|
|
||||||
trace.validation_code);
|
|
||||||
metrics->set(Node_Metric_Kind::backend_validation_command,
|
|
||||||
trace.validation_command_index);
|
|
||||||
metrics->set(Node_Metric_Kind::backend_artifact_json_bytes,
|
|
||||||
trace.artifact_json.size());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Scene_Model {
|
struct Scene_Model {
|
||||||
@@ -301,22 +311,100 @@ struct Basic_Point_Scene final
|
|||||||
const std::uint64_t frame_sequence =
|
const std::uint64_t frame_sequence =
|
||||||
context.frame.render_sequence;
|
context.frame.render_sequence;
|
||||||
Node_Execution_Metrics* const metrics = context.metrics;
|
Node_Execution_Metrics* const metrics = context.metrics;
|
||||||
|
const Node_Diagnostic_Sink diagnostics = context.diagnostics;
|
||||||
|
const bool observe = metrics != nullptr;
|
||||||
auto source = std::make_shared<External_Operation_Source>();
|
auto source = std::make_shared<External_Operation_Source>();
|
||||||
const auto operation = source->operation();
|
const auto operation = source->operation();
|
||||||
const auto queued_at = std::chrono::steady_clock::now();
|
using Pending_Frame = detail::Datoviz_Visual_Backend::Pending_Frame;
|
||||||
|
static_assert(std::is_nothrow_move_constructible_v<Pending_Frame>);
|
||||||
|
using Completion_Result = detail::Gpu_Completion_Service::Result;
|
||||||
|
static_assert(std::is_nothrow_move_assignable_v<Completion_Result>);
|
||||||
|
struct Async_Frame_State {
|
||||||
|
std::optional<Pending_Frame> pending;
|
||||||
|
Completion_Result completion;
|
||||||
|
};
|
||||||
|
auto async_frame = std::make_shared<Async_Frame_State>();
|
||||||
|
auto render_completion = std::make_shared<
|
||||||
|
detail::Render_Domain::Prepared_Task>(
|
||||||
|
render_domain.prepare(
|
||||||
|
[this, async_frame, metrics, diagnostics, source]() mutable {
|
||||||
|
if (!async_frame->pending) {
|
||||||
|
static_cast<void>(source->fail(
|
||||||
|
std::make_exception_ptr(std::logic_error(
|
||||||
|
"GPU completion has no pending frame"))));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto pending = std::move(*async_frame->pending);
|
||||||
|
async_frame->pending.reset();
|
||||||
|
pending.trace.gpu_fence_wait_ns =
|
||||||
|
async_frame->completion.wait_duration_ns;
|
||||||
|
if (async_frame->completion.error) {
|
||||||
|
try {
|
||||||
|
backend->discard(std::move(pending));
|
||||||
|
static_cast<void>(source->fail(
|
||||||
|
std::move(async_frame->completion.error)));
|
||||||
|
} catch (...) {
|
||||||
|
static_cast<void>(source->fail(
|
||||||
|
std::current_exception()));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
auto completed = backend->collect(
|
||||||
|
std::move(pending));
|
||||||
|
auto frame = std::move(completed.frame);
|
||||||
|
if (metrics && frame) {
|
||||||
|
metrics->set(
|
||||||
|
Node_Metric_Kind::pixel_count,
|
||||||
|
static_cast<std::uint64_t>(
|
||||||
|
frame->extent.width) *
|
||||||
|
frame->extent.height);
|
||||||
|
}
|
||||||
|
publish_trace(metrics, diagnostics,
|
||||||
|
std::move(completed.trace));
|
||||||
|
{
|
||||||
|
std::lock_guard lock(frame_mutex);
|
||||||
|
latest = std::move(frame);
|
||||||
|
}
|
||||||
|
static_cast<void>(source->complete());
|
||||||
|
} catch (...) {
|
||||||
|
static_cast<void>(source->fail(
|
||||||
|
std::current_exception()));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
auto completion = std::make_shared<
|
||||||
|
detail::Gpu_Completion_Service::Reservation>(
|
||||||
|
gpu_completion.prepare(
|
||||||
|
[this, async_frame, render_completion](
|
||||||
|
Completion_Result result) noexcept {
|
||||||
|
async_frame->completion = std::move(result);
|
||||||
|
render_domain.post(std::move(*render_completion));
|
||||||
|
},
|
||||||
|
observe));
|
||||||
|
const auto queued_at = observe
|
||||||
|
? std::chrono::steady_clock::now()
|
||||||
|
: std::chrono::steady_clock::time_point{};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
render_domain.post(
|
render_domain.post(
|
||||||
[this, scene_state, scene_revision, frame_sequence, prepared,
|
[this, scene_state, scene_revision, frame_sequence, prepared,
|
||||||
metrics, source, queued_at] {
|
source, async_frame, completion, observe,
|
||||||
|
queued_at] {
|
||||||
try {
|
try {
|
||||||
const auto queue_wait =
|
std::uint64_t queue_wait_ns{};
|
||||||
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
if (observe) {
|
||||||
std::chrono::steady_clock::now() - queued_at)
|
const auto queue_wait =
|
||||||
.count();
|
std::chrono::duration_cast<
|
||||||
|
std::chrono::nanoseconds>(
|
||||||
|
std::chrono::steady_clock::now() -
|
||||||
|
queued_at).count();
|
||||||
|
queue_wait_ns = queue_wait > 0
|
||||||
|
? static_cast<std::uint64_t>(queue_wait)
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
auto pending = backend->submit(
|
auto pending = backend->submit(
|
||||||
scene_state, scene_revision, *prepared,
|
scene_state, scene_revision, *prepared,
|
||||||
frame_sequence, metrics != nullptr);
|
frame_sequence, observe);
|
||||||
if (!pending) {
|
if (!pending) {
|
||||||
{
|
{
|
||||||
std::lock_guard lock(frame_mutex);
|
std::lock_guard lock(frame_mutex);
|
||||||
@@ -326,70 +414,10 @@ struct Basic_Point_Scene final
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
pending->trace.render_domain_queue_wait_ns =
|
pending->trace.render_domain_queue_wait_ns =
|
||||||
queue_wait > 0
|
queue_wait_ns;
|
||||||
? static_cast<std::uint64_t>(queue_wait)
|
auto& owned = async_frame->pending.emplace(
|
||||||
: 0;
|
std::move(*pending));
|
||||||
gpu_completion.watch(
|
completion->watch(owned.device, owned.fence);
|
||||||
pending->device, pending->fence,
|
|
||||||
[this, pending = *pending, metrics, source](
|
|
||||||
detail::Gpu_Completion_Service::Result result)
|
|
||||||
mutable {
|
|
||||||
pending.trace.gpu_fence_wait_ns =
|
|
||||||
result.wait_duration_ns;
|
|
||||||
try {
|
|
||||||
render_domain.post(
|
|
||||||
[this, pending = std::move(pending),
|
|
||||||
metrics, source,
|
|
||||||
wait_error = std::move(result.error)]()
|
|
||||||
mutable {
|
|
||||||
if (wait_error) {
|
|
||||||
try {
|
|
||||||
backend->discard(
|
|
||||||
std::move(pending));
|
|
||||||
static_cast<void>(
|
|
||||||
source->fail(std::move(
|
|
||||||
wait_error)));
|
|
||||||
} catch (...) {
|
|
||||||
static_cast<void>(
|
|
||||||
source->fail(
|
|
||||||
std::current_exception()));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
auto completed =
|
|
||||||
backend->collect(
|
|
||||||
std::move(pending));
|
|
||||||
auto frame =
|
|
||||||
std::move(completed.frame);
|
|
||||||
if (metrics && frame) {
|
|
||||||
metrics->set(
|
|
||||||
Node_Metric_Kind::
|
|
||||||
pixel_count,
|
|
||||||
static_cast<
|
|
||||||
std::uint64_t>(
|
|
||||||
frame->extent.width) *
|
|
||||||
frame->extent.height);
|
|
||||||
}
|
|
||||||
publish_trace(
|
|
||||||
metrics, completed.trace);
|
|
||||||
{
|
|
||||||
std::lock_guard lock(
|
|
||||||
frame_mutex);
|
|
||||||
latest = std::move(frame);
|
|
||||||
}
|
|
||||||
static_cast<void>(
|
|
||||||
source->complete());
|
|
||||||
} catch (...) {
|
|
||||||
static_cast<void>(source->fail(
|
|
||||||
std::current_exception()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (...) {
|
|
||||||
static_cast<void>(source->fail(
|
|
||||||
std::current_exception()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
static_cast<void>(
|
static_cast<void>(
|
||||||
source->fail(std::current_exception()));
|
source->fail(std::current_exception()));
|
||||||
|
|||||||
@@ -8,11 +8,14 @@ namespace renderive::render_3d::detail {
|
|||||||
|
|
||||||
struct Datoviz_Gpu_Timing {
|
struct Datoviz_Gpu_Timing {
|
||||||
std::uint64_t render_ns{};
|
std::uint64_t render_ns{};
|
||||||
|
std::uint64_t transition_ns{};
|
||||||
std::uint64_t copy_ns{};
|
std::uint64_t copy_ns{};
|
||||||
|
std::uint64_t total_ns{};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Datoviz_Frame_Trace {
|
struct Datoviz_Frame_Trace {
|
||||||
std::uint64_t render_sequence{};
|
std::uint64_t render_sequence{};
|
||||||
|
bool observed{};
|
||||||
std::uint64_t render_domain_queue_wait_ns{};
|
std::uint64_t render_domain_queue_wait_ns{};
|
||||||
std::uint64_t apply_ns{};
|
std::uint64_t apply_ns{};
|
||||||
std::uint64_t emit_ns{};
|
std::uint64_t emit_ns{};
|
||||||
|
|||||||
@@ -238,7 +238,6 @@ public:
|
|||||||
dvz_buffer_usage(readback_, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
|
dvz_buffer_usage(readback_, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
|
||||||
if (dvz_buffer_create(readback_) != 0)
|
if (dvz_buffer_create(readback_) != 0)
|
||||||
throw std::runtime_error("failed to create Datoviz readback buffer");
|
throw std::runtime_error("failed to create Datoviz readback buffer");
|
||||||
initialize_timestamps(device, queue);
|
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
destroy();
|
destroy();
|
||||||
throw;
|
throw;
|
||||||
@@ -247,9 +246,15 @@ public:
|
|||||||
|
|
||||||
~Frame_Target() { destroy(); }
|
~Frame_Target() { destroy(); }
|
||||||
|
|
||||||
void begin() {
|
void begin(bool observe) {
|
||||||
if (in_flight_)
|
if (in_flight_)
|
||||||
throw std::logic_error("Datoviz frame target is still in flight");
|
throw std::logic_error("Datoviz frame target is still in flight");
|
||||||
|
observing_ = observe;
|
||||||
|
if (observing_ && !timestamps_initialized_) {
|
||||||
|
initialize_timestamps(
|
||||||
|
dvz_gpu_ctx_device(gpu_context_),
|
||||||
|
dvz_gpu_ctx_queue(gpu_context_, DVZ_QUEUE_MAIN));
|
||||||
|
}
|
||||||
dvz_cmd_reset(commands_);
|
dvz_cmd_reset(commands_);
|
||||||
if (dvz_cmd_begin_result(commands_) != 0)
|
if (dvz_cmd_begin_result(commands_) != 0)
|
||||||
throw std::runtime_error("failed to begin Datoviz command buffer");
|
throw std::runtime_error("failed to begin Datoviz command buffer");
|
||||||
@@ -278,7 +283,7 @@ public:
|
|||||||
dvz_barrier_image_mip(image_barrier, 0, 1);
|
dvz_barrier_image_mip(image_barrier, 0, 1);
|
||||||
dvz_barrier_image_layers(image_barrier, 0, 1);
|
dvz_barrier_image_layers(image_barrier, 0, 1);
|
||||||
dvz_cmd_barriers(commands_, &barriers);
|
dvz_cmd_barriers(commands_, &barriers);
|
||||||
if (timestamps_supported_) {
|
if (observing_ && timestamps_supported_) {
|
||||||
const VkCommandBuffer command_buffer =
|
const VkCommandBuffer command_buffer =
|
||||||
dvz_commands_handle(commands_);
|
dvz_commands_handle(commands_);
|
||||||
vkCmdResetQueryPool(command_buffer, query_pool_, 0, 4);
|
vkCmdResetQueryPool(command_buffer, query_pool_, 0, 4);
|
||||||
@@ -315,7 +320,7 @@ public:
|
|||||||
if (!recording_)
|
if (!recording_)
|
||||||
throw std::logic_error("Datoviz frame target is not recording");
|
throw std::logic_error("Datoviz frame target is not recording");
|
||||||
const VkCommandBuffer command_buffer = dvz_commands_handle(commands_);
|
const VkCommandBuffer command_buffer = dvz_commands_handle(commands_);
|
||||||
if (timestamps_supported_) {
|
if (observing_ && timestamps_supported_) {
|
||||||
vkCmdWriteTimestamp(
|
vkCmdWriteTimestamp(
|
||||||
command_buffer,
|
command_buffer,
|
||||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||||
@@ -338,7 +343,7 @@ public:
|
|||||||
dvz_barrier_image_mip(image_barrier, 0, 1);
|
dvz_barrier_image_mip(image_barrier, 0, 1);
|
||||||
dvz_barrier_image_layers(image_barrier, 0, 1);
|
dvz_barrier_image_layers(image_barrier, 0, 1);
|
||||||
dvz_cmd_barriers(commands_, &image_barriers);
|
dvz_cmd_barriers(commands_, &image_barriers);
|
||||||
if (timestamps_supported_) {
|
if (observing_ && timestamps_supported_) {
|
||||||
vkCmdWriteTimestamp(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
vkCmdWriteTimestamp(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||||
query_pool_, 2);
|
query_pool_, 2);
|
||||||
}
|
}
|
||||||
@@ -360,7 +365,7 @@ public:
|
|||||||
dvz_barrier_buffer_access(buffer_barrier, VK_ACCESS_2_TRANSFER_WRITE_BIT,
|
dvz_barrier_buffer_access(buffer_barrier, VK_ACCESS_2_TRANSFER_WRITE_BIT,
|
||||||
VK_ACCESS_2_HOST_READ_BIT);
|
VK_ACCESS_2_HOST_READ_BIT);
|
||||||
dvz_cmd_barriers(commands_, &buffer_barriers);
|
dvz_cmd_barriers(commands_, &buffer_barriers);
|
||||||
if (timestamps_supported_) {
|
if (observing_ && timestamps_supported_) {
|
||||||
vkCmdWriteTimestamp(command_buffer,
|
vkCmdWriteTimestamp(command_buffer,
|
||||||
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
|
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
|
||||||
query_pool_, 3);
|
query_pool_, 3);
|
||||||
@@ -390,9 +395,11 @@ public:
|
|||||||
result.gpu_timing = collect_gpu_timing();
|
result.gpu_timing = collect_gpu_timing();
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
in_flight_ = false;
|
in_flight_ = false;
|
||||||
|
observing_ = false;
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
in_flight_ = false;
|
in_flight_ = false;
|
||||||
|
observing_ = false;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,6 +407,7 @@ public:
|
|||||||
if (!in_flight_)
|
if (!in_flight_)
|
||||||
throw std::logic_error("Datoviz frame target has no pending frame");
|
throw std::logic_error("Datoviz frame target has no pending frame");
|
||||||
in_flight_ = false;
|
in_flight_ = false;
|
||||||
|
observing_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] VkDevice device() const {
|
[[nodiscard]] VkDevice device() const {
|
||||||
@@ -418,10 +426,12 @@ public:
|
|||||||
if (recording_ && commands_ != nullptr)
|
if (recording_ && commands_ != nullptr)
|
||||||
dvz_cmd_reset(commands_);
|
dvz_cmd_reset(commands_);
|
||||||
recording_ = false;
|
recording_ = false;
|
||||||
|
observing_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void initialize_timestamps(DvzDevice* device, DvzQueue* queue) noexcept {
|
void initialize_timestamps(DvzDevice* device, DvzQueue* queue) noexcept {
|
||||||
|
timestamps_initialized_ = true;
|
||||||
if (device == nullptr || queue == nullptr ||
|
if (device == nullptr || queue == nullptr ||
|
||||||
vkGetPhysicalDeviceQueueFamilyProperties == nullptr ||
|
vkGetPhysicalDeviceQueueFamilyProperties == nullptr ||
|
||||||
vkGetPhysicalDeviceProperties == nullptr ||
|
vkGetPhysicalDeviceProperties == nullptr ||
|
||||||
@@ -467,7 +477,8 @@ private:
|
|||||||
|
|
||||||
[[nodiscard]] std::optional<Datoviz_Gpu_Timing>
|
[[nodiscard]] std::optional<Datoviz_Gpu_Timing>
|
||||||
collect_gpu_timing() const noexcept {
|
collect_gpu_timing() const noexcept {
|
||||||
if (!timestamps_supported_ || query_pool_ == VK_NULL_HANDLE)
|
if (!observing_ || !timestamps_supported_ ||
|
||||||
|
query_pool_ == VK_NULL_HANDLE)
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
const VkDevice device =
|
const VkDevice device =
|
||||||
dvz_device_handle(dvz_gpu_ctx_device(gpu_context_));
|
dvz_device_handle(dvz_gpu_ctx_device(gpu_context_));
|
||||||
@@ -497,7 +508,9 @@ private:
|
|||||||
};
|
};
|
||||||
return Datoviz_Gpu_Timing{
|
return Datoviz_Gpu_Timing{
|
||||||
elapsed(timestamps[0], timestamps[1]),
|
elapsed(timestamps[0], timestamps[1]),
|
||||||
elapsed(timestamps[2], timestamps[3])};
|
elapsed(timestamps[1], timestamps[2]),
|
||||||
|
elapsed(timestamps[2], timestamps[3]),
|
||||||
|
elapsed(timestamps[0], timestamps[3])};
|
||||||
}
|
}
|
||||||
|
|
||||||
void destroy() noexcept {
|
void destroy() noexcept {
|
||||||
@@ -556,6 +569,8 @@ private:
|
|||||||
std::uint32_t timestamp_valid_bits_{};
|
std::uint32_t timestamp_valid_bits_{};
|
||||||
bool recording_{};
|
bool recording_{};
|
||||||
bool in_flight_{};
|
bool in_flight_{};
|
||||||
|
bool observing_{};
|
||||||
|
bool timestamps_initialized_{};
|
||||||
bool timestamps_supported_{};
|
bool timestamps_supported_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1023,15 +1038,17 @@ std::optional<Datoviz_Visual_Backend::Pending_Frame>
|
|||||||
Datoviz_Visual_Backend::submit(
|
Datoviz_Visual_Backend::submit(
|
||||||
const Scene_State& scene, std::uint64_t scene_revision,
|
const Scene_State& scene, std::uint64_t scene_revision,
|
||||||
const Prepared_Point& point, std::uint64_t frame_sequence,
|
const Prepared_Point& point, std::uint64_t frame_sequence,
|
||||||
bool capture_trace) {
|
bool observe) {
|
||||||
require_domain();
|
require_domain();
|
||||||
if (scene.viewport.empty())
|
if (scene.viewport.empty())
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
Datoviz_Frame_Trace trace;
|
Datoviz_Frame_Trace trace;
|
||||||
trace.render_sequence = frame_sequence;
|
trace.render_sequence = frame_sequence;
|
||||||
auto phase_started = trace_now_ns();
|
trace.observed = observe;
|
||||||
|
std::uint64_t phase_started = observe ? trace_now_ns() : 0;
|
||||||
apply(scene, scene_revision, point);
|
apply(scene, scene_revision, point);
|
||||||
trace.apply_ns = trace_now_ns() - phase_started;
|
if (observe)
|
||||||
|
trace.apply_ns = trace_now_ns() - phase_started;
|
||||||
|
|
||||||
if (target_ == nullptr || target_extent_ != scene.viewport) {
|
if (target_ == nullptr || target_extent_ != scene.viewport) {
|
||||||
target_.reset();
|
target_.reset();
|
||||||
@@ -1040,36 +1057,42 @@ Datoviz_Visual_Backend::submit(
|
|||||||
++target_generation_);
|
++target_generation_);
|
||||||
}
|
}
|
||||||
|
|
||||||
target_->begin();
|
target_->begin(observe);
|
||||||
DvzSceneFrameArtifact* artifact{};
|
DvzSceneFrameArtifact* artifact{};
|
||||||
try {
|
try {
|
||||||
phase_started = trace_now_ns();
|
if (observe)
|
||||||
|
phase_started = trace_now_ns();
|
||||||
artifact = emit(scene);
|
artifact = emit(scene);
|
||||||
trace.emit_ns = trace_now_ns() - phase_started;
|
if (observe)
|
||||||
|
trace.emit_ns = trace_now_ns() - phase_started;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
target_->abort();
|
target_->abort();
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
trace.artifact_status = static_cast<std::uint32_t>(
|
if (observe) {
|
||||||
dvz_scene_frame_artifact_status(artifact));
|
trace.artifact_status = static_cast<std::uint32_t>(
|
||||||
trace.artifact_resource_version =
|
dvz_scene_frame_artifact_status(artifact));
|
||||||
dvz_scene_frame_artifact_resource_version(artifact);
|
trace.artifact_resource_version =
|
||||||
trace.artifact_frame_index =
|
dvz_scene_frame_artifact_resource_version(artifact);
|
||||||
dvz_scene_frame_artifact_frame_index(artifact);
|
trace.artifact_frame_index =
|
||||||
|
dvz_scene_frame_artifact_frame_index(artifact);
|
||||||
|
}
|
||||||
const DvzDrp2CommandStream* stream = dvz_scene_frame_artifact_stream(artifact);
|
const DvzDrp2CommandStream* stream = dvz_scene_frame_artifact_stream(artifact);
|
||||||
const DvzStreamFrame target_frame = target_->stream_frame();
|
const DvzStreamFrame target_frame = target_->stream_frame();
|
||||||
phase_started = trace_now_ns();
|
if (observe)
|
||||||
|
phase_started = trace_now_ns();
|
||||||
const bool attached = stream != nullptr &&
|
const bool attached = stream != nullptr &&
|
||||||
dvz_drp2_runtime_attach_frame_target(
|
dvz_drp2_runtime_attach_frame_target(
|
||||||
runtime_, color_target_id, &target_frame);
|
runtime_, color_target_id, &target_frame);
|
||||||
const DvzDrp2ValidationResult result =
|
const DvzDrp2ValidationResult result =
|
||||||
attached ? dvz_drp2_runtime_execute(runtime_, stream)
|
attached ? dvz_drp2_runtime_execute(runtime_, stream)
|
||||||
: DvzDrp2ValidationResult{};
|
: DvzDrp2ValidationResult{};
|
||||||
trace.execute_ns = trace_now_ns() - phase_started;
|
if (observe)
|
||||||
|
trace.execute_ns = trace_now_ns() - phase_started;
|
||||||
trace.validation_ok = attached && result.ok;
|
trace.validation_ok = attached && result.ok;
|
||||||
trace.validation_code = static_cast<std::uint32_t>(result.code);
|
trace.validation_code = static_cast<std::uint32_t>(result.code);
|
||||||
trace.validation_command_index = result.command_index;
|
trace.validation_command_index = result.command_index;
|
||||||
if (capture_trace || !trace.validation_ok) {
|
if (observe || !trace.validation_ok) {
|
||||||
if (char* json = dvz_scene_frame_artifact_json(
|
if (char* json = dvz_scene_frame_artifact_json(
|
||||||
artifact, "renderive_frame")) {
|
artifact, "renderive_frame")) {
|
||||||
trace.artifact_json = json;
|
trace.artifact_json = json;
|
||||||
@@ -1095,9 +1118,11 @@ Datoviz_Visual_Backend::submit(
|
|||||||
throw std::runtime_error(std::move(message));
|
throw std::runtime_error(std::move(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
phase_started = trace_now_ns();
|
if (observe)
|
||||||
|
phase_started = trace_now_ns();
|
||||||
target_->submit();
|
target_->submit();
|
||||||
trace.submit_ns = trace_now_ns() - phase_started;
|
if (observe)
|
||||||
|
trace.submit_ns = trace_now_ns() - phase_started;
|
||||||
return Pending_Frame{target_->device(), target_->fence(), scene.viewport,
|
return Pending_Frame{target_->device(), target_->fence(), scene.viewport,
|
||||||
frame_sequence, target_->generation(),
|
frame_sequence, target_->generation(),
|
||||||
std::move(trace)};
|
std::move(trace)};
|
||||||
@@ -1109,9 +1134,12 @@ Datoviz_Visual_Backend::Completed_Frame Datoviz_Visual_Backend::collect(
|
|||||||
if (target_ == nullptr ||
|
if (target_ == nullptr ||
|
||||||
target_->generation() != pending.target_generation)
|
target_->generation() != pending.target_generation)
|
||||||
throw std::logic_error("Datoviz pending frame target no longer exists");
|
throw std::logic_error("Datoviz pending frame target no longer exists");
|
||||||
const std::uint64_t readback_started = trace_now_ns();
|
const std::uint64_t readback_started = pending.trace.observed
|
||||||
|
? trace_now_ns()
|
||||||
|
: 0;
|
||||||
auto collection = target_->collect();
|
auto collection = target_->collect();
|
||||||
pending.trace.readback_ns = trace_now_ns() - readback_started;
|
if (pending.trace.observed)
|
||||||
|
pending.trace.readback_ns = trace_now_ns() - readback_started;
|
||||||
pending.trace.gpu = std::move(collection.gpu_timing);
|
pending.trace.gpu = std::move(collection.gpu_timing);
|
||||||
auto output = std::make_shared<Pixel_Frame>();
|
auto output = std::make_shared<Pixel_Frame>();
|
||||||
output->extent = pending.extent;
|
output->extent = pending.extent;
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ public:
|
|||||||
[[nodiscard]] std::optional<Pending_Frame> submit(
|
[[nodiscard]] std::optional<Pending_Frame> submit(
|
||||||
const Scene_State& scene, std::uint64_t scene_revision,
|
const Scene_State& scene, std::uint64_t scene_revision,
|
||||||
const Prepared_Point& point, std::uint64_t frame_sequence,
|
const Prepared_Point& point, std::uint64_t frame_sequence,
|
||||||
bool capture_trace);
|
bool observe);
|
||||||
[[nodiscard]] Completed_Frame collect(Pending_Frame pending);
|
[[nodiscard]] Completed_Frame collect(Pending_Frame pending);
|
||||||
void discard(Pending_Frame pending);
|
void discard(Pending_Frame pending);
|
||||||
|
|
||||||
|
|||||||
@@ -14,19 +14,61 @@ Gpu_Completion_Service::~Gpu_Completion_Service() {
|
|||||||
shutdown();
|
shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gpu_Completion_Service::watch(
|
Gpu_Completion_Service::Reservation::Reservation(
|
||||||
VkDevice device, VkFence fence, Completion completion) {
|
std::shared_ptr<Pending_Fence> pending) noexcept
|
||||||
if (device == VK_NULL_HANDLE || fence == VK_NULL_HANDLE)
|
: pending_(std::move(pending)) {}
|
||||||
throw std::invalid_argument("GPU completion fence is invalid");
|
|
||||||
|
Gpu_Completion_Service::Reservation::~Reservation() {
|
||||||
|
cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
Gpu_Completion_Service::Reservation::Reservation(
|
||||||
|
Reservation&& other) noexcept
|
||||||
|
: pending_(std::exchange(other.pending_, {})) {}
|
||||||
|
|
||||||
|
void Gpu_Completion_Service::Reservation::watch(
|
||||||
|
VkDevice device, VkFence fence) noexcept {
|
||||||
|
if (!pending_ || device == VK_NULL_HANDLE || fence == VK_NULL_HANDLE)
|
||||||
|
std::terminate();
|
||||||
|
auto pending = std::exchange(pending_, {});
|
||||||
|
{
|
||||||
|
std::lock_guard lock(pending->mutex);
|
||||||
|
if (pending->status != Pending_Fence::Status::reserved)
|
||||||
|
std::terminate();
|
||||||
|
pending->device = device;
|
||||||
|
pending->fence = fence;
|
||||||
|
pending->status = Pending_Fence::Status::watched;
|
||||||
|
}
|
||||||
|
pending->ready.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Gpu_Completion_Service::Reservation::cancel() noexcept {
|
||||||
|
if (!pending_)
|
||||||
|
return;
|
||||||
|
auto pending = std::exchange(pending_, {});
|
||||||
|
{
|
||||||
|
std::lock_guard lock(pending->mutex);
|
||||||
|
if (pending->status == Pending_Fence::Status::reserved)
|
||||||
|
pending->status = Pending_Fence::Status::canceled;
|
||||||
|
}
|
||||||
|
pending->ready.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
Gpu_Completion_Service::Reservation Gpu_Completion_Service::prepare(
|
||||||
|
Completion completion, bool observe) {
|
||||||
if (!completion)
|
if (!completion)
|
||||||
throw std::invalid_argument("GPU completion callback is empty");
|
throw std::invalid_argument("GPU completion callback is empty");
|
||||||
|
auto pending = std::make_shared<Pending_Fence>();
|
||||||
|
pending->completion = std::move(completion);
|
||||||
|
pending->observe = observe;
|
||||||
{
|
{
|
||||||
std::lock_guard lock(mutex_);
|
std::lock_guard lock(mutex_);
|
||||||
if (stopping_)
|
if (stopping_)
|
||||||
throw std::runtime_error("GPU completion service is stopping");
|
throw std::runtime_error("GPU completion service is stopping");
|
||||||
pending_.push({device, fence, std::move(completion)});
|
pending_.push_back(pending);
|
||||||
}
|
}
|
||||||
ready_.notify_one();
|
ready_.notify_one();
|
||||||
|
return Reservation(std::move(pending));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gpu_Completion_Service::shutdown() noexcept {
|
void Gpu_Completion_Service::shutdown() noexcept {
|
||||||
@@ -35,6 +77,19 @@ void Gpu_Completion_Service::shutdown() noexcept {
|
|||||||
if (stopping_ && !thread_.joinable())
|
if (stopping_ && !thread_.joinable())
|
||||||
return;
|
return;
|
||||||
stopping_ = true;
|
stopping_ = true;
|
||||||
|
const auto cancel_reserved = [](const auto& pending) {
|
||||||
|
if (!pending)
|
||||||
|
return;
|
||||||
|
{
|
||||||
|
std::lock_guard pending_lock(pending->mutex);
|
||||||
|
if (pending->status == Pending_Fence::Status::reserved)
|
||||||
|
pending->status = Pending_Fence::Status::canceled;
|
||||||
|
}
|
||||||
|
pending->ready.notify_one();
|
||||||
|
};
|
||||||
|
cancel_reserved(active_);
|
||||||
|
for (const auto& pending : pending_)
|
||||||
|
cancel_reserved(pending);
|
||||||
}
|
}
|
||||||
ready_.notify_one();
|
ready_.notify_one();
|
||||||
if (thread_.joinable())
|
if (thread_.joinable())
|
||||||
@@ -43,7 +98,7 @@ void Gpu_Completion_Service::shutdown() noexcept {
|
|||||||
|
|
||||||
void Gpu_Completion_Service::run() noexcept {
|
void Gpu_Completion_Service::run() noexcept {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
Pending_Fence pending;
|
std::shared_ptr<Pending_Fence> pending;
|
||||||
{
|
{
|
||||||
std::unique_lock lock(mutex_);
|
std::unique_lock lock(mutex_);
|
||||||
ready_.wait(lock, [this] {
|
ready_.wait(lock, [this] {
|
||||||
@@ -52,19 +107,45 @@ void Gpu_Completion_Service::run() noexcept {
|
|||||||
if (stopping_ && pending_.empty())
|
if (stopping_ && pending_.empty())
|
||||||
return;
|
return;
|
||||||
pending = std::move(pending_.front());
|
pending = std::move(pending_.front());
|
||||||
pending_.pop();
|
pending_.pop_front();
|
||||||
|
active_ = pending;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto wait_started = std::chrono::steady_clock::now();
|
VkDevice device{VK_NULL_HANDLE};
|
||||||
|
VkFence fence{VK_NULL_HANDLE};
|
||||||
|
Completion completion;
|
||||||
|
bool observe{};
|
||||||
|
{
|
||||||
|
std::unique_lock lock(pending->mutex);
|
||||||
|
pending->ready.wait(lock, [&pending] {
|
||||||
|
return pending->status != Pending_Fence::Status::reserved;
|
||||||
|
});
|
||||||
|
if (pending->status == Pending_Fence::Status::canceled) {
|
||||||
|
lock.unlock();
|
||||||
|
std::lock_guard service_lock(mutex_);
|
||||||
|
active_.reset();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
device = pending->device;
|
||||||
|
fence = pending->fence;
|
||||||
|
completion = std::move(pending->completion);
|
||||||
|
observe = pending->observe;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto wait_started = observe
|
||||||
|
? std::chrono::steady_clock::now()
|
||||||
|
: std::chrono::steady_clock::time_point{};
|
||||||
const VkResult result = vkWaitForFences(
|
const VkResult result = vkWaitForFences(
|
||||||
pending.device, 1, &pending.fence, VK_TRUE, UINT64_MAX);
|
device, 1, &fence, VK_TRUE, UINT64_MAX);
|
||||||
const auto wait_duration =
|
|
||||||
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
|
||||||
std::chrono::steady_clock::now() - wait_started).count();
|
|
||||||
Result completion_result;
|
Result completion_result;
|
||||||
completion_result.wait_duration_ns = wait_duration > 0
|
if (observe) {
|
||||||
? static_cast<std::uint64_t>(wait_duration)
|
const auto wait_duration =
|
||||||
: 0;
|
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||||
|
std::chrono::steady_clock::now() - wait_started).count();
|
||||||
|
completion_result.wait_duration_ns = wait_duration > 0
|
||||||
|
? static_cast<std::uint64_t>(wait_duration)
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
if (result != VK_SUCCESS) {
|
if (result != VK_SUCCESS) {
|
||||||
try {
|
try {
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
@@ -75,9 +156,13 @@ void Gpu_Completion_Service::run() noexcept {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
pending.completion(std::move(completion_result));
|
completion(std::move(completion_result));
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
active_.reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,15 +4,18 @@
|
|||||||
|
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <deque>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <queue>
|
#include <memory>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
namespace renderive::render_3d::detail {
|
namespace renderive::render_3d::detail {
|
||||||
|
|
||||||
class Gpu_Completion_Service final {
|
class Gpu_Completion_Service final {
|
||||||
|
struct Pending_Fence;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
struct Result {
|
struct Result {
|
||||||
std::exception_ptr error;
|
std::exception_ptr error;
|
||||||
@@ -21,27 +24,59 @@ public:
|
|||||||
|
|
||||||
using Completion = std::function<void(Result)>;
|
using Completion = std::function<void(Result)>;
|
||||||
|
|
||||||
|
class Reservation final {
|
||||||
|
public:
|
||||||
|
Reservation() = default;
|
||||||
|
~Reservation();
|
||||||
|
|
||||||
|
Reservation(const Reservation&) = delete;
|
||||||
|
Reservation& operator=(const Reservation&) = delete;
|
||||||
|
Reservation(Reservation&& other) noexcept;
|
||||||
|
Reservation& operator=(Reservation&&) = delete;
|
||||||
|
|
||||||
|
void watch(VkDevice device, VkFence fence) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit Reservation(std::shared_ptr<Pending_Fence> pending) noexcept;
|
||||||
|
void cancel() noexcept;
|
||||||
|
|
||||||
|
std::shared_ptr<Pending_Fence> pending_;
|
||||||
|
|
||||||
|
friend class Gpu_Completion_Service;
|
||||||
|
};
|
||||||
|
|
||||||
Gpu_Completion_Service();
|
Gpu_Completion_Service();
|
||||||
~Gpu_Completion_Service();
|
~Gpu_Completion_Service();
|
||||||
|
|
||||||
Gpu_Completion_Service(const Gpu_Completion_Service&) = delete;
|
Gpu_Completion_Service(const Gpu_Completion_Service&) = delete;
|
||||||
Gpu_Completion_Service& operator=(const Gpu_Completion_Service&) = delete;
|
Gpu_Completion_Service& operator=(const Gpu_Completion_Service&) = delete;
|
||||||
|
|
||||||
void watch(VkDevice device, VkFence fence, Completion completion);
|
[[nodiscard]] Reservation prepare(Completion completion, bool observe);
|
||||||
void shutdown() noexcept;
|
void shutdown() noexcept;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Pending_Fence {
|
struct Pending_Fence {
|
||||||
|
enum class Status {
|
||||||
|
reserved,
|
||||||
|
watched,
|
||||||
|
canceled
|
||||||
|
};
|
||||||
|
|
||||||
|
std::mutex mutex;
|
||||||
|
std::condition_variable ready;
|
||||||
VkDevice device{VK_NULL_HANDLE};
|
VkDevice device{VK_NULL_HANDLE};
|
||||||
VkFence fence{VK_NULL_HANDLE};
|
VkFence fence{VK_NULL_HANDLE};
|
||||||
Completion completion;
|
Completion completion;
|
||||||
|
Status status{Status::reserved};
|
||||||
|
bool observe{};
|
||||||
};
|
};
|
||||||
|
|
||||||
void run() noexcept;
|
void run() noexcept;
|
||||||
|
|
||||||
std::mutex mutex_;
|
std::mutex mutex_;
|
||||||
std::condition_variable ready_;
|
std::condition_variable ready_;
|
||||||
std::queue<Pending_Fence> pending_;
|
std::deque<std::shared_ptr<Pending_Fence>> pending_;
|
||||||
|
std::shared_ptr<Pending_Fence> active_;
|
||||||
std::thread thread_;
|
std::thread thread_;
|
||||||
bool stopping_{};
|
bool stopping_{};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
#include <future>
|
#include <future>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <queue>
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
@@ -14,7 +13,34 @@
|
|||||||
namespace renderive::render_3d::detail {
|
namespace renderive::render_3d::detail {
|
||||||
|
|
||||||
class Render_Domain final {
|
class Render_Domain final {
|
||||||
|
struct Task {
|
||||||
|
explicit Task(std::function<void()> value)
|
||||||
|
: function(std::move(value)) {}
|
||||||
|
|
||||||
|
std::function<void()> function;
|
||||||
|
std::unique_ptr<Task> next;
|
||||||
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
class Prepared_Task final {
|
||||||
|
public:
|
||||||
|
Prepared_Task() = default;
|
||||||
|
~Prepared_Task() = default;
|
||||||
|
|
||||||
|
Prepared_Task(const Prepared_Task&) = delete;
|
||||||
|
Prepared_Task& operator=(const Prepared_Task&) = delete;
|
||||||
|
Prepared_Task(Prepared_Task&&) noexcept = default;
|
||||||
|
Prepared_Task& operator=(Prepared_Task&&) noexcept = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit Prepared_Task(std::unique_ptr<Task> task) noexcept
|
||||||
|
: task_(std::move(task)) {}
|
||||||
|
|
||||||
|
std::unique_ptr<Task> task_;
|
||||||
|
|
||||||
|
friend class Render_Domain;
|
||||||
|
};
|
||||||
|
|
||||||
Render_Domain() : thread_([this] { run(); }) {}
|
Render_Domain() : thread_([this] { run(); }) {}
|
||||||
~Render_Domain() {
|
~Render_Domain() {
|
||||||
{
|
{
|
||||||
@@ -29,14 +55,32 @@ public:
|
|||||||
Render_Domain(const Render_Domain&) = delete;
|
Render_Domain(const Render_Domain&) = delete;
|
||||||
Render_Domain& operator=(const Render_Domain&) = delete;
|
Render_Domain& operator=(const Render_Domain&) = delete;
|
||||||
|
|
||||||
void post(std::function<void()> function) {
|
[[nodiscard]] Prepared_Task prepare(std::function<void()> function) {
|
||||||
if (!function)
|
if (!function)
|
||||||
throw std::invalid_argument("render domain task is empty");
|
throw std::invalid_argument("render domain task is empty");
|
||||||
|
return Prepared_Task(
|
||||||
|
std::make_unique<Task>(std::move(function)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void post(std::function<void()> function) {
|
||||||
|
auto task = prepare(std::move(function));
|
||||||
{
|
{
|
||||||
std::lock_guard lock(mutex_);
|
std::lock_guard lock(mutex_);
|
||||||
if (stopping_)
|
if (stopping_)
|
||||||
throw std::runtime_error("Point_Scene render domain is stopping");
|
throw std::runtime_error("Point_Scene render domain is stopping");
|
||||||
tasks_.push(std::move(function));
|
enqueue_locked(std::move(task.task_));
|
||||||
|
}
|
||||||
|
condition_.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
void post(Prepared_Task task) noexcept {
|
||||||
|
if (!task.task_)
|
||||||
|
std::terminate();
|
||||||
|
{
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
if (stopping_)
|
||||||
|
std::terminate();
|
||||||
|
enqueue_locked(std::move(task.task_));
|
||||||
}
|
}
|
||||||
condition_.notify_one();
|
condition_.notify_one();
|
||||||
}
|
}
|
||||||
@@ -57,22 +101,34 @@ public:
|
|||||||
private:
|
private:
|
||||||
void run() {
|
void run() {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
std::function<void()> task;
|
std::unique_ptr<Task> task;
|
||||||
{
|
{
|
||||||
std::unique_lock lock(mutex_);
|
std::unique_lock lock(mutex_);
|
||||||
condition_.wait(lock, [&] { return stopping_ || !tasks_.empty(); });
|
condition_.wait(lock, [&] { return stopping_ || first_; });
|
||||||
if (stopping_ && tasks_.empty())
|
if (stopping_ && !first_)
|
||||||
return;
|
return;
|
||||||
task = std::move(tasks_.front());
|
task = std::move(first_);
|
||||||
tasks_.pop();
|
first_ = std::move(task->next);
|
||||||
|
if (!first_)
|
||||||
|
last_ = nullptr;
|
||||||
}
|
}
|
||||||
task();
|
task->function();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void enqueue_locked(std::unique_ptr<Task> task) noexcept {
|
||||||
|
Task* const inserted = task.get();
|
||||||
|
if (last_)
|
||||||
|
last_->next = std::move(task);
|
||||||
|
else
|
||||||
|
first_ = std::move(task);
|
||||||
|
last_ = inserted;
|
||||||
|
}
|
||||||
|
|
||||||
std::mutex mutex_;
|
std::mutex mutex_;
|
||||||
std::condition_variable condition_;
|
std::condition_variable condition_;
|
||||||
std::queue<std::function<void()>> tasks_;
|
std::unique_ptr<Task> first_;
|
||||||
|
Task* last_{};
|
||||||
bool stopping_{};
|
bool stopping_{};
|
||||||
std::thread thread_;
|
std::thread thread_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
#include "render_3D/detail/Gpu_Completion_Service.h"
|
||||||
|
#include "render_3D/detail/Render_Domain.h"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace renderive::render_3d::detail {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
static_assert(noexcept(
|
||||||
|
std::declval<Gpu_Completion_Service::Reservation&>().watch(
|
||||||
|
VK_NULL_HANDLE, VK_NULL_HANDLE)));
|
||||||
|
static_assert(noexcept(
|
||||||
|
std::declval<Render_Domain&>().post(
|
||||||
|
std::declval<Render_Domain::Prepared_Task>())));
|
||||||
|
|
||||||
|
TEST(GpuCompletionService, AbandonedReservationCancelsBeforeFenceWait) {
|
||||||
|
Gpu_Completion_Service service;
|
||||||
|
std::atomic<int> completion_count{};
|
||||||
|
{
|
||||||
|
auto reservation = service.prepare(
|
||||||
|
[&](Gpu_Completion_Service::Result) {
|
||||||
|
completion_count.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
},
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
service.shutdown();
|
||||||
|
EXPECT_EQ(completion_count.load(std::memory_order_relaxed), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GpuCompletionService, PrepareRejectsAStoppedServiceBeforeSubmission) {
|
||||||
|
Gpu_Completion_Service service;
|
||||||
|
service.shutdown();
|
||||||
|
EXPECT_THROW({
|
||||||
|
auto reservation = service.prepare(
|
||||||
|
[](Gpu_Completion_Service::Result) {}, false);
|
||||||
|
static_cast<void>(reservation);
|
||||||
|
}, std::runtime_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GpuCompletionService, ShutdownCancelsAnUnarmedReservation) {
|
||||||
|
Gpu_Completion_Service service;
|
||||||
|
auto reservation = service.prepare(
|
||||||
|
[](Gpu_Completion_Service::Result) {}, false);
|
||||||
|
service.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RenderDomain, PreparedTaskRunsAfterNoThrowHandoff) {
|
||||||
|
Render_Domain domain;
|
||||||
|
std::atomic<bool> executed{};
|
||||||
|
auto task = domain.prepare([&] {
|
||||||
|
executed.store(true, std::memory_order_release);
|
||||||
|
});
|
||||||
|
domain.post(std::move(task));
|
||||||
|
domain.invoke([] {});
|
||||||
|
EXPECT_TRUE(executed.load(std::memory_order_acquire));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace renderive::render_3d::detail
|
||||||
@@ -139,8 +139,36 @@ TEST(PointRenderIntegration,
|
|||||||
Node_Metric_Kind::gpu_fence_wait_ns));
|
Node_Metric_Kind::gpu_fence_wait_ns));
|
||||||
EXPECT_TRUE(execution.metrics.contains(
|
EXPECT_TRUE(execution.metrics.contains(
|
||||||
Node_Metric_Kind::readback_duration_ns));
|
Node_Metric_Kind::readback_duration_ns));
|
||||||
EXPECT_GT(execution.metrics.get(
|
const auto artifact_bytes = execution.metrics.get(
|
||||||
Node_Metric_Kind::backend_artifact_json_bytes), 0U);
|
Node_Metric_Kind::backend_artifact_json_bytes);
|
||||||
|
EXPECT_GT(artifact_bytes, 0U);
|
||||||
|
const auto artifact = std::find_if(
|
||||||
|
execution.attachments.begin(), execution.attachments.end(),
|
||||||
|
[](const Node_Diagnostic_Attachment& attachment) {
|
||||||
|
return attachment.type == "datoviz.drp2.artifact.json";
|
||||||
|
});
|
||||||
|
ASSERT_NE(artifact, execution.attachments.end());
|
||||||
|
EXPECT_EQ(artifact->content.size(), artifact_bytes);
|
||||||
|
EXPECT_FALSE(artifact->content.empty());
|
||||||
|
|
||||||
|
const bool gpu_timing = execution.metrics.contains(
|
||||||
|
Node_Metric_Kind::gpu_render_duration_ns);
|
||||||
|
EXPECT_EQ(gpu_timing, execution.metrics.contains(
|
||||||
|
Node_Metric_Kind::gpu_transition_duration_ns));
|
||||||
|
EXPECT_EQ(gpu_timing, execution.metrics.contains(
|
||||||
|
Node_Metric_Kind::gpu_copy_duration_ns));
|
||||||
|
EXPECT_EQ(gpu_timing, execution.metrics.contains(
|
||||||
|
Node_Metric_Kind::gpu_total_duration_ns));
|
||||||
|
if (gpu_timing) {
|
||||||
|
const auto total = execution.metrics.get(
|
||||||
|
Node_Metric_Kind::gpu_total_duration_ns);
|
||||||
|
EXPECT_GE(total, execution.metrics.get(
|
||||||
|
Node_Metric_Kind::gpu_render_duration_ns));
|
||||||
|
EXPECT_GE(total, execution.metrics.get(
|
||||||
|
Node_Metric_Kind::gpu_transition_duration_ns));
|
||||||
|
EXPECT_GE(total, execution.metrics.get(
|
||||||
|
Node_Metric_Kind::gpu_copy_duration_ns));
|
||||||
|
}
|
||||||
} catch (const std::exception& error) {
|
} catch (const std::exception& error) {
|
||||||
GTEST_SKIP() << "Vulkan/Datoviz unavailable: " << error.what();
|
GTEST_SKIP() << "Vulkan/Datoviz unavailable: " << error.what();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ inline Json metrics_json(const Node_Execution_Metrics& metrics) {
|
|||||||
"primitive_count", "queue_wait_ns", "apply_duration_ns",
|
"primitive_count", "queue_wait_ns", "apply_duration_ns",
|
||||||
"plan_emit_duration_ns", "backend_execute_duration_ns",
|
"plan_emit_duration_ns", "backend_execute_duration_ns",
|
||||||
"submit_duration_ns", "gpu_fence_wait_ns",
|
"submit_duration_ns", "gpu_fence_wait_ns",
|
||||||
"gpu_render_duration_ns", "gpu_copy_duration_ns",
|
"gpu_render_duration_ns", "gpu_transition_duration_ns",
|
||||||
|
"gpu_copy_duration_ns", "gpu_total_duration_ns",
|
||||||
"readback_duration_ns", "backend_resource_version",
|
"readback_duration_ns", "backend_resource_version",
|
||||||
"backend_frame_index", "backend_artifact_status",
|
"backend_frame_index", "backend_artifact_status",
|
||||||
"backend_validation_code", "backend_validation_command",
|
"backend_validation_code", "backend_validation_command",
|
||||||
@@ -147,6 +148,11 @@ inline Json plan_json(const Scene_Base& scene, const Render_Plan& plan) {
|
|||||||
|
|
||||||
inline Json execution_json(const Node_Execution& execution,
|
inline Json execution_json(const Node_Execution& execution,
|
||||||
const Frame_Snapshot& frame) {
|
const Frame_Snapshot& frame) {
|
||||||
|
Json attachments = Json::array();
|
||||||
|
for (const auto& attachment : execution.attachments) {
|
||||||
|
attachments.push_back({{"type", attachment.type},
|
||||||
|
{"content", attachment.content}});
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
{"node_id", execution.node_id},
|
{"node_id", execution.node_id},
|
||||||
{"ready_time_ns", execution.ready_time_ns},
|
{"ready_time_ns", execution.ready_time_ns},
|
||||||
@@ -172,7 +178,8 @@ inline Json execution_json(const Node_Execution& execution,
|
|||||||
{"external_duration_ns", execution.external_duration_ns()},
|
{"external_duration_ns", execution.external_duration_ns()},
|
||||||
{"worker_id", execution.worker_id},
|
{"worker_id", execution.worker_id},
|
||||||
{"status", execution_status(execution.status)},
|
{"status", execution_status(execution.status)},
|
||||||
{"metrics", metrics_json(execution.metrics)}
|
{"metrics", metrics_json(execution.metrics)},
|
||||||
|
{"attachments", std::move(attachments)}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,6 +247,10 @@ inline Json node_statistics_json(const Node_Statistics& statistics) {
|
|||||||
{"p99_ns", statistics.p99_ns},
|
{"p99_ns", statistics.p99_ns},
|
||||||
{"minimum_ns", statistics.minimum_ns},
|
{"minimum_ns", statistics.minimum_ns},
|
||||||
{"maximum_ns", statistics.maximum_ns},
|
{"maximum_ns", statistics.maximum_ns},
|
||||||
|
{"average_cpu_ns", statistics.average_cpu_ns},
|
||||||
|
{"average_external_ns", statistics.average_external_ns},
|
||||||
|
{"p95_cpu_ns", statistics.p95_cpu_ns},
|
||||||
|
{"p95_external_ns", statistics.p95_external_ns},
|
||||||
{"critical_path_frequency", statistics.critical_path_frequency},
|
{"critical_path_frequency", statistics.critical_path_frequency},
|
||||||
{"average_scheduler_wait_ns", statistics.average_scheduler_wait_ns}
|
{"average_scheduler_wait_ns", statistics.average_scheduler_wait_ns}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -551,6 +551,7 @@ TEST(RenderiveWebGallery, PerformanceCaptureReturnsPlanFrameAnalysisAndStatistic
|
|||||||
EXPECT_TRUE(execution.contains("end_time_ns"));
|
EXPECT_TRUE(execution.contains("end_time_ns"));
|
||||||
EXPECT_TRUE(execution.contains("worker_id"));
|
EXPECT_TRUE(execution.contains("worker_id"));
|
||||||
EXPECT_TRUE(execution.contains("status"));
|
EXPECT_TRUE(execution.contains("status"));
|
||||||
|
EXPECT_TRUE(execution.contains("attachments"));
|
||||||
const auto& analysis = frame.at("analysis");
|
const auto& analysis = frame.at("analysis");
|
||||||
EXPECT_TRUE(analysis.contains("critical_path"));
|
EXPECT_TRUE(analysis.contains("critical_path"));
|
||||||
EXPECT_TRUE(analysis.contains("parallel_overlap_ns"));
|
EXPECT_TRUE(analysis.contains("parallel_overlap_ns"));
|
||||||
@@ -570,6 +571,10 @@ TEST(RenderiveWebGallery, PerformanceCaptureReturnsPlanFrameAnalysisAndStatistic
|
|||||||
EXPECT_TRUE(statistic.contains("p50_ns"));
|
EXPECT_TRUE(statistic.contains("p50_ns"));
|
||||||
EXPECT_TRUE(statistic.contains("p95_ns"));
|
EXPECT_TRUE(statistic.contains("p95_ns"));
|
||||||
EXPECT_TRUE(statistic.contains("p99_ns"));
|
EXPECT_TRUE(statistic.contains("p99_ns"));
|
||||||
|
EXPECT_TRUE(statistic.contains("average_cpu_ns"));
|
||||||
|
EXPECT_TRUE(statistic.contains("average_external_ns"));
|
||||||
|
EXPECT_TRUE(statistic.contains("p95_cpu_ns"));
|
||||||
|
EXPECT_TRUE(statistic.contains("p95_external_ns"));
|
||||||
EXPECT_TRUE(statistic.contains("critical_path_frequency"));
|
EXPECT_TRUE(statistic.contains("critical_path_frequency"));
|
||||||
EXPECT_TRUE(statistic.contains("average_scheduler_wait_ns"));
|
EXPECT_TRUE(statistic.contains("average_scheduler_wait_ns"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
import {Stack,Typography} from "@mui/material";
|
import {Stack,Typography} from "@mui/material";
|
||||||
import type {Gallery_Capture_Session,Gallery_Captured_Frame,Gallery_Render_Plan} from "../protocol/gallery_types";
|
import type {Gallery_Capture_Session,Gallery_Captured_Frame,Gallery_Render_Plan,Json_Value} from "../protocol/gallery_types";
|
||||||
import {Metric_Grid} from "../common/metric_grid";
|
import {Metric_Grid} from "../common/metric_grid";
|
||||||
import {Json_Viewer} from "../common/json_viewer";
|
import {Json_Viewer} from "../common/json_viewer";
|
||||||
import {format_nanoseconds} from "../protocol/format";
|
import {format_nanoseconds} from "../protocol/format";
|
||||||
|
|
||||||
|
function attachment_value(type:string,content:string):Json_Value {
|
||||||
|
if(!type.endsWith(".json"))return content;
|
||||||
|
try{return JSON.parse(content) as Json_Value;}catch{return content;}
|
||||||
|
}
|
||||||
|
|
||||||
export function Node_Detail({frame,plan,session,selected_node_id}:{frame:Gallery_Captured_Frame;plan:Gallery_Render_Plan;session:Gallery_Capture_Session;selected_node_id:number|null}) {
|
export function Node_Detail({frame,plan,session,selected_node_id}:{frame:Gallery_Captured_Frame;plan:Gallery_Render_Plan;session:Gallery_Capture_Session;selected_node_id:number|null}) {
|
||||||
const node=plan.nodes.find(item=>item.node_id===selected_node_id);
|
const node=plan.nodes.find(item=>item.node_id===selected_node_id);
|
||||||
const execution=frame.node_executions.find(item=>item.node_id===selected_node_id);
|
const execution=frame.node_executions.find(item=>item.node_id===selected_node_id);
|
||||||
@@ -22,6 +27,10 @@ export function Node_Detail({frame,plan,session,selected_node_id}:{frame:Gallery
|
|||||||
["Critical",`${((analysis?.critical_path_contribution??0)*100).toFixed(2)}%`],
|
["Critical",`${((analysis?.critical_path_contribution??0)*100).toFixed(2)}%`],
|
||||||
["Moving avg",format_nanoseconds(history?.moving_average_ns)],
|
["Moving avg",format_nanoseconds(history?.moving_average_ns)],
|
||||||
["P95",format_nanoseconds(history?.p95_ns)],
|
["P95",format_nanoseconds(history?.p95_ns)],
|
||||||
["P99",format_nanoseconds(history?.p99_ns)]
|
["P99",format_nanoseconds(history?.p99_ns)],
|
||||||
]}/>{execution.metrics&&<Json_Viewer value={execution.metrics}/>}</Stack>;
|
["CPU avg",format_nanoseconds(history?.average_cpu_ns)],
|
||||||
|
["CPU P95",format_nanoseconds(history?.p95_cpu_ns)],
|
||||||
|
["External avg",format_nanoseconds(history?.average_external_ns)],
|
||||||
|
["External P95",format_nanoseconds(history?.p95_external_ns)]
|
||||||
|
]}/>{execution.metrics&&<Json_Viewer value={execution.metrics}/>} {execution.attachments.map((attachment,index)=><Stack key={`${attachment.type}:${index}`} spacing={1}><Typography variant="subtitle2">{attachment.type}</Typography><Json_Viewer value={attachment_value(attachment.type,attachment.content)}/></Stack>)}</Stack>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,10 +31,11 @@ export interface Gallery_Render_Node {node_id: number; id?: string | number; nam
|
|||||||
export interface Gallery_Render_Edge {from: number | string; to: number | string;}
|
export interface Gallery_Render_Edge {from: number | string; to: number | string;}
|
||||||
export interface Gallery_Renderable_Cache {owner_id: number; name: string; prepare_cache: string; paint_cache: string;}
|
export interface Gallery_Renderable_Cache {owner_id: number; name: string; prepare_cache: string; paint_cache: string;}
|
||||||
export interface Gallery_Render_Plan {version: number; nodes: Gallery_Render_Node[]; edges: Gallery_Render_Edge[]; renderables: Gallery_Renderable_Cache[];}
|
export interface Gallery_Render_Plan {version: number; nodes: Gallery_Render_Node[]; edges: Gallery_Render_Edge[]; renderables: Gallery_Renderable_Cache[];}
|
||||||
export interface Gallery_Node_Execution {node_id: number; worker_id: number; start_offset_ns: number; cpu_end_offset_ns: number; external_start_offset_ns: number; external_end_offset_ns: number; end_offset_ns: number; duration_ns: number; cpu_duration_ns: number; external_duration_ns: number; status: "pending"|"ready"|"running"|"waiting_external"|"complete"|"failed"; metrics?: Record<string, Json_Primitive>;}
|
export interface Gallery_Node_Diagnostic_Attachment {type: string; content: string;}
|
||||||
|
export interface Gallery_Node_Execution {node_id: number; worker_id: number; start_offset_ns: number; cpu_end_offset_ns: number; external_start_offset_ns: number; external_end_offset_ns: number; end_offset_ns: number; duration_ns: number; cpu_duration_ns: number; external_duration_ns: number; status: "pending"|"ready"|"running"|"waiting_external"|"complete"|"failed"; metrics?: Record<string, Json_Primitive>; attachments: Gallery_Node_Diagnostic_Attachment[];}
|
||||||
export interface Gallery_Node_Analysis {node_id: number; duration_ns: number; cpu_duration_ns: number; external_duration_ns: number; scheduler_wait_ns: number; critical_path_contribution: number; on_critical_path: boolean;}
|
export interface Gallery_Node_Analysis {node_id: number; duration_ns: number; cpu_duration_ns: number; external_duration_ns: number; scheduler_wait_ns: number; critical_path_contribution: number; on_critical_path: boolean;}
|
||||||
export interface Gallery_Captured_Frame {frame_id: number; render_plan_version: number; render_duration_ns: number; node_executions: Gallery_Node_Execution[]; analysis?: {total_work_duration_ns?: number; total_external_duration_ns?: number; nodes: Gallery_Node_Analysis[]};}
|
export interface Gallery_Captured_Frame {frame_id: number; render_plan_version: number; render_duration_ns: number; node_executions: Gallery_Node_Execution[]; analysis?: {total_work_duration_ns?: number; total_external_duration_ns?: number; nodes: Gallery_Node_Analysis[]};}
|
||||||
export interface Gallery_Node_Statistics {node_id: number; moving_average_ns: number; p95_ns: number; p99_ns: number; critical_path_frequency: number;}
|
export interface Gallery_Node_Statistics {node_id: number; moving_average_ns: number; p95_ns: number; p99_ns: number; average_cpu_ns: number; average_external_ns: number; p95_cpu_ns: number; p95_external_ns: number; critical_path_frequency: number;}
|
||||||
export interface Gallery_Plan_Statistics {render_plan_version: number; frame_count: number; render_average_ns: number; render_p95_ns: number; average_parallelism: number; peak_parallelism: number; scheduler_wait_average_ns: number; nodes: Gallery_Node_Statistics[];}
|
export interface Gallery_Plan_Statistics {render_plan_version: number; frame_count: number; render_average_ns: number; render_p95_ns: number; average_parallelism: number; peak_parallelism: number; scheduler_wait_average_ns: number; nodes: Gallery_Node_Statistics[];}
|
||||||
export interface Gallery_Capture_Session {session_id: number; active: boolean; requested_count: number; captured_count: number; frames: Gallery_Captured_Frame[]; node_statistics: Gallery_Node_Statistics[]; plan_statistics: Gallery_Plan_Statistics[]; summary?: Record<string, Json_Value>;}
|
export interface Gallery_Capture_Session {session_id: number; active: boolean; requested_count: number; captured_count: number; frames: Gallery_Captured_Frame[]; node_statistics: Gallery_Node_Statistics[]; plan_statistics: Gallery_Plan_Statistics[]; summary?: Record<string, Json_Value>;}
|
||||||
export interface Gallery_Performance_Capture {controller: {enabled?: boolean; session_id?: number}; sessions: Gallery_Capture_Session[]; plans: Gallery_Render_Plan[];}
|
export interface Gallery_Performance_Capture {controller: {enabled?: boolean; session_id?: number}; sessions: Gallery_Capture_Session[]; plans: Gallery_Render_Plan[];}
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
import {describe,expect,it} from "vitest";import {build_dag_model} from "../../src/dag/dag_model";
|
import {describe,expect,it} from "vitest";import {build_dag_model} from "../../src/dag/dag_model";
|
||||||
describe("DAG view model",()=>{it("uses node_id for current plan and capture overlays",()=>{const plan={version:2,nodes:[{node_id:7,name:"paint",owner:"plot",kind:"paint"}],edges:[],renderables:[]};const frame={frame_id:1,render_plan_version:2,render_duration_ns:10,node_executions:[{node_id:7,worker_id:3,start_offset_ns:0,cpu_end_offset_ns:10,external_start_offset_ns:0,external_end_offset_ns:0,end_offset_ns:10,duration_ns:10,cpu_duration_ns:10,external_duration_ns:0,status:"complete" as const}],analysis:{total_work_duration_ns:10,total_external_duration_ns:0,nodes:[{node_id:7,duration_ns:10,cpu_duration_ns:10,external_duration_ns:0,scheduler_wait_ns:1,critical_path_contribution:1,on_critical_path:true}]}};const model=build_dag_model(plan,frame,[],7);expect(model.nodes[0].id).toBe("7");expect(model.nodes[0].data).toMatchObject({duration_ns:10,worker_id:3,critical:true,selected:true});});});
|
describe("DAG view model",()=>{it("uses node_id for current plan and capture overlays",()=>{const plan={version:2,nodes:[{node_id:7,name:"paint",owner:"plot",kind:"paint"}],edges:[],renderables:[]};const frame={frame_id:1,render_plan_version:2,render_duration_ns:10,node_executions:[{node_id:7,worker_id:3,start_offset_ns:0,cpu_end_offset_ns:10,external_start_offset_ns:0,external_end_offset_ns:0,end_offset_ns:10,duration_ns:10,cpu_duration_ns:10,external_duration_ns:0,status:"complete" as const,attachments:[]}],analysis:{total_work_duration_ns:10,total_external_duration_ns:0,nodes:[{node_id:7,duration_ns:10,cpu_duration_ns:10,external_duration_ns:0,scheduler_wait_ns:1,critical_path_contribution:1,on_critical_path:true}]}};const model=build_dag_model(plan,frame,[],7);expect(model.nodes[0].id).toBe("7");expect(model.nodes[0].data).toMatchObject({duration_ns:10,worker_id:3,critical:true,selected:true});});});
|
||||||
|
|||||||
Reference in New Issue
Block a user