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_citro3d.h"
|
|
|
|
|
|
|
|
#include <citro3d.h>
|
|
|
|
|
|
|
|
#include "vshader_shbin.h"
|
|
|
|
|
|
|
|
#include "imgui.h"
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#include <cstdlib>
|
|
|
|
#include <cstring>
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
2020-04-06 07:36:03 +02:00
|
|
|
/// \brief 3DS font glyph ranges
|
2020-04-05 21:16:16 +02:00
|
|
|
std::vector<ImWchar> s_fontRanges;
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
/// \brief Vertex shader
|
2020-04-05 21:16:16 +02:00
|
|
|
DVLB_s *s_vsh = nullptr;
|
2020-04-06 07:36:03 +02:00
|
|
|
/// \brief Vertex shader program
|
2020-04-05 21:16:16 +02:00
|
|
|
shaderProgram_s s_program;
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
/// \brief Projection matrix uniform location
|
2020-04-05 21:16:16 +02:00
|
|
|
int s_projLocation;
|
2020-04-06 07:36:03 +02:00
|
|
|
/// \brief Top screen projection matrix
|
2020-04-05 21:16:16 +02:00
|
|
|
C3D_Mtx s_projTop;
|
2020-04-06 07:36:03 +02:00
|
|
|
/// \brief Bottom screen projection matrix
|
2020-04-05 21:16:16 +02:00
|
|
|
C3D_Mtx s_projBottom;
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
/// \brief System font textures
|
2020-04-05 21:16:16 +02:00
|
|
|
std::vector<C3D_Tex> s_fontTextures;
|
2020-04-06 07:36:03 +02:00
|
|
|
/// \brief Text scale
|
2020-04-05 21:16:16 +02:00
|
|
|
float s_textScale;
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
/// \brief Scissor test bounds
|
2020-04-05 21:16:16 +02:00
|
|
|
std::uint32_t s_boundScissor[4];
|
2020-04-06 07:36:03 +02:00
|
|
|
/// \brief Currently bound vertex data
|
2020-04-05 21:16:16 +02:00
|
|
|
ImDrawVert *s_boundVtxData;
|
2020-04-06 07:36:03 +02:00
|
|
|
/// \brief Currently bound texture
|
2020-04-05 21:16:16 +02:00
|
|
|
C3D_Tex *s_boundTexture;
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
/// \brief Vertex data buffer
|
2020-04-05 21:16:16 +02:00
|
|
|
ImDrawVert *s_vtxData = nullptr;
|
2020-04-06 07:36:03 +02:00
|
|
|
/// \brief Size of vertex data buffer
|
2020-04-05 21:16:16 +02:00
|
|
|
std::size_t s_vtxSize = 0;
|
2020-04-06 07:36:03 +02:00
|
|
|
/// \brief Index data buffer
|
|
|
|
ImDrawIdx *s_idxData = nullptr;
|
|
|
|
/// \brief Size of index data buffer
|
2020-04-05 21:16:16 +02:00
|
|
|
std::size_t s_idxSize = 0;
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
/// \brief Get code point from glyph index
|
|
|
|
/// \param font_ Font to search
|
|
|
|
/// \param glyphIndex_ Glyph index
|
2020-04-05 21:16:16 +02:00
|
|
|
std::uint32_t fontCodePointFromGlyphIndex (CFNT_s *const font_, int const glyphIndex_)
|
|
|
|
{
|
|
|
|
for (auto cmap = fontGetInfo (font_)->cmap; cmap; cmap = cmap->next)
|
|
|
|
{
|
|
|
|
switch (cmap->mappingMethod)
|
|
|
|
{
|
|
|
|
case CMAP_TYPE_DIRECT:
|
|
|
|
assert (cmap->codeEnd >= cmap->codeBegin);
|
|
|
|
if (glyphIndex_ >= cmap->indexOffset &&
|
|
|
|
glyphIndex_ <= cmap->codeEnd - cmap->codeBegin + cmap->indexOffset)
|
|
|
|
return glyphIndex_ - cmap->indexOffset + cmap->codeBegin;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CMAP_TYPE_TABLE:
|
|
|
|
for (int i = 0; i <= cmap->codeEnd - cmap->codeBegin; ++i)
|
|
|
|
{
|
|
|
|
if (cmap->indexTable[i] == glyphIndex_)
|
|
|
|
return cmap->codeBegin + i;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CMAP_TYPE_SCAN:
|
|
|
|
for (unsigned i = 0; i < cmap->nScanEntries; ++i)
|
|
|
|
{
|
|
|
|
assert (cmap->scanEntries[i].code >= cmap->codeBegin);
|
|
|
|
assert (cmap->scanEntries[i].code <= cmap->codeEnd);
|
|
|
|
if (glyphIndex_ == cmap->scanEntries[i].glyphIndex)
|
|
|
|
return cmap->scanEntries[i].code;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
/// \brief Setup render state
|
|
|
|
/// \param screen_ Whether top or bottom screen
|
2020-04-05 21:16:16 +02:00
|
|
|
void setupRenderState (gfxScreen_t const screen_)
|
|
|
|
{
|
2020-04-06 07:36:03 +02:00
|
|
|
// disable face culling
|
2020-04-05 21:16:16 +02:00
|
|
|
C3D_CullFace (GPU_CULL_NONE);
|
|
|
|
|
|
|
|
// configure attributes for user with vertex shader
|
|
|
|
auto const attrInfo = C3D_GetAttrInfo ();
|
|
|
|
AttrInfo_Init (attrInfo);
|
|
|
|
AttrInfo_AddLoader (attrInfo, 0, GPU_FLOAT, 2); // v0 = inPos
|
|
|
|
AttrInfo_AddLoader (attrInfo, 1, GPU_FLOAT, 2); // v1 = inUv
|
|
|
|
AttrInfo_AddLoader (attrInfo, 2, GPU_UNSIGNED_BYTE, 4); // v2 = inColor
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// clear bindings
|
2020-04-05 21:16:16 +02:00
|
|
|
std::memset (s_boundScissor, 0xFF, sizeof (s_boundScissor));
|
|
|
|
s_boundVtxData = nullptr;
|
|
|
|
s_boundTexture = nullptr;
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// bind program
|
2020-04-05 21:16:16 +02:00
|
|
|
C3D_BindProgram (&s_program);
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// enable depth test
|
2020-04-05 21:16:16 +02:00
|
|
|
C3D_DepthTest (true, GPU_GREATER, GPU_WRITE_COLOR);
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// enable alpha blending
|
2020-04-05 21:16:16 +02:00
|
|
|
C3D_AlphaBlend (GPU_BLEND_ADD,
|
|
|
|
GPU_BLEND_ADD,
|
|
|
|
GPU_SRC_ALPHA,
|
|
|
|
GPU_ONE_MINUS_SRC_ALPHA,
|
|
|
|
GPU_SRC_ALPHA,
|
|
|
|
GPU_ONE_MINUS_SRC_ALPHA);
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// apply projection matrix
|
2020-04-05 21:16:16 +02:00
|
|
|
if (screen_ == GFX_TOP)
|
|
|
|
C3D_FVUnifMtx4x4 (GPU_VERTEX_SHADER, s_projLocation, &s_projTop);
|
|
|
|
else
|
|
|
|
C3D_FVUnifMtx4x4 (GPU_VERTEX_SHADER, s_projLocation, &s_projBottom);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void imgui::citro3d::init ()
|
|
|
|
{
|
2020-04-06 07:36:03 +02:00
|
|
|
// setup back-end capabilities flags
|
2020-04-07 05:38:14 +02:00
|
|
|
auto &io = ImGui::GetIO ();
|
2020-04-05 21:16:16 +02:00
|
|
|
|
|
|
|
io.BackendRendererName = "citro3d";
|
|
|
|
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// load vertex shader
|
2020-04-05 21:16:16 +02:00
|
|
|
s_vsh = DVLB_ParseFile (
|
|
|
|
const_cast<std::uint32_t *> (reinterpret_cast<std::uint32_t const *> (vshader_shbin)),
|
|
|
|
vshader_shbin_size);
|
2020-04-06 07:36:03 +02:00
|
|
|
|
|
|
|
// initialize vertex shader program
|
2020-04-05 21:16:16 +02:00
|
|
|
shaderProgramInit (&s_program);
|
|
|
|
shaderProgramSetVsh (&s_program, &s_vsh->DVLE[0]);
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// get projection matrix uniform location
|
2020-04-05 21:16:16 +02:00
|
|
|
s_projLocation = shaderInstanceGetUniformLocation (s_program.vertexShader, "proj");
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// allocate vertex data buffer
|
2020-04-05 21:16:16 +02:00
|
|
|
s_vtxSize = 65536;
|
|
|
|
s_vtxData = reinterpret_cast<ImDrawVert *> (linearAlloc (sizeof (ImDrawVert) * s_vtxSize));
|
|
|
|
if (!s_vtxData)
|
|
|
|
svcBreak (USERBREAK_PANIC);
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// allocate index data buffer
|
2020-04-05 21:16:16 +02:00
|
|
|
s_idxSize = 65536;
|
|
|
|
s_idxData = reinterpret_cast<ImDrawIdx *> (linearAlloc (sizeof (ImDrawIdx) * s_idxSize));
|
|
|
|
if (!s_idxData)
|
|
|
|
svcBreak (USERBREAK_PANIC);
|
|
|
|
|
|
|
|
// ensure the shared system font is mapped
|
|
|
|
if (R_FAILED (fontEnsureMapped ()))
|
|
|
|
svcBreak (USERBREAK_PANIC);
|
|
|
|
|
|
|
|
// load the glyph texture sheets
|
|
|
|
auto const font = fontGetSystemFont ();
|
|
|
|
auto const fontInfo = fontGetInfo (font);
|
|
|
|
auto const glyphInfo = fontGetGlyphInfo (font);
|
|
|
|
assert (s_fontTextures.empty ());
|
|
|
|
s_fontTextures.resize (glyphInfo->nSheets + 1);
|
|
|
|
std::memset (s_fontTextures.data (), 0x00, s_fontTextures.size () * sizeof (s_fontTextures[0]));
|
|
|
|
|
|
|
|
s_textScale = 30.0f / glyphInfo->cellHeight;
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// use system font sheets as citro3d textures
|
2020-04-05 21:16:16 +02:00
|
|
|
for (unsigned i = 0; i < glyphInfo->nSheets; ++i)
|
|
|
|
{
|
|
|
|
auto &tex = s_fontTextures[i];
|
|
|
|
tex.data = fontGetGlyphSheetTex (font, i);
|
|
|
|
if (!tex.data)
|
|
|
|
svcBreak (USERBREAK_PANIC);
|
|
|
|
tex.fmt = static_cast<GPU_TEXCOLOR> (glyphInfo->sheetFmt);
|
|
|
|
tex.size = glyphInfo->sheetSize;
|
|
|
|
tex.width = glyphInfo->sheetWidth;
|
|
|
|
tex.height = glyphInfo->sheetHeight;
|
|
|
|
tex.param = GPU_TEXTURE_MAG_FILTER (GPU_LINEAR) | GPU_TEXTURE_MIN_FILTER (GPU_LINEAR) |
|
|
|
|
GPU_TEXTURE_WRAP_S (GPU_REPEAT) | GPU_TEXTURE_WRAP_T (GPU_REPEAT);
|
|
|
|
tex.border = 0xFFFFFFFF;
|
|
|
|
tex.lodParam = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
2020-04-06 07:36:03 +02:00
|
|
|
// create texture for ImGui's white pixel
|
2020-04-05 21:16:16 +02:00
|
|
|
auto &tex = s_fontTextures[glyphInfo->nSheets];
|
|
|
|
C3D_TexInit (&tex, 8, 8, GPU_A4);
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// fill texture with full alpha
|
2020-04-05 21:16:16 +02:00
|
|
|
std::uint32_t size;
|
|
|
|
auto data = C3D_Tex2DGetImagePtr (&tex, 0, &size);
|
|
|
|
if (!data || !size)
|
|
|
|
svcBreak (USERBREAK_PANIC);
|
|
|
|
std::memset (data, 0xFF, size);
|
|
|
|
}
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// get alternate character glyph
|
2020-04-05 21:16:16 +02:00
|
|
|
ImWchar alterChar = fontCodePointFromGlyphIndex (font, fontInfo->alterCharIndex);
|
|
|
|
if (!alterChar)
|
|
|
|
alterChar = '?';
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// collect character map
|
2020-04-05 21:16:16 +02:00
|
|
|
std::vector<ImWchar> charSet;
|
|
|
|
for (auto cmap = fontInfo->cmap; cmap; cmap = cmap->next)
|
|
|
|
{
|
|
|
|
switch (cmap->mappingMethod)
|
|
|
|
{
|
|
|
|
case CMAP_TYPE_DIRECT:
|
|
|
|
case CMAP_TYPE_TABLE:
|
|
|
|
assert (cmap->codeEnd >= cmap->codeBegin);
|
|
|
|
charSet.reserve (charSet.size () + cmap->codeEnd - cmap->codeBegin + 1);
|
|
|
|
for (auto i = cmap->codeBegin; i <= cmap->codeEnd; ++i)
|
|
|
|
charSet.emplace_back (i);
|
|
|
|
break;
|
|
|
|
case CMAP_TYPE_SCAN:
|
|
|
|
charSet.reserve (charSet.size () + cmap->nScanEntries);
|
|
|
|
for (unsigned i = 0; i < cmap->nScanEntries; ++i)
|
|
|
|
{
|
|
|
|
assert (cmap->scanEntries[i].code >= cmap->codeBegin);
|
|
|
|
assert (cmap->scanEntries[i].code <= cmap->codeEnd);
|
|
|
|
charSet.emplace_back (cmap->scanEntries[i].code);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (charSet.empty ())
|
|
|
|
svcBreak (USERBREAK_PANIC);
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// deduplicate character map
|
2020-04-05 21:16:16 +02:00
|
|
|
std::sort (std::begin (charSet), std::end (charSet));
|
|
|
|
charSet.erase (std::unique (std::begin (charSet), std::end (charSet)), std::end (charSet));
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// fill in font glyph ranges
|
2020-04-05 21:16:16 +02:00
|
|
|
auto it = std::begin (charSet);
|
|
|
|
ImWchar start = *it++;
|
|
|
|
ImWchar prev = start;
|
|
|
|
while (it != std::end (charSet))
|
|
|
|
{
|
|
|
|
if (*it != prev + 1)
|
|
|
|
{
|
|
|
|
s_fontRanges.emplace_back (start);
|
|
|
|
s_fontRanges.emplace_back (prev);
|
|
|
|
|
|
|
|
start = *it;
|
|
|
|
}
|
|
|
|
|
|
|
|
prev = *it++;
|
|
|
|
}
|
|
|
|
s_fontRanges.emplace_back (start);
|
|
|
|
s_fontRanges.emplace_back (prev);
|
2020-04-06 07:36:03 +02:00
|
|
|
|
|
|
|
// terminate glyph ranges
|
2020-04-05 21:16:16 +02:00
|
|
|
s_fontRanges.emplace_back (0);
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// initialize font atlas
|
2020-04-05 21:16:16 +02:00
|
|
|
auto const atlas = ImGui::GetIO ().Fonts;
|
|
|
|
atlas->Clear ();
|
|
|
|
atlas->TexWidth = glyphInfo->sheetWidth;
|
|
|
|
atlas->TexHeight = glyphInfo->sheetHeight * glyphInfo->nSheets;
|
|
|
|
atlas->TexUvScale = ImVec2 (1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight);
|
|
|
|
atlas->TexUvWhitePixel = ImVec2 (0.5f / 8.0f, glyphInfo->nSheets + 0.5f / 8.0f);
|
|
|
|
atlas->TexPixelsAlpha8 = static_cast<unsigned char *> (IM_ALLOC (1)); // dummy allocation
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// initialize font config
|
2020-04-05 21:16:16 +02:00
|
|
|
ImFontConfig config;
|
|
|
|
config.FontData = nullptr;
|
|
|
|
config.FontDataSize = 0;
|
|
|
|
config.FontDataOwnedByAtlas = true;
|
|
|
|
config.FontNo = 0;
|
|
|
|
config.SizePixels = 14.0f;
|
|
|
|
config.OversampleH = 3;
|
|
|
|
config.OversampleV = 1;
|
|
|
|
config.PixelSnapH = false;
|
|
|
|
config.GlyphExtraSpacing = ImVec2 (0.0f, 0.0f);
|
|
|
|
config.GlyphOffset = ImVec2 (0.0f, 0.0f);
|
|
|
|
config.GlyphRanges = s_fontRanges.data ();
|
|
|
|
config.GlyphMinAdvanceX = 0.0f;
|
|
|
|
config.GlyphMaxAdvanceX = std::numeric_limits<float>::max ();
|
|
|
|
config.MergeMode = false;
|
|
|
|
config.RasterizerFlags = 0;
|
|
|
|
config.RasterizerMultiply = 1.0f;
|
|
|
|
config.EllipsisChar = 0x2026;
|
|
|
|
std::memset (config.Name, 0, sizeof (config.Name));
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// create font
|
2020-04-05 21:16:16 +02:00
|
|
|
auto const imFont = IM_NEW (ImFont);
|
|
|
|
config.DstFont = imFont;
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// add config and font to atlas
|
2020-04-05 21:16:16 +02:00
|
|
|
atlas->ConfigData.push_back (config);
|
|
|
|
atlas->Fonts.push_back (imFont);
|
|
|
|
atlas->SetTexID (s_fontTextures.data ());
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// initialize font metrics
|
2020-04-05 21:16:16 +02:00
|
|
|
imFont->FallbackAdvanceX = fontInfo->defaultWidth.charWidth;
|
|
|
|
imFont->FontSize = fontInfo->lineFeed;
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// add glyphs to font
|
2020-04-05 21:16:16 +02:00
|
|
|
fontGlyphPos_s glyphPos;
|
|
|
|
for (auto const &code : charSet)
|
|
|
|
{
|
|
|
|
auto const glyphIndex = fontGlyphIndexFromCodePoint (font, code);
|
|
|
|
if (glyphIndex < 0)
|
|
|
|
svcBreak (USERBREAK_PANIC);
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// calculate glyph metrics
|
2020-04-05 21:16:16 +02:00
|
|
|
fontCalcGlyphPos (&glyphPos,
|
|
|
|
font,
|
|
|
|
glyphIndex,
|
|
|
|
GLYPH_POS_CALC_VTXCOORD | GLYPH_POS_AT_BASELINE,
|
|
|
|
1.0f,
|
|
|
|
1.0f);
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// convert to ImGui font metrics
|
2020-04-05 21:16:16 +02:00
|
|
|
ImFontGlyph glyph;
|
|
|
|
glyph.Codepoint = code;
|
|
|
|
glyph.AdvanceX = glyphPos.xAdvance;
|
|
|
|
glyph.X0 = glyphPos.vtxcoord.left;
|
|
|
|
glyph.Y0 = glyphPos.vtxcoord.top;
|
|
|
|
glyph.X1 = glyphPos.vtxcoord.right;
|
|
|
|
glyph.Y1 = glyphPos.vtxcoord.bottom;
|
|
|
|
glyph.U0 = glyphPos.texcoord.left;
|
|
|
|
glyph.V0 = glyphPos.sheetIndex + glyphPos.texcoord.top;
|
|
|
|
glyph.U1 = glyphPos.texcoord.right;
|
|
|
|
glyph.V1 = glyphPos.sheetIndex + glyphPos.texcoord.bottom;
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// add glyph to font
|
2020-04-05 21:16:16 +02:00
|
|
|
imFont->Glyphs.push_back (glyph);
|
|
|
|
imFont->MetricsTotalSurface +=
|
|
|
|
static_cast<int> ((glyph.U1 - glyph.U0) * atlas->TexWidth + 1.99f) *
|
|
|
|
static_cast<int> ((glyph.V1 - glyph.V0) * atlas->TexHeight + 1.99f);
|
|
|
|
}
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// build lookup table
|
2020-04-05 21:16:16 +02:00
|
|
|
imFont->BuildLookupTable ();
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// finalize font
|
2020-04-05 21:16:16 +02:00
|
|
|
imFont->DisplayOffset.x = 0.0f;
|
2020-04-07 03:24:06 +02:00
|
|
|
imFont->DisplayOffset.y = fontInfo->ascent * 0.5f;
|
2020-04-05 21:16:16 +02:00
|
|
|
imFont->ContainerAtlas = atlas;
|
|
|
|
imFont->ConfigData = &atlas->ConfigData[0];
|
|
|
|
imFont->ConfigDataCount = 1;
|
|
|
|
imFont->FallbackChar = alterChar;
|
|
|
|
imFont->EllipsisChar = config.EllipsisChar;
|
2020-04-07 03:24:06 +02:00
|
|
|
imFont->Scale = s_textScale * 0.5f;
|
2020-04-05 21:16:16 +02:00
|
|
|
imFont->Ascent = fontInfo->ascent;
|
|
|
|
imFont->Descent = 0.0f;
|
|
|
|
}
|
|
|
|
|
|
|
|
void imgui::citro3d::exit ()
|
|
|
|
{
|
2020-04-06 07:36:03 +02:00
|
|
|
// free vertex/index data buffers
|
2020-04-05 21:16:16 +02:00
|
|
|
linearFree (s_idxData);
|
|
|
|
linearFree (s_vtxData);
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// delete ImGui white pixel texture
|
2020-04-05 21:16:16 +02:00
|
|
|
assert (!s_fontTextures.empty ());
|
|
|
|
C3D_TexDelete (&s_fontTextures.back ());
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// free shader program
|
2020-04-05 21:16:16 +02:00
|
|
|
shaderProgramFree (&s_program);
|
|
|
|
DVLB_Free (s_vsh);
|
|
|
|
}
|
|
|
|
|
2020-04-07 05:38:14 +02:00
|
|
|
void imgui::citro3d::render (C3D_RenderTarget *const top_, C3D_RenderTarget *const bottom_)
|
2020-04-05 21:16:16 +02:00
|
|
|
{
|
2020-04-06 07:36:03 +02:00
|
|
|
// get 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-07 05:38:14 +02:00
|
|
|
|
|
|
|
// initialize projection matrices
|
|
|
|
Mtx_OrthoTilt (&s_projTop,
|
|
|
|
0.0f,
|
|
|
|
drawData->DisplaySize.x,
|
|
|
|
drawData->DisplaySize.y * 0.5f,
|
|
|
|
0.0f,
|
|
|
|
-1.0f,
|
|
|
|
1.0f,
|
|
|
|
false);
|
|
|
|
Mtx_OrthoTilt (&s_projBottom,
|
|
|
|
drawData->DisplaySize.x * 0.1f,
|
|
|
|
drawData->DisplaySize.x * 0.9f,
|
|
|
|
drawData->DisplaySize.y,
|
|
|
|
drawData->DisplaySize.y * 0.5f,
|
|
|
|
-1.0f,
|
|
|
|
1.0f,
|
|
|
|
false);
|
2020-04-05 21:16:16 +02:00
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// check if we need to grow vertex data buffer
|
2020-04-05 21:16:16 +02:00
|
|
|
if (s_vtxSize < static_cast<std::size_t> (drawData->TotalVtxCount))
|
|
|
|
{
|
|
|
|
linearFree (s_vtxData);
|
|
|
|
|
|
|
|
// add 10% to avoid growing many frames in a row
|
|
|
|
s_vtxSize = drawData->TotalVtxCount * 1.1f;
|
|
|
|
s_vtxData = reinterpret_cast<ImDrawVert *> (linearAlloc (sizeof (ImDrawVert) * s_vtxSize));
|
|
|
|
if (!s_vtxData)
|
|
|
|
svcBreak (USERBREAK_PANIC);
|
|
|
|
}
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// check if we need to grow index data buffer
|
2020-04-05 21:16:16 +02:00
|
|
|
if (s_idxSize < static_cast<std::size_t> (drawData->TotalIdxCount))
|
|
|
|
{
|
|
|
|
// add 10% to avoid growing many frames in a row
|
|
|
|
s_idxSize = drawData->TotalIdxCount * 1.1f;
|
|
|
|
s_idxData = reinterpret_cast<ImDrawIdx *> (linearAlloc (sizeof (ImDrawIdx) * s_idxSize));
|
|
|
|
if (!s_vtxData)
|
|
|
|
svcBreak (USERBREAK_PANIC);
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
// copy data into vertex/index buffers
|
|
|
|
std::size_t offsetVtx = 0;
|
|
|
|
std::size_t offsetIdx = 0;
|
|
|
|
for (int i = 0; i < drawData->CmdListsCount; ++i)
|
|
|
|
{
|
|
|
|
auto const &cmdList = *drawData->CmdLists[i];
|
2020-04-06 07:36:03 +02:00
|
|
|
|
|
|
|
// double check that we don't overrun vertex/index data buffers
|
2020-04-05 21:16:16 +02:00
|
|
|
if (s_vtxSize - offsetVtx < static_cast<std::size_t> (cmdList.VtxBuffer.Size))
|
|
|
|
svcBreak (USERBREAK_PANIC);
|
|
|
|
if (s_idxSize - offsetIdx < static_cast<std::size_t> (cmdList.IdxBuffer.Size))
|
|
|
|
svcBreak (USERBREAK_PANIC);
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// copy vertex/index data into buffers
|
2020-04-05 21:16:16 +02:00
|
|
|
std::memcpy (&s_vtxData[offsetVtx],
|
|
|
|
cmdList.VtxBuffer.Data,
|
|
|
|
sizeof (ImDrawVert) * cmdList.VtxBuffer.Size);
|
|
|
|
std::memcpy (&s_idxData[offsetIdx],
|
|
|
|
cmdList.IdxBuffer.Data,
|
|
|
|
sizeof (ImDrawIdx) * cmdList.IdxBuffer.Size);
|
|
|
|
|
|
|
|
offsetVtx += cmdList.VtxBuffer.Size;
|
|
|
|
offsetIdx += cmdList.IdxBuffer.Size;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (auto const &screen : {GFX_TOP, GFX_BOTTOM})
|
|
|
|
{
|
|
|
|
if (screen == GFX_TOP)
|
2020-04-07 05:38:14 +02:00
|
|
|
C3D_FrameDrawOn (top_);
|
2020-04-05 21:16:16 +02:00
|
|
|
else
|
2020-04-07 05:38:14 +02:00
|
|
|
C3D_FrameDrawOn (bottom_);
|
2020-04-05 21:16:16 +02:00
|
|
|
|
|
|
|
setupRenderState (screen);
|
|
|
|
|
|
|
|
offsetVtx = 0;
|
|
|
|
offsetIdx = 0;
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// render command lists
|
2020-04-05 21:16:16 +02:00
|
|
|
for (int i = 0; i < drawData->CmdListsCount; ++i)
|
|
|
|
{
|
|
|
|
auto const &cmdList = *drawData->CmdLists[i];
|
|
|
|
for (auto const &cmd : cmdList.CmdBuffer)
|
|
|
|
{
|
|
|
|
if (cmd.UserCallback)
|
|
|
|
{
|
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)
|
|
|
|
setupRenderState (screen);
|
|
|
|
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;
|
|
|
|
|
|
|
|
if (clip.x >= width || clip.y >= height || clip.z < 0.0f || clip.w < 0.0f)
|
|
|
|
continue;
|
|
|
|
if (clip.x < 0.0f)
|
|
|
|
clip.x = 0.0f;
|
|
|
|
if (clip.y < 0.0f)
|
|
|
|
clip.y = 0.0f;
|
2020-04-06 07:36:03 +02:00
|
|
|
if (clip.z > width)
|
|
|
|
clip.z = width;
|
|
|
|
if (clip.w > height)
|
|
|
|
clip.z = height;
|
2020-04-05 21:16:16 +02:00
|
|
|
|
|
|
|
if (screen == GFX_TOP)
|
|
|
|
{
|
|
|
|
// check if clip starts on bottom screen
|
2020-04-07 03:24:06 +02:00
|
|
|
if (clip.y > height * 0.5f)
|
2020-04-05 21:16:16 +02:00
|
|
|
continue;
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// convert from framebuffer space to screen space (3DS screen rotation)
|
2020-04-07 03:24:06 +02:00
|
|
|
auto const x1 =
|
|
|
|
std::clamp<unsigned> (height * 0.5f - clip.w, 0, height * 0.5f);
|
|
|
|
auto const y1 = std::clamp<unsigned> (width - clip.z, 0, width);
|
|
|
|
auto const x2 =
|
|
|
|
std::clamp<unsigned> (height * 0.5f - clip.y, 0, height * 0.5f);
|
|
|
|
auto const y2 = std::clamp<unsigned> (width - clip.x, 0, width);
|
2020-04-05 21:16:16 +02:00
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// check if scissor needs to be updated
|
|
|
|
if (s_boundScissor[0] != x1 || s_boundScissor[1] != y1 ||
|
|
|
|
s_boundScissor[2] != x2 || s_boundScissor[3] != y2)
|
|
|
|
{
|
|
|
|
s_boundScissor[0] = x1;
|
|
|
|
s_boundScissor[1] = y1;
|
|
|
|
s_boundScissor[2] = x2;
|
|
|
|
s_boundScissor[3] = y2;
|
|
|
|
C3D_SetScissor (GPU_SCISSOR_NORMAL, x1, y1, x2, y2);
|
|
|
|
}
|
2020-04-05 21:16:16 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// check if clip ends on top screen
|
2020-04-07 03:24:06 +02:00
|
|
|
if (clip.w < height * 0.5f)
|
2020-04-05 21:16:16 +02:00
|
|
|
continue;
|
|
|
|
|
|
|
|
// check if clip ends before left edge of bottom screen
|
2020-04-07 03:24:06 +02:00
|
|
|
if (clip.z < width * 0.1f)
|
2020-04-05 21:16:16 +02:00
|
|
|
continue;
|
|
|
|
|
|
|
|
// check if clip starts after right edge of bottom screen
|
2020-04-07 03:24:06 +02:00
|
|
|
if (clip.x > width * 0.9f)
|
2020-04-05 21:16:16 +02:00
|
|
|
continue;
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// convert from framebuffer space to screen space
|
|
|
|
// (3DS screen rotation + bottom screen offset)
|
2020-04-07 03:24:06 +02:00
|
|
|
auto const x1 = std::clamp<unsigned> (height - clip.w, 0, height * 0.5f);
|
|
|
|
auto const y1 =
|
|
|
|
std::clamp<unsigned> (width * 0.9f - clip.z, 0, width * 0.8f);
|
|
|
|
auto const x2 = std::clamp<unsigned> (height - clip.y, 0, height * 0.5f);
|
|
|
|
auto const y2 =
|
|
|
|
std::clamp<unsigned> (width * 0.9f - clip.x, 0, width * 0.8f);
|
2020-04-05 21:16:16 +02:00
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// check if scissor needs to be updated
|
2020-04-05 21:16:16 +02:00
|
|
|
if (s_boundScissor[0] != x1 || s_boundScissor[1] != y1 ||
|
|
|
|
s_boundScissor[2] != x2 || s_boundScissor[3] != y2)
|
|
|
|
{
|
|
|
|
s_boundScissor[0] = x1;
|
|
|
|
s_boundScissor[1] = y1;
|
|
|
|
s_boundScissor[2] = x2;
|
|
|
|
s_boundScissor[3] = y2;
|
|
|
|
C3D_SetScissor (GPU_SCISSOR_NORMAL, x1, y1, x2, y2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// check if we need to update vertex data binding
|
2020-04-05 21:16:16 +02:00
|
|
|
auto const vtxData = &s_vtxData[cmd.VtxOffset + offsetVtx];
|
|
|
|
if (vtxData != s_boundVtxData)
|
|
|
|
{
|
2020-04-06 07:36:03 +02:00
|
|
|
s_boundVtxData = vtxData;
|
2020-04-05 21:16:16 +02:00
|
|
|
auto const bufInfo = C3D_GetBufInfo ();
|
|
|
|
BufInfo_Init (bufInfo);
|
2020-04-06 07:36:03 +02:00
|
|
|
BufInfo_Add (bufInfo, vtxData, sizeof (ImDrawVert), 3, 0x210);
|
2020-04-05 21:16:16 +02:00
|
|
|
}
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// check if we need to update texture binding
|
2020-04-05 21:16:16 +02:00
|
|
|
auto tex = static_cast<C3D_Tex *> (cmd.TextureId);
|
|
|
|
if (tex == s_fontTextures.data ())
|
|
|
|
{
|
|
|
|
assert (cmd.ElemCount % 3 == 0);
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// get sheet number from uv coords
|
2020-04-05 21:16:16 +02:00
|
|
|
auto const getSheet = [] (auto const vtx_, auto const idx_) {
|
|
|
|
unsigned const sheet = std::min (
|
|
|
|
{vtx_[idx_[0]].uv.y, vtx_[idx_[1]].uv.y, vtx_[idx_[2]].uv.y});
|
2020-04-06 07:36:03 +02:00
|
|
|
|
|
|
|
// assert that these three vertices use the same sheet
|
2020-04-05 21:16:16 +02:00
|
|
|
for (unsigned i = 0; i < 3; ++i)
|
|
|
|
assert (vtx_[idx_[i]].uv.y - sheet <= 1.0f);
|
|
|
|
return sheet;
|
|
|
|
};
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// initialize texture binding
|
2020-04-05 21:16:16 +02:00
|
|
|
unsigned boundSheet = getSheet (&s_vtxData[cmd.VtxOffset + offsetVtx],
|
|
|
|
&s_idxData[cmd.IdxOffset + offsetIdx]);
|
2020-04-06 07:36:03 +02:00
|
|
|
C3D_TexBind (0, &s_fontTextures[boundSheet]);
|
2020-04-05 21:16:16 +02:00
|
|
|
|
|
|
|
unsigned offset = 0;
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// update texture environment for non-image drawing
|
2020-04-05 21:16:16 +02:00
|
|
|
auto const env = C3D_GetTexEnv (0);
|
|
|
|
C3D_TexEnvInit (env);
|
|
|
|
C3D_TexEnvSrc (
|
|
|
|
env, C3D_RGB, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR);
|
|
|
|
C3D_TexEnvFunc (env, C3D_RGB, GPU_REPLACE);
|
|
|
|
C3D_TexEnvSrc (
|
|
|
|
env, C3D_Alpha, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR);
|
|
|
|
C3D_TexEnvFunc (env, C3D_Alpha, GPU_MODULATE);
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// process one triangle at a time
|
2020-04-05 21:16:16 +02:00
|
|
|
for (unsigned i = 3; i < cmd.ElemCount; i += 3)
|
|
|
|
{
|
2020-04-06 07:36:03 +02:00
|
|
|
// get sheet for this triangle
|
2020-04-05 21:16:16 +02:00
|
|
|
unsigned const sheet = getSheet (&s_vtxData[cmd.VtxOffset + offsetVtx],
|
|
|
|
&s_idxData[cmd.IdxOffset + offsetIdx + i]);
|
2020-04-06 07:36:03 +02:00
|
|
|
|
|
|
|
// check if we're changing textures
|
2020-04-05 21:16:16 +02:00
|
|
|
if (boundSheet != sheet)
|
|
|
|
{
|
2020-04-06 07:36:03 +02:00
|
|
|
// draw everything up until now
|
2020-04-05 21:16:16 +02:00
|
|
|
C3D_DrawElements (GPU_TRIANGLES,
|
|
|
|
i - offset,
|
|
|
|
C3D_UNSIGNED_SHORT,
|
|
|
|
&s_idxData[cmd.IdxOffset + offsetIdx + offset]);
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// bind texture for next draw call
|
2020-04-05 21:16:16 +02:00
|
|
|
boundSheet = sheet;
|
|
|
|
offset = i;
|
|
|
|
C3D_TexBind (0, &s_fontTextures[boundSheet]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// draw the final set of triangles
|
2020-04-05 21:16:16 +02:00
|
|
|
assert ((cmd.ElemCount - offset) % 3 == 0);
|
|
|
|
C3D_DrawElements (GPU_TRIANGLES,
|
|
|
|
cmd.ElemCount - offset,
|
|
|
|
C3D_UNSIGNED_SHORT,
|
|
|
|
&s_idxData[cmd.IdxOffset + offsetIdx + offset]);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-04-06 07:36:03 +02:00
|
|
|
// drawing an image; check if we need to change texture binding
|
2020-04-05 21:16:16 +02:00
|
|
|
if (tex != s_boundTexture)
|
|
|
|
{
|
2020-04-06 07:36:03 +02:00
|
|
|
// bind new texture
|
2020-04-05 21:16:16 +02:00
|
|
|
C3D_TexBind (0, tex);
|
2020-04-06 07:36:03 +02:00
|
|
|
|
|
|
|
// update texture environment for drawing images
|
2020-04-05 21:16:16 +02:00
|
|
|
auto const env = C3D_GetTexEnv (0);
|
|
|
|
C3D_TexEnvInit (env);
|
|
|
|
C3D_TexEnvSrc (
|
|
|
|
env, C3D_Both, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR);
|
|
|
|
C3D_TexEnvFunc (env, C3D_Both, GPU_MODULATE);
|
|
|
|
}
|
|
|
|
|
2020-04-06 07:36:03 +02:00
|
|
|
// draw triangles
|
2020-04-05 21:16:16 +02:00
|
|
|
C3D_DrawElements (GPU_TRIANGLES,
|
|
|
|
cmd.ElemCount,
|
|
|
|
C3D_UNSIGNED_SHORT,
|
|
|
|
&s_idxData[cmd.IdxOffset + offsetIdx]);
|
|
|
|
}
|
|
|
|
|
|
|
|
s_boundTexture = tex;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
offsetVtx += cmdList.VtxBuffer.Size;
|
|
|
|
offsetIdx += cmdList.IdxBuffer.Size;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|