Browse Source

Move the UI implementation form nuklear to ImGUI. ImGUI has better support and it's actively developed

Panagiotis Christopoulos Charitos 6 years ago
parent
commit
73a80e2eae

+ 3 - 3
shaders/Ui.glslp

@@ -24,7 +24,7 @@ out gl_PerVertex
 	Vec4 gl_Position;
 };
 
-layout(set = 0, binding = 0) uniform u_
+layout(push_constant) uniform u_
 {
 	Vec4 u_transform; // x: x scale, y: y scale, z: x transl, w: y transl
 };
@@ -52,8 +52,8 @@ layout(location = 1) in Vec4 in_col;
 layout(location = 0) out Vec4 out_col;
 
 #if TEXTURE_TYPE > 0
-layout(set = 0, binding = 1) uniform sampler u_trilinearRepeatSampler;
-layout(set = 0, binding = 2) uniform texture2D u_tex;
+layout(set = 0, binding = 0) uniform sampler u_trilinearRepeatSampler;
+layout(set = 0, binding = 1) uniform texture2D u_tex;
 #endif
 
 void main()

+ 38 - 41
src/anki/core/App.cpp

@@ -106,54 +106,53 @@ public:
 		}
 
 		// Start drawing the UI
-		nk_context* ctx = &canvas->getNkContext();
-
 		canvas->pushFont(canvas->getDefaultFont(), 16);
 
-		nk_style_push_style_item(ctx, &ctx->style.window.fixed_background, nk_style_item_color(nk_rgba(0, 0, 0, 128)));
+		const Vec4 oldWindowColor = ImGui::GetStyle().Colors[ImGuiCol_WindowBg];
+		ImGui::GetStyle().Colors[ImGuiCol_WindowBg].w = 0.3f;
 
-		if(nk_begin(ctx, "Stats", nk_rect(5, 5, 230, 450), 0))
+		if(ImGui::Begin("Stats", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize))
 		{
-			nk_layout_row_dynamic(ctx, 17, 1);
-
-			nk_label(ctx, "Time:", NK_TEXT_ALIGN_LEFT);
-			labelTime(ctx, m_frameTime.get(flush), "Total frame");
-			labelTime(ctx, m_renderTime.get(flush) - m_lightBinTime.get(flush), "Renderer");
-			labelTime(ctx, m_lightBinTime.get(false), "Light bin");
-			labelTime(ctx, m_sceneUpdateTime.get(flush), "Scene update");
-			labelTime(ctx, m_visTestsTime.get(flush), "Visibility");
-			labelTime(ctx, m_physicsTime.get(flush), "Physics");
-
-			nk_label(ctx, " ", NK_TEXT_ALIGN_LEFT);
-			nk_label(ctx, "Memory:", NK_TEXT_ALIGN_LEFT);
-			labelBytes(ctx, m_allocatedCpuMem, "Total CPU");
-			labelUint(ctx, m_allocCount, "Total allocations");
-			labelUint(ctx, m_freeCount, "Total frees");
-			labelBytes(ctx, m_vkCpuMem, "Vulkan CPU");
-			labelBytes(ctx, m_vkGpuMem, "Vulkan GPU");
-
-			nk_label(ctx, " ", NK_TEXT_ALIGN_LEFT);
-			nk_label(ctx, "Vulkan:", NK_TEXT_ALIGN_LEFT);
-			labelUint(ctx, m_vkCmdbCount, "Cmd buffers");
-
-			nk_label(ctx, " ", NK_TEXT_ALIGN_LEFT);
-			nk_label(ctx, "Other:", NK_TEXT_ALIGN_LEFT);
-			labelUint(ctx, m_drawableCount, "Drawbles");
+			ImGui::SetWindowPos(Vec2(5.0f, 5.0f));
+			ImGui::SetWindowSize(Vec2(230.0f, 450.0f));
+
+			ImGui::Text("Time:");
+			labelTime(m_frameTime.get(flush), "Total frame");
+			labelTime(m_renderTime.get(flush) - m_lightBinTime.get(flush), "Renderer");
+			labelTime(m_lightBinTime.get(false), "Light bin");
+			labelTime(m_sceneUpdateTime.get(flush), "Scene update");
+			labelTime(m_visTestsTime.get(flush), "Visibility");
+			labelTime(m_physicsTime.get(flush), "Physics");
+
+			ImGui::Text("----");
+			ImGui::Text("Memory:");
+			labelBytes(m_allocatedCpuMem, "Total CPU");
+			labelUint(m_allocCount, "Total allocations");
+			labelUint(m_freeCount, "Total frees");
+			labelBytes(m_vkCpuMem, "Vulkan CPU");
+			labelBytes(m_vkGpuMem, "Vulkan GPU");
+
+			ImGui::Text("----");
+			ImGui::Text("Vulkan:");
+			labelUint(m_vkCmdbCount, "Cmd buffers");
+
+			ImGui::Text("----");
+			ImGui::Text("Other:");
+			labelUint(m_drawableCount, "Drawbles");
 		}
 
-		nk_style_pop_style_item(ctx);
-		nk_end(ctx);
+		ImGui::End();
+		ImGui::GetStyle().Colors[ImGuiCol_WindowBg] = oldWindowColor;
+
 		canvas->popFont();
 	}
 
-	void labelTime(nk_context* ctx, Second val, CString name)
+	void labelTime(Second val, CString name)
 	{
-		StringAuto timestamp(getAllocator());
-		timestamp.sprintf("%s: %fms", name.cstr(), val * 1000.0);
-		nk_label(ctx, timestamp.cstr(), NK_TEXT_ALIGN_LEFT);
+		ImGui::Text("%s: %fms", name.cstr(), val * 1000.0);
 	}
 
