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