Add stereoscopic feature to 3DS

This commit is contained in:
Michael Theall 2024-03-16 12:33:19 -05:00
parent 503e49854b
commit 0144af2a83
7 changed files with 194 additions and 23 deletions

BIN
3ds/gfx/bubble.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -5,6 +5,7 @@ battery2.png
battery3.png battery3.png
battery4.png battery4.png
batteryCharge.png batteryCharge.png
bubble.png
c3dlogo.png c3dlogo.png
wifiNull.png wifiNull.png
wifi1.png wifi1.png

View File

@ -214,7 +214,7 @@ elseif(NINTENDO_3DS)
endif() endif()
target_compile_definitions(${FTPD_TARGET} PRIVATE target_compile_definitions(${FTPD_TARGET} PRIVATE
ANTI_ALIAS=1 ANTI_ALIAS=0
) )
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
@ -250,6 +250,7 @@ elseif(NINTENDO_3DS)
3ds/gfx/battery3.png 3ds/gfx/battery3.png
3ds/gfx/battery4.png 3ds/gfx/battery4.png
3ds/gfx/batteryCharge.png 3ds/gfx/batteryCharge.png
3ds/gfx/bubble.png
3ds/gfx/c3dlogo.png 3ds/gfx/c3dlogo.png
3ds/gfx/wifi1.png 3ds/gfx/wifi1.png
3ds/gfx/wifi2.png 3ds/gfx/wifi2.png

View File