-	void labelBytes(nk_context* ctx, PtrSize val, CString name)
+	void labelBytes(PtrSize val, CString name)
 	{
 		U gb, mb, kb, b;
 
@@ -185,14 +184,12 @@ public:
 		{
 			timestamp.sprintf("%s: %4u", name.cstr(), b);
 		}
-		nk_label(ctx, timestamp.cstr(), NK_TEXT_ALIGN_LEFT);
+		ImGui::TextUnformatted(timestamp.cstr());
 	}
 
-	void labelUint(nk_context* ctx, U64 val, CString name)
+	void labelUint(U64 val, CString name)
 	{
-		StringAuto timestamp(getAllocator());
-		timestamp.sprintf("%s: %llu", name.cstr(), val);
-		nk_label(ctx, timestamp.cstr(), NK_TEXT_ALIGN_LEFT);
+		ImGui::Text("%s: %lu", name.cstr(), val);
 	}
 };
 

+ 3 - 1
src/anki/renderer/UiStage.cpp

@@ -8,6 +8,7 @@
 #include <anki/renderer/RenderQueue.h>
 #include <anki/ui/Font.h>
 #include <anki/ui/UiManager.h>
+#include <anki/core/Trace.h>
 
 namespace anki
 {
@@ -38,6 +39,8 @@ void UiStage::draw(RenderingContext& ctx, CommandBufferPtr& cmdb)
 		return;
 	}
 
+	ANKI_TRACE_SCOPED_EVENT(UI_RENDER);
+
 	m_canvas->handleInput();
 	m_canvas->beginBuilding();
 
@@ -46,7 +49,6 @@ void UiStage::draw(RenderingContext& ctx, CommandBufferPtr& cmdb)
 		el.m_drawCallback(m_canvas, el.m_userData);
 	}
 
-	m_canvas->endBuilding();
 	m_canvas->appendToCommandBuffer(cmdb);
 
 	// UI messes with the state, restore it

+ 157 - 177
src/anki/ui/Canvas.cpp

@@ -20,24 +20,20 @@ Canvas::Canvas(UiManager* manager)
 
 Canvas::~Canvas()
 {
-	nk_free(&m_nkCtx);
-	nk_buffer_free(&m_nkCmdsBuff);
+	if(m_imCtx)
+	{
+		setImAllocator();
+		ImGui::DestroyContext(m_imCtx);
+		unsetImAllocator();
+	}
 }
 
 Error Canvas::init(FontPtr font, U32 fontHeight, U32 width, U32 height)
 {
 	m_font = font;
+	m_dfltFontHeight = fontHeight;
 	resize(width, height);
 
-	nk_allocator alloc = makeNkAllocator(&getAllocator().getMemoryPool());
-	if(!nk_init(&m_nkCtx, &alloc, &m_font->getFont(fontHeight)))
-	{
-		ANKI_UI_LOGE("nk_init() failed");
-		return Error::FUNCTION_FAILED;
-	}
-
-	nk_buffer_init(&m_nkCmdsBuff, &alloc, 1_KB);
-
 	// Create program
 	ANKI_CHECK(m_manager->getResourceManager().loadResource("shaders/Ui.glslp", m_prog));
 	const ShaderProgramResourceVariant* variant;
@@ -50,224 +46,208 @@ Error Canvas::init(FontPtr font, U32 fontHeight, U32 width, U32 height)
 		m_grProgs[i] = variant->getProgram();
 	}
 
-	// Other
-	m_stackAlloc = StackAllocator<U8>(getAllocator().getMemoryPool().getAllocationCallback(),
-		getAllocator().getMemoryPool().getAllocationCallbackUserData(),
-		512_B);
-
+	// Sampler
 	SamplerInitInfo samplerInit("Canvas");
 	samplerInit.m_minMagFilter = SamplingFilter::LINEAR;
 	samplerInit.m_mipmapFilter = SamplingFilter::LINEAR;
 	samplerInit.m_addressing = SamplingAddressing::REPEAT;
 	m_sampler = m_manager->getGrManager().newSampler(samplerInit);
 
-	return Error::NONE;
-}
+	// Allocator
+	m_stackAlloc = StackAllocator<U8>(getAllocator().getMemoryPool().getAllocationCallback(),
+		getAllocator().getMemoryPool().getAllocationCallbackUserData(),
+		512_B);
 
