Forráskód Böngészése

First attempt to the developer console

Panagiotis Christopoulos Charitos 6 éve
szülő
commit
1d211d4d65

BIN
engine_data/UbuntuMonoRegular.ttf


+ 5 - 0
samples/common/Framework.cpp

@@ -56,6 +56,11 @@ Error SampleApp::userMainLoop(Bool& quit)
 		return Error::NONE;
 	}
 
+	if(in.getKey(KeyCode::BACKQUOTE) == 1)
+	{
+		setDisplayDeveloperConsole(!getDisplayDeveloperConsole());
+	}
+
 	// move the camera
 	static MoveComponent* mover = &scene.getActiveCameraNode().getComponent<MoveComponent>();
 

+ 26 - 9
src/anki/core/App.cpp

@@ -11,7 +11,7 @@
 #include <anki/util/System.h>
 #include <anki/util/ThreadHive.h>
 #include <anki/core/Trace.h>
-
+#include <anki/core/DeveloperConsole.h>
 #include <anki/core/NativeWindow.h>
 #include <anki/input/Input.h>
 #include <anki/scene/SceneGraph.h>
@@ -95,7 +95,7 @@ public:
 	{
 	}
 
-	void build(CanvasPtr canvas)
+	void build(CanvasPtr canvas) override
 	{
 		// Misc
 		++m_bufferedFrames;
@@ -271,6 +271,7 @@ void App::cleanup()
 	m_heapAlloc.deleteInstance(m_script);
 	m_heapAlloc.deleteInstance(m_renderer);
 	m_statsUi.reset(nullptr);
+	m_console.reset(nullptr);
 	m_heapAlloc.deleteInstance(m_ui);
 	m_heapAlloc.deleteInstance(m_resources);
 	m_heapAlloc.deleteInstance(m_resourceFs);
@@ -447,6 +448,7 @@ Error App::initInternal(const ConfigSet& config_, AllocAlignedCallback allocCb,
 	ANKI_CHECK(m_ui->init(m_allocCb, m_allocCbData, m_resources, m_gr, m_stagingMem, m_input));
 
 	ANKI_CHECK(m_ui->newInstance<StatsUi>(m_statsUi));
+	ANKI_CHECK(m_ui->newInstance<DeveloperConsole>(m_console, m_allocCb, m_allocCbData));
 
 	//
 	// Renderer
@@ -578,7 +580,7 @@ Error App::mainLoop()
 
 		// Inject stats UI
 		DynamicArrayAuto<UiQueueElement> newUiElementArr(m_heapAlloc);
-		injectStatsUiElement(newUiElementArr, rqueue);
+		injectUiElements(newUiElementArr, rqueue);
 
 		// Render
 		TexturePtr presentableTex = m_gr->acquireNextPresentableTexture();
@@ -639,24 +641,39 @@ Error App::mainLoop()
 	return Error::NONE;
 }
 
-void App::injectStatsUiElement(DynamicArrayAuto<UiQueueElement>& newUiElementArr, RenderQueue& rqueue)
+void App::injectUiElements(DynamicArrayAuto<UiQueueElement>& newUiElementArr, RenderQueue& rqueue)
 {
-	if(m_displayStats)
+	const U originalCount = rqueue.m_uis.getSize();
+	if(m_displayStats || m_consoleEnabled)
 	{
-		U count = rqueue.m_uis.getSize();
-		newUiElementArr.create(count + 1u);
+		const U extraElements = (m_displayStats != 0) + (m_consoleEnabled != 0);
+		newUiElementArr.create(originalCount + extraElements);
 
-		if(count)
+		if(originalCount > 0)
 		{
 			memcpy(&newUiElementArr[0], &rqueue.m_uis[0], rqueue.m_uis.getSizeInBytes());
 		}
 
+		rqueue.m_uis = WeakArray<UiQueueElement>(newUiElementArr);
+	}
+
+	U count = originalCount;
+	if(m_displayStats)
+	{
 		newUiElementArr[count].m_userData = m_statsUi.get();
 		newUiElementArr[count].m_drawCallback = [](CanvasPtr& canvas, void* userData) -> void {
 			static_cast<StatsUi*>(userData)->build(canvas);
 		};
+		++count;
+	}
 
-		rqueue.m_uis = WeakArray<UiQueueElement>(newUiElementArr);
+	if(m_consoleEnabled)
+	{
+		newUiElementArr[count].m_userData = m_console.get();
+		newUiElementArr[count].m_drawCallback = [](CanvasPtr& canvas, void* userData) -> void {
+			static_cast<DeveloperConsole*>(userData)->build(canvas);
+		};
+		++count;
 	}
 }
 

+ 14 - 2
src/anki/core/App.h

@@ -147,6 +147,16 @@ public:
 		return m_displayStats;
 	}
 
