diff --git a/3ds/gfx/bubble.png b/3ds/gfx/bubble.png new file mode 100644 index 0000000..cd4808b Binary files /dev/null and b/3ds/gfx/bubble.png differ diff --git a/3ds/gfx/gfx.t3s b/3ds/gfx/gfx.t3s index 7a959ef..01dd4ee 100644 --- a/3ds/gfx/gfx.t3s +++ b/3ds/gfx/gfx.t3s @@ -5,6 +5,7 @@ battery2.png battery3.png battery4.png batteryCharge.png +bubble.png c3dlogo.png wifiNull.png wifi1.png diff --git a/CMakeLists.txt b/CMakeLists.txt index 65cf1ab..e37be25 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -214,7 +214,7 @@ elseif(NINTENDO_3DS) endif() target_compile_definitions(${FTPD_TARGET} PRIVATE - ANTI_ALIAS=1 + ANTI_ALIAS=0 ) find_package(PkgConfig REQUIRED) @@ -250,6 +250,7 @@ elseif(NINTENDO_3DS) 3ds/gfx/battery3.png 3ds/gfx/battery4.png 3ds/gfx/batteryCharge.png + 3ds/gfx/bubble.png 3ds/gfx/c3dlogo.png 3ds/gfx/wifi1.png 3ds/gfx/wifi2.png diff --git a/source/3ds/imgui_citro3d.cpp b/source/3ds/imgui_citro3d.cpp index cb596e8..76e2014 100644 --- a/source/3ds/imgui_citro3d.cpp +++ b/source/3ds/imgui_citro3d.cpp @@ -5,7 +5,7 @@ // // 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 // of this software and associated documentation files (the "Software"), to deal @@ -35,6 +35,7 @@ #include "imgui.h" #include +#include #include #include #include @@ -42,6 +43,13 @@ 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 std::vector s_fontRanges; @@ -175,6 +183,9 @@ void imgui::citro3d::init () shaderProgramInit (&s_program); shaderProgramSetVsh (&s_program, &s_vsh->DVLE[0]); + // get projection matrix uniform location + s_zLocation = shaderInstanceGetUniformLocation (s_program.vertexShader, "z"); + // get projection matrix uniform location s_projLocation = shaderInstanceGetUniformLocation (s_program.vertexShader, "proj"); @@ -417,7 +428,9 @@ void imgui::citro3d::exit () 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 auto const drawData = ImGui::GetDrawData (); @@ -497,12 +510,27 @@ void imgui::citro3d::render (C3D_RenderTarget *const top_, C3D_RenderTarget *con 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) - C3D_FrameDrawOn (top_); + if (!target) + continue; + + if (target == topLeft_) + s_slider = slider; + else if (target == topRight_) + s_slider = -slider; else - C3D_FrameDrawOn (bottom_); + s_slider = 0.0f; + + C3D_FrameDrawOn (target); 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.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) continue; + 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; @@ -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 (drawCmd_->UserCallbackData); + + C3D_FVUnifSet (GPU_VERTEX_SHADER, s_zLocation, s_slider * s_z, 0.0f, 0.0f, 0.0f); +} #endif diff --git a/source/3ds/imgui_citro3d.h b/source/3ds/imgui_citro3d.h index a713993..71ae183 100644 --- a/source/3ds/imgui_citro3d.h +++ b/source/3ds/imgui_citro3d.h @@ -5,7 +5,7 @@ // // 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 // of this software and associated documentation files (the "Software"), to deal @@ -30,6 +30,9 @@ #ifndef CLASSIC #include +struct ImDrawList; +struct ImDrawCmd; + namespace imgui { namespace citro3d @@ -40,7 +43,15 @@ void init (); void exit (); /// \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 diff --git a/source/3ds/platform.cpp b/source/3ds/platform.cpp index fc0e97a..fd4f342 100644 --- a/source/3ds/platform.cpp +++ b/source/3ds/platform.cpp @@ -39,12 +39,15 @@ #include #include +#include #include #include #include #include #include #include +#include +#include #ifdef CLASSIC 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_SCALING (TRANSFER_SCALING); -/// \brief Top screen render target -C3D_RenderTarget *s_top = nullptr; +/// \brief Top left screen render target +C3D_RenderTarget *s_topLeft = nullptr; +/// \brief Top right screen render target +C3D_RenderTarget *s_topRight = nullptr; /// \brief Bottom screen render target C3D_RenderTarget *s_bottom = nullptr; @@ -271,12 +276,15 @@ void drawLogo () auto const uv1 = ImVec2 (subTex->left, subTex->top); auto const uv2 = ImVec2 (subTex->right, subTex->bottom); + auto const drawList = ImGui::GetBackgroundDrawList (); + // draw to top screen - ImGui::GetBackgroundDrawList ()->AddImage ( - &s_gfxTexture, ImVec2 (x1, y1), ImVec2 (x2, y2), uv1, uv2); + drawList->AddCallback (&imgui::citro3d::setZ, std::bit_cast (-5.0f)); + drawList->AddImage (&s_gfxTexture, ImVec2 (x1, y1), ImVec2 (x2, y2), uv1, uv2); + drawList->AddCallback (&imgui::citro3d::setZ, std::bit_cast (0.0f)); // draw to bottom screen - ImGui::GetBackgroundDrawList ()->AddImage (&s_gfxTexture, + drawList->AddImage (&s_gfxTexture, ImVec2 (x1, y1 + screenHeight * 0.5f), ImVec2 (x2, y2 + screenHeight * 0.5f), uv1, @@ -284,6 +292,94 @@ void drawLogo () #endif } +#ifndef CLASSIC +struct Bubble +{ + float x; + float y; + float z; + float scale; + float dy; +}; + +std::vector &getBubbles () +{ + static std::vector bubbles; + + if (!bubbles.empty ()) + return bubbles; + + auto eng = std::default_random_engine (std::random_device{}()); + auto dist = std::uniform_real_distribution (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 (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 (0.0f)); +#endif +} + /// \brief Draw status void drawStatus () { @@ -389,9 +485,10 @@ bool platform::init () romfsInit (); #endif gfxInitDefault (); - gfxSet3D (false); #ifdef CLASSIC + gfxSet3D (false); + consoleInit (GFX_TOP, &g_statusConsole); consoleInit (GFX_TOP, &g_logConsole); consoleInit (GFX_BOTTOM, &g_sessionConsole); @@ -399,6 +496,8 @@ bool platform::init () consoleSetWindow (&g_statusConsole, 0, 0, 50, 1); consoleSetWindow (&g_logConsole, 0, 1, 50, 29); consoleSetWindow (&g_sessionConsole, 0, 0, 40, 30); +#else + gfxSet3D (true); #endif #ifndef NDEBUG @@ -410,12 +509,17 @@ bool platform::init () #ifndef CLASSIC // initialize citro3d - C3D_Init (2 * C3D_DEFAULT_CMDBUF_SIZE); + C3D_Init (4 * C3D_DEFAULT_CMDBUF_SIZE); - // create top screen render target - s_top = + // create top left screen render target + s_topLeft = 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 s_bottom = C3D_RenderTargetCreate ( @@ -530,6 +634,7 @@ bool platform::loop () void platform::render () { drawLogo (); + drawBubbles (); drawStatus (); #ifdef CLASSIC @@ -542,10 +647,11 @@ void platform::render () C3D_FrameBegin (C3D_FRAME_SYNCDRAW); // 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); - imgui::citro3d::render (s_top, s_bottom); + imgui::citro3d::render (s_topLeft, s_topRight, s_bottom); C3D_FrameEnd (0); #endif @@ -562,7 +668,8 @@ void platform::exit () // free render targets C3D_RenderTargetDelete (s_bottom); - C3D_RenderTargetDelete (s_top); + C3D_RenderTargetDelete (s_topRight); + C3D_RenderTargetDelete (s_topLeft); // deinitialize citro3d C3D_Fini (); diff --git a/source/3ds/vshader.v.pica b/source/3ds/vshader.v.pica index e7f6f71..5a2b1ba 100644 --- a/source/3ds/vshader.v.pica +++ b/source/3ds/vshader.v.pica @@ -3,7 +3,7 @@ ; - RFC 3659 (https://tools.ietf.org/html/rfc3659) ; - 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 ; it under the terms of the GNU General Public License as published by @@ -23,6 +23,7 @@ ; uniforms ; Projection matrix .fvec proj[4] +.fvec z ; constants ; [1.0, 0.0, 1.0/255.0, 0.0] @@ -43,6 +44,9 @@ mov r0.xy, inPos.xy mov r0.zw, constants.yx + ; offset stereoscopy + add r0.x, z.x, r0.x + ; outPos = proj * inPos dp4 outPos.x, proj[0], r0 dp4 outPos.y, proj[1], r0