-void Canvas::reset()
-{
-	m_references.destroy(m_stackAlloc);
-	m_stackAlloc.getMemoryPool().reset();
+	// Create the context
+	setImAllocator();
+	m_imCtx = ImGui::CreateContext(font->getImFontAtlas());
+	ImGui::SetCurrentContext(m_imCtx);
+	ImGui::GetIO().IniFilename = nullptr;
+	ImGui::GetIO().LogFilename = nullptr;
+	ImGui::StyleColorsLight();
+	ImGui::SetCurrentContext(nullptr);
+	unsetImAllocator();
 
-	nk_clear(&m_nkCtx);
+	return Error::NONE;
 }
 
 void Canvas::handleInput()
 {
-	// Start
 	const Input& in = m_manager->getInput();
-	nk_input_begin(&m_nkCtx);
 
-	Array<U32, 4> viewport = {{0, 0, m_width, m_height}};
+	// Begin
+	setImAllocator();
+	ImGui::SetCurrentContext(m_imCtx);
+	ImGuiIO& io = ImGui::GetIO();
 
 	// Handle mouse
+	Array<U32, 4> viewport = {{0, 0, m_width, m_height}};
 	Vec2 mousePosf = in.getMousePosition() / 2.0f + 0.5f;
 	mousePosf.y() = 1.0f - mousePosf.y();
-	UVec2 mousePos(mousePosf.x() * viewport[2], mousePosf.y() * viewport[3]);
+	const UVec2 mousePos(mousePosf.x() * viewport[2], mousePosf.y() * viewport[3]);
 
-	nk_input_motion(&m_nkCtx, mousePos.x(), mousePos.y());
-	nk_input_button(&m_nkCtx, NK_BUTTON_LEFT, mousePos.x(), mousePos.y(), in.getMouseButton(MouseButton::LEFT) > 1);
-	nk_input_button(&m_nkCtx, NK_BUTTON_RIGHT, mousePos.x(), mousePos.y(), in.getMouseButton(MouseButton::RIGHT) > 1);
+	io.MousePos.x = mousePos.x();
+	io.MousePos.y = mousePos.y();
 
-	// Done
-	nk_input_end(&m_nkCtx);
+	io.MouseClicked[0] = in.getMouseButton(MouseButton::LEFT) == 1;
+	io.MouseDown[0] = in.getMouseButton(MouseButton::LEFT) > 0;
+
+	// End
+	ImGui::SetCurrentContext(nullptr);
+	unsetImAllocator();
 }
 
 void Canvas::beginBuilding()
 {
-#if ANKI_EXTRA_CHECKS
-	ANKI_ASSERT(!m_building);
-	m_building = true;
-#endif
-}
+	setImAllocator();
+	ImGui::SetCurrentContext(m_imCtx);
 
-void Canvas::endBuilding()
-{
-#if ANKI_EXTRA_CHECKS
-	ANKI_ASSERT(m_building);
-	m_building = false;
-#endif
+	ImGuiIO& io = ImGui::GetIO();
+	io.DeltaTime = 1.0f / 60.0f;
+	io.DisplaySize = ImVec2(m_width, m_height);
+
+	ImGui::NewFrame();
+	ImGui::PushFont(&m_font->getImFont(m_dfltFontHeight));
 }
 
 void Canvas::pushFont(const FontPtr& font, U32 fontHeight)
 {
-	ANKI_ASSERT(m_building);
 	m_references.pushBack(m_stackAlloc, IntrusivePtr<UiObject>(const_cast<Font*>(font.get())));
-	nk_style_push_font(&m_nkCtx, &font->getFont(fontHeight));
+	ImGui::PushFont(&font->getImFont(fontHeight));
 }
 
 void Canvas::appendToCommandBuffer(CommandBufferPtr cmdb)
 {
-	Array<U32, 4> viewport = {{0, 0, m_width, m_height}};
+	appendToCommandBufferInternal(cmdb);
 
-	//
-	// Allocate vertex data
-	//
-	struct Vert
-	{
-		Vec2 m_pos;
-		Vec2 m_uv;
-		Array<U8, 4> m_col;
-	};
-
-	static const struct nk_draw_vertex_layout_element VERT_LAYOUT[] = {
-		{NK_VERTEX_POSITION, NK_FORMAT_FLOAT, NK_OFFSETOF(Vert, m_pos)},
-		{NK_VERTEX_TEXCOORD, NK_FORMAT_FLOAT, NK_OFFSETOF(Vert, m_uv)},
-		{NK_VERTEX_COLOR, NK_FORMAT_R8G8B8A8, NK_OFFSETOF(Vert, m_col)},
-		{NK_VERTEX_LAYOUT_END}};
-
-	struct GpuAllocCtx
-	{
-		StagingGpuMemoryManager* m_alloc;
-		StagingGpuMemoryToken m_token;
-	};
-
-	auto allocVertDataCallback = [](nk_handle handle, void* old, nk_size size) -> void* {
-		GpuAllocCtx* ctx = static_cast<GpuAllocCtx*>(handle.ptr);
-		void* out = ctx->m_alloc->allocateFrame(size, StagingGpuMemoryType::VERTEX, ctx->m_token);
-		ANKI_ASSERT(out);
-		return out;
-	};
-
-	auto freeVertDataCallback = [](nk_handle, void*) -> void {
-		// Do nothing
-	};
-
-	GpuAllocCtx idxCtx;
-	idxCtx.m_alloc = &m_manager->getStagingGpuMemoryManager();
-	nk_allocator idxAlloc;
-	idxAlloc.userdata.ptr = &idxCtx;
-	idxAlloc.alloc = allocVertDataCallback;
-	idxAlloc.free = freeVertDataCallback;
-
-	nk_buffer idxBuff = {};
-	nk_buffer_init(&idxBuff, &idxAlloc, 1_KB);
-
-	GpuAllocCtx vertCtx;
-	vertCtx.m_alloc = &m_manager->getStagingGpuMemoryManager();
-	nk_allocator vertAlloc;
-	vertAlloc.userdata.ptr = &vertCtx;
-	vertAlloc.alloc = allocVertDataCallback;
-	vertAlloc.free = freeVertDataCallback;
-
-	nk_buffer vertBuff = {};
-	nk_buffer_init(&vertBuff, &vertAlloc, 1_KB);
-
-	nk_convert_config config = {};
-	config.vertex_layout = VERT_LAYOUT;
-	config.vertex_size = sizeof(Vert);
-	config.vertex_alignment = NK_ALIGNOF(Vert);
-	config.null.texture.ptr = nullptr;
-	config.null.uv = {0, 0};
-	config.circle_segment_count = 22;
-	config.curve_segment_count = 22;
-	config.arc_segment_count = 22;
-	config.global_alpha = 1.0f;
-	config.shape_AA = NK_ANTI_ALIASING_OFF;
-	config.line_AA = NK_ANTI_ALIASING_OFF;
-
-	nk_convert(&m_nkCtx, &m_nkCmdsBuff, &vertBuff, &idxBuff, &config);
-
-	//
-	// Draw
-	//
-
-	// Uniforms
-	StagingGpuMemoryToken uboToken;
-	Vec4* trf = static_cast<Vec4*>(
-		m_manager->getStagingGpuMemoryManager().allocateFrame(sizeof(Vec4), StagingGpuMemoryType::UNIFORM, uboToken));
-	trf->x() = 2.0f / (viewport[2] - viewport[0]);
-	trf->y() = -2.0f / (viewport[3] - viewport[1]);
-	trf->z() = -1.0f;
-	trf->w() = 1.0f;
-	cmdb->bindUniformBuffer(0, 0, uboToken.m_buffer, uboToken.m_offset, uboToken.m_range);
-
-	// Vert & idx buffers
-	cmdb->bindVertexBuffer(0, vertCtx.m_token.m_buffer, vertCtx.m_token.m_offset, sizeof(Vert));
-	cmdb->setVertexAttribute(0, 0, Format::R32G32_SFLOAT, 0);
-	cmdb->setVertexAttribute(1, 0, Format::R8G8B8A8_UNORM, sizeof(Vec2) * 2);
-	cmdb->setVertexAttribute(2, 0, Format::R32G32_SFLOAT, sizeof(Vec2));
-
-	cmdb->bindIndexBuffer(idxCtx.m_token.m_buffer, idxCtx.m_token.m_offset, IndexType::U16);
+	// Done
+	ImGui::SetCurrentContext(nullptr);
 
-	cmdb->setViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
+	m_references.destroy(m_stackAlloc);
+	m_stackAlloc.getMemoryPool().reset();
+}
 
