Bicameral Engine
GPU-resident engine for a magic-research open-world game
Loading...
Searching...
No Matches
debug_ring.cpp
Go to the documentation of this file.
1// debug_ring.cpp — シェーダーの printf / assert のリングを読み戻してログに出す(T-0003、docs/design/16-debug-test.md §1)。
2// レコードのレイアウトは shaders/common/debug_ring.hlsli、書式の一覧は shaders/common/debug_formats.hlsli。
3// 書式は std::format の書式だが、引数の型(符号の有無・32 / 64bit)は実行時に分かるので、DebugArgument の formatter が
4// 書式の指定({:#x} など)を覚えておき、値を本来の型に戻してから当てはめる。
5#include "gpu/debug_ring.h"
6
7#include <array>
8
9#include "gpu/resources.h"
10
11namespace bicameral::gpu {
12
13 // 1 つの引数: 64bit に広げた値と、元の型(DEBUG_ARG_*)
15 uint64_t bits = 0;
16 uint32_t kind = DEBUG_ARG_U32;
17 };
18
19} // namespace bicameral::gpu
20
21// 書式の指定(":" の後ろ)を parse で覚え、format で元の型の値に同じ指定を当てはめる
22template <>
23struct std::formatter<bicameral::gpu::DebugArgument, char> {
24 std::string_view spec; // 書式の指定(":" の後ろ、"}" の前)
25
26 // NOLINTNEXTLINE(readability-identifier-naming) std::formatter が要求する名前
27 constexpr std::format_parse_context::iterator parse(std::format_parse_context& context) {
28 const auto end = std::find(context.begin(), context.end(), '}');
29 spec = std::string_view(context.begin(), end);
30
31 return end;
32 }
33
34 // NOLINTNEXTLINE(readability-identifier-naming) std::formatter が要求する名前
35 std::format_context::iterator format(const bicameral::gpu::DebugArgument& argument,
36 std::format_context& context) const {
37 using namespace bicameral;
38 const std::string pattern = std::format("{{:{}}}", spec);
39 switch (argument.kind) {
40 case DEBUG_ARG_U32: {
41 auto value = static_cast<uint32_t>(argument.bits);
42 return std::vformat_to(context.out(), pattern, std::make_format_args(value));
43 }
44 case DEBUG_ARG_I32: {
45 auto value = static_cast<int32_t>(static_cast<uint32_t>(argument.bits));
46 return std::vformat_to(context.out(), pattern, std::make_format_args(value));
47 }
48 case DEBUG_ARG_U64: {
49 auto value = argument.bits;
50 return std::vformat_to(context.out(), pattern, std::make_format_args(value));
51 }
52 default: {
53 auto value = static_cast<int64_t>(argument.bits);
54 return std::vformat_to(context.out(), pattern, std::make_format_args(value));
55 }
56 }
57 }
58};
59
60namespace bicameral::gpu {
61 namespace {
62
63 // --- 書式の一覧(debug_formats.hlsli を C++ の表にする)---
64
67 std::string_view where;
68 std::string_view text;
69 };
70
72#define DEBUG_FORMAT(name, channel, where, text) {Channel::channel, where, text},
73#include "common/debug_formats.hlsli"
74#include "gpu/com_ptr.h"
75#undef DEBUG_FORMAT
76 };
77
78 static_assert(std::size(DEBUG_FORMATS) == static_cast<size_t>(DebugFormat::Count),
79 "debug_formats.hlsli と DebugFormat の数が合わない");
80
81 using DebugArguments = std::array<DebugArgument, DEBUG_RECORD_MAX_ARGS>;
82
83 // 書式に当てはめられないときに、引数を型どおりに並べる
84 std::string RawArguments(const DebugArguments& arguments, uint32_t argCount) {
85 std::string text;
86 for (uint32_t index = 0; index < argCount; ++index)
87 text += std::format("{}{}", index == 0 ? "" : ", ", arguments[index]);
88
89 return "[" + text + "]";
90 }
91
92 // シェーダーが書いた数だけの引数で当てはめる(書式より少なければ format_error。多いのは構わない)
93 std::string VFormatArguments(std::string_view pattern, const DebugArguments& a, uint32_t argCount) {
94 switch (argCount) {
95 case 0: return std::vformat(pattern, std::make_format_args());
96 case 1: return std::vformat(pattern, std::make_format_args(a[0]));
97 case 2: return std::vformat(pattern, std::make_format_args(a[0], a[1]));
98 case 3: return std::vformat(pattern, std::make_format_args(a[0], a[1], a[2]));
99 case 4: return std::vformat(pattern, std::make_format_args(a[0], a[1], a[2], a[3]));
100 case 5: return std::vformat(pattern, std::make_format_args(a[0], a[1], a[2], a[3], a[4]));
101 default: return std::vformat(pattern, std::make_format_args(a[0], a[1], a[2], a[3], a[4], a[5]));
102 }
103 }
104
105 std::string FormatArguments(std::string_view pattern, const DebugArguments& arguments, uint32_t argCount) {
106 try {
107 return VFormatArguments(pattern, arguments, argCount);
108 } catch (const std::format_error& error) {
109 return std::format("(書式 \"{}\" に当てはまらない: {}) {}", pattern, error.what(),
110 RawArguments(arguments, argCount));
111 }
112 }
113
114 // --- ログに出す ---
115
116 void LogMessage(const DebugMessage& message) {
117 Log(message.channel, message.isAssert ? Level::Error : Level::Info, "GPU {} {}:{} {}",
118 message.isAssert ? "assert" : "print", message.where, message.line, message.text);
119 }
120
121 void LogSummary(const DebugRingContents& contents, uint32_t loggedCount) {
122 const size_t notLogged = contents.messages.size() - loggedCount;
123 if (notLogged > 0) {
125 "GPU のデバッグ出力: ほか {} 件はログに出していない(全 {} 件、assert {} 件)", notLogged,
126 contents.messages.size(), contents.assertCount);
127 }
128
129 if (contents.droppedCount > 0) {
130 Log(Channel::Gpu, Level::Warning, "GPU のデバッグ出力: リングが溢れて {} 件を落とした(容量 {} 件)",
131 contents.droppedCount, DEBUG_RING_CAPACITY);
132 }
133 }
134
135 } // namespace
136
137 // --- 1 レコードを読む ---
138
139 DebugMessage DecodeDebugRecord(std::span<const uint32_t, DEBUG_RECORD_WORDS> record) {
140 DebugMessage message;
141 const uint32_t formatIndex = record[0];
142 const uint32_t argCount = std::min((record[1] >> 4) & 0xFu, DEBUG_RECORD_MAX_ARGS);
143 const uint32_t argKinds = record[1] >> 8;
144 message.isAssert = (record[1] & 0xFu) == DEBUG_KIND_ASSERT;
145 message.line = record[2];
146
147 DebugArguments arguments{};
148 for (uint32_t index = 0; index < argCount; ++index) {
149 const uint32_t word = DEBUG_RECORD_ARG_WORD + index * 2;
150 arguments[index] = {.bits = record[word] | (uint64_t{record[word + 1]} << 32),
151 .kind = (argKinds >> (index * 2)) & 0x3u};
152 }
153
154 if (formatIndex >= std::size(DEBUG_FORMATS)) {
155 message.where = "?";
156 message.text = std::format("(知らない書式の番号 {}) {}", formatIndex, RawArguments(arguments, argCount));
157
158 return message;
159 }
160
161 const DebugFormatEntry& entry = DEBUG_FORMATS[formatIndex];
162 message.format = static_cast<DebugFormat>(formatIndex);
163 message.channel = entry.channel;
164 message.where = entry.where;
165 message.text = FormatArguments(entry.text, arguments, argCount);
166
167 return message;
168 }
169
170 // --- DebugRing ---
171
172 std::expected<DebugRing, std::string> DebugRing::Create(ID3D12Device* device, uint32_t slotCount) {
173 auto ring = ReadbackRing::Create(device, DEBUG_RING_BYTES, DEBUG_RING_HEADER_BYTES, slotCount, L"DebugRing");
174 if (!ring)
175 return std::unexpected("デバッグのリングを作れない: " + ring.error());
176
177 return DebugRing(std::move(*ring));
178 }
179
180 void DebugRing::RecordBegin(ID3D12GraphicsCommandList* list) const {
181 m_ring.RecordBegin(list);
182 }
183
184 void DebugRing::RecordReadbackAndReset(ID3D12GraphicsCommandList* list, uint32_t slot) const {
185 m_ring.RecordReadbackAndReset(list, slot);
186 }
187
188 DebugRingContents DebugRing::Drain(uint32_t maxLoggedMessages, uint32_t slot) const {
189 DebugRingContents contents;
190 // 見出しを先に読み、書かれた分だけを読む(ふつうは 0 件。毎フレーム 256 KiB を写さない)
191 uint32_t requested = 0;
192 if (!m_ring.Read(slot, std::as_writable_bytes(std::span(&requested, 1))))
193 return contents;
194
195 contents.requestedCount = requested;
196 const uint32_t storedCount = std::min(contents.requestedCount, DEBUG_RING_CAPACITY);
197 std::vector<uint32_t> words((DEBUG_RING_HEADER_BYTES + storedCount * DEBUG_RECORD_BYTES) / 4);
198 if (storedCount > 0 && !m_ring.Read(slot, std::as_writable_bytes(std::span(words))))
199 return contents;
200
201 contents.droppedCount = contents.requestedCount - storedCount;
202 contents.messages.reserve(storedCount);
203 const std::span<const uint32_t> records = std::span(words).subspan(DEBUG_RING_HEADER_BYTES / 4);
204
205 for (uint32_t index = 0; index < storedCount; ++index) {
206 const auto record = records.subspan(size_t{index} * DEBUG_RECORD_WORDS).first<DEBUG_RECORD_WORDS>();
207 DebugMessage message = DecodeDebugRecord(record);
208 if (message.isAssert)
209 ++contents.assertCount;
210
211 contents.messages.push_back(std::move(message));
212 }
213
214 // assert を先に出す(print が多いと埋もれるので)
215 uint32_t loggedCount = 0;
216 for (const bool assertPass : {true, false}) {
217 for (const DebugMessage& message : contents.messages) {
218 if (message.isAssert != assertPass || loggedCount >= maxLoggedMessages)
219 continue;
220
221 LogMessage(message);
222 ++loggedCount;
223 }
224 }
225
226 LogSummary(contents, loggedCount);
227
228 return contents;
229 }
230
231} // namespace bicameral::gpu
DebugRing(ReadbackRing &&ring)
Definition debug_ring.h:71
void RecordReadbackAndReset(ID3D12GraphicsCommandList *list, uint32_t slot=0) const
DebugRingContents Drain(uint32_t maxLoggedMessages=DEFAULT_MAX_LOGGED_MESSAGES, uint32_t slot=0) const
static std::expected< DebugRing, std::string > Create(ID3D12Device *device, uint32_t slotCount=1)
void RecordBegin(ID3D12GraphicsCommandList *list) const
static std::expected< ReadbackRing, std::string > Create(ID3D12Device *device, uint64_t bytes, uint64_t headerBytes, uint32_t slotCount, std::wstring_view name)
std::string RawArguments(const DebugArguments &arguments, uint32_t argCount)
std::string VFormatArguments(std::string_view pattern, const DebugArguments &a, uint32_t argCount)
void LogMessage(const DebugMessage &message)
std::string FormatArguments(std::string_view pattern, const DebugArguments &arguments, uint32_t argCount)
std::array< DebugArgument, DEBUG_RECORD_MAX_ARGS > DebugArguments
void LogSummary(const DebugRingContents &contents, uint32_t loggedCount)
DebugMessage DecodeDebugRecord(std::span< const uint32_t, DEBUG_RECORD_WORDS > record)
Channel
Definition log.h:38
void Log(Channel channel, Level level, FormatWithLocation< std::type_identity_t< Args >... > pattern, Args &&... args)
Definition log.h:140
std::vector< DebugMessage > messages
Definition debug_ring.h:48
std::format_context::iterator format(const bicameral::gpu::DebugArgument &argument, std::format_context &context) const
constexpr std::format_parse_context::iterator parse(std::format_parse_context &context)