// ftpd is a server implementation based on the following: // - RFC 959 (https://tools.ietf.org/html/rfc959) // - RFC 3659 (https://tools.ietf.org/html/rfc3659) // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html // // The MIT License (MIT) // // Copyright (C) 2020 Michael Theall // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #ifndef CLASSIC #include "imgui_deko3d.h" #include "fs.h" #include "imgui.h" #include #define GLM_FORCE_DEFAULT_ALIGNED_GENTYPES #define GLM_FORCE_INTRINSICS #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { /// \brief Vertex buffer size constexpr auto VTXBUF_SIZE = 1024u * 1024u; /// \brief Index buffer size constexpr auto IDXBUF_SIZE = 1024u * 1024u; /// \brief Vertex shader UBO struct VertUBO { /// \brief Projection matrix glm::mat4 projMtx; }; /// \brief Fragment shader UBO struct FragUBO { /// \brief Whether drawing a font or not std::uint32_t font; }; /// \brief Vertex attribute state constexpr std::array VERTEX_ATTRIB_STATE = { // clang-format off DkVtxAttribState{0, 0, offsetof (ImDrawVert, pos), DkVtxAttribSize_2x32, DkVtxAttribType_Float, 0}, DkVtxAttribState{0, 0, offsetof (ImDrawVert, uv), DkVtxAttribSize_2x32, DkVtxAttribType_Float, 0}, DkVtxAttribState{0, 0, offsetof (ImDrawVert, col), DkVtxAttribSize_4x8, DkVtxAttribType_Unorm, 0}, // clang-format on }; /// \brief Vertex buffer state constexpr std::array VERTEX_BUFFER_STATE = { DkVtxBufferState{sizeof (ImDrawVert), 0}, }; /// \brief Shader code memblock dk::UniqueMemBlock s_codeMemBlock; /// \brief Shaders (vertex, fragment) dk::Shader s_shaders[2]; /// \brief UBO memblock dk::UniqueMemBlock s_uboMemBlock; /// \brief Vertex data memblock std::vector s_vtxMemBlock; /// \brief Index data memblock std::vector s_idxMemBlock; /// \brief Font image memblock dk::UniqueMemBlock s_fontImageMemBlock; /// \brief Font texture handle DkResHandle s_fontTextureHandle; /// \brief Load shader code void loadShaders (dk::UniqueDevice &device_) { /// \brief Shader file descriptor struct ShaderFile { /// \brief Parameterized constructor /// \param shader_ Shader object /// \param path_ Path to source code ShaderFile (dk::Shader &shader_, char const *const path_) : shader (shader_), path (path_), size (getSize (path_)) { } /// \brief Get size of a file /// \param path_ Path to file static std::size_t getSize (char const *const path_) { struct stat st; auto const rc = ::stat (path_, &st); if (rc != 0) { std::fprintf (stderr, "stat(%s): %s\n", path_, std::strerror (errno)); std::abort (); } return st.st_size; } /// \brief Shader object dk::Shader &shader; /// \brief Path to source code char const *const path; /// \brief Source code file size std::size_t const size; }; auto shaderFiles = {ShaderFile{s_shaders[0], "romfs:/shaders/imgui_vsh.dksh"}, ShaderFile{s_shaders[1], "romfs:/shaders/imgui_fsh.dksh"}}; // calculate total size of shaders auto const codeSize = std::accumulate (std::begin (shaderFiles), std::end (shaderFiles), DK_SHADER_CODE_UNUSABLE_SIZE, [] (auto const sum_, auto const &file_) { return sum_ + imgui::deko3d::align (file_.size, DK_SHADER_CODE_ALIGNMENT); }); // create shader code memblock s_codeMemBlock = dk::MemBlockMaker{device_, imgui::deko3d::align (codeSize, DK_MEMBLOCK_ALIGNMENT)} .setFlags ( DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached | DkMemBlockFlags_Code) .create (); auto const addr = static_cast (s_codeMemBlock.getCpuAddr ()); std::size_t offset = 0; // read shaders into memblock for (auto &file : shaderFiles) { std::uint32_t const codeOffset = offset; fs::File fp; if (!fp.open (file.path)) { std::fprintf (stderr, "open(%s): %s\n", file.path, std::strerror (errno)); std::abort (); } if (!fp.readAll (&addr[offset], file.size)) { std::fprintf (stderr, "read(%s): %s\n", file.path, std::strerror (errno)); std::abort (); } dk::ShaderMaker{s_codeMemBlock, codeOffset}.initialize (file.shader); offset = imgui::deko3d::align (offset + file.size, DK_SHADER_CODE_ALIGNMENT); } } /// \brief Setup render state /// \param cmdBuf_ Command buffer /// \param drawData_ Data to draw /// \param width_ Framebuffer width /// \param height_ Framebuffer height DkCmdList setupRenderState (dk::UniqueCmdBuf &cmdBuf_, ImDrawData *const drawData_, unsigned const width_, unsigned const height_) { // setup viewport, orthographic projection matrix // our visible imgui space lies from drawData_->DisplayPos (top left) to // drawData_->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single // viewport apps. auto const L = drawData_->DisplayPos.x; auto const R = drawData_->DisplayPos.x + drawData_->DisplaySize.x; auto const T = drawData_->DisplayPos.y; auto const B = drawData_->DisplayPos.y + drawData_->DisplaySize.y; VertUBO vertUBO; vertUBO.projMtx = glm::orthoRH_ZO (L, R, B, T, -1.0f, 1.0f); // create command buffer to initialize/reset render state cmdBuf_.setViewports (0, DkViewport{0.0f, 0.0f, width_, height_}); cmdBuf_.bindShaders (DkStageFlag_GraphicsMask, {&s_shaders[0], &s_shaders[1]}); cmdBuf_.bindUniformBuffer (DkStage_Vertex, 0, s_uboMemBlock.getGpuAddr (), imgui::deko3d::align (sizeof (VertUBO), DK_UNIFORM_BUF_ALIGNMENT)); cmdBuf_.pushConstants (s_uboMemBlock.getGpuAddr (), imgui::deko3d::align (sizeof (VertUBO), DK_UNIFORM_BUF_ALIGNMENT), 0, sizeof (VertUBO), &vertUBO); cmdBuf_.bindUniformBuffer (DkStage_Fragment, 0, s_uboMemBlock.getGpuAddr () + imgui::deko3d::align (sizeof (VertUBO), DK_UNIFORM_BUF_ALIGNMENT), imgui::deko3d::align (sizeof (FragUBO), DK_UNIFORM_BUF_ALIGNMENT)); cmdBuf_.bindRasterizerState (dk::RasterizerState{}.setCullMode (DkFace_None)); cmdBuf_.bindColorState (dk::ColorState{}.setBlendEnable (0, true)); cmdBuf_.bindColorWriteState (dk::ColorWriteState{}); cmdBuf_.bindDepthStencilState (dk::DepthStencilState{}.setDepthTestEnable (false)); cmdBuf_.bindBlendStates (0, dk::BlendState{}.setFactors (DkBlendFactor_SrcAlpha, DkBlendFactor_InvSrcAlpha, DkBlendFactor_InvSrcAlpha, DkBlendFactor_Zero)); cmdBuf_.bindVtxAttribState (VERTEX_ATTRIB_STATE); cmdBuf_.bindVtxBufferState (VERTEX_BUFFER_STATE); return cmdBuf_.finishList (); } } void imgui::deko3d::init (dk::UniqueDevice &device_, dk::UniqueQueue &queue_, dk::UniqueCmdBuf &cmdBuf_, dk::SamplerDescriptor &samplerDescriptor_, dk::ImageDescriptor &imageDescriptor_, DkResHandle fontTextureHandle_, unsigned const imageCount_) { auto &io = ImGui::GetIO (); // setup back-end capabilities flags io.BackendRendererName = "deko3d"; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // load shader code loadShaders (device_); // create UBO memblock s_uboMemBlock = dk::MemBlockMaker{device_, align (align (sizeof (VertUBO), DK_UNIFORM_BUF_ALIGNMENT) + align (sizeof (FragUBO), DK_UNIFORM_BUF_ALIGNMENT), DK_MEMBLOCK_ALIGNMENT)} .setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) .create (); // create memblocks for each image slot for (std::size_t i = 0; i < imageCount_; ++i) { // create vertex data memblock s_vtxMemBlock.emplace_back ( dk::MemBlockMaker{device_, align (VTXBUF_SIZE, DK_MEMBLOCK_ALIGNMENT)} .setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) .create ()); // create index data memblock s_idxMemBlock.emplace_back ( dk::MemBlockMaker{device_, align (IDXBUF_SIZE, DK_MEMBLOCK_ALIGNMENT)} .setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) .create ()); } // get texture atlas io.Fonts->SetTexID (makeTextureID (fontTextureHandle_)); s_fontTextureHandle = fontTextureHandle_; unsigned char *pixels; int width; int height; io.Fonts->GetTexDataAsAlpha8 (&pixels, &width, &height); // create memblock for transfer dk::UniqueMemBlock memBlock = dk::MemBlockMaker{device_, align (width * height, DK_MEMBLOCK_ALIGNMENT)} .setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) .create (); std::memcpy (memBlock.getCpuAddr (), pixels, width * height); // initialize sampler descriptor samplerDescriptor_.initialize ( dk::Sampler{} .setFilter (DkFilter_Linear, DkFilter_Linear) .setWrapMode (DkWrapMode_ClampToEdge, DkWrapMode_ClampToEdge, DkWrapMode_ClampToEdge)); // initialize texture atlas image layout dk::ImageLayout layout; dk::ImageLayoutMaker{device_} .setFlags (0) .setFormat (DkImageFormat_R8_Unorm) .setDimensions (width, height) .initialize (layout); auto const fontAlign = layout.getAlignment (); auto const fontSize = layout.getSize (); // create image memblock s_fontImageMemBlock = dk::MemBlockMaker{device_, align (fontSize, std::max (fontAlign, DK_MEMBLOCK_ALIGNMENT))} .setFlags (DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image) .create (); // initialize font texture atlas image descriptor dk::Image fontTexture; fontTexture.initialize (layout, s_fontImageMemBlock, 0); imageDescriptor_.initialize (fontTexture); // copy font texture atlas to image view dk::ImageView imageView{fontTexture}; cmdBuf_.copyBufferToImage ({memBlock.getGpuAddr ()}, imageView, {0, 0, 0, width, height, 1}); // submit commands to transfer font texture queue_.submitCommands (cmdBuf_.finishList ()); // wait for commands to complete before releasing memblock queue_.waitIdle (); } void imgui::deko3d::exit () { s_fontImageMemBlock = nullptr; s_idxMemBlock.clear (); s_vtxMemBlock.clear (); s_uboMemBlock = nullptr; s_codeMemBlock = nullptr; } void imgui::deko3d::render (dk::UniqueDevice &device_, dk::UniqueQueue &queue_, dk::UniqueCmdBuf &cmdBuf_, unsigned const slot_) { // get ImGui draw data auto const drawData = ImGui::GetDrawData (); if (drawData->CmdListsCount <= 0) return; // get framebuffer dimensions unsigned width = drawData->DisplaySize.x * drawData->FramebufferScale.x; unsigned height = drawData->DisplaySize.y * drawData->FramebufferScale.y; if (width <= 0 || height <= 0) return; // setup desired render state auto const setupCmd = setupRenderState (cmdBuf_, drawData, width, height); queue_.submitCommands (setupCmd); // currently bound texture std::optional boundTextureHandle; // will project scissor/clipping rectangles into framebuffer space // (0,0) unless using multi-viewports auto const clipOff = drawData->DisplayPos; // (1,1) unless using retina display which are often (2,2) auto const clipScale = drawData->FramebufferScale; // check if we need to grow vertex data memblock if (s_vtxMemBlock[slot_].getSize () < drawData->TotalVtxCount * sizeof (ImDrawVert)) { // add 10% to avoid growing many frames in a row std::size_t const count = drawData->TotalVtxCount * 1.1f; s_vtxMemBlock[slot_] = dk::MemBlockMaker{device_, align (count * sizeof (ImDrawVert), DK_MEMBLOCK_ALIGNMENT)} .setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) .create (); } // check if we need to grow index data memblock if (s_idxMemBlock[slot_].getSize () < drawData->TotalIdxCount * sizeof (ImDrawIdx)) { // add 10% to avoid growing many frames in a row std::size_t const count = drawData->TotalIdxCount * 1.1f; s_idxMemBlock[slot_] = dk::MemBlockMaker{device_, align (count * sizeof (ImDrawIdx), DK_MEMBLOCK_ALIGNMENT)} .setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) .create (); } // get base cpu addresses auto const cpuVtx = static_cast (s_vtxMemBlock[slot_].getCpuAddr ()); auto const cpuIdx = static_cast (s_idxMemBlock[slot_].getCpuAddr ()); // get base gpu addresses auto const gpuVtx = s_vtxMemBlock[slot_].getGpuAddr (); auto const gpuIdx = s_idxMemBlock[slot_].getGpuAddr (); // get memblock sizes auto const sizeVtx = s_vtxMemBlock[slot_].getSize (); auto const sizeIdx = s_idxMemBlock[slot_].getSize (); // bind vertex/index data memblocks static_assert (sizeof (ImDrawIdx) == sizeof (std::uint16_t)); cmdBuf_.bindVtxBuffer (0, gpuVtx, sizeVtx); cmdBuf_.bindIdxBuffer (DkIdxFormat_Uint16, gpuIdx); // render command lists std::size_t offsetVtx = 0; std::size_t offsetIdx = 0; for (int i = 0; i < drawData->CmdListsCount; ++i) { auto const &cmdList = *drawData->CmdLists[i]; auto const vtxSize = cmdList.VtxBuffer.Size * sizeof (ImDrawVert); auto const idxSize = cmdList.IdxBuffer.Size * sizeof (ImDrawIdx); // double check that we don't overrun vertex data memblock if (sizeVtx - offsetVtx < vtxSize) { std::fprintf (stderr, "Not enough vertex buffer\n"); std::fprintf (stderr, "\t%zu/%u used, need %zu\n", offsetVtx, sizeVtx, vtxSize); continue; } // double check that we don't overrun index data memblock if (sizeIdx - offsetIdx < idxSize) { std::fprintf (stderr, "Not enough index buffer\n"); std::fprintf (stderr, "\t%zu/%u used, need %zu\n", offsetIdx, sizeIdx, idxSize); continue; } // copy vertex/index data into memblocks std::memcpy (cpuVtx + offsetVtx, cmdList.VtxBuffer.Data, vtxSize); std::memcpy (cpuIdx + offsetIdx, cmdList.IdxBuffer.Data, idxSize); for (auto const &cmd : cmdList.CmdBuffer) { if (cmd.UserCallback) { // submit commands to preserve ordering queue_.submitCommands (cmdBuf_.finishList ()); // user callback, registered via ImDrawList::AddCallback() // (ImDrawCallback_ResetRenderState is a special callback value used by the user to // request the renderer to reset render state.) if (cmd.UserCallback == ImDrawCallback_ResetRenderState) queue_.submitCommands (setupCmd); else cmd.UserCallback (&cmdList, &cmd); } else { // project scissor/clipping rectangles into framebuffer space ImVec4 clip; clip.x = (cmd.ClipRect.x - clipOff.x) * clipScale.x; clip.y = (cmd.ClipRect.y - clipOff.y) * clipScale.y; clip.z = (cmd.ClipRect.z - clipOff.x) * clipScale.x; clip.w = (cmd.ClipRect.w - clipOff.y) * clipScale.y; // check if clip coordinate are outside of the framebuffer if (clip.x >= width || clip.y >= height || clip.z < 0.0f || clip.w < 0.0f) continue; // keep scissor coordinates inside viewport if (clip.x < 0.0f) clip.x = 0.0f; if (clip.y < 0.0f) clip.y = 0.0f; if (clip.z > width) clip.z = width; if (clip.w > height) clip.w = height; // apply scissor boundaries cmdBuf_.setScissors ( 0, DkScissor{clip.x, clip.y, clip.z - clip.x, clip.w - clip.y}); // get texture handle auto const textureHandle = reinterpret_cast (cmd.TextureId); // check if we need to bind a new texture if (!boundTextureHandle || textureHandle != *boundTextureHandle) { // check if this is the first draw or changing to or from the font texture if (!boundTextureHandle || textureHandle == s_fontTextureHandle || *boundTextureHandle == s_fontTextureHandle) { FragUBO fragUBO; fragUBO.font = (textureHandle == s_fontTextureHandle); // update fragment shader UBO cmdBuf_.pushConstants ( s_uboMemBlock.getGpuAddr () + align (sizeof (VertUBO), DK_UNIFORM_BUF_ALIGNMENT), align (sizeof (FragUBO), DK_UNIFORM_BUF_ALIGNMENT), 0, sizeof (FragUBO), &fragUBO); } boundTextureHandle = textureHandle; // bind the new texture cmdBuf_.bindTextures (DkStage_Fragment, 0, textureHandle); } // draw the draw list cmdBuf_.drawIndexed (DkPrimitive_Triangles, cmd.ElemCount, 1, cmd.IdxOffset + offsetIdx / sizeof (ImDrawIdx), cmd.VtxOffset + offsetVtx / sizeof (ImDrawVert), 0); } } offsetVtx += vtxSize; offsetIdx += idxSize; } // submit final commands queue_.submitCommands (cmdBuf_.finishList ()); } #endif