-	// Prog & tex
-	ShaderProgramPtr boundProg;
+void Canvas::appendToCommandBufferInternal(CommandBufferPtr& cmdb)
+{
+	ImGui::PopFont();
+	ImGui::Render();
+	ImDrawData& drawData = *ImGui::GetDrawData();
 
-	const nk_draw_command* cmd = nullptr;
-	U offset = 0;
-	nk_draw_foreach(cmd, &m_nkCtx, &m_nkCmdsBuff)
+	// Allocate index and vertex buffers
+	StagingGpuMemoryToken vertsToken, indicesToken;
 	{
-		if(!cmd->elem_count)
-		{
-			continue;
-		}
+		const U32 verticesSize = drawData.TotalVtxCount * sizeof(ImDrawVert);
+		const U32 indicesSize = drawData.TotalIdxCount * sizeof(ImDrawIdx);
 
-		// Set texture and program
-		ShaderProgramPtr progToBind;
-		if(cmd->texture.ptr == nullptr)
+		if(verticesSize == 0 || indicesSize == 0)
 		{
-			progToBind = m_grProgs[NO_TEX];
+			return;
 		}
-		else if(ptrToNumber(cmd->texture.ptr) & FONT_TEXTURE_MASK)
-		{
-			progToBind = m_grProgs[RGBA_TEX];
 
-			TextureView* t = numberToPtr<TextureView*>(ptrToNumber(cmd->texture.ptr) & ~FONT_TEXTURE_MASK);
-			cmdb->bindSampler(0, 1, m_sampler);
-			cmdb->bindTexture(0, 2, TextureViewPtr(t), TextureUsageBit::SAMPLED_FRAGMENT);
-		}
-		else
-		{
-			ANKI_ASSERT(!"TODO");
-		}
+		ImDrawVert* verts = static_cast<ImDrawVert*>(m_manager->getStagingGpuMemoryManager().allocateFrame(
+			verticesSize, StagingGpuMemoryType::VERTEX, vertsToken));
+		ImDrawIdx* indices = static_cast<ImDrawIdx*>(m_manager->getStagingGpuMemoryManager().allocateFrame(
+			indicesSize, StagingGpuMemoryType::VERTEX, indicesToken));
 
-		if(boundProg != progToBind)
+		for(I n = 0; n < drawData.CmdListsCount; ++n)
 		{
-			cmdb->bindShaderProgram(progToBind);
-			boundProg = progToBind;
+			const ImDrawList& cmdList = *drawData.CmdLists[n];
+			memcpy(verts, cmdList.VtxBuffer.Data, cmdList.VtxBuffer.Size * sizeof(ImDrawVert));
+			memcpy(indices, cmdList.IdxBuffer.Data, cmdList.IdxBuffer.Size * sizeof(ImDrawIdx));
+			verts += cmdList.VtxBuffer.Size;
+			indices += cmdList.IdxBuffer.Size;
 		}
+	}
+
+	cmdb->setBlendFactors(0, BlendFactor::SRC_ALPHA, BlendFactor::ONE_MINUS_SRC_ALPHA);
+	cmdb->setCullMode(FaceSelectionBit::FRONT);
 
-		// Other state
-		cmdb->setBlendFactors(0, BlendFactor::SRC_ALPHA, BlendFactor::ONE_MINUS_SRC_ALPHA);
-		cmdb->setDepthWrite(false);
-		cmdb->setDepthCompareOperation(CompareOperation::ALWAYS);
-		cmdb->setCullMode(FaceSelectionBit::FRONT);
+	const U fbWidth = drawData.DisplaySize.x * drawData.FramebufferScale.x;
+	const U fbHeight = drawData.DisplaySize.y * drawData.FramebufferScale.y;
+	cmdb->setViewport(0, 0, fbWidth, fbHeight);
 
-		// TODO set scissor
+	cmdb->bindVertexBuffer(0, vertsToken.m_buffer, vertsToken.m_offset, sizeof(ImDrawVert));
+	cmdb->setVertexAttribute(0, 0, Format::R32G32_SFLOAT, 0);
+	cmdb->setVertexAttribute(1, 0, Format::R8G8B8A8_UNORM, sizeof(Vec2) * 2);
+	cmdb->setVertexAttribute(2, 0, Format::R32G32_SFLOAT, sizeof(Vec2));
 
-		// Draw
-		cmdb->drawElements(PrimitiveTopology::TRIANGLES, cmd->elem_count, 1, offset);
+	cmdb->bindIndexBuffer(indicesToken.m_buffer, indicesToken.m_offset, IndexType::U16);
 
-		// Advance
-		offset += cmd->elem_count;
+	cmdb->bindSampler(0, 0, m_sampler);
+
+	// Will project scissor/clipping rectangles into framebuffer space
+	const Vec2 clipOff = drawData.DisplayPos; // (0,0) unless using multi-viewports
+	const Vec2 clipScale = drawData.FramebufferScale; // (1,1) unless using retina display which are often (2,2)
+
+	// Render
+	U vertOffset = 0;
+	U idxOffset = 0;
+	for(I n = 0; n < drawData.CmdListsCount; n++)
+	{
+		const ImDrawList& cmdList = *drawData.CmdLists[n];
+		for(I i = 0; i < cmdList.CmdBuffer.Size; i++)
+		{
+			const ImDrawCmd& pcmd = cmdList.CmdBuffer[i];
+			if(pcmd.UserCallback)
+			{
+				// User callback (registered via ImDrawList::AddCallback)
+				pcmd.UserCallback(&cmdList, &pcmd);
+			}
+			else
+			{
+				// Project scissor/clipping rectangles into framebuffer space. Flip Y
+				Vec4 clipRect;
+				clipRect.x() = (pcmd.ClipRect.x - clipOff.x()) * clipScale.x();
+				clipRect.y() = (fbHeight - pcmd.ClipRect.w - clipOff.y()) * clipScale.y();
+				clipRect.z() = (pcmd.ClipRect.z - clipOff.x()) * clipScale.x();
+				clipRect.w() = (fbHeight - pcmd.ClipRect.y - clipOff.y()) * clipScale.y();
+
+				if(clipRect.x() < fbWidth && clipRect.y() < fbHeight && clipRect.z() >= 0.0f && clipRect.w() >= 0.0f)
+				{
+					// Negative offsets are illegal for vkCmdSetScissor
+					if(clipRect.x() < 0.0f)
+					{
+						clipRect.x() = 0.0f;
+					}
+					if(clipRect.y() < 0.0f)
+					{
+						clipRect.y() = 0.0f;
+					}
+
+					// Apply scissor/clipping rectangle
+					cmdb->setScissor(
+						clipRect.x(), clipRect.y(), clipRect.z() - clipRect.x(), clipRect.w() - clipRect.y());
+
+					if(pcmd.TextureId)
+					{
+						cmdb->bindShaderProgram(m_grProgs[RGBA_TEX]);
+
+						TextureView* view = static_cast<TextureView*>(pcmd.TextureId);
+						cmdb->bindTexture(0, 1, TextureViewPtr(view), TextureUsageBit::SAMPLED_FRAGMENT);
+					}
+					else
+					{
+						cmdb->bindShaderProgram(m_grProgs[NO_TEX]);
+					}
+
+					// Push constants. TODO: Set them once
+					Vec4 transform;
+					transform.x() = 2.0f / drawData.DisplaySize.x;
+					transform.y() = -2.0f / drawData.DisplaySize.y;
+					transform.z() = (drawData.DisplayPos.x / drawData.DisplaySize.x) * 2.0f - 1.0f;
+					transform.w() = -((drawData.DisplayPos.y / drawData.DisplaySize.y) * 2.0f - 1.0f);
+					cmdb->setPushConstants(&transform, sizeof(transform));
+
+					// Draw
+					cmdb->drawElements(PrimitiveTopology::TRIANGLES, pcmd.ElemCount, 1, idxOffset, vertOffset);
+				}
+			}
+			idxOffset += pcmd.ElemCount;
+		}
+		vertOffset += cmdList.VtxBuffer.Size;
 	}
 
-	//
-	// Done
-	//
-	reset();
+	cmdb->setBlendFactors(0, BlendFactor::ONE, BlendFactor::ZERO);
 }
 
 } // end namespace anki