+	void setDisplayDeveloperConsole(Bool display)
+	{
+		m_consoleEnabled = display;
+	}
+
+	Bool getDisplayDeveloperConsole() const
+	{
+		return m_consoleEnabled;
+	}
+
 private:
 	class StatsUi;
 
@@ -171,6 +181,8 @@ private:
 	// Misc
 	UiImmediateModeBuilderPtr m_statsUi;
 	Bool m_displayStats = false;
+	UiImmediateModeBuilderPtr m_console;
+	Bool m_consoleEnabled = false;
 	Timestamp m_globalTimestamp = 1;
 	ThreadHive* m_threadHive = nullptr;
 	String m_settingsDir; ///< The path that holds the configuration
@@ -198,8 +210,8 @@ private:
 	ANKI_USE_RESULT Error initDirs(const ConfigSet& cfg);
 	void cleanup();
 
-	/// Inject a new UI element in the render queue for displaying stats.
-	void injectStatsUiElement(DynamicArrayAuto<UiQueueElement>& elements, RenderQueue& rqueue);
+	/// Inject a new UI element in the render queue for displaying various stuff.
+	void injectUiElements(DynamicArrayAuto<UiQueueElement>& elements, RenderQueue& rqueue);
 };
 
 } // end namespace anki

+ 1 - 1
src/anki/core/CMakeLists.txt

@@ -1,4 +1,4 @@
-set(SOURCES App.cpp Config.cpp StagingGpuMemoryManager.cpp)
+set(SOURCES App.cpp Config.cpp StagingGpuMemoryManager.cpp DeveloperConsole.cpp)
 
 if(SDL)
 	set(SOURCES ${SOURCES} NativeWindowSdl.cpp)

+ 142 - 0
src/anki/core/DeveloperConsole.cpp

