This commit is contained in:
2026-09-05 19:49:11 +08:00
parent 9a9e0b0d23
commit 4b610cc49b
32 changed files with 176 additions and 597 deletions
+4 -4
View File
@@ -12,10 +12,6 @@ struct Frame_Context {
const auto identity = make_identity(frame, generation);
return Frame_Context{make_root_context(identity), frame, generation};
}
inline void trace_frame(const Frame_Context& value) noexcept {
AETHERA_TRACE_FRAME("Aethera.Frame");
AETHERA_TRACE_MESSAGE_TAGS(value);
}
template <class Sink>
void trace_serialize(Sink& sink, const Frame_Context& value) {
sink.field("event", "frame");
@@ -23,4 +19,8 @@ void trace_serialize(Sink& sink, const Frame_Context& value) {
sink.field("frame", value.frame);
sink.field("generation", value.generation);
}
inline void trace_frame(const Frame_Context& value) noexcept {
AETHERA_TRACE_FRAME("Aethera.Frame");
AETHERA_TRACE_MESSAGE_TAGS(value);
}
}
@@ -1,103 +0,0 @@
#pragma once
#include "Frame_Timing.hpp"
#include "GPU_Completion.hpp"
#include "Frame_Policy.hpp"
#include "Tracy.hpp"
#include "Vulkan.hpp"
#include <cstdint>
namespace aethera::observation {
struct Frame_Attention {
Frame_Timing timing{};
std::uint64_t gpu_render_ns{};
std::uint64_t gpu_total_ns{};
std::uint64_t uploaded_bytes{};
std::uint64_t readback_bytes{};
std::uint64_t upload_ns{};
std::uint64_t upload_queue_wait_ns{};
std::uint64_t upload_fence_wait_ns{};
std::uint64_t shader_compile_ns{};
std::uint64_t pipeline_create_ns{};
std::uint32_t pipeline_create_count{};
Vulkan_State vulkan{};
GPU_Completion_State gpu_completion{};
};
struct Frame_Attention_Index {
Frame_Context frame{};
bool resource_activity{};
bool upload_wait{};
bool pipeline_build{};
bool frame_overlap{};
bool non_frame_submission{};
bool completion_queue_gap{};
bool completion_saturated{};
};
[[nodiscard]] inline Frame_Attention_Index make_frame_attention_index(const Frame_Attention& value) noexcept {
return Frame_Attention_Index{
value.timing.frame,
value.uploaded_bytes != 0 || value.readback_bytes != 0 || value.upload_ns != 0 || value.shader_compile_ns != 0 || value.pipeline_create_ns != 0 || value.pipeline_create_count != 0,
value.upload_queue_wait_ns != 0 || value.upload_fence_wait_ns != 0,
value.shader_compile_ns != 0 || value.pipeline_create_ns != 0 || value.pipeline_create_count != 0,
value.vulkan.frames_in_flight > 1,
value.vulkan.pending_submissions > value.vulkan.frames_in_flight,
value.gpu_completion.in_flight > value.gpu_completion.active,
value.gpu_completion.capacity != 0 && value.gpu_completion.in_flight >= value.gpu_completion.capacity};
}
[[nodiscard]] inline bool has_frame_attention_index(const Frame_Attention_Index& value) noexcept {
return value.resource_activity || value.upload_wait || value.pipeline_build || value.frame_overlap || value.non_frame_submission || value.completion_queue_gap || value.completion_saturated;
}
template <class Sink>
void trace_serialize(Sink& sink, const Frame_Attention& value) {
sink.field("event", "frame_attention");
trace_serialize(sink, value.timing.frame.context);
sink.field("frame", value.timing.frame.frame);
sink.field("generation", value.timing.frame.generation);
sink.field("render_latency_ns", value.timing.render_latency_ns);
sink.field("graph_start_latency_ns", value.timing.graph_start_latency_ns);
sink.field("graph_to_submit_ns", value.timing.graph_to_submit_ns);
sink.field("backend_prepare_ns", value.timing.backend_prepare_ns);
sink.field("submit_path_ns", value.timing.submit_path_ns);
sink.field("completion_tail_ns", value.timing.completion_tail_ns);
sink.field("gpu_render_ns", value.gpu_render_ns);
sink.field("gpu_total_ns", value.gpu_total_ns);
sink.field("uploaded_bytes", value.uploaded_bytes);
sink.field("readback_bytes", value.readback_bytes);
sink.field("upload_ns", value.upload_ns);
sink.field("upload_queue_wait_ns", value.upload_queue_wait_ns);
sink.field("upload_fence_wait_ns", value.upload_fence_wait_ns);
sink.field("shader_compile_ns", value.shader_compile_ns);
sink.field("pipeline_create_ns", value.pipeline_create_ns);
sink.field("pipeline_create_count", value.pipeline_create_count);
sink.field("vulkan_pending", value.vulkan.pending_submissions);
sink.field("frames_in_flight", value.vulkan.frames_in_flight);
sink.field("completion_in_flight", value.gpu_completion.in_flight);
sink.field("completion_active", value.gpu_completion.active);
sink.field("completion_capacity", value.gpu_completion.capacity);
sink.field("completion_capacity_exhausted", value.gpu_completion.capacity_exhausted_count);
sink.field("completion_errors", value.gpu_completion.error_count);
}
template <class Sink>
void trace_serialize(Sink& sink, const Frame_Attention_Index& value) {
sink.field("event", "frame_attention_index");
trace_serialize(sink, value.frame.context);
sink.field("frame", value.frame.frame);
sink.field("generation", value.frame.generation);
if (value.resource_activity) sink.field("resource_activity", true);
if (value.upload_wait) sink.field("upload_wait", true);
if (value.pipeline_build) sink.field("pipeline_build", true);
if (value.frame_overlap) sink.field("frame_overlap", true);
if (value.non_frame_submission) sink.field("non_frame_submission", true);
if (value.completion_queue_gap) sink.field("completion_queue_gap", true);
if (value.completion_saturated) sink.field("completion_saturated", true);
}
inline void trace_frame_attention(const Frame_Attention& value) noexcept {
#ifdef TRACY_ENABLE
if (!tracy_connected()) return;
AETHERA_TRACE_MESSAGE_TAGS(value);
trace_frame_policy_context(value.timing.frame);
const auto index = make_frame_attention_index(value);
if (has_frame_attention_index(index)) AETHERA_TRACE_MESSAGE_TAGS(index);
#else
static_cast<void>(value);
#endif
}
}
@@ -15,16 +15,6 @@ struct Frame_Policy_State_View {
const State& state;
Frame_Policy_Slots slots{};
};
template <class State>
inline void trace_frame_policy_state(std::uintptr_t policy, const State& state, Frame_Policy_Slots slots) noexcept {
#ifdef TRACY_ENABLE
AETHERA_TRACE_MESSAGE_TAGS(Frame_Policy_State_View<State>{policy, state, slots});
#else
static_cast<void>(policy);
static_cast<void>(state);
static_cast<void>(slots);
#endif
}
template <class Sink, class State>
void trace_serialize(Sink& sink, const Frame_Policy_State_View<State>& value) {
sink.field("event", "frame_policy_state");
@@ -55,4 +45,14 @@ void trace_serialize(Sink& sink, const Frame_Policy_State_View<State>& value) {
sink.field("end_to_end_stddev_ns", value.state.end_to_end_time_ns.standard_deviation);
}
}
template <class State>
inline void trace_frame_policy_state(std::uintptr_t policy, const State& state, Frame_Policy_Slots slots) noexcept {
#ifdef TRACY_ENABLE
AETHERA_TRACE_MESSAGE_TAGS(Frame_Policy_State_View<State>{policy, state, slots});
#else
static_cast<void>(policy);
static_cast<void>(state);
static_cast<void>(slots);
#endif
}
}
@@ -18,6 +18,16 @@ struct GPU_Completion_Metrics {
std::int32_t error{};
std::int32_t backend_result{};
};
template <class Sink>
void trace_serialize(Sink& sink, const GPU_Completion_Metrics& value) {
sink.field("event", value.event);
sink.field("in_flight", value.in_flight);
sink.field("active", value.active);
sink.field("capacity", value.capacity);
if (value.wait_ns != 0) sink.field("wait_ns", value.wait_ns);
if (value.error != 0) sink.field("error", value.error);
if (value.backend_result != 0) sink.field("backend_result", value.backend_result);
}
inline void trace_gpu_completion_admitted(std::size_t in_flight) noexcept {
#ifdef TRACY_ENABLE
AETHERA_TRACE_PLOT_I("Aethera.GPUCompletion.InFlight", in_flight);
@@ -71,14 +81,4 @@ inline void trace_gpu_completion_completed(std::size_t in_flight, std::size_t ac
static_cast<void>(backend_result);
#endif
}
template <class Sink>
void trace_serialize(Sink& sink, const GPU_Completion_Metrics& value) {
sink.field("event", value.event);
sink.field("in_flight", value.in_flight);
sink.field("active", value.active);
sink.field("capacity", value.capacity);
if (value.wait_ns != 0) sink.field("wait_ns", value.wait_ns);
if (value.error != 0) sink.field("error", value.error);
if (value.backend_result != 0) sink.field("backend_result", value.backend_result);
}
}
+14 -14
View File
@@ -32,20 +32,6 @@ struct Frame_Resource_Metrics {
[[nodiscard]] constexpr bool has_resource_activity(const Frame_Resource_Metrics& value) noexcept {
return value.uploaded_bytes != 0 || value.readback_bytes != 0 || value.buffer_create_ns != 0 || value.texture_create_ns != 0 || value.shader_compile_ns != 0 || value.pipeline_create_ns != 0 || value.upload_ns != 0 || value.staging_allocate_ns != 0 || value.host_copy_ns != 0 || value.submit_queue_wait_ns != 0 || value.fence_wait_ns != 0 || value.transfer_ns != 0 || value.pipeline_create_count != 0;
}
inline void trace_frame_resources(const Frame_Resource_Metrics& value) noexcept {
#ifdef TRACY_ENABLE
AETHERA_TRACE_PLOT_I("Aethera.Resource.UploadedBytes", value.uploaded_bytes);
AETHERA_TRACE_PLOT_I("Aethera.Resource.ReadbackBytes", value.readback_bytes);
AETHERA_TRACE_PLOT_I("Aethera.Resource.UploadNs", value.upload_ns);
AETHERA_TRACE_PLOT_I("Aethera.Resource.UploadQueueWaitNs", value.submit_queue_wait_ns);
AETHERA_TRACE_PLOT_I("Aethera.Resource.UploadFenceWaitNs", value.fence_wait_ns);
AETHERA_TRACE_PLOT_I("Aethera.Resource.ShaderCompileNs", value.shader_compile_ns);
AETHERA_TRACE_PLOT_I("Aethera.Resource.PipelineCreateNs", value.pipeline_create_ns);
if (has_resource_activity(value)) AETHERA_TRACE_MESSAGE_TAGS(value);
#else
static_cast<void>(value);
#endif
}
template <class Sink>
void trace_serialize(Sink& sink, const Frame_Resource_Metrics& value) {
sink.field("event", "frame_resources");
@@ -66,4 +52,18 @@ void trace_serialize(Sink& sink, const Frame_Resource_Metrics& value) {
sink.field("transfer_ns", value.transfer_ns);
sink.field("pipeline_create_count", value.pipeline_create_count);
}
inline void trace_frame_resources(const Frame_Resource_Metrics& value) noexcept {
#ifdef TRACY_ENABLE
AETHERA_TRACE_PLOT_I("Aethera.Resource.UploadedBytes", value.uploaded_bytes);
AETHERA_TRACE_PLOT_I("Aethera.Resource.ReadbackBytes", value.readback_bytes);
AETHERA_TRACE_PLOT_I("Aethera.Resource.UploadNs", value.upload_ns);
AETHERA_TRACE_PLOT_I("Aethera.Resource.UploadQueueWaitNs", value.submit_queue_wait_ns);
AETHERA_TRACE_PLOT_I("Aethera.Resource.UploadFenceWaitNs", value.fence_wait_ns);
AETHERA_TRACE_PLOT_I("Aethera.Resource.ShaderCompileNs", value.shader_compile_ns);
AETHERA_TRACE_PLOT_I("Aethera.Resource.PipelineCreateNs", value.pipeline_create_ns);
if (has_resource_activity(value)) AETHERA_TRACE_MESSAGE_TAGS(value);
#else
static_cast<void>(value);
#endif
}
}
@@ -7,7 +7,6 @@
#include <utility>
namespace aethera::observation {
namespace detail {
void trace_serialize() = delete;
template <class Sink, class T>
concept Custom_Serializable = requires(Sink& sink, const T& value) { trace_serialize(sink, value); };
template <class Sink, class T>
+2 -2
View File
@@ -125,7 +125,7 @@ struct Tracy_Task_Zone {
inline void trace_tracy_text_truncation(const char* channel, const Tracy_Text_Sink& truncated) noexcept {
Tracy_Text_Sink sink;
serialize(sink, Tracy_Text_Truncation{channel, truncated.required_size(), truncated.capacity()});
TracyCMessage(sink.data(), sink.size())
TracyCMessage(sink.data(), sink.size());
}
inline void set_tracy_thread_name(const char* name) noexcept {
TracyCSetThreadName(name)
@@ -161,7 +161,7 @@ void emit_tracy_message(const Tags& tags) {
if (!trace_capture_active()) return;
Tracy_Text_Sink sink;
serialize(sink, tags);
if (!sink.empty()) TracyCMessage(sink.data(), sink.size())
if (!sink.empty()) TracyCMessage(sink.data(), sink.size());
if (sink.truncated()) trace_tracy_text_truncation("message", sink);
}
#else
@@ -1,20 +0,0 @@
#pragma once
#include "Tracy.hpp"
#ifdef TRACY_ENABLE
#include <tracy/TracyVulkan.hpp>
#define AETHERA_TRACE_VULKAN_CONTEXT(...) TracyVkContext(__VA_ARGS__)
#define AETHERA_TRACE_VULKAN_DESTROY(context) TracyVkDestroy(context)
#define AETHERA_TRACE_VULKAN_CONTEXT_NAME(context, name, size) TracyVkContextName(context, name, size)
#define AETHERA_TRACE_VULKAN_ZONE(context, command_buffer, name) TracyVkZone(context, command_buffer, name)
#define AETHERA_TRACE_VULKAN_ZONE_TAGS(context, command_buffer, name, ...) AETHERA_TRACE_MESSAGE_TAGS(__VA_ARGS__); TracyVkZone(context, command_buffer, name)
#define AETHERA_TRACE_VULKAN_COLLECT(context, command_buffer) TracyVkCollect(context, command_buffer)
#define AETHERA_TRACE_VULKAN_COLLECT_HOST(context) TracyVkCollectHost(context)
#else
#define AETHERA_TRACE_VULKAN_CONTEXT(...) nullptr
#define AETHERA_TRACE_VULKAN_DESTROY(context)
#define AETHERA_TRACE_VULKAN_CONTEXT_NAME(context, name, size)
#define AETHERA_TRACE_VULKAN_ZONE(context, command_buffer, name)
#define AETHERA_TRACE_VULKAN_ZONE_TAGS(context, command_buffer, name, ...)
#define AETHERA_TRACE_VULKAN_COLLECT(context, command_buffer)
#define AETHERA_TRACE_VULKAN_COLLECT_HOST(context)
#endif
+32 -32
View File
@@ -55,38 +55,6 @@ decltype(auto) trace_vulkan_submit(Submit&& submit) {
AETHERA_TRACE_ZONE("Vulkan.Submit");
return std::invoke(std::forward<Submit>(submit));
}
inline void trace_vulkan_submitted(const Vulkan_Submission_Context& submission) noexcept {
#ifdef TRACY_ENABLE
AETHERA_TRACE_MESSAGE_TAGS(submission);
#else
static_cast<void>(submission);
#endif
}
inline void trace_vulkan_completed(const Vulkan_Submission_Context& submission, std::uint64_t latency_ns) noexcept {
#ifdef TRACY_ENABLE
const auto value = make_vulkan_completion_context(submission, latency_ns);
AETHERA_TRACE_MESSAGE_TAGS(value);
if (submission.frame_bound) AETHERA_TRACE_PLOT_I("Aethera.Vulkan.FrameSubmitToCompleteNs", latency_ns);
else AETHERA_TRACE_PLOT_I("Aethera.Vulkan.ImmediateSubmitToCompleteNs", latency_ns);
#else
static_cast<void>(submission);
static_cast<void>(latency_ns);
#endif
}
inline void trace_vulkan_gpu_timing(const Vulkan_Submission_Context& submission, std::uint64_t render_ns, std::uint64_t transition_ns, std::uint64_t copy_ns, std::uint64_t total_ns) noexcept {
#ifdef TRACY_ENABLE
const auto value = make_vulkan_gpu_timing_context(submission, render_ns, transition_ns, copy_ns, total_ns);
AETHERA_TRACE_MESSAGE_TAGS(value);
AETHERA_TRACE_PLOT_I("Aethera.Vulkan.GPU.RenderNs", render_ns);
AETHERA_TRACE_PLOT_I("Aethera.Vulkan.GPU.TotalNs", total_ns);
#else
static_cast<void>(submission);
static_cast<void>(render_ns);
static_cast<void>(transition_ns);
static_cast<void>(copy_ns);
static_cast<void>(total_ns);
#endif
}
template <class Sink>
void trace_serialize(Sink& sink, const Vulkan_Submission_Context& value) {
sink.field("event", "vulkan_submit");
@@ -122,4 +90,36 @@ void trace_serialize(Sink& sink, const Vulkan_Completion_Context& value) {
if (value.queue != 0) sink.field("queue", value.queue);
sink.field("latency_ns", value.latency_ns);
}
inline void trace_vulkan_submitted(const Vulkan_Submission_Context& submission) noexcept {
#ifdef TRACY_ENABLE
AETHERA_TRACE_MESSAGE_TAGS(submission);
#else
static_cast<void>(submission);
#endif
}
inline void trace_vulkan_completed(const Vulkan_Submission_Context& submission, std::uint64_t latency_ns) noexcept {
#ifdef TRACY_ENABLE
const auto value = make_vulkan_completion_context(submission, latency_ns);
AETHERA_TRACE_MESSAGE_TAGS(value);
if (submission.frame_bound) AETHERA_TRACE_PLOT_I("Aethera.Vulkan.FrameSubmitToCompleteNs", latency_ns);
else AETHERA_TRACE_PLOT_I("Aethera.Vulkan.ImmediateSubmitToCompleteNs", latency_ns);
#else
static_cast<void>(submission);
static_cast<void>(latency_ns);
#endif
}
inline void trace_vulkan_gpu_timing(const Vulkan_Submission_Context& submission, std::uint64_t render_ns, std::uint64_t transition_ns, std::uint64_t copy_ns, std::uint64_t total_ns) noexcept {
#ifdef TRACY_ENABLE
const auto value = make_vulkan_gpu_timing_context(submission, render_ns, transition_ns, copy_ns, total_ns);
AETHERA_TRACE_MESSAGE_TAGS(value);
AETHERA_TRACE_PLOT_I("Aethera.Vulkan.GPU.RenderNs", render_ns);
AETHERA_TRACE_PLOT_I("Aethera.Vulkan.GPU.TotalNs", total_ns);
#else
static_cast<void>(submission);
static_cast<void>(render_ns);
static_cast<void>(transition_ns);
static_cast<void>(copy_ns);
static_cast<void>(total_ns);
#endif
}
}
@@ -1,5 +1,6 @@
#include "Throttled_Latest_Only.hpp"
#include "Error_handling_specification/Failure_Policy.hpp"
#include "Trace_Adapter.hpp"
#include <algorithm>
#include <concepts>
#include <exception>
@@ -385,6 +386,9 @@ void Throttled_Latest_only::Private::finish_stop_if_ready() {
}
}
void Throttled_Latest_only::Private::publish_state() {
#ifdef TRACY_ENABLE
frame_policy_trace::published(this, *concurrent<State_Tag>().internal.use(), frames);
#endif
concurrent<State_Tag>().internal.advance();
}
bool Throttled_Latest_only::Private::destructible() const noexcept {
@@ -1,6 +1,6 @@
#include "Task_Runtime.hpp"
#include "detail/Taskflow_Execution.ipp"
#include "observation/Tracy.hpp"
#include "Trace_Adapter.hpp"
#include <atomic>
#include <chrono>
#include <expected>
@@ -71,28 +71,15 @@ const tf::Worker& task_worker(const tf::WorkerView& worker) noexcept {
struct Task_Observer final : tf::ObserverInterface {
#ifdef TRACY_ENABLE
static constexpr std::size_t Tracy_Zone_Depth = 128;
struct Frame_Task_Trace_Tags {
std::uint64_t frame{};
std::uint64_t generation{};
std::string_view graph{};
std::size_t worker{};
std::uint64_t task{};
};
struct Tracy_Worker_State {
std::array<observation::Tracy_Task_Zone, Tracy_Zone_Depth> zones{};
std::array<task_flow_trace::Task_Zone, Tracy_Zone_Depth> zones{};
std::size_t depth{};
std::size_t ignored_depth{};
bool named{};
};
void name_worker(Tracy_Worker_State& state, std::size_t worker_id) noexcept {
if (state.named) return;
constexpr std::string_view Prefix{"Aethera.Taskflow."};
std::array<char, 64> name{};
std::memcpy(name.data(), Prefix.data(), Prefix.size());
const auto [end, error] = std::to_chars(name.data() + Prefix.size(), name.data() + name.size() - 1, worker_id);
if (error != std::errc{}) return;
*end = '\0';
observation::set_tracy_thread_name(name.data());
task_flow_trace::name_worker(worker_id);
state.named = true;
}
void trace_entry(const tf::Worker& worker, const tf::TaskView& task, std::uint64_t native_id, const detail::Taskflow_Observation_Execution* execution) noexcept {
@@ -102,7 +89,7 @@ struct Task_Observer final : tf::ObserverInterface {
++state.ignored_depth;
return;
}
if (!observation::tracy_connected()) {
if (!task_flow_trace::capture_active()) {
if (state.depth != 0) state.ignored_depth = 1;
return;
}
@@ -112,12 +99,12 @@ struct Task_Observer final : tf::ObserverInterface {
}
const std::string_view name{task.name()};
auto& zone = state.zones[state.depth++];
zone = observation::begin_tracy_task_zone(name, native_id);
zone = task_flow_trace::begin_task(name, native_id);
if (execution == nullptr) return;
std::string_view graph;
const auto* context = execution->trace_context(graph);
if (context == nullptr) return;
observation::attach_tracy_task_tags(zone, Frame_Task_Trace_Tags{context->frame, context->generation, graph, worker.id(), native_id});
task_flow_trace::attach_frame(zone, *context);
}
void trace_exit(const tf::Worker& worker) noexcept {
auto& state = tracy_worker_states[worker.id()];
@@ -126,7 +113,7 @@ struct Task_Observer final : tf::ObserverInterface {
return;
}
if (state.depth == 0) return;
observation::end_tracy_task_zone(state.zones[--state.depth]);
task_flow_trace::end_task(state.zones[--state.depth]);
}
std::vector<Tracy_Worker_State> tracy_worker_states;
#endif
@@ -223,7 +210,7 @@ public:
return Run_Taskflow_Result::observation_unavailable;
}
#ifdef TRACY_ENABLE
if (trace_context && observation::tracy_connected()) observation_execution->set_trace_context(*trace_context, graph_execution->native_taskflow().name());
if (trace_context && task_flow_trace::capture_active()) observation_execution->set_trace_context(*trace_context, graph_execution->native_taskflow().name());
#else
(void)trace_context;
#endif