+ 9 - 22
src/anki/ui/Canvas.h

@@ -30,14 +30,6 @@ public:
 		return m_font;
 	}
 
-	nk_context& getNkContext()
-	{
-		return m_nkCtx;
-	}
-
-	/// Handle input.
-	virtual void handleInput();
-
 	/// Resize canvas.
 	void resize(U32 width, U32 height)
 	{
@@ -46,30 +38,29 @@ public:
 		m_height = height;
 	}
 
-	/// @name building commands
+	/// @name building commands. The order matters.
 	/// @{
 
+	/// Handle input.
+	virtual void handleInput();
+
 	/// Begin building the UI.
 	void beginBuilding();
 
-	/// End building.
-	void endBuilding();
-
 	void pushFont(const FontPtr& font, U32 fontHeight);
 
 	void popFont()
 	{
-		ANKI_ASSERT(m_building);
-		nk_style_pop_font(&m_nkCtx);
+		ImGui::PopFont();
 	}
-	/// @}
 
 	void appendToCommandBuffer(CommandBufferPtr cmdb);
+	/// @}
 
 private:
 	FontPtr m_font;
-	nk_context m_nkCtx = {};
-	nk_buffer m_nkCmdsBuff = {};
+	U32 m_dfltFontHeight = 0;
+	ImGuiContext* m_imCtx = nullptr;
 	U32 m_width;
 	U32 m_height;
 