@@ -0,0 +1,142 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/core/DeveloperConsole.h>
+
+namespace anki
+{
+
+DeveloperConsole::~DeveloperConsole()
+{
+	LoggerSingleton::get().removeMessageHandler(this, loggerCallback);
+
+	while(!m_logItems.isEmpty())
+	{
+		LogItem* item = &m_logItems.getFront();
+		m_logItems.popFront();
+		item->m_msg.destroy(m_alloc);
+		m_alloc.deleteInstance(item);
+	}
+}
+
+Error DeveloperConsole::init(AllocAlignedCallback allocCb, void* allocCbUserData)
+{
+	m_alloc = HeapAllocator<U8>(allocCb, allocCbUserData);
+	zeroMemory(m_inputText);
+
+	ANKI_CHECK(m_manager->newInstance(m_font, "engine_data/UbuntuMonoRegular.ttf", std::initializer_list<U32>{16}));
+
+	// Add a new callback to the logger
+	LoggerSingleton::get().addMessageHandler(this, loggerCallback);
+
+	return Error::NONE;
+}
+
+void DeveloperConsole::build(CanvasPtr ctx)
+{
+	const Vec4 oldWindowColor = ImGui::GetStyle().Colors[ImGuiCol_WindowBg];
+	ImGui::GetStyle().Colors[ImGuiCol_WindowBg].w = 0.3f;
+	ctx->pushFont(m_font, 16);
+
+	ImGui::Begin("Console", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoTitleBar);
+
+	ImGui::SetWindowPos(Vec2(0.0f, 0.0f));
+	ImGui::SetWindowSize(Vec2(ctx->getWidth(), ctx->getHeight() * (2.0f / 3.0f)));
+
+	// Push the items
+	const F32 footerHeightToPreserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing();
+	ImGui::BeginChild("ScrollingRegion",
+		Vec2(0, -footerHeightToPreserve),
+		false,
+		ImGuiWindowFlags_HorizontalScrollbar); // Leave room for 1 separator + 1 InputText
+
+	for(const LogItem& item : m_logItems)
+	{
+		switch(item.m_type)
+		{
+		case LoggerMessageType::NORMAL:
+			ImGui::PushStyleColor(ImGuiCol_Text, Vec4(0.0f, 1.0f, 0.0f, 1.0f));
+			break;
+		case LoggerMessageType::ERROR:
+		case LoggerMessageType::FATAL:
+			ImGui::PushStyleColor(ImGuiCol_Text, Vec4(1.0f, 0.0f, 0.0f, 1.0f));
+			break;
+		case LoggerMessageType::WARNING:
+			ImGui::PushStyleColor(ImGuiCol_Text, Vec4(0.9f, 0.6f, 0.14f, 1.0f));
+			break;
+		default:
+			ANKI_ASSERT(0);
+		}
+
+		static const Array<const char*, static_cast<U>(LoggerMessageType::COUNT)> MSG_TEXT = {{"I", "E", "W", "F"}};
+		ImGui::Text("[%s][%s] %s (%s:%d %s)",
+			MSG_TEXT[static_cast<U>(item.m_type)],
+			item.m_subsystem,
+			item.m_msg.cstr(),
+			item.m_file,
+			item.m_line,
+			item.m_func);
+
+		ImGui::PopStyleColor();
+	}
+	ImGui::EndChild();
+	ImGui::SetScrollHereY(1.0f);
+
+	// Commands
+	ImGui::Separator();
+	if(ImGui::InputText("",
+		   &m_inputText[0],
+		   m_inputText.getSizeInBytes(),
+		   ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackCompletion
+			   | ImGuiInputTextFlags_CallbackHistory,
+		   [](ImGuiInputTextCallbackData* data) -> int {
+			   // TODO
+			   return 0;
+		   },
+		   this))
+	{
+		printf("lala\n");
+	}
+
+	ImGui::End();
+	ImGui::GetStyle().Colors[ImGuiCol_WindowBg] = oldWindowColor;
+	ctx->popFont();
+}
+
+void DeveloperConsole::newLogItem(const LoggerMessageInfo& inf)
+{
+	LogItem* newLogItem;
+
+	// Pop first
+	if(m_logItemCount + 1 > MAX_LOG_ITEMS)
+	{
+		LogItem* first = &m_logItems.getFront();
+		m_logItems.popFront();
+
+		first->m_msg.destroy(m_alloc);
+
+		// Re-use the log item
+		newLogItem = first;
+		--m_logItemCount;
+	}
+	else
+	{
+		newLogItem = m_alloc.newInstance<LogItem>();
+	}
+
+	// Create the new item
+	newLogItem->m_file = inf.m_file;
+	newLogItem->m_func = inf.m_func;
+	newLogItem->m_subsystem = inf.m_subsystem;
+	newLogItem->m_msg.create(m_alloc, inf.m_msg);
+	newLogItem->m_line = inf.m_line;
+	newLogItem->m_type = inf.m_type;
+
+	// Push it back
+	m_logItems.pushBack(newLogItem);
+	++m_logItemCount;
+}
+
+} // end namespace anki

+ 61 - 0
src/anki/core/DeveloperConsole.h