@ -5,7 +5,7 @@
// //
// The MIT License (MIT) // The MIT License (MIT)
// //
// Copyright (C) 2023 Michael Theall // Copyright (C) 2024 Michael Theall
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@ -35,6 +35,7 @@
#include "imgui.h" #include "imgui.h"
#include <algorithm> #include <algorithm>
#include <bit>
#include <cstdint> #include <cstdint>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
@ -42,6 +43,13 @@
namespace namespace
{ {
/// \brief Slider value
float s_slider;
/// \brief Z offset (for stereoscopic effect)
float s_z;
/// \brief Z offset uniform location
int s_zLocation;
/// \brief 3DS font glyph ranges /// \brief 3DS font glyph ranges
std::vector<ImWchar> s_fontRanges; std::vector<ImWchar> s_fontRanges;
@ -175,6 +183,9 @@ void imgui::citro3d::init ()
shaderProgramInit (&s_program); shaderProgramInit (&s_program);
shaderProgramSetVsh (&s_program, &s_vsh->DVLE[0]); shaderProgramSetVsh (&s_program, &s_vsh->DVLE[0]);
// get projection matrix uniform location
s_zLocation = shaderInstanceGetUniformLocation (s_program.vertexShader, "z");
// get projection matrix uniform location // get projection matrix uniform location
s_projLocation = shaderInstanceGetUniformLocation (s_program.vertexShader, "proj"); s_projLocation = shaderInstanceGetUniformLocation (s_program.vertexShader, "proj");
@ -417,7 +428,9 @@ void imgui::citro3d::exit ()
DVLB_Free (s_vsh); DVLB_Free (s_vsh);
} }
void imgui::citro3d::render (C3D_RenderTarget *const top_, C3D_RenderTarget *const bottom_) void imgui::citro3d::render (C3D_RenderTarget *const topLeft_,
C3D_RenderTarget *const topRight_,
C3D_RenderTarget *const bottom_)
{ {
// get draw data // get draw data
auto const drawData = ImGui::GetDrawData (); auto const drawData = ImGui::GetDrawData ();
@ -497,12 +510,27 @@ void imgui::citro3d::render (C3D_RenderTarget *const top_, C3D_RenderTarget *con
offsetIdx += cmdList.IdxBuffer.Size; offsetIdx += cmdList.IdxBuffer.Size;
} }
for (auto const &screen : {GFX_TOP, GFX_BOTTOM}) auto const slider = osGet3DSliderState ();
for (auto const &[target, screen, side] : {
// clang-format off
std::make_tuple (topLeft_, GFX_TOP, GFX_LEFT),
std::make_tuple (slider ? topRight_ : nullptr, GFX_TOP, GFX_RIGHT),
std::make_tuple (bottom_, GFX_BOTTOM, GFX_LEFT)
// clang-format on
})
{ {
if (screen == GFX_TOP) if (!target)
C3D_FrameDrawOn (top_); continue;
if (target == topLeft_)
s_slider = slider;
else if (target == topRight_)
s_slider = -slider;
else else
C3D_FrameDrawOn (bottom_); s_slider = 0.0f;
C3D_FrameDrawOn (target);
setupRenderState (screen); setupRenderState (screen);
@ -534,14 +562,24 @@ void imgui::citro3d::render (C3D_RenderTarget *const top_, C3D_RenderTarget *con
clip.z = (cmd.ClipRect.z - clipOff.x) * clipScale.x; clip.z = (cmd.ClipRect.z - clipOff.x) * clipScale.x;
clip.w = (cmd.ClipRect.w - clipOff.y) * clipScale.y; clip.w = (cmd.ClipRect.w - clipOff.y) * clipScale.y;
if (s_slider && s_z && screen == GFX_TOP)
{
clip.x -= s_slider * s_z;
clip.z += s_slider * s_z;
}
if (clip.x >= width || clip.y >= height || clip.z < 0.0f || clip.w < 0.0f) if (clip.x >= width || clip.y >= height || clip.z < 0.0f || clip.w < 0.0f)
continue; continue;
if (clip.x < 0.0f) if (clip.x < 0.0f)
clip.x = 0.0f; clip.x = 0.0f;
if (clip.y < 0.0f) if (clip.y < 0.0f)
clip.y = 0.0f; clip.y = 0.0f;
if (clip.z > width) if (clip.z > width)
clip.z = width; clip.z = width;
if (clip.w > height) if (clip.w > height)
clip.z = height; clip.z = height;
@ -732,4 +770,13 @@ void imgui::citro3d::render (C3D_RenderTarget *const top_, C3D_RenderTarget *con
} }
} }
} }
void imgui::citro3d::setZ (ImDrawList const *const drawList_, ImDrawCmd const *const drawCmd_)
{
(void)drawList_;
s_z = std::bit_cast<float> (drawCmd_->UserCallbackData);
C3D_FVUnifSet (GPU_VERTEX_SHADER, s_zLocation, s_slider * s_z, 0.0f, 0.0f, 0.0f);
}
#endif #endif

View File

@ -5,7 +5,7 @@
// //
// The MIT License (MIT) // The MIT License (MIT)
// //
// Copyright (C) 2020 Michael Theall // Copyright (C) 2024 Michael Theall
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@ -30,6 +30,9 @@
#ifndef CLASSIC #ifndef CLASSIC
#include <citro3d.h> #include <citro3d.h>
struct ImDrawList;
struct ImDrawCmd;
namespace imgui namespace imgui
{ {
namespace citro3d namespace citro3d
@ -40,7 +43,15 @@ void init ();
void exit (); void exit ();
/// \brief Render ImGui draw list /// \brief Render ImGui draw list
void render (C3D_RenderTarget *top_, C3D_RenderTarget *bottom_); /// \param topLeft_ Top left render target
/// \param topRight_ Top right render target (skipped if not stereoscopic)
/// \param bottom_ Bottom render target
void render (C3D_RenderTarget *topLeft_, C3D_RenderTarget *topRight_, C3D_RenderTarget *bottom_);
/// \brief Set Z offset (for stereoscopic effect)
/// \param drawList_ Draw list
/// \param drawCmd_ Draw command
void setZ (ImDrawList const *drawList_, ImDrawCmd const *drawCmd_);
} }
} }
#endif #endif

View File