@@ -88,11 +79,7 @@ private:
 
 	List<IntrusivePtr<UiObject>> m_references;
 
-#if ANKI_EXTRA_CHECKS
-	Bool m_building = false;
-#endif
-
-	void reset();
+	void appendToCommandBufferInternal(CommandBufferPtr& cmdb);
 };
 /// @}
 

+ 0 - 34
src/anki/ui/Common.h

@@ -5,8 +5,6 @@
 
 #pragma once
 
-#include <anki/ui/NuklearConfig.h>
-
 // Include ImGUI
 #include <anki/ui/ImGuiConfig.h>
 #include <imgui/imgui.h>
@@ -36,39 +34,7 @@ using UiAllocator = HeapAllocator<U8>;
 ANKI_UI_OBJECT_FW(Font)
 ANKI_UI_OBJECT_FW(Canvas)
 ANKI_UI_OBJECT_FW(UiImmediateModeBuilder)
-
 #undef ANKI_UI_OBJECT
-
-const PtrSize FONT_TEXTURE_MASK = 1llu << (sizeof(PtrSize) * 8llu - 1llu);
-
-/// Initialize a nk_allocator.
-template<typename TMemPool>
-inline nk_allocator makeNkAllocator(TMemPool* alloc)
-{
-	nk_allocator nkAlloc;
-	nkAlloc.userdata.ptr = alloc;
-
-	auto allocCallback = [](nk_handle handle, void*, nk_size size) -> void* {
-		ANKI_ASSERT(handle.ptr);
-		TMemPool* al = static_cast<TMemPool*>(handle.ptr);
-		return al->allocate(size, 16);
-	};
-
-	nkAlloc.alloc = allocCallback;
-
-	auto freeCallback = [](nk_handle handle, void* ptr) -> void {
-		ANKI_ASSERT(handle.ptr);
-		if(ptr)
-		{
-			TMemPool* al = static_cast<TMemPool*>(handle.ptr);
-			al->free(ptr);
-		}
-	};
-
-	nkAlloc.free = freeCallback;
-
-	return nkAlloc;
-}
 /// @}
 
 } // end namespace anki

+ 25 - 22
src/anki/ui/Font.cpp

@@ -17,50 +17,52 @@ namespace anki
 
 Font::~Font()
 {
-	nk_font_atlas_clear(&m_atlas);
+	setImAllocator();
+	m_imFontAtlas.destroy();
+	unsetImAllocator();
+
 	m_fonts.destroy(getAllocator());
+	m_fontData.destroy(getAllocator());
 }
 
 Error Font::init(const CString& filename, const std::initializer_list<U32>& fontHeights)
 {
+	setImAllocator();
+	m_imFontAtlas.init();
+
 	// Load font in memory
 	ResourceFilePtr file;
 	ANKI_CHECK(m_manager->getResourceManager().getFilesystem().openFile(filename, file));
-	DynamicArrayAuto<U8> fontData(getAllocator());
-	fontData.create(file->getSize());
-	ANKI_CHECK(file->read(&fontData[0], file->getSize()));
+	m_fontData.create(getAllocator(), file->getSize());
+	ANKI_CHECK(file->read(&m_fontData[0], file->getSize()));
 
 	m_fonts.create(getAllocator(), fontHeights.size());
 
 	// Bake font
-	nk_allocator nkAlloc = makeNkAllocator(&getAllocator().getMemoryPool());
-	nk_font_atlas_init_custom(&m_atlas, &nkAlloc, &nkAlloc);
-	nk_font_atlas_begin(&m_atlas);
-
+	ImFontConfig cfg;
+	cfg.FontDataOwnedByAtlas = false;
 	U count = 0;
 	for(U32 height : fontHeights)
 	{
-		struct nk_font_config cfg = nk_font_config(height);
-		cfg.oversample_h = 4;
-		cfg.oversample_v = 4;
-		m_fonts[count].m_font = nk_font_atlas_add_from_memory(&m_atlas, &fontData[0], fontData.getSize(), height, &cfg);
+		cfg.SizePixels = height;
+
+		m_fonts[count].m_imFont =
+			m_imFontAtlas->AddFontFromMemoryTTF(&m_fontData[0], m_fontData.getSize(), height, &cfg);
 		m_fonts[count].m_height = height;
 		++count;
 	}
 
-	int width, height;
-	const void* img = nk_font_atlas_bake(&m_atlas, &width, &height, NK_FONT_ATLAS_RGBA32);
+	const Bool ok = m_imFontAtlas->Build();
+	ANKI_ASSERT(ok);
+	(void)ok;
 
 	// Create the texture
+	U8* img;
+	int width, height;
+	m_imFontAtlas->GetTexDataAsRGBA32(&img, &width, &height);
 	createTexture(img, width, height);
 
-	// End building
-	nk_handle texHandle;
-	texHandle.ptr = numberToPtr<void*>(ptrToNumber(m_texView.get()) | FONT_TEXTURE_MASK);
-	nk_font_atlas_end(&m_atlas, texHandle, nullptr);
-
-	nk_font_atlas_cleanup(&m_atlas);
-
+	unsetImAllocator();
 	return Error::NONE;
 }
 
