graph.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806
  1. /*
  2. * Copyright (c) 2012-2026 Daniele Bartolini and individual contributors.
  3. * License: https://github.com/dbartolini/crown/blob/master/LICENSE
  4. */
  5. #include "device/graph.h"
  6. #if CROWN_DEBUG
  7. #include "core/json/sjson.h"
  8. #include "core/list.inl"
  9. #include "core/math/color4.inl"
  10. #include "core/math/constants.h"
  11. #include "core/math/matrix4x4.inl"
  12. #include "core/memory/temp_allocator.inl"
  13. #include "core/profiler.h"
  14. #include "core/strings/dynamic_string.inl"
  15. #include "core/strings/string_id.inl"
  16. #include "device/console_server.h"
  17. #include "device/device.h"
  18. #include "device/log.h"
  19. #include "world/debug_line.h"
  20. #include <errno.h>
  21. #include <stb_sprintf.h>
  22. LOG_SYSTEM(GRAPH, "graph")
  23. namespace crown
  24. {
  25. static ListNode _graphs = LIST_INIT_HEAD(_graphs);
  26. static const Vector3 segments[8][2] =
  27. {
  28. #define SEGMENTS_ASPECT_RATIO (1.0f/1.618f)
  29. //
  30. // E F F A
  31. // E A
  32. // E A
  33. // E G G A
  34. // D B
  35. // D B
  36. // D C C B
  37. //
  38. { { 1.0f*SEGMENTS_ASPECT_RATIO, 1.0f, 0.0f }, { 1.0f*SEGMENTS_ASPECT_RATIO, 0.5f, 0.0f } }, // A
  39. { { 1.0f*SEGMENTS_ASPECT_RATIO, 0.5f, 0.0f }, { 1.0f*SEGMENTS_ASPECT_RATIO, 0.0f, 0.0f } }, // B
  40. { { 0.0f*SEGMENTS_ASPECT_RATIO, 0.0f, 0.0f }, { 1.0f*SEGMENTS_ASPECT_RATIO, 0.0f, 0.0f } }, // C
  41. { { 0.0f*SEGMENTS_ASPECT_RATIO, 0.0f, 0.0f }, { 0.0f*SEGMENTS_ASPECT_RATIO, 0.5f, 0.0f } }, // D
  42. { { 0.0f*SEGMENTS_ASPECT_RATIO, 0.5f, 0.0f }, { 0.0f*SEGMENTS_ASPECT_RATIO, 1.0f, 0.0f } }, // E
  43. { { 0.0f*SEGMENTS_ASPECT_RATIO, 1.0f, 0.0f }, { 1.0f*SEGMENTS_ASPECT_RATIO, 1.0f, 0.0f } }, // F
  44. { { 0.0f*SEGMENTS_ASPECT_RATIO, 0.5f, 0.0f }, { 1.0f*SEGMENTS_ASPECT_RATIO, 0.5f, 0.0f } }, // G
  45. { { 0.0f*SEGMENTS_ASPECT_RATIO, 0.0f, 0.0f }, { -0.1f*SEGMENTS_ASPECT_RATIO, -0.4f, 0.0f } }, // H
  46. };
  47. static void draw_string(DebugLine &dl, const char *str, float x, float y)
  48. {
  49. u8 table[256] = {0};
  50. // HGFEDCBA
  51. table[' '] = 0b00000000;
  52. table['0'] = 0b00111111;
  53. table['1'] = 0b00000011;
  54. table['2'] = 0b01101101;
  55. table['3'] = 0b01100111;
  56. table['4'] = 0b01010011;
  57. table['5'] = 0b01110110;
  58. table['6'] = 0b01111110;
  59. table['7'] = 0b00100011;
  60. table['8'] = 0b01111111;
  61. table['9'] = 0b01110111;
  62. table['.'] = 0b10000000;
  63. table['-'] = 0b01000000;
  64. table['a'] = 0b01111011;
  65. table['b'] = 0b01011110;
  66. table['c'] = 0b00111100;
  67. table['d'] = 0b01001111;
  68. table['e'] = 0b01111100;
  69. table['f'] = 0b01111000;
  70. table['A'] = table['a'];
  71. table['B'] = table['b'];
  72. table['C'] = table['c'];
  73. table['D'] = table['d'];
  74. table['E'] = table['e'];
  75. table['F'] = table['f'];
  76. Vector3 advance;
  77. advance.x = x;
  78. advance.y = y;
  79. advance.z = 0.0f;
  80. for (const char *ch = str; *ch; ++ch) {
  81. for (int i = 0; i < 8; ++i) {
  82. if ((table[(int)*ch] & (1 << i)) != 0)
  83. dl.add_line((32.0f * segments[i][0]) + advance, (32.0f * segments[i][1]) + advance, COLOR4_YELLOW);
  84. }
  85. float x_adv = 32.0f*SEGMENTS_ASPECT_RATIO;
  86. advance.x += *ch != '.' ? x_adv*1.2f : x_adv*0.2f;
  87. }
  88. }
  89. struct ColorInfo
  90. {
  91. StringId32 name;
  92. Color4 color;
  93. };
  94. static const ColorInfo s_colors[] =
  95. {
  96. { STRING_ID_32("red", UINT32_C(0x493d87b1)), COLOR4_RED },
  97. { STRING_ID_32("green", UINT32_C(0xaba60fc3)), COLOR4_GREEN },
  98. { STRING_ID_32("blue", UINT32_C(0x399a4f18)), COLOR4_BLUE },
  99. { STRING_ID_32("yellow", UINT32_C(0x4ec858fe)), COLOR4_YELLOW }
  100. };
  101. static u32 name_to_color_index(StringId32 name)
  102. {
  103. for (u32 i = 0; i < countof(s_colors); ++i) {
  104. if (s_colors[i].name == name)
  105. return i;
  106. }
  107. return UINT32_MAX;
  108. }
  109. /// Plots graphs for debugging purposes.
  110. ///
  111. /// @ingroup Device
  112. struct Graph
  113. {
  114. #define GRAPH_MAX_AXES 3
  115. struct ChannelData
  116. {
  117. enum { MAX_SAMPLES = 1024 };
  118. ProfilerEventType::Enum type;
  119. StringId32 field;
  120. u32 size;
  121. u32 head;
  122. Vector3 *samples;
  123. Color4 color;
  124. };
  125. Allocator *_allocator;
  126. f32 _range_min;
  127. f32 _range_max;
  128. u32 _num_samples;
  129. DynamicString _name;
  130. ListNode _node;
  131. bool _visible : 1;
  132. bool _range_auto : 1;
  133. bool _range_dirty : 1; // Whether the range needs to be recomputed.
  134. enum Layout { FILL, LEFT, RIGHT, BOTTOM, TOP } _layout;
  135. Array<ChannelData> _channels;
  136. Graph(Allocator &a)
  137. : _allocator(&a)
  138. , _range_min(0.0f)
  139. , _range_max(0.0f)
  140. , _num_samples(ChannelData::MAX_SAMPLES)
  141. , _name(a)
  142. , _visible(true)
  143. , _range_auto(true)
  144. , _range_dirty(true)
  145. , _layout(FILL)
  146. , _channels(a)
  147. {
  148. _node.next = NULL;
  149. _node.prev = NULL;
  150. }
  151. void set_range(f32 range_min, f32 range_max, bool range_auto)
  152. {
  153. if (range_auto) {
  154. _range_dirty = _range_auto != range_auto;
  155. } else {
  156. _range_min = range_min;
  157. _range_max = range_max;
  158. }
  159. _range_auto = range_auto;
  160. }
  161. void set_samples(u32 num_samples)
  162. {
  163. _num_samples = min(u32(ChannelData::MAX_SAMPLES), num_samples);
  164. }
  165. void sample_with_filter(const char *cur, const char *end)
  166. {
  167. while (cur != end) {
  168. const u32 type = *(u32 *)cur;
  169. cur += sizeof(u32);
  170. if (type == ProfilerEventType::COUNT)
  171. break;
  172. const u32 size = *(u32 *)cur;
  173. cur += sizeof(u32);
  174. switch (type) {
  175. case ProfilerEventType::RECORD_FLOAT: {
  176. RecordFloat *rf = (RecordFloat *)cur;
  177. for (u32 i = 0; i < array::size(_channels); ++i) {
  178. if (_channels[i].field == StringId32(rf->name)) {
  179. sample(i, rf->value);
  180. break;
  181. }
  182. }
  183. }
  184. cur += size;
  185. break;
  186. case ProfilerEventType::RECORD_VECTOR3: {
  187. RecordVector3 *rv = (RecordVector3 *)cur;
  188. for (u32 i = 0; i < array::size(_channels); ++i) {
  189. if (_channels[i].field == StringId32(rv->name)) {
  190. sample(i, rv->value);
  191. break;
  192. }
  193. }
  194. }
  195. cur += size;
  196. break;
  197. case ProfilerEventType::ENTER_PROFILE_SCOPE:
  198. case ProfilerEventType::LEAVE_PROFILE_SCOPE:
  199. case ProfilerEventType::ALLOCATE_MEMORY:
  200. case ProfilerEventType::DEALLOCATE_MEMORY:
  201. cur += size;
  202. break;
  203. default:
  204. CE_FATAL("Unknown profiler event type");
  205. break;
  206. }
  207. }
  208. }
  209. void add(StringId32 field)
  210. {
  211. ChannelData cd;
  212. cd.field = field;
  213. cd.size = 0;
  214. cd.head = 0;
  215. cd.samples = (Vector3 *)_allocator->allocate(sizeof(Vector3) * ChannelData::MAX_SAMPLES);
  216. cd.color = COLOR4_YELLOW;
  217. array::push_back(_channels, cd);
  218. }
  219. void add(const char *field)
  220. {
  221. add(StringId32(field));
  222. }
  223. void remove(StringId32 field)
  224. {
  225. for (u32 i = 0; i < array::size(_channels); ++i) {
  226. if (_channels[i].field == field) {
  227. _allocator->deallocate(_channels[i].samples);
  228. u32 last_index = array::size(_channels) - 1;
  229. if (i != last_index)
  230. _channels[i] = _channels[last_index];
  231. array::pop_back(_channels);
  232. return;
  233. }
  234. }
  235. }
  236. void remove(const char *field)
  237. {
  238. remove(StringId32(field));
  239. }
  240. void sample(u32 samples_index, f32 value)
  241. {
  242. ChannelData &cd = _channels[samples_index];
  243. cd.type = ProfilerEventType::RECORD_FLOAT;
  244. cd.samples[cd.head] = { value, 0.0f, 0.0f };
  245. cd.head = (cd.head + 1) % ChannelData::MAX_SAMPLES;
  246. cd.size = min(cd.size + 1, (u32)ChannelData::MAX_SAMPLES);
  247. if (_range_auto) {
  248. if (CE_UNLIKELY(_range_dirty)) {
  249. for (u32 ii = 0; ii < array::size(_channels); ++ii) {
  250. ChannelData &cd = _channels[ii];
  251. for (u32 jj = 0; jj < cd.size; ++jj) {
  252. const f32 val = cd.samples[(cd.head - 1 - jj) % ChannelData::MAX_SAMPLES].x;
  253. if (jj == 0) {
  254. _range_min = val;
  255. _range_max = val;
  256. } else {
  257. _range_min = min(_range_min, val);
  258. _range_max = max(_range_max, val);
  259. }
  260. }
  261. }
  262. _range_dirty = false;
  263. } else {
  264. _range_min = min(_range_min, value);
  265. _range_max = max(_range_max, value);
  266. }
  267. }
  268. }
  269. void sample(u32 samples_index, const Vector3 &value)
  270. {
  271. ChannelData &cd = _channels[samples_index];
  272. cd.type = ProfilerEventType::RECORD_VECTOR3;
  273. cd.samples[cd.head] = value;
  274. cd.head = (cd.head + 1) % ChannelData::MAX_SAMPLES;
  275. cd.size = min(cd.size + 1, (u32)ChannelData::MAX_SAMPLES);
  276. if (_range_auto) {
  277. if (CE_UNLIKELY(_range_dirty)) {
  278. for (u32 ii = 0; ii < array::size(_channels); ++ii) {
  279. ChannelData &cd = _channels[ii];
  280. for (u32 jj = 0; jj < cd.size; ++jj) {
  281. const Vector3 val = cd.samples[(cd.head - 1 - jj) % ChannelData::MAX_SAMPLES];
  282. if (jj == 0) {
  283. _range_min = min(val.x, val.y, val.z);
  284. _range_max = max(val.x, val.y, val.z);
  285. } else {
  286. _range_min = min(_range_min, min(value.x, value.y, value.z));
  287. _range_max = max(_range_max, max(value.x, value.y, value.z));
  288. }
  289. }
  290. }
  291. _range_dirty = false;
  292. } else {
  293. _range_min = min(_range_min, min(value.x, value.y, value.z));
  294. _range_max = max(_range_max, max(value.x, value.y, value.z));
  295. }
  296. }
  297. }
  298. void draw(DebugLine &dl, u16 window_width, u16 window_height)
  299. {
  300. if (!_visible)
  301. return;
  302. sample_with_filter(profiler_globals::buffer_begin(), profiler_globals::buffer_end());
  303. auto remap = [](f32 x, f32 in_min, f32 in_max, f32 out_min, f32 out_max) -> f32 {
  304. return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
  305. };
  306. const f32 margin_padding = 32.0; // Pixels of padding inside window margins.
  307. const f32 window_width_padded = f32(window_width) - margin_padding;
  308. const f32 window_height_padded = f32(window_height) - margin_padding;
  309. // Margins in window-coordinates.
  310. const f32 margin_right = window_width_padded / 2.0f;
  311. const f32 margin_left = -margin_right;
  312. const f32 margin_top = window_height_padded / 2.0f;
  313. const f32 margin_bottom = -margin_top;
  314. f32 x_start;
  315. f32 x_step;
  316. f32 y_min;
  317. f32 y_max;
  318. switch (_layout) {
  319. case LEFT:
  320. x_start = margin_left;
  321. x_step = f32(margin_right - margin_left) / f32(_num_samples - 1) / 2.0f;
  322. y_min = margin_bottom;
  323. y_max = margin_top;
  324. break;
  325. case RIGHT:
  326. x_start = 0.0f;
  327. x_step = f32(margin_right - margin_left) / f32(_num_samples - 1) / 2.0f;
  328. y_min = margin_bottom;
  329. y_max = margin_top;
  330. break;
  331. case BOTTOM:
  332. x_start = margin_left;
  333. x_step = f32(margin_right - margin_left) / f32(_num_samples - 1);
  334. y_min = margin_bottom;
  335. y_max = 0.0f;
  336. break;
  337. case TOP:
  338. x_start = margin_left;
  339. x_step = f32(margin_right - margin_left) / f32(_num_samples - 1);
  340. y_min = 0.0f;
  341. y_max = margin_top;
  342. break;
  343. case FILL:
  344. default:
  345. x_start = margin_left;
  346. x_step = f32(margin_right - margin_left) / f32(_num_samples - 1);
  347. y_min = margin_bottom;
  348. y_max = margin_top;
  349. break;
  350. }
  351. f32 x_end = x_start + x_step*(_num_samples - 1);
  352. // Draw margin top.
  353. dl.add_line({ x_start, y_max, 0.0f }
  354. , { x_end, y_max, 0.0f }
  355. , COLOR4_ORANGE
  356. );
  357. // Draw margin right.
  358. dl.add_line({ x_end, y_max, 0.0f }
  359. , { x_end, y_min, 0.0f }
  360. , COLOR4_ORANGE
  361. );
  362. // Draw margin bottom.
  363. dl.add_line({ x_end, y_min, 0.0f }
  364. , { x_start, y_min, 0.0f }
  365. , COLOR4_ORANGE
  366. );
  367. // Draw margin left.
  368. dl.add_line({ x_start, y_min, 0.0f }
  369. , { x_start, y_max, 0.0f }
  370. , COLOR4_ORANGE
  371. );
  372. // For each channel.
  373. for (u32 cc = 0; cc < array::size(_channels); ++cc) {
  374. ChannelData &cd = _channels[cc];
  375. u32 cur_sample = (cd.head - 1 - _num_samples) % ChannelData::MAX_SAMPLES;
  376. // For each sample.
  377. for (u32 ii = 0; ii < (_num_samples - 1); ++ii) {
  378. // Do not draw invalid samples.
  379. if (cd.size >= _num_samples || ii >= _num_samples - cd.size) {
  380. const u32 num_axis = cd.type == ProfilerEventType::RECORD_FLOAT ? 1 : 3;
  381. for (u32 axis = 0; axis < num_axis; ++axis) {
  382. f32 *a_data = to_float_ptr(cd.samples[(cur_sample + 0) % ChannelData::MAX_SAMPLES]);
  383. f32 *b_data = to_float_ptr(cd.samples[(cur_sample + 1) % ChannelData::MAX_SAMPLES]);
  384. Vector3 a;
  385. Vector3 b;
  386. a.x = x_start + (ii + 0)*x_step;
  387. a.y = remap(a_data[axis], _range_min, _range_max, y_min, y_max);
  388. a.z = 0.0f;
  389. b.x = x_start + (ii + 1)*x_step;
  390. b.y = remap(b_data[axis], _range_min, _range_max, y_min, y_max);
  391. b.z = 0.0f;
  392. dl.add_line(a, b, num_axis == 1 ? cd.color : s_colors[axis].color);
  393. }
  394. }
  395. cur_sample = (cur_sample + 1) % ChannelData::MAX_SAMPLES;
  396. }
  397. }
  398. // Draw labels.
  399. #define TEXT_PADDING 6.0f
  400. float str_x = x_start + TEXT_PADDING;
  401. char buf[64] = {};
  402. stbsp_snprintf(buf, sizeof(buf), "%g", _range_min);
  403. draw_string(dl, buf, str_x, y_min + TEXT_PADDING);
  404. stbsp_snprintf(buf, sizeof(buf), "%g", _range_max);
  405. draw_string(dl, buf, str_x, y_max - TEXT_PADDING - 32.0f);
  406. dl.submit(View::GRAPH);
  407. }
  408. };
  409. namespace graph
  410. {
  411. /// Creates a new graph with the given @a name and appends it
  412. /// to the list @a head.
  413. Graph *create(ListNode &head, const char *name)
  414. {
  415. Graph *graph = CE_NEW(default_allocator(), Graph)(default_allocator());
  416. graph->_name = name;
  417. list::add(graph->_node, head);
  418. return graph;
  419. }
  420. /// Destroys the given @a graph.
  421. void destroy(Graph *graph)
  422. {
  423. list::remove(graph->_node);
  424. CE_DELETE(default_allocator(), graph);
  425. }
  426. /// Returns the graph with the given @a name or NULL if no graph is found.
  427. Graph *find(ListNode &head, const char *name)
  428. {
  429. ListNode *cur;
  430. list_for_each(cur, &head)
  431. {
  432. Graph *graph = (Graph *)container_of(cur, Graph, _node);
  433. if (graph->_name == name)
  434. return graph;
  435. }
  436. return NULL;
  437. }
  438. } // namespace graph
  439. namespace graph_internal
  440. {
  441. void handle_command(ConsoleServer &cs, u32 client_id, const JsonArray &args, void * /*user_data*/)
  442. {
  443. TempAllocator1024 ta;
  444. if (array::size(args) < 2) {
  445. cs.error(client_id, "Usage: graph make <name>");
  446. return;
  447. }
  448. DynamicString subcmd(ta);
  449. sjson::parse_string(subcmd, args[1]);
  450. if (subcmd == "help") {
  451. logi(GRAPH, "make Create a new graph.");
  452. logi(GRAPH, "list List graphs.");
  453. logi(GRAPH, "range Set the range of a graph.");
  454. logi(GRAPH, "add Add a field to a graph.");
  455. logi(GRAPH, "remove Remove a field from a graph.");
  456. logi(GRAPH, "hide Hide a graph.");
  457. logi(GRAPH, "show Show a graph.");
  458. logi(GRAPH, "layout Set the layout of a graph.");
  459. logi(GRAPH, "color Set the color of a field in a graph.");
  460. logi(GRAPH, "samples Set the number of samples to show in a graph.");
  461. } else if (subcmd == "make") {
  462. if (array::size(args) != 3) {
  463. cs.error(client_id, "Usage: graph make <name>");
  464. return;
  465. }
  466. DynamicString name(ta);
  467. sjson::parse_string(name, args[2]);
  468. Graph *graph = graph::find(_graphs, name.c_str());
  469. if (graph != NULL) {
  470. cs.error(client_id, "Graph with this name already exists");
  471. return;
  472. }
  473. graph::create(_graphs, name.c_str());
  474. } else if (subcmd == "list") {
  475. ListNode *cur;
  476. list_for_each(cur, &_graphs)
  477. {
  478. Graph *graph = (Graph *)container_of(cur, Graph, _node);
  479. logi(GRAPH, "%s", graph->_name.c_str());
  480. }
  481. } else if (subcmd == "range") {
  482. if (array::size(args) != 3 && array::size(args) != 5) {
  483. cs.error(client_id, "Usage: graph range <graph> [min max]");
  484. return;
  485. }
  486. DynamicString name(ta);
  487. DynamicString min(ta);
  488. DynamicString max(ta);
  489. sjson::parse_string(name, args[2]);
  490. if (array::size(args) == 5) {
  491. sjson::parse_string(min, args[3]);
  492. sjson::parse_string(max, args[4]);
  493. } else {
  494. min = "0";
  495. max = "0";
  496. }
  497. Graph *graph = graph::find(_graphs, name.c_str());
  498. if (graph == NULL) {
  499. cs.error(client_id, "Graph not found");
  500. return;
  501. }
  502. graph->set_range(sjson::parse_float(min.c_str()), sjson::parse_float(max.c_str()), array::size(args) == 3);
  503. } else if (subcmd == "add") {
  504. if (array::size(args) != 4) {
  505. cs.error(client_id, "Usage: graph add <graph> <field>");
  506. return;
  507. }
  508. DynamicString name(ta);
  509. DynamicString field(ta);
  510. sjson::parse_string(name, args[2]);
  511. sjson::parse_string(field, args[3]);
  512. Graph *graph = graph::find(_graphs, name.c_str());
  513. if (graph == NULL) {
  514. cs.error(client_id, "Graph not found");
  515. return;
  516. }
  517. graph->add(field.c_str());
  518. } else if (subcmd == "remove") {
  519. if (array::size(args) != 4) {
  520. cs.error(client_id, "Usage: graph remove <graph> <field>");
  521. return;
  522. }
  523. DynamicString name(ta);
  524. DynamicString field(ta);
  525. sjson::parse_string(name, args[2]);
  526. sjson::parse_string(field, args[3]);
  527. Graph *graph = graph::find(_graphs, name.c_str());
  528. if (graph == NULL) {
  529. cs.error(client_id, "Graph not found");
  530. return;
  531. }
  532. graph->remove(field.c_str());
  533. } else if (subcmd == "hide") {
  534. if (array::size(args) != 3) {
  535. cs.error(client_id, "Usage: graph hide <graph>");
  536. return;
  537. }
  538. DynamicString name(ta);
  539. sjson::parse_string(name, args[2]);
  540. Graph *graph = graph::find(_graphs, name.c_str());
  541. if (graph == NULL) {
  542. cs.error(client_id, "Graph not found");
  543. return;
  544. }
  545. graph->_visible = false;
  546. } else if (subcmd == "show") {
  547. if (array::size(args) != 3) {
  548. cs.error(client_id, "Usage: graph show <graph>");
  549. return;
  550. }
  551. DynamicString name(ta);
  552. sjson::parse_string(name, args[2]);
  553. Graph *graph = graph::find(_graphs, name.c_str());
  554. if (graph == NULL) {
  555. cs.error(client_id, "Graph not found");
  556. return;
  557. }
  558. graph->_visible = true;
  559. } else if (subcmd == "layout") {
  560. if (array::size(args) != 4) {
  561. cs.error(client_id, "Usage: graph layout <graph> <type>");
  562. return;
  563. }
  564. DynamicString name(ta);
  565. DynamicString type(ta);
  566. sjson::parse_string(name, args[2]);
  567. sjson::parse_string(type, args[3]);
  568. s32 lt = Graph::FILL;
  569. if (type == "fill") {
  570. lt = Graph::FILL;
  571. } else if (type == "left") {
  572. lt = Graph::LEFT;
  573. } else if (type == "right") {
  574. lt = Graph::RIGHT;
  575. } else if (type == "bottom") {
  576. lt = Graph::BOTTOM;
  577. } else if (type == "top") {
  578. lt = Graph::TOP;
  579. } else {
  580. cs.error(client_id, "Invalid layout type");
  581. return;
  582. }
  583. Graph *graph = graph::find(_graphs, name.c_str());
  584. if (graph == NULL) {
  585. cs.error(client_id, "Graph not found");
  586. return;
  587. }
  588. graph->_layout = (Graph::Layout)lt;
  589. } else if (subcmd == "color") {
  590. if (array::size(args) != 5) {
  591. cs.error(client_id, "Usage: graph color <graph> <field> <color>");
  592. return;
  593. }
  594. DynamicString name(ta);
  595. DynamicString color_name(ta);
  596. StringId32 field = sjson::parse_string_id(args[3]);
  597. sjson::parse_string(name, args[2]);
  598. sjson::parse_string(color_name, args[4]);
  599. Color4 color;
  600. u32 color_index = name_to_color_index(color_name.to_string_id());
  601. if (color_index == UINT32_MAX) {
  602. if (color_name.length() != 3 && color_name.length() != 6) {
  603. cs.error(client_id, "Invalid color");
  604. return;
  605. } else {
  606. // Decode hex color.
  607. errno = 0;
  608. u32 val = strtol(color_name.c_str(), NULL, 16);
  609. if (errno != ERANGE && errno != EINVAL) {
  610. if (color_name.length() == 3) {
  611. u8 r = ((val & 0xf00) >> 8) | ((val & 0xf00) >> 4);
  612. u8 g = ((val & 0x0f0) >> 4) | ((val & 0x0f0) >> 0);
  613. u8 b = ((val & 0x00f) >> 0) | ((val & 0x00f) << 4);
  614. color = from_rgb(r, g, b);
  615. } else {
  616. color = from_rgb(val);
  617. }
  618. }
  619. }
  620. } else {
  621. color = s_colors[color_index].color;
  622. }
  623. Graph *graph = graph::find(_graphs, name.c_str());
  624. if (graph == NULL) {
  625. cs.error(client_id, "Graph not found");
  626. return;
  627. }
  628. for (u32 i = 0; i < array::size(graph->_channels); ++i) {
  629. if (graph->_channels[i].field == field) {
  630. graph->_channels[i].color = color;
  631. break;
  632. }
  633. }
  634. } else if (subcmd == "samples") {
  635. if (array::size(args) != 4) {
  636. cs.error(client_id, "Usage: graph samples <graph> <samples>");
  637. return;
  638. }
  639. DynamicString name(ta);
  640. DynamicString samples(ta);
  641. sjson::parse_string(name, args[2]);
  642. sjson::parse_string(samples, args[3]);
  643. Graph *graph = graph::find(_graphs, name.c_str());
  644. if (graph == NULL) {
  645. cs.error(client_id, "Graph not found");
  646. return;
  647. }
  648. graph->set_samples((u32)sjson::parse_int(samples.c_str()));
  649. } else {
  650. cs.error(client_id, "Unknown graph parameter");
  651. }
  652. }
  653. } // namespace graph_internal
  654. } // namespace crown
  655. #endif // CROWN_DEBUG
  656. namespace crown
  657. {
  658. namespace graph_globals
  659. {
  660. Allocator *_allocator = NULL;
  661. DebugLine *_lines = NULL;
  662. void init(Allocator &a, Pipeline &pl, ConsoleServer &cs)
  663. {
  664. #if CROWN_DEBUG
  665. _allocator = &a;
  666. _lines = debug_line::create(a, pl, false);
  667. cs.register_command_name("graph", "Plot selected profiler data.", graph_internal::handle_command, NULL);
  668. #else
  669. CE_UNUSED_3(a, pl, cs);
  670. CE_NOOP();
  671. #endif
  672. }
  673. void shutdown()
  674. {
  675. #if CROWN_DEBUG
  676. // Destroy all graphs
  677. ListNode *cur;
  678. ListNode *tmp;
  679. list_for_each_safe(cur, tmp, &_graphs)
  680. {
  681. Graph *graph = (Graph *)container_of(cur, Graph, _node);
  682. CE_DELETE(default_allocator(), graph);
  683. }
  684. CE_DELETE(*_allocator, _lines);
  685. _lines = NULL;
  686. _allocator = NULL;
  687. #else
  688. CE_NOOP();
  689. #endif
  690. }
  691. void draw_all(u16 window_width, u16 window_height)
  692. {
  693. #if CROWN_DEBUG
  694. _lines->reset();
  695. ListNode *cur;
  696. list_for_each(cur, &_graphs)
  697. {
  698. Graph *graph = (Graph *)container_of(cur, Graph, _node);
  699. graph->draw(*_lines, window_width, window_height);
  700. }
  701. #else
  702. CE_UNUSED_2(window_width, window_height);
  703. CE_NOOP();
  704. #endif
  705. }
  706. } // namespace graph_globals
  707. } // namespace crown