Przeglądaj źródła

device: add Graph subsystem

Daniele Bartolini 3 lat temu
rodzic
commit
20ae8ee700
5 zmienionych plików z 640 dodań i 0 usunięć
  1. 1 0
      docs/changelog.rst
  2. 2 0
      src/core/list.inl
  3. 19 0
      src/device/device.cpp
  4. 570 0
      src/device/graph.cpp
  5. 48 0
      src/device/graph.h

+ 1 - 0
docs/changelog.rst

@@ -14,6 +14,7 @@ Changelog
 **Runtime**
 
 * Windows: reduced CPU usage by polling joypads' status in a background thread.
+* Added ``graph`` command to plot profiler data at runtime.
 
 **Tools**
 

+ 2 - 0
src/core/list.inl

@@ -9,6 +9,8 @@
 
 namespace crown
 {
+#define LIST_INIT_HEAD(head) { &(head), &(head) }
+
 namespace list
 {
 	inline void init_head(ListNode &head)

+ 19 - 0
src/device/device.cpp

@@ -29,6 +29,7 @@
 #include "core/types.h"
 #include "device/console_server.h"
 #include "device/device.h"
+#include "device/graph.h"
 #include "device/input_device.h"
 #include "device/input_manager.h"
 #include "device/log.h"
@@ -417,6 +418,8 @@ void Device::run()
 	_pipeline = CE_NEW(_allocator, Pipeline)();
 	_pipeline->create(_width, _height);
 
+	graph_globals::init(_allocator, *_shader_manager, *_console_server);
+
 #if CROWN_TOOLS
 	tool_init();
 #endif
@@ -488,6 +491,8 @@ void Device::run()
 
 		profiler_globals::flush();
 
+		graph_globals::draw_all(_width, _height);
+
 #if CROWN_TOOLS
 		tool_update(dt);
 #else
@@ -508,6 +513,7 @@ void Device::run()
 
 	physics_globals::shutdown(_allocator);
 	audio_globals::shutdown();
+	graph_globals::shutdown();
 
 	_pipeline->destroy();
 	CE_DELETE(_allocator, _pipeline);
@@ -594,6 +600,19 @@ void Device::render(World &world, UnitId camera_unit)
 	bgfx::setViewTransform(VIEW_GUI, to_float_ptr(MATRIX4X4_IDENTITY), to_float_ptr(ortho_proj));
 	bgfx::setViewTransform(VIEW_SELECTION, to_float_ptr(view), to_float_ptr(proj));
 
+	f32 graph_ortho[16];
+	bx::mtxOrtho(graph_ortho
+		, -_width / 2.0f
+		,  _width / 2.0f
+		, -_height / 2.0f
+		,  _height / 2.0f
+		, 0.0f
+		, 1.0f
+		, 0.0f
+		, caps->homogeneousDepth
+		);
+	bgfx::setViewTransform(VIEW_GRAPH, to_float_ptr(MATRIX4X4_IDENTITY), to_float_ptr(from_array(graph_ortho)));
+
 	bgfx::setViewRect(VIEW_SPRITE_0, 0, 0, _width, _height);
 	bgfx::setViewRect(VIEW_SPRITE_1, 0, 0, _width, _height);
 	bgfx::setViewRect(VIEW_SPRITE_2, 0, 0, _width, _height);

+ 570 - 0
src/device/graph.cpp

@@ -0,0 +1,570 @@
+/*
+ * Copyright (c) 2012-2022 Daniele Bartolini and individual contributors.
+ * License: https://github.com/dbartolini/crown/blob/master/LICENSE
+ */
+
+#include "device/graph.h"
+
+#if CROWN_DEBUG
+
+#include "core/json/sjson.h"
+#include "core/list.inl"
+#include "core/math/constants.h"
+#include "core/math/matrix4x4.inl"
+#include "core/memory/temp_allocator.inl"
+#include "core/strings/dynamic_string.inl"
+#include "core/strings/string_id.inl"
+#include "device/console_server.h"
+#include "device/device.h"
+#include "device/log.h"
+#include "device/profiler.h"
+#include "world/debug_line.h"
+#include <stb_sprintf.h>
+
+LOG_SYSTEM(GRAPH, "graph")
+
+namespace crown
+{
+static ListNode _graphs = LIST_INIT_HEAD(_graphs);
+
+static const Vector3 segments[8][2] =
+{
+#define SEGMENTS_ASPECT_RATIO (1.0f/1.618f)
+	//
+	// E F F A
+	// E     A
+	// E     A
+	// E G G A
+	// D     B
+	// D     B
+	// D C C B
+	//
+	{ { 1.0f*SEGMENTS_ASPECT_RATIO, 1.0f, 0.0f }, {  1.0f*SEGMENTS_ASPECT_RATIO,  0.5f, 0.0f } }, // A
+	{ { 1.0f*SEGMENTS_ASPECT_RATIO, 0.5f, 0.0f }, {  1.0f*SEGMENTS_ASPECT_RATIO,  0.0f, 0.0f } }, // B
+	{ { 0.0f*SEGMENTS_ASPECT_RATIO, 0.0f, 0.0f }, {  1.0f*SEGMENTS_ASPECT_RATIO,  0.0f, 0.0f } }, // C
+	{ { 0.0f*SEGMENTS_ASPECT_RATIO, 0.0f, 0.0f }, {  0.0f*SEGMENTS_ASPECT_RATIO,  0.5f, 0.0f } }, // D
+	{ { 0.0f*SEGMENTS_ASPECT_RATIO, 0.5f, 0.0f }, {  0.0f*SEGMENTS_ASPECT_RATIO,  1.0f, 0.0f } }, // E
+	{ { 0.0f*SEGMENTS_ASPECT_RATIO, 1.0f, 0.0f }, {  1.0f*SEGMENTS_ASPECT_RATIO,  1.0f, 0.0f } }, // F
+	{ { 0.0f*SEGMENTS_ASPECT_RATIO, 0.5f, 0.0f }, {  1.0f*SEGMENTS_ASPECT_RATIO,  0.5f, 0.0f } }, // G
+	{ { 0.0f*SEGMENTS_ASPECT_RATIO, 0.0f, 0.0f }, { -0.1f*SEGMENTS_ASPECT_RATIO, -0.4f, 0.0f } }, // H
+};
+
+static void draw_string(DebugLine &dl, const char *str, float x, float y)
+{
+	char table[256] = {0};
+	//             HGFEDCBA
+	table[' '] = 0b00000000;
+	table['0'] = 0b00111111;
+	table['1'] = 0b00000011;
+	table['2'] = 0b01101101;
+	table['3'] = 0b01100111;
+	table['4'] = 0b01010011;
+	table['5'] = 0b01110110;
+	table['6'] = 0b01111110;
+	table['7'] = 0b00100011;
+	table['8'] = 0b01111111;
+	table['9'] = 0b01110111;
+	table['.'] = 0b10000000;
+	table['-'] = 0b01000000;
+	table['a'] = 0b01111011;
+	table['b'] = 0b01011110;
+	table['c'] = 0b00111100;
+	table['d'] = 0b01001111;
+	table['e'] = 0b01111100;
+	table['f'] = 0b01111000;
+	table['A'] = table['a'];
+	table['B'] = table['b'];
+	table['C'] = table['c'];
+	table['D'] = table['d'];
+	table['E'] = table['e'];
+	table['F'] = table['f'];
+
+	Vector3 advance;
+	advance.x = x;
+	advance.y = y;
+	advance.z = 0.0f;
+	for (const char *ch = str; *ch; ++ch) {
+		for (int i = 0; i < 8; ++i) {
+			if ((table[(int)*ch] & (1 << i)) != 0)
+				dl.add_line((32.0f * segments[i][0]) + advance, (32.0f * segments[i][1]) + advance, COLOR4_YELLOW);
+		}
+		float x_adv = 32.0f*SEGMENTS_ASPECT_RATIO;
+		advance.x += *ch != '.' ? x_adv*1.2f : x_adv*0.2f;
+	}
+}
+
+/// Plots graphs for debugging purposes.
+///
+/// @ingroup Device
+struct Graph
+{
+#define GRAPH_MAX_AXES 3
+#define GRAPH_MAX_SAMPLES 128
+	s32 _head;
+	f32 _samples[GRAPH_MAX_AXES][GRAPH_MAX_SAMPLES];
+	f32 _range_min;
+	f32 _range_max;
+	DynamicString _name;
+	ListNode _node;
+	char _field[32];
+	bool _visible;
+	bool _range_auto;
+	enum Layout { FILL, LEFT, RIGHT, BOTTOM, TOP } _layout;
+
+	Graph(Allocator &a)
+		: _head(0)
+		, _range_min(0.0f)
+		, _range_max(0.0f)
+		, _name(a)
+		, _visible(true)
+		, _range_auto(true)
+		, _layout(FILL)
+	{
+		_node.next = NULL;
+		_node.prev = NULL;
+
+		memset(_samples, 0, sizeof(_samples));
+		memset(_field, 0, sizeof(_field));
+	}
+
+	void set_range(f32 range_min, f32 range_max, bool range_auto)
+	{
+		if (range_auto) {
+			_range_min = 0.0f;
+			_range_max = 0.0f;
+		} else {
+			_range_min = range_min;
+			_range_max = range_max;
+		}
+
+		_range_auto = range_auto;
+	}
+
+	void sample_with_filter(const char *cur, const char *end)
+	{
+		while (cur != end) {
+			const u32 type = *(u32 *)cur;
+			cur += sizeof(u32);
+			if (type == ProfilerEventType::COUNT)
+				break;
+
+			const u32 size = *(u32 *)cur;
+			cur += sizeof(u32);
+
+			switch (type) {
+			case ProfilerEventType::RECORD_FLOAT: {
+				RecordFloat *rf = (RecordFloat *)cur;
+				if (strcmp(_field, rf->name) == 0)
+					sample(rf->value);
+			}
+				cur += size;
+				break;
+
+			case ProfilerEventType::RECORD_VECTOR3: {
+				RecordVector3 *rv = (RecordVector3 *)cur;
+				if (strcmp(_field, rv->name) == 0)
+					sample(rv->value);
+			}
+				cur += size;
+				break;
+
+			case ProfilerEventType::ENTER_PROFILE_SCOPE:
+			case ProfilerEventType::LEAVE_PROFILE_SCOPE:
+			case ProfilerEventType::ALLOCATE_MEMORY:
+			case ProfilerEventType::DEALLOCATE_MEMORY:
+				cur += size;
+				break;
+
+			default:
+				CE_FATAL("Unknown profiler event type");
+				break;
+			}
+		}
+	}
+
+	void add(const char *field)
+	{
+		strcpy(_field, field);
+	}
+
+	void sample(f32 value)
+	{
+		if (_range_auto) {
+			if (_range_min > value)
+				_range_min = value;
+			if (_range_max < value)
+				_range_max = value;
+		}
+		_samples[0][_head] = value;
+		_head = (_head + 1) % GRAPH_MAX_SAMPLES;
+	}
+
+	void sample(const Vector3 &value)
+	{
+		if (_range_auto) {
+			if (_range_min > value.x)
+				_range_min = value.x;
+			if (_range_min > value.y)
+				_range_min = value.y;
+			if (_range_min > value.z)
+				_range_min = value.z;
+			if (_range_max < value.x)
+				_range_max = value.x;
+			if (_range_max < value.y)
+				_range_max = value.y;
+			if (_range_max < value.z)
+				_range_max = value.z;
+		}
+		_samples[0][_head] = value.x;
+		_samples[1][_head] = value.y;
+		_samples[2][_head] = value.z;
+		_head = (_head + 1) % GRAPH_MAX_SAMPLES;
+	}
+
+	void draw(DebugLine &dl, u16 window_width, u16 window_height)
+	{
+		if (!_visible)
+			return;
+
+		sample_with_filter(profiler_globals::buffer_begin(), profiler_globals::buffer_end());
+
+		auto remap = [](f32 x, f32 in_min, f32 in_max, f32 out_min, f32 out_max) -> f32 {
+			return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
+		};
+
+		Color4 colors[] =
+		{
+			COLOR4_YELLOW,
+			COLOR4_RED,
+			COLOR4_GREEN
+		};
+		CE_STATIC_ASSERT(countof(colors) == GRAPH_MAX_AXES);
+
+		const f32 margin_padding = 32.0; // Pixels of padding inside window margins
+		const f32 window_width_padded  = f32(window_width) - margin_padding;
+		const f32 window_height_padded = f32(window_height) - margin_padding;
+
+		// Margins in window-coordinates
+		const f32 margin_right   =  window_width_padded / 2.0f;
+		const f32 margin_left    = -margin_right;
+		const f32 margin_top     =  window_height_padded / 2.0f;
+		const f32 margin_bottom  = -margin_top;
+
+		f32 x_start;
+		f32 x_step;
+		f32 y_min;
+		f32 y_max;
+		switch (_layout) {
+		case LEFT:
+			x_start = margin_left;
+			x_step = f32(margin_right - margin_left) / f32(GRAPH_MAX_SAMPLES - 1) / 2.0f;
+			y_min = margin_bottom;
+			y_max = margin_top;
+			break;
+
+		case RIGHT:
+			x_start = 0.0f;
+			x_step = f32(margin_right - margin_left) / f32(GRAPH_MAX_SAMPLES - 1) / 2.0f;
+			y_min = margin_bottom;
+			y_max = margin_top;
+			break;
+
+		case BOTTOM:
+			x_start = margin_left;
+			x_step = f32(margin_right - margin_left) / f32(GRAPH_MAX_SAMPLES - 1);
+			y_min = margin_bottom;
+			y_max = 0.0f;
+			break;
+
+		case TOP:
+			x_start = margin_left;
+			x_step = f32(margin_right - margin_left) / f32(GRAPH_MAX_SAMPLES - 1);
+			y_min = 0.0f;
+			y_max = margin_top;
+			break;
+
+		case FILL:
+		default:
+			x_start = margin_left;
+			x_step = f32(margin_right - margin_left) / f32(GRAPH_MAX_SAMPLES - 1);
+			y_min = margin_bottom;
+			y_max = margin_top;
+			break;
+		}
+
+		// Draw margin top
+		dl.add_line(vector3(x_start, y_max, 0.0f)
+			, vector3(x_start + x_step*(GRAPH_MAX_SAMPLES - 1), y_max, 0.0f)
+			, COLOR4_ORANGE
+			);
+		// Draw margin right
+		dl.add_line(vector3(x_start + x_step*(GRAPH_MAX_SAMPLES - 1), y_max, 0.0f)
+			, vector3(x_start + x_step*(GRAPH_MAX_SAMPLES - 1), y_min, 0.0f)
+			, COLOR4_ORANGE
+			);
+		// Draw margin bottom
+		dl.add_line(vector3(x_start + x_step*(GRAPH_MAX_SAMPLES - 1), y_min, 0.0f)
+			, vector3(x_start, y_min, 0.0f)
+			, COLOR4_ORANGE
+			);
+		// Draw margin left
+		dl.add_line(vector3(x_start, y_min, 0.0f)
+			, vector3(x_start, y_max, 0.0f)
+			, COLOR4_ORANGE
+			);
+
+		// For each channel
+		for (s32 cc = 0; cc < GRAPH_MAX_AXES; ++cc) {
+			// For each sample in the channel
+			for (s32 ii = 0, oldest = _head; ii < GRAPH_MAX_SAMPLES - 1; ++ii) {
+				f32 value[2];
+				value[0] = remap(_samples[cc][(oldest + 0) % GRAPH_MAX_SAMPLES], _range_min, _range_max, y_min, y_max);
+				value[1] = remap(_samples[cc][(oldest + 1) % GRAPH_MAX_SAMPLES], _range_min, _range_max, y_min, y_max);
+				dl.add_line(vector3(x_start + (ii + 0)*x_step, value[0], 0.0f)
+					, vector3(x_start + (ii + 1)*x_step, value[1], 0.0f)
+					, colors[cc]
+					);
+				oldest = (oldest + 1) % GRAPH_MAX_SAMPLES;
+			}
+		}
+
+		// Draw labels
+#define TEXT_PADDING 6.0f
+		float str_x = x_start + TEXT_PADDING;
+		char buf[64] = {};
+		stbsp_snprintf(buf, sizeof(buf), "%g", _range_min);
+		draw_string(dl, buf, str_x, y_min + TEXT_PADDING);
+		stbsp_snprintf(buf, sizeof(buf), "%g", _range_max);
+		draw_string(dl, buf, str_x, y_max - TEXT_PADDING - 32.0f);
+
+		dl.submit(VIEW_GRAPH);
+	}
+};
+
+namespace graph
+{
+	/// Creates a new graph with the given @a name and appends it
+	/// to the list @a head.
+	Graph *create(ListNode &head, const char *name)
+	{
+		Graph *graph = CE_NEW(default_allocator(), Graph)(default_allocator());
+		graph->_name = name;
+		list::add(graph->_node, head);
+		return graph;
+	}
+
+	/// Destroys the given @a graph.
+	void destroy(Graph *graph)
+	{
+		list::remove(graph->_node);
+		CE_DELETE(default_allocator(), graph);
+	}
+
+	/// Returns the graph with the given @a name or NULL if no graph is found.
+	Graph *find(ListNode &head, const char *name)
+	{
+		ListNode *cur;
+		list_for_each(cur, &head)
+		{
+			Graph *graph = (Graph *)container_of(cur, Graph, _node);
+			if (graph->_name == name)
+				return graph;
+		}
+
+		return NULL;
+	}
+
+} // namespace graph
+
+namespace graph_internal
+{
+	void handle_command(ConsoleServer &cs, u32 client_id, const JsonArray &args, void * /*user_data*/)
+	{
+		TempAllocator1024 ta;
+
+		if (array::size(args) < 2) {
+			cs.error(client_id, "Usage: graph make <name>");
+			return;
+		}
+
+		DynamicString subcmd(ta);
+		sjson::parse_string(subcmd, args[1]);
+		if (subcmd == "make") {
+			if (array::size(args) != 3) {
+				cs.error(client_id, "Usage: graph make <name>");
+				return;
+			}
+
+			DynamicString name(ta);
+			sjson::parse_string(name, args[2]);
+
+			Graph *graph = graph::find(_graphs, name.c_str());
+			if (graph != NULL) {
+				cs.error(client_id, "Graph with this name already exists");
+				return;
+			}
+			graph::create(_graphs, name.c_str());
+		} else if (subcmd == "list") {
+			ListNode *cur;
+			list_for_each(cur, &_graphs)
+			{
+				Graph *graph = (Graph *)container_of(cur, Graph, _node);
+				logi(GRAPH, "%s", graph->_name.c_str());
+			}
+		} else if (subcmd == "range") {
+			if (array::size(args) != 3 && array::size(args) != 5) {
+				cs.error(client_id, "Usage: graph range <name> [min max]");
+				return;
+			}
+
+			DynamicString name(ta);
+			DynamicString min(ta);
+			DynamicString max(ta);
+			sjson::parse_string(name, args[2]);
+			if (array::size(args) == 5) {
+				sjson::parse_string(min, args[3]);
+				sjson::parse_string(max, args[4]);
+			} else {
+				min = "0";
+				max = "0";
+			}
+
+			Graph *graph = graph::find(_graphs, name.c_str());
+			if (graph == NULL) {
+				cs.error(client_id, "Graph not found");
+				return;
+			}
+			graph->set_range(sjson::parse_float(min.c_str()), sjson::parse_float(max.c_str()), array::size(args) == 3);
+		} else if (subcmd == "add") {
+			if (array::size(args) != 4) {
+				cs.error(client_id, "Usage: graph add <name> <field>");
+				return;
+			}
+
+			DynamicString name(ta);
+			DynamicString field(ta);
+			sjson::parse_string(name, args[2]);
+			sjson::parse_string(field, args[3]);
+
+			Graph *graph = graph::find(_graphs, name.c_str());
+			if (graph == NULL) {
+				cs.error(client_id, "Graph not found");
+				return;
+			}
+			graph->add(field.c_str());
+		} else if (subcmd == "hide") {
+			if (array::size(args) != 3) {
+				cs.error(client_id, "Usage: graph hide <name>");
+				return;
+			}
+
+			DynamicString name(ta);
+			sjson::parse_string(name, args[2]);
+
+			Graph *graph = graph::find(_graphs, name.c_str());
+			if (graph == NULL) {
+				cs.error(client_id, "Graph not found");
+				return;
+			}
+			graph->_visible = false;
+		} else if (subcmd == "show") {
+			if (array::size(args) != 3) {
+				cs.error(client_id, "Usage: graph show <name>");
+				return;
+			}
+
+			DynamicString name(ta);
+			sjson::parse_string(name, args[2]);
+
+			Graph *graph = graph::find(_graphs, name.c_str());
+			if (graph == NULL) {
+				cs.error(client_id, "Graph not found");
+				return;
+			}
+			graph->_visible = true;
+		} else if (subcmd == "layout") {
+			if (array::size(args) != 4) {
+				cs.error(client_id, "Usage: graph layout <name> <type>");
+				return;
+			}
+
+			DynamicString name(ta);
+			DynamicString type(ta);
+			sjson::parse_string(name, args[2]);
+			sjson::parse_string(type, args[3]);
+
+			s32 lt = Graph::FILL;
+			if (type == "fill") {
+				lt = Graph::FILL;
+			} else if (type == "left") {
+				lt = Graph::LEFT;
+			} else if (type == "right") {
+				lt = Graph::RIGHT;
+			} else if (type == "bottom") {
+				lt = Graph::BOTTOM;
+			} else if (type == "top") {
+				lt = Graph::TOP;
+			} else {
+				cs.error(client_id, "Invalid layout type");
+				return;
+			}
+
+			Graph *graph = graph::find(_graphs, name.c_str());
+			if (graph == NULL) {
+				cs.error(client_id, "Graph not found");
+				return;
+			}
+			graph->_layout = (Graph::Layout)lt;
+		} else {
+			cs.error(client_id, "Unknown graph command");
+		}
+	}
+
+} // namespace graph_internal
+
+namespace graph_globals
+{
+	Allocator *_allocator = NULL;
+	DebugLine *_lines = NULL;
+
+	void init(Allocator &a, ShaderManager &sm, ConsoleServer &cs)
+	{
+		_allocator = &a;
+		_lines = CE_NEW(a, DebugLine)(sm, false);
+
+		cs.register_command_name("graph", "Plot selected profiler data", graph_internal::handle_command, NULL);
+	}
+
+	void shutdown()
+	{
+		// Destroy all graphs
+		ListNode *cur;
+		ListNode *tmp;
+		list_for_each_safe(cur, tmp, &_graphs)
+		{
+			Graph *graph = (Graph *)container_of(cur, Graph, _node);
+			CE_DELETE(default_allocator(), graph);
+		}
+
+		CE_DELETE(*_allocator, _lines);
+		_lines = NULL;
+		_allocator = NULL;
+	}
+
+	void draw_all(u16 window_width, u16 window_height)
+	{
+		_lines->reset();
+
+		ListNode *cur;
+		list_for_each(cur, &_graphs)
+		{
+			Graph *graph = (Graph *)container_of(cur, Graph, _node);
+			graph->draw(*_lines, window_width, window_height);
+		}
+	}
+
+} // namespace graph_globals
+
+} // namespace crown
+
+#endif // CROWN_DEBUG

+ 48 - 0
src/device/graph.h

@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2012-2022 Daniele Bartolini and individual contributors.
+ * License: https://github.com/dbartolini/crown/blob/master/LICENSE
+ */
+
+#pragma once
+
+#include "config.h"
+#include "core/memory/types.h"
+#include "device/types.h"
+#include "world/types.h"
+
+namespace crown
+{
+///
+/// @ingroup Device
+namespace graph_globals
+{
+#if CROWN_DEBUG
+	///
+	void init(Allocator &a, ShaderManager &sm, ConsoleServer &cs);
+
+	///
+	void shutdown();
+
+	/// Draws all the graphs.
+	void draw_all(u16 window_width, u16 window_height);
+#else
+	inline void init(Allocator &a, ShaderManager &sm, ConsoleServer &cs)
+	{
+		CE_UNUSED_3(a, sm, cs);
+		CE_NOOP();
+	}
+
+	inline void shutdown()
+	{
+		CE_NOOP();
+	}
+
+	inline void draw_all(u16 window_width, u16 window_height)
+	{
+		CE_UNUSED_2(window_width, window_height);
+		CE_NOOP();
+	}
+#endif // if CROWN_DEBUG
+} // namespce graph_globals
+
+} // namespace crown