@@ -83,12 +85,13 @@ void Font::createTexture(const void* data, U32 width, U32 height)
 	texInit.m_format = Format::R8G8B8A8_UNORM;
 	texInit.m_usage =
 		TextureUsageBit::TRANSFER_DESTINATION | TextureUsageBit::SAMPLED_FRAGMENT | TextureUsageBit::GENERATE_MIPMAPS;
-	texInit.m_mipmapCount = 4;
+	texInit.m_mipmapCount = 1; // No mips because it creates will appear blurry with trilinear filtering
 
 	m_tex = m_manager->getGrManager().newTexture(texInit);
 
 	// Create the whole texture view
 	m_texView = m_manager->getGrManager().newTextureView(TextureViewInitInfo(m_tex, "Font"));
+	m_imFontAtlas->SetTexID(m_texView.get());
 
 	// Do the copy
 	static const TextureSurfaceInfo surf(0, 0, 0, 0);

+ 21 - 8
src/anki/ui/Font.h

@@ -7,6 +7,7 @@
 
 #include <anki/ui/UiObject.h>
 #include <anki/gr/Texture.h>
+#include <anki/util/ClassWrapper.h>
 #include <initializer_list>
 
 namespace anki
@@ -36,30 +37,42 @@ anki_internal:
 		return m_texView;
 	}
 
-	const nk_user_font& getFont(U32 fontHeight) const
+	const ImFont& getImFont(U32 fontHeight) const
 	{
-		for(const NkFont& f : m_fonts)
+		for(const FontEntry& f : m_fonts)
 		{
 			if(f.m_height == fontHeight)
 			{
-				return f.m_font->handle;
+				return *f.m_imFont;
 			}
 		}
 
 		ANKI_ASSERT(0);
-		return m_fonts[0].m_font->handle;
+		return *m_fonts[0].m_imFont;
+	}
+
+	const ImFont& getFirstImFont() const
+	{
+		return *m_fonts[0].m_imFont;
+	}
+
+	ImFontAtlas* getImFontAtlas()
+	{
+		return m_imFontAtlas.get();
 	}
 
 private:
-	nk_font_atlas m_atlas = {};
+	ClassWrapper<ImFontAtlas> m_imFontAtlas;
+	DynamicArray<U8> m_fontData;
 
-	struct NkFont
+	class FontEntry
 	{
-		nk_font* m_font;
+	public:
+		ImFont* m_imFont;
 		U32 m_height;
 	};
 
-	DynamicArray<NkFont> m_fonts;
+	DynamicArray<FontEntry> m_fonts;
 
 	TexturePtr m_tex;
 	TextureViewPtr m_texView; ///< Whole texture view

+ 46 - 0
src/anki/ui/ImGuiConfig.h

@@ -0,0 +1,46 @@
+// 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/util/Assert.h>
+#include <anki/Math.h>
+
+#define IM_ASSERT(_EXPR) ANKI_ASSERT(_EXPR)
+
+#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS 1
+#define IMGUI_DISABLE_DEFAULT_ALLOCATORS 1
+
+#define IM_VEC2_CLASS_EXTRA \
+	ImVec2(const anki::Vec2& f) \
+	{ \
+		x = f.x(); \
+		y = f.y(); \
+	} \
+	operator anki::Vec2() const \
+	{ \
+		return anki::Vec2(x, y); \
+	}
+
+#define IM_VEC4_CLASS_EXTRA \
+	ImVec4(const anki::Vec4& f) \
+	{ \
+		x = f.x(); \
+		y = f.y(); \
+		z = f.z(); \
+		w = f.w(); \
+	} \
+	operator anki::Vec4() const \
+	{ \
+		return anki::Vec4(x, y, z, w); \
+	}
+
+// TLS context.
+struct ImGuiContext;
+namespace anki
+{
+extern thread_local ImGuiContext* g_imguiTlsCtx;
+} // end namespace anki
+#define GImGui anki::g_imguiTlsCtx

+ 0 - 32
src/anki/ui/NuklearConfig.h

@@ -1,32 +0,0 @@
-// 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/util/Assert.h>
-#include <cstring>
-
-#define NK_INCLUDE_FIXED_TYPES
-#define NK_INCLUDE_STANDARD_VARARGS
-#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
-#define NK_INCLUDE_FONT_BAKING
-// #define NK_INCLUDE_DEFAULT_FONT
-// #define NK_INCLUDE_COMMAND_USERDATA
-// #define NK_BUTTON_TRIGGER_ON_RELEASE
-// #define NK_ZERO_COMMAND_MEMORY
-#define NK_ASSERT ANKI_ASSERT
-// #define NK_BUFFER_DEFAULT_INITIAL_SIZE
-// #define NK_MAX_NUMBER_BUFFER
-// #define NK_INPUT_MAX
-// #define NK_MEMSET
-// #define NK_MEMCPY
-// #define NK_SQRT
-// #define NK_SIN
-// #define NK_COS
-// #define NK_STRTOD
-// #define NK_DTOA
-// #define NK_VSNPRINTF
-#define NK_MEMCPY memcpy
-#include <nuklear/nuklear.h>

+ 0 - 12
src/anki/ui/NuklearImpl.cpp

