ftpiiu_plugin/source/switch/imgui_deko3d.cpp

820 lines
26 KiB
C++
Raw Normal View History

2020-04-05 21:16:16 +02:00
// 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
//
// Copyright (C) 2020 Michael Theall
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "imgui_deko3d.h"
#include "imgui.h"
#include "fs.h"
#include "log.h"
#include <deko3d.hpp>
#include <switch.h>
#include <zstd.h>
#define GLM_FORCE_DEFAULT_ALIGNED_GENTYPES
#define GLM_FORCE_INTRINSICS
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/mat4x4.hpp>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <numeric>
namespace
{
2020-04-06 07:36:03 +02:00
/// \brief deko3d logo width
constexpr auto LOGO_WIDTH = 500;
/// \brief deko3d logo height
2020-04-05 21:16:16 +02:00
constexpr auto LOGO_HEIGHT = 493;
2020-04-06 07:36:03 +02:00
/// \brief Number of framebuffers
2020-04-05 21:16:16 +02:00
constexpr auto FB_NUM = 2u;
2020-04-06 07:36:03 +02:00
/// \brief Command buffer size
constexpr auto CMDBUF_SIZE = 1024 * 1024;
/// \brief Data buffer size
constexpr auto DATABUF_SIZE = 1024 * 1024;
/// \brief Index buffer size
2020-04-05 21:16:16 +02:00
constexpr auto INDEXBUF_SIZE = 1024 * 1024;
2020-04-06 07:36:03 +02:00
/// \brief Image buffer size
2020-04-05 21:16:16 +02:00
constexpr auto IMAGEBUF_SIZE = 16 * 1024 * 1024;
2020-04-06 07:36:03 +02:00
/// \brief Vertex shader UBO
2020-04-05 21:16:16 +02:00
struct VertUBO
{
2020-04-06 07:36:03 +02:00
/// \brief Projection matrix
2020-04-05 21:16:16 +02:00
glm::mat4 projMtx;
};
2020-04-06 07:36:03 +02:00
/// \brief Fragment shader UBO
2020-04-05 21:16:16 +02:00
struct FragUBO
{
2020-04-06 07:36:03 +02:00
/// \brief Whether drawing a font or not
2020-04-05 21:16:16 +02:00
std::uint32_t font;
};
2020-04-06 07:36:03 +02:00
/// \brief Vertex attribute state
constexpr std::array VERTEX_ATTRIB_STATE = {
2020-04-05 21:16:16 +02:00
// 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
};
2020-04-06 07:36:03 +02:00
/// \brief Vertex buffer state
constexpr std::array VERTEX_BUFFER_STATE = {
2020-04-05 21:16:16 +02:00
DkVtxBufferState{sizeof (ImDrawVert), 0},
};
2020-04-06 07:36:03 +02:00
/// \brief deko3d device
2020-04-05 21:16:16 +02:00
dk::UniqueDevice s_device;
2020-04-06 07:36:03 +02:00
/// \brief Depth buffer memblock
2020-04-05 21:16:16 +02:00
dk::UniqueMemBlock s_depthMemBlock;
2020-04-06 07:36:03 +02:00
/// \brief Depth buffer image
2020-04-05 21:16:16 +02:00
dk::Image s_depthBuffer;
2020-04-06 07:36:03 +02:00
/// \brief Framebuffer memblock
2020-04-05 21:16:16 +02:00
dk::UniqueMemBlock s_fbMemBlock;
2020-04-06 07:36:03 +02:00
/// \brief Framebuffer images
2020-04-05 21:16:16 +02:00
dk::Image s_frameBuffers[FB_NUM];
2020-04-06 07:36:03 +02:00
/// \brief Font image
2020-04-05 21:16:16 +02:00
dk::Image s_fontTexture;
2020-04-06 07:36:03 +02:00
/// \brief deko3d logo image
2020-04-05 21:16:16 +02:00
dk::Image s_logoTexture;
2020-04-06 07:36:03 +02:00
/// \brief deko3d swapchain
2020-04-05 21:16:16 +02:00
dk::UniqueSwapchain s_swapchain;
2020-04-06 07:36:03 +02:00
/// \brief Shader code memblock
2020-04-05 21:16:16 +02:00
dk::UniqueMemBlock s_codeMemBlock;
2020-04-06 07:36:03 +02:00
/// \brief Shaders (vertex, fragment)
2020-04-05 21:16:16 +02:00
dk::Shader s_shaders[2];
2020-04-06 07:36:03 +02:00
/// \brief UBO memblock
2020-04-05 21:16:16 +02:00
dk::UniqueMemBlock s_uboMemBlock;
2020-04-06 07:36:03 +02:00
/// \brief Vertex data memblock
2020-04-05 21:16:16 +02:00
dk::UniqueMemBlock s_vtxMemBlock[FB_NUM];
2020-04-06 07:36:03 +02:00
/// \brief Index data memblock
2020-04-05 21:16:16 +02:00
dk::UniqueMemBlock s_idxMemBlock[FB_NUM];
2020-04-06 07:36:03 +02:00
/// \brief Command buffer memblock
2020-04-05 21:16:16 +02:00
dk::UniqueMemBlock s_cmdMemBlock[FB_NUM];
2020-04-06 07:36:03 +02:00
/// \brief Command buffers
2020-04-05 21:16:16 +02:00
dk::UniqueCmdBuf s_cmdBuf[FB_NUM];
2020-04-06 07:36:03 +02:00
/// \brief Image memblock
2020-04-05 21:16:16 +02:00
dk::UniqueMemBlock s_imageMemBlock;
2020-04-06 07:36:03 +02:00
/// \brief Image/Sampler descriptor memblock
2020-04-05 21:16:16 +02:00
dk::UniqueMemBlock s_descriptorMemBlock;
2020-04-06 07:36:03 +02:00
/// \brief deko3d queue
2020-04-05 21:16:16 +02:00
dk::UniqueQueue s_queue;
2020-04-06 07:36:03 +02:00
/// \brief Maximum number of samplers
constexpr auto MAX_SAMPLERS = 1;
/// \brief Maximum number of images
constexpr auto MAX_IMAGES = 2;
/// \brief Sample descriptors
2020-04-05 21:16:16 +02:00
dk::SamplerDescriptor *s_samplerDescriptors = nullptr;
2020-04-06 07:36:03 +02:00
/// \brief Image descriptors
dk::ImageDescriptor *s_imageDescriptors = nullptr;
2020-04-05 21:16:16 +02:00
2020-04-06 07:36:03 +02:00
/// \brief Currently bound image descriptor
2020-04-05 21:16:16 +02:00
std::uintptr_t s_boundDescriptor = 0;
2020-04-06 07:36:03 +02:00
/// \brief Framebuffer width
unsigned s_width = 0;
/// \brief Framebuffer height
2020-04-05 21:16:16 +02:00
unsigned s_height = 0;
2020-04-06 07:36:03 +02:00
/// \brief Align value
/// \tparam T Value type
/// \tparam U Alignment type
/// \param size_ Value to align
/// \param align_ Alignment
2020-04-05 21:16:16 +02:00
template <typename T, typename U>
constexpr inline std::uint32_t align (T const &size_, U const &align_)
{
return static_cast<std::uint32_t> (size_ + align_ - 1) & ~(align_ - 1);
}
2020-04-06 07:36:03 +02:00
/// \brief Rebuild swapchain
/// \param width_ Framebuffer width
/// \param height_ Framebuffer height
/// \note This assumes the first call is the largest a framebuffer will ever be
2020-04-05 21:16:16 +02:00
void rebuildSwapchain (unsigned const width_, unsigned const height_)
{
2020-04-06 07:36:03 +02:00
// destroy old swapchain
2020-04-05 21:16:16 +02:00
s_swapchain = nullptr;
2020-04-06 07:36:03 +02:00
// create new depth buffer image layout
2020-04-05 21:16:16 +02:00
dk::ImageLayout depthLayout;
dk::ImageLayoutMaker{s_device}
.setFlags (DkImageFlags_UsageRender | DkImageFlags_HwCompression)
.setFormat (DkImageFormat_Z24S8)
.setDimensions (width_, height_)
.initialize (depthLayout);
auto const depthAlign = depthLayout.getAlignment ();
auto const depthSize = depthLayout.getSize ();
2020-04-06 07:36:03 +02:00
// create depth buffer memblock
2020-04-05 21:16:16 +02:00
if (!s_depthMemBlock)
{
s_depthMemBlock = dk::MemBlockMaker{s_device,
align (depthSize, std::max<unsigned> (depthAlign, DK_MEMBLOCK_ALIGNMENT))}
.setFlags (DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image)
.create ();
}
s_depthBuffer.initialize (depthLayout, s_depthMemBlock, 0);
2020-04-06 07:36:03 +02:00
// create framebuffer image layout
2020-04-05 21:16:16 +02:00
dk::ImageLayout fbLayout;
dk::ImageLayoutMaker{s_device}
.setFlags (
DkImageFlags_UsageRender | DkImageFlags_UsagePresent | DkImageFlags_HwCompression)
.setFormat (DkImageFormat_RGBA8_Unorm)
.setDimensions (width_, height_)
.initialize (fbLayout);
auto const fbAlign = fbLayout.getAlignment ();
auto const fbSize = fbLayout.getSize ();
2020-04-06 07:36:03 +02:00
// create framebuffer memblock
2020-04-05 21:16:16 +02:00
if (!s_fbMemBlock)
{
s_fbMemBlock = dk::MemBlockMaker{s_device,
align (FB_NUM * fbSize, std::max<unsigned> (fbAlign, DK_MEMBLOCK_ALIGNMENT))}
.setFlags (DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image)
.create ();
}
2020-04-06 07:36:03 +02:00
// initialize swapchain images
2020-04-05 21:16:16 +02:00
std::array<DkImage const *, FB_NUM> swapchainImages;
for (unsigned i = 0; i < FB_NUM; ++i)
{
swapchainImages[i] = &s_frameBuffers[i];
s_frameBuffers[i].initialize (fbLayout, s_fbMemBlock, i * fbSize);
}
2020-04-06 07:36:03 +02:00
// create swapchain
2020-04-05 21:16:16 +02:00
s_swapchain = dk::SwapchainMaker{s_device, nwindowGetDefault (), swapchainImages}.create ();
}
2020-04-06 07:36:03 +02:00
/// \brief Load shader code
2020-04-05 21:16:16 +02:00
void loadShaders ()
{
2020-04-06 07:36:03 +02:00
/// \brief Shader file descriptor
2020-04-05 21:16:16 +02:00
struct ShaderFile
{
2020-04-06 07:36:03 +02:00
/// \brief Parameterized constructor
/// \param shader_ Shader object
/// \param path_ Path to source code
2020-04-05 21:16:16 +02:00
ShaderFile (dk::Shader &shader_, char const *const path_)
: shader (shader_), path (path_), size (getSize (path_))
{
}
2020-04-06 07:36:03 +02:00
/// \brief Get size of a file
/// \param path_ Path to file
2020-04-05 21:16:16 +02:00
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;
}
2020-04-06 07:36:03 +02:00
/// \brief Shader object
2020-04-05 21:16:16 +02:00
dk::Shader &shader;
2020-04-06 07:36:03 +02:00
/// \brief Path to source code
2020-04-05 21:16:16 +02:00
char const *const path;
2020-04-06 07:36:03 +02:00
/// \brief Source code file size
2020-04-05 21:16:16 +02:00
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"}};
2020-04-06 07:36:03 +02:00
// calculate total size of shaders
2020-04-05 21:16:16 +02:00
auto const codeSize = std::accumulate (std::begin (shaderFiles),
std::end (shaderFiles),
DK_SHADER_CODE_UNUSABLE_SIZE,
[] (auto const sum_, auto const &file_) {
return sum_ + align (file_.size, DK_SHADER_CODE_ALIGNMENT);
});
2020-04-06 07:36:03 +02:00
// create shader code memblock
2020-04-05 21:16:16 +02:00
s_codeMemBlock = dk::MemBlockMaker{s_device, align (codeSize, DK_MEMBLOCK_ALIGNMENT)}
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached |
DkMemBlockFlags_Code)
.create ();
auto const addr = static_cast<std::uint8_t *> (s_codeMemBlock.getCpuAddr ());
std::size_t offset = 0;
2020-04-06 07:36:03 +02:00
// read shaders into memblock
2020-04-05 21:16:16 +02:00
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 = align (offset + file.size, DK_SHADER_CODE_ALIGNMENT);
}
}
2020-04-06 07:36:03 +02:00
/// \brief Setup render state
/// \param slot_ Swapchain slot
/// \param drawData_ Data to draw
/// \param width_ Framebuffer width
/// \param height_ Framebuffer height
2020-04-05 21:16:16 +02:00
DkCmdList setupRenderState (int const slot_,
ImDrawData *const drawData_,
unsigned const width_,
unsigned const height_)
{
2020-04-06 07:36:03 +02:00
// setup viewport, orthographic projection matrix
// our visible imgui space lies from drawData_->DisplayPos (top left) to
2020-04-05 21:16:16 +02:00
// 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);
2020-04-06 07:36:03 +02:00
// create command buffer to initialize/reset render state
2020-04-05 21:16:16 +02:00
s_cmdBuf[slot_].setViewports (0, DkViewport{0.0f, 0.0f, width_, height_});
s_cmdBuf[slot_].bindShaders (DkStageFlag_GraphicsMask, {&s_shaders[0], &s_shaders[1]});
s_cmdBuf[slot_].bindUniformBuffer (DkStage_Vertex,
0,
s_uboMemBlock.getGpuAddr (),
align (sizeof (VertUBO), DK_UNIFORM_BUF_ALIGNMENT));
s_cmdBuf[slot_].pushConstants (s_uboMemBlock.getGpuAddr (),
align (sizeof (VertUBO), DK_UNIFORM_BUF_ALIGNMENT),
0,
sizeof (VertUBO),
&vertUBO);
s_cmdBuf[slot_].bindUniformBuffer (DkStage_Fragment,
0,
s_uboMemBlock.getGpuAddr () + align (sizeof (VertUBO), DK_UNIFORM_BUF_ALIGNMENT),
align (sizeof (FragUBO), DK_UNIFORM_BUF_ALIGNMENT));
s_cmdBuf[slot_].bindRasterizerState (dk::RasterizerState{}.setCullMode (DkFace_None));
s_cmdBuf[slot_].bindColorState (dk::ColorState{}.setBlendEnable (0, true));
s_cmdBuf[slot_].bindColorWriteState (dk::ColorWriteState{});
s_cmdBuf[slot_].bindDepthStencilState (dk::DepthStencilState{}.setDepthTestEnable (false));
s_cmdBuf[slot_].bindBlendStates (0,
dk::BlendState{}.setFactors (DkBlendFactor_SrcAlpha,
DkBlendFactor_InvSrcAlpha,
DkBlendFactor_InvSrcAlpha,
DkBlendFactor_Zero));
2020-04-06 07:36:03 +02:00
s_cmdBuf[slot_].bindVtxAttribState (VERTEX_ATTRIB_STATE);
s_cmdBuf[slot_].bindVtxBufferState (VERTEX_BUFFER_STATE);
2020-04-05 21:16:16 +02:00
return s_cmdBuf[slot_].finishList ();
}
}
void imgui::deko3d::init ()
{
2020-04-06 07:36:03 +02:00
// setup back-end capabilities flags
2020-04-05 21:16:16 +02:00
ImGuiIO &io = ImGui::GetIO ();
io.BackendRendererName = "deko3d";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
2020-04-06 07:36:03 +02:00
// defer initialization to first newFrame ()
2020-04-05 21:16:16 +02:00
}
void imgui::deko3d::exit ()
{
2020-04-06 07:36:03 +02:00
// wait for queue to be idle
2020-04-05 21:16:16 +02:00
s_queue.waitIdle ();
2020-04-06 07:36:03 +02:00
// clean up all of the deko3d objects
2020-04-05 21:16:16 +02:00
s_queue = nullptr;
s_descriptorMemBlock = nullptr;
s_imageMemBlock = nullptr;
for (unsigned i = 0; i < FB_NUM; ++i)
{
s_cmdBuf[i] = nullptr;
s_cmdMemBlock[i] = nullptr;
s_idxMemBlock[i] = nullptr;
s_vtxMemBlock[i] = nullptr;
}
s_uboMemBlock = nullptr;
s_codeMemBlock = nullptr;
s_swapchain = nullptr;
s_fbMemBlock = nullptr;
s_depthMemBlock = nullptr;
s_device = nullptr;
}
void imgui::deko3d::newFrame ()
{
if (s_device)
return;
2020-04-06 07:36:03 +02:00
// create deko3d device
2020-04-05 21:16:16 +02:00
s_device = dk::DeviceMaker{}.create ();
2020-04-06 07:36:03 +02:00
// initialize swapchain with maximum resolution
2020-04-05 21:16:16 +02:00
rebuildSwapchain (1920, 1080);
2020-04-06 07:36:03 +02:00
// load shader code
2020-04-05 21:16:16 +02:00
loadShaders ();
2020-04-06 07:36:03 +02:00
// create UBO memblock
2020-04-05 21:16:16 +02:00
s_uboMemBlock = dk::MemBlockMaker{s_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 ();
2020-04-06 07:36:03 +02:00
// create memblocks for each framebuffer slot
2020-04-05 21:16:16 +02:00
for (std::size_t i = 0; i < FB_NUM; ++i)
{
2020-04-06 07:36:03 +02:00
// create vertex data memblock
2020-04-05 21:16:16 +02:00
s_vtxMemBlock[i] = dk::MemBlockMaker{s_device, align (DATABUF_SIZE, DK_MEMBLOCK_ALIGNMENT)}
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
.create ();
2020-04-06 07:36:03 +02:00
// create index data memblock
2020-04-05 21:16:16 +02:00
s_idxMemBlock[i] = dk::MemBlockMaker{s_device, align (INDEXBUF_SIZE, DK_MEMBLOCK_ALIGNMENT)}
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
.create ();
2020-04-06 07:36:03 +02:00
// create command buffer memblock
2020-04-05 21:16:16 +02:00
s_cmdMemBlock[i] = dk::MemBlockMaker{s_device, align (CMDBUF_SIZE, DK_MEMBLOCK_ALIGNMENT)}
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
.create ();
2020-04-06 07:36:03 +02:00
// create command buffer
2020-04-05 21:16:16 +02:00
s_cmdBuf[i] = dk::CmdBufMaker{s_device}.create ();
s_cmdBuf[i].addMemory (s_cmdMemBlock[i], 0, s_cmdMemBlock[i].getSize ());
}
2020-04-06 07:36:03 +02:00
// create queue
2020-04-05 21:16:16 +02:00
s_queue = dk::QueueMaker{s_device}.setFlags (DkQueueFlags_Graphics).create ();
2020-04-06 07:36:03 +02:00
// create image memblock
2020-04-05 21:16:16 +02:00
s_imageMemBlock = dk::MemBlockMaker{s_device, align (IMAGEBUF_SIZE, DK_MEMBLOCK_ALIGNMENT)}
.setFlags (DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image)
.create ();
2020-04-06 07:36:03 +02:00
// get texture atlas
2020-04-05 21:16:16 +02:00
ImGuiIO &io = ImGui::GetIO ();
io.Fonts->SetTexID (nullptr);
unsigned char *pixels;
int width;
int height;
io.Fonts->GetTexDataAsAlpha8 (&pixels, &width, &height);
2020-04-06 07:36:03 +02:00
// create memblock for transfer
2020-04-05 21:16:16 +02:00
dk::UniqueMemBlock memBlock =
dk::MemBlockMaker{s_device, align (width * height, DK_MEMBLOCK_ALIGNMENT)}
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
.create ();
std::memcpy (memBlock.getCpuAddr (), pixels, width * height);
2020-04-06 07:36:03 +02:00
// create image/sampler memblock
2020-04-05 21:16:16 +02:00
static_assert (sizeof (dk::ImageDescriptor) == DK_IMAGE_DESCRIPTOR_ALIGNMENT);
static_assert (sizeof (dk::SamplerDescriptor) == DK_SAMPLER_DESCRIPTOR_ALIGNMENT);
static_assert (DK_IMAGE_DESCRIPTOR_ALIGNMENT == DK_SAMPLER_DESCRIPTOR_ALIGNMENT);
s_descriptorMemBlock = dk::MemBlockMaker{s_device,
align ((MAX_SAMPLERS + MAX_IMAGES) * sizeof (dk::ImageDescriptor), DK_MEMBLOCK_ALIGNMENT)}
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
.create ();
2020-04-06 07:36:03 +02:00
// get cpu address for descriptors
2020-04-05 21:16:16 +02:00
s_samplerDescriptors =
static_cast<dk::SamplerDescriptor *> (s_descriptorMemBlock.getCpuAddr ());
s_imageDescriptors =
reinterpret_cast<dk::ImageDescriptor *> (&s_samplerDescriptors[MAX_SAMPLERS]);
2020-04-06 07:36:03 +02:00
// initialize sampler descriptor
2020-04-05 21:16:16 +02:00
s_samplerDescriptors[0].initialize (
dk::Sampler{}
.setFilter (DkFilter_Linear, DkFilter_Linear)
.setWrapMode (DkWrapMode_ClampToEdge, DkWrapMode_ClampToEdge, DkWrapMode_ClampToEdge));
2020-04-06 07:36:03 +02:00
// use command buffer 0 for initialization
2020-04-05 21:16:16 +02:00
auto &cmdBuf = s_cmdBuf[0];
2020-04-06 07:36:03 +02:00
// initialize texture atlas image layout
2020-04-05 21:16:16 +02:00
dk::ImageLayout layout;
dk::ImageLayoutMaker{s_device}
.setFlags (0)
.setFormat (DkImageFormat_R8_Unorm)
.setDimensions (width, height)
.initialize (layout);
2020-04-06 07:36:03 +02:00
// initialize font texture atlas image descriptor
2020-04-05 21:16:16 +02:00
s_fontTexture.initialize (layout, s_imageMemBlock, 0);
s_imageDescriptors[0].initialize (s_fontTexture);
2020-04-06 07:36:03 +02:00
// copy font texture atlas to image view
2020-04-05 21:16:16 +02:00
dk::ImageView imageView{s_fontTexture};
cmdBuf.copyBufferToImage ({memBlock.getGpuAddr ()}, imageView, {0, 0, 0, width, height, 1});
2020-04-06 07:36:03 +02:00
// bind image/sampler descriptors
2020-04-05 21:16:16 +02:00
cmdBuf.bindSamplerDescriptorSet (s_descriptorMemBlock.getGpuAddr (), MAX_SAMPLERS);
cmdBuf.bindImageDescriptorSet (
s_descriptorMemBlock.getGpuAddr () + MAX_SAMPLERS * sizeof (dk::SamplerDescriptor),
MAX_IMAGES);
2020-04-06 07:36:03 +02:00
// submit commands while we get the next image ready to transfer
2020-04-05 21:16:16 +02:00
s_queue.submitCommands (cmdBuf.finishList ());
{
2020-04-06 07:36:03 +02:00
// read the deko3d logo
2020-04-05 21:16:16 +02:00
auto const path = "romfs:/deko3d.rgba.zst";
struct stat st;
if (stat (path, &st) != 0)
{
std::fprintf (stderr, "stat(%s): %s\n", path, std::strerror (errno));
std::abort ();
}
fs::File fp;
if (!fp.open (path))
{
std::fprintf (stderr, "open(%s): %s\n", path, std::strerror (errno));
std::abort ();
}
std::vector<char> buffer (st.st_size);
if (!fp.readAll (buffer.data (), st.st_size))
{
std::fprintf (stderr, "read(%s): %s\n", path, std::strerror (errno));
std::abort ();
}
fp.close ();
auto const size = ZSTD_getFrameContentSize (buffer.data (), st.st_size);
if (ZSTD_isError (size))
{
std::fprintf (stderr, "ZSTD_getFrameContentSize: %s\n", ZSTD_getErrorName (size));
std::abort ();
}
2020-04-06 07:36:03 +02:00
// create memblock for transfer
dk::UniqueMemBlock memBlock =
dk::MemBlockMaker{s_device, align (size, DK_MEMBLOCK_ALIGNMENT)}
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
.create ();
2020-04-05 21:16:16 +02:00
2020-04-06 07:36:03 +02:00
// decompress into transfer memblock
2020-04-05 21:16:16 +02:00
auto const decoded =
ZSTD_decompress (memBlock.getCpuAddr (), size, buffer.data (), st.st_size);
if (ZSTD_isError (decoded))
{
std::fprintf (stderr, "ZSTD_decompress: %s\n", ZSTD_getErrorName (decoded));
std::abort ();
}
2020-04-06 07:36:03 +02:00
// initialize deko3d logo texture image layout
2020-04-05 21:16:16 +02:00
dk::ImageLayout layout;
dk::ImageLayoutMaker{s_device}
.setFlags (0)
.setFormat (DkImageFormat_RGBA8_Unorm)
.setDimensions (LOGO_WIDTH, LOGO_HEIGHT)
.initialize (layout);
2020-04-06 07:36:03 +02:00
// initialize deko3d logo texture image descriptor
2020-04-05 21:16:16 +02:00
auto const offset = align (width * height, DK_IMAGE_LINEAR_STRIDE_ALIGNMENT);
s_logoTexture.initialize (layout, s_imageMemBlock, offset);
s_imageDescriptors[1].initialize (s_logoTexture);
2020-04-06 07:36:03 +02:00
// copy deko3d logo texture to image view
2020-04-05 21:16:16 +02:00
dk::ImageView imageView{s_logoTexture};
cmdBuf.copyBufferToImage (
{memBlock.getGpuAddr ()}, imageView, {0, 0, 0, LOGO_WIDTH, LOGO_HEIGHT, 1});
2020-04-06 07:36:03 +02:00
// submit commands to transfer deko3d logo texture
2020-04-05 21:16:16 +02:00
s_queue.submitCommands (cmdBuf.finishList ());
2020-04-06 07:36:03 +02:00
// wait for commands to complete before releasing memblocks
2020-04-05 21:16:16 +02:00
s_queue.waitIdle ();
}
2020-04-06 07:36:03 +02:00
// reset command buffer
2020-04-05 21:16:16 +02:00
cmdBuf.clear ();
}
void imgui::deko3d::render ()
{
2020-04-06 07:36:03 +02:00
// get ImGui draw data
2020-04-05 21:16:16 +02:00
auto const drawData = ImGui::GetDrawData ();
if (drawData->CmdListsCount <= 0)
return;
2020-04-06 07:36:03 +02:00
// get framebuffer dimensions
2020-04-05 21:16:16 +02:00
unsigned width = drawData->DisplaySize.x * drawData->FramebufferScale.x;
unsigned height = drawData->DisplaySize.y * drawData->FramebufferScale.y;
if (width <= 0 || height <= 0)
return;
2020-04-06 07:36:03 +02:00
// check if we need to rebuild the swapchain
2020-04-05 21:16:16 +02:00
if (width != s_width || height != s_height)
{
s_width = width;
s_height = height;
s_queue.waitIdle ();
rebuildSwapchain (width, height);
}
2020-04-06 07:36:03 +02:00
// get image from queue
2020-04-05 21:16:16 +02:00
auto const slot = s_queue.acquireImage (s_swapchain);
s_cmdBuf[slot].clear ();
2020-04-06 07:36:03 +02:00
// bind frame/depth buffers and clear them
2020-04-05 21:16:16 +02:00
dk::ImageView colorTarget{s_frameBuffers[slot]};
dk::ImageView depthTarget{s_depthBuffer};
s_cmdBuf[slot].bindRenderTargets (&colorTarget, &depthTarget);
s_cmdBuf[slot].clearColor (0, DkColorMask_RGBA, 0.125f, 0.294f, 0.478f, 1.0f);
s_cmdBuf[slot].clearDepthStencil (true, 1.0f, 0xFF, 0);
s_queue.submitCommands (s_cmdBuf[slot].finishList ());
2020-04-06 07:36:03 +02:00
// setup desired render state
2020-04-05 21:16:16 +02:00
auto const setupCmd = setupRenderState (slot, drawData, width, height);
s_queue.submitCommands (setupCmd);
2020-04-06 07:36:03 +02:00
// start with bogus descriptor binding so it'll be updated before first draw call
2020-04-05 21:16:16 +02:00
s_boundDescriptor = ~static_cast<std::uintptr_t> (0);
2020-04-06 07:36:03 +02:00
// will project scissor/clipping rectangles into framebuffer space
2020-04-05 21:16:16 +02:00
// (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;
2020-04-06 07:36:03 +02:00
// check if we need to grow vertex data memblock
2020-04-05 21:16:16 +02:00
if (s_vtxMemBlock[slot].getSize () < drawData->TotalVtxCount * sizeof (ImDrawVert))
{
s_vtxMemBlock[slot] =
dk::MemBlockMaker{s_device,
align (drawData->TotalVtxCount * sizeof (ImDrawVert), DK_MEMBLOCK_ALIGNMENT)}
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
.create ();
}
2020-04-06 07:36:03 +02:00
// check if we need to grow index data memblock
2020-04-05 21:16:16 +02:00
if (s_idxMemBlock[slot].getSize () < drawData->TotalIdxCount * sizeof (ImDrawIdx))
{
s_idxMemBlock[slot] =
dk::MemBlockMaker{s_device,
align (drawData->TotalIdxCount * sizeof (ImDrawIdx), DK_MEMBLOCK_ALIGNMENT)}
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
.create ();
}
2020-04-06 07:36:03 +02:00
// get base cpu addresses
2020-04-05 21:16:16 +02:00
auto const cpuVtx = static_cast<std::uint8_t *> (s_vtxMemBlock[slot].getCpuAddr ());
auto const cpuIdx = static_cast<std::uint8_t *> (s_idxMemBlock[slot].getCpuAddr ());
2020-04-06 07:36:03 +02:00
// get base gpu addresses
2020-04-05 21:16:16 +02:00
auto const gpuVtx = s_vtxMemBlock[slot].getGpuAddr ();
auto const gpuIdx = s_idxMemBlock[slot].getGpuAddr ();
2020-04-06 07:36:03 +02:00
// get memblock sizes
2020-04-05 21:16:16 +02:00
auto const sizeVtx = s_vtxMemBlock[slot].getSize ();
auto const sizeIdx = s_idxMemBlock[slot].getSize ();
2020-04-06 07:36:03 +02:00
// bind vertex/index data memblocks
2020-04-05 21:16:16 +02:00
static_assert (sizeof (ImDrawIdx) == 2);
s_cmdBuf[slot].bindVtxBuffer (0, gpuVtx, sizeVtx);
s_cmdBuf[slot].bindIdxBuffer (DkIdxFormat_Uint16, gpuIdx);
2020-04-06 07:36:03 +02:00
// render command lists
2020-04-05 21:16:16 +02:00
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);
2020-04-06 07:36:03 +02:00
// double check that we don't overrun vertex data memblock
2020-04-05 21:16:16 +02:00
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;
}
2020-04-06 07:36:03 +02:00
// double check that we don't overrun index data memblock
2020-04-05 21:16:16 +02:00
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;
}
2020-04-06 07:36:03 +02:00
// copy vertex/index data into memblocks
2020-04-05 21:16:16 +02:00
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)
{
2020-04-06 07:36:03 +02:00
// submit commands to preserve ordering
2020-04-05 21:16:16 +02:00
s_queue.submitCommands (s_cmdBuf[slot].finishList ());
2020-04-06 07:36:03 +02:00
// user callback, registered via ImDrawList::AddCallback()
2020-04-05 21:16:16 +02:00
// (ImDrawCallback_ResetRenderState is a special callback value used by the user to
// request the renderer to reset render state.)
if (cmd.UserCallback == ImDrawCallback_ResetRenderState)
s_queue.submitCommands (setupCmd);
else
cmd.UserCallback (&cmdList, &cmd);
}
else
{
2020-04-06 07:36:03 +02:00
// project scissor/clipping rectangles into framebuffer space
2020-04-05 21:16:16 +02:00
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;
2020-04-06 07:36:03 +02:00
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.z = height;
// apply scissor boundaries
s_cmdBuf[slot].setScissors (
0, DkScissor{clip.x, clip.y, clip.z - clip.x, clip.w - clip.y});
// get image descriptor
auto const descriptor = reinterpret_cast<std::uintptr_t> (cmd.TextureId);
if (descriptor >= MAX_IMAGES)
continue;
// check if we need to bind a new texture
if (descriptor != s_boundDescriptor)
2020-04-05 21:16:16 +02:00
{
2020-04-06 07:36:03 +02:00
s_boundDescriptor = descriptor;
// bind the new texture
s_cmdBuf[slot].bindTextures (
DkStage_Fragment, 0, dkMakeTextureHandle (descriptor, 0));
// check if this is the font texture atlas image descriptor
FragUBO fragUBO;
fragUBO.font = (descriptor == 0);
// update fragment shader UBO
s_cmdBuf[slot].pushConstants (
s_uboMemBlock.getGpuAddr () +
align (sizeof (VertUBO), DK_UNIFORM_BUF_ALIGNMENT),
align (sizeof (FragUBO), DK_UNIFORM_BUF_ALIGNMENT),
0,
sizeof (FragUBO),
&fragUBO);
2020-04-05 21:16:16 +02:00
}
2020-04-06 07:36:03 +02:00
// draw the draw list
s_cmdBuf[slot].drawIndexed (DkPrimitive_Triangles,
cmd.ElemCount,
1,
cmd.IdxOffset + offsetIdx / sizeof (ImDrawIdx),
cmd.VtxOffset + offsetVtx / sizeof (ImDrawVert),
0);
2020-04-05 21:16:16 +02:00
}
}
offsetVtx += vtxSize;
offsetIdx += idxSize;
}
2020-04-06 07:36:03 +02:00
// wait for fragments to be completed before discarding depth/stencil buffer
2020-04-05 21:16:16 +02:00
s_cmdBuf[slot].barrier (DkBarrier_Fragments, 0);
s_cmdBuf[slot].discardDepthStencil ();
2020-04-06 07:36:03 +02:00
// submit final commands
2020-04-05 21:16:16 +02:00
s_queue.submitCommands (s_cmdBuf[slot].finishList ());
2020-04-06 07:36:03 +02:00
// present image
2020-04-05 21:16:16 +02:00
s_queue.presentImage (s_swapchain, slot);
}
void imgui::deko3d::test ()
{
auto const x1 = (s_width - LOGO_WIDTH) / 2.0f;
auto const x2 = x1 + LOGO_WIDTH;
auto const y1 = (s_height - LOGO_HEIGHT) / 2.0f;
auto const y2 = y1 + LOGO_HEIGHT;
ImGui::GetBackgroundDrawList ()->AddImage (
reinterpret_cast<ImTextureID> (1), ImVec2 (x1, y1), ImVec2 (x2, y2));
}