graph.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756
  1. /*
  2. * Copyright (c) 2012-2024 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/strings/dynamic_string.inl"
  14. #include "core/strings/string_id.inl"
  15. #include "device/console_server.h"
  16. #include "device/device.h"
  17. #include "device/log.h"
  18. #include "device/profiler.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 head;
  121. Vector3 *samples;
  122. Color4 color;
  123. };
  124. Allocator *_allocator;
  125. f32 _range_min;
  126. f32 _range_max;
  127. u32 _num_samples;
  128. DynamicString _name;
  129. ListNode _node;
  130. bool _visible;
  131. bool _range_auto;
  132. enum Layout { FILL, LEFT, RIGHT, BOTTOM, TOP } _layout;
  133. Array<ChannelData> _channels;
  134. Graph(Allocator &a)
  135. : _allocator(&a)
  136. , _range_min(0.0f)
  137. , _range_max(0.0f)
  138. , _num_samples(ChannelData::MAX_SAMPLES)
  139. , _name(a)
  140. , _visible(true)
  141. , _range_auto(true)
  142. , _layout(FILL)
  143. , _channels(a)
  144. {
  145. _node.next = NULL;
  146. _node.prev = NULL;
  147. }
  148. void set_range(f32 range_min, f32 range_max, bool range_auto)
  149. {
  150. if (range_auto) {
  151. _range_min = 0.0f;
  152. _range_max = 0.0f;
  153. } else {
  154. _range_min = range_min;
  155. _range_max = range_max;
  156. }
  157. _range_auto = range_auto;
  158. }
  159. void set_samples(u32 num_samples)
  160. {
  161. _num_samples = min(u32(ChannelData::MAX_SAMPLES), num_samples);
  162. }
  163. void sample_with_filter(const char *cur, const char *end)
  164. {
  165. while (cur != end) {
  166. const u32 type = *(u32 *)cur;
  167. cur += sizeof(u32);
  168. if (type == ProfilerEventType::COUNT)
  169. break;
  170. const u32 size = *(u32 *)cur;
  171. cur += sizeof(u32);
  172. switch (type) {
  173. case ProfilerEventType::RECORD_FLOAT: {
  174. RecordFloat *rf = (RecordFloat *)cur;
  175. for (u32 i = 0; i < array::size(_channels); ++i) {
  176. if (_channels[i].field == StringId32(rf->name)) {
  177. sample(i, rf->value);
  178. break;
  179. }
  180. }
  181. }
  182. cur += size;
  183. break;
  184. case ProfilerEventType::RECORD_VECTOR3: {
  185. RecordVector3 *rv = (RecordVector3 *)cur;
  186. for (u32 i = 0; i < array::size(_channels); ++i) {
  187. if (_channels[i].field == StringId32(rv->name)) {
  188. sample(i, rv->value);
  189. break;
  190. }
  191. }
  192. }
  193. cur += size;
  194. break;
  195. case ProfilerEventType::ENTER_PROFILE_SCOPE:
  196. case ProfilerEventType::LEAVE_PROFILE_SCOPE:
  197. case ProfilerEventType::ALLOCATE_MEMORY:
  198. case ProfilerEventType::DEALLOCATE_MEMORY:
  199. cur += size;
  200. break;
  201. default:
  202. CE_FATAL("Unknown profiler event type");
  203. break;
  204. }
  205. }
  206. }
  207. void add(StringId32 field)
  208. {
  209. ChannelData cd;
  210. cd.field = field;
  211. cd.head = 0;
  212. cd.samples = (Vector3 *)_allocator->allocate(sizeof(Vector3) * ChannelData::MAX_SAMPLES);
  213. cd.color = COLOR4_YELLOW;
  214. array::push_back(_channels, cd);
  215. }
  216. void add(const char *field)
  217. {
  218. add(StringId32(field));
  219. }
  220. void remove(StringId32 field)
  221. {
  222. for (u32 i = 0; i < array::size(_channels); ++i) {
  223. if (_channels[i].field == field) {
  224. _allocator->deallocate(_channels[i].samples);
  225. u32 last_index = array::size(_channels) - 1;
  226. if (i != last_index)
  227. _channels[i] = _channels[last_index];
  228. array::pop_back(_channels);
  229. return;
  230. }
  231. }
  232. }
  233. void remove(const char *field)
  234. {
  235. remove(StringId32(field));
  236. }
  237. void sample(u32 samples_index, f32 value)
  238. {
  239. if (_range_auto) {
  240. if (_range_min > value)
  241. _range_min = value;
  242. if (_range_max < value)
  243. _range_max = value;
  244. }
  245. ChannelData &cd = _channels[samples_index];
  246. cd.type = ProfilerEventType::RECORD_FLOAT;
  247. cd.samples[cd.head] = vector3(value, 0.0f, 0.0f);
  248. cd.head = (cd.head + 1) % ChannelData::MAX_SAMPLES;
  249. }
  250. void sample(u32 samples_index, const Vector3 &value)
  251. {
  252. if (_range_auto) {
  253. if (_range_min > value.x)
  254. _range_min = value.x;
  255. if (_range_min > value.y)
  256. _range_min = value.y;
  257. if (_range_min > value.z)
  258. _range_min = value.z;
  259. if (_range_max < value.x)
  260. _range_max = value.x;
  261. if (_range_max < value.y)
  262. _range_max = value.y;
  263. if (_range_max < value.z)
  264. _range_max = value.z;
  265. }
  266. ChannelData &cd = _channels[samples_index];
  267. cd.type = ProfilerEventType::RECORD_VECTOR3;
  268. cd.samples[cd.head] = value;
  269. cd.head = (cd.head + 1) % ChannelData::MAX_SAMPLES;
  270. }
  271. void draw(DebugLine &dl, u16 window_width, u16 window_height)
  272. {
  273. if (!_visible)
  274. return;
  275. sample_with_filter(profiler_globals::buffer_begin(), profiler_globals::buffer_end());
  276. auto remap = [](f32 x, f32 in_min, f32 in_max, f32 out_min, f32 out_max) -> f32 {
  277. return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
  278. };
  279. const f32 margin_padding = 32.0; // Pixels of padding inside window margins.
  280. const f32 window_width_padded = f32(window_width) - margin_padding;
  281. const f32 window_height_padded = f32(window_height) - margin_padding;
  282. // Margins in window-coordinates.
  283. const f32 margin_right = window_width_padded / 2.0f;
  284. const f32 margin_left = -margin_right;
  285. const f32 margin_top = window_height_padded / 2.0f;
  286. const f32 margin_bottom = -margin_top;
  287. f32 x_start;
  288. f32 x_step;
  289. f32 y_min;
  290. f32 y_max;
  291. switch (_layout) {
  292. case LEFT:
  293. x_start = margin_left;
  294. x_step = f32(margin_right - margin_left) / f32(_num_samples - 1) / 2.0f;
  295. y_min = margin_bottom;
  296. y_max = margin_top;
  297. break;
  298. case RIGHT:
  299. x_start = 0.0f;
  300. x_step = f32(margin_right - margin_left) / f32(_num_samples - 1) / 2.0f;
  301. y_min = margin_bottom;
  302. y_max = margin_top;
  303. break;
  304. case BOTTOM:
  305. x_start = margin_left;
  306. x_step = f32(margin_right - margin_left) / f32(_num_samples - 1);
  307. y_min = margin_bottom;
  308. y_max = 0.0f;
  309. break;
  310. case TOP:
  311. x_start = margin_left;
  312. x_step = f32(margin_right - margin_left) / f32(_num_samples - 1);
  313. y_min = 0.0f;
  314. y_max = margin_top;
  315. break;
  316. case FILL:
  317. default:
  318. x_start = margin_left;
  319. x_step = f32(margin_right - margin_left) / f32(_num_samples - 1);
  320. y_min = margin_bottom;
  321. y_max = margin_top;
  322. break;
  323. }
  324. f32 x_end = x_start + x_step*(_num_samples - 1);
  325. // Draw margin top.
  326. dl.add_line(vector3(x_start, y_max, 0.0f)
  327. , vector3(x_end, y_max, 0.0f)
  328. , COLOR4_ORANGE
  329. );
  330. // Draw margin right.
  331. dl.add_line(vector3(x_end, y_max, 0.0f)
  332. , vector3(x_end, y_min, 0.0f)
  333. , COLOR4_ORANGE
  334. );
  335. // Draw margin bottom.
  336. dl.add_line(vector3(x_end, y_min, 0.0f)
  337. , vector3(x_start, y_min, 0.0f)
  338. , COLOR4_ORANGE
  339. );
  340. // Draw margin left.
  341. dl.add_line(vector3(x_start, y_min, 0.0f)
  342. , vector3(x_start, y_max, 0.0f)
  343. , COLOR4_ORANGE
  344. );
  345. // For each channel.
  346. for (u32 cc = 0; cc < array::size(_channels); ++cc) {
  347. ChannelData &cd = _channels[cc];
  348. u32 cur_sample = (cd.head - 1 - _num_samples) % ChannelData::MAX_SAMPLES;
  349. // For each sample.
  350. for (u32 ii = 0; ii < _num_samples - 1; ++ii) {
  351. const u32 num_axis = cd.type == ProfilerEventType::RECORD_FLOAT ? 1 : 3;
  352. for (u32 axis = 0; axis < num_axis; ++axis) {
  353. f32 *a_data = to_float_ptr(cd.samples[(cur_sample + 0) % ChannelData::MAX_SAMPLES]);
  354. f32 *b_data = to_float_ptr(cd.samples[(cur_sample + 1) % ChannelData::MAX_SAMPLES]);
  355. Vector3 a;
  356. Vector3 b;
  357. a.x = x_start + (ii + 0)*x_step;
  358. a.y = remap(a_data[axis], _range_min, _range_max, y_min, y_max);
  359. a.z = 0.0f;
  360. b.x = x_start + (ii + 1)*x_step;
  361. b.y = remap(b_data[axis], _range_min, _range_max, y_min, y_max);
  362. b.z = 0.0f;
  363. dl.add_line(a, b, num_axis == 1 ? cd.color : s_colors[1 + axis].color);
  364. }
  365. cur_sample = (cur_sample + 1) % ChannelData::MAX_SAMPLES;
  366. }
  367. }
  368. // Draw labels.
  369. #define TEXT_PADDING 6.0f
  370. float str_x = x_start + TEXT_PADDING;
  371. char buf[64] = {};
  372. stbsp_snprintf(buf, sizeof(buf), "%g", _range_min);
  373. draw_string(dl, buf, str_x, y_min + TEXT_PADDING);
  374. stbsp_snprintf(buf, sizeof(buf), "%g", _range_max);
  375. draw_string(dl, buf, str_x, y_max - TEXT_PADDING - 32.0f);
  376. dl.submit(VIEW_GRAPH);
  377. }
  378. };
  379. namespace graph
  380. {
  381. /// Creates a new graph with the given @a name and appends it
  382. /// to the list @a head.
  383. Graph *create(ListNode &head, const char *name)
  384. {
  385. Graph *graph = CE_NEW(default_allocator(), Graph)(default_allocator());
  386. graph->_name = name;
  387. list::add(graph->_node, head);
  388. return graph;
  389. }
  390. /// Destroys the given @a graph.
  391. void destroy(Graph *graph)
  392. {
  393. list::remove(graph->_node);
  394. CE_DELETE(default_allocator(), graph);
  395. }
  396. /// Returns the graph with the given @a name or NULL if no graph is found.
  397. Graph *find(ListNode &head, const char *name)
  398. {
  399. ListNode *cur;
  400. list_for_each(cur, &head)
  401. {
  402. Graph *graph = (Graph *)container_of(cur, Graph, _node);
  403. if (graph->_name == name)
  404. return graph;
  405. }
  406. return NULL;
  407. }
  408. } // namespace graph
  409. namespace graph_internal
  410. {
  411. void handle_command(ConsoleServer &cs, u32 client_id, const JsonArray &args, void * /*user_data*/)
  412. {
  413. TempAllocator1024 ta;
  414. if (array::size(args) < 2) {
  415. cs.error(client_id, "Usage: graph make <name>");
  416. return;
  417. }
  418. DynamicString subcmd(ta);
  419. sjson::parse_string(subcmd, args[1]);
  420. if (subcmd == "help") {
  421. logi(GRAPH, "make Create a new graph.");
  422. logi(GRAPH, "list List graphs.");
  423. logi(GRAPH, "range Set the range of a graph.");
  424. logi(GRAPH, "add Add a field to a graph.");
  425. logi(GRAPH, "remove Remove a field from a graph.");
  426. logi(GRAPH, "hide Hide a graph.");
  427. logi(GRAPH, "show Show a graph.");
  428. logi(GRAPH, "layout Set the layout of a graph.");
  429. logi(GRAPH, "color Set the color of a field in a graph.");
  430. logi(GRAPH, "samples Set the number of samples to show in a graph.");
  431. } else if (subcmd == "make") {
  432. if (array::size(args) != 3) {
  433. cs.error(client_id, "Usage: graph make <name>");
  434. return;
  435. }
  436. DynamicString name(ta);
  437. sjson::parse_string(name, args[2]);
  438. Graph *graph = graph::find(_graphs, name.c_str());
  439. if (graph != NULL) {
  440. cs.error(client_id, "Graph with this name already exists");
  441. return;
  442. }
  443. graph::create(_graphs, name.c_str());
  444. } else if (subcmd == "list") {
  445. ListNode *cur;
  446. list_for_each(cur, &_graphs)
  447. {
  448. Graph *graph = (Graph *)container_of(cur, Graph, _node);
  449. logi(GRAPH, "%s", graph->_name.c_str());
  450. }
  451. } else if (subcmd == "range") {
  452. if (array::size(args) != 3 && array::size(args) != 5) {
  453. cs.error(client_id, "Usage: graph range <graph> [min max]");
  454. return;
  455. }
  456. DynamicString name(ta);
  457. DynamicString min(ta);
  458. DynamicString max(ta);
  459. sjson::parse_string(name, args[2]);
  460. if (array::size(args) == 5) {
  461. sjson::parse_string(min, args[3]);
  462. sjson::parse_string(max, args[4]);
  463. } else {
  464. min = "0";
  465. max = "0";
  466. }
  467. Graph *graph = graph::find(_graphs, name.c_str());
  468. if (graph == NULL) {
  469. cs.error(client_id, "Graph not found");
  470. return;
  471. }
  472. graph->set_range(sjson::parse_float(min.c_str()), sjson::parse_float(max.c_str()), array::size(args) == 3);
  473. } else if (subcmd == "add") {
  474. if (array::size(args) != 4) {
  475. cs.error(client_id, "Usage: graph add <graph> <field>");
  476. return;
  477. }
  478. DynamicString name(ta);
  479. DynamicString field(ta);
  480. sjson::parse_string(name, args[2]);
  481. sjson::parse_string(field, args[3]);
  482. Graph *graph = graph::find(_graphs, name.c_str());
  483. if (graph == NULL) {
  484. cs.error(client_id, "Graph not found");
  485. return;
  486. }
  487. graph->add(field.c_str());
  488. } else if (subcmd == "remove") {
  489. if (array::size(args) != 4) {
  490. cs.error(client_id, "Usage: graph remove <graph> <field>");
  491. return;
  492. }
  493. DynamicString name(ta);
  494. DynamicString field(ta);
  495. sjson::parse_string(name, args[2]);
  496. sjson::parse_string(field, args[3]);
  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->remove(field.c_str());
  503. } else if (subcmd == "hide") {
  504. if (array::size(args) != 3) {
  505. cs.error(client_id, "Usage: graph hide <graph>");
  506. return;
  507. }
  508. DynamicString name(ta);
  509. sjson::parse_string(name, args[2]);
  510. Graph *graph = graph::find(_graphs, name.c_str());
  511. if (graph == NULL) {
  512. cs.error(client_id, "Graph not found");
  513. return;
  514. }
  515. graph->_visible = false;
  516. } else if (subcmd == "show") {
  517. if (array::size(args) != 3) {
  518. cs.error(client_id, "Usage: graph show <graph>");
  519. return;
  520. }
  521. DynamicString name(ta);
  522. sjson::parse_string(name, args[2]);
  523. Graph *graph = graph::find(_graphs, name.c_str());
  524. if (graph == NULL) {
  525. cs.error(client_id, "Graph not found");
  526. return;
  527. }
  528. graph->_visible = true;
  529. } else if (subcmd == "layout") {
  530. if (array::size(args) != 4) {
  531. cs.error(client_id, "Usage: graph layout <graph> <type>");
  532. return;
  533. }
  534. DynamicString name(ta);
  535. DynamicString type(ta);
  536. sjson::parse_string(name, args[2]);
  537. sjson::parse_string(type, args[3]);
  538. s32 lt = Graph::FILL;
  539. if (type == "fill") {
  540. lt = Graph::FILL;
  541. } else if (type == "left") {
  542. lt = Graph::LEFT;
  543. } else if (type == "right") {
  544. lt = Graph::RIGHT;
  545. } else if (type == "bottom") {
  546. lt = Graph::BOTTOM;
  547. } else if (type == "top") {
  548. lt = Graph::TOP;
  549. } else {
  550. cs.error(client_id, "Invalid layout type");
  551. return;
  552. }
  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->_layout = (Graph::Layout)lt;
  559. } else if (subcmd == "color") {
  560. if (array::size(args) != 5) {
  561. cs.error(client_id, "Usage: graph color <graph> <field> <color>");
  562. return;
  563. }
  564. DynamicString name(ta);
  565. DynamicString color_name(ta);
  566. StringId32 field = sjson::parse_string_id(args[3]);
  567. sjson::parse_string(name, args[2]);
  568. sjson::parse_string(color_name, args[4]);
  569. Color4 color;
  570. u32 color_index = name_to_color_index(color_name.to_string_id());
  571. if (color_index == UINT32_MAX) {
  572. if (color_name.length() != 3 && color_name.length() != 6) {
  573. cs.error(client_id, "Invalid color");
  574. return;
  575. } else {
  576. // Decode hex color.
  577. errno = 0;
  578. u32 val = strtol(color_name.c_str(), NULL, 16);
  579. if (errno != ERANGE && errno != EINVAL) {
  580. if (color_name.length() == 3) {
  581. u8 r = ((val & 0xf00) >> 8) | ((val & 0xf00) >> 4);
  582. u8 g = ((val & 0x0f0) >> 4) | ((val & 0x0f0) >> 0);
  583. u8 b = ((val & 0x00f) >> 0) | ((val & 0x00f) << 4);
  584. color = from_rgb(r, g, b);
  585. } else {
  586. color = from_rgb(val);
  587. }
  588. }
  589. }
  590. } else {
  591. color = s_colors[color_index].color;
  592. }
  593. Graph *graph = graph::find(_graphs, name.c_str());
  594. if (graph == NULL) {
  595. cs.error(client_id, "Graph not found");
  596. return;
  597. }
  598. for (u32 i = 0; i < array::size(graph->_channels); ++i) {
  599. if (graph->_channels[i].field == field) {
  600. graph->_channels[i].color = color;
  601. break;
  602. }
  603. }
  604. } else if (subcmd == "samples") {
  605. if (array::size(args) != 4) {
  606. cs.error(client_id, "Usage: graph samples <graph> <samples>");
  607. return;
  608. }
  609. DynamicString name(ta);
  610. DynamicString samples(ta);
  611. sjson::parse_string(name, args[2]);
  612. sjson::parse_string(samples, args[3]);
  613. Graph *graph = graph::find(_graphs, name.c_str());
  614. if (graph == NULL) {
  615. cs.error(client_id, "Graph not found");
  616. return;
  617. }
  618. graph->set_samples((u32)sjson::parse_int(samples.c_str()));
  619. } else {
  620. cs.error(client_id, "Unknown graph parameter");
  621. }
  622. }
  623. } // namespace graph_internal
  624. namespace graph_globals
  625. {
  626. Allocator *_allocator = NULL;
  627. DebugLine *_lines = NULL;
  628. void init(Allocator &a, ShaderManager &sm, ConsoleServer &cs)
  629. {
  630. _allocator = &a;
  631. _lines = CE_NEW(a, DebugLine)(sm, false);
  632. cs.register_command_name("graph", "Plot selected profiler data.", graph_internal::handle_command, NULL);
  633. }
  634. void shutdown()
  635. {
  636. // Destroy all graphs
  637. ListNode *cur;
  638. ListNode *tmp;
  639. list_for_each_safe(cur, tmp, &_graphs)
  640. {
  641. Graph *graph = (Graph *)container_of(cur, Graph, _node);
  642. CE_DELETE(default_allocator(), graph);
  643. }
  644. CE_DELETE(*_allocator, _lines);
  645. _lines = NULL;
  646. _allocator = NULL;
  647. }
  648. void draw_all(u16 window_width, u16 window_height)
  649. {
  650. _lines->reset();
  651. ListNode *cur;
  652. list_for_each(cur, &_graphs)
  653. {
  654. Graph *graph = (Graph *)container_of(cur, Graph, _node);
  655. graph->draw(*_lines, window_width, window_height);
  656. }
  657. }
  658. } // namespace graph_globals
  659. } // namespace crown
  660. #endif // CROWN_DEBUG