@@ -1,12 +0,0 @@
-// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
-// All rights reserved.
-// Code licensed under the BSD License.
-// http://www.anki3d.org/LICENSE
-
-#if defined(__GNUC__)
-#	pragma GCC diagnostic push
-#	pragma GCC diagnostic ignored "-Wunused-function"
-#endif
-
-#define NK_IMPLEMENTATION
-#include <anki/ui/NuklearConfig.h>

+ 27 - 0
src/anki/ui/UiObject.h

@@ -32,6 +32,33 @@ public:
 		return m_refcount;
 	}
 
+	/// Set the global IMGUI allocator.
+	void setImAllocator(BaseMemoryPool* pool = nullptr)
+	{
+		pool = (pool) ? pool : &getAllocator().getMemoryPool();
+
+		auto allocCallback = [](size_t size, void* userData) -> void* {
+			BaseMemoryPool* pool = static_cast<BaseMemoryPool*>(userData);
+			return pool->allocate(size, 16);
+		};
+
+		auto freeCallback = [](void* ptr, void* userData) -> void {
+			if(ptr)
+			{
+				BaseMemoryPool* pool = static_cast<BaseMemoryPool*>(userData);
+				pool->free(ptr);
+			}
+		};
+
+		ImGui::SetAllocatorFunctions(allocCallback, freeCallback, pool);
+	}
+
+	/// Unset the global IMGUI allocator.
+	static void unsetImAllocator()
+	{
+		ImGui::SetAllocatorFunctions(nullptr, nullptr, nullptr);
+	}
+
 protected:
 	UiManager* m_manager;
 	Atomic<I32> m_refcount = {0};

+ 26 - 17
tests/ui/Ui.cpp

@@ -18,27 +18,37 @@ class Label : public UiImmediateModeBuilder
 public:
 	using UiImmediateModeBuilder::UiImmediateModeBuilder;
 
+	Bool m_windowInitialized = false;
+	U32 m_buttonClickCount = 0;
+
 	void build(CanvasPtr canvas) final
 	{
-		nk_context* ctx = &canvas->getNkContext();
+		Vec4 oldBackground = ImGui::GetStyle().Colors[ImGuiCol_WindowBg];
+		ImGui::GetStyle().Colors[ImGuiCol_WindowBg].w = 0.8f;
+
+		ImGui::Begin("ImGui Demo", nullptr);
 
-		if(nk_begin(ctx,
-			   "Window name",
-			   nk_rect(10, 10, 200, 500),
-			   NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_SCALABLE | NK_WINDOW_MINIMIZABLE | NK_WINDOW_TITLE))
+		if(!m_windowInitialized)
 		{
-			nk_layout_row_dynamic(ctx, 30, 1);
+			ImGui::SetWindowPos(Vec2(20, 10));
+			ImGui::SetWindowSize(Vec2(200, 500));
+			m_windowInitialized = true;
+		}
 
-			canvas->pushFont(canvas->getDefaultFont(), 10);
-			nk_label(ctx, "Label0", NK_TEXT_ALIGN_LEFT);
-			canvas->popFont();
+		ImGui::Text("Label default size");
 
-			canvas->pushFont(canvas->getDefaultFont(), 60);
-			nk_label(ctx, "Label1", NK_TEXT_ALIGN_LEFT);
-			canvas->popFont();
+		canvas->pushFont(canvas->getDefaultFont(), 30);
+		ImGui::Text("Label size 30");
+		ImGui::PopFont();
+
+		m_buttonClickCount += ImGui::Button("Toggle");
+		if(m_buttonClickCount & 1)
+		{
+			ImGui::Button("Toggled");
 		}
 
-		nk_end(ctx);
+		ImGui::End();
+		ImGui::GetStyle().Colors[ImGuiCol_WindowBg] = oldBackground;
 	}
 };
 
@@ -50,6 +60,7 @@ ANKI_TEST(Ui, Ui)
 	cfg.set("window.debugContext", 0);
 	cfg.set("width", 1024);
 	cfg.set("height", 760);
+	cfg.set("rsrc.dataPaths", "engine_data");
 
 	NativeWindow* win = createWindow(cfg);
 	Input* in = new Input();
@@ -69,11 +80,10 @@ ANKI_TEST(Ui, Ui)
 
 	{
 		FontPtr font;
-		ANKI_TEST_EXPECT_NO_ERR(
-			ui->newInstance(font, "engine_data/UbuntuRegular.ttf", std::initializer_list<U32>{10, 20, 30, 60}));
+		ANKI_TEST_EXPECT_NO_ERR(ui->newInstance(font, "UbuntuRegular.ttf", std::initializer_list<U32>{10, 20, 30, 60}));
 
 		CanvasPtr canvas;
-		ANKI_TEST_EXPECT_NO_ERR(ui->newInstance(canvas, font, 30, win->getWidth(), win->getHeight()));
+		ANKI_TEST_EXPECT_NO_ERR(ui->newInstance(canvas, font, 20, win->getWidth(), win->getHeight()));
 
 		IntrusivePtr<Label> label;
 		ANKI_TEST_EXPECT_NO_ERR(ui->newInstance(label));
@@ -93,7 +103,6 @@ ANKI_TEST(Ui, Ui)
 
 			canvas->beginBuilding();
 			label->build(canvas);
-			canvas->endBuilding();
 
 			TexturePtr presentTex = gr->acquireNextPresentableTexture();
 			FramebufferPtr fb;

+ 1 - 1
thirdparty

@@ -1 +1 @@
-Subproject commit a9a9dad78989ee9d8f9b00687a9b78852aac2dda
+Subproject commit 37185e4b4f56be869a2e0a759aa317f12a087a67