├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── screenshot.jpg └── src ├── imgui_fsh.glsl ├── imgui_impl_deko3d.cpp ├── imgui_impl_deko3d.h ├── imgui_vsh.glsl ├── main.cc └── util.h /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_parties/imgui"] 2 | path = third_parties/imgui 3 | url = https://github.com/ocornut/imgui.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.18.4) 2 | # or use: /opt/devkitpro/portlibs/switch/bin/aarch64-none-elf-cmake 3 | include(/opt/devkitpro/cmake/Switch.cmake) 4 | project(imgui_deko3d_example VERSION 0.0.1 LANGUAGES C CXX) 5 | set(PROJECT_AUTHOR "scturtle") 6 | 7 | if(NOT CMAKE_BUILD_TYPE) 8 | set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE) 9 | endif() 10 | 11 | set(TARGET ${PROJECT_NAME}) 12 | set(IMGUI_DIR third_parties/imgui) 13 | 14 | add_executable(${TARGET} 15 | src/main.cc 16 | src/imgui_impl_deko3d.cpp 17 | ${IMGUI_DIR}/imgui.cpp 18 | ${IMGUI_DIR}/imgui_demo.cpp 19 | ${IMGUI_DIR}/imgui_draw.cpp 20 | ${IMGUI_DIR}/imgui_tables.cpp 21 | ${IMGUI_DIR}/imgui_widgets.cpp 22 | ) 23 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 24 | target_compile_definitions(${TARGET} PUBLIC 25 | IMGUI_DISABLE_OBSOLETE_KEYIO 26 | IMGUI_DISABLE_OBSOLETE_FUNCTIONS 27 | ) 28 | 29 | target_link_libraries(${TARGET} PUBLIC 30 | $,deko3dd,deko3d> 31 | ) 32 | target_include_directories(${TARGET} PUBLIC 33 | ${DEVKITPRO}/portlibs/switch/include 34 | ${IMGUI_DIR}) 35 | 36 | nx_add_shader_program(imgui_vsh src/imgui_vsh.glsl vert) 37 | nx_add_shader_program(imgui_fsh src/imgui_fsh.glsl frag) 38 | dkp_add_asset_target(${TARGET}_romfs ${CMAKE_CURRENT_BINARY_DIR}/romfs) 39 | dkp_install_assets(${TARGET}_romfs 40 | DESTINATION shaders 41 | TARGETS imgui_vsh imgui_fsh) 42 | 43 | nx_generate_nacp(${TARGET}.nacp 44 | NAME ${TARGET} 45 | AUTHOR ${PROJECT_AUTHOR} 46 | ) 47 | nx_create_nro(${TARGET} 48 | NACP ${TARGET}.nacp 49 | ROMFS ${TARGET}_romfs 50 | ) 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 scturtle 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # imgui_deko3d_example 2 | 3 | A [Dear ImGui](https://github.com/ocornut/imgui) template project with [deko3d](https://github.com/devkitPro/deko3d) backend for Switch homebrew. 4 | 5 | Featuring: 6 | - Clean and Dear ImGui style backend code. 7 | - Compatible with latest Dear ImGui version. 8 | - CMake support. 9 | 10 | ## screenshot 11 | 12 | ![screenshot](screenshot.jpg) 13 | 14 | ## compiling 15 | 16 | Just use docker from [devkitpro/devkita64](https://hub.docker.com/r/devkitpro/devkita64). 17 | 18 | ## credits 19 | 20 | [switchbrew/switch-examples](https://github.com/switchbrew/switch-examples) for how to use deko3d. 21 | 22 | [mtheall/ftpd](https://raw.githubusercontent.com/mtheall/ftpd) and [averne/Turips](https://github.com/averne/Turnips) for their backends. 23 | -------------------------------------------------------------------------------- /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scturtle/imgui_deko3d_example/537ae203f24c9d66822863643c9a1e3b73f85749/screenshot.jpg -------------------------------------------------------------------------------- /src/imgui_fsh.glsl: -------------------------------------------------------------------------------- 1 | #version 460 2 | 3 | layout (location = 0) in vec2 vtxUv; 4 | layout (location = 1) in vec4 vtxColor; 5 | 6 | layout (binding = 0) uniform sampler2D tex; 7 | 8 | layout (location = 0) out vec4 outColor; 9 | 10 | void main() { 11 | outColor = vtxColor * texture(tex, vtxUv); 12 | } 13 | -------------------------------------------------------------------------------- /src/imgui_impl_deko3d.cpp: -------------------------------------------------------------------------------- 1 | #include "imgui_impl_deko3d.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #define FB_NUM 2 11 | #define FB_WIDTH 1280 12 | #define FB_HEIGHT 720 13 | #define CODEMEMSIZE (4 * 1024) 14 | #define CMDMEMSIZE (16 * 1024) 15 | 16 | struct VertUBO { 17 | glm::mat4 proj; 18 | }; 19 | 20 | struct Descriptors { 21 | dk::SamplerDescriptor sampler; 22 | dk::ImageDescriptor image; 23 | }; 24 | 25 | struct ImGui_ImplDeko3d_Data { 26 | dk::UniqueDevice device; 27 | dk::UniqueQueue queue; 28 | 29 | dk::UniqueMemBlock fbMemBlock; 30 | dk::Image framebuffers[FB_NUM]; 31 | dk::UniqueSwapchain swapchain; 32 | 33 | dk::UniqueMemBlock depthMemBlock; 34 | dk::Image depthbuffer; 35 | 36 | dk::UniqueMemBlock codeMemBlock; 37 | dk::Shader vertexShader; 38 | dk::Shader fragmentShader; 39 | 40 | dk::UniqueMemBlock uboMemBlock; 41 | dk::UniqueMemBlock vtxMemBlock[FB_NUM]; 42 | dk::UniqueMemBlock idxMemBlock[FB_NUM]; 43 | 44 | dk::UniqueMemBlock descriptorsMemBlock; 45 | dk::UniqueMemBlock fontImageMemBlock; 46 | DkResHandle fontTextureHandle; 47 | 48 | dk::UniqueMemBlock cmdbufMemBlock[FB_NUM]; 49 | dk::UniqueCmdBuf cmdbuf[FB_NUM]; 50 | 51 | PadState pad; 52 | u64 last_tick = armGetSystemTick(); 53 | }; 54 | 55 | static ImGui_ImplDeko3d_Data *getBackendData() { 56 | return ImGui::GetCurrentContext() 57 | ? (ImGui_ImplDeko3d_Data *)ImGui::GetIO().BackendRendererUserData 58 | : nullptr; 59 | } 60 | 61 | static constexpr u32 align(u32 size, u32 align) { 62 | return (size + align - 1) & ~(align - 1); 63 | } 64 | 65 | static u32 loadShader(dk::Shader &shader, const char *path, 66 | DkMemBlock codeMemBlock, u32 codeOffset) { 67 | FILE *f = fopen(path, "rb"); 68 | fseek(f, 0, SEEK_END); 69 | u32 size = ftell(f); 70 | rewind(f); 71 | fread((char *)dkMemBlockGetCpuAddr(codeMemBlock) + codeOffset, size, 1, f); 72 | fclose(f); 73 | // init sahder 74 | dk::ShaderMaker(codeMemBlock, codeOffset).initialize(shader); 75 | return align(size, DK_SHADER_CODE_ALIGNMENT); 76 | } 77 | 78 | static void InitDeko3Shaders(ImGui_ImplDeko3d_Data *bd) { 79 | DkDevice device = bd->device; 80 | // create memory block for shader code 81 | static_assert(CODEMEMSIZE == align(CODEMEMSIZE, DK_MEMBLOCK_ALIGNMENT), ""); 82 | bd->codeMemBlock = 83 | dk::MemBlockMaker(device, CODEMEMSIZE) 84 | .setFlags(DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached | 85 | DkMemBlockFlags_Code) 86 | .create(); 87 | 88 | // load shaders 89 | u32 codeMemOffset = 0; 90 | codeMemOffset += loadShader(bd->vertexShader, "romfs:/shaders/imgui_vsh.dksh", 91 | bd->codeMemBlock, codeMemOffset); 92 | codeMemOffset += 93 | loadShader(bd->fragmentShader, "romfs:/shaders/imgui_fsh.dksh", 94 | bd->codeMemBlock, codeMemOffset); 95 | IM_ASSERT(codeMemOffset + DK_SHADER_CODE_UNUSABLE_SIZE <= CODEMEMSIZE); 96 | } 97 | 98 | static void InitDeko3dSwapchain(ImGui_ImplDeko3d_Data *bd) { 99 | DkDevice device = bd->device; 100 | 101 | // create depth layout 102 | dk::ImageLayout depthLayout; 103 | dk::ImageLayoutMaker(device) 104 | .setFlags(DkImageFlags_UsageRender | DkImageFlags_HwCompression) 105 | .setFormat(DkImageFormat_Z24S8) 106 | .setDimensions(FB_WIDTH, FB_HEIGHT) 107 | .initialize(depthLayout); 108 | 109 | // create depth memblock 110 | bd->depthMemBlock = 111 | dk::MemBlockMaker(device, align(align(depthLayout.getSize(), 112 | depthLayout.getAlignment()), 113 | (u32)DK_MEMBLOCK_ALIGNMENT)) 114 | .setFlags(DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image) 115 | .create(); 116 | 117 | // create depth image 118 | bd->depthbuffer.initialize(depthLayout, bd->depthMemBlock, 0); 119 | 120 | // create framebuffer layout 121 | dk::ImageLayout fbLayout; 122 | dk::ImageLayoutMaker(device) 123 | .setFlags(DkImageFlags_UsageRender | DkImageFlags_UsagePresent | 124 | DkImageFlags_HwCompression) 125 | .setFormat(DkImageFormat_RGBA8_Unorm) 126 | .setDimensions(FB_WIDTH, FB_HEIGHT) 127 | .initialize(fbLayout); 128 | 129 | u32 fbSize = align(align(fbLayout.getSize(), fbLayout.getAlignment()), 130 | (u32)DK_MEMBLOCK_ALIGNMENT); 131 | 132 | // create framebuffer memblock 133 | bd->fbMemBlock = 134 | dk::MemBlockMaker(device, FB_NUM * fbSize) 135 | .setFlags(DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image) 136 | .create(); 137 | 138 | // create framebuffer images 139 | std::array swapchainImages; 140 | for (unsigned i = 0; i < FB_NUM; i++) { 141 | swapchainImages[i] = &bd->framebuffers[i]; 142 | bd->framebuffers[i].initialize(fbLayout, bd->fbMemBlock, i * fbSize); 143 | } 144 | 145 | // create a swapchain 146 | bd->swapchain = 147 | dk::SwapchainMaker(device, nwindowGetDefault(), swapchainImages).create(); 148 | 149 | // Create command buffer and memory block 150 | for (int i = 0; i < FB_NUM; ++i) { 151 | bd->cmdbufMemBlock[i] = 152 | dk::MemBlockMaker(device, align(CMDMEMSIZE, DK_MEMBLOCK_ALIGNMENT)) 153 | .setFlags(DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) 154 | .create(); 155 | bd->cmdbuf[i] = dk::CmdBufMaker(device).create(); 156 | bd->cmdbuf[i].addMemory(bd->cmdbufMemBlock[i], 0, CMDMEMSIZE); 157 | } 158 | } 159 | 160 | static void ImGui_LoadSwitchFonts(ImGuiIO &io) { 161 | PlFontData standard, extended, chinese, korean; 162 | ImWchar extended_range[] = {0xe000, 0xe152}; 163 | bool ok = R_SUCCEEDED( 164 | plGetSharedFontByType(&standard, PlSharedFontType_Standard)) && 165 | R_SUCCEEDED(plGetSharedFontByType(&extended, 166 | PlSharedFontType_NintendoExt)) && 167 | R_SUCCEEDED(plGetSharedFontByType( 168 | &chinese, PlSharedFontType_ChineseSimplified)) && 169 | R_SUCCEEDED(plGetSharedFontByType(&korean, PlSharedFontType_KO)); 170 | IM_ASSERT(ok); 171 | 172 | ImFontConfig font_cfg; 173 | font_cfg.FontDataOwnedByAtlas = false; 174 | io.Fonts->AddFontFromMemoryTTF(standard.address, standard.size, 18.0f, 175 | &font_cfg, io.Fonts->GetGlyphRangesDefault()); 176 | font_cfg.MergeMode = true; 177 | io.Fonts->AddFontFromMemoryTTF(extended.address, extended.size, 18.0f, 178 | &font_cfg, extended_range); 179 | // NOTE: uncomment to enable Chinese/Korean support but with slow startup time 180 | /* 181 | io.Fonts->AddFontFromMemoryTTF( 182 | chinese.address, chinese.size, 18.0f, &font_cfg, 183 | io.Fonts->GetGlyphRangesChineseSimplifiedCommon()); 184 | io.Fonts->AddFontFromMemoryTTF(korean.address, korean.size, 18.0f, &font_cfg, 185 | io.Fonts->GetGlyphRangesKorean()); 186 | */ 187 | 188 | io.Fonts->Flags |= ImFontAtlasFlags_NoPowerOfTwoHeight; 189 | io.Fonts->Build(); 190 | } 191 | 192 | static void InitDeko3dFontTexture(ImGui_ImplDeko3d_Data *bd) { 193 | DkDevice device = bd->device; 194 | dk::CmdBuf cmdbuf = bd->cmdbuf[0]; 195 | 196 | // initialize memblock for descriptors 197 | bd->descriptorsMemBlock = 198 | dk::MemBlockMaker(device, 199 | align(sizeof(Descriptors), DK_MEMBLOCK_ALIGNMENT)) 200 | .setFlags(DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) 201 | .create(); 202 | Descriptors *descCpuAddr = 203 | (Descriptors *)bd->descriptorsMemBlock.getCpuAddr(); 204 | DkGpuAddr descGpuAddr = bd->descriptorsMemBlock.getGpuAddr(); 205 | 206 | // bind all descriptors 207 | cmdbuf.bindSamplerDescriptorSet(descGpuAddr, 1); 208 | constexpr int offset = offsetof(Descriptors, image); 209 | cmdbuf.bindImageDescriptorSet(descGpuAddr + offset, 1); 210 | 211 | // generate font texture id 212 | ImGuiIO &io = ImGui::GetIO(); 213 | ImGui_LoadSwitchFonts(io); 214 | bd->fontTextureHandle = dkMakeTextureHandle(0, 0); 215 | io.Fonts->SetTexID(&bd->fontTextureHandle); 216 | 217 | // copy font data to scratch buffer 218 | unsigned char *pixels; 219 | int width, height, channel; 220 | io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height, &channel); 221 | dk::UniqueMemBlock scratchMemBlock = 222 | dk::MemBlockMaker(device, 223 | align(width * height * channel, DK_MEMBLOCK_ALIGNMENT)) 224 | .setFlags(DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) 225 | .create(); 226 | memcpy(scratchMemBlock.getCpuAddr(), pixels, width * height * channel); 227 | 228 | // create font image memblock 229 | dk::ImageLayout layout; 230 | dk::ImageLayoutMaker{device} 231 | .setFlags(0) 232 | .setFormat(DkImageFormat_RGBA8_Unorm) 233 | .setDimensions(width, height) 234 | .initialize(layout); 235 | bd->fontImageMemBlock = 236 | dk::MemBlockMaker{ 237 | device, align(layout.getSize(), std::max(layout.getAlignment(), 238 | (u32)DK_MEMBLOCK_ALIGNMENT))} 239 | .setFlags(DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image) 240 | .create(); 241 | 242 | dk::Image fontImage; 243 | fontImage.initialize(layout, bd->fontImageMemBlock, 0); 244 | 245 | // init font descriptors 246 | descCpuAddr->image.initialize(fontImage); 247 | descCpuAddr->sampler.initialize( 248 | dk::Sampler{} 249 | .setFilter(DkFilter_Linear, DkFilter_Linear) 250 | .setWrapMode(DkWrapMode_ClampToEdge, DkWrapMode_ClampToEdge, 251 | DkWrapMode_ClampToEdge)); 252 | 253 | // copy from scratch buffer to font image 254 | cmdbuf.copyBufferToImage({scratchMemBlock.getGpuAddr()}, 255 | dk::ImageView{fontImage}, 256 | {0, 0, 0, u32(width), u32(height), 1}); 257 | 258 | bd->queue.submitCommands(cmdbuf.finishList()); 259 | bd->queue.waitIdle(); 260 | } 261 | 262 | static void InitDeko3dData(ImGui_ImplDeko3d_Data *bd) { 263 | bd->device = dk::DeviceMaker().create(); 264 | bd->queue = 265 | dk::QueueMaker(bd->device).setFlags(DkQueueFlags_Graphics).create(); 266 | 267 | InitDeko3Shaders(bd); 268 | 269 | InitDeko3dSwapchain(bd); 270 | 271 | InitDeko3dFontTexture(bd); 272 | 273 | // Create a memory block for UBO 274 | bd->uboMemBlock = 275 | dk::MemBlockMaker(bd->device, 276 | align(align(sizeof(VertUBO), DK_UNIFORM_BUF_ALIGNMENT), 277 | DK_MEMBLOCK_ALIGNMENT)) 278 | .setFlags(DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) 279 | .create(); 280 | } 281 | 282 | void ImGui_ImplDeko3d_Init() { 283 | ImGuiIO &io = ImGui::GetIO(); 284 | IM_ASSERT(!io.BackendRendererUserData && 285 | "Already initialized a renderer backend!"); 286 | 287 | io.BackendPlatformName = "Switch"; 288 | io.BackendRendererName = "imgui_impl_deko3d"; 289 | io.IniFilename = nullptr; 290 | io.MouseDrawCursor = false; 291 | io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen; 292 | io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; 293 | io.BackendFlags |= ImGuiBackendFlags_HasGamepad; 294 | io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; 295 | 296 | ImGui_ImplDeko3d_Data *bd = new ImGui_ImplDeko3d_Data(); 297 | io.BackendRendererUserData = (void *)bd; 298 | 299 | // init all resources of deko3d 300 | InitDeko3dData(bd); 301 | 302 | // init the gamepad 303 | padConfigureInput(1, HidNpadStyleSet_NpadStandard); 304 | padInitializeDefault(&bd->pad); 305 | } 306 | 307 | void ImGui_ImplDeko3d_Shutdown() { 308 | ImGui_ImplDeko3d_Data *bd = getBackendData(); 309 | dkQueueWaitIdle(bd->queue); 310 | delete bd; 311 | } 312 | 313 | uint64_t ImGui_ImplDeko3d_UpdatePad() { 314 | ImGuiIO &io = ImGui::GetIO(); 315 | ImGui_ImplDeko3d_Data *bd = getBackendData(); 316 | 317 | // fetch gamepad state 318 | padUpdate(&bd->pad); 319 | const u64 down = padGetButtonsDown(&bd->pad); 320 | const u64 up = padGetButtonsUp(&bd->pad); 321 | 322 | HidTouchScreenState state = {0}; 323 | hidGetTouchScreenStates(&state, 1); 324 | static bool touch_down = false; 325 | if (state.count < 1) { 326 | if (touch_down) { 327 | touch_down = false; 328 | io.AddMouseButtonEvent(0, false); 329 | } 330 | } else { 331 | float x, y; 332 | x = state.touches[0].x; 333 | y = state.touches[0].y; 334 | io.AddMousePosEvent(x, y); 335 | touch_down = true; 336 | io.AddMouseButtonEvent(0, true); 337 | } 338 | 339 | constexpr int mapping[][2] = { 340 | {ImGuiKey_GamepadFaceDown, HidNpadButton_A}, 341 | {ImGuiKey_GamepadFaceRight, HidNpadButton_B}, 342 | {ImGuiKey_GamepadFaceUp, HidNpadButton_X}, 343 | {ImGuiKey_GamepadFaceLeft, HidNpadButton_Y}, 344 | {ImGuiKey_GamepadL1, HidNpadButton_L}, 345 | {ImGuiKey_GamepadR1, HidNpadButton_R}, 346 | {ImGuiKey_GamepadL2, HidNpadButton_ZL}, 347 | {ImGuiKey_GamepadR2, HidNpadButton_ZR}, 348 | {ImGuiKey_GamepadStart, HidNpadButton_Plus}, 349 | {ImGuiKey_GamepadBack, HidNpadButton_Minus}, 350 | {ImGuiKey_GamepadDpadLeft, HidNpadButton_Left}, 351 | {ImGuiKey_GamepadDpadRight, HidNpadButton_Right}, 352 | {ImGuiKey_GamepadDpadUp, HidNpadButton_Up}, 353 | {ImGuiKey_GamepadDpadDown, HidNpadButton_Down}, 354 | {ImGuiKey_GamepadLStickLeft, HidNpadButton_StickLLeft}, 355 | {ImGuiKey_GamepadLStickRight, HidNpadButton_StickLRight}, 356 | {ImGuiKey_GamepadLStickUp, HidNpadButton_StickLUp}, 357 | {ImGuiKey_GamepadLStickDown, HidNpadButton_StickLDown}, 358 | }; 359 | 360 | for (int i = 0; i < IM_ARRAYSIZE(mapping); ++i) { 361 | auto [im_k, nx_k] = mapping[i]; 362 | if (down & nx_k) 363 | io.AddKeyEvent((ImGuiKey)im_k, true); 364 | else if (up & nx_k) 365 | io.AddKeyEvent((ImGuiKey)im_k, false); 366 | } 367 | return up; 368 | } 369 | 370 | void ImGui_ImplDeko3d_NewFrame() { 371 | ImGuiIO &io = ImGui::GetIO(); 372 | ImGui_ImplDeko3d_Data *bd = getBackendData(); 373 | io.DisplaySize = ImVec2(FB_WIDTH, FB_HEIGHT); 374 | u64 tick = armGetSystemTick(); 375 | io.DeltaTime = armTicksToNs(tick - bd->last_tick) / 1e9; 376 | bd->last_tick = tick; 377 | } 378 | 379 | static void SetupDeko3dRenderState(ImGui_ImplDeko3d_Data *bd, dk::CmdBuf cmdbuf, 380 | int slot) { 381 | ImGuiIO &io = ImGui::GetIO(); 382 | dk::ImageView imageView(bd->framebuffers[slot]); 383 | dk::ImageView depthView(bd->depthbuffer); 384 | cmdbuf.bindRenderTargets(&imageView, &depthView); 385 | cmdbuf.setViewports(0, {{0.0f, 0.0f, io.DisplaySize.x, io.DisplaySize.y}}); 386 | cmdbuf.setScissors( 387 | 0, DkScissor{0, 0, (u32)io.DisplaySize.x, (u32)io.DisplaySize.y}); 388 | cmdbuf.clearColor(0, DkColorMask_RGBA, 0.0f, 0.0f, 0.0f, 1.0f); 389 | cmdbuf.clearDepthStencil(true, 1.0f, 0xFF, 0); 390 | cmdbuf.bindShaders(DkStageFlag_GraphicsMask, 391 | {&bd->vertexShader, &bd->fragmentShader}); 392 | cmdbuf.bindRasterizerState(dk::RasterizerState{}.setCullMode(DkFace_None)); 393 | cmdbuf.bindColorState(dk::ColorState{}.setBlendEnable(0, true)); 394 | cmdbuf.bindColorWriteState(dk::ColorWriteState{}); 395 | cmdbuf.bindDepthStencilState( 396 | dk::DepthStencilState{}.setDepthTestEnable(false)); 397 | cmdbuf.bindBlendStates(0, dk::BlendState{}); 398 | 399 | VertUBO vertUBO; 400 | vertUBO.proj = glm::orthoRH_ZO(0.0f, io.DisplaySize.x, io.DisplaySize.y, 0.0f, 401 | -1.0f, 1.0f); 402 | DkGpuAddr vertUBOGpuAddr = bd->uboMemBlock.getGpuAddr(); 403 | size_t vertUBOSize = align(sizeof(vertUBO), DK_UNIFORM_BUF_ALIGNMENT); 404 | cmdbuf.bindUniformBuffer(DkStage_Vertex, 0, vertUBOGpuAddr, vertUBOSize); 405 | cmdbuf.pushConstants(vertUBOGpuAddr, vertUBOSize, 0, sizeof(VertUBO), 406 | &vertUBO); 407 | 408 | cmdbuf.bindVtxAttribState({ 409 | // clang-format off 410 | DkVtxAttribState{0, 0, offsetof(ImDrawVert, pos), DkVtxAttribSize_2x32, DkVtxAttribType_Float, 0}, 411 | DkVtxAttribState{0, 0, offsetof(ImDrawVert, uv), DkVtxAttribSize_2x32, DkVtxAttribType_Float, 0}, 412 | DkVtxAttribState{0, 0, offsetof(ImDrawVert, col), DkVtxAttribSize_4x8, DkVtxAttribType_Unorm, 0}, 413 | // clang-format on 414 | }); 415 | cmdbuf.bindVtxBufferState({DkVtxBufferState{sizeof(ImDrawVert), 0}}); 416 | bd->queue.submitCommands(cmdbuf.finishList()); 417 | } 418 | 419 | void ImGui_ImplDeko3d_RenderDrawData(ImDrawData *drawData) { 420 | ImGui_ImplDeko3d_Data *bd = getBackendData(); 421 | 422 | // acquire a framebuffer from the swapchain (and wait for it to be available) 423 | int slot = dkQueueAcquireImage(bd->queue, bd->swapchain); 424 | dk::CmdBuf cmdbuf = bd->cmdbuf[slot]; 425 | cmdbuf.clear(); 426 | SetupDeko3dRenderState(bd, cmdbuf, slot); 427 | 428 | // init or grow vertex/index buffer if not enough 429 | size_t totVtxSize = std::max(drawData->TotalVtxCount * sizeof(ImDrawVert), 430 | (size_t)DK_MEMBLOCK_ALIGNMENT * 16); 431 | if (!bd->vtxMemBlock[slot] || bd->vtxMemBlock[slot].getSize() < totVtxSize) { 432 | bd->vtxMemBlock[slot] = nullptr; // destroy 433 | bd->vtxMemBlock[slot] = 434 | dk::MemBlockMaker(bd->device, 435 | align(2 * totVtxSize, DK_MEMBLOCK_ALIGNMENT)) 436 | .setFlags(DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) 437 | .create(); 438 | } 439 | size_t totIdxSize = std::max(drawData->TotalIdxCount * sizeof(ImDrawIdx), 440 | (size_t)DK_MEMBLOCK_ALIGNMENT * 16); 441 | if (!bd->idxMemBlock[slot] || bd->idxMemBlock[slot].getSize() < totIdxSize) { 442 | bd->idxMemBlock[slot] = nullptr; // destroy 443 | bd->idxMemBlock[slot] = 444 | dk::MemBlockMaker(bd->device, 445 | align(2 * totIdxSize, DK_MEMBLOCK_ALIGNMENT)) 446 | .setFlags(DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) 447 | .create(); 448 | } 449 | 450 | // bind vertex/index buffer 451 | static_assert(sizeof(ImDrawIdx) == sizeof(uint16_t), ""); 452 | cmdbuf.bindVtxBuffer(0, bd->vtxMemBlock[slot].getGpuAddr(), 453 | bd->vtxMemBlock[slot].getSize()); 454 | cmdbuf.bindIdxBuffer(DkIdxFormat_Uint16, bd->idxMemBlock[slot].getGpuAddr()); 455 | 456 | DkResHandle boundTextureHandle = ~0; 457 | size_t vtxOffset = 0, idxOffset = 0; 458 | for (int i = 0; i < drawData->CmdListsCount; ++i) { 459 | const ImDrawList &cmdList = *drawData->CmdLists[i]; 460 | size_t vtxSize = cmdList.VtxBuffer.Size * sizeof(ImDrawVert); 461 | size_t idxSize = cmdList.IdxBuffer.Size * sizeof(ImDrawIdx); 462 | // copy vertex/index data to buffer 463 | memcpy((char *)bd->vtxMemBlock[slot].getCpuAddr() + vtxOffset, 464 | cmdList.VtxBuffer.Data, vtxSize); 465 | memcpy((char *)bd->idxMemBlock[slot].getCpuAddr() + idxOffset, 466 | cmdList.IdxBuffer.Data, idxSize); 467 | 468 | for (auto const &cmd : cmdList.CmdBuffer) { 469 | ImVec4 clip = cmd.ClipRect; 470 | cmdbuf.setScissors(0, 471 | DkScissor{u32(clip.x), u32(clip.y), 472 | u32(clip.z - clip.x), u32(clip.w - clip.y)}); 473 | DkResHandle textureHandle = *(DkResHandle *)cmd.TextureId; 474 | // check if we need to bind a new texture 475 | if (textureHandle != boundTextureHandle) { 476 | boundTextureHandle = textureHandle; 477 | cmdbuf.bindTextures(DkStage_Fragment, 0, textureHandle); 478 | } 479 | // draw the triangle list 480 | cmdbuf.drawIndexed(DkPrimitive_Triangles, cmd.ElemCount, 1, 481 | cmd.IdxOffset + idxOffset / sizeof(ImDrawIdx), 482 | cmd.VtxOffset + vtxOffset / sizeof(ImDrawVert), 0); 483 | } 484 | vtxOffset += vtxSize; 485 | idxOffset += idxSize; 486 | } 487 | 488 | cmdbuf.barrier(DkBarrier_Fragments, 0); 489 | cmdbuf.discardDepthStencil(); 490 | 491 | bd->queue.submitCommands(cmdbuf.finishList()); 492 | bd->queue.presentImage(bd->swapchain, slot); 493 | } 494 | -------------------------------------------------------------------------------- /src/imgui_impl_deko3d.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "imgui.h" 4 | 5 | IMGUI_IMPL_API void ImGui_ImplDeko3d_Init(); 6 | IMGUI_IMPL_API void ImGui_ImplDeko3d_Shutdown(); 7 | IMGUI_IMPL_API void ImGui_ImplDeko3d_NewFrame(); 8 | IMGUI_IMPL_API void ImGui_ImplDeko3d_RenderDrawData(ImDrawData *drawData); 9 | 10 | IMGUI_IMPL_API uint64_t ImGui_ImplDeko3d_UpdatePad(); 11 | -------------------------------------------------------------------------------- /src/imgui_vsh.glsl: -------------------------------------------------------------------------------- 1 | #version 460 2 | 3 | layout (location = 0) in vec2 inPos; 4 | layout (location = 1) in vec2 inUv; 5 | layout (location = 2) in vec4 inColor; 6 | 7 | layout (location = 0) out vec2 vtxUv; 8 | layout (location = 1) out vec4 vtxColor; 9 | 10 | layout (std140, binding = 0) uniform VertUBO { 11 | mat4 proj; 12 | } ubo; 13 | 14 | void main() { 15 | gl_Position = ubo.proj * vec4(inPos, 0.0, 1.0); 16 | vtxUv = inUv; 17 | vtxColor = inColor; 18 | } 19 | -------------------------------------------------------------------------------- /src/main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "imgui_impl_deko3d.h" 7 | #include "util.h" 8 | 9 | extern "C" void userAppInit() { 10 | plInitialize(PlServiceType_User); 11 | romfsInit(); 12 | #ifdef DEBUG 13 | socketInitializeDefault(); 14 | nxlinkStdio(); 15 | #endif 16 | } 17 | 18 | extern "C" void userAppExit() { 19 | plExit(); 20 | romfsExit(); 21 | #ifdef DEBUG 22 | socketExit(); 23 | #endif 24 | } 25 | 26 | int main(int argc, char *argv[]) { 27 | IMGUI_CHECKVERSION(); 28 | ImGui::CreateContext(); 29 | ImGui::StyleColorsDark(); 30 | // ImGuiIO &io = ImGui::GetIO(); // to configure imgui 31 | 32 | ImGui_ImplDeko3d_Init(); 33 | 34 | while (appletMainLoop()) { 35 | u64 down = ImGui_ImplDeko3d_UpdatePad(); 36 | if (down & HidNpadButton_Plus) // "+" to exit 37 | break; 38 | 39 | ImGui_ImplDeko3d_NewFrame(); 40 | ImGui::NewFrame(); 41 | 42 | bool open; 43 | ImGui::ShowDemoWindow(&open); 44 | 45 | ImGui::Render(); 46 | ImGui_ImplDeko3d_RenderDrawData(ImGui::GetDrawData()); 47 | } 48 | 49 | ImGui_ImplDeko3d_Shutdown(); 50 | ImGui::DestroyContext(); 51 | return 0; 52 | } 53 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace ImGui { 7 | void DebugLog(const char *, ...); 8 | } 9 | 10 | #define MSGBOX(fmt, ...) \ 11 | do { \ 12 | char buf[256]; \ 13 | snprintf(buf, sizeof(buf), fmt, ##__VA_ARGS__); \ 14 | ErrorApplicationConfig c; \ 15 | errorApplicationCreate(&c, buf, 0); \ 16 | errorApplicationSetNumber(&c, 0); \ 17 | errorApplicationShow(&c); \ 18 | } while (0) 19 | --------------------------------------------------------------------------------