Cogs.Core
ImguiRenderer.cpp
1#include "ImguiRenderer.h"
2
3#include "../../Context.h"
4#include "../../ViewContext.h"
5
6#include "Input/KeyboardMapping.h"
7#include "Renderer/IRenderer.h"
8#include "Resources/ResourceStore.h"
9
10#include "Rendering/IGraphicsDevice.h"
11#include "Rendering/IContext.h"
12#include "Rendering/IBuffers.h"
13#include "Rendering/ITextures.h"
14#include "Rendering/IEffects.h"
15#include "Rendering/IRenderTargets.h"
16#include "Rendering/ICapabilities.h"
17#include "Rendering/CommandGroupAnnotation.h"
18#include "Services/DPIService.h"
19
20#include "Foundation/Logging/Logger.h"
21#include "Foundation/Platform/Keyboard.h"
22#include "Foundation/Platform/Mouse.h"
23
24#include <set>
25#include "imgui.h"
26#include "imgui_internal.h"
27
28#include <glm/mat4x4.hpp>
29#include <glm/ext/matrix_projection.hpp>
30
31#include <algorithm>
32
33namespace
34{
35 const Cogs::Logging::Log logger = Cogs::Logging::getLogger("ImguiRenderer");
36 std::set<ImGuiContext*> guiContexts;
37
38 struct ImguiConstants
39 {
40 glm::mat4 projectionMatrix;
41 int showAlpha;
42 int texMode;
43 int offset;
44 int output_sRGB;
45 };
46
47 void dummyRenderCallback(const ImDrawList* /*parent_list*/, const ImDrawCmd* /*cmd*/)
48 {
49 LOG_WARNING(logger, "Calling dummyRenderCallback");
50 }
51
52 ImGuiKey CogsKeyToImGuiKey(Cogs::Key key) {
53 switch (key) {
54 case Cogs::Key::A: return ImGuiKey_A;
55 case Cogs::Key::B: return ImGuiKey_B;
56 case Cogs::Key::C: return ImGuiKey_C;
57 case Cogs::Key::D: return ImGuiKey_D;
58 case Cogs::Key::E: return ImGuiKey_E;
59 case Cogs::Key::F: return ImGuiKey_F;
60 case Cogs::Key::G: return ImGuiKey_G;
61 case Cogs::Key::H: return ImGuiKey_H;
62 case Cogs::Key::I: return ImGuiKey_I;
63 case Cogs::Key::J: return ImGuiKey_J;
64 case Cogs::Key::K: return ImGuiKey_K;
65 case Cogs::Key::L: return ImGuiKey_L;
66 case Cogs::Key::M: return ImGuiKey_M;
67 case Cogs::Key::N: return ImGuiKey_N;
68 case Cogs::Key::O: return ImGuiKey_O;
69 case Cogs::Key::P: return ImGuiKey_P;
70 case Cogs::Key::Q: return ImGuiKey_Q;
71 case Cogs::Key::R: return ImGuiKey_R;
72 case Cogs::Key::S: return ImGuiKey_S;
73 case Cogs::Key::T: return ImGuiKey_T;
74 case Cogs::Key::U: return ImGuiKey_U;
75 case Cogs::Key::V: return ImGuiKey_V;
76 case Cogs::Key::W: return ImGuiKey_W;
77 case Cogs::Key::X: return ImGuiKey_X;
78 case Cogs::Key::Y: return ImGuiKey_Y;
79 case Cogs::Key::Z: return ImGuiKey_Z;
80 case Cogs::Key::Zero: return ImGuiKey_0;
81 case Cogs::Key::One: return ImGuiKey_1;
82 case Cogs::Key::Two: return ImGuiKey_2;
83 case Cogs::Key::Three: return ImGuiKey_3;
84 case Cogs::Key::Four: return ImGuiKey_4;
85 case Cogs::Key::Five: return ImGuiKey_5;
86 case Cogs::Key::Six: return ImGuiKey_6;
87 case Cogs::Key::Seven: return ImGuiKey_7;
88 case Cogs::Key::Eight: return ImGuiKey_8;
89 case Cogs::Key::Nine: return ImGuiKey_9;
90 case Cogs::Key::Left: return ImGuiKey_LeftArrow;
91 case Cogs::Key::Right: return ImGuiKey_RightArrow;
92 case Cogs::Key::Up: return ImGuiKey_UpArrow;
93 case Cogs::Key::Down: return ImGuiKey_DownArrow;
94 case Cogs::Key::Shift:
95 case Cogs::Key::LeftShift:
96 case Cogs::Key::RightShift: return ImGuiMod_Shift;
97 case Cogs::Key::Control:
98 case Cogs::Key::LeftControl:
99 case Cogs::Key::RightControl: return ImGuiMod_Ctrl;
100 case Cogs::Key::Alt:
101 case Cogs::Key::LeftAlt:
102 case Cogs::Key::RightAlt: return ImGuiMod_Alt;
103 case Cogs::Key::CapsLock: return ImGuiKey_CapsLock;
104 case Cogs::Key::Tab: return ImGuiKey_Tab;
105 case Cogs::Key::Escape: return ImGuiKey_Escape;
106 case Cogs::Key::Enter: return ImGuiKey_Enter;
107 case Cogs::Key::Space: return ImGuiKey_Space;
108 case Cogs::Key::Insert: return ImGuiKey_Insert;
109 case Cogs::Key::Delete: return ImGuiKey_Delete;
110 case Cogs::Key::Backspace: return ImGuiKey_Backspace;
111 case Cogs::Key::Home: return ImGuiKey_Home;
112 case Cogs::Key::End: return ImGuiKey_End;
113 case Cogs::Key::PageUp: return ImGuiKey_PageUp;
114 case Cogs::Key::PageDown: return ImGuiKey_PageDown;
115 case Cogs::Key::F1: return ImGuiKey_F1;
116 case Cogs::Key::F2: return ImGuiKey_F2;
117 case Cogs::Key::F3: return ImGuiKey_F3;
118 case Cogs::Key::F4: return ImGuiKey_F4;
119 case Cogs::Key::F5: return ImGuiKey_F5;
120 case Cogs::Key::F6: return ImGuiKey_F6;
121 case Cogs::Key::F7: return ImGuiKey_F7;
122 case Cogs::Key::F8: return ImGuiKey_F8;
123 case Cogs::Key::F9: return ImGuiKey_F9;
124 case Cogs::Key::F10: return ImGuiKey_F10;
125 case Cogs::Key::F11: return ImGuiKey_F11;
126 case Cogs::Key::F12: return ImGuiKey_F12;
127 default: return ImGuiKey_None;
128 }
129 }
130
131}
132
133GetClipboardTextFn Cogs::Core::ImguiRenderer::getClipboardTextFn = nullptr;
134SetClipboardTextFn Cogs::Core::ImguiRenderer::setClipboardTextFn = nullptr;
135
136ImDrawCallback Cogs::Core::setGuiMode = dummyRenderCallback;
137
138bool Cogs::Core::ImguiRenderer::initialize(Cogs::Core::Context * context)
139{
140 auto & defaultFont = fontRegistry.fonts[0];
141 fontRegistry.defaultFont = &defaultFont;
142
143 float scaleFactor = context->getDefaultView()->dpiService->getScaleFactor();
144
145 this->context = context;
146 this->device = context->device;
147
148 imguiContext = createGuiContext();
149 ImGui::SetCurrentContext(imguiContext);
150
151 // Default initialize font glyph cache to default font
152 ImGuiIO& io = ImGui::GetIO();
153 fontGlyphBuilder = ImFontGlyphRangesBuilder();
154 // Add at least the default Glyph range so western characters are displayed
155 fontGlyphBuilder.AddRanges(io.Fonts->GetGlyphRangesDefault());
156 fontGlyphBuilder.BuildRanges(&loadedRanges);
157
158#ifdef EMSCRIPTEN
159 defaultFont.load(context, "Fonts/SourceSansPro-Regular.ttf", 20 * scaleFactor, loadedRanges.Data);
160#elif defined(_WIN32)
161 defaultFont.load(context, "C:/Windows/Fonts/segoeui.ttf", 16 * scaleFactor, loadedRanges.Data);
162#elif defined(__linux__)
163 defaultFont.load(context, "/usr/share/fonts/truetype/freefont/FreeSans.ttf", 16 * scaleFactor, loadedRanges.Data);
164#elif defined(__APPLE__)
165 defaultFont.load(context, "/System/Library/Fonts/SFNS.ttf", 16 * scaleFactor, loadedRanges.Data);
166#else
167 assert(false); // TODO
168#endif
169
170 timer = Timer::startNew();
171
172 createResources();
173
174 return true;
175}
176
177void Cogs::Core::ImguiRenderer::cleanup()
178{
179 if (imguiContext) {
180 ImGui::SetCurrentContext(imguiContext);
181
182 ImGui::GetIO().Fonts = nullptr;
183
184 fontRegistry.fonts.clear();
185 fontRegistry.defaultFont = nullptr;
186
187 deleteGuiContext(imguiContext);
188 imguiContext = nullptr;
189
190 fontGlyphBuilder.Clear();
191 }
192}
193
194ImGuiContext* Cogs::Core::ImguiRenderer::createGuiContext() {
195 ImGuiContext* guiContext = ImGui::CreateContext(&fontRegistry.defaultFont->fontAtlas);
196
197 guiContexts.insert(guiContext);
198 ImGui::SetCurrentContext(guiContext);
199
200 ImGuiPlatformIO platformIO = ImGui::GetPlatformIO();
201
202 // NOTE: This is not technically correct. The old clipboard functions take
203 // a void* as their first argument which is meant to be user defined data.
204 // The new clipboard functions take an ImGuiContext instead (from which the
205 // user data can be retrieved). Cogs never used the user data anyway, so
206 // while this should be perfectly safe it is still nevertheless very wrong.
207 if (getClipboardTextFn) {
208 platformIO.Platform_GetClipboardTextFn = reinterpret_cast<const char*(*)(ImGuiContext*)>(getClipboardTextFn);
209 }
210 if (setClipboardTextFn) {
211 platformIO.Platform_SetClipboardTextFn = reinterpret_cast<void(*)(ImGuiContext*, const char*)>(setClipboardTextFn);
212 }
213
214 ImGui::GetStyle().ScaleAllSizes(context->getDefaultView()->dpiService->getScaleFactor());
215
216 return guiContext;
217}
218
219void Cogs::Core::ImguiRenderer::deleteGuiContext(ImGuiContext* guiContext) {
220 auto i = guiContexts.find(guiContext);
221
222 if (i != guiContexts.end()) {
223 guiContexts.erase(i);
224 }
225
226 ImGui::SetCurrentContext(guiContext);
227 ImGui::DestroyContext(guiContext);
228}
229
235 bool active = false;
236
237 for (ImGuiContext* context : guiContexts) {
238 active |= context->IO.WantCaptureMouse;
239 }
240 return active;
241}
242
248 bool active = false;
249
250 for (ImGuiContext* context : guiContexts) {
251 active |= context->IO.WantCaptureKeyboard;
252 }
253 return active;
254}
255
256void Cogs::Core::ImguiRenderer::setClipboardCallbacks(GetClipboardTextFn getter, SetClipboardTextFn setter) {
257 getClipboardTextFn = getter;
258 setClipboardTextFn = setter;
259
260 ImGuiContext* currentContext = ImGui::GetCurrentContext();
261
262 for (ImGuiContext* context : guiContexts) {
263 ImGui::SetCurrentContext(context);
264
265 ImGuiPlatformIO& platformIO = ImGui::GetPlatformIO();
266
267 // NOTE: This is not technically correct. The old clipboard functions take
268 // a void* as their first argument which is meant to be user defined data.
269 // The new clipboard functions take an ImGuiContext instead (from which the
270 // user data can be retrieved). Cogs never used the user data anyway, so
271 // while this should be perfectly safe it is still nevertheless very wrong.
272 platformIO.Platform_GetClipboardTextFn = reinterpret_cast<const char*(*)(ImGuiContext*)>(getClipboardTextFn);
273 platformIO.Platform_SetClipboardTextFn = reinterpret_cast<void(*)(ImGuiContext*, const char*)>(setClipboardTextFn);
274 }
275 ImGui::SetCurrentContext(currentContext);
276}
277
278void Cogs::Core::ImguiRenderer::updateConstantBuffer(IContext * deviceContext, uint32_t mode)
279{
280 this->mode = mode;
281 const float L = 0.0f;
282 const float R = ImGui::GetIO().DisplaySize.x;
283 const float B = ImGui::GetIO().DisplaySize.y;
284 const float T = 0.0f;
285
286 auto M = glm::ortho(L, R, B, T);
287
288 int texMode = 0;
289 int offset = 0;
290 switch (mode & GUI_MODE_TEX_TYPE_MASK)
291 {
292 case GUI_MODE_TEX_TYPE_2D:
293 texMode = 0;
294 break;
295 case GUI_MODE_TEX_TYPE_ARRAY:
296 texMode = 1;
297 offset = mode & GUI_MODE_TEX_OFFSET_MASK;
298 break;
299 case GUI_MODE_TEX_TYPE_CUBE:
300 texMode = 2;
301 offset = mode & GUI_MODE_TEX_OFFSET_MASK;
302 break;
303 case GUI_MODE_TEX_TYPE_2DMS:
304 texMode = 3;
305 break;
306 default:
307 assert(false);
308 break;
309 }
310
311 int showAlpha = 0;
312 switch (mode & GUI_MODE_TEX_CHANNELS_MASK) {
313 case GUI_MODE_TEX_CHANNELS_RGB:
314 showAlpha = 0;
315 break;
316 case GUI_MODE_TEX_CHANNELS_ALPHA:
317 showAlpha = 1;
318 break;
319 case GUI_MODE_TEX_CHANNELS_RED:
320 showAlpha = 2;
321 break;
322 case GUI_MODE_TEX_CHANNELS_111R:
323 showAlpha = 3;
324 break;
325 default:
326 assert(false);
327 break;
328 }
329
330 int output_sRGB = 0;
331 if (context->renderer->getSettings().defaultRenderTargetExpectsSRGB) {
332 output_sRGB = 1;
333 }
334
335 if (HandleIsValid(constantBuffer)) {
336 {
337 MappedBuffer<ImguiConstants> constants(deviceContext, constantBuffer, MapMode::WriteDiscard);
338
339 if (constants) {
340 constants->projectionMatrix = M;
341 constants->showAlpha = showAlpha;
342 constants->texMode = texMode;
343 constants->offset = offset;
344 constants->output_sRGB = output_sRGB;
345 }
346 }
347 deviceContext->setConstantBuffer("ImguiBuffer", constantBuffer);
348 }
349 else {
350 assert(false);
351 }
352}
353
354void Cogs::Core::ImguiRenderer::frame(ImGuiContext* guiContext, ViewContext& view, bool updateio)
355{
356 ImGui::SetCurrentContext(guiContext);
357
358 ImGuiIO& io = ImGui::GetIO();
359
360 if (updateio) {
361 const Cogs::Mouse::State& mouseState = view.refMouse().getState();
362 const std::vector<Cogs::Gesture>& gestures = view.refGestures().getGestures();
363 const Keyboard& keyboard = view.refKeyboard();
364 Cogs::PointerType pointerType = view.refGestures().getPointerType();
365
366 std::memset(io.MouseDown, 0, sizeof(io.MouseDown));
367
368 if (pointerType == Cogs::PointerType::Touch) { // We don't want to interfere with mouse devices
369 view.refGestures().hoverEnable = true;
370 for (const Cogs::Gesture& gesture : gestures) {
371 if (gesture.kind == Cogs::Gesture::Kind::Press) {
372 touchPointerPosition.x = gesture.press.coord.x;
373 touchPointerPosition.y = gesture.press.coord.y;
374 touchPointerHeld = true;
375 }
376 else if (gesture.kind == Cogs::Gesture::Kind::Tap) {
377 touchPointerPosition.x = gesture.tap.coord.x;
378 touchPointerPosition.y = gesture.tap.coord.y;
379 touchPointerHeld = false;
380 }
381 else if (gesture.kind == Cogs::Gesture::Kind::Drag) {
382 touchPointerPosition.x = gesture.drag.currCoord.x;
383 touchPointerPosition.y = gesture.drag.currCoord.y;
384 touchPointerHeld = true;
385 }
386 else if (gesture.kind == Cogs::Gesture::Kind::Hover) {
387 touchPointerPosition.x = gesture.hover.coord.x;
388 touchPointerPosition.y = gesture.hover.coord.y;
389 }
390 }
391 }
392
393 io.MouseDown[0] = mouseState.buttonDown[MouseButton::Left] || touchPointerHeld; // We can only left click with touch pointer
394 io.MouseDown[1] = mouseState.buttonDown[MouseButton::Right];
395 io.MouseDown[2] = mouseState.buttonDown[MouseButton::Middle];
396
397 if (pointerType == Cogs::PointerType::Touch) {
398 io.MousePos.x = static_cast<float>(touchPointerPosition.x);
399 io.MousePos.y = static_cast<float>(touchPointerPosition.y);
400 }
401 else {
402 io.MousePos.x = static_cast<float>(mouseState.position.x);
403 io.MousePos.y = static_cast<float>(mouseState.position.y);
404 }
405
406 io.MouseWheel = mouseState.wheel != 0 ? (mouseState.wheel > 0 ? 1.0f : -1.0f) : 0;
407
408 for (const Keyboard::Event& e: keyboard.getEvents()) {
409 if ((e.type == Keyboard::Event::Type::Press) || (e.type == Keyboard::Event::Type::Release)) {
410 Key key = std::get<Key>(e.data);
411 io.AddKeyEvent(CogsKeyToImGuiKey(key), e.type == Keyboard::Event::Type::Press);
412 }
413 }
414 io.AddInputCharactersUTF8(keyboard.getState().chars.c_str());
415 }
416 io.DisplaySize = view.getSize();
417
418 // Ensure time since last render positive.
419 io.DeltaTime = std::max(static_cast<float>(timer.elapsedSeconds()), 0.000001f);
420 timer.start();
421
422 if (updateLoadedRanges) {
423 ImVector<ImWchar> requiredRanges;
424
425 fontGlyphBuilder.BuildRanges(&requiredRanges);
426
427 if (!std::equal(loadedRanges.begin(), loadedRanges.end(), requiredRanges.begin(), requiredRanges.end())) {
428 for (auto& [_, font] : fontRegistry.fonts) {
429 font.reloadWithNewGlyphs(context, requiredRanges.Data);
430 }
431 loadedRanges = requiredRanges;
432 }
433 updateLoadedRanges = false;
434 }
435
436 ImGui::NewFrame();
437}
438
439void Cogs::Core::ImguiRenderer::render()
440{
441 auto deviceContext = device->getImmediateContext();
442
443 CommandGroupAnnotation commandGroup(deviceContext, "Imgui::Render");
444
445 ImGui::Render();
446
447 auto drawData = ImGui::GetDrawData();
448
449 auto buffers = device->getBuffers();
450
451 // Create and grow vertex/index buffers if needed
452 if (!HandleIsValid(vertexBuffer) || vertexBufferSize < drawData->TotalVtxCount) {
453 if (HandleIsValid(vertexBuffer)) { buffers->releaseVertexBuffer(vertexBuffer); vertexBuffer = VertexBufferHandle::NoHandle; }
454 vertexBufferSize = drawData->TotalVtxCount + 5000;
455
456 vertexBuffer = buffers->loadVertexBuffer(nullptr, vertexBufferSize, format);
457 }
458
459 if (!HandleIsValid(indexBuffer) || indexBufferSize < drawData->TotalIdxCount) {
460 if (HandleIsValid(indexBuffer)) { buffers->releaseIndexBuffer(indexBuffer); indexBuffer = IndexBufferHandle::NoHandle; }
461 indexBufferSize = drawData->TotalIdxCount + 10000;
462
463 indexBuffer = buffers->loadIndexBuffer(nullptr, indexBufferSize, sizeof(ImDrawIdx));
464 }
465
466 if (device->getType() == GraphicsDeviceType::OpenGLES30) {
467
468 // Use updateSubBuffer since emscripten doesn't support memory mapping without faking it
469 // through an extra buffer. Avoid that copy.
470
471 size_t vtx_offset = 0;
472 for (int n = 0; n < drawData->CmdListsCount; n++) {
473 const ImDrawList* commandList = drawData->CmdLists[n];
474 deviceContext->updateSubBuffer(vertexBuffer, vtx_offset, sizeof(ImDrawVert) * commandList->VtxBuffer.size(), &commandList->VtxBuffer[0]);
475 vtx_offset += sizeof(ImDrawVert) * commandList->VtxBuffer.size();
476 }
477
478 size_t idx_offset = 0;
479 for (int n = 0; n < drawData->CmdListsCount; n++) {
480 const ImDrawList* commandList = drawData->CmdLists[n];
481 deviceContext->updateSubBuffer(indexBuffer, idx_offset, sizeof(ImDrawIdx) * commandList->IdxBuffer.size(), &commandList->IdxBuffer[0]);
482 idx_offset += sizeof(ImDrawIdx) * commandList->IdxBuffer.size();
483 }
484
485 }
486 else {
487 ImDrawVert* vtx_dst = (ImDrawVert*)deviceContext->map(vertexBuffer, MapMode::WriteDiscard);
488 ImDrawIdx* idx_dst = (ImDrawIdx*)deviceContext->map(indexBuffer, MapMode::WriteDiscard);
489
490 if (vtx_dst && idx_dst) {
491 for (int n = 0; n < drawData->CmdListsCount; n++) {
492 const auto commandList = drawData->CmdLists[n];
493
494 memcpy(vtx_dst, &commandList->VtxBuffer[0], commandList->VtxBuffer.size() * sizeof(ImDrawVert));
495 memcpy(idx_dst, &commandList->IdxBuffer[0], commandList->IdxBuffer.size() * sizeof(ImDrawIdx));
496
497 vtx_dst += commandList->VtxBuffer.size();
498 idx_dst += commandList->IdxBuffer.size();
499 }
500 }
501 deviceContext->unmap(vertexBuffer);
502 deviceContext->unmap(indexBuffer);
503 }
504
505 deviceContext->setViewport(0, 0, ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y);
506 deviceContext->clearDepth(context->renderer->getClearDepth());
507
508 deviceContext->setEffect(effect);
509 const uint32_t strides[] = { sizeof(ImDrawVert) };
510 deviceContext->setVertexBuffers(&vertexBuffer, 1, strides, nullptr);
511 deviceContext->setIndexBuffer(indexBuffer, sizeof(ImDrawIdx));
512 if (HandleIsValid(inputLayout)) deviceContext->setInputLayout(inputLayout);
513 updateConstantBuffer(deviceContext, GUI_MODE_DEFAULT);
514
515 deviceContext->setBlendState(blendState);
516 deviceContext->setRasterizerState(rasterizerState);
517 deviceContext->setDepthStencilState(depthState);
518
519 switch (mode & GUI_MODE_TEX_TYPE_MASK)
520 {
521 case GUI_MODE_TEX_TYPE_2D:
522 deviceContext->setTexture("texture0", 0, Cogs::TextureHandle::NoHandle);
523 deviceContext->setSamplerState("texture0Sampler", 0, sampler);
524 break;
525 case GUI_MODE_TEX_TYPE_ARRAY:
526 deviceContext->setTexture("texarray", 3, Cogs::TextureHandle::NoHandle);
527 deviceContext->setSamplerState("texarraySampler", 3, sampler);
528 break;
529 case GUI_MODE_TEX_TYPE_CUBE:
530 deviceContext->setTexture("texcube", 2, Cogs::TextureHandle::NoHandle);
531 deviceContext->setSamplerState("texture0Sampler", 2, sampler);
532 break;
533 case GUI_MODE_TEX_TYPE_2DMS:
534 deviceContext->setTexture("texturems", 1, Cogs::TextureHandle::NoHandle);
535 deviceContext->setSamplerState("texture0Sampler", 1, sampler);
536 break;
537 default:
538 assert(false);
539 break;
540 }
541
542 // Render command lists
543 int baseVertex = 0;
544 int baseIndex = 0;
545
546 for (int n = 0; n < drawData->CmdListsCount; n++) {
547 const ImDrawList* commandList = drawData->CmdLists[n];
548
549 for (int commandIndex = 0; commandIndex < commandList->CmdBuffer.size(); commandIndex++) {
550 const ImDrawCmd* pcmd = &commandList->CmdBuffer[commandIndex];
551
552 if (pcmd->UserCallback == setGuiMode) {
553 updateConstantBuffer(deviceContext, (uint32_t)(uint64_t)pcmd->UserCallbackData);
554 }
555 else {
556 if (pcmd->UserCallback) {
557 pcmd->UserCallback(commandList, pcmd);
558 } else {
559 switch (device->getType()) {
562 deviceContext->setScissor(int(pcmd->ClipRect.x),
563 int(ImGui::GetIO().DisplaySize.y) - int(pcmd->ClipRect.w),
564 int(pcmd->ClipRect.z) - int(pcmd->ClipRect.x),
565 int(pcmd->ClipRect.w) - int(pcmd->ClipRect.y));
566
567 break;
568
569 default:
570 deviceContext->setScissor(int(pcmd->ClipRect.x),
571 int(pcmd->ClipRect.y),
572 int(pcmd->ClipRect.z),
573 int(pcmd->ClipRect.w));
574 break;
575 }
576
577 Cogs::TextureHandle textureHandle(pcmd->GetTexID());
578
579 deviceContext->setBlendState(blendState);
580
581 assert(pcmd->ElemCount > 0);
582 switch (mode & GUI_MODE_TEX_TYPE_MASK)
583 {
584 case GUI_MODE_TEX_TYPE_2D:
585 deviceContext->setTexture("texture0", 0, textureHandle);
586 deviceContext->setSamplerState("texture0Sampler", 0, sampler);
587 break;
588 case GUI_MODE_TEX_TYPE_ARRAY:
589 deviceContext->setTexture("texarray", 3, textureHandle);
590 deviceContext->setSamplerState("texture0Sampler", 3, sampler);
591 break;
592 case GUI_MODE_TEX_TYPE_CUBE:
593 deviceContext->setTexture("texcube", 2, textureHandle);
594 deviceContext->setSamplerState("texture0Sampler", 2, sampler);
595 break;
596 case GUI_MODE_TEX_TYPE_2DMS:
597 deviceContext->setTexture("texturems", 1, textureHandle);
598 deviceContext->setSamplerState("texture0Sampler", 1, sampler);
599 break;
600 default:
601 assert(false);
602 break;
603 }
604 deviceContext->drawIndexed(PrimitiveType::TriangleList, baseIndex, pcmd->ElemCount, baseVertex);
605 }
606 }
607
608 baseIndex += pcmd->ElemCount;
609 }
610
611 baseVertex += commandList->VtxBuffer.size();
612 }
613
614 auto & io = ImGui::GetIO();
615
616 io.ClearInputCharacters();
617}
618
619void Cogs::Core::ImguiRenderer::addTextToGlyphBuilder(const char* text)
620{
621 if (text) {
622 fontGlyphBuilder.AddText(text);
623 updateLoadedRanges = true;
624 }
625}
626
627void Cogs::Core::ImguiRenderer::addRangeToGlyphBuilder(const ImWchar* ranges)
628{
629 fontGlyphBuilder.AddRanges(ranges);
630 updateLoadedRanges = true;
631}
632
633void Cogs::Core::ImguiRenderer::style()
634{
635 // FIXME: those should become parameters to the function
636 float satMult = 0.0f;
637 static int hue = 146;
638 static float col_main_sat = satMult * 180.f / 255.f;
639 static float col_main_val = 161.f / 255.f;
640 static float col_area_sat = satMult * 124.f / 255.f;
641 static float col_area_val = 100.f / 255.f;
642 static float col_back_sat = satMult * 59.f / 255.f;
643 static float col_back_val = 40.f / 255.f;
644
645 ImGuiStyle & style = ImGui::GetStyle();
646
647 ImVec4 col_text = ImColor::HSV(hue / 255.f, 20.f / 255.f, 255.f / 255.f);
648 ImVec4 col_main = ImColor::HSV(hue / 255.f, col_main_sat, col_main_val);
649 ImVec4 col_back = ImColor::HSV(hue / 255.f, col_back_sat, col_back_val);
650 ImVec4 col_dark = ImColor::HSV(hue / 255.f, col_back_sat, 55 / 255.f);
651 ImVec4 col_area = ImColor::HSV(hue / 255.f, col_area_sat, col_area_val);
652
653 style.Colors[ImGuiCol_Text] = ImVec4(col_text.x, col_text.y, col_text.z, 1.00f);
654 style.Colors[ImGuiCol_TextDisabled] = ImVec4(col_text.x, col_text.y, col_text.z, 0.58f);
655 style.Colors[ImGuiCol_WindowBg] = ImVec4(col_back.x, col_back.y, col_back.z, 1.00f);
656 style.Colors[ImGuiCol_Border] = ImVec4(col_text.x, col_text.y, col_text.z, 0.30f);
657 style.Colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
658 style.Colors[ImGuiCol_FrameBg] = ImVec4(col_area.x, col_area.y, col_area.z, 1.00f);
659 style.Colors[ImGuiCol_FrameBgHovered] = ImVec4(col_main.x, col_main.y, col_main.z, 0.68f);
660 style.Colors[ImGuiCol_FrameBgActive] = ImVec4(col_main.x, col_main.y, col_main.z, 1.00f);
661 style.Colors[ImGuiCol_TitleBg] = ImVec4(col_main.x, col_main.y, col_main.z, 0.45f);
662 style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(col_main.x, col_main.y, col_main.z, 0.35f);
663 style.Colors[ImGuiCol_TitleBgActive] = ImVec4(col_main.x, col_main.y, col_main.z, 0.78f);
664 style.Colors[ImGuiCol_MenuBarBg] = ImVec4(col_back.x, col_back.y, col_back.z, 1.00f);
665 style.Colors[ImGuiCol_ScrollbarBg] = ImVec4(col_area.x, col_area.y, col_area.z, 1.00f);
666 style.Colors[ImGuiCol_ScrollbarGrab] = ImVec4(col_main.x, col_main.y, col_main.z, 0.31f);
667 style.Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(col_main.x, col_main.y, col_main.z, 0.78f);
668 style.Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(col_main.x, col_main.y, col_main.z, 1.00f);
669 style.Colors[ImGuiCol_CheckMark] = ImVec4(col_main.x, col_main.y, col_main.z, 0.80f);
670 style.Colors[ImGuiCol_SliderGrab] = ImVec4(col_main.x, col_main.y, col_main.z, 0.24f);
671 style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(col_main.x, col_main.y, col_main.z, 1.00f);
672 style.Colors[ImGuiCol_Button] = ImVec4(col_main.x, col_main.y, col_main.z, 0.44f);
673 style.Colors[ImGuiCol_ButtonHovered] = ImVec4(col_main.x, col_main.y, col_main.z, 0.86f);
674 style.Colors[ImGuiCol_ButtonActive] = ImVec4(col_main.x, col_main.y, col_main.z, 1.00f);
675 style.Colors[ImGuiCol_Header] = ImVec4(col_main.x, col_main.y, col_main.z, 0.76f);
676 style.Colors[ImGuiCol_HeaderHovered] = ImVec4(col_dark.x, col_dark.y, col_dark.z, 0.86f);
677 style.Colors[ImGuiCol_HeaderActive] = ImVec4(col_main.x, col_main.y, col_main.z, 1.00f);
678 style.Colors[ImGuiCol_ResizeGrip] = ImVec4(col_main.x, col_main.y, col_main.z, 0.20f);
679 style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(col_main.x, col_main.y, col_main.z, 0.78f);
680 style.Colors[ImGuiCol_ResizeGripActive] = ImVec4(col_main.x, col_main.y, col_main.z, 1.00f);
681 style.Colors[ImGuiCol_PlotLines] = ImVec4(col_text.x, col_text.y, col_text.z, 0.63f);
682 style.Colors[ImGuiCol_PlotLinesHovered] = ImVec4(col_main.x, col_main.y, col_main.z, 1.00f);
683 style.Colors[ImGuiCol_PlotHistogram] = ImVec4(col_text.x, col_text.y, col_text.z, 0.63f);
684 style.Colors[ImGuiCol_PlotHistogramHovered] = ImVec4(col_main.x, col_main.y, col_main.z, 1.00f);
685 style.Colors[ImGuiCol_TextSelectedBg] = ImVec4(col_main.x, col_main.y, col_main.z, 0.43f);
686 style.Colors[ImGuiCol_PopupBg] = ImVec4(col_back.x, col_back.y, col_back.z, 0.99f);
687 style.Colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f);
688
689 style.WindowRounding = 2.0f;
690 style.WindowBorderSize = 0.0f;
691}
692
693void Cogs::Core::ImguiRenderer::createSampler()
694{
695 SamplerState fs = {};
696 fs.addressModeS = SamplerState::Clamp;
697 fs.addressModeT = SamplerState::Clamp;
699 sampler = device->getTextures()->loadSamplerState(fs);
700}
701
702bool Cogs::Core::ImguiRenderer::createResources()
703{
704 VertexElement elements[] = {
705 {0, DataFormat::X32Y32_FLOAT, ElementSemantic::Position, 0, InputType::VertexData, 0},
706 { 2 * sizeof(float), DataFormat::X32Y32_FLOAT, ElementSemantic::TextureCoordinate, 0, InputType::VertexData, 0 },
707 { 4 * sizeof(float), DataFormat::R8G8B8A8_UNORM, ElementSemantic::Color, 0, InputType::VertexData, 0 }
708 };
709 format = device->getBuffers()->createVertexFormat(elements, glm::countof(elements));
710
712 desc.name = "ImguiEffect";
713 desc.type = EffectDescriptionType::File;
714
715 switch (device->getType()) {
716
718 desc.vertexShader = "Engine/ImguiVS.wgsl";
719 desc.pixelShader = "Engine/ImguiPS.wgsl";
720 desc.vsEntryPoint = "vs_main";
721 desc.psEntryPoint = "fs_main";
722 desc.flags = static_cast<EffectFlags::EEffectFlags>(desc.flags | EffectFlags::WGSL);
723
724 desc.definitions.push_back(Cogs::PreprocessorDefinition("COGS_SRGB_CONVERSION_FAST", "1"));
725 desc.definitions.push_back(Cogs::PreprocessorDefinition("COGS_SRGB_CONVERSION_APPROX", "2"));
726 desc.definitions.push_back(Cogs::PreprocessorDefinition("COGS_SRGB_CONVERSION_EXACT", "3"));
727 break;
728
730 desc.vertexShader = "Engine/ImguiVS.es30.glsl";
731 desc.pixelShader = "Engine/ImguiPS.es30.glsl";
732 desc.flags = static_cast<EffectFlags::EEffectFlags>(desc.flags | EffectFlags::GLSL);
733 break;
734
735 default:
736 desc.vertexShader = "Engine/ImguiVS.hlsl";
737 desc.pixelShader = "Engine/ImguiPS.hlsl";
738 break;
739 }
740 effect = device->getEffects()->loadEffect(desc);
741 inputLayout = device->getBuffers()->loadInputLayout(&format, 1, effect);
742
743 constantBuffer = device->getBuffers()->loadBuffer(nullptr, sizeof(ImguiConstants), Usage::Dynamic, AccessMode::Write, BindFlags::ConstantBuffer);
744 device->getBuffers()->annotate(constantBuffer, "ImGui");
745
746 BlendState bs = {};
747 bs.enabled = true;
748 bs.sourceBlend = BlendState::Blend::SourceAlpha;
749 bs.destinationBlend = BlendState::Blend::InverseSourceAlpha;
750 bs.operation = BlendState::BlendOperation::Add;
751
752 blendState = device->getRenderTargets()->loadBlendState(bs);
753
754 RasterizerState rs = {};
755 rs.cullMode = RasterizerState::None;
756 rs.frontCounterClockwise = false;
757 rs.wireFrame = false;
758 rs.scissor = true;
759
760 rasterizerState = device->getRenderTargets()->loadRasterizerState(rs);
761
762 DepthStencilState ds = {};
763 ds.depthEnabled = false;
764 ds.depthFunction = DepthStencilState::Always;
765
766 depthState = device->getRenderTargets()->loadDepthStencilState(ds);
767
768 createSampler();
769
770 return true;
771}
772
773void Cogs::Core::GuiFont::load(const Context * context, const std::string& name, float size, const ImWchar* glyphRanges)
774{
775 path = name;
776 auto fontBytes = context->resourceStore->getResourceContents(name);
777 if (fontBytes.empty()) {
778 static const std::string fallbackFont = "Fonts/Inconsolata-Regular.ttf";
779 path = fallbackFont;
780 LOG_WARNING(logger, "Failed to read font %.*s, trying %s", StringViewFormat(name), fallbackFont.c_str());
781 fontBytes = context->resourceStore->getResourceContents(fallbackFont);
782 }
783
784 ImFontConfig config;
785 unsigned char* pixels;
786 int width;
787 int height;
788
789 config.FontDataOwnedByAtlas = false;
790 initialSize = size;
791 fontAtlas.Clear();
792
793 for (int i = 0; i < cNoOfFontSizes; ++i, size += initialSize) {
794 font[i] = fontAtlas.AddFontFromMemoryTTF(fontBytes.data(), static_cast<int>(fontBytes.size()), size, &config, glyphRanges);
795 }
796 fontAtlas.GetTexDataAsRGBA32(&pixels, &width, &height);
797 fontAtlas.TexID = context->device->getTextures()->loadTexture(pixels, width, height, TextureFormat::R8G8B8A8_UNORM_SRGB, 0).handle;
798}
799
800void Cogs::Core::GuiFont::reloadWithNewGlyphs(const Context* context, const ImWchar* glyphRanges) {
801 load(context, path, initialSize, glyphRanges);
802}
803
804ImFont* Cogs::Core::GuiFont::find(float size, float& scale) const {
805 int idx = 0;
806 float fontSize = initialSize;
807
808 for (; idx < (cNoOfFontSizes - 1); ++idx, fontSize += initialSize) {
809 if (size <= (fontSize + (fontSize * 0.3f))) {
810 break;
811 }
812 }
813 scale = size / fontSize;
814 return font[idx];
815}
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
static bool isUsingMouse()
Tests whether any ImGui control is being interacted with, or if the mouse is over an ImGui window or ...
static bool isUsingKeyboard()
Tests whether any ImGui control is currently accepting text input.
std::unique_ptr< class DPIService > dpiService
DPI service instance.
Definition: ViewContext.h:71
Log implementation class.
Definition: LogManager.h:139
bool HandleIsValid(const ResourceHandle_t< T > &handle)
Check if the given resource is valid, that is not equal to NoHandle or InvalidHandle.
ImDrawCallback setGuiMode
Callback for Render updates - not really called.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180
@ OpenGLES30
Graphics device using the OpenGLES 3.0 API.
@ OpenGL20
Graphics device using OpenGL, supporting at least OpenGL 2.0.
@ WebGPU
Graphics device using the WebGPU API Backend.
@ VertexData
Per vertex data.
std::pair< std::string, std::string > PreprocessorDefinition
Preprocessor definition.
Definition: IEffects.h:10
@ Position
Position semantic.
@ Color
Color semantic.
@ TextureCoordinate
Texture coordinate semantic.
@ Write
The buffer can be mapped and written to by the CPU after creation.
Definition: Flags.h:50
@ ConstantBuffer
The buffer can be bound as input to effects as a constant buffer.
Definition: Flags.h:72
@ Always
Always evaluates to true.
Contains an effect description used to load a single effect.
Definition: IEffects.h:55
EEffectFlags
Effect source flags.
Definition: IEffects.h:20
@ WGSL
Effect source is WGSL.
Definition: IEffects.h:36
@ GLSL
Effect source is GLSL.
Definition: IEffects.h:26
@ Press
A long press, a press that was too long to be a tap, events on press start and end.
@ Tap
A short press, see tapMaxDuration, single-fire event.
@ Drag
Pointer movement with a button pressed or touch, see mouseMoveThreshold and touchMoveThreshold,...
@ Hover
Mouse pointer hovers over window without any buttons pressed, single-fire event.
static const Handle_t NoHandle
Represents a handle to nothing.
Definition: Common.h:77
@ WriteDiscard
Write access. When unmapping the graphics system will discard the old contents of the resource.
Definition: Flags.h:103
@ TriangleList
List of triangles.
Definition: Common.h:116
@ None
Do not perform any face culling.
@ Clamp
Texture coordinates are clamped to the [0, 1] range.
Definition: SamplerState.h:17
@ MinMagMipLinear
Linear sampling for both minification and magnification.
Definition: SamplerState.h:35
@ Dynamic
Buffer will be loaded and modified with some frequency.
Definition: Flags.h:30