@@ -0,0 +1,61 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <anki/Ui.h>
+#include <anki/util/List.h>
+
+namespace anki
+{
+
+/// @addtogroup core
+/// @{
+
+/// Developer console UI.
+class DeveloperConsole : public UiImmediateModeBuilder
+{
+public:
+	DeveloperConsole(UiManager* ui)
+		: UiImmediateModeBuilder(ui)
+	{
+	}
+
+	~DeveloperConsole();
+
+	ANKI_USE_RESULT Error init(AllocAlignedCallback allocCb, void* allocCbUserData);
+
+	void build(CanvasPtr ctx) override;
+
+private:
+	static constexpr U MAX_LOG_ITEMS = 64;
+
+	class LogItem : public IntrusiveListEnabled<LogItem>
+	{
+	public:
+		const char* m_file;
+		const char* m_func;
+		const char* m_subsystem;
+		String m_msg;
+		I32 m_line;
+		LoggerMessageType m_type;
+	};
+
+	HeapAllocator<U8> m_alloc;
+	FontPtr m_font;
+	IntrusiveList<LogItem> m_logItems;
+	U32 m_logItemCount = 0;
+	Array<char, 256> m_inputText;
+
+	void newLogItem(const LoggerMessageInfo& inf);
+
+	static void loggerCallback(void* userData, const LoggerMessageInfo& info)
+	{
+		static_cast<DeveloperConsole*>(userData)->newLogItem(info);
+	}
+};
+/// @}
+
+} // end namespace anki

+ 10 - 0
src/anki/ui/Canvas.h

@@ -38,6 +38,16 @@ public:
 		m_height = height;
 	}
 
+	U32 getWidth() const
+	{
+		return m_width;
+	}
+
+	U32 getHeight() const
+	{
+		return m_height;
+	}
+
 	/// @name building commands. The order matters.
 	/// @{
 

+ 5 - 0
src/anki/util/Array.h

@@ -145,6 +145,11 @@ public:
 	{
 		return N;
 	}
+
+	static constexpr PtrSize getSizeInBytes()
+	{
+		return N * sizeof(Value);
+	}
 };
 
 /// 2D Array. @code Array2d<X, 10, 2> a; @endcode is equivelent to @code X a[10][2]; @endcode

+ 44 - 20
src/anki/util/Logger.cpp