@ -39,12 +39,15 @@
#include <arpa/inet.h> #include <arpa/inet.h>
#include <malloc.h> #include <malloc.h>
#include <algorithm>
#include <atomic> #include <atomic>
#include <cassert> #include <cassert>
#include <chrono> #include <chrono>
#include <cstring> #include <cstring>
#include <ctime> #include <ctime>
#include <mutex> #include <mutex>
#include <random>
#include <vector>
#ifdef CLASSIC #ifdef CLASSIC
PrintConsole g_statusConsole; PrintConsole g_statusConsole;
@ -116,8 +119,10 @@ constexpr auto DISPLAY_TRANSFER_FLAGS =
GX_TRANSFER_IN_FORMAT (GX_TRANSFER_FMT_RGBA8) | GX_TRANSFER_OUT_FORMAT (GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_IN_FORMAT (GX_TRANSFER_FMT_RGBA8) | GX_TRANSFER_OUT_FORMAT (GX_TRANSFER_FMT_RGB8) |
GX_TRANSFER_SCALING (TRANSFER_SCALING); GX_TRANSFER_SCALING (TRANSFER_SCALING);
/// \brief Top screen render target /// \brief Top left screen render target
C3D_RenderTarget *s_top = nullptr; C3D_RenderTarget *s_topLeft = nullptr;
/// \brief Top right screen render target
C3D_RenderTarget *s_topRight = nullptr;
/// \brief Bottom screen render target /// \brief Bottom screen render target
C3D_RenderTarget *s_bottom = nullptr; C3D_RenderTarget *s_bottom = nullptr;
@ -271,12 +276,15 @@ void drawLogo ()
auto const uv1 = ImVec2 (subTex->left, subTex->top); auto const uv1 = ImVec2 (subTex->left, subTex->top);
auto const uv2 = ImVec2 (subTex->right, subTex->bottom); auto const uv2 = ImVec2 (subTex->right, subTex->bottom);
auto const drawList = ImGui::GetBackgroundDrawList ();
// draw to top screen // draw to top screen
ImGui::GetBackgroundDrawList ()->AddImage ( drawList->AddCallback (&imgui::citro3d::setZ, std::bit_cast<void *> (-5.0f));
&s_gfxTexture, ImVec2 (x1, y1), ImVec2 (x2, y2), uv1, uv2); drawList->AddImage (&s_gfxTexture, ImVec2 (x1, y1), ImVec2 (x2, y2), uv1, uv2);
drawList->AddCallback (&imgui::citro3d::setZ, std::bit_cast<void *> (0.0f));
// draw to bottom screen // draw to bottom screen
ImGui::GetBackgroundDrawList ()->AddImage (&s_gfxTexture, drawList->AddImage (&s_gfxTexture,
ImVec2 (x1, y1 + screenHeight * 0.5f), ImVec2 (x1, y1 + screenHeight * 0.5f),
ImVec2 (x2, y2 + screenHeight * 0.5f), ImVec2 (x2, y2 + screenHeight * 0.5f),
uv1, uv1,
@ -284,6 +292,94 @@ void drawLogo ()
#endif #endif
} }
#ifndef CLASSIC
struct Bubble
{
float x;
float y;
float z;
float scale;
float dy;
};
std::vector<Bubble> &getBubbles ()
{
static std::vector<Bubble> bubbles;
if (!bubbles.empty ())
return bubbles;
auto eng = std::default_random_engine (std::random_device{}());
auto dist = std::uniform_real_distribution<float> (0.0f, 1.0f);
constexpr auto COUNT = 250;
bubbles.reserve (COUNT);
for (unsigned i = 0; i < COUNT; ++i)
{
auto &bubble = bubbles.emplace_back ();
bubble.x = 500.0f * dist (eng) - 50.0f;
bubble.y = 240.0f * dist (eng);
bubble.z = std::floor (-5.0f * dist (eng));
bubble.scale = std::max (dist (eng) / 8.0f, 0.0625f);
bubble.dy = std::max (1.5f * dist (eng), 0.25f);
}
std::ranges::sort (
bubbles, [] (auto const &lhs_, auto const &rhs_) { return lhs_.z < rhs_.z; });
return bubbles;
}
#endif
void drawBubbles ()
{
#ifndef CLASSIC
// only draw in stereoscopy
if (!osGet3DSliderState ())
return;
auto const &io = ImGui::GetIO ();
auto const screenHeight = io.DisplaySize.y / 2.0f;
auto const tex = Tex3DS_GetSubTexture (s_gfxT3x, gfx_bubble_idx);
auto const uv1 = ImVec2 (tex->left, tex->top);
auto const uv2 = ImVec2 (tex->right, tex->bottom);
float lastZ = 0.0f;
auto const drawList = ImGui::GetBackgroundDrawList ();
for (auto &bubble : getBubbles ())
{
if (bubble.z != lastZ)
{
lastZ = bubble.z;
drawList->AddCallback (&imgui::citro3d::setZ, std::bit_cast<void *> (lastZ));
}
bubble.y -= bubble.dy;
if (bubble.y < 0.0f)
bubble.y = screenHeight;
auto const width = bubble.scale * tex->width;
auto const height = bubble.scale * tex->height;
auto const p1 = ImVec2 (
bubble.x + 100.0f * bubble.scale * std::sin (bubble.z + bubble.y / 40.0f), bubble.y);
auto const p2 = ImVec2 (p1.x + width, p1.y + height);
drawList->AddImage (&s_gfxTexture, p1, p2, uv1, uv2);
}
if (lastZ != 0.0f)
drawList->AddCallback (&imgui::citro3d::setZ, std::bit_cast<void *> (0.0f));
#endif
}
/// \brief Draw status /// \brief Draw status
void drawStatus () void drawStatus ()
{ {
@ -389,9 +485,10 @@ bool platform::init ()
romfsInit (); romfsInit ();
#endif #endif
gfxInitDefault (); gfxInitDefault ();
gfxSet3D (false);
#ifdef CLASSIC #ifdef CLASSIC
gfxSet3D (false);
consoleInit (GFX_TOP, &g_statusConsole); consoleInit (GFX_TOP, &g_statusConsole);
consoleInit (GFX_TOP, &g_logConsole); consoleInit (GFX_TOP, &g_logConsole);
consoleInit (GFX_BOTTOM, &g_sessionConsole); consoleInit (GFX_BOTTOM, &g_sessionConsole);
@ -399,6 +496,8 @@ bool platform::init ()
consoleSetWindow (&g_statusConsole, 0, 0, 50, 1); consoleSetWindow (&g_statusConsole, 0, 0, 50, 1);
consoleSetWindow (&g_logConsole, 0, 1, 50, 29); consoleSetWindow (&g_logConsole, 0, 1, 50, 29);
consoleSetWindow (&g_sessionConsole, 0, 0, 40, 30); consoleSetWindow (&g_sessionConsole, 0, 0, 40, 30);
#else
gfxSet3D (true);
#endif #endif
#ifndef NDEBUG #ifndef NDEBUG
@ -410,12 +509,17 @@ bool platform::init ()
#ifndef CLASSIC #ifndef CLASSIC
// initialize citro3d // initialize citro3d
C3D_Init (2 * C3D_DEFAULT_CMDBUF_SIZE); C3D_Init (4 * C3D_DEFAULT_CMDBUF_SIZE);
// create top screen render target // create top left screen render target
s_top = s_topLeft =
C3D_RenderTargetCreate (FB_HEIGHT * 0.5f, FB_WIDTH, GPU_RB_RGBA8, GPU_RB_DEPTH24_STENCIL8); C3D_RenderTargetCreate (FB_HEIGHT * 0.5f, FB_WIDTH, GPU_RB_RGBA8, GPU_RB_DEPTH24_STENCIL8);
C3D_RenderTargetSetOutput (s_top, GFX_TOP, GFX_LEFT, DISPLAY_TRANSFER_FLAGS); C3D_RenderTargetSetOutput (s_topLeft, GFX_TOP, GFX_LEFT, DISPLAY_TRANSFER_FLAGS);
// create top right screen render target
s_topRight =
C3D_RenderTargetCreate (FB_HEIGHT * 0.5f, FB_WIDTH, GPU_RB_RGBA8, GPU_RB_DEPTH24_STENCIL8);
C3D_RenderTargetSetOutput (s_topRight, GFX_TOP, GFX_RIGHT, DISPLAY_TRANSFER_FLAGS);
// create bottom screen render target // create bottom screen render target
s_bottom = C3D_RenderTargetCreate ( s_bottom = C3D_RenderTargetCreate (
@ -530,6 +634,7 @@ bool platform::loop ()
void platform::render () void platform::render ()
{ {
drawLogo (); drawLogo ();
drawBubbles ();
drawStatus (); drawStatus ();
#ifdef CLASSIC #ifdef CLASSIC
@ -542,10 +647,11 @@ void platform::render ()
C3D_FrameBegin (C3D_FRAME_SYNCDRAW); C3D_FrameBegin (C3D_FRAME_SYNCDRAW);
// clear frame/depth buffers // clear frame/depth buffers
C3D_RenderTargetClear (s_top, C3D_CLEAR_ALL, CLEAR_COLOR, 0); C3D_RenderTargetClear (s_topLeft, C3D_CLEAR_ALL, CLEAR_COLOR, 0);
C3D_RenderTargetClear (s_topRight, C3D_CLEAR_ALL, CLEAR_COLOR, 0);
C3D_RenderTargetClear (s_bottom, C3D_CLEAR_ALL, CLEAR_COLOR, 0); C3D_RenderTargetClear (s_bottom, C3D_CLEAR_ALL, CLEAR_COLOR, 0);
imgui::citro3d::render (s_top, s_bottom); imgui::citro3d::render (s_topLeft, s_topRight, s_bottom);
C3D_FrameEnd (0); C3D_FrameEnd (0);
#endif #endif
@ -562,7 +668,8 @@ void platform::exit ()
// free render targets // free render targets
C3D_RenderTargetDelete (s_bottom); C3D_RenderTargetDelete (s_bottom);
C3D_RenderTargetDelete (s_top); C3D_RenderTargetDelete (s_topRight);
C3D_RenderTargetDelete (s_topLeft);
// deinitialize citro3d // deinitialize citro3d
C3D_Fini (); C3D_Fini ();

View File

@ -3,7 +3,7 @@
; - RFC 3659 (https://tools.ietf.org/html/rfc3659) ; - RFC 3659 (https://tools.ietf.org/html/rfc3659)
; - suggested implementation details from https://cr.yp.to/ftp/filesystem.html ; - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
; ;
; Copyright (C) 2020 Michael Theall ; Copyright (C) 2024 Michael Theall
; ;
; This program is free software: you can redistribute it and/or modify ; 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 ; it under the terms of the GNU General Public License as published by
@ -23,6 +23,7 @@
; uniforms ; uniforms
; Projection matrix ; Projection matrix
.fvec proj[4] .fvec proj[4]
.fvec z
; constants ; constants
; [1.0, 0.0, 1.0/255.0, 0.0] ; [1.0, 0.0, 1.0/255.0, 0.0]
@ -43,6 +44,9 @@
mov r0.xy, inPos.xy mov r0.xy, inPos.xy
mov r0.zw, constants.yx mov r0.zw, constants.yx
; offset stereoscopy
add r0.x, z.x, r0.x
; outPos = proj * inPos ; outPos = proj * inPos
dp4 outPos.x, proj[0], r0 dp4 outPos.x, proj[0], r0
dp4 outPos.y, proj[1], r0 dp4 outPos.y, proj[1], r0