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 case GUI_MODE_TEX_CHANNELS_111INVR:
326 showAlpha = 4;
327 break;
328 default:
329 assert(false);
330 break;
331 }
332
333 int output_sRGB = 0;
334 if (context->renderer->getSettings().defaultRenderTargetExpectsSRGB) {
335 output_sRGB = 1;
336 }
337
338 if (HandleIsValid(constantBuffer)) {
339 {
340 MappedBuffer<ImguiConstants> constants(deviceContext, constantBuffer, MapMode::WriteDiscard);
341
342 if (constants) {
343 constants->projectionMatrix = M;
344 constants->showAlpha = showAlpha;
345 constants->texMode = texMode;
346 constants->offset = offset;
347 constants->output_sRGB = output_sRGB;
348 }
349 }
350 deviceContext->setConstantBuffer("ImguiBuffer", constantBuffer);
351 }
352 else {
353 assert(false);
354 }
355}
356
357void Cogs::Core::ImguiRenderer::frame(ImGuiContext* guiContext, ViewContext& view, bool updateio)
358{
359 ImGui::SetCurrentContext(guiContext);
360
361 ImGuiIO& io = ImGui::GetIO();
362 const Keyboard& keyboard = view.refKeyboard();
363
364 if (updateio) {
365 const Cogs::Mouse::State& mouseState = view.refMouse().getState();
366 const std::vector<Cogs::Gesture>& gestures = view.refGestures().getGestures();
367 Cogs::PointerType pointerType = view.refGestures().getPointerType();
368
369 std::memset(io.MouseDown, 0, sizeof(io.MouseDown));
370
371 if (pointerType == Cogs::PointerType::Touch) { // We don't want to interfere with mouse devices
372 view.refGestures().hoverEnable = true;
373 for (const Cogs::Gesture& gesture : gestures) {
374 if (gesture.kind == Cogs::Gesture::Kind::Press) {
375 touchPointerPosition.x = gesture.press.coord.x;
376 touchPointerPosition.y = gesture.press.coord.y;
377 touchPointerHeld = true;
378 }
379 else if (gesture.kind == Cogs::Gesture::Kind::Tap) {
380 touchPointerPosition.x = gesture.tap.coord.x;
381 touchPointerPosition.y = gesture.tap.coord.y;
382 touchPointerHeld = false;
383 }
384 else if (gesture.kind == Cogs::Gesture::Kind::Drag) {
385 touchPointerPosition.x = gesture.drag.currCoord.x;
386 touchPointerPosition.y = gesture.drag.currCoord.y;
387 touchPointerHeld = true;
388 }
389 else if (gesture.kind == Cogs::Gesture::Kind::Hover) {
390 touchPointerPosition.x = gesture.hover.coord.x;
391 touchPointerPosition.y = gesture.hover.coord.y;
392 }
393 }
394 }
395
396 io.MouseDown[0] = mouseState.buttonDown[MouseButton::Left] || touchPointerHeld; // We can only left click with touch pointer
397 io.MouseDown[1] = mouseState.buttonDown[MouseButton::Right];
398 io.MouseDown[2] = mouseState.buttonDown[MouseButton::Middle];
399
400 if (pointerType == Cogs::PointerType::Touch) {
401 io.MousePos.x = touchPointerPosition.x;
402 io.MousePos.y = touchPointerPosition.y;
403 }
404 else {
405 io.MousePos.x = mouseState.position.x;
406 io.MousePos.y = mouseState.position.y;
407 }
408
409 io.MouseWheel = mouseState.wheel != 0 ? (mouseState.wheel > 0 ? 1.0f : -1.0f) : 0;
410
411 for (const Keyboard::Event& e: keyboard.getEvents()) {
412 if ((e.type == Keyboard::Event::Type::Press) || (e.type == Keyboard::Event::Type::Release)) {
413 io.AddKeyEvent(CogsKeyToImGuiKey(std::get<Key>(e.data)), e.type == Keyboard::Event::Type::Press);
414 }
415 }
416 io.AddInputCharactersUTF8(keyboard.getState().chars.c_str());
417 }
418 else {
419 for (const Keyboard::Event& e: keyboard.getEvents()) {
420 if (e.type == Keyboard::Event::Type::Release) {
421 io.AddKeyEvent(CogsKeyToImGuiKey(std::get<Key>(e.data)), false);
422 }
423 }
424 }
425 io.DisplaySize = view.getSize();
426
427 // Ensure time since last render positive.
428 io.DeltaTime = std::max(static_cast<float>(timer.elapsedSeconds()), 0.000001f);
429 timer.start();
430
431 if (updateLoadedRanges) {
432 ImVector<ImWchar> requiredRanges;
433
434 fontGlyphBuilder.BuildRanges(&requiredRanges);
435
436 if (!std::equal(loadedRanges.begin(), loadedRanges.end(), requiredRanges.begin(), requiredRanges.end())) {
437 for (auto& [_, font] : fontRegistry.fonts) {
438 font.reloadWithNewGlyphs(context, requiredRanges.Data);
439 }
440 loadedRanges = requiredRanges;
441 }
442 updateLoadedRanges = false;
443 }
444
445 ImGui::NewFrame();
446}
447
448void Cogs::Core::ImguiRenderer::render()
449{
450 auto deviceContext = device->getImmediateContext();
451
452 CommandGroupAnnotation commandGroup(deviceContext, "Imgui::Render");
453
454 ImGui::Render();
455
456 auto drawData = ImGui::GetDrawData();
457
458 auto buffers = device->getBuffers();
459
460 // Create and grow vertex/index buffers if needed
461 if (!HandleIsValid(vertexBuffer) || vertexBufferSize < drawData->TotalVtxCount) {
462 if (HandleIsValid(vertexBuffer)) { buffers->releaseVertexBuffer(vertexBuffer); vertexBuffer = VertexBufferHandle::NoHandle; }
463 vertexBufferSize = drawData->TotalVtxCount + 5000;
464
465 vertexBuffer = buffers->loadVertexBuffer(nullptr, vertexBufferSize, format);
466 }
467
468 if (!HandleIsValid(indexBuffer) || indexBufferSize < drawData->TotalIdxCount) {
469 if (HandleIsValid(indexBuffer)) { buffers->releaseIndexBuffer(indexBuffer); indexBuffer = IndexBufferHandle::NoHandle; }
470 indexBufferSize = drawData->TotalIdxCount + 10000;
471
472 indexBuffer = buffers->loadIndexBuffer(nullptr, indexBufferSize, sizeof(ImDrawIdx));
473 }
474
475 if (device->getType() == GraphicsDeviceType::OpenGLES30) {
476
477 // Use updateSubBuffer since emscripten doesn't support memory mapping without faking it
478 // through an extra buffer. Avoid that copy.
479
480 size_t vtx_offset = 0;
481 for (int n = 0; n < drawData->CmdListsCount; n++) {
482 const ImDrawList* commandList = drawData->CmdLists[n];
483 deviceContext->updateSubBuffer(vertexBuffer, vtx_offset, sizeof(ImDrawVert) * commandList->VtxBuffer.size(), &commandList->VtxBuffer[0]);
484 vtx_offset += sizeof(ImDrawVert) * commandList->VtxBuffer.size();
485 }
486
487 size_t idx_offset = 0;
488 for (int n = 0; n < drawData->CmdListsCount; n++) {
489 const ImDrawList* commandList = drawData->CmdLists[n];
490 deviceContext->updateSubBuffer(indexBuffer, idx_offset, sizeof(ImDrawIdx) * commandList->IdxBuffer.size(), &commandList->IdxBuffer[0]);
491 idx_offset += sizeof(ImDrawIdx) * commandList->IdxBuffer.size();
492 }
493
494 }
495 else {
496 ImDrawVert* vtx_dst = (ImDrawVert*)deviceContext->map(vertexBuffer, MapMode::WriteDiscard);
497 ImDrawIdx* idx_dst = (ImDrawIdx*)deviceContext->map(indexBuffer, MapMode::WriteDiscard);
498
499 if (vtx_dst && idx_dst) {
500 for (int n = 0; n < drawData->CmdListsCount; n++) {
501 const auto commandList = drawData->CmdLists[n];
502
503 memcpy(vtx_dst, &commandList->VtxBuffer[0], commandList->VtxBuffer.size() * sizeof(ImDrawVert));
504 memcpy(idx_dst, &commandList->IdxBuffer[0], commandList->IdxBuffer.size() * sizeof(ImDrawIdx));
505
506 vtx_dst += commandList->VtxBuffer.size();
507 idx_dst += commandList->IdxBuffer.size();
508 }
509 }
510 deviceContext->unmap(vertexBuffer);
511 deviceContext->unmap(indexBuffer);
512 }
513
514 deviceContext->setViewport(0, 0, ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y);
515 deviceContext->clearDepth(context->renderer->getClearDepth());
516
517 deviceContext->setEffect(effect);
518 const uint32_t strides[] = { sizeof(ImDrawVert) };
519 deviceContext->setVertexBuffers(&vertexBuffer, 1, strides, nullptr);
520 deviceContext->setIndexBuffer(indexBuffer, sizeof(ImDrawIdx));
521 if (HandleIsValid(inputLayout)) deviceContext->setInputLayout(inputLayout);
522 updateConstantBuffer(deviceContext, GUI_MODE_DEFAULT);
523
524 deviceContext->setBlendState(blendState);
525 deviceContext->setRasterizerState(rasterizerState);
526 deviceContext->setDepthStencilState(depthState);
527
528 deviceContext->setTexture("texture0", 0, dummyTex2D);
529 deviceContext->setSamplerState("texture0Sampler", 0, sampler);
530
531 deviceContext->setTexture("texturems", 1, dummyTex2DMS);
532 deviceContext->setSamplerState("texture0Sampler", 1, sampler);
533
534 deviceContext->setTexture("texcube", 2, dummyTexCube);
535 deviceContext->setSamplerState("texture0Sampler", 2, sampler);
536
537 deviceContext->setTexture("texarray", 3, dummyTex2DArray);
538 deviceContext->setSamplerState("texarraySampler", 3, sampler);
539
540 // Render command lists
541 int baseVertex = 0;
542 int baseIndex = 0;
543
544 for (int n = 0; n < drawData->CmdListsCount; n++) {
545 const ImDrawList* commandList = drawData->CmdLists[n];
546
547 for (int commandIndex = 0; commandIndex < commandList->CmdBuffer.size(); commandIndex++) {
548 const ImDrawCmd* pcmd = &commandList->CmdBuffer[commandIndex];
549
550 if (pcmd->UserCallback == setGuiMode) {
551 updateConstantBuffer(deviceContext, (uint32_t)(uint64_t)pcmd->UserCallbackData);
552 }
553 else {
554 if (pcmd->UserCallback) {
555 pcmd->UserCallback(commandList, pcmd);
556 } else {
557 switch (device->getType()) {
560 deviceContext->setScissor(int(pcmd->ClipRect.x),
561 int(ImGui::GetIO().DisplaySize.y) - int(pcmd->ClipRect.w),
562 int(pcmd->ClipRect.z) - int(pcmd->ClipRect.x),
563 int(pcmd->ClipRect.w) - int(pcmd->ClipRect.y));
564
565 break;
566
567 default:
568 deviceContext->setScissor(int(pcmd->ClipRect.x),
569 int(pcmd->ClipRect.y),
570 int(pcmd->ClipRect.z),
571 int(pcmd->ClipRect.w));
572 break;
573 }
574
575 Cogs::TextureHandle textureHandle(pcmd->GetTexID());
576
577 deviceContext->setBlendState(blendState);
578
579 assert(pcmd->ElemCount > 0);
580 switch (mode & GUI_MODE_TEX_TYPE_MASK)
581 {
582 case GUI_MODE_TEX_TYPE_2D:
583 deviceContext->setTexture("texture0", 0, textureHandle);
584 deviceContext->setSamplerState("texture0Sampler", 0, sampler);
585 break;
586 case GUI_MODE_TEX_TYPE_ARRAY:
587 deviceContext->setTexture("texarray", 3, textureHandle);
588 deviceContext->setSamplerState("texture0Sampler", 3, sampler);
589 break;
590 case GUI_MODE_TEX_TYPE_CUBE:
591 deviceContext->setTexture("texcube", 2, textureHandle);
592 deviceContext->setSamplerState("texture0Sampler", 2, sampler);
593 break;
594 case GUI_MODE_TEX_TYPE_2DMS:
595 deviceContext->setTexture("texturems", 1, textureHandle);
596 deviceContext->setSamplerState("texture0Sampler", 1, sampler);
597 break;
598 default:
599 assert(false);
600 break;
601 }
602 deviceContext->drawIndexed(PrimitiveType::TriangleList, baseIndex, pcmd->ElemCount, baseVertex);
603 }
604 }
605
606 baseIndex += pcmd->ElemCount;
607 }
608
609 baseVertex += commandList->VtxBuffer.size();
610 }
611
612 auto & io = ImGui::GetIO();
613
614 io.ClearInputCharacters();
615}
616
617void Cogs::Core::ImguiRenderer::addTextToGlyphBuilder(const char* text)
618{
619 if (text) {
620 fontGlyphBuilder.AddText(text);
621 updateLoadedRanges = true;
622 }
623}
624
625void Cogs::Core::ImguiRenderer::addRangeToGlyphBuilder(const ImWchar* ranges)
626{
627 fontGlyphBuilder.AddRanges(ranges);
628 updateLoadedRanges = true;
629}
630
631void Cogs::Core::ImguiRenderer::style()
632{
633 // FIXME: those should become parameters to the function
634 float satMult = 0.0f;
635 static int hue = 146;
636 static float col_main_sat = satMult * 180.f / 255.f;
637 static float col_main_val = 161.f / 255.f;
638 static float col_area_sat = satMult * 124.f / 255.f;
639 static float col_area_val = 100.f / 255.f;
640 static float col_back_sat = satMult * 59.f / 255.f;
641 static float col_back_val = 40.f / 255.f;
642
643 ImGuiStyle & style = ImGui::GetStyle();
644
645 ImVec4 col_text = ImColor::HSV(hue / 255.f, 20.f / 255.f, 255.f / 255.f);
646 ImVec4 col_main = ImColor::HSV(hue / 255.f, col_main_sat, col_main_val);
647 ImVec4 col_back = ImColor::HSV(hue / 255.f, col_back_sat, col_back_val);
648 ImVec4 col_dark = ImColor::HSV(hue / 255.f, col_back_sat, 55 / 255.f);
649 ImVec4 col_area = ImColor::HSV(hue / 255.f, col_area_sat, col_area_val);
650
651 style.Colors[ImGuiCol_Text] = ImVec4(col_text.x, col_text.y, col_text.z, 1.00f);
652 style.Colors[ImGuiCol_TextDisabled] = ImVec4(col_text.x, col_text.y, col_text.z, 0.58f);
653 style.Colors[ImGuiCol_WindowBg] = ImVec4(col_back.x, col_back.y, col_back.z, 1.00f);
654 style.Colors[ImGuiCol_Border] = ImVec4(col_text.x, col_text.y, col_text.z, 0.30f);
655 style.Colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
656 style.Colors[ImGuiCol_FrameBg] = ImVec4(col_area.x, col_area.y, col_area.z, 1.00f);
657 style.Colors[ImGuiCol_FrameBgHovered] = ImVec4(col_main.x, col_main.y, col_main.z, 0.68f);
658 style.Colors[ImGuiCol_FrameBgActive] = ImVec4(col_main.x, col_main.y, col_main.z, 1.00f);
659 style.Colors[ImGuiCol_TitleBg] = ImVec4(col_main.x, col_main.y, col_main.z, 0.45f);
660 style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(col_main.x, col_main.y, col_main.z, 0.35f);
661 style.Colors[ImGuiCol_TitleBgActive] = ImVec4(col_main.x, col_main.y, col_main.z, 0.78f);
662 style.Colors[ImGuiCol_MenuBarBg] = ImVec4(col_back.x, col_back.y, col_back.z, 1.00f);
663 style.Colors[ImGuiCol_ScrollbarBg] = ImVec4(col_area.x, col_area.y, col_area.z, 1.00f);
664 style.Colors[ImGuiCol_ScrollbarGrab] = ImVec4(col_main.x, col_main.y, col_main.z, 0.31f);
665 style.Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(col_main.x, col_main.y, col_main.z, 0.78f);
666 style.Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(col_main.x, col_main.y, col_main.z, 1.00f);
667 style.Colors[ImGuiCol_CheckMark] = ImVec4(col_main.x, col_main.y, col_main.z, 0.80f);
668 style.Colors[ImGuiCol_SliderGrab] = ImVec4(col_main.x, col_main.y, col_main.z, 0.24f);
669 style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(col_main.x, col_main.y, col_main.z, 1.00f);
670 style.Colors[ImGuiCol_Button] = ImVec4(col_main.x, col_main.y, col_main.z, 0.44f);
671 style.Colors[ImGuiCol_ButtonHovered] = ImVec4(col_main.x, col_main.y, col_main.z, 0.86f);
672 style.Colors[ImGuiCol_ButtonActive] = ImVec4(col_main.x, col_main.y, col_main.z, 1.00f);
673 style.Colors[ImGuiCol_Header] = ImVec4(col_main.x, col_main.y, col_main.z, 0.76f);
674 style.Colors[ImGuiCol_HeaderHovered] = ImVec4(col_dark.x, col_dark.y, col_dark.z, 0.86f);
675 style.Colors[ImGuiCol_HeaderActive] = ImVec4(col_main.x, col_main.y, col_main.z, 1.00f);
676 style.Colors[ImGuiCol_ResizeGrip] = ImVec4(col_main.x, col_main.y, col_main.z, 0.20f);
677 style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(col_main.x, col_main.y, col_main.z, 0.78f);
678 style.Colors[ImGuiCol_ResizeGripActive] = ImVec4(col_main.x, col_main.y, col_main.z, 1.00f);
679 style.Colors[ImGuiCol_PlotLines] = ImVec4(col_text.x, col_text.y, col_text.z, 0.63f);
680 style.Colors[ImGuiCol_PlotLinesHovered] = ImVec4(col_main.x, col_main.y, col_main.z, 1.00f);
681 style.Colors[ImGuiCol_PlotHistogram] = ImVec4(col_text.x, col_text.y, col_text.z, 0.63f);
682 style.Colors[ImGuiCol_PlotHistogramHovered] = ImVec4(col_main.x, col_main.y, col_main.z, 1.00f);
683 style.Colors[ImGuiCol_TextSelectedBg] = ImVec4(col_main.x, col_main.y, col_main.z, 0.43f);
684 style.Colors[ImGuiCol_PopupBg] = ImVec4(col_back.x, col_back.y, col_back.z, 0.99f);
685 style.Colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f);
686
687 style.WindowRounding = 2.0f;
688 style.WindowBorderSize = 0.0f;
689}
690
691void Cogs::Core::ImguiRenderer::createSampler()
692{
693 SamplerState fs = {};
694 fs.addressModeS = SamplerState::Clamp;
695 fs.addressModeT = SamplerState::Clamp;
697 sampler = device->getTextures()->loadSamplerState(fs);
698}
699
700bool Cogs::Core::ImguiRenderer::createResources()
701{
702 VertexElement elements[] = {
703 { 0, DataFormat::X32Y32_FLOAT, ElementSemantic::Position, 0, InputType::VertexData, 0 },
704 { 2 * sizeof(float), DataFormat::X32Y32_FLOAT, ElementSemantic::TextureCoordinate, 0, InputType::VertexData, 0 },
705 { 4 * sizeof(float), DataFormat::R8G8B8A8_UNORM, ElementSemantic::Color, 0, InputType::VertexData, 0 }
706 };
707 format = device->getBuffers()->createVertexFormat(elements, glm::countof(elements));
708
710 desc.name = "ImguiEffect";
711 desc.type = EffectDescriptionType::File;
712
713 switch (device->getType()) {
714
716 desc.vertexShader = "Engine/ImguiVS.wgsl";
717 desc.pixelShader = "Engine/ImguiPS.wgsl";
718 desc.vsEntryPoint = "vs_main";
719 desc.psEntryPoint = "fs_main";
720 desc.flags = static_cast<EffectFlags::EEffectFlags>(desc.flags | EffectFlags::WGSL);
721
722 desc.definitions.push_back(Cogs::PreprocessorDefinition("COGS_SRGB_CONVERSION_FAST", "1"));
723 desc.definitions.push_back(Cogs::PreprocessorDefinition("COGS_SRGB_CONVERSION_APPROX", "2"));
724 desc.definitions.push_back(Cogs::PreprocessorDefinition("COGS_SRGB_CONVERSION_EXACT", "3"));
725 break;
726
728 desc.vertexShader = "Engine/ImguiVS.es30.glsl";
729 desc.pixelShader = "Engine/ImguiPS.es30.glsl";
730 desc.flags = static_cast<EffectFlags::EEffectFlags>(desc.flags | EffectFlags::GLSL);
731 break;
732
733 default:
734 desc.vertexShader = "Engine/ImguiVS.hlsl";
735 desc.pixelShader = "Engine/ImguiPS.hlsl";
736 break;
737 }
738 effect = device->getEffects()->loadEffect(desc);
739 inputLayout = device->getBuffers()->loadInputLayout(&format, 1, effect);
740
741 constantBuffer = device->getBuffers()->loadBuffer(nullptr, sizeof(ImguiConstants), Usage::Dynamic, AccessMode::Write, BindFlags::ConstantBuffer);
742 device->getBuffers()->annotate(constantBuffer, "ImGui");
743
744 BlendState bs = {};
745 bs.enabled = true;
746 bs.sourceBlend = BlendState::Blend::SourceAlpha;
747 bs.destinationBlend = BlendState::Blend::InverseSourceAlpha;
748 bs.operation = BlendState::BlendOperation::Add;
749
750 blendState = device->getRenderTargets()->loadBlendState(bs);
751
752 RasterizerState rs = {};
753 rs.cullMode = RasterizerState::None;
754 rs.frontCounterClockwise = false;
755 rs.wireFrame = false;
756 rs.scissor = true;
757
758 rasterizerState = device->getRenderTargets()->loadRasterizerState(rs);
759
760 DepthStencilState ds = {};
761 ds.depthEnabled = false;
762 ds.depthFunction = DepthStencilState::Always;
763
764 depthState = device->getRenderTargets()->loadDepthStencilState(ds);
765
766 Cogs::ITextures* textures = device->getTextures();
767 assert(textures);
768
769 uint32_t width = 2;
770 uint32_t height = 2;
771 const unsigned char data[2 * 2 * 4] = {
772 0xffu, 0xffu, 0xffu, 0xffu,
773 0x88u, 0x88u, 0x88u, 0x88u,
774 0x88u, 0x88u, 0x88u, 0x88u,
775 0xffu, 0xffu, 0xffu, 0xffu,
776 };
777 const unsigned char* dataPtrs[6] = { data, data, data, data, data, data };
778
779 dummyTex2D = textures->loadTexture(data, width, height, TextureFormat::R8G8B8A8_UNORM_SRGB);
780 dummyTex2DMS = textures->loadTexture(nullptr, width, height, TextureFormat::R8G8B8A8_UNORM_SRGB, 2, 0);
781 dummyTex2DArray = textures->loadTextureArray(dataPtrs, 1, 1, &width, &height, TextureFormat::R8G8B8A8_UNORM_SRGB);
782 dummyTexCube = textures->loadCubeMap(dataPtrs, 1, 1, &width, &height, TextureFormat::R8G8B8A8_UNORM_SRGB);
783
784 createSampler();
785
786 return true;
787}
788
789void Cogs::Core::GuiFont::load(const Context * context, const std::string& name, float size, const ImWchar* glyphRanges)
790{
791 path = name;
792 auto fontBytes = context->resourceStore->getResourceContents(name);
793 if (fontBytes.empty()) {
794 static const std::string fallbackFont = "Fonts/Inconsolata-Regular.ttf";
795 path = fallbackFont;
796 LOG_WARNING(logger, "Failed to read font %.*s, trying %s", StringViewFormat(name), fallbackFont.c_str());
797 fontBytes = context->resourceStore->getResourceContents(fallbackFont);
798 }
799
800 ImFontConfig config;
801 unsigned char* pixels;
802 int width;
803 int height;
804
805 config.FontDataOwnedByAtlas = false;
806 initialSize = size;
807 fontAtlas.Clear();
808
809 for (int i = 0; i < cNoOfFontSizes; ++i, size += initialSize) {
810 font[i] = fontAtlas.AddFontFromMemoryTTF(fontBytes.data(), static_cast<int>(fontBytes.size()), size, &config, glyphRanges);
811 }
812 fontAtlas.GetTexDataAsRGBA32(&pixels, &width, &height);
813 fontAtlas.TexID = context->device->getTextures()->loadTexture(pixels, width, height, TextureFormat::R8G8B8A8_UNORM_SRGB, 0).handle;
814}
815
816void Cogs::Core::GuiFont::reloadWithNewGlyphs(const Context* context, const ImWchar* glyphRanges) {
817 load(context, path, initialSize, glyphRanges);
818}
819
820ImFont* Cogs::Core::GuiFont::find(float size, float& scale) const {
821 int idx = 0;
822 float fontSize = initialSize;
823
824 for (; idx < (cNoOfFontSizes - 1); ++idx, fontSize += initialSize) {
825 if (size <= (fontSize + (fontSize * 0.3f))) {
826 break;
827 }
828 }
829 scale = size / fontSize;
830 return font[idx];
831}
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.
@ VertexData
Per vertex data.
@ TriangleList
List of triangles.
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:78
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
@ Dynamic
Buffer will be loaded and modified with some frequency.
Definition: Flags.h:30