@@ -17,7 +17,7 @@
 namespace anki
 {
 
-static const Array<const char*, static_cast<U>(Logger::MessageType::COUNT)> MSG_TEXT = {{"I", "E", "W", "F"}};
+static const Array<const char*, static_cast<U>(LoggerMessageType::COUNT)> MSG_TEXT = {{"I", "E", "W", "F"}};
 
 Logger::Logger()
 {
@@ -28,17 +28,41 @@ Logger::~Logger()
 {
 }
 
-void Logger::addMessageHandler(void* data, MessageHandlerCallback callback)
+void Logger::addMessageHandler(void* data, LoggerMessageHandlerCallback callback)
 {
+	LockGuard<Mutex> lock(m_mutex);
 	m_handlers[m_handlersCount++] = Handler(data, callback);
 }
 
+void Logger::removeMessageHandler(void* data, LoggerMessageHandlerCallback callback)
+{
+	LockGuard<Mutex> lock(m_mutex);
+
+	U i;
+	for(i = 0; i < m_handlersCount; ++i)
+	{
+		if(m_handlers[i].m_callback == callback && m_handlers[i].m_data == data)
+		{
+			break;
+		}
+	}
+
+	if(i < m_handlersCount)
+	{
+		for(U j = i + 1; j < m_handlersCount; ++j)
+		{
+			m_handlers[j - 1] = m_handlers[j];
+		}
+		--m_handlersCount;
+	}
+}
+
 void Logger::write(
-	const char* file, int line, const char* func, const char* subsystem, MessageType type, const char* msg)
+	const char* file, int line, const char* func, const char* subsystem, LoggerMessageType type, const char* msg)
 {
 	m_mutex.lock();
 
-	Info inf = {file, line, func, type, msg, subsystem};
+	LoggerMessageInfo inf = {file, line, func, type, msg, subsystem};
 
 	U count = m_handlersCount;
 	while(count-- != 0)
@@ -48,14 +72,14 @@ void Logger::write(
 
 	m_mutex.unlock();
 
-	if(type == MessageType::FATAL)
+	if(type == LoggerMessageType::FATAL)
 	{
 		abort();
 	}
 }
 
 void Logger::writeFormated(
-	const char* file, int line, const char* func, const char* subsystem, MessageType type, const char* fmt, ...)
+	const char* file, int line, const char* func, const char* subsystem, LoggerMessageType type, const char* fmt, ...)
 {
 	char buffer[1024 * 10];
 	va_list args;
@@ -90,7 +114,7 @@ void Logger::writeFormated(
 	}
 }
 
-void Logger::defaultSystemMessageHandler(void*, const Info& info)
+void Logger::defaultSystemMessageHandler(void*, const LoggerMessageInfo& info)
 {
 #if ANKI_OS == ANKI_OS_LINUX
 	FILE* out = nullptr;
@@ -100,22 +124,22 @@ void Logger::defaultSystemMessageHandler(void*, const Info& info)
 
 	switch(info.m_type)
 	{
-	case Logger::MessageType::NORMAL:
+	case LoggerMessageType::NORMAL:
 		out = stdout;
 		terminalColor = "\033[0;32m";
 		terminalColorBg = "\033[1;42;37m";
 		break;
-	case Logger::MessageType::ERROR:
+	case LoggerMessageType::ERROR:
 		out = stderr;
 		terminalColor = "\033[0;31m";
 		terminalColorBg = "\033[1;41;37m";
 		break;
-	case Logger::MessageType::WARNING:
+	case LoggerMessageType::WARNING:
 		out = stderr;
 		terminalColor = "\033[2;33m";
 		terminalColorBg = "\033[1;43;37m";
 		break;
-	case Logger::MessageType::FATAL:
+	case LoggerMessageType::FATAL:
 		out = stderr;
 		terminalColor = "\033[0;31m";
 		terminalColorBg = "\033[1;41;37m";
@@ -149,16 +173,16 @@ void Logger::defaultSystemMessageHandler(void*, const Info& info)
 
 	switch(info.m_type)
 	{
-	case Logger::MessageType::NORMAL:
+	case LoggerMessageType::NORMAL:
 		andMsgType = ANDROID_LOG_INFO;
 		break;
-	case Logger::MessageType::ERROR:
+	case LoggerMessageType::ERROR:
 		andMsgType = ANDROID_LOG_ERROR;
 		break;
-	case Logger::MessageType::WARNING:
+	case LoggerMessageType::WARNING:
 		andMsgType = ANDROID_LOG_WARN;
 		break;
-	case Logger::MessageType::FATAL:
+	case LoggerMessageType::FATAL:
 		andMsgType = ANDROID_LOG_ERROR;
 		break;
 	default:
@@ -173,16 +197,16 @@ void Logger::defaultSystemMessageHandler(void*, const Info& info)
 
 	switch(info.m_type)
 	{
-	case Logger::MessageType::NORMAL:
+	case LoggerMessageType::NORMAL:
 		out = stdout;
 		break;
-	case Logger::MessageType::ERROR:
+	case LoggerMessageType::ERROR:
 		out = stderr;
 		break;
-	case Logger::MessageType::WARNING:
+	case LoggerMessageType::WARNING:
 		out = stderr;
 		break;
-	case Logger::MessageType::FATAL:
+	case LoggerMessageType::FATAL:
 		out = stderr;
 		break;
 	default:
@@ -202,7 +226,7 @@ void Logger::defaultSystemMessageHandler(void*, const Info& info)
 #endif
 }
 
-void Logger::fileMessageHandler(void* pfile, const Info& info)
+void Logger::fileMessageHandler(void* pfile, const LoggerMessageInfo& info)
 {
 	File* file = reinterpret_cast<File*>(pfile);
 

+ 46 - 34
src/anki/util/Logger.h

@@ -18,6 +18,34 @@ class File;
 /// @addtogroup util_other
 /// @{
 
+/// Logger message type.
+/// @memberof Logger
+enum class LoggerMessageType : U8
+{
+	NORMAL,
+	ERROR,
+	WARNING,
+	FATAL,
+	COUNT
+};
+
+/// Used as parammeter when emitting the signal.
+/// @memberof Logger
+class LoggerMessageInfo
+{
+public:
+	const char* m_file;
+	I32 m_line;
+	const char* m_func;
+	LoggerMessageType m_type;
+	const char* m_msg;
+	const char* m_subsystem;
+};
+
+/// The message handler callback.
+/// @memberof Logger
+using LoggerMessageHandlerCallback = void (*)(void*, const LoggerMessageInfo& info);
+
 /// The logger singleton class. The logger cannot print errors or throw exceptions, it has to recover somehow. It's
 /// thread safe.
 /// To add a new signal:
@@ -25,61 +53,45 @@ class File;
 class Logger
 {
 public:
-	/// Logger message type
-	enum class MessageType : U8
-	{
-		NORMAL,
-		ERROR,
-		WARNING,
-		FATAL,
-		COUNT
-	};
-
-	/// Used as parammeter when emitting the signal
-	class Info
-	{
-	public:
-		const char* m_file;
-		I32 m_line;
-		const char* m_func;
-		MessageType m_type;
-		const char* m_msg;
-		const char* m_subsystem;
-	};
-
-	/// The message handler callback
-	using MessageHandlerCallback = void (*)(void*, const Info& info);
-
 	/// Initialize the logger and add the default message handler
 	Logger();
 
 	~Logger();
 
 	/// Add a new message handler
-	void addMessageHandler(void* data, MessageHandlerCallback callback);
+	void addMessageHandler(void* data, LoggerMessageHandlerCallback callback);
+
+	/// Remove a message handler.
+	void removeMessageHandler(void* data, LoggerMessageHandlerCallback callback);
 
 	/// Add file message handler.
 	void addFileMessageHandler(File* file);
 
 	/// Send a message
-	void write(const char* file, int line, const char* func, const char* subsystem, MessageType type, const char* msg);
+	void write(
+		const char* file, int line, const char* func, const char* subsystem, LoggerMessageType type, const char* msg);
 
 	/// Send a formated message
-	void writeFormated(
-		const char* file, int line, const char* func, const char* subsystem, MessageType type, const char* fmt, ...);
+	void writeFormated(const char* file,
+		int line,
+		const char* func,
+		const char* subsystem,
+		LoggerMessageType type,
+		const char* fmt,
+		...);
 
 private:
 	class Handler
 	{
 	public:
 		void* m_data = nullptr;
-		MessageHandlerCallback m_callback = nullptr;
+		LoggerMessageHandlerCallback m_callback = nullptr;
 
 		Handler() = default;
 
 		Handler(const Handler&) = default;
 
-		Handler(void* data, MessageHandlerCallback callback)
+		Handler(void* data, LoggerMessageHandlerCallback callback)
 			: m_data(data)
 			, m_callback(callback)
 		{
@@ -90,8 +102,8 @@ private:
 	Array<Handler, 4> m_handlers;
 	U32 m_handlersCount = 0;
 
-	static void defaultSystemMessageHandler(void*, const Info& info);
-	static void fileMessageHandler(void* file, const Info& info);
+	static void defaultSystemMessageHandler(void*, const LoggerMessageInfo& info);
+	static void fileMessageHandler(void* file, const LoggerMessageInfo& info);
 };
 
 typedef Singleton<Logger> LoggerSingleton;
@@ -100,7 +112,7 @@ typedef Singleton<Logger> LoggerSingleton;
 	do \
 	{ \
 		LoggerSingleton::get().writeFormated( \
-			ANKI_FILE, __LINE__, ANKI_FUNC, subsystem_, Logger::MessageType::t, __VA_ARGS__); \
+			ANKI_FILE, __LINE__, ANKI_FUNC, subsystem_, LoggerMessageType::t, __VA_ARGS__); \
 	} while(false);
 /// @}