Documentation and minor refactors

This commit is contained in:
Michael Theall 2020-04-06 00:36:03 -05:00
parent 86be27cd31
commit 34c6d61215
56 changed files with 1294 additions and 290 deletions

12
.gitignore vendored
View File

@ -10,8 +10,10 @@
*.pfs0 *.pfs0
*.smdh *.smdh
.gdb_history .gdb_history
build.*/ 3ds/build
ftpd 3ds/romfs/*.t3x
romfs.3ds/*.t3x linux/build
romfs.switch/*.zst linux/ftpd
romfs.switch/shaders/*.dksh switch/build
switch/romfs/*.zst
switch/romfs/shaders/*.dksh

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 791 B

After

Width:  |  Height:  |  Size: 791 B

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -17,19 +17,19 @@ all: 3dsx nro linux
nxlink: nxlink:
@$(MAKE) -f Makefile.switch nxlink @$(MAKE) -f Makefile.switch nxlink
3dslink: 3dsx 3dslink:
@/opt/devkitpro/tools/bin/3dslink $(TARGET)-3ds.3dsx @$(MAKE) -f Makefile.3ds 3dslink
format: format:
@clang-format -style=file -i $(filter-out \ @clang-format -style=file -i $(filter-out \
include/imgui.h \ include/imgui.h \
source/pc/imgui_impl_glfw.cpp \ source/linux/imgui_impl_glfw.cpp \
source/pc/imgui_impl_glfw.h \ source/linux/imgui_impl_glfw.h \
source/pc/imgui_impl_opengl3.cpp \ source/linux/imgui_impl_opengl3.cpp \
source/pc/imgui_impl_opengl3.h \ source/linux/imgui_impl_opengl3.h \
source/pc/KHR/khrplatform.h \ source/linux/KHR/khrplatform.h \
source/pc/glad.c \ source/linux/glad.c \
source/pc/glad/glad.h \ source/linux/glad/glad.h \
source/imgui/imgui.cpp \ source/imgui/imgui.cpp \
source/imgui/imgui_demo.cpp \ source/imgui/imgui_demo.cpp \
source/imgui/imgui_draw.cpp \ source/imgui/imgui_draw.cpp \
@ -41,9 +41,9 @@ format:
$(shell find source include -type f -name \*.c -o -name \*.cpp -o -name \*.h)) $(shell find source include -type f -name \*.c -o -name \*.cpp -o -name \*.h))
release: release-3ds release-nro release: release-3ds release-nro
@xz -c <$(TARGET)-3ds.3dsx >ftpd.3dsx.xz @xz -c <3ds/$(TARGET).3dsx >ftpd.3dsx.xz
@echo xz -c <$(TARGET)-3ds.cia >ftpd.cia.xz @xz -c <3ds/$(TARGET).cia >ftpd.cia.xz
@echo xz -c <$(TARGET)-nx.nro >ftpd.nro.xz @xz -c <switch/$(TARGET).nro >ftpd.nro.xz
nro: nro:
@$(MAKE) -f Makefile.switch all @$(MAKE) -f Makefile.switch all
@ -64,7 +64,7 @@ release-cia: release-3dsx
@$(MAKE) DEFINES=-NDEBUG -f Makefile.3ds cia @$(MAKE) DEFINES=-NDEBUG -f Makefile.3ds cia
release-3ds: release-3ds:
# can't let these three run in parallel with each other due to using same # can't let these run in parallel with each other due to using same
# .elf file name # .elf file name
@$(MAKE) DEFINES=-DNDEBUG -f Makefile.3ds 3dsx @$(MAKE) DEFINES=-DNDEBUG -f Makefile.3ds 3dsx
@$(MAKE) DEFINES=-DNDEBUG -f Makefile.3ds cia @$(MAKE) DEFINES=-DNDEBUG -f Makefile.3ds cia

View File

@ -31,13 +31,13 @@ include $(DEVKITARM)/3ds_rules
# - icon.png # - icon.png
# - <libctru folder>/default_icon.png # - <libctru folder>/default_icon.png
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
TARGET := $(notdir $(CURDIR))-3ds TARGET := 3ds/$(notdir $(CURDIR))
BUILD := build.3ds BUILD := 3ds/build
SOURCES := source source/3ds source/imgui SOURCES := source source/3ds source/imgui
DATA := data DATA := data
INCLUDES := include INCLUDES := include
GRAPHICS := gfx.3ds GRAPHICS := 3ds/gfx
ROMFS := romfs.3ds ROMFS := 3ds/romfs
GFXBUILD := $(ROMFS) GFXBUILD := $(ROMFS)
APP_TITLE := ftpd APP_TITLE := ftpd
@ -66,7 +66,7 @@ CFLAGS += $(INCLUDE) -DARM11 -D_3DS \
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17 CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17
ASFLAGS := -g $(ARCH) ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(TARGET).map $(OPTIMIZE) LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) $(OPTIMIZE)
LIBS := -lcitro3d -lctru -lm LIBS := -lcitro3d -lctru -lm
@ -81,7 +81,7 @@ LIBDIRS := $(CTRULIB)
# no real need to edit anything past this point unless you need to add additional # no real need to edit anything past this point unless you need to add additional
# rules for different file extensions # rules for different file extensions
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR))) ifneq ($(TOPDIR)/$(BUILD),$(CURDIR))
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
export OUTPUT := $(CURDIR)/$(TARGET) export OUTPUT := $(CURDIR)/$(TARGET)
@ -165,7 +165,7 @@ ifneq ($(ROMFS),)
export _3DSXFLAGS += --romfs=$(CURDIR)/$(ROMFS) export _3DSXFLAGS += --romfs=$(CURDIR)/$(ROMFS)
endif endif
.PHONY: $(BUILD) clean all 3dsx cia .PHONY: $(BUILD) clean all 3dsx cia 3dslink
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
all: $(BUILD) $(GFXBUILD) $(DEPSDIR) $(ROMFS_T3XFILES) $(T3XHFILES) all: $(BUILD) $(GFXBUILD) $(DEPSDIR) $(ROMFS_T3XFILES) $(T3XHFILES)
@ -196,6 +196,9 @@ $(GFXBUILD)/%.t3x $(BUILD)/%.h: %.t3s
cia: $(BUILD) $(GFXBUILD) $(DEPSDIR) $(ROMFS_T3XFILES) $(T3XHFILES) cia: $(BUILD) $(GFXBUILD) $(DEPSDIR) $(ROMFS_T3XFILES) $(T3XHFILES)
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile.3ds cia @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile.3ds cia
3dslink: 3dsx
@3dslink $(OUTPUT).3dsx
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
clean: clean:
@echo clean ... @echo clean ...

View File

@ -1,12 +1,12 @@
TARGET := ftpd TARGET := linux/ftpd
BUILD := build.linux BUILD := linux/build
CFILES := $(wildcard source/pc/*.c) CFILES := $(wildcard source/linux/*.c)
OFILES := $(patsubst source/%,$(BUILD)/%,$(CFILES:.c=.c.o)) OFILES := $(patsubst source/%,$(BUILD)/%,$(CFILES:.c=.c.o))
CXXFILES := $(wildcard source/*.cpp source/imgui/*.cpp source/pc/*.cpp) CXXFILES := $(wildcard source/*.cpp source/imgui/*.cpp source/linux/*.cpp)
OXXFILES := $(patsubst source/%,$(BUILD)/%,$(CXXFILES:.cpp=.cpp.o)) OXXFILES := $(patsubst source/%,$(BUILD)/%,$(CXXFILES:.cpp=.cpp.o))
CPPFLAGS := -g -Wall -pthread -Iinclude -Isource/pc \ CPPFLAGS := -g -Wall -pthread -Iinclude -Isource/linux \
`pkg-config --cflags gl glfw3` \ `pkg-config --cflags gl glfw3` \
-DSTATUS_STRING="\"ftpd v$(VERSION)\"" \ -DSTATUS_STRING="\"ftpd v$(VERSION)\"" \
-DIMGUI_DISABLE_INCLUDE_IMCONFIG_H=1 \ -DIMGUI_DISABLE_INCLUDE_IMCONFIG_H=1 \

View File

@ -42,13 +42,13 @@ APP_AUTHOR := mtheall, TuxSH, WinterMute
ICON := meta/ftpd.jpg ICON := meta/ftpd.jpg
APP_VERSION := $(VERSION) APP_VERSION := $(VERSION)
TARGET := $(notdir $(CURDIR))-nx TARGET := switch/$(notdir $(CURDIR))
BUILD := build.switch BUILD := switch/build
SOURCES := source source/imgui source/nx SOURCES := source source/imgui source/switch
DATA := data DATA := data
INCLUDES := include INCLUDES := include
GRAPHICS := gfx.switch GRAPHICS := switch/gfx
ROMFS := romfs.switch ROMFS := switch/romfs
# Output folders for autogenerated files in romfs # Output folders for autogenerated files in romfs
OUT_SHADERS := shaders OUT_SHADERS := shaders
@ -85,7 +85,7 @@ LIBDIRS := $(PORTLIBS) $(LIBNX)
# no real need to edit anything past this point unless you need to add additional # no real need to edit anything past this point unless you need to add additional
# rules for different file extensions # rules for different file extensions
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR))) ifneq ($(TOPDIR)/$(BUILD),$(CURDIR))
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
export OUTPUT := $(CURDIR)/$(TARGET) export OUTPUT := $(CURDIR)/$(TARGET)
@ -193,7 +193,7 @@ all: $(ROMFS_TARGETS) | $(BUILD)
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile.switch @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile.switch
nxlink: all nxlink: all
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile.switch nxlink @nxlink -s $(OUTPUT).nro
$(BUILD): $(BUILD):
@mkdir -p $@ @mkdir -p $@
@ -260,9 +260,6 @@ ifeq ($(strip $(APP_JSON)),)
all : $(OUTPUT).nro all : $(OUTPUT).nro
nxlink: $(OUTPUT).nro
@nxlink -s $(OUTPUT).nro
ifeq ($(strip $(NO_NACP)),) ifeq ($(strip $(NO_NACP)),)
$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp $(ROMFS_DEPS) $(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp $(ROMFS_DEPS)
else else

View File

@ -29,8 +29,11 @@
namespace fs namespace fs
{ {
/// \brief Print size in human-readable format (KiB, MiB, etc)
/// \param size_ Size to print
std::string printSize (std::uint64_t size_); std::string printSize (std::uint64_t size_);
/// \brief File I/O object
class File class File
{ {
public: public:
@ -40,51 +43,97 @@ public:
File (File const &that_) = delete; File (File const &that_) = delete;
/// \brief Move constructor
/// \param that_ Object to move from
File (File &&that_); File (File &&that_);
File &operator= (File const &that_) = delete; File &operator= (File const &that_) = delete;
/// \brief Move assignment
/// \param that_ Object to move from
File &operator= (File &&that_); File &operator= (File &&that_);
operator bool () const; /// \brief bool cast operator
operator FILE * () const; explicit operator bool () const;
/// \brief std::FILE* cast operator
operator std::FILE * () const;
/// \brief Set buffer size
/// \param size_ Buffer size
void setBufferSize (std::size_t size_); void setBufferSize (std::size_t size_);
/// \brief Open file
/// \param path_ Path to open
/// \param mode_ Access mode (\sa std::fopen)
bool open (char const *path_, char const *mode_ = "rb"); bool open (char const *path_, char const *mode_ = "rb");
/// \brief Close file
void close (); void close ();
/// \brief Seek to file position
/// \param pos_ File position
/// \param origin_ Reference position (\sa std::fseek)
ssize_t seek (std::size_t pos_, int origin_); ssize_t seek (std::size_t pos_, int origin_);
/// \brief Read data
/// \param data_ Output buffer
/// \param size_ Size to read
/// \note Can return partial reads
ssize_t read (void *data_, std::size_t size_); ssize_t read (void *data_, std::size_t size_);
/// \brief Read data
/// \param data_ Output buffer
/// \param size_ Size to read
/// \note Fails on partial reads and errors
bool readAll (void *data_, std::size_t size_); bool readAll (void *data_, std::size_t size_);
/// \brief Write data
/// \param data_ Input data
/// \param size_ Size to write
/// \note Can return partial writes
ssize_t write (void const *data_, std::size_t size_); ssize_t write (void const *data_, std::size_t size_);
/// \brief Write data
/// \param data_ Input data
/// \param size_ Size to write
/// \note Fails on partials writes and errors
bool writeAll (void const *data_, std::size_t size_); bool writeAll (void const *data_, std::size_t size_);
/// \brief Read data
/// \tparam T Type to read
template <typename T> template <typename T>
T read () T read ()
{ {
T data; T data;
if (read (&data, sizeof (data)) != sizeof (data)) if (!readAll (&data, sizeof (data)))
std::abort (); std::abort ();
return data; return data;
} }
/// \brief Write data
/// \tparam T type to write
/// \param data_ Data to write
template <typename T> template <typename T>
void write (T const &data_) void write (T const &data_)
{ {
if (write (&data_, sizeof (data_)) != sizeof (data_)) if (!writeAll (&data_, sizeof (data_)))
std::abort (); std::abort ();
} }
private: private:
/// \brief Underlying std::FILE*
std::unique_ptr<std::FILE, int (*) (std::FILE *)> m_fp{nullptr, nullptr}; std::unique_ptr<std::FILE, int (*) (std::FILE *)> m_fp{nullptr, nullptr};
/// \brief Buffer
std::unique_ptr<char[]> m_buffer; std::unique_ptr<char[]> m_buffer;
/// \brief Buffer size
std::size_t m_bufferSize = 0; std::size_t m_bufferSize = 0;
}; };
/// Directory object
class Dir class Dir
{ {
public: public:
@ -94,20 +143,35 @@ public:
Dir (Dir const &that_) = delete; Dir (Dir const &that_) = delete;
/// \brief Move constructor
/// \param that_ Object to move from
Dir (Dir &&that_); Dir (Dir &&that_);
Dir &operator= (Dir const &that_) = delete; Dir &operator= (Dir const &that_) = delete;
/// \brief Move assignment
/// \param that_ Object to move from
Dir &operator= (Dir &&that_); Dir &operator= (Dir &&that_);
operator bool () const; /// \brief bool cast operator
explicit operator bool () const;
/// \brief DIR* cast operator
operator DIR * () const; operator DIR * () const;
/// \brief Open directory
/// \param path_ Path to open
bool open (char const *const path_); bool open (char const *const path_);
/// \brief Close directory
void close (); void close ();
/// \brief Read a directory entry
/// \note Returns nullptr on end-of-directory or error; check errno
struct dirent *read (); struct dirent *read ();
private: private:
/// \brief Underlying DIR*
std::unique_ptr<DIR, int (*) (DIR *)> m_dp{nullptr, nullptr}; std::unique_ptr<DIR, int (*) (DIR *)> m_dp{nullptr, nullptr};
}; };
} }

View File

@ -34,40 +34,63 @@
class FtpServer; class FtpServer;
using UniqueFtpServer = std::unique_ptr<FtpServer>; using UniqueFtpServer = std::unique_ptr<FtpServer>;
/// \brief FTP server
class FtpServer class FtpServer
{ {
public: public:
~FtpServer (); ~FtpServer ();
/// \brief Draw server and all of its sessions
void draw (); void draw ();
/// \brief Create server
/// \param port_ Port to listen on
static UniqueFtpServer create (std::uint16_t port_); static UniqueFtpServer create (std::uint16_t port_);
/// \brief Update free space
static void updateFreeSpace (); static void updateFreeSpace ();
/// \brief Server start time
static std::time_t startTime (); static std::time_t startTime ();
private: private:
/// \brief Paramterized constructor
/// \param port_ Port to listen on
FtpServer (std::uint16_t port_); FtpServer (std::uint16_t port_);
/// \brief Handle when start button is pressed
void handleStartButton (); void handleStartButton ();
/// \brief Handle when stop button is pressed
void handleStopButton (); void handleStopButton ();
/// \brief Server loop
void loop (); void loop ();
/// \brief Thread entry point
void threadFunc (); void threadFunc ();
/// \brief Thread
platform::Thread m_thread; platform::Thread m_thread;
/// \brief Mutex
platform::Mutex m_lock; platform::Mutex m_lock;
/// \brief Listen socket
UniqueSocket m_socket; UniqueSocket m_socket;
/// \brief ImGui window name
std::string m_name; std::string m_name;
/// \brief Log
SharedLog m_log; SharedLog m_log;
/// \brief Sessions
std::vector<UniqueFtpSession> m_sessions; std::vector<UniqueFtpSession> m_sessions;
std::uint16_t m_port; /// \brief Port to listen on
std::uint16_t const m_port;
/// \brief Whether thread should quit
std::atomic<bool> m_quit; std::atomic<bool> m_quit;
}; };

View File

@ -34,33 +34,54 @@
class FtpSession; class FtpSession;
using UniqueFtpSession = std::unique_ptr<FtpSession>; using UniqueFtpSession = std::unique_ptr<FtpSession>;
/// \brief FTP session
class FtpSession class FtpSession
{ {
public: public:
~FtpSession (); ~FtpSession ();
/// \brief Whether session sockets are all inactive
bool dead (); bool dead ();
/// \brief Draw session status
void draw (); void draw ();
/// \brief Create session
/// \param commandSocket_ Command socket
static UniqueFtpSession create (UniqueSocket commandSocket_); static UniqueFtpSession create (UniqueSocket commandSocket_);
/// \brief Poll for activity
/// \param sessions_ Sessions to poll
static void poll (std::vector<UniqueFtpSession> const &sessions_); static void poll (std::vector<UniqueFtpSession> const &sessions_);
private: private:
constexpr static auto COMMAND_BUFFERSIZE = 4096; /// \brief Command buffer size
constexpr static auto COMMAND_BUFFERSIZE = 4096;
/// \brief Response buffer size
constexpr static auto RESPONSE_BUFFERSIZE = 32768; constexpr static auto RESPONSE_BUFFERSIZE = 32768;
constexpr static auto XFER_BUFFERSIZE = 65536;
constexpr static auto FILE_BUFFERSIZE = 4 * XFER_BUFFERSIZE; /// \brief Transfer buffersize
constexpr static auto XFER_BUFFERSIZE = 65536;
/// \brief File buffersize
constexpr static auto FILE_BUFFERSIZE = 4 * XFER_BUFFERSIZE;
#ifdef _3DS #ifdef _3DS
constexpr static auto SOCK_BUFFERSIZE = 32768; /// \brief Socket buffer size
constexpr static auto SOCK_BUFFERSIZE = 32768;
/// \brief Amount of file position history to keep
constexpr static auto POSITION_HISTORY = 100; constexpr static auto POSITION_HISTORY = 100;
#else #else
constexpr static auto SOCK_BUFFERSIZE = XFER_BUFFERSIZE; /// \brief Socket buffer size
constexpr static auto SOCK_BUFFERSIZE = XFER_BUFFERSIZE;
/// \brief Amount of file position history to keep
constexpr static auto POSITION_HISTORY = 300; constexpr static auto POSITION_HISTORY = 300;
#endif #endif
/// \brief Session state
enum class State enum class State
{ {
COMMAND, COMMAND,
@ -68,6 +89,7 @@ private:
DATA_TRANSFER, DATA_TRANSFER,
}; };
/// \brief Transfer file mode
enum class XferFileMode enum class XferFileMode
{ {
RETR, RETR,
@ -75,6 +97,7 @@ private:
APPE, APPE,
}; };
/// \brief Transfer directory mode
enum class XferDirMode enum class XferDirMode
{ {
LIST, LIST,
@ -84,122 +107,322 @@ private:
STAT, STAT,
}; };
/// \brief Parameterized constructor
/// \param commandSocket_ Command socket
FtpSession (UniqueSocket commandSocket_); FtpSession (UniqueSocket commandSocket_);
/// \brief Set session state
/// \param state_ State to set
/// \param closePasv_ Whether to close listening socket
/// \param closeData_ Whether to close data socket
void setState (State state_, bool closePasv_, bool closeData_); void setState (State state_, bool closePasv_, bool closeData_);
/// \brief Close data socket
void closeData (); void closeData ();
/// \brief Change working directory
bool changeDir (char const *args_); bool changeDir (char const *args_);
/// \brief Accept connection as data socket
bool dataAccept (); bool dataAccept ();
/// \brief Connect data socket
bool dataConnect (); bool dataConnect ();
void updateFreeSpace (); /// \brief Fill directory entry
/// \param st_ Entry status
/// \param path_ Path name
/// \param type_ MLST type
int fillDirent (struct stat const &st_, std::string_view path_, char const *type_ = nullptr); int fillDirent (struct stat const &st_, std::string_view path_, char const *type_ = nullptr);
/// \brief Fill directory entry
/// \param path_ Path name
/// \param type_ MLST type
int fillDirent (std::string const &path_, char const *type_ = nullptr); int fillDirent (std::string const &path_, char const *type_ = nullptr);
/// \brief Transfer file
/// \param args_ Command arguments
/// \param mode_ Transfer file mode
void xferFile (char const *args_, XferFileMode mode_); void xferFile (char const *args_, XferFileMode mode_);
/// \brief Transfer directory
/// \param args_ Command arguments
/// \param mode_ Transfer directory mode
void xferDir (char const *args_, XferDirMode mode_, bool workaround_); void xferDir (char const *args_, XferDirMode mode_, bool workaround_);
/// \brief Read command
/// \param events_ Poll events
void readCommand (int events_); void readCommand (int events_);
/// \brief Write response
void writeResponse (); void writeResponse ();
/// \brief Send response
/// \param fmt_ Message format
__attribute__ ((format (printf, 2, 3))) void sendResponse (char const *fmt_, ...); __attribute__ ((format (printf, 2, 3))) void sendResponse (char const *fmt_, ...);
/// \brief Send response
/// \param response_ Response message
void sendResponse (std::string_view response_); void sendResponse (std::string_view response_);
/// \brief Transfer function
bool (FtpSession::*m_transfer) () = nullptr; bool (FtpSession::*m_transfer) () = nullptr;
/// \brief Transfer directory list
bool listTransfer (); bool listTransfer ();
/// \brief Transfer download
bool retrieveTransfer (); bool retrieveTransfer ();
/// \brief Transfer upload
bool storeTransfer (); bool storeTransfer ();
/// \brief Mutex
platform::Mutex m_lock; platform::Mutex m_lock;
/// \brief Command socket
SharedSocket m_commandSocket; SharedSocket m_commandSocket;
/// \brief Data listen socker
UniqueSocket m_pasvSocket; UniqueSocket m_pasvSocket;
/// \brief Data socket
SharedSocket m_dataSocket; SharedSocket m_dataSocket;
/// \brief Sockets pending close
std::vector<SharedSocket> m_pendingCloseSocket; std::vector<SharedSocket> m_pendingCloseSocket;
/// \brief Command buffer
IOBuffer m_commandBuffer; IOBuffer m_commandBuffer;
/// \brief Response buffer
IOBuffer m_responseBuffer; IOBuffer m_responseBuffer;
/// \brief Transfer buffer
IOBuffer m_xferBuffer; IOBuffer m_xferBuffer;
SockAddr m_pasvAddr; /// \brief Address from last PORT command
SockAddr m_portAddr; SockAddr m_portAddr;
/// \brief Current working directory
std::string m_cwd = "/"; std::string m_cwd = "/";
/// \brief List working directory
std::string m_lwd; std::string m_lwd;
/// \brief Path from RNFR command
std::string m_rename; std::string m_rename;
/// \brief Current work item
std::string m_workItem; std::string m_workItem;
/// \brief ImGui window name
std::string m_windowName; std::string m_windowName;
/// \brief ImGui plot widget name
std::string m_plotName; std::string m_plotName;
/// \brief Position from REST command
std::uint64_t m_restartPosition = 0; std::uint64_t m_restartPosition = 0;
std::uint64_t m_filePosition = 0;
std::uint64_t m_fileSize = 0;
/// \brief Current file position
std::uint64_t m_filePosition = 0;
/// \brief File size of current transfer
std::uint64_t m_fileSize = 0;
/// \brief Last file position update timestamp
platform::steady_clock::time_point m_filePositionTime; platform::steady_clock::time_point m_filePositionTime;
/// \brief File position history
std::uint64_t m_filePositionHistory[POSITION_HISTORY]; std::uint64_t m_filePositionHistory[POSITION_HISTORY];
/// \brief File position history deltas
float m_filePositionDeltas[POSITION_HISTORY]; float m_filePositionDeltas[POSITION_HISTORY];
/// \brief Transfer rate (EWMA low-pass filtered)
float m_xferRate; float m_xferRate;
/// \brief Session state
State m_state = State::COMMAND; State m_state = State::COMMAND;
/// \brief File being transferred
fs::File m_file; fs::File m_file;
/// \brief Directory being transferred
fs::Dir m_dir; fs::Dir m_dir;
/// \brief Directory transfer mode
XferDirMode m_xferDirMode; XferDirMode m_xferDirMode;
/// \brief Whether previous command was PASV
bool m_pasv : 1; bool m_pasv : 1;
/// \brief Whether previous command was PORT
bool m_port : 1; bool m_port : 1;
/// \brief Whether receiving data
bool m_recv : 1; bool m_recv : 1;
/// \brief Whether sending data
bool m_send : 1; bool m_send : 1;
/// \brief Whether urgent (out-of-band) data is on the way
bool m_urgent : 1; bool m_urgent : 1;
/// \brief Whether MLST type fact is enabled
bool m_mlstType : 1; bool m_mlstType : 1;
/// \brief Whether MLST size fact is enabled
bool m_mlstSize : 1; bool m_mlstSize : 1;
/// \brief Whether MLST modify fact is enabled
bool m_mlstModify : 1; bool m_mlstModify : 1;
/// \brief Whether MLST perm fact is enabled
bool m_mlstPerm : 1; bool m_mlstPerm : 1;
/// \brief Whether MLST unix.mode fact is enabled
bool m_mlstUnixMode : 1; bool m_mlstUnixMode : 1;
/// \brief Abort a transfer
/// \param args_ Command arguments
void ABOR (char const *args_); void ABOR (char const *args_);
/// \brief Allocate space
/// \param args_ Command arguments
void ALLO (char const *args_); void ALLO (char const *args_);
/// \brief Append data to a file
/// \param args_ Command arguments
void APPE (char const *args_); void APPE (char const *args_);
/// \brief CWD to parent directory
/// \param args_ Command arguments
void CDUP (char const *args_); void CDUP (char const *args_);
/// \brief Change working directory
/// \param args_ Command arguments
void CWD (char const *args_); void CWD (char const *args_);
/// \brief Delete a file
/// \param args_ Command arguments
void DELE (char const *args_); void DELE (char const *args_);
/// \brief List server features
/// \param args_ Command arguments
void FEAT (char const *args_); void FEAT (char const *args_);
/// \brief Print server help
/// \param args_ Command arguments
void HELP (char const *args_); void HELP (char const *args_);
/// \brief List directory
/// \param args_ Command arguments
void LIST (char const *args_); void LIST (char const *args_);
/// \brief Last modification time
/// \param args_ Command arguments
void MDTM (char const *args_); void MDTM (char const *args_);
/// \brief Create a directory
/// \param args_ Command arguments
void MKD (char const *args_); void MKD (char const *args_);
/// \brief Machine list directory
/// \param args_ Command arguments
void MLSD (char const *args_); void MLSD (char const *args_);
/// \brief Machine list
/// \param args_ Command arguments
void MLST (char const *args_); void MLST (char const *args_);
/// \brief Set transfer mode
/// \param args_ Command arguments
void MODE (char const *args_); void MODE (char const *args_);
/// \brief Name list
/// \param args_ Command arguments
void NLST (char const *args_); void NLST (char const *args_);
/// \brief No-op
/// \param args_ Command arguments
void NOOP (char const *args_); void NOOP (char const *args_);
/// \brief Set server options
/// \param args_ Command arguments
void OPTS (char const *args_); void OPTS (char const *args_);
/// \brief Password
/// \param args_ Command arguments
void PASS (char const *args_); void PASS (char const *args_);
/// \brief Request an address to connect to for data transfers
/// \param args_ Command arguments
void PASV (char const *args_); void PASV (char const *args_);
/// \brief Provide an address to connect to for data transfers
/// \param args_ Command arguments
void PORT (char const *args_); void PORT (char const *args_);
/// \brief Print working directory
/// \param args_ Command arguments
void PWD (char const *args_); void PWD (char const *args_);
/// \brief Terminate session
/// \param args_ Command arguments
void QUIT (char const *args_); void QUIT (char const *args_);
/// \brief Restart a file transfer
/// \param args_ Command arguments
void REST (char const *args_); void REST (char const *args_);
/// \brief Retrieve a file
/// \param args_ Command arguments
/// \note Requires a PASV or PORT connection
void RETR (char const *args_); void RETR (char const *args_);
/// \brief Remove a directory
/// \param args_ Command arguments
void RMD (char const *args_); void RMD (char const *args_);
/// \brief Rename from
/// \param args_ Command arguments
void RNFR (char const *args_); void RNFR (char const *args_);
/// \brief Rename to
/// \param args_ Command arguments
void RNTO (char const *args_); void RNTO (char const *args_);
/// \brief Get file size
/// \param args_ Command arguments
void SIZE (char const *args_); void SIZE (char const *args_);
/// \brief Get status
/// \param args_ Command arguments
/// \note If no argument is supplied, and a transfer is occurring, get the current transfer
/// status. If no argument is supplied, and no transfer is occurring, get the server status. If
/// an argument is supplied, this is equivalent to LIST, except the data is sent over the
/// command socket.
void STAT (char const *args_); void STAT (char const *args_);
/// \brief Store a file
/// \param args_ Command arguments
void STOR (char const *args_); void STOR (char const *args_);
/// \brief Store a unique file
/// \param args_ Command arguments
void STOU (char const *args_); void STOU (char const *args_);
/// \brief Set file structure
/// \param args_ Command arguments
void STRU (char const *args_); void STRU (char const *args_);
/// \brief Identify system
/// \param args_ Command arguments
void SYST (char const *args_); void SYST (char const *args_);
/// \brief Set representation type
/// \param args_ Command arguments
void TYPE (char const *args_); void TYPE (char const *args_);
/// \brief User name
/// \param args_ Command arguments
void USER (char const *args_); void USER (char const *args_);
/// \brief Map of command handlers
static std::vector<std::pair<std::string_view, void (FtpSession::*) (char const *)>> const static std::vector<std::pair<std::string_view, void (FtpSession::*) (char const *)>> const
handlers; handlers;
}; };

View File

@ -23,29 +23,66 @@
#include <cstddef> #include <cstddef>
#include <memory> #include <memory>
/// \brief I/O buffer
/// [unusable][usedArea][freeArea]
class IOBuffer class IOBuffer
{ {
public: public:
~IOBuffer (); ~IOBuffer ();
/// \brief Parameterized constructor
/// \param size_ Buffer size
IOBuffer (std::size_t size_); IOBuffer (std::size_t size_);
/// \brief Get pointer to writable area
char *freeArea () const; char *freeArea () const;
/// \brief Get size of writable area
std::size_t freeSize () const; std::size_t freeSize () const;
void markFree (std::size_t size_);
/// \brief Get pointer to readable area
char *usedArea () const; char *usedArea () const;
/// \brief Get size of readable area
std::size_t usedSize () const; std::size_t usedSize () const;
/// \brief Consume data from the beginning of usedArea
/// \param size_ Size to consume
/// [unusable][+++++usedArea][freeArea]
/// becomes
/// [unusable+++++][usedArea][freeArea]
void markFree (std::size_t size_);
/// \brief Produce data to the end of usedArea from freeArea
/// [unusable][usedArea][++++++freeArea]
/// becomes
/// [unusable][usedArea++++++][freeArea]
void markUsed (std::size_t size_); void markUsed (std::size_t size_);
/// \brief Whether usedArea is empty
bool empty () const; bool empty () const;
/// \brief Get buffer capacity
std::size_t capacity () const; std::size_t capacity () const;
/// \brief Clear buffer; usedArea becomes empty
/// [unusable][usedArea][++++++freeArea]
/// becomes
/// [freeArea++++++++++++++++++++++++++]
void clear (); void clear ();
/// \brief Move usedArea to the beginning of the buffer
/// [unusable][usedArea][freeArea]
/// becomes
/// [usedArea][freeArea++++++++++]
void coalesce (); void coalesce ();
private: private:
/// \brief Buffer
std::unique_ptr<char[]> m_buffer; std::unique_ptr<char[]> m_buffer;
/// \brief Buffer size
std::size_t const m_size; std::size_t const m_size;
/// \brief Start of usedArea
std::size_t m_start = 0; std::size_t m_start = 0;
std::size_t m_end = 0; /// \brief Start of freeArea
std::size_t m_end = 0;
}; };

View File

@ -32,9 +32,11 @@ class Log;
using SharedLog = std::shared_ptr<Log>; using SharedLog = std::shared_ptr<Log>;
using WeakLog = std::weak_ptr<Log>; using WeakLog = std::weak_ptr<Log>;
/// \brief Log object
class Log class Log
{ {
public: public:
/// \brief Log level
enum Level enum Level
{ {
DEBUG, DEBUG,
@ -46,36 +48,72 @@ public:
~Log (); ~Log ();
/// \brief Draw log
void draw (); void draw ();
/// \brief Create log
static SharedLog create (); static SharedLog create ();
/// \brief Bind log
/// \param log_ Log to bind
static void bind (SharedLog log_); static void bind (SharedLog log_);
/// \brief Add debug message to bound log
/// \param fmt_ Message format
__attribute__ ((format (printf, 1, 2))) static void debug (char const *fmt_, ...); __attribute__ ((format (printf, 1, 2))) static void debug (char const *fmt_, ...);
/// \brief Add info message to bound log
/// \param fmt_ Message format
__attribute__ ((format (printf, 1, 2))) static void info (char const *fmt_, ...); __attribute__ ((format (printf, 1, 2))) static void info (char const *fmt_, ...);
/// \brief Add error message to bound log
/// \param fmt_ Message format
__attribute__ ((format (printf, 1, 2))) static void error (char const *fmt_, ...); __attribute__ ((format (printf, 1, 2))) static void error (char const *fmt_, ...);
/// \brief Add command message to bound log
/// \param fmt_ Message format
__attribute__ ((format (printf, 1, 2))) static void command (char const *fmt_, ...); __attribute__ ((format (printf, 1, 2))) static void command (char const *fmt_, ...);
/// \brief Add response message to bound log
/// \param fmt_ Message format
__attribute__ ((format (printf, 1, 2))) static void response (char const *fmt_, ...); __attribute__ ((format (printf, 1, 2))) static void response (char const *fmt_, ...);
/// \brief Add log message to bound log
/// \param level_ Log level
/// \param fmt_ Message format
/// \param ap_ Message arguments
static void log (Level level_, char const *fmt_, va_list ap_); static void log (Level level_, char const *fmt_, va_list ap_);
/// \brief Add log message to bound log
/// \param level_ Log level
/// \param message_ Message to log
static void log (Level level_, std::string_view message_); static void log (Level level_, std::string_view message_);
private: private:
Log (); Log ();
/// \brief Add log message
/// \param level_ Log level
/// \param fmt_ Message format
/// \param ap_ Message arguments
void _log (Level level_, char const *fmt_, va_list ap_); void _log (Level level_, char const *fmt_, va_list ap_);
/// \brief Log message
struct Message struct Message
{ {
/// \brief Parameterized constructor
/// \param level_ Log level
/// \param message_ Log message
Message (Level const level_, std::string message_) Message (Level const level_, std::string message_)
: level (level_), message (std::move (message_)) : level (level_), message (std::move (message_))
{ {
} }
/// \brief Log level
Level level; Level level;
/// \brief Log message
std::string message; std::string message;
}; };
/// \brief Log messages
std::vector<Message> m_messages; std::vector<Message> m_messages;
/// \brief Log lock
platform::Mutex m_lock; platform::Mutex m_lock;
}; };

View File

@ -31,65 +31,102 @@
namespace platform namespace platform
{ {
/// \brief Initialize platform
bool init (); bool init ();
/// \brief Platform loop
bool loop (); bool loop ();
/// \brief Platform render
void render (); void render ();
/// \brief Deinitialize platform
void exit (); void exit ();
#ifdef _3DS #ifdef _3DS
/// \brief Steady clock
struct steady_clock struct steady_clock
{ {
using rep = std::uint64_t; /// \brief Type representing number of ticks
using period = std::ratio<1, SYSCLOCK_ARM11>; using rep = std::uint64_t;
using duration = std::chrono::duration<rep, period>;
/// \brief Type representing ratio of clock period in seconds
using period = std::ratio<1, SYSCLOCK_ARM11>;
/// \brief Duration type
using duration = std::chrono::duration<rep, period>;
/// \brief Timestamp type
using time_point = std::chrono::time_point<steady_clock>; using time_point = std::chrono::time_point<steady_clock>;
/// \brief Whether clock is steady
constexpr static bool is_steady = true; constexpr static bool is_steady = true;
/// \brief Current timestamp
static time_point now () noexcept static time_point now () noexcept
{ {
return time_point (duration (svcGetSystemTick ())); return time_point (duration (svcGetSystemTick ()));
} }
}; };
#else #else
/// \brief Steady clock
using steady_clock = std::chrono::steady_clock; using steady_clock = std::chrono::steady_clock;
#endif #endif
/// \brief Platform thread
class Thread class Thread
{ {
public: public:
~Thread (); ~Thread ();
Thread (); Thread ();
/// \brief Parameterized constructor
/// \param func_ Thread entrypoint
Thread (std::function<void ()> func_); Thread (std::function<void ()> func_);
Thread (Thread const &that_) = delete; Thread (Thread const &that_) = delete;
/// \brief Move constructor
/// \param that_ Object to move from
Thread (Thread &&that_); Thread (Thread &&that_);
Thread &operator= (Thread const &that_) = delete; Thread &operator= (Thread const &that_) = delete;
/// \brief Move assignment
/// \param that_ Object to move from
Thread &operator= (Thread &&that_); Thread &operator= (Thread &&that_);
/// \brief Join thread
void join (); void join ();
/// \brief Suspend current thread
/// \param timeout_ Minimum time to sleep
static void sleep (std::chrono::milliseconds timeout_); static void sleep (std::chrono::milliseconds timeout_);
private: private:
class privateData_t; class privateData_t;
/// \brief pimpl
std::unique_ptr<privateData_t> m_d; std::unique_ptr<privateData_t> m_d;
}; };
/// \brief Platform mutex
class Mutex class Mutex
{ {
public: public:
~Mutex (); ~Mutex ();
Mutex (); Mutex ();
/// \brief Lock mutex
void lock (); void lock ();
/// \brief Unlock mutex
void unlock (); void unlock ();
private: private:
class privateData_t; class privateData_t;
/// \brief pimpl
std::unique_ptr<privateData_t> m_d; std::unique_ptr<privateData_t> m_d;
}; };
} }

View File

@ -25,6 +25,7 @@
#include <cstdint> #include <cstdint>
/// \brief Socket address
class SockAddr class SockAddr
{ {
public: public:
@ -32,39 +33,72 @@ public:
SockAddr (); SockAddr ();
/// \brief Copy constructor
/// \param that_ Object to copy
SockAddr (SockAddr const &that_); SockAddr (SockAddr const &that_);
/// \brief Move constructor
/// \param that_ Object to move from
SockAddr (SockAddr &&that_); SockAddr (SockAddr &&that_);
/// \brief Copy assignment
/// \param that_ Object to copy
SockAddr &operator= (SockAddr const &that_); SockAddr &operator= (SockAddr const &that_);
/// \brief Move assignment
/// \param that_ Object to move from
SockAddr &operator= (SockAddr &&that_); SockAddr &operator= (SockAddr &&that_);
/// \param Parameterized constructor
/// \param addr_ Address
SockAddr (struct sockaddr const &addr_); SockAddr (struct sockaddr const &addr_);
/// \param Parameterized constructor
/// \param addr_ Address
SockAddr (struct sockaddr_in const &addr_); SockAddr (struct sockaddr_in const &addr_);
#ifndef _3DS #ifndef _3DS
/// \param Parameterized constructor
/// \param addr_ Address
SockAddr (struct sockaddr_in6 const &addr_); SockAddr (struct sockaddr_in6 const &addr_);
#endif #endif
/// \param Parameterized constructor
/// \param addr_ Address
SockAddr (struct sockaddr_storage const &addr_); SockAddr (struct sockaddr_storage const &addr_);
/// \param sockaddr_in cast operator
operator struct sockaddr_in const & () const; operator struct sockaddr_in const & () const;
#ifndef _3DS #ifndef _3DS
/// \param sockaddr_in6 cast operator
operator struct sockaddr_in6 const & () const; operator struct sockaddr_in6 const & () const;
#endif #endif
/// \param sockaddr_storage cast operator
operator struct sockaddr_storage const & () const; operator struct sockaddr_storage const & () const;
/// \param sockaddr* cast operator
operator struct sockaddr * (); operator struct sockaddr * ();
/// \param sockaddr const* cast operator
operator struct sockaddr const * () const; operator struct sockaddr const * () const;
/// \brief Address port
std::uint16_t port () const; std::uint16_t port () const;
/// \brief Address name
/// \param buffer_
/// \param size_ Size of buffer_
/// \retval buffer_ success
/// \retval nullptr failure
char const *name (char *buffer_, std::size_t size_) const; char const *name (char *buffer_, std::size_t size_) const;
/// \brief Address name
/// \retval nullptr failure
/// \note This function is not reentrant
char const *name () const; char const *name () const;
private: private:
/// \brief Address storage
struct sockaddr_storage m_addr = {}; struct sockaddr_storage m_addr = {};
}; };

View File

@ -30,53 +30,115 @@ class Socket;
using UniqueSocket = std::unique_ptr<Socket>; using UniqueSocket = std::unique_ptr<Socket>;
using SharedSocket = std::shared_ptr<Socket>; using SharedSocket = std::shared_ptr<Socket>;
/// \brief Socket object
class Socket class Socket
{ {
public: public:
/// \brief Poll info
struct PollInfo struct PollInfo
{ {
/// \brief Socket to poll
std::reference_wrapper<Socket> socket; std::reference_wrapper<Socket> socket;
/// \brief Input events
int events; int events;
/// \brief Output events
int revents; int revents;
}; };
~Socket (); ~Socket ();
/// \brief Accept connection
UniqueSocket accept (); UniqueSocket accept ();
/// \brief Whether socket is at out-of-band mark
int atMark (); int atMark ();
/// \brief Bind socket to address
/// \param addr_ Address to bind
bool bind (SockAddr const &addr_); bool bind (SockAddr const &addr_);
/// \brief Connect to a peer
/// \param addr_ Peer address
bool connect (SockAddr const &addr_); bool connect (SockAddr const &addr_);
/// \brief Listen for connections
/// \param backlog_ Queue size for incoming connections
bool listen (int backlog_); bool listen (int backlog_);
/// \brief Shutdown socket
/// \param how_ Type of shutdown (\sa ::shutdown)
bool shutdown (int how_); bool shutdown (int how_);
/// \brief Set linger option
/// \param enable_ Whether to enable linger
/// \param time_ Linger timeout
bool setLinger (bool enable_, std::chrono::seconds time_); bool setLinger (bool enable_, std::chrono::seconds time_);
/// \brief Set non-blocking
/// \param nonBlocking_ Whether to set non-blocking
bool setNonBlocking (bool nonBlocking_ = true); bool setNonBlocking (bool nonBlocking_ = true);
/// \brief Set reuse address in subsequent bind
/// \param reuse_ Whether to reuse address
bool setReuseAddress (bool reuse_ = true); bool setReuseAddress (bool reuse_ = true);
/// \brief Set recv buffer size
/// \param size_ Buffer size
bool setRecvBufferSize (std::size_t size_); bool setRecvBufferSize (std::size_t size_);
/// \brief Set send buffer size
/// \param size_ Buffer size
bool setSendBufferSize (std::size_t size_); bool setSendBufferSize (std::size_t size_);
/// \brief Read data
/// \param buffer_ Output buffer
/// \param size_ Size to read
/// \param oob_ Whether to read from out-of-band
ssize_t read (void *buffer_, std::size_t size_, bool oob_ = false); ssize_t read (void *buffer_, std::size_t size_, bool oob_ = false);
/// \brief Read data
/// \param buffer_ Output buffer
/// \param size_ Size to read
/// \param oob_ Whether to read from out-of-band
ssize_t read (IOBuffer &buffer_, bool oob_ = false); ssize_t read (IOBuffer &buffer_, bool oob_ = false);
/// \brief Write data
/// \param buffer_ Input buffer
/// \param size_ Size to write
ssize_t write (void const *buffer_, std::size_t size_); ssize_t write (void const *buffer_, std::size_t size_);
/// \brief Write data
/// \param buffer_ Input buffer
/// \param size_ Size to write
ssize_t write (IOBuffer &buffer_); ssize_t write (IOBuffer &buffer_);
/// \brief Local name
SockAddr const &sockName () const; SockAddr const &sockName () const;
/// \brief Peer name
SockAddr const &peerName () const; SockAddr const &peerName () const;
/// \brief Create socket
static UniqueSocket create (); static UniqueSocket create ();
/// \brief Poll sockets
/// \param info_ Poll info
/// \param count_ Number of poll entries
/// \param timeout_ Poll timeout
static int poll (PollInfo *info_, std::size_t count_, std::chrono::milliseconds timeout_); static int poll (PollInfo *info_, std::size_t count_, std::chrono::milliseconds timeout_);
int fd () const
{
return m_fd;
}
private: private:
Socket () = delete; Socket () = delete;
/// \brief Parameterized constructor
/// \param fd_ Socket fd
Socket (int fd_); Socket (int fd_);
/// \brief Parameterized constructor
/// \param fd_ Socket fd
/// \param sockName_ Local name
/// \param peerName_ Peer name
Socket (int fd_, SockAddr const &sockName_, SockAddr const &peerName_); Socket (int fd_, SockAddr const &sockName_, SockAddr const &peerName_);
Socket (Socket const &that_) = delete; Socket (Socket const &that_) = delete;
@ -87,11 +149,17 @@ private:
Socket &operator= (Socket &&that_) = delete; Socket &operator= (Socket &&that_) = delete;
/// \param Local name
SockAddr m_sockName; SockAddr m_sockName;
/// \param Peer name
SockAddr m_peerName; SockAddr m_peerName;
/// \param Socket fd
int const m_fd; int const m_fd;
/// \param Whether listening
bool m_listening : 1; bool m_listening : 1;
/// \param Whether connected
bool m_connected : 1; bool m_connected : 1;
}; };

View File

@ -33,37 +33,59 @@
namespace namespace
{ {
/// \brief 3DS font glyph ranges
std::vector<ImWchar> s_fontRanges; std::vector<ImWchar> s_fontRanges;
/// \brief Clear color
constexpr auto CLEAR_COLOR = 0x204B7AFF; constexpr auto CLEAR_COLOR = 0x204B7AFF;
/// \brief Display transfer flags
constexpr auto DISPLAY_TRANSFER_FLAGS = constexpr auto DISPLAY_TRANSFER_FLAGS =
GX_TRANSFER_FLIP_VERT (0) | GX_TRANSFER_OUT_TILED (0) | GX_TRANSFER_RAW_COPY (0) | GX_TRANSFER_FLIP_VERT (0) | GX_TRANSFER_OUT_TILED (0) | GX_TRANSFER_RAW_COPY (0) |
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 (GX_TRANSFER_SCALE_NO); GX_TRANSFER_SCALING (GX_TRANSFER_SCALE_NO);
C3D_RenderTarget *s_top = nullptr; /// \brief Top screen render target
C3D_RenderTarget *s_top = nullptr;
/// \brief Bottom screen render target
C3D_RenderTarget *s_bottom = nullptr; C3D_RenderTarget *s_bottom = nullptr;
/// \brief Vertex shader
DVLB_s *s_vsh = nullptr; DVLB_s *s_vsh = nullptr;
/// \brief Vertex shader program
shaderProgram_s s_program; shaderProgram_s s_program;
/// \brief Projection matrix uniform location
int s_projLocation; int s_projLocation;
/// \brief Top screen projection matrix
C3D_Mtx s_projTop; C3D_Mtx s_projTop;
/// \brief Bottom screen projection matrix
C3D_Mtx s_projBottom; C3D_Mtx s_projBottom;
/// \brief System font textures
std::vector<C3D_Tex> s_fontTextures; std::vector<C3D_Tex> s_fontTextures;
/// \brief Text scale
float s_textScale; float s_textScale;
/// \brief Scissor test bounds
std::uint32_t s_boundScissor[4]; std::uint32_t s_boundScissor[4];
/// \brief Currently bound vertex data
ImDrawVert *s_boundVtxData; ImDrawVert *s_boundVtxData;
/// \brief Currently bound texture
C3D_Tex *s_boundTexture; C3D_Tex *s_boundTexture;
/// \brief Vertex data buffer
ImDrawVert *s_vtxData = nullptr; ImDrawVert *s_vtxData = nullptr;
/// \brief Size of vertex data buffer
std::size_t s_vtxSize = 0; std::size_t s_vtxSize = 0;
ImDrawIdx *s_idxData = nullptr; /// \brief Index data buffer
ImDrawIdx *s_idxData = nullptr;
/// \brief Size of index data buffer
std::size_t s_idxSize = 0; std::size_t s_idxSize = 0;
/// \brief Get code point from glyph index
/// \param font_ Font to search
/// \param glyphIndex_ Glyph index
std::uint32_t fontCodePointFromGlyphIndex (CFNT_s *const font_, int const glyphIndex_) std::uint32_t fontCodePointFromGlyphIndex (CFNT_s *const font_, int const glyphIndex_)
{ {
for (auto cmap = fontGetInfo (font_)->cmap; cmap; cmap = cmap->next) for (auto cmap = fontGetInfo (font_)->cmap; cmap; cmap = cmap->next)
@ -100,8 +122,11 @@ std::uint32_t fontCodePointFromGlyphIndex (CFNT_s *const font_, int const glyphI
return 0; return 0;
} }
/// \brief Setup render state
/// \param screen_ Whether top or bottom screen
void setupRenderState (gfxScreen_t const screen_) void setupRenderState (gfxScreen_t const screen_)
{ {
// disable face culling
C3D_CullFace (GPU_CULL_NONE); C3D_CullFace (GPU_CULL_NONE);
// configure attributes for user with vertex shader // configure attributes for user with vertex shader
@ -111,14 +136,18 @@ void setupRenderState (gfxScreen_t const screen_)
AttrInfo_AddLoader (attrInfo, 1, GPU_FLOAT, 2); // v1 = inUv AttrInfo_AddLoader (attrInfo, 1, GPU_FLOAT, 2); // v1 = inUv
AttrInfo_AddLoader (attrInfo, 2, GPU_UNSIGNED_BYTE, 4); // v2 = inColor AttrInfo_AddLoader (attrInfo, 2, GPU_UNSIGNED_BYTE, 4); // v2 = inColor
// clear bindings
std::memset (s_boundScissor, 0xFF, sizeof (s_boundScissor)); std::memset (s_boundScissor, 0xFF, sizeof (s_boundScissor));
s_boundVtxData = nullptr; s_boundVtxData = nullptr;
s_boundTexture = nullptr; s_boundTexture = nullptr;
// bind program
C3D_BindProgram (&s_program); C3D_BindProgram (&s_program);
// enable depth test
C3D_DepthTest (true, GPU_GREATER, GPU_WRITE_COLOR); C3D_DepthTest (true, GPU_GREATER, GPU_WRITE_COLOR);
// enable alpha blending
C3D_AlphaBlend (GPU_BLEND_ADD, C3D_AlphaBlend (GPU_BLEND_ADD,
GPU_BLEND_ADD, GPU_BLEND_ADD,
GPU_SRC_ALPHA, GPU_SRC_ALPHA,
@ -126,6 +155,7 @@ void setupRenderState (gfxScreen_t const screen_)
GPU_SRC_ALPHA, GPU_SRC_ALPHA,
GPU_ONE_MINUS_SRC_ALPHA); GPU_ONE_MINUS_SRC_ALPHA);
// apply projection matrix
if (screen_ == GFX_TOP) if (screen_ == GFX_TOP)
C3D_FVUnifMtx4x4 (GPU_VERTEX_SHADER, s_projLocation, &s_projTop); C3D_FVUnifMtx4x4 (GPU_VERTEX_SHADER, s_projLocation, &s_projTop);
else else
@ -135,36 +165,46 @@ void setupRenderState (gfxScreen_t const screen_)
void imgui::citro3d::init () void imgui::citro3d::init ()
{ {
// Setup back-end capabilities flags // setup back-end capabilities flags
ImGuiIO &io = ImGui::GetIO (); ImGuiIO &io = ImGui::GetIO ();
io.BackendRendererName = "citro3d"; io.BackendRendererName = "citro3d";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
// initialize citro3d
C3D_Init (C3D_DEFAULT_CMDBUF_SIZE); C3D_Init (C3D_DEFAULT_CMDBUF_SIZE);
// create top screen render target
s_top = C3D_RenderTargetCreate (240, 400, GPU_RB_RGBA8, GPU_RB_DEPTH24_STENCIL8); s_top = C3D_RenderTargetCreate (240, 400, GPU_RB_RGBA8, GPU_RB_DEPTH24_STENCIL8);
C3D_RenderTargetSetOutput (s_top, GFX_TOP, GFX_LEFT, DISPLAY_TRANSFER_FLAGS); C3D_RenderTargetSetOutput (s_top, GFX_TOP, GFX_LEFT, DISPLAY_TRANSFER_FLAGS);
// create bottom screen render target
s_bottom = C3D_RenderTargetCreate (240, 320, GPU_RB_RGBA8, GPU_RB_DEPTH24_STENCIL8); s_bottom = C3D_RenderTargetCreate (240, 320, GPU_RB_RGBA8, GPU_RB_DEPTH24_STENCIL8);
C3D_RenderTargetSetOutput (s_bottom, GFX_BOTTOM, GFX_LEFT, DISPLAY_TRANSFER_FLAGS); C3D_RenderTargetSetOutput (s_bottom, GFX_BOTTOM, GFX_LEFT, DISPLAY_TRANSFER_FLAGS);
// load vertex shader
s_vsh = DVLB_ParseFile ( s_vsh = DVLB_ParseFile (
const_cast<std::uint32_t *> (reinterpret_cast<std::uint32_t const *> (vshader_shbin)), const_cast<std::uint32_t *> (reinterpret_cast<std::uint32_t const *> (vshader_shbin)),
vshader_shbin_size); vshader_shbin_size);
// initialize vertex shader program
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_projLocation = shaderInstanceGetUniformLocation (s_program.vertexShader, "proj"); s_projLocation = shaderInstanceGetUniformLocation (s_program.vertexShader, "proj");
// initialize projection matrices
Mtx_OrthoTilt (&s_projTop, 0.0f, 800.0f, 480.0f, 0.0f, -1.0f, 1.0f, false); Mtx_OrthoTilt (&s_projTop, 0.0f, 800.0f, 480.0f, 0.0f, -1.0f, 1.0f, false);
Mtx_OrthoTilt (&s_projBottom, 80.0f, 720.0f, 960.0f, 480.0f, -1.0f, 1.0f, false); Mtx_OrthoTilt (&s_projBottom, 80.0f, 720.0f, 960.0f, 480.0f, -1.0f, 1.0f, false);
// allocate vertex data buffer
s_vtxSize = 65536; s_vtxSize = 65536;
s_vtxData = reinterpret_cast<ImDrawVert *> (linearAlloc (sizeof (ImDrawVert) * s_vtxSize)); s_vtxData = reinterpret_cast<ImDrawVert *> (linearAlloc (sizeof (ImDrawVert) * s_vtxSize));
if (!s_vtxData) if (!s_vtxData)
svcBreak (USERBREAK_PANIC); svcBreak (USERBREAK_PANIC);
// allocate index data buffer
s_idxSize = 65536; s_idxSize = 65536;
s_idxData = reinterpret_cast<ImDrawIdx *> (linearAlloc (sizeof (ImDrawIdx) * s_idxSize)); s_idxData = reinterpret_cast<ImDrawIdx *> (linearAlloc (sizeof (ImDrawIdx) * s_idxSize));
if (!s_idxData) if (!s_idxData)
@ -184,6 +224,7 @@ void imgui::citro3d::init ()
s_textScale = 30.0f / glyphInfo->cellHeight; s_textScale = 30.0f / glyphInfo->cellHeight;
// use system font sheets as citro3d textures
for (unsigned i = 0; i < glyphInfo->nSheets; ++i) for (unsigned i = 0; i < glyphInfo->nSheets; ++i)
{ {
auto &tex = s_fontTextures[i]; auto &tex = s_fontTextures[i];
@ -201,9 +242,11 @@ void imgui::citro3d::init ()
} }
{ {
// create texture for ImGui's white pixel
auto &tex = s_fontTextures[glyphInfo->nSheets]; auto &tex = s_fontTextures[glyphInfo->nSheets];
C3D_TexInit (&tex, 8, 8, GPU_A4); C3D_TexInit (&tex, 8, 8, GPU_A4);
// fill texture with full alpha
std::uint32_t size; std::uint32_t size;
auto data = C3D_Tex2DGetImagePtr (&tex, 0, &size); auto data = C3D_Tex2DGetImagePtr (&tex, 0, &size);
if (!data || !size) if (!data || !size)
@ -211,10 +254,12 @@ void imgui::citro3d::init ()
std::memset (data, 0xFF, size); std::memset (data, 0xFF, size);
} }
// get alternate character glyph
ImWchar alterChar = fontCodePointFromGlyphIndex (font, fontInfo->alterCharIndex); ImWchar alterChar = fontCodePointFromGlyphIndex (font, fontInfo->alterCharIndex);
if (!alterChar) if (!alterChar)
alterChar = '?'; alterChar = '?';
// collect character map
std::vector<ImWchar> charSet; std::vector<ImWchar> charSet;
for (auto cmap = fontInfo->cmap; cmap; cmap = cmap->next) for (auto cmap = fontInfo->cmap; cmap; cmap = cmap->next)
{ {
@ -242,9 +287,11 @@ void imgui::citro3d::init ()
if (charSet.empty ()) if (charSet.empty ())
svcBreak (USERBREAK_PANIC); svcBreak (USERBREAK_PANIC);
// deduplicate character map
std::sort (std::begin (charSet), std::end (charSet)); std::sort (std::begin (charSet), std::end (charSet));
charSet.erase (std::unique (std::begin (charSet), std::end (charSet)), std::end (charSet)); charSet.erase (std::unique (std::begin (charSet), std::end (charSet)), std::end (charSet));
// fill in font glyph ranges
auto it = std::begin (charSet); auto it = std::begin (charSet);
ImWchar start = *it++; ImWchar start = *it++;
ImWchar prev = start; ImWchar prev = start;
@ -262,8 +309,11 @@ void imgui::citro3d::init ()
} }
s_fontRanges.emplace_back (start); s_fontRanges.emplace_back (start);
s_fontRanges.emplace_back (prev); s_fontRanges.emplace_back (prev);
// terminate glyph ranges
s_fontRanges.emplace_back (0); s_fontRanges.emplace_back (0);
// initialize font atlas
auto const atlas = ImGui::GetIO ().Fonts; auto const atlas = ImGui::GetIO ().Fonts;
atlas->Clear (); atlas->Clear ();
atlas->TexWidth = glyphInfo->sheetWidth; atlas->TexWidth = glyphInfo->sheetWidth;
@ -272,6 +322,7 @@ void imgui::citro3d::init ()
atlas->TexUvWhitePixel = ImVec2 (0.5f / 8.0f, glyphInfo->nSheets + 0.5f / 8.0f); atlas->TexUvWhitePixel = ImVec2 (0.5f / 8.0f, glyphInfo->nSheets + 0.5f / 8.0f);
atlas->TexPixelsAlpha8 = static_cast<unsigned char *> (IM_ALLOC (1)); // dummy allocation atlas->TexPixelsAlpha8 = static_cast<unsigned char *> (IM_ALLOC (1)); // dummy allocation
// initialize font config
ImFontConfig config; ImFontConfig config;
config.FontData = nullptr; config.FontData = nullptr;
config.FontDataSize = 0; config.FontDataSize = 0;
@ -292,19 +343,20 @@ void imgui::citro3d::init ()
config.EllipsisChar = 0x2026; config.EllipsisChar = 0x2026;
std::memset (config.Name, 0, sizeof (config.Name)); std::memset (config.Name, 0, sizeof (config.Name));
// create font
auto const imFont = IM_NEW (ImFont); auto const imFont = IM_NEW (ImFont);
config.DstFont = imFont; config.DstFont = imFont;
// add config and font to atlas
atlas->ConfigData.push_back (config); atlas->ConfigData.push_back (config);
atlas->Fonts.push_back (imFont); atlas->Fonts.push_back (imFont);
// atlas->CustomRectIds[0] = atlas->AddCustomRectRegular (0x80000000, 108 * 2 + 1, 27);
// atlas->CustomRects[0].X = 0;
// atlas->CustomRects[0].Y = 0;
atlas->SetTexID (s_fontTextures.data ()); atlas->SetTexID (s_fontTextures.data ());
// initialize font metrics
imFont->FallbackAdvanceX = fontInfo->defaultWidth.charWidth; imFont->FallbackAdvanceX = fontInfo->defaultWidth.charWidth;
imFont->FontSize = fontInfo->lineFeed; imFont->FontSize = fontInfo->lineFeed;
// add glyphs to font
fontGlyphPos_s glyphPos; fontGlyphPos_s glyphPos;
for (auto const &code : charSet) for (auto const &code : charSet)
{ {
@ -312,6 +364,7 @@ void imgui::citro3d::init ()
if (glyphIndex < 0) if (glyphIndex < 0)
svcBreak (USERBREAK_PANIC); svcBreak (USERBREAK_PANIC);
// calculate glyph metrics
fontCalcGlyphPos (&glyphPos, fontCalcGlyphPos (&glyphPos,
font, font,
glyphIndex, glyphIndex,
@ -319,8 +372,8 @@ void imgui::citro3d::init ()
1.0f, 1.0f,
1.0f); 1.0f);
// convert to ImGui font metrics
ImFontGlyph glyph; ImFontGlyph glyph;
glyph.Codepoint = code; glyph.Codepoint = code;
glyph.AdvanceX = glyphPos.xAdvance; glyph.AdvanceX = glyphPos.xAdvance;
glyph.X0 = glyphPos.vtxcoord.left; glyph.X0 = glyphPos.vtxcoord.left;
@ -332,41 +385,48 @@ void imgui::citro3d::init ()
glyph.U1 = glyphPos.texcoord.right; glyph.U1 = glyphPos.texcoord.right;
glyph.V1 = glyphPos.sheetIndex + glyphPos.texcoord.bottom; glyph.V1 = glyphPos.sheetIndex + glyphPos.texcoord.bottom;
// add glyph to font
imFont->Glyphs.push_back (glyph); imFont->Glyphs.push_back (glyph);
imFont->MetricsTotalSurface += imFont->MetricsTotalSurface +=
static_cast<int> ((glyph.U1 - glyph.U0) * atlas->TexWidth + 1.99f) * static_cast<int> ((glyph.U1 - glyph.U0) * atlas->TexWidth + 1.99f) *
static_cast<int> ((glyph.V1 - glyph.V0) * atlas->TexHeight + 1.99f); static_cast<int> ((glyph.V1 - glyph.V0) * atlas->TexHeight + 1.99f);
} }
// build lookup table
imFont->BuildLookupTable (); imFont->BuildLookupTable ();
// finalize font
imFont->DisplayOffset.x = 0.0f; imFont->DisplayOffset.x = 0.0f;
imFont->DisplayOffset.y = fontInfo->ascent; imFont->DisplayOffset.y = fontInfo->ascent;
imFont->ContainerAtlas = atlas; imFont->ContainerAtlas = atlas;
imFont->ConfigData = &atlas->ConfigData[0]; imFont->ConfigData = &atlas->ConfigData[0];
imFont->ConfigDataCount = 1; imFont->ConfigDataCount = 1;
imFont->FallbackChar = alterChar; imFont->FallbackChar = alterChar;
imFont->EllipsisChar = config.EllipsisChar; imFont->EllipsisChar = config.EllipsisChar;
imFont->Scale = 1.0f; imFont->Scale = s_textScale;
imFont->Ascent = fontInfo->ascent; imFont->Ascent = fontInfo->ascent;
imFont->Descent = 0.0f; imFont->Descent = 0.0f;
} }
void imgui::citro3d::exit () void imgui::citro3d::exit ()
{ {
// free vertex/index data buffers
linearFree (s_idxData); linearFree (s_idxData);
linearFree (s_vtxData); linearFree (s_vtxData);
// delete ImGui white pixel texture
assert (!s_fontTextures.empty ()); assert (!s_fontTextures.empty ());
C3D_TexDelete (&s_fontTextures.back ()); C3D_TexDelete (&s_fontTextures.back ());
// free shader program
shaderProgramFree (&s_program); shaderProgramFree (&s_program);
DVLB_Free (s_vsh); DVLB_Free (s_vsh);
// free render targets
C3D_RenderTargetDelete (s_bottom); C3D_RenderTargetDelete (s_bottom);
C3D_RenderTargetDelete (s_top); C3D_RenderTargetDelete (s_top);
// deinitialize citro3d
C3D_Fini (); C3D_Fini ();
} }
@ -377,9 +437,12 @@ void imgui::citro3d::newFrame ()
void imgui::citro3d::render () void imgui::citro3d::render ()
{ {
C3D_FrameBegin (C3D_FRAME_SYNCDRAW); C3D_FrameBegin (C3D_FRAME_SYNCDRAW);
// clear frame/depth buffers
C3D_RenderTargetClear (s_top, C3D_CLEAR_ALL, CLEAR_COLOR, 0); C3D_RenderTargetClear (s_top, 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);
// get draw data
auto const drawData = ImGui::GetDrawData (); auto const drawData = ImGui::GetDrawData ();
if (drawData->CmdListsCount <= 0) if (drawData->CmdListsCount <= 0)
{ {
@ -387,6 +450,7 @@ void imgui::citro3d::render ()
return; return;
} }
// get framebuffer dimensions
unsigned width = drawData->DisplaySize.x * drawData->FramebufferScale.x; unsigned width = drawData->DisplaySize.x * drawData->FramebufferScale.x;
unsigned height = drawData->DisplaySize.y * drawData->FramebufferScale.y; unsigned height = drawData->DisplaySize.y * drawData->FramebufferScale.y;
if (width <= 0 || height <= 0) if (width <= 0 || height <= 0)
@ -395,6 +459,7 @@ void imgui::citro3d::render ()
return; return;
} }
// check if we need to grow vertex data buffer
if (s_vtxSize < static_cast<std::size_t> (drawData->TotalVtxCount)) if (s_vtxSize < static_cast<std::size_t> (drawData->TotalVtxCount))
{ {
linearFree (s_vtxData); linearFree (s_vtxData);
@ -406,6 +471,7 @@ void imgui::citro3d::render ()
svcBreak (USERBREAK_PANIC); svcBreak (USERBREAK_PANIC);
} }
// check if we need to grow index data buffer
if (s_idxSize < static_cast<std::size_t> (drawData->TotalIdxCount)) if (s_idxSize < static_cast<std::size_t> (drawData->TotalIdxCount))
{ {
// add 10% to avoid growing many frames in a row // add 10% to avoid growing many frames in a row
@ -415,7 +481,7 @@ void imgui::citro3d::render ()
svcBreak (USERBREAK_PANIC); svcBreak (USERBREAK_PANIC);
} }
// Will project scissor/clipping rectangles into framebuffer space // will project scissor/clipping rectangles into framebuffer space
// (0,0) unless using multi-viewports // (0,0) unless using multi-viewports
auto const clipOff = drawData->DisplayPos; auto const clipOff = drawData->DisplayPos;
// (1,1) unless using retina display which are often (2,2) // (1,1) unless using retina display which are often (2,2)
@ -427,11 +493,14 @@ void imgui::citro3d::render ()
for (int i = 0; i < drawData->CmdListsCount; ++i) for (int i = 0; i < drawData->CmdListsCount; ++i)
{ {
auto const &cmdList = *drawData->CmdLists[i]; auto const &cmdList = *drawData->CmdLists[i];
// double check that we don't overrun vertex/index data buffers
if (s_vtxSize - offsetVtx < static_cast<std::size_t> (cmdList.VtxBuffer.Size)) if (s_vtxSize - offsetVtx < static_cast<std::size_t> (cmdList.VtxBuffer.Size))
svcBreak (USERBREAK_PANIC); svcBreak (USERBREAK_PANIC);
if (s_idxSize - offsetIdx < static_cast<std::size_t> (cmdList.IdxBuffer.Size)) if (s_idxSize - offsetIdx < static_cast<std::size_t> (cmdList.IdxBuffer.Size))
svcBreak (USERBREAK_PANIC); svcBreak (USERBREAK_PANIC);
// copy vertex/index data into buffers
std::memcpy (&s_vtxData[offsetVtx], std::memcpy (&s_vtxData[offsetVtx],
cmdList.VtxBuffer.Data, cmdList.VtxBuffer.Data,
sizeof (ImDrawVert) * cmdList.VtxBuffer.Size); sizeof (ImDrawVert) * cmdList.VtxBuffer.Size);
@ -455,7 +524,7 @@ void imgui::citro3d::render ()
offsetVtx = 0; offsetVtx = 0;
offsetIdx = 0; offsetIdx = 0;
// Render command lists // render command lists
for (int i = 0; i < drawData->CmdListsCount; ++i) for (int i = 0; i < drawData->CmdListsCount; ++i)
{ {
auto const &cmdList = *drawData->CmdLists[i]; auto const &cmdList = *drawData->CmdLists[i];
@ -463,7 +532,7 @@ void imgui::citro3d::render ()
{ {
if (cmd.UserCallback) if (cmd.UserCallback)
{ {
// User callback, registered via ImDrawList::AddCallback() // user callback, registered via ImDrawList::AddCallback()
// (ImDrawCallback_ResetRenderState is a special callback value used by the user // (ImDrawCallback_ResetRenderState is a special callback value used by the user
// to request the renderer to reset render state.) // to request the renderer to reset render state.)
if (cmd.UserCallback == ImDrawCallback_ResetRenderState) if (cmd.UserCallback == ImDrawCallback_ResetRenderState)
@ -473,7 +542,7 @@ void imgui::citro3d::render ()
} }
else else
{ {
// Project scissor/clipping rectangles into framebuffer space // project scissor/clipping rectangles into framebuffer space
ImVec4 clip; ImVec4 clip;
clip.x = (cmd.ClipRect.x - clipOff.x) * clipScale.x; clip.x = (cmd.ClipRect.x - clipOff.x) * clipScale.x;
clip.y = (cmd.ClipRect.y - clipOff.y) * clipScale.y; clip.y = (cmd.ClipRect.y - clipOff.y) * clipScale.y;
@ -486,6 +555,10 @@ void imgui::citro3d::render ()
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)
clip.z = width;
if (clip.w > height)
clip.z = height;
if (screen == GFX_TOP) if (screen == GFX_TOP)
{ {
@ -493,12 +566,22 @@ void imgui::citro3d::render ()
if (clip.y > 240.0f) if (clip.y > 240.0f)
continue; continue;
// convert from framebuffer space to screen space (3DS screen rotation)
auto const x1 = std::clamp<unsigned> (240.0f - clip.w, 0, 240); auto const x1 = std::clamp<unsigned> (240.0f - clip.w, 0, 240);
auto const y1 = std::clamp<unsigned> (400.0f - clip.z, 0, 400); auto const y1 = std::clamp<unsigned> (400.0f - clip.z, 0, 400);
auto const x2 = std::clamp<unsigned> (240.0f - clip.y, 0, 240); auto const x2 = std::clamp<unsigned> (240.0f - clip.y, 0, 240);
auto const y2 = std::clamp<unsigned> (400.0f - clip.x, 0, 400); auto const y2 = std::clamp<unsigned> (400.0f - clip.x, 0, 400);
C3D_SetScissor (GPU_SCISSOR_NORMAL, x1, y1, x2, y2); // 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);
}
} }
else else
{ {
@ -514,11 +597,14 @@ void imgui::citro3d::render ()
if (clip.x > 360.0f) if (clip.x > 360.0f)
continue; continue;
// convert from framebuffer space to screen space
// (3DS screen rotation + bottom screen offset)
auto const x1 = std::clamp<unsigned> (480.0f - clip.w, 0, 240); auto const x1 = std::clamp<unsigned> (480.0f - clip.w, 0, 240);
auto const y1 = std::clamp<unsigned> (360.0f - clip.z, 0, 320); auto const y1 = std::clamp<unsigned> (360.0f - clip.z, 0, 320);
auto const x2 = std::clamp<unsigned> (480.0f - clip.y, 0, 240); auto const x2 = std::clamp<unsigned> (480.0f - clip.y, 0, 240);
auto const y2 = std::clamp<unsigned> (360.0f - clip.x, 0, 320); auto const y2 = std::clamp<unsigned> (360.0f - clip.x, 0, 320);
// check if scissor needs to be updated
if (s_boundScissor[0] != x1 || s_boundScissor[1] != y1 || if (s_boundScissor[0] != x1 || s_boundScissor[1] != y1 ||
s_boundScissor[2] != x2 || s_boundScissor[3] != y2) s_boundScissor[2] != x2 || s_boundScissor[3] != y2)
{ {
@ -530,36 +616,41 @@ void imgui::citro3d::render ()
} }
} }
// check if we need to update vertex data binding
auto const vtxData = &s_vtxData[cmd.VtxOffset + offsetVtx]; auto const vtxData = &s_vtxData[cmd.VtxOffset + offsetVtx];
if (vtxData != s_boundVtxData) if (vtxData != s_boundVtxData)
{ {
s_boundVtxData = &s_vtxData[cmd.VtxOffset + offsetVtx]; s_boundVtxData = vtxData;
auto const bufInfo = C3D_GetBufInfo (); auto const bufInfo = C3D_GetBufInfo ();
BufInfo_Init (bufInfo); BufInfo_Init (bufInfo);
BufInfo_Add (bufInfo, s_boundVtxData, sizeof (ImDrawVert), 3, 0x210); BufInfo_Add (bufInfo, vtxData, sizeof (ImDrawVert), 3, 0x210);
} }
// check if we need to update texture binding
auto tex = static_cast<C3D_Tex *> (cmd.TextureId); auto tex = static_cast<C3D_Tex *> (cmd.TextureId);
if (tex == s_fontTextures.data ()) if (tex == s_fontTextures.data ())
{ {
assert (cmd.ElemCount % 3 == 0); assert (cmd.ElemCount % 3 == 0);
// TODO get by idx not consecutive vtx // get sheet number from uv coords
auto const getSheet = [] (auto const vtx_, auto const idx_) { auto const getSheet = [] (auto const vtx_, auto const idx_) {
unsigned const sheet = std::min ( unsigned const sheet = std::min (
{vtx_[idx_[0]].uv.y, vtx_[idx_[1]].uv.y, vtx_[idx_[2]].uv.y}); {vtx_[idx_[0]].uv.y, vtx_[idx_[1]].uv.y, vtx_[idx_[2]].uv.y});
// assert that these three vertices use the same sheet
for (unsigned i = 0; i < 3; ++i) for (unsigned i = 0; i < 3; ++i)
assert (vtx_[idx_[i]].uv.y - sheet <= 1.0f); assert (vtx_[idx_[i]].uv.y - sheet <= 1.0f);
return sheet; return sheet;
}; };
// initialize texture binding
unsigned boundSheet = getSheet (&s_vtxData[cmd.VtxOffset + offsetVtx], unsigned boundSheet = getSheet (&s_vtxData[cmd.VtxOffset + offsetVtx],
&s_idxData[cmd.IdxOffset + offsetIdx]); &s_idxData[cmd.IdxOffset + offsetIdx]);
C3D_TexBind (0, &s_fontTextures[boundSheet]);
unsigned offset = 0; unsigned offset = 0;
C3D_TexBind (0, &s_fontTextures[boundSheet]); // update texture environment for non-image drawing
auto const env = C3D_GetTexEnv (0); auto const env = C3D_GetTexEnv (0);
C3D_TexEnvInit (env); C3D_TexEnvInit (env);
C3D_TexEnvSrc ( C3D_TexEnvSrc (
@ -569,23 +660,30 @@ void imgui::citro3d::render ()
env, C3D_Alpha, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); env, C3D_Alpha, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR);
C3D_TexEnvFunc (env, C3D_Alpha, GPU_MODULATE); C3D_TexEnvFunc (env, C3D_Alpha, GPU_MODULATE);
// process one triangle at a time
for (unsigned i = 3; i < cmd.ElemCount; i += 3) for (unsigned i = 3; i < cmd.ElemCount; i += 3)
{ {
// get sheet for this triangle
unsigned const sheet = getSheet (&s_vtxData[cmd.VtxOffset + offsetVtx], unsigned const sheet = getSheet (&s_vtxData[cmd.VtxOffset + offsetVtx],
&s_idxData[cmd.IdxOffset + offsetIdx + i]); &s_idxData[cmd.IdxOffset + offsetIdx + i]);
// check if we're changing textures
if (boundSheet != sheet) if (boundSheet != sheet)
{ {
// draw everything up until now
C3D_DrawElements (GPU_TRIANGLES, C3D_DrawElements (GPU_TRIANGLES,
i - offset, i - offset,
C3D_UNSIGNED_SHORT, C3D_UNSIGNED_SHORT,
&s_idxData[cmd.IdxOffset + offsetIdx + offset]); &s_idxData[cmd.IdxOffset + offsetIdx + offset]);
// bind texture for next draw call
boundSheet = sheet; boundSheet = sheet;
offset = i; offset = i;
C3D_TexBind (0, &s_fontTextures[boundSheet]); C3D_TexBind (0, &s_fontTextures[boundSheet]);
} }
} }
// draw the final set of triangles
assert ((cmd.ElemCount - offset) % 3 == 0); assert ((cmd.ElemCount - offset) % 3 == 0);
C3D_DrawElements (GPU_TRIANGLES, C3D_DrawElements (GPU_TRIANGLES,
cmd.ElemCount - offset, cmd.ElemCount - offset,
@ -594,9 +692,13 @@ void imgui::citro3d::render ()
} }
else else
{ {
// drawing an image; check if we need to change texture binding
if (tex != s_boundTexture) if (tex != s_boundTexture)
{ {
// bind new texture
C3D_TexBind (0, tex); C3D_TexBind (0, tex);
// update texture environment for drawing images
auto const env = C3D_GetTexEnv (0); auto const env = C3D_GetTexEnv (0);
C3D_TexEnvInit (env); C3D_TexEnvInit (env);
C3D_TexEnvSrc ( C3D_TexEnvSrc (
@ -604,6 +706,7 @@ void imgui::citro3d::render ()
C3D_TexEnvFunc (env, C3D_Both, GPU_MODULATE); C3D_TexEnvFunc (env, C3D_Both, GPU_MODULATE);
} }
// draw triangles
C3D_DrawElements (GPU_TRIANGLES, C3D_DrawElements (GPU_TRIANGLES,
cmd.ElemCount, cmd.ElemCount,
C3D_UNSIGNED_SHORT, C3D_UNSIGNED_SHORT,

View File

@ -24,10 +24,14 @@ namespace imgui
{ {
namespace citro3d namespace citro3d
{ {
/// \brief Initialize citro3d
void init (); void init ();
/// \brief Deinitialize citro3d
void exit (); void exit ();
/// \brief Prepare citro3d for a new frame
void newFrame (); void newFrame ();
/// \brief Render ImGui draw list
void render (); void render ();
} }
} }

View File

@ -23,6 +23,7 @@
#include "imgui.h" #include "imgui.h"
#include "fs.h" #include "fs.h"
#include "platform.h"
#include <3ds.h> #include <3ds.h>
@ -35,32 +36,45 @@ using namespace std::chrono_literals;
namespace namespace
{ {
constexpr auto SCREEN_WIDTH = 400.0f; /// \brief Screen width
constexpr auto SCREEN_WIDTH = 400.0f;
/// \brief Screen height
constexpr auto SCREEN_HEIGHT = 480.0f; constexpr auto SCREEN_HEIGHT = 480.0f;
/// \brief Clipboard
std::string s_clipboard; std::string s_clipboard;
/// \brief Get clipboard text callback
/// \param userData_ User data
char const *getClipboardText (void *const userData_) char const *getClipboardText (void *const userData_)
{ {
(void)userData_; (void)userData_;
return s_clipboard.c_str (); return s_clipboard.c_str ();
} }
/// \brief Set clipboard text callback
/// \param userData_ User data
/// \param text_ Clipboard text
void setClipboardText (void *const userData_, char const *const text_) void setClipboardText (void *const userData_, char const *const text_)
{ {
(void)userData_; (void)userData_;
s_clipboard = text_; s_clipboard = text_;
} }
/// \brief Update touch position
/// \param io_ ImGui IO
void updateTouch (ImGuiIO &io_) void updateTouch (ImGuiIO &io_)
{ {
if (!((hidKeysDown () | hidKeysHeld ()) & KEY_TOUCH)) // check if touchpad was touched
if (!(hidKeysHeld () & KEY_TOUCH))
{ {
// set mouse cursor off-screen
io_.MousePos = ImVec2 (-10.0f, -10.0f); io_.MousePos = ImVec2 (-10.0f, -10.0f);
io_.MouseDown[0] = false; io_.MouseDown[0] = false;
return; return;
} }
// read touch position
touchPosition pos; touchPosition pos;
hidTouchRead (&pos); hidTouchRead (&pos);
@ -69,8 +83,11 @@ void updateTouch (ImGuiIO &io_)
io_.MouseDown[0] = true; io_.MouseDown[0] = true;
} }
/// \brief Update gamepad inputs
/// \param io_ ImGui IO
void updateGamepads (ImGuiIO &io_) void updateGamepads (ImGuiIO &io_)
{ {
// clear navigation inputs
std::memset (io_.NavInputs, 0, sizeof (io_.NavInputs)); std::memset (io_.NavInputs, 0, sizeof (io_.NavInputs));
auto const buttonMapping = { auto const buttonMapping = {
@ -88,6 +105,7 @@ void updateGamepads (ImGuiIO &io_)
std::make_pair (KEY_DLEFT, ImGuiNavInput_DpadLeft), std::make_pair (KEY_DLEFT, ImGuiNavInput_DpadLeft),
}; };
// read buttons from 3DS
auto const keys = hidKeysHeld (); auto const keys = hidKeysHeld ();
for (auto const &[in, out] : buttonMapping) for (auto const &[in, out] : buttonMapping)
{ {
@ -95,6 +113,7 @@ void updateGamepads (ImGuiIO &io_)
io_.NavInputs[out] = 1.0f; io_.NavInputs[out] = 1.0f;
} }
// update joystick
circlePosition cpad; circlePosition cpad;
auto const analogMapping = { auto const analogMapping = {
std::make_tuple (std::ref (cpad.dx), ImGuiNavInput_LStickLeft, -0.3f, -0.9f), std::make_tuple (std::ref (cpad.dx), ImGuiNavInput_LStickLeft, -0.3f, -0.9f),
@ -103,12 +122,12 @@ void updateGamepads (ImGuiIO &io_)
std::make_tuple (std::ref (cpad.dy), ImGuiNavInput_LStickDown, -0.3f, -0.9f), std::make_tuple (std::ref (cpad.dy), ImGuiNavInput_LStickDown, -0.3f, -0.9f),
}; };
// read left joystick from circle pad
hidCircleRead (&cpad); hidCircleRead (&cpad);
for (auto const &[in, out, min, max] : analogMapping) for (auto const &[in, out, min, max] : analogMapping)
{ {
auto const value = in / static_cast<float> (0x9C); auto const value = in / static_cast<float> (0x9C);
auto const v = std::min (1.0f, (value - min) / (max - min)); io_.NavInputs[out] = std::clamp ((value - min) / (max - min), 0.0f, 1.0f);
io_.NavInputs[out] = std::max (io_.NavInputs[out], v);
} }
} }
} }
@ -117,17 +136,21 @@ bool imgui::ctru::init ()
{ {
ImGuiIO &io = ImGui::GetIO (); ImGuiIO &io = ImGui::GetIO ();
// disable imgui.ini file
io.IniFilename = nullptr; io.IniFilename = nullptr;
// setup config flags
io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen; io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
// setup platform backend
io.BackendFlags |= ImGuiBackendFlags_HasGamepad; io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
io.BackendPlatformName = "3DS";
io.BackendPlatformName = "3ds"; // disable mouse cursor
io.MouseDrawCursor = false; io.MouseDrawCursor = false;
// clipboard callbacks
io.SetClipboardTextFn = setClipboardText; io.SetClipboardTextFn = setClipboardText;
io.GetClipboardTextFn = getClipboardText; io.GetClipboardTextFn = getClipboardText;
io.ClipboardUserData = nullptr; io.ClipboardUserData = nullptr;
@ -139,19 +162,21 @@ void imgui::ctru::newFrame ()
{ {
ImGuiIO &io = ImGui::GetIO (); ImGuiIO &io = ImGui::GetIO ();
// check that font was built
IM_ASSERT (io.Fonts->IsBuilt () && IM_ASSERT (io.Fonts->IsBuilt () &&
"Font atlas not built! It is generally built by the renderer back-end. Missing call " "Font atlas not built! It is generally built by the renderer back-end. Missing call "
"to renderer _NewFrame() function?"); "to renderer _NewFrame() function?");
// setup display metrics
io.DisplaySize = ImVec2 (SCREEN_WIDTH * 2.0f, SCREEN_HEIGHT * 2.0f); io.DisplaySize = ImVec2 (SCREEN_WIDTH * 2.0f, SCREEN_HEIGHT * 2.0f);
io.DisplayFramebufferScale = ImVec2 (0.5f, 0.5f); io.DisplayFramebufferScale = ImVec2 (0.5f, 0.5f);
// Setup time step // time step
static auto const start = svcGetSystemTick (); static auto const start = platform::steady_clock::now ();
static auto prev = start; static auto prev = start;
auto const now = svcGetSystemTick (); auto const now = platform::steady_clock::now ();
io.DeltaTime = (now - prev) / static_cast<float> (SYSCLOCK_ARM11); io.DeltaTime = std::chrono::duration<float> (now - prev).count ();
prev = now; prev = now;
updateTouch (io); updateTouch (io);

View File

@ -24,9 +24,12 @@ namespace imgui
{ {
namespace ctru namespace ctru
{ {
/// \brief Initialize 3ds platform
bool init (); bool init ();
/// \brief Deinitialize 3ds platform
void exit (); void exit ();
/// \brief Prepare 3ds for a new frame
void newFrame (); void newFrame ();
} }
} }

View File

@ -41,33 +41,45 @@
namespace namespace
{ {
constexpr auto STACK_SIZE = 32768; /// \brief Thread stack size
constexpr auto SOCU_ALIGN = 0x1000; constexpr auto STACK_SIZE = 0x8000;
/// \brief soc:u buffer alignment
constexpr auto SOCU_ALIGN = 0x1000;
/// \brief soc:u buffer size
constexpr auto SOCU_BUFFERSIZE = 0x100000; constexpr auto SOCU_BUFFERSIZE = 0x100000;
static_assert (SOCU_BUFFERSIZE % SOCU_ALIGN == 0); static_assert (SOCU_BUFFERSIZE % SOCU_ALIGN == 0);
/// \brief Whether soc:u is active
bool s_socuActive = false; bool s_socuActive = false;
/// \brief soc:u buffer
u32 *s_socuBuffer = nullptr; u32 *s_socuBuffer = nullptr;
/// \brief Texture atlas
C3D_Tex s_gfxTexture; C3D_Tex s_gfxTexture;
/// \brief Texture atlas metadata
Tex3DS_Texture s_gfxT3x; Tex3DS_Texture s_gfxT3x;
/// \brief Start network
void startNetwork () void startNetwork ()
{ {
// check if already active
if (s_socuActive) if (s_socuActive)
return; return;
// get wifi status
std::uint32_t wifi = 0; std::uint32_t wifi = 0;
if (R_FAILED (ACU_GetWifiStatus (&wifi)) || !wifi) if (R_FAILED (ACU_GetWifiStatus (&wifi)) || !wifi)
return; return;
// allocate soc:u buffer
if (!s_socuBuffer) if (!s_socuBuffer)
s_socuBuffer = static_cast<u32 *> (::memalign (SOCU_ALIGN, SOCU_BUFFERSIZE)); s_socuBuffer = static_cast<u32 *> (::memalign (SOCU_ALIGN, SOCU_BUFFERSIZE));
if (!s_socuBuffer) if (!s_socuBuffer)
return; return;
// initialize soc:u service
if (R_FAILED (socInit (s_socuBuffer, SOCU_BUFFERSIZE))) if (R_FAILED (socInit (s_socuBuffer, SOCU_BUFFERSIZE)))
return; return;
@ -75,27 +87,34 @@ void startNetwork ()
Log::info ("Wifi connected\n"); Log::info ("Wifi connected\n");
} }
/// \brief Draw citro3d logo
void drawLogo () void drawLogo ()
{ {
// get citro3d logo subtexture
auto subTex = Tex3DS_GetSubTexture (s_gfxT3x, gfx_c3dlogo_idx); auto subTex = Tex3DS_GetSubTexture (s_gfxT3x, gfx_c3dlogo_idx);
// get framebuffer metrics
ImGuiIO &io = ImGui::GetIO (); ImGuiIO &io = ImGui::GetIO ();
auto const screenWidth = io.DisplaySize.x; auto const screenWidth = io.DisplaySize.x;
auto const screenHeight = io.DisplaySize.y; auto const screenHeight = io.DisplaySize.y;
auto const logoWidth = subTex->width / io.DisplayFramebufferScale.x; auto const logoWidth = subTex->width / io.DisplayFramebufferScale.x;
auto const logoHeight = subTex->height / io.DisplayFramebufferScale.y; auto const logoHeight = subTex->height / io.DisplayFramebufferScale.y;
// calculate top screen coords
auto const x1 = (screenWidth - logoWidth) / 2.0f; auto const x1 = (screenWidth - logoWidth) / 2.0f;
auto const x2 = x1 + logoWidth; auto const x2 = x1 + logoWidth;
auto const y1 = (screenHeight / 2.0f - logoHeight) / 2.0f; auto const y1 = (screenHeight / 2.0f - logoHeight) / 2.0f;
auto const y2 = y1 + logoHeight; auto const y2 = y1 + logoHeight;
// calculate uv coords
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);
// draw to top screen
ImGui::GetBackgroundDrawList ()->AddImage ( ImGui::GetBackgroundDrawList ()->AddImage (
&s_gfxTexture, ImVec2 (x1, y1), ImVec2 (x2, y2), uv1, uv2); &s_gfxTexture, ImVec2 (x1, y1), ImVec2 (x2, y2), uv1, uv2);
// draw to bottom screen
ImGui::GetBackgroundDrawList ()->AddImage (&s_gfxTexture, ImGui::GetBackgroundDrawList ()->AddImage (&s_gfxTexture,
ImVec2 (x1, y1 + screenHeight / 2.0f), ImVec2 (x1, y1 + screenHeight / 2.0f),
ImVec2 (x2, y2 + screenHeight / 2.0f), ImVec2 (x2, y2 + screenHeight / 2.0f),
@ -103,6 +122,7 @@ void drawLogo ()
uv2); uv2);
} }
/// \brief Draw status
void drawStatus () void drawStatus ()
{ {
constexpr unsigned batteryLevels[] = { constexpr unsigned batteryLevels[] = {
@ -121,6 +141,7 @@ void drawStatus ()
gfx_wifi3_idx, gfx_wifi3_idx,
}; };
// get battery charging state or level
static u8 charging = 0; static u8 charging = 0;
static u8 level = 5; static u8 level = 5;
PTMU_GetBatteryChargeState (&charging); PTMU_GetBatteryChargeState (&charging);
@ -136,33 +157,43 @@ void drawStatus ()
auto const screenWidth = io.DisplaySize.x; auto const screenWidth = io.DisplaySize.x;
// calculate battery icon metrics
auto const battery = auto const battery =
Tex3DS_GetSubTexture (s_gfxT3x, charging ? gfx_batteryCharge_idx : batteryLevels[level]); Tex3DS_GetSubTexture (s_gfxT3x, charging ? gfx_batteryCharge_idx : batteryLevels[level]);
auto const batteryWidth = battery->width / io.DisplayFramebufferScale.x; auto const batteryWidth = battery->width / io.DisplayFramebufferScale.x;
auto const batteryHeight = battery->height / io.DisplayFramebufferScale.y; auto const batteryHeight = battery->height / io.DisplayFramebufferScale.y;
// calculate battery icon position
auto const p1 = ImVec2 (screenWidth - batteryWidth, 0.0f); auto const p1 = ImVec2 (screenWidth - batteryWidth, 0.0f);
auto const p2 = ImVec2 (screenWidth, batteryHeight); auto const p2 = ImVec2 (screenWidth, batteryHeight);
// calculate battery icon uv coords
auto const uv1 = ImVec2 (battery->left, battery->top); auto const uv1 = ImVec2 (battery->left, battery->top);
auto const uv2 = ImVec2 (battery->right, battery->bottom); auto const uv2 = ImVec2 (battery->right, battery->bottom);
// draw battery icon
ImGui::GetForegroundDrawList ()->AddImage (&s_gfxTexture, p1, p2, uv1, uv2); ImGui::GetForegroundDrawList ()->AddImage (&s_gfxTexture, p1, p2, uv1, uv2);
// get wifi strength
auto const wifiStrength = osGetWifiStrength (); auto const wifiStrength = osGetWifiStrength ();
// calculate wifi icon metrics
auto const wifi = Tex3DS_GetSubTexture (s_gfxT3x, wifiLevels[wifiStrength]); auto const wifi = Tex3DS_GetSubTexture (s_gfxT3x, wifiLevels[wifiStrength]);
auto const wifiWidth = wifi->width / io.DisplayFramebufferScale.x; auto const wifiWidth = wifi->width / io.DisplayFramebufferScale.x;
auto const wifiHeight = wifi->height / io.DisplayFramebufferScale.y; auto const wifiHeight = wifi->height / io.DisplayFramebufferScale.y;
// calculate wifi icon position
auto const p3 = ImVec2 (p1.x - wifiWidth - 4.0f, 0.0f); auto const p3 = ImVec2 (p1.x - wifiWidth - 4.0f, 0.0f);
auto const p4 = ImVec2 (p1.x - 4.0f, wifiHeight); auto const p4 = ImVec2 (p1.x - 4.0f, wifiHeight);
// calculate wifi icon uv coords
auto const uv3 = ImVec2 (wifi->left, wifi->top); auto const uv3 = ImVec2 (wifi->left, wifi->top);
auto const uv4 = ImVec2 (wifi->right, wifi->bottom); auto const uv4 = ImVec2 (wifi->right, wifi->bottom);
// draw wifi icon
ImGui::GetForegroundDrawList ()->AddImage (&s_gfxTexture, p3, p4, uv3, uv4); ImGui::GetForegroundDrawList ()->AddImage (&s_gfxTexture, p3, p4, uv3, uv4);
// draw current timestamp
char buffer[64]; char buffer[64];
auto const now = std::time (nullptr); auto const now = std::time (nullptr);
std::strftime (buffer, sizeof (buffer), "%H:%M:%S", std::localtime (&now)); std::strftime (buffer, sizeof (buffer), "%H:%M:%S", std::localtime (&now));
@ -173,6 +204,7 @@ void drawStatus ()
bool platform::init () bool platform::init ()
{ {
// enable New 3DS speedup
osSetSpeedupEnable (true); osSetSpeedupEnable (true);
acInit (); acInit ();
@ -187,18 +219,13 @@ bool platform::init ()
std::setvbuf (stderr, nullptr, _IOLBF, 0); std::setvbuf (stderr, nullptr, _IOLBF, 0);
#endif #endif
IMGUI_CHECKVERSION ();
ImGui::CreateContext ();
if (!imgui::ctru::init ()) if (!imgui::ctru::init ())
{
ImGui::DestroyContext ();
return false; return false;
}
imgui::citro3d::init (); imgui::citro3d::init ();
{ {
// load texture atlas
fs::File file; fs::File file;
if (!file.open ("romfs:/gfx.t3x")) if (!file.open ("romfs:/gfx.t3x"))
svcBreak (USERBREAK_PANIC); svcBreak (USERBREAK_PANIC);
@ -206,9 +233,9 @@ bool platform::init ()
s_gfxT3x = Tex3DS_TextureImportStdio (file, &s_gfxTexture, nullptr, true); s_gfxT3x = Tex3DS_TextureImportStdio (file, &s_gfxTexture, nullptr, true);
if (!s_gfxT3x) if (!s_gfxT3x)
svcBreak (USERBREAK_PANIC); svcBreak (USERBREAK_PANIC);
}
C3D_TexSetFilter (&s_gfxTexture, GPU_LINEAR, GPU_LINEAR); C3D_TexSetFilter (&s_gfxTexture, GPU_LINEAR, GPU_LINEAR);
}
return true; return true;
} }
@ -247,8 +274,6 @@ void platform::exit ()
Tex3DS_TextureFree (s_gfxT3x); Tex3DS_TextureFree (s_gfxT3x);
C3D_TexDelete (&s_gfxTexture); C3D_TexDelete (&s_gfxTexture);
ImGui::DestroyContext ();
imgui::citro3d::exit (); imgui::citro3d::exit ();
imgui::ctru::exit (); imgui::ctru::exit ();
@ -262,6 +287,7 @@ void platform::exit ()
} }
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
/// \brief Platform thread pimpl
class platform::Thread::privateData_t class platform::Thread::privateData_t
{ {
public: public:
@ -271,6 +297,8 @@ public:
threadFree (thread); threadFree (thread);
} }
/// \brief Parameterized constructor
/// \param func_ Thread entry point
privateData_t (std::function<void ()> func_) : thread (nullptr) privateData_t (std::function<void ()> func_) : thread (nullptr)
{ {
s32 priority = 0x30; s32 priority = 0x30;
@ -280,13 +308,18 @@ public:
assert (thread); assert (thread);
} }
/// \brief Underlying thread entry point
/// \param arg_ Thread pimpl object
static void threadFunc (void *const arg_) static void threadFunc (void *const arg_)
{ {
// call passed-in entry point
auto const t = static_cast<privateData_t *> (arg_); auto const t = static_cast<privateData_t *> (arg_);
t->func (); t->func ();
} }
/// \brief Underlying thread
::Thread thread = nullptr; ::Thread thread = nullptr;
/// \brief Thread entry point
std::function<void ()> func; std::function<void ()> func;
}; };
@ -323,9 +356,11 @@ void platform::Thread::sleep (std::chrono::milliseconds const timeout_)
} }
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
/// \brief Platform mutex pimpl
class platform::Mutex::privateData_t class platform::Mutex::privateData_t
{ {
public: public:
/// \brief Underlying mutex
LightLock mutex; LightLock mutex;
}; };

View File

@ -18,26 +18,28 @@
; You should have received a copy of the GNU General Public License ; You should have received a copy of the GNU General Public License
; along with this program. If not, see <https://www.gnu.org/licenses/>. ; along with this program. If not, see <https://www.gnu.org/licenses/>.
; Example PICA200 vertex shader ; ImGui PICA200 vertex shader
; Uniforms ; uniforms
; Projection matrix
.fvec proj[4] .fvec proj[4]
; Constants ; constants
; [1.0, 0.0, 1.0/255.0, 0.0]
.constf constants(1.0, 0.0, 0.00392156862745, 0.0) .constf constants(1.0, 0.0, 0.00392156862745, 0.0)
; Outputs ; outputs
.out outPos position .out outPos position
.out outUv texcoord0 .out outUv texcoord0
.out outColor color .out outColor color
; Inputs (defined as aliases for convenience) ; inputs (defined as aliases for convenience)
.alias inPos v0 .alias inPos v0
.alias inUv v1 .alias inUv v1
.alias inColor v2 .alias inColor v2
.proc main .proc main
; Force inPos.z = 0.0, inPos.w = 1.0 ; force inPos.z = 0.0, inPos.w = 1.0
mov r0.xy, inPos.xy mov r0.xy, inPos.xy
mov r0.zw, constants.yx mov r0.zw, constants.yx
@ -56,6 +58,6 @@
; outColor = inColor ; outColor = inColor
mov outColor, r1 mov outColor, r1
; We're finished ; we're finished
end end
.end .end

View File

@ -45,28 +45,34 @@ std::string fs::printSize (std::uint64_t const size_)
// clang-format on // clang-format on
}) })
{ {
// get the integral portion of the number
auto const whole = size_ / bin; auto const whole = size_ / bin;
if (size_ >= 100 * bin) if (size_ >= 100 * bin)
{ {
// >= 100, print xxxXiB
std::sprintf (buffer, "%" PRIu64 "%s", whole, name); std::sprintf (buffer, "%" PRIu64 "%s", whole, name);
return buffer; return buffer;
} }
// get the fractional portion of the number
auto const frac = size_ - whole * bin; auto const frac = size_ - whole * bin;
if (size_ >= 10 * bin) if (size_ >= 10 * bin)
{ {
// >= 10, print xx.xXiB
std::sprintf (buffer, "%" PRIu64 ".%" PRIu64 "%s", whole, frac * 10 / bin, name); std::sprintf (buffer, "%" PRIu64 ".%" PRIu64 "%s", whole, frac * 10 / bin, name);
return buffer; return buffer;
} }
if (size_ >= 1000 * (bin / KiB)) if (size_ >= 1000 * (bin / KiB))
{ {
// >= 1000 of lesser bin, print x.xxXiB
std::sprintf (buffer, "%" PRIu64 ".%02" PRIu64 "%s", whole, frac * 100 / bin, name); std::sprintf (buffer, "%" PRIu64 ".%02" PRIu64 "%s", whole, frac * 100 / bin, name);
return buffer; return buffer;
} }
} }
std::sprintf (buffer, "%" PRIu64, size_); // < 1KiB, just print the number
std::sprintf (buffer, "%" PRIu64 "B", size_);
return buffer; return buffer;
} }
@ -207,5 +213,6 @@ void fs::Dir::close ()
struct dirent *fs::Dir::read () struct dirent *fs::Dir::read ()
{ {
errno = 0;
return ::readdir (m_dp.get ()); return ::readdir (m_dp.get ());
} }

View File

@ -36,6 +36,7 @@
using namespace std::chrono_literals; using namespace std::chrono_literals;
#ifndef _3DS #ifndef _3DS
/// \todo Investigate multithreading on 3DS
#define MULTITHREADED 1 #define MULTITHREADED 1
#else #else
#define MULTITHREADED 0 #define MULTITHREADED 0
@ -43,17 +44,24 @@ using namespace std::chrono_literals;
namespace namespace
{ {
auto const s_startTime = std::chrono::system_clock::to_time_t (std::chrono::system_clock::now ()); /// \brief Application start time
auto const s_startTime = std::time (nullptr);
/// \brief Mutex for s_freeSpace
platform::Mutex s_lock; platform::Mutex s_lock;
/// \brief Free space string
std::string s_freeSpace; std::string s_freeSpace;
} }
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
FtpServer::~FtpServer () FtpServer::~FtpServer ()
{ {
#if MULTITHREADED
m_quit = true; m_quit = true;
m_thread.join (); m_thread.join ();
#endif
} }
FtpServer::FtpServer (std::uint16_t const port_) FtpServer::FtpServer (std::uint16_t const port_)
@ -118,6 +126,7 @@ void FtpServer::draw ()
ImGui::Separator (); ImGui::Separator ();
#ifdef _3DS #ifdef _3DS
// Fill rest of top screen window
ImGui::BeginChild ("Logs", ImVec2 (0, 0), false, ImGuiWindowFlags_HorizontalScrollbar); ImGui::BeginChild ("Logs", ImVec2 (0, 0), false, ImGuiWindowFlags_HorizontalScrollbar);
#else #else
ImGui::BeginChild ("Logs", ImVec2 (0, 200), false, ImGuiWindowFlags_HorizontalScrollbar); ImGui::BeginChild ("Logs", ImVec2 (0, 200), false, ImGuiWindowFlags_HorizontalScrollbar);
@ -214,6 +223,7 @@ void FtpServer::handleStopButton ()
void FtpServer::loop () void FtpServer::loop ()
{ {
{ {
// poll listen socket
auto const lock = std::scoped_lock (m_lock); auto const lock = std::scoped_lock (m_lock);
if (m_socket) if (m_socket)
{ {
@ -227,6 +237,7 @@ void FtpServer::loop ()
} }
} }
// remove dead sessions
for (auto it = std::begin (m_sessions); it != std::end (m_sessions);) for (auto it = std::begin (m_sessions); it != std::end (m_sessions);)
{ {
auto const &session = *it; auto const &session = *it;
@ -236,9 +247,11 @@ void FtpServer::loop ()
++it; ++it;
} }
// poll sessions
if (!m_sessions.empty ()) if (!m_sessions.empty ())
FtpSession::poll (m_sessions); FtpSession::poll (m_sessions);
#if MULTITHREADED #if MULTITHREADED
// avoid busy polling in background thread
else else
platform::Thread::sleep (16ms); platform::Thread::sleep (16ms);
#endif #endif
@ -246,6 +259,7 @@ void FtpServer::loop ()
void FtpServer::threadFunc () void FtpServer::threadFunc ()
{ {
// bind log for this thread
Log::bind (m_log); Log::bind (m_log);
while (!m_quit) while (!m_quit)

View File

@ -55,8 +55,13 @@ using namespace std::chrono_literals;
namespace namespace
{ {
/// \brief Parse command
/// \param buffer_ Buffer to parse
/// \param size_ Size of buffer
/// \returns {delimiterPos, nextPos}
std::pair<char *, char *> parseCommand (char *const buffer_, std::size_t const size_) std::pair<char *, char *> parseCommand (char *const buffer_, std::size_t const size_)
{ {
// look for \r\n or \n delimiter
auto const end = &buffer_[size_]; auto const end = &buffer_[size_];
for (auto p = buffer_; p < end; ++p) for (auto p = buffer_; p < end; ++p)
{ {
@ -70,6 +75,9 @@ std::pair<char *, char *> parseCommand (char *const buffer_, std::size_t const s
return {nullptr, nullptr}; return {nullptr, nullptr};
} }
/// \brief Decode path
/// \param buffer_ Buffer to decode
/// \param size_ Size of buffer
void decodePath (char *const buffer_, std::size_t const size_) void decodePath (char *const buffer_, std::size_t const size_)
{ {
auto const end = &buffer_[size_]; auto const end = &buffer_[size_];
@ -81,6 +89,9 @@ void decodePath (char *const buffer_, std::size_t const size_)
} }
} }
/// \brief Encode path
/// \param buffer_ Buffer to encode
/// \param quotes_ Whether to encode quotes
std::string encodePath (std::string_view const buffer_, bool const quotes_ = false) std::string encodePath (std::string_view const buffer_, bool const quotes_ = false)
{ {
// check if the buffer has \n // check if the buffer has \n
@ -104,13 +115,16 @@ std::string encodePath (std::string_view const buffer_, bool const quotes_ = fal
} while (p); } while (p);
} }
// if nothing needs escaping, return it as-is
if (!lf && !numQuotes) if (!lf && !numQuotes)
return std::string (buffer_); return std::string (buffer_);
// reserve output buffer
std::string path (buffer_.size () + numQuotes, '\0'); std::string path (buffer_.size () + numQuotes, '\0');
auto in = buffer_.data (); auto in = buffer_.data ();
auto out = path.data (); auto out = path.data ();
// encode into the output buffer
while (in < end) while (in < end)
{ {
if (*in == '\n') if (*in == '\n')
@ -132,6 +146,8 @@ std::string encodePath (std::string_view const buffer_, bool const quotes_ = fal
return path; return path;
} }
/// \brief Get parent directory name of a path
/// \param path_ Path to get parent of
std::string dirName (std::string_view const path_) std::string dirName (std::string_view const path_)
{ {
// remove last path component // remove last path component
@ -142,6 +158,8 @@ std::string dirName (std::string_view const path_)
return dir; return dir;
} }
/// \brief Resolve path
/// \param path_ Path to resolve
std::string resolvePath (std::string_view const path_) std::string resolvePath (std::string_view const path_)
{ {
assert (!path_.empty ()); assert (!path_.empty ());
@ -209,6 +227,9 @@ std::string resolvePath (std::string_view const path_)
return outPath; return outPath;
} }
/// \brief Build path from a parent and child
/// \param cwd_ Parent directory
/// \param args_ Child component
std::string buildPath (std::string_view const cwd_, std::string_view const args_) std::string buildPath (std::string_view const cwd_, std::string_view const args_)
{ {
// absolute path // absolute path
@ -222,6 +243,9 @@ std::string buildPath (std::string_view const cwd_, std::string_view const args_
return std::string (cwd_) + '/' + std::string (args_); return std::string (cwd_) + '/' + std::string (args_);
} }
/// \brief Build resolved path from a parent and child
/// \param cwd_ Parent directory
/// \param args_ Child component
std::string buildResolvedPath (std::string_view const cwd_, std::string_view const args_) std::string buildResolvedPath (std::string_view const cwd_, std::string_view const args_)
{ {
return resolvePath (buildPath (cwd_, args_)); return resolvePath (buildPath (cwd_, args_));

View File

@ -29,6 +29,7 @@ IOBuffer::~IOBuffer () = default;
IOBuffer::IOBuffer (std::size_t const size_) IOBuffer::IOBuffer (std::size_t const size_)
: m_buffer (std::make_unique<char[]> (size_)), m_size (size_) : m_buffer (std::make_unique<char[]> (size_)), m_size (size_)
{ {
assert (size_ > 0);
} }
char *IOBuffer::freeArea () const char *IOBuffer::freeArea () const

View File

@ -36,8 +36,13 @@
namespace namespace
{ {
/// \brief GLFW main window
std::unique_ptr<GLFWwindow, void (*) (GLFWwindow *)> s_mainWindow (nullptr, glfwDestroyWindow); std::unique_ptr<GLFWwindow, void (*) (GLFWwindow *)> s_mainWindow (nullptr, glfwDestroyWindow);
/// \brief Window resize callback
/// \param window_ GLFW window
/// \param width_ New window width
/// \param height_ New window height
void windowResize (GLFWwindow *const window_, int const width_, int const height_) void windowResize (GLFWwindow *const window_, int const width_, int const height_)
{ {
(void)window_; (void)window_;
@ -49,6 +54,13 @@ void windowResize (GLFWwindow *const window_, int const width_, int const height
} }
#ifndef NDEBUG #ifndef NDEBUG
/// \brief GL log callback
/// \param source_ Message source
/// \param type_ Message type
/// \param id_ Message id
/// \param severity_ Message severity
/// \param length_ Message length
/// \param userParam_ User parameter
void logCallback (GLenum const source_, void logCallback (GLenum const source_,
GLenum const type_, GLenum const type_,
GLuint const id_, GLuint const id_,
@ -63,19 +75,21 @@ void logCallback (GLenum const source_,
(void)severity_; (void)severity_;
(void)length_; (void)length_;
(void)userParam_; (void)userParam_;
// std::fprintf (stderr, "%s\n", message_); std::fprintf (stderr, "%s\n", message_);
} }
#endif #endif
} }
bool platform::init () bool platform::init ()
{ {
// initialize GLFW
if (!glfwInit ()) if (!glfwInit ())
{ {
std::fprintf (stderr, "Failed to initialize GLFW\n"); std::fprintf (stderr, "Failed to initialize GLFW\n");
return false; return false;
} }
// use OpenGL 4.3 Core Profile
glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint (GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint (GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
@ -87,6 +101,7 @@ bool platform::init ()
glfwWindowHint (GLFW_DEPTH_BITS, 24); glfwWindowHint (GLFW_DEPTH_BITS, 24);
glfwWindowHint (GLFW_STENCIL_BITS, 8); glfwWindowHint (GLFW_STENCIL_BITS, 8);
// create GLFW window
s_mainWindow.reset (glfwCreateWindow (1280, 720, "Test Game", nullptr, nullptr)); s_mainWindow.reset (glfwCreateWindow (1280, 720, "Test Game", nullptr, nullptr));
if (!s_mainWindow) if (!s_mainWindow)
{ {
@ -95,12 +110,14 @@ bool platform::init ()
return false; return false;
} }
// enable vsync
glfwSwapInterval (1); glfwSwapInterval (1);
// create context // create context
glfwMakeContextCurrent (s_mainWindow.get ()); glfwMakeContextCurrent (s_mainWindow.get ());
glfwSetFramebufferSizeCallback (s_mainWindow.get (), windowResize); glfwSetFramebufferSizeCallback (s_mainWindow.get (), windowResize);
// load OpenGL
if (!gladLoadGL ()) if (!gladLoadGL ())
{ {
std::fprintf (stderr, "gladLoadGL: failed\n"); std::fprintf (stderr, "gladLoadGL: failed\n");
@ -134,12 +151,10 @@ bool platform::init ()
std::printf ("Renderer: %s\n", glGetString (GL_RENDERER)); std::printf ("Renderer: %s\n", glGetString (GL_RENDERER));
std::printf ("OpenGL Version: %s\n", glGetString (GL_VERSION)); std::printf ("OpenGL Version: %s\n", glGetString (GL_VERSION));
IMGUI_CHECKVERSION ();
ImGui::CreateContext ();
ImGui_ImplGlfw_InitForOpenGL (s_mainWindow.get (), true); ImGui_ImplGlfw_InitForOpenGL (s_mainWindow.get (), true);
ImGui_ImplOpenGL3_Init ("#version 150"); ImGui_ImplOpenGL3_Init ("#version 150");
// disable imgui.ini file
ImGuiIO &io = ImGui::GetIO (); ImGuiIO &io = ImGui::GetIO ();
io.IniFilename = nullptr; io.IniFilename = nullptr;
@ -169,10 +184,6 @@ void platform::render ()
{ {
ImGui::Render (); ImGui::Render ();
int width;
int height;
glfwGetFramebufferSize (s_mainWindow.get (), &width, &height);
glViewport (0, 0, width, height);
glClearColor (0.45f, 0.55f, 0.60f, 1.00f); glClearColor (0.45f, 0.55f, 0.60f, 1.00f);
glClear (GL_COLOR_BUFFER_BIT); glClear (GL_COLOR_BUFFER_BIT);
@ -183,22 +194,24 @@ void platform::render ()
void platform::exit () void platform::exit ()
{ {
ImGui::DestroyContext ();
s_mainWindow.reset (); s_mainWindow.reset ();
glfwTerminate (); glfwTerminate ();
} }
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
/// \brief Platform thread pimpl
class platform::Thread::privateData_t class platform::Thread::privateData_t
{ {
public: public:
privateData_t () = default; privateData_t () = default;
/// \brief Parameterized constructor
/// \param func_ Thread entry point
privateData_t (std::function<void ()> func_) : thread (func_) privateData_t (std::function<void ()> func_) : thread (func_)
{ {
} }
/// \brief Underlying thread object
std::thread thread; std::thread thread;
}; };
@ -235,9 +248,11 @@ void platform::Thread::sleep (std::chrono::milliseconds const timeout_)
} }
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
/// \brief Platform mutex pimpl
class platform::Mutex::privateData_t class platform::Mutex::privateData_t
{ {
public: public:
/// \brief Underlying mutex
std::mutex mutex; std::mutex mutex;
}; };

View File

@ -27,12 +27,17 @@
namespace namespace
{ {
#ifdef _3DS #ifdef _3DS
/// \brief Maximum number of log messages to keep
constexpr auto MAX_LOGS = 250; constexpr auto MAX_LOGS = 250;
#else #else
/// \brief Maximum number of log messages to keep
constexpr auto MAX_LOGS = 10000; constexpr auto MAX_LOGS = 10000;
#endif #endif
/// \brief Bound log
thread_local WeakLog s_log; thread_local WeakLog s_log;
/// \brief Message prefix
static char const *const s_prefix[] = { static char const *const s_prefix[] = {
[Log::DEBUG] = "[DEBUG]", [Log::DEBUG] = "[DEBUG]",
[Log::INFO] = "[INFO]", [Log::INFO] = "[INFO]",
@ -59,19 +64,11 @@ void Log::draw ()
} }
static ImVec4 const s_colors[] = { static ImVec4 const s_colors[] = {
[Log::DEBUG] = ImVec4 (1.0f, 1.0f, 0.4f, 1.0f), [Log::DEBUG] = ImVec4 (1.0f, 1.0f, 0.4f, 1.0f), // yellow
[Log::INFO] = ImVec4 (1.0f, 1.0f, 1.0f, 1.0f), [Log::INFO] = ImVec4 (1.0f, 1.0f, 1.0f, 1.0f), // white
[Log::ERROR] = ImVec4 (1.0f, 0.4f, 0.4f, 1.0f), [Log::ERROR] = ImVec4 (1.0f, 0.4f, 0.4f, 1.0f), // red
[Log::COMMAND] = ImVec4 (0.4f, 1.0f, 0.4f, 1.0f), [Log::COMMAND] = ImVec4 (0.4f, 1.0f, 0.4f, 1.0f), // green
[Log::RESPONSE] = ImVec4 (0.4f, 1.0f, 1.0f, 1.0f), [Log::RESPONSE] = ImVec4 (0.4f, 1.0f, 1.0f, 1.0f), // cyan
};
static char const *const s_prefix[] = {
[Log::DEBUG] = "[DEBUG]",
[Log::INFO] = "[INFO]",
[Log::ERROR] = "[ERROR]",
[Log::COMMAND] = "[COMMAND]",
[Log::RESPONSE] = "[RESPONSE]",
}; };
for (auto const &message : m_messages) for (auto const &message : m_messages)
@ -83,7 +80,7 @@ void Log::draw ()
ImGui::PopStyleColor (); ImGui::PopStyleColor ();
} }
// auto scroll if scroll bar is at end // auto-scroll if scroll bar is at end
if (ImGui::GetScrollY () >= ImGui::GetScrollMaxY ()) if (ImGui::GetScrollY () >= ImGui::GetScrollMaxY ())
ImGui::SetScrollHereY (1.0f); ImGui::SetScrollHereY (1.0f);
} }
@ -183,6 +180,7 @@ void Log::log (Level const level_, std::string_view const message_)
auto msg = std::string (message_); auto msg = std::string (message_);
for (auto &c : msg) for (auto &c : msg)
{ {
// replace nul-characters with ? to avoid truncation
if (c == '\0') if (c == '\0')
c = '?'; c = '?';
} }

View File

@ -29,14 +29,21 @@
int main (int argc_, char *argv_[]) int main (int argc_, char *argv_[])
{ {
IMGUI_CHECKVERSION ();
ImGui::CreateContext ();
if (!platform::init ()) if (!platform::init ())
{
ImGui::DestroyContext ();
return EXIT_FAILURE; return EXIT_FAILURE;
}
auto &style = ImGui::GetStyle (); auto &style = ImGui::GetStyle ();
style.WindowRounding = 0.0f; style.WindowRounding = 0.0f;
#ifdef _3DS #ifdef _3DS
style.Colors[ImGuiCol_WindowBg].w = 0.5f; // citro3d logo doesn't quite show with the default transparency
style.Colors[ImGuiCol_WindowBg].w = 0.8f;
#endif #endif
auto server = FtpServer::create (5000); auto server = FtpServer::create (5000);
@ -48,5 +55,7 @@ int main (int argc_, char *argv_[])
platform::render (); platform::render ();
} }
server.reset ();
platform::exit (); platform::exit ();
ImGui::DestroyContext ();
} }

View File

@ -23,6 +23,7 @@
#include <arpa/inet.h> #include <arpa/inet.h>
#include <cassert> #include <cassert>
#include <cstdlib>
#include <cstring> #include <cstring>
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
@ -51,18 +52,24 @@ SockAddr::SockAddr (struct sockaddr const &addr_)
std::memcpy (&m_addr, &addr_, sizeof (struct sockaddr_in6)); std::memcpy (&m_addr, &addr_, sizeof (struct sockaddr_in6));
break; break;
#endif #endif
default:
std::abort ();
break;
} }
} }
SockAddr::SockAddr (struct sockaddr_in const &addr_) SockAddr::SockAddr (struct sockaddr_in const &addr_)
: SockAddr (reinterpret_cast<struct sockaddr const &> (addr_)) : SockAddr (reinterpret_cast<struct sockaddr const &> (addr_))
{ {
assert (m_addr.ss_family == AF_INET);
} }
#ifndef _3DS #ifndef _3DS
SockAddr::SockAddr (struct sockaddr_in6 const &addr_) SockAddr::SockAddr (struct sockaddr_in6 const &addr_)
: SockAddr (reinterpret_cast<struct sockaddr const &> (addr_)) : SockAddr (reinterpret_cast<struct sockaddr const &> (addr_))
{ {
assert (m_addr.ss_family == AF_INET6);
} }
#endif #endif
@ -111,9 +118,11 @@ std::uint16_t SockAddr::port () const
case AF_INET6: case AF_INET6:
return ntohs (reinterpret_cast<struct sockaddr_in6 const *> (&m_addr)->sin6_port); return ntohs (reinterpret_cast<struct sockaddr_in6 const *> (&m_addr)->sin6_port);
#endif #endif
}
return 0; default:
std::abort ();
break;
}
} }
char const *SockAddr::name (char *buffer_, std::size_t size_) const char const *SockAddr::name (char *buffer_, std::size_t size_) const
@ -133,9 +142,11 @@ char const *SockAddr::name (char *buffer_, std::size_t size_) const
buffer_, buffer_,
size_); size_);
#endif #endif
}
return nullptr; default:
std::abort ();
break;
}
} }
char const *SockAddr::name () const char const *SockAddr::name () const

View File

@ -38,8 +38,10 @@ Socket::~Socket ()
{ {
if (m_listening) if (m_listening)
Log::info ("Stop listening on [%s]:%u\n", m_sockName.name (), m_sockName.port ()); Log::info ("Stop listening on [%s]:%u\n", m_sockName.name (), m_sockName.port ());
if (m_connected) if (m_connected)
Log::info ("Closing connection to [%s]:%u\n", m_peerName.name (), m_peerName.port ()); Log::info ("Closing connection to [%s]:%u\n", m_peerName.name (), m_peerName.port ());
if (::close (m_fd) != 0) if (::close (m_fd) != 0)
Log::error ("close: %s\n", std::strerror (errno)); Log::error ("close: %s\n", std::strerror (errno));
} }
@ -66,7 +68,7 @@ UniqueSocket Socket::accept ()
if (fd < 0) if (fd < 0)
{ {
Log::error ("accept: %s\n", std::strerror (errno)); Log::error ("accept: %s\n", std::strerror (errno));
return {nullptr, {}}; return nullptr;
} }
Log::info ("Accepted connection from [%s]:%u\n", addr.name (), addr.port ()); Log::info ("Accepted connection from [%s]:%u\n", addr.name (), addr.port ());
@ -112,6 +114,7 @@ bool Socket::bind (SockAddr const &addr_)
if (addr_.port () == 0) if (addr_.port () == 0)
{ {
// get socket name due to request for ephemeral port
socklen_t addrLen = sizeof (struct sockaddr_storage); socklen_t addrLen = sizeof (struct sockaddr_storage);
if (::getsockname (m_fd, m_sockName, &addrLen) != 0) if (::getsockname (m_fd, m_sockName, &addrLen) != 0)
Log::error ("getsockname: %s\n", std::strerror (errno)); Log::error ("getsockname: %s\n", std::strerror (errno));
@ -200,7 +203,7 @@ bool Socket::setNonBlocking (bool const nonBlocking_)
if (::fcntl (m_fd, F_SETFL, flags) != 0) if (::fcntl (m_fd, F_SETFL, flags) != 0)
{ {
Log::error ("fcntl(F_SETFL): %s\n", std::strerror (errno)); Log::error ("fcntl(F_SETFL, %d): %s\n", flags, std::strerror (errno));
return false; return false;
} }
@ -212,7 +215,8 @@ bool Socket::setReuseAddress (bool const reuse_)
int reuse = reuse_; int reuse = reuse_;
if (::setsockopt (m_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof (reuse)) != 0) if (::setsockopt (m_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof (reuse)) != 0)
{ {
Log::error ("setsockopt(SO_REUSEADDR, %u): %s\n", reuse_, std::strerror (errno)); Log::error (
"setsockopt(SO_REUSEADDR, %s): %s\n", reuse_ ? "yes" : "no", std::strerror (errno));
return false; return false;
} }

View File

@ -48,27 +48,39 @@
namespace namespace
{ {
constexpr auto LOGO_WIDTH = 500; /// \brief deko3d logo width
constexpr auto LOGO_WIDTH = 500;
/// \brief deko3d logo height
constexpr auto LOGO_HEIGHT = 493; constexpr auto LOGO_HEIGHT = 493;
/// \brief Number of framebuffers
constexpr auto FB_NUM = 2u; constexpr auto FB_NUM = 2u;
constexpr auto CMDBUF_SIZE = 1024 * 1024; /// \brief Command buffer size
constexpr auto DATABUF_SIZE = 1024 * 1024; constexpr auto CMDBUF_SIZE = 1024 * 1024;
/// \brief Data buffer size
constexpr auto DATABUF_SIZE = 1024 * 1024;
/// \brief Index buffer size
constexpr auto INDEXBUF_SIZE = 1024 * 1024; constexpr auto INDEXBUF_SIZE = 1024 * 1024;
/// \brief Image buffer size
constexpr auto IMAGEBUF_SIZE = 16 * 1024 * 1024; constexpr auto IMAGEBUF_SIZE = 16 * 1024 * 1024;
/// \brief Vertex shader UBO
struct VertUBO struct VertUBO
{ {
/// \brief Projection matrix
glm::mat4 projMtx; glm::mat4 projMtx;
}; };
/// \brief Fragment shader UBO
struct FragUBO struct FragUBO
{ {
/// \brief Whether drawing a font or not
std::uint32_t font; std::uint32_t font;
}; };
constexpr std::array VertexAttribState = { /// \brief Vertex attribute state
constexpr std::array VERTEX_ATTRIB_STATE = {
// clang-format off // clang-format off
DkVtxAttribState{0, 0, offsetof (ImDrawVert, pos), DkVtxAttribSize_2x32, DkVtxAttribType_Float, 0}, 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, uv), DkVtxAttribSize_2x32, DkVtxAttribType_Float, 0},
@ -76,58 +88,95 @@ constexpr std::array VertexAttribState = {
// clang-format on // clang-format on
}; };
constexpr std::array VertexBufferState = { /// \brief Vertex buffer state
constexpr std::array VERTEX_BUFFER_STATE = {
DkVtxBufferState{sizeof (ImDrawVert), 0}, DkVtxBufferState{sizeof (ImDrawVert), 0},
}; };
/// \brief deko3d device
dk::UniqueDevice s_device; dk::UniqueDevice s_device;
/// \brief Depth buffer memblock
dk::UniqueMemBlock s_depthMemBlock; dk::UniqueMemBlock s_depthMemBlock;
/// \brief Depth buffer image
dk::Image s_depthBuffer; dk::Image s_depthBuffer;
/// \brief Framebuffer memblock
dk::UniqueMemBlock s_fbMemBlock; dk::UniqueMemBlock s_fbMemBlock;
/// \brief Framebuffer images
dk::Image s_frameBuffers[FB_NUM]; dk::Image s_frameBuffers[FB_NUM];
/// \brief Font image
dk::Image s_fontTexture; dk::Image s_fontTexture;
/// \brief deko3d logo image
dk::Image s_logoTexture; dk::Image s_logoTexture;
/// \brief deko3d swapchain
dk::UniqueSwapchain s_swapchain; dk::UniqueSwapchain s_swapchain;
/// \brief Shader code memblock
dk::UniqueMemBlock s_codeMemBlock; dk::UniqueMemBlock s_codeMemBlock;
/// \brief Shaders (vertex, fragment)
dk::Shader s_shaders[2]; dk::Shader s_shaders[2];
/// \brief UBO memblock
dk::UniqueMemBlock s_uboMemBlock; dk::UniqueMemBlock s_uboMemBlock;
/// \brief Vertex data memblock
dk::UniqueMemBlock s_vtxMemBlock[FB_NUM]; dk::UniqueMemBlock s_vtxMemBlock[FB_NUM];
/// \brief Index data memblock
dk::UniqueMemBlock s_idxMemBlock[FB_NUM]; dk::UniqueMemBlock s_idxMemBlock[FB_NUM];
/// \brief Command buffer memblock
dk::UniqueMemBlock s_cmdMemBlock[FB_NUM]; dk::UniqueMemBlock s_cmdMemBlock[FB_NUM];
/// \brief Command buffers
dk::UniqueCmdBuf s_cmdBuf[FB_NUM]; dk::UniqueCmdBuf s_cmdBuf[FB_NUM];
/// \brief Image memblock
dk::UniqueMemBlock s_imageMemBlock; dk::UniqueMemBlock s_imageMemBlock;
/// \brief Image/Sampler descriptor memblock
dk::UniqueMemBlock s_descriptorMemBlock; dk::UniqueMemBlock s_descriptorMemBlock;
/// \brief deko3d queue
dk::UniqueQueue s_queue; dk::UniqueQueue s_queue;
constexpr auto MAX_SAMPLERS = 1; /// \brief Maximum number of samplers
constexpr auto MAX_IMAGES = 2; constexpr auto MAX_SAMPLERS = 1;
/// \brief Maximum number of images
constexpr auto MAX_IMAGES = 2;
/// \brief Sample descriptors
dk::SamplerDescriptor *s_samplerDescriptors = nullptr; dk::SamplerDescriptor *s_samplerDescriptors = nullptr;
dk::ImageDescriptor *s_imageDescriptors = nullptr; /// \brief Image descriptors
dk::ImageDescriptor *s_imageDescriptors = nullptr;
/// \brief Currently bound image descriptor
std::uintptr_t s_boundDescriptor = 0; std::uintptr_t s_boundDescriptor = 0;
unsigned s_width = 0; /// \brief Framebuffer width
unsigned s_width = 0;
/// \brief Framebuffer height
unsigned s_height = 0; unsigned s_height = 0;
/// \brief Align value
/// \tparam T Value type
/// \tparam U Alignment type
/// \param size_ Value to align
/// \param align_ Alignment
template <typename T, typename U> template <typename T, typename U>
constexpr inline std::uint32_t align (T const &size_, U const &align_) constexpr inline std::uint32_t align (T const &size_, U const &align_)
{ {
return static_cast<std::uint32_t> (size_ + align_ - 1) & ~(align_ - 1); return static_cast<std::uint32_t> (size_ + align_ - 1) & ~(align_ - 1);
} }
/// \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
void rebuildSwapchain (unsigned const width_, unsigned const height_) void rebuildSwapchain (unsigned const width_, unsigned const height_)
{ {
// destroy old swapchain
s_swapchain = nullptr; s_swapchain = nullptr;
// create new depth buffer image layout
dk::ImageLayout depthLayout; dk::ImageLayout depthLayout;
dk::ImageLayoutMaker{s_device} dk::ImageLayoutMaker{s_device}
.setFlags (DkImageFlags_UsageRender | DkImageFlags_HwCompression) .setFlags (DkImageFlags_UsageRender | DkImageFlags_HwCompression)
@ -138,6 +187,7 @@ void rebuildSwapchain (unsigned const width_, unsigned const height_)
auto const depthAlign = depthLayout.getAlignment (); auto const depthAlign = depthLayout.getAlignment ();
auto const depthSize = depthLayout.getSize (); auto const depthSize = depthLayout.getSize ();
// create depth buffer memblock
if (!s_depthMemBlock) if (!s_depthMemBlock)
{ {
s_depthMemBlock = dk::MemBlockMaker{s_device, s_depthMemBlock = dk::MemBlockMaker{s_device,
@ -148,6 +198,7 @@ void rebuildSwapchain (unsigned const width_, unsigned const height_)
s_depthBuffer.initialize (depthLayout, s_depthMemBlock, 0); s_depthBuffer.initialize (depthLayout, s_depthMemBlock, 0);
// create framebuffer image layout
dk::ImageLayout fbLayout; dk::ImageLayout fbLayout;
dk::ImageLayoutMaker{s_device} dk::ImageLayoutMaker{s_device}
.setFlags ( .setFlags (
@ -159,6 +210,7 @@ void rebuildSwapchain (unsigned const width_, unsigned const height_)
auto const fbAlign = fbLayout.getAlignment (); auto const fbAlign = fbLayout.getAlignment ();
auto const fbSize = fbLayout.getSize (); auto const fbSize = fbLayout.getSize ();
// create framebuffer memblock
if (!s_fbMemBlock) if (!s_fbMemBlock)
{ {
s_fbMemBlock = dk::MemBlockMaker{s_device, s_fbMemBlock = dk::MemBlockMaker{s_device,
@ -167,6 +219,7 @@ void rebuildSwapchain (unsigned const width_, unsigned const height_)
.create (); .create ();
} }
// initialize swapchain images
std::array<DkImage const *, FB_NUM> swapchainImages; std::array<DkImage const *, FB_NUM> swapchainImages;
for (unsigned i = 0; i < FB_NUM; ++i) for (unsigned i = 0; i < FB_NUM; ++i)
{ {
@ -174,18 +227,26 @@ void rebuildSwapchain (unsigned const width_, unsigned const height_)
s_frameBuffers[i].initialize (fbLayout, s_fbMemBlock, i * fbSize); s_frameBuffers[i].initialize (fbLayout, s_fbMemBlock, i * fbSize);
} }
// create swapchain
s_swapchain = dk::SwapchainMaker{s_device, nwindowGetDefault (), swapchainImages}.create (); s_swapchain = dk::SwapchainMaker{s_device, nwindowGetDefault (), swapchainImages}.create ();
} }
/// \brief Load shader code
void loadShaders () void loadShaders ()
{ {
/// \brief Shader file descriptor
struct ShaderFile struct ShaderFile
{ {
/// \brief Parameterized constructor
/// \param shader_ Shader object
/// \param path_ Path to source code
ShaderFile (dk::Shader &shader_, char const *const path_) ShaderFile (dk::Shader &shader_, char const *const path_)
: shader (shader_), path (path_), size (getSize (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_) static std::size_t getSize (char const *const path_)
{ {
struct stat st; struct stat st;
@ -199,14 +260,18 @@ void loadShaders ()
return st.st_size; return st.st_size;
} }
/// \brief Shader object
dk::Shader &shader; dk::Shader &shader;
/// \brief Path to source code
char const *const path; char const *const path;
/// \brief Source code file size
std::size_t const size; std::size_t const size;
}; };
auto shaderFiles = {ShaderFile{s_shaders[0], "romfs:/shaders/imgui_vsh.dksh"}, auto shaderFiles = {ShaderFile{s_shaders[0], "romfs:/shaders/imgui_vsh.dksh"},
ShaderFile{s_shaders[1], "romfs:/shaders/imgui_fsh.dksh"}}; ShaderFile{s_shaders[1], "romfs:/shaders/imgui_fsh.dksh"}};
// calculate total size of shaders
auto const codeSize = std::accumulate (std::begin (shaderFiles), auto const codeSize = std::accumulate (std::begin (shaderFiles),
std::end (shaderFiles), std::end (shaderFiles),
DK_SHADER_CODE_UNUSABLE_SIZE, DK_SHADER_CODE_UNUSABLE_SIZE,
@ -214,6 +279,7 @@ void loadShaders ()
return sum_ + align (file_.size, DK_SHADER_CODE_ALIGNMENT); return sum_ + align (file_.size, DK_SHADER_CODE_ALIGNMENT);
}); });
// create shader code memblock
s_codeMemBlock = dk::MemBlockMaker{s_device, align (codeSize, DK_MEMBLOCK_ALIGNMENT)} s_codeMemBlock = dk::MemBlockMaker{s_device, align (codeSize, DK_MEMBLOCK_ALIGNMENT)}
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached | .setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached |
DkMemBlockFlags_Code) DkMemBlockFlags_Code)
@ -222,6 +288,7 @@ void loadShaders ()
auto const addr = static_cast<std::uint8_t *> (s_codeMemBlock.getCpuAddr ()); auto const addr = static_cast<std::uint8_t *> (s_codeMemBlock.getCpuAddr ());
std::size_t offset = 0; std::size_t offset = 0;
// read shaders into memblock
for (auto &file : shaderFiles) for (auto &file : shaderFiles)
{ {
std::uint32_t const codeOffset = offset; std::uint32_t const codeOffset = offset;
@ -245,13 +312,18 @@ void loadShaders ()
} }
} }
/// \brief Setup render state
/// \param slot_ Swapchain slot
/// \param drawData_ Data to draw
/// \param width_ Framebuffer width
/// \param height_ Framebuffer height
DkCmdList setupRenderState (int const slot_, DkCmdList setupRenderState (int const slot_,
ImDrawData *const drawData_, ImDrawData *const drawData_,
unsigned const width_, unsigned const width_,
unsigned const height_) unsigned const height_)
{ {
// Setup viewport, orthographic projection matrix // setup viewport, orthographic projection matrix
// Our visible imgui space lies from drawData_->DisplayPos (top left) to // our visible imgui space lies from drawData_->DisplayPos (top left) to
// drawData_->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single // drawData_->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single
// viewport apps. // viewport apps.
auto const L = drawData_->DisplayPos.x; auto const L = drawData_->DisplayPos.x;
@ -262,6 +334,7 @@ DkCmdList setupRenderState (int const slot_,
VertUBO vertUBO; VertUBO vertUBO;
vertUBO.projMtx = glm::orthoRH_ZO (L, R, B, T, -1.0f, 1.0f); vertUBO.projMtx = glm::orthoRH_ZO (L, R, B, T, -1.0f, 1.0f);
// create command buffer to initialize/reset render state
s_cmdBuf[slot_].setViewports (0, DkViewport{0.0f, 0.0f, width_, height_}); 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_].bindShaders (DkStageFlag_GraphicsMask, {&s_shaders[0], &s_shaders[1]});
s_cmdBuf[slot_].bindUniformBuffer (DkStage_Vertex, s_cmdBuf[slot_].bindUniformBuffer (DkStage_Vertex,
@ -286,8 +359,8 @@ DkCmdList setupRenderState (int const slot_,
DkBlendFactor_InvSrcAlpha, DkBlendFactor_InvSrcAlpha,
DkBlendFactor_InvSrcAlpha, DkBlendFactor_InvSrcAlpha,
DkBlendFactor_Zero)); DkBlendFactor_Zero));
s_cmdBuf[slot_].bindVtxAttribState (VertexAttribState); s_cmdBuf[slot_].bindVtxAttribState (VERTEX_ATTRIB_STATE);
s_cmdBuf[slot_].bindVtxBufferState (VertexBufferState); s_cmdBuf[slot_].bindVtxBufferState (VERTEX_BUFFER_STATE);
return s_cmdBuf[slot_].finishList (); return s_cmdBuf[slot_].finishList ();
} }
@ -295,17 +368,21 @@ DkCmdList setupRenderState (int const slot_,
void imgui::deko3d::init () void imgui::deko3d::init ()
{ {
// Setup back-end capabilities flags // setup back-end capabilities flags
ImGuiIO &io = ImGui::GetIO (); ImGuiIO &io = ImGui::GetIO ();
io.BackendRendererName = "deko3d"; io.BackendRendererName = "deko3d";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
// defer initialization to first newFrame ()
} }
void imgui::deko3d::exit () void imgui::deko3d::exit ()
{ {
// wait for queue to be idle
s_queue.waitIdle (); s_queue.waitIdle ();
// clean up all of the deko3d objects
s_queue = nullptr; s_queue = nullptr;
s_descriptorMemBlock = nullptr; s_descriptorMemBlock = nullptr;
s_imageMemBlock = nullptr; s_imageMemBlock = nullptr;
@ -331,12 +408,16 @@ void imgui::deko3d::newFrame ()
if (s_device) if (s_device)
return; return;
// create deko3d device
s_device = dk::DeviceMaker{}.create (); s_device = dk::DeviceMaker{}.create ();
// initialize swapchain with maximum resolution
rebuildSwapchain (1920, 1080); rebuildSwapchain (1920, 1080);
// load shader code
loadShaders (); loadShaders ();
// create UBO memblock
s_uboMemBlock = dk::MemBlockMaker{s_device, s_uboMemBlock = dk::MemBlockMaker{s_device,
align (align (sizeof (VertUBO), DK_UNIFORM_BUF_ALIGNMENT) + align (align (sizeof (VertUBO), DK_UNIFORM_BUF_ALIGNMENT) +
align (sizeof (FragUBO), DK_UNIFORM_BUF_ALIGNMENT), align (sizeof (FragUBO), DK_UNIFORM_BUF_ALIGNMENT),
@ -344,32 +425,38 @@ void imgui::deko3d::newFrame ()
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) .setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
.create (); .create ();
// create memblocks for each framebuffer slot
for (std::size_t i = 0; i < FB_NUM; ++i) for (std::size_t i = 0; i < FB_NUM; ++i)
{ {
// create vertex data memblock
s_vtxMemBlock[i] = dk::MemBlockMaker{s_device, align (DATABUF_SIZE, DK_MEMBLOCK_ALIGNMENT)} s_vtxMemBlock[i] = dk::MemBlockMaker{s_device, align (DATABUF_SIZE, DK_MEMBLOCK_ALIGNMENT)}
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) .setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
.create (); .create ();
// create index data memblock
s_idxMemBlock[i] = dk::MemBlockMaker{s_device, align (INDEXBUF_SIZE, DK_MEMBLOCK_ALIGNMENT)} s_idxMemBlock[i] = dk::MemBlockMaker{s_device, align (INDEXBUF_SIZE, DK_MEMBLOCK_ALIGNMENT)}
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) .setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
.create (); .create ();
// create command buffer memblock
s_cmdMemBlock[i] = dk::MemBlockMaker{s_device, align (CMDBUF_SIZE, DK_MEMBLOCK_ALIGNMENT)} s_cmdMemBlock[i] = dk::MemBlockMaker{s_device, align (CMDBUF_SIZE, DK_MEMBLOCK_ALIGNMENT)}
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) .setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
.create (); .create ();
// create command buffer
s_cmdBuf[i] = dk::CmdBufMaker{s_device}.create (); s_cmdBuf[i] = dk::CmdBufMaker{s_device}.create ();
s_cmdBuf[i].addMemory (s_cmdMemBlock[i], 0, s_cmdMemBlock[i].getSize ()); s_cmdBuf[i].addMemory (s_cmdMemBlock[i], 0, s_cmdMemBlock[i].getSize ());
} }
// create queue
s_queue = dk::QueueMaker{s_device}.setFlags (DkQueueFlags_Graphics).create (); s_queue = dk::QueueMaker{s_device}.setFlags (DkQueueFlags_Graphics).create ();
// create image memblock
s_imageMemBlock = dk::MemBlockMaker{s_device, align (IMAGEBUF_SIZE, DK_MEMBLOCK_ALIGNMENT)} s_imageMemBlock = dk::MemBlockMaker{s_device, align (IMAGEBUF_SIZE, DK_MEMBLOCK_ALIGNMENT)}
.setFlags (DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image) .setFlags (DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image)
.create (); .create ();
// Build texture atlas // get texture atlas
ImGuiIO &io = ImGui::GetIO (); ImGuiIO &io = ImGui::GetIO ();
io.Fonts->SetTexID (nullptr); io.Fonts->SetTexID (nullptr);
unsigned char *pixels; unsigned char *pixels;
@ -377,12 +464,14 @@ void imgui::deko3d::newFrame ()
int height; int height;
io.Fonts->GetTexDataAsAlpha8 (&pixels, &width, &height); io.Fonts->GetTexDataAsAlpha8 (&pixels, &width, &height);
// create memblock for transfer
dk::UniqueMemBlock memBlock = dk::UniqueMemBlock memBlock =
dk::MemBlockMaker{s_device, align (width * height, DK_MEMBLOCK_ALIGNMENT)} dk::MemBlockMaker{s_device, align (width * height, DK_MEMBLOCK_ALIGNMENT)}
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) .setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
.create (); .create ();
std::memcpy (memBlock.getCpuAddr (), pixels, width * height); std::memcpy (memBlock.getCpuAddr (), pixels, width * height);
// create image/sampler memblock
static_assert (sizeof (dk::ImageDescriptor) == DK_IMAGE_DESCRIPTOR_ALIGNMENT); static_assert (sizeof (dk::ImageDescriptor) == DK_IMAGE_DESCRIPTOR_ALIGNMENT);
static_assert (sizeof (dk::SamplerDescriptor) == DK_SAMPLER_DESCRIPTOR_ALIGNMENT); static_assert (sizeof (dk::SamplerDescriptor) == DK_SAMPLER_DESCRIPTOR_ALIGNMENT);
static_assert (DK_IMAGE_DESCRIPTOR_ALIGNMENT == DK_SAMPLER_DESCRIPTOR_ALIGNMENT); static_assert (DK_IMAGE_DESCRIPTOR_ALIGNMENT == DK_SAMPLER_DESCRIPTOR_ALIGNMENT);
@ -391,17 +480,22 @@ void imgui::deko3d::newFrame ()
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) .setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
.create (); .create ();
// get cpu address for descriptors
s_samplerDescriptors = s_samplerDescriptors =
static_cast<dk::SamplerDescriptor *> (s_descriptorMemBlock.getCpuAddr ()); static_cast<dk::SamplerDescriptor *> (s_descriptorMemBlock.getCpuAddr ());
s_imageDescriptors = s_imageDescriptors =
reinterpret_cast<dk::ImageDescriptor *> (&s_samplerDescriptors[MAX_SAMPLERS]); reinterpret_cast<dk::ImageDescriptor *> (&s_samplerDescriptors[MAX_SAMPLERS]);
// initialize sampler descriptor
s_samplerDescriptors[0].initialize ( s_samplerDescriptors[0].initialize (
dk::Sampler{} dk::Sampler{}
.setFilter (DkFilter_Linear, DkFilter_Linear) .setFilter (DkFilter_Linear, DkFilter_Linear)
.setWrapMode (DkWrapMode_ClampToEdge, DkWrapMode_ClampToEdge, DkWrapMode_ClampToEdge)); .setWrapMode (DkWrapMode_ClampToEdge, DkWrapMode_ClampToEdge, DkWrapMode_ClampToEdge));
// use command buffer 0 for initialization
auto &cmdBuf = s_cmdBuf[0]; auto &cmdBuf = s_cmdBuf[0];
// initialize texture atlas image layout
dk::ImageLayout layout; dk::ImageLayout layout;
dk::ImageLayoutMaker{s_device} dk::ImageLayoutMaker{s_device}
.setFlags (0) .setFlags (0)
@ -409,21 +503,25 @@ void imgui::deko3d::newFrame ()
.setDimensions (width, height) .setDimensions (width, height)
.initialize (layout); .initialize (layout);
// initialize font texture atlas image descriptor
s_fontTexture.initialize (layout, s_imageMemBlock, 0); s_fontTexture.initialize (layout, s_imageMemBlock, 0);
s_imageDescriptors[0].initialize (s_fontTexture); s_imageDescriptors[0].initialize (s_fontTexture);
// copy font texture atlas to image view
dk::ImageView imageView{s_fontTexture}; dk::ImageView imageView{s_fontTexture};
cmdBuf.copyBufferToImage ({memBlock.getGpuAddr ()}, imageView, {0, 0, 0, width, height, 1}); cmdBuf.copyBufferToImage ({memBlock.getGpuAddr ()}, imageView, {0, 0, 0, width, height, 1});
// bind image/sampler descriptors
cmdBuf.bindSamplerDescriptorSet (s_descriptorMemBlock.getGpuAddr (), MAX_SAMPLERS); cmdBuf.bindSamplerDescriptorSet (s_descriptorMemBlock.getGpuAddr (), MAX_SAMPLERS);
cmdBuf.bindImageDescriptorSet ( cmdBuf.bindImageDescriptorSet (
s_descriptorMemBlock.getGpuAddr () + MAX_SAMPLERS * sizeof (dk::SamplerDescriptor), s_descriptorMemBlock.getGpuAddr () + MAX_SAMPLERS * sizeof (dk::SamplerDescriptor),
MAX_IMAGES); MAX_IMAGES);
// submit commands while we get the next image ready to transfer
s_queue.submitCommands (cmdBuf.finishList ()); s_queue.submitCommands (cmdBuf.finishList ());
s_queue.waitIdle ();
{ {
// read the deko3d logo
auto const path = "romfs:/deko3d.rgba.zst"; auto const path = "romfs:/deko3d.rgba.zst";
struct stat st; struct stat st;
@ -456,10 +554,13 @@ void imgui::deko3d::newFrame ()
std::abort (); std::abort ();
} }
memBlock = dk::MemBlockMaker{s_device, align (size, DK_MEMBLOCK_ALIGNMENT)} // create memblock for transfer
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) dk::UniqueMemBlock memBlock =
.create (); dk::MemBlockMaker{s_device, align (size, DK_MEMBLOCK_ALIGNMENT)}
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
.create ();
// decompress into transfer memblock
auto const decoded = auto const decoded =
ZSTD_decompress (memBlock.getCpuAddr (), size, buffer.data (), st.st_size); ZSTD_decompress (memBlock.getCpuAddr (), size, buffer.data (), st.st_size);
if (ZSTD_isError (decoded)) if (ZSTD_isError (decoded))
@ -468,6 +569,7 @@ void imgui::deko3d::newFrame ()
std::abort (); std::abort ();
} }
// initialize deko3d logo texture image layout
dk::ImageLayout layout; dk::ImageLayout layout;
dk::ImageLayoutMaker{s_device} dk::ImageLayoutMaker{s_device}
.setFlags (0) .setFlags (0)
@ -475,32 +577,41 @@ void imgui::deko3d::newFrame ()
.setDimensions (LOGO_WIDTH, LOGO_HEIGHT) .setDimensions (LOGO_WIDTH, LOGO_HEIGHT)
.initialize (layout); .initialize (layout);
// initialize deko3d logo texture image descriptor
auto const offset = align (width * height, DK_IMAGE_LINEAR_STRIDE_ALIGNMENT); auto const offset = align (width * height, DK_IMAGE_LINEAR_STRIDE_ALIGNMENT);
s_logoTexture.initialize (layout, s_imageMemBlock, offset); s_logoTexture.initialize (layout, s_imageMemBlock, offset);
s_imageDescriptors[1].initialize (s_logoTexture); s_imageDescriptors[1].initialize (s_logoTexture);
// copy deko3d logo texture to image view
dk::ImageView imageView{s_logoTexture}; dk::ImageView imageView{s_logoTexture};
cmdBuf.copyBufferToImage ( cmdBuf.copyBufferToImage (
{memBlock.getGpuAddr ()}, imageView, {0, 0, 0, LOGO_WIDTH, LOGO_HEIGHT, 1}); {memBlock.getGpuAddr ()}, imageView, {0, 0, 0, LOGO_WIDTH, LOGO_HEIGHT, 1});
// submit commands to transfer deko3d logo texture
s_queue.submitCommands (cmdBuf.finishList ()); s_queue.submitCommands (cmdBuf.finishList ());
// wait for commands to complete before releasing memblocks
s_queue.waitIdle (); s_queue.waitIdle ();
} }
// reset command buffer
cmdBuf.clear (); cmdBuf.clear ();
} }
void imgui::deko3d::render () void imgui::deko3d::render ()
{ {
// get ImGui draw data
auto const drawData = ImGui::GetDrawData (); auto const drawData = ImGui::GetDrawData ();
if (drawData->CmdListsCount <= 0) if (drawData->CmdListsCount <= 0)
return; return;
// get framebuffer dimensions
unsigned width = drawData->DisplaySize.x * drawData->FramebufferScale.x; unsigned width = drawData->DisplaySize.x * drawData->FramebufferScale.x;
unsigned height = drawData->DisplaySize.y * drawData->FramebufferScale.y; unsigned height = drawData->DisplaySize.y * drawData->FramebufferScale.y;
if (width <= 0 || height <= 0) if (width <= 0 || height <= 0)
return; return;
// check if we need to rebuild the swapchain
if (width != s_width || height != s_height) if (width != s_width || height != s_height)
{ {
s_width = width; s_width = width;
@ -510,9 +621,11 @@ void imgui::deko3d::render ()
rebuildSwapchain (width, height); rebuildSwapchain (width, height);
} }
// get image from queue
auto const slot = s_queue.acquireImage (s_swapchain); auto const slot = s_queue.acquireImage (s_swapchain);
s_cmdBuf[slot].clear (); s_cmdBuf[slot].clear ();
// bind frame/depth buffers and clear them
dk::ImageView colorTarget{s_frameBuffers[slot]}; dk::ImageView colorTarget{s_frameBuffers[slot]};
dk::ImageView depthTarget{s_depthBuffer}; dk::ImageView depthTarget{s_depthBuffer};
s_cmdBuf[slot].bindRenderTargets (&colorTarget, &depthTarget); s_cmdBuf[slot].bindRenderTargets (&colorTarget, &depthTarget);
@ -520,21 +633,22 @@ void imgui::deko3d::render ()
s_cmdBuf[slot].clearDepthStencil (true, 1.0f, 0xFF, 0); s_cmdBuf[slot].clearDepthStencil (true, 1.0f, 0xFF, 0);
s_queue.submitCommands (s_cmdBuf[slot].finishList ()); s_queue.submitCommands (s_cmdBuf[slot].finishList ());
// Setup desired render state // setup desired render state
auto const setupCmd = setupRenderState (slot, drawData, width, height); auto const setupCmd = setupRenderState (slot, drawData, width, height);
s_queue.submitCommands (setupCmd); s_queue.submitCommands (setupCmd);
// start with bogus descriptor binding so it'll be updated before first draw call
s_boundDescriptor = ~static_cast<std::uintptr_t> (0); s_boundDescriptor = ~static_cast<std::uintptr_t> (0);
// Will project scissor/clipping rectangles into framebuffer space // will project scissor/clipping rectangles into framebuffer space
// (0,0) unless using multi-viewports // (0,0) unless using multi-viewports
auto const clipOff = drawData->DisplayPos; auto const clipOff = drawData->DisplayPos;
// (1,1) unless using retina display which are often (2,2) // (1,1) unless using retina display which are often (2,2)
auto const clipScale = drawData->FramebufferScale; auto const clipScale = drawData->FramebufferScale;
// check if we need to grow vertex data memblock
if (s_vtxMemBlock[slot].getSize () < drawData->TotalVtxCount * sizeof (ImDrawVert)) if (s_vtxMemBlock[slot].getSize () < drawData->TotalVtxCount * sizeof (ImDrawVert))
{ {
s_vtxMemBlock[slot] = nullptr;
s_vtxMemBlock[slot] = s_vtxMemBlock[slot] =
dk::MemBlockMaker{s_device, dk::MemBlockMaker{s_device,
align (drawData->TotalVtxCount * sizeof (ImDrawVert), DK_MEMBLOCK_ALIGNMENT)} align (drawData->TotalVtxCount * sizeof (ImDrawVert), DK_MEMBLOCK_ALIGNMENT)}
@ -542,9 +656,9 @@ void imgui::deko3d::render ()
.create (); .create ();
} }
// check if we need to grow index data memblock
if (s_idxMemBlock[slot].getSize () < drawData->TotalIdxCount * sizeof (ImDrawIdx)) if (s_idxMemBlock[slot].getSize () < drawData->TotalIdxCount * sizeof (ImDrawIdx))
{ {
s_idxMemBlock[slot] = nullptr;
s_idxMemBlock[slot] = s_idxMemBlock[slot] =
dk::MemBlockMaker{s_device, dk::MemBlockMaker{s_device,
align (drawData->TotalIdxCount * sizeof (ImDrawIdx), DK_MEMBLOCK_ALIGNMENT)} align (drawData->TotalIdxCount * sizeof (ImDrawIdx), DK_MEMBLOCK_ALIGNMENT)}
@ -552,20 +666,24 @@ void imgui::deko3d::render ()
.create (); .create ();
} }
// get base cpu addresses
auto const cpuVtx = static_cast<std::uint8_t *> (s_vtxMemBlock[slot].getCpuAddr ()); auto const cpuVtx = static_cast<std::uint8_t *> (s_vtxMemBlock[slot].getCpuAddr ());
auto const cpuIdx = static_cast<std::uint8_t *> (s_idxMemBlock[slot].getCpuAddr ()); auto const cpuIdx = static_cast<std::uint8_t *> (s_idxMemBlock[slot].getCpuAddr ());
// get base gpu addresses
auto const gpuVtx = s_vtxMemBlock[slot].getGpuAddr (); auto const gpuVtx = s_vtxMemBlock[slot].getGpuAddr ();
auto const gpuIdx = s_idxMemBlock[slot].getGpuAddr (); auto const gpuIdx = s_idxMemBlock[slot].getGpuAddr ();
// get memblock sizes
auto const sizeVtx = s_vtxMemBlock[slot].getSize (); auto const sizeVtx = s_vtxMemBlock[slot].getSize ();
auto const sizeIdx = s_idxMemBlock[slot].getSize (); auto const sizeIdx = s_idxMemBlock[slot].getSize ();
// bind vertex/index data memblocks
static_assert (sizeof (ImDrawIdx) == 2); static_assert (sizeof (ImDrawIdx) == 2);
s_cmdBuf[slot].bindVtxBuffer (0, gpuVtx, sizeVtx); s_cmdBuf[slot].bindVtxBuffer (0, gpuVtx, sizeVtx);
s_cmdBuf[slot].bindIdxBuffer (DkIdxFormat_Uint16, gpuIdx); s_cmdBuf[slot].bindIdxBuffer (DkIdxFormat_Uint16, gpuIdx);
// Render command lists // render command lists
std::size_t offsetVtx = 0; std::size_t offsetVtx = 0;
std::size_t offsetIdx = 0; std::size_t offsetIdx = 0;
for (int i = 0; i < drawData->CmdListsCount; ++i) for (int i = 0; i < drawData->CmdListsCount; ++i)
@ -575,6 +693,7 @@ void imgui::deko3d::render ()
auto const vtxSize = cmdList.VtxBuffer.Size * sizeof (ImDrawVert); auto const vtxSize = cmdList.VtxBuffer.Size * sizeof (ImDrawVert);
auto const idxSize = cmdList.IdxBuffer.Size * sizeof (ImDrawIdx); auto const idxSize = cmdList.IdxBuffer.Size * sizeof (ImDrawIdx);
// double check that we don't overrun vertex data memblock
if (sizeVtx - offsetVtx < vtxSize) if (sizeVtx - offsetVtx < vtxSize)
{ {
std::fprintf (stderr, "Not enough vertex buffer\n"); std::fprintf (stderr, "Not enough vertex buffer\n");
@ -582,6 +701,7 @@ void imgui::deko3d::render ()
continue; continue;
} }
// double check that we don't overrun index data memblock
if (sizeIdx - offsetIdx < idxSize) if (sizeIdx - offsetIdx < idxSize)
{ {
std::fprintf (stderr, "Not enough index buffer\n"); std::fprintf (stderr, "Not enough index buffer\n");
@ -589,6 +709,7 @@ void imgui::deko3d::render ()
continue; continue;
} }
// copy vertex/index data into memblocks
std::memcpy (cpuVtx + offsetVtx, cmdList.VtxBuffer.Data, vtxSize); std::memcpy (cpuVtx + offsetVtx, cmdList.VtxBuffer.Data, vtxSize);
std::memcpy (cpuIdx + offsetIdx, cmdList.IdxBuffer.Data, idxSize); std::memcpy (cpuIdx + offsetIdx, cmdList.IdxBuffer.Data, idxSize);
@ -596,9 +717,10 @@ void imgui::deko3d::render ()
{ {
if (cmd.UserCallback) if (cmd.UserCallback)
{ {
// submit commands to preserve ordering
s_queue.submitCommands (s_cmdBuf[slot].finishList ()); s_queue.submitCommands (s_cmdBuf[slot].finishList ());
// User callback, registered via ImDrawList::AddCallback() // user callback, registered via ImDrawList::AddCallback()
// (ImDrawCallback_ResetRenderState is a special callback value used by the user to // (ImDrawCallback_ResetRenderState is a special callback value used by the user to
// request the renderer to reset render state.) // request the renderer to reset render state.)
if (cmd.UserCallback == ImDrawCallback_ResetRenderState) if (cmd.UserCallback == ImDrawCallback_ResetRenderState)
@ -608,53 +730,65 @@ void imgui::deko3d::render ()
} }
else else
{ {
// Project scissor/clipping rectangles into framebuffer space // project scissor/clipping rectangles into framebuffer space
ImVec4 clip; ImVec4 clip;
clip.x = (cmd.ClipRect.x - clipOff.x) * clipScale.x; clip.x = (cmd.ClipRect.x - clipOff.x) * clipScale.x;
clip.y = (cmd.ClipRect.y - clipOff.y) * clipScale.y; clip.y = (cmd.ClipRect.y - clipOff.y) * clipScale.y;
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 (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;
// 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)
{ {
if (clip.x < 0.0f) s_boundDescriptor = descriptor;
clip.x = 0.0f;
if (clip.y < 0.0f)
clip.y = 0.0f;
s_cmdBuf[slot].setScissors ( // bind the new texture
0, DkScissor{clip.x, clip.y, clip.z - clip.x, clip.w - clip.y}); s_cmdBuf[slot].bindTextures (
DkStage_Fragment, 0, dkMakeTextureHandle (descriptor, 0));
auto const descriptor = reinterpret_cast<std::uintptr_t> (cmd.TextureId); // check if this is the font texture atlas image descriptor
if (descriptor >= MAX_IMAGES) FragUBO fragUBO;
continue; fragUBO.font = (descriptor == 0);
if (descriptor != s_boundDescriptor) // update fragment shader UBO
{ s_cmdBuf[slot].pushConstants (
s_boundDescriptor = descriptor; s_uboMemBlock.getGpuAddr () +
align (sizeof (VertUBO), DK_UNIFORM_BUF_ALIGNMENT),
s_cmdBuf[slot].bindTextures ( align (sizeof (FragUBO), DK_UNIFORM_BUF_ALIGNMENT),
DkStage_Fragment, 0, dkMakeTextureHandle (descriptor, 0)); 0,
sizeof (FragUBO),
FragUBO fragUBO; &fragUBO);
fragUBO.font = (descriptor == 0);
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);
}
s_cmdBuf[slot].drawIndexed (DkPrimitive_Triangles,
cmd.ElemCount,
1,
cmd.IdxOffset + offsetIdx / sizeof (ImDrawIdx),
cmd.VtxOffset + offsetVtx / sizeof (ImDrawVert),
0);
} }
// draw the draw list
s_cmdBuf[slot].drawIndexed (DkPrimitive_Triangles,
cmd.ElemCount,
1,
cmd.IdxOffset + offsetIdx / sizeof (ImDrawIdx),
cmd.VtxOffset + offsetVtx / sizeof (ImDrawVert),
0);
} }
} }
@ -662,10 +796,14 @@ void imgui::deko3d::render ()
offsetIdx += idxSize; offsetIdx += idxSize;
} }
// wait for fragments to be completed before discarding depth/stencil buffer
s_cmdBuf[slot].barrier (DkBarrier_Fragments, 0); s_cmdBuf[slot].barrier (DkBarrier_Fragments, 0);
s_cmdBuf[slot].discardDepthStencil (); s_cmdBuf[slot].discardDepthStencil ();
// submit final commands
s_queue.submitCommands (s_cmdBuf[slot].finishList ()); s_queue.submitCommands (s_cmdBuf[slot].finishList ());
// present image
s_queue.presentImage (s_swapchain, slot); s_queue.presentImage (s_swapchain, slot);
} }

View File

@ -33,6 +33,7 @@ layout (location = 0) out vec4 outColor;
void main() void main()
{ {
// font texture is single-channel (alpha)
if (ubo.font != 0) if (ubo.font != 0)
outColor = vtxColor * vec4 (vec3 (1.0), texture (tex, vtxUv).r); outColor = vtxColor * vec4 (vec3 (1.0), texture (tex, vtxUv).r);
else else

View File

@ -23,6 +23,7 @@
#include "imgui.h" #include "imgui.h"
#include "fs.h" #include "fs.h"
#include "platform.h"
#include <switch.h> #include <switch.h>
@ -35,23 +36,32 @@ using namespace std::chrono_literals;
namespace namespace
{ {
/// \brief Font atlas cache file
constexpr auto FONT_ATLAS_BIN = "ftpd-font.bin"; constexpr auto FONT_ATLAS_BIN = "ftpd-font.bin";
bool s_mouseJustPressed[IM_ARRAYSIZE (ImGuiIO::MouseDown)]; /// \brief Last mouse update timestamp
std::chrono::steady_clock::time_point s_lastMouseUpdate;
std::chrono::high_resolution_clock::time_point s_lastMouseUpdate;
/// \brief Whether application is focused
bool s_focused = true; bool s_focused = true;
float s_width = 1280.0f;
/// \brief Framebuffer width
float s_width = 1280.0f;
/// \brief Framebuffer height
float s_height = 720.0f; float s_height = 720.0f;
/// \brief Whether to show mouse
float s_showMouse = false; float s_showMouse = false;
/// \brief Mouse position
ImVec2 s_mousePos = ImVec2 (0.0f, 0.0f); ImVec2 s_mousePos = ImVec2 (0.0f, 0.0f);
/// \brief Clipboard
std::string s_clipboard; std::string s_clipboard;
/// \brief Applet hook cookie
AppletHookCookie s_appletHookCookie; AppletHookCookie s_appletHookCookie;
/// \brief System font glyph ranges
ImWchar const nxFontRanges[] = { ImWchar const nxFontRanges[] = {
// clang-format off // clang-format off
0x0020, 0x007e, 0x00a0, 0x017f, 0x0192, 0x0192, 0x01c0, 0x01c0, 0x0020, 0x007e, 0x00a0, 0x017f, 0x0192, 0x0192, 0x01c0, 0x01c0,
@ -1189,12 +1199,16 @@ ImWchar const nxFontRanges[] = {
// clang-format on // clang-format on
}; };
/// \brief Handle applet hook
/// \param hook_ Callback reason
/// \param param_ User param
void handleAppletHook (AppletHookType const hook_, void *const param_) void handleAppletHook (AppletHookType const hook_, void *const param_)
{ {
(void)param_; (void)param_;
switch (hook_) switch (hook_)
{ {
case AppletHookType_OnFocusState: case AppletHookType_OnFocusState:
// grab focus state
s_focused = (appletGetFocusState () == AppletFocusState_Focused); s_focused = (appletGetFocusState () == AppletFocusState_Focused);
break; break;
@ -1203,15 +1217,18 @@ void handleAppletHook (AppletHookType const hook_, void *const param_)
{ {
default: default:
case AppletOperationMode_Handheld: case AppletOperationMode_Handheld:
// use handheld mode resolution (720p)
s_width = 1280.0f; s_width = 1280.0f;
s_height = 720.0f; s_height = 720.0f;
break; break;
case AppletOperationMode_Docked: case AppletOperationMode_Docked:
// use docked mode resolution (1080p)
#if 0 #if 0
s_width = 1920.0f; s_width = 1920.0f;
s_height = 1080.0f; s_height = 1080.0f;
#else #else
/// \todo check if we'd rather use framebuffer scale
s_width = 1280.0f; s_width = 1280.0f;
s_height = 720.0f; s_height = 720.0f;
#endif #endif
@ -1224,30 +1241,43 @@ void handleAppletHook (AppletHookType const hook_, void *const param_)
} }
} }
/// \brief Get clipboard text callback
/// \param userData_ User data
char const *getClipboardText (void *const userData_) char const *getClipboardText (void *const userData_)
{ {
(void)userData_; (void)userData_;
return s_clipboard.c_str (); return s_clipboard.c_str ();
} }
/// \brief Set clipboard text callback
/// \param userData_ User data
/// \param text_ Clipboard text
void setClipboardText (void *const userData_, char const *const text_) void setClipboardText (void *const userData_, char const *const text_)
{ {
(void)userData_; (void)userData_;
s_clipboard = text_; s_clipboard = text_;
} }
/// \brief Move mouse cursor
/// \param io_ ImGui IO
/// \param pos_ New mouse position
/// \param force_ Whether to ignore prior mouse position
void moveMouse (ImGuiIO &io_, ImVec2 const &pos_, bool const force_ = false) void moveMouse (ImGuiIO &io_, ImVec2 const &pos_, bool const force_ = false)
{ {
auto const now = std::chrono::high_resolution_clock::now (); // get update timestamp
auto const now = std::chrono::steady_clock::now ();
// check if the mouse position has been updated
if (!force_ && pos_.x == s_mousePos.x && pos_.y == s_mousePos.y) if (!force_ && pos_.x == s_mousePos.x && pos_.y == s_mousePos.y)
{ {
// stop displaying mouse cursor after inactivity timeout
if (now - s_lastMouseUpdate > 1s) if (now - s_lastMouseUpdate > 1s)
s_showMouse = false; s_showMouse = false;
return; return;
} }
// update mouse position
s_showMouse = true; s_showMouse = true;
s_lastMouseUpdate = now; s_lastMouseUpdate = now;
s_mousePos = pos_; s_mousePos = pos_;
@ -1255,27 +1285,27 @@ void moveMouse (ImGuiIO &io_, ImVec2 const &pos_, bool const force_ = false)
io_.MousePos = s_mousePos; io_.MousePos = s_mousePos;
} }
/// \brief Update mouse buttons
/// \param io_ ImGui IO
void updateMouseButtons (ImGuiIO &io_) void updateMouseButtons (ImGuiIO &io_)
{ {
// read mouse buttons
auto const buttons = hidMouseButtonsHeld (); auto const buttons = hidMouseButtonsHeld ();
for (std::size_t i = 0; i < IM_ARRAYSIZE (io_.MouseDown); ++i) for (std::size_t i = 0; i < IM_ARRAYSIZE (io_.MouseDown); ++i)
{ {
// If a mouse press event came, always pass it as "mouse held this frame", so we don't miss io_.MouseDown[i] = buttons & BIT (i);
// click-release events that are shorter than 1 frame.
io_.MouseDown[i] = s_mouseJustPressed[i] || (buttons & BIT (i));
s_mouseJustPressed[i] = false;
// force mouse cursor to show on click
if (io_.MouseDown[i]) if (io_.MouseDown[i])
moveMouse (io_, s_mousePos, true); moveMouse (io_, s_mousePos, true);
} }
} }
/// \brief Update mouse position
/// \param io_ ImGui IO
void updateMousePos (ImGuiIO &io_) void updateMousePos (ImGuiIO &io_)
{ {
if (!s_focused)
return;
MousePosition pos; MousePosition pos;
hidMouseRead (&pos); hidMouseRead (&pos);
@ -1286,25 +1316,30 @@ void updateMousePos (ImGuiIO &io_)
io_, ImVec2 (s_mousePos.x + 2.0f * pos.velocityX, s_mousePos.y + 2.0f * pos.velocityY)); io_, ImVec2 (s_mousePos.x + 2.0f * pos.velocityX, s_mousePos.y + 2.0f * pos.velocityY));
} }
/// \brief Update touch position
/// \param io_ ImGui IO
void updateTouch (ImGuiIO &io_) void updateTouch (ImGuiIO &io_)
{ {
if (!s_focused) // read touch positions
return;
auto const touchCount = hidTouchCount (); auto const touchCount = hidTouchCount ();
if (touchCount < 1) if (touchCount < 1)
return; return;
// use first touch position
touchPosition pos; touchPosition pos;
hidTouchRead (&pos, 0); hidTouchRead (&pos, 0);
// set mouse position to touch point; force hide mouse cursor
moveMouse (io_, ImVec2 (pos.px, pos.py)); moveMouse (io_, ImVec2 (pos.px, pos.py));
io_.MouseDown[0] = true; io_.MouseDown[0] = true;
s_showMouse = false; s_showMouse = false;
} }
/// \brief Update gamepad inputs
/// \param io_ ImGui IO
void updateGamepads (ImGuiIO &io_) void updateGamepads (ImGuiIO &io_)
{ {
// clear navigation inputs
std::memset (io_.NavInputs, 0, sizeof (io_.NavInputs)); std::memset (io_.NavInputs, 0, sizeof (io_.NavInputs));
auto const buttonMapping = { auto const buttonMapping = {
@ -1322,6 +1357,7 @@ void updateGamepads (ImGuiIO &io_)
std::make_pair (KEY_DLEFT, ImGuiNavInput_DpadLeft), std::make_pair (KEY_DLEFT, ImGuiNavInput_DpadLeft),
}; };
// read buttons from primary controller
auto const keys = hidKeysHeld (CONTROLLER_P1_AUTO); auto const keys = hidKeysHeld (CONTROLLER_P1_AUTO);
for (auto const &[in, out] : buttonMapping) for (auto const &[in, out] : buttonMapping)
{ {
@ -1329,7 +1365,7 @@ void updateGamepads (ImGuiIO &io_)
io_.NavInputs[out] = 1.0f; io_.NavInputs[out] = 1.0f;
} }
// Use ZR/ZL as Mouse0/Mouse1, respectively // use ZR/ZL as left-click/right-click, respectively
if (keys & KEY_ZR) if (keys & KEY_ZR)
{ {
io_.MouseDown[0] = true; io_.MouseDown[0] = true;
@ -1341,6 +1377,7 @@ void updateGamepads (ImGuiIO &io_)
moveMouse (io_, s_mousePos, true); moveMouse (io_, s_mousePos, true);
} }
// update joystick
JoystickPosition js; JoystickPosition js;
auto const analogMapping = { auto const analogMapping = {
std::make_tuple (std::ref (js.dx), ImGuiNavInput_LStickLeft, -0.3f, -0.9f), std::make_tuple (std::ref (js.dx), ImGuiNavInput_LStickLeft, -0.3f, -0.9f),
@ -1349,15 +1386,15 @@ void updateGamepads (ImGuiIO &io_)
std::make_tuple (std::ref (js.dy), ImGuiNavInput_LStickDown, -0.3f, -0.9f), std::make_tuple (std::ref (js.dy), ImGuiNavInput_LStickDown, -0.3f, -0.9f),
}; };
// read left joystick from primary controller
hidJoystickRead (&js, CONTROLLER_P1_AUTO, JOYSTICK_LEFT); hidJoystickRead (&js, CONTROLLER_P1_AUTO, JOYSTICK_LEFT);
for (auto const &[in, out, min, max] : analogMapping) for (auto const &[in, out, min, max] : analogMapping)
{ {
auto const value = in / static_cast<float> (JOYSTICK_MAX); auto const value = in / static_cast<float> (JOYSTICK_MAX);
auto const v = std::min (1.0f, (value - min) / (max - min)); io_.NavInputs[out] = std::clamp ((value - min) / (max - min), 0.0f, 1.0f);
io_.NavInputs[out] = std::max (io_.NavInputs[out], v);
} }
// Use right stick as mouse // use right stick as mouse
auto scale = 5.0f; auto scale = 5.0f;
if (keys & KEY_L) if (keys & KEY_L)
scale = 1.0f; scale = 1.0f;
@ -1365,37 +1402,41 @@ void updateGamepads (ImGuiIO &io_)
scale = 20.0f; scale = 20.0f;
hidJoystickRead (&js, CONTROLLER_P1_AUTO, JOYSTICK_RIGHT); hidJoystickRead (&js, CONTROLLER_P1_AUTO, JOYSTICK_RIGHT);
// move mouse
moveMouse (io_, moveMouse (io_,
ImVec2 (s_mousePos.x + js.dx / static_cast<float> (JOYSTICK_MAX) * scale, ImVec2 (s_mousePos.x + js.dx / static_cast<float> (JOYSTICK_MAX) * scale,
s_mousePos.y - js.dy / static_cast<float> (JOYSTICK_MAX) * scale)); s_mousePos.y - js.dy / static_cast<float> (JOYSTICK_MAX) * scale));
} }
/// \brief Update keyboard inputs
/// \param io_ ImGui IO
void updateKeyboard (ImGuiIO &io_) void updateKeyboard (ImGuiIO &io_)
{ {
io_.KeyCtrl = io_.KeyCtrl =
hidKeyboardModifierHeld (static_cast<HidKeyboardModifier> (KBD_MOD_LCTRL | KBD_MOD_RCTRL)); hidKeyboardModifierHeld (static_cast<HidKeyboardModifier> (KBD_MOD_LCTRL | KBD_MOD_RCTRL));
io_.KeyShift = hidKeyboardModifierHeld ( io_.KeyShift = hidKeyboardModifierHeld (
static_cast<HidKeyboardModifier> (KBD_MOD_LSHIFT | KBD_MOD_RSHIFT)); static_cast<HidKeyboardModifier> (KBD_MOD_LSHIFT | KBD_MOD_RSHIFT));
io_.KeyAlt = io_.KeyAlt =
hidKeyboardModifierHeld (static_cast<HidKeyboardModifier> (KBD_MOD_LALT | KBD_MOD_RALT)); hidKeyboardModifierHeld (static_cast<HidKeyboardModifier> (KBD_MOD_LALT | KBD_MOD_RALT));
io_.KeySuper = io_.KeySuper =
hidKeyboardModifierHeld (static_cast<HidKeyboardModifier> (KBD_MOD_LMETA | KBD_MOD_RMETA)); hidKeyboardModifierHeld (static_cast<HidKeyboardModifier> (KBD_MOD_LMETA | KBD_MOD_RMETA));
for (int i = 0; i < 256; ++i) for (int i = 0; i < 256; ++i)
io_.KeysDown[i] = hidKeyboardHeld (static_cast<HidKeyboardScancode> (i)); io_.KeysDown[i] = hidKeyboardHeld (static_cast<HidKeyboardScancode> (i));
if (!io_.WantTextInput)
return;
// io_.AddInputCharacter (c);
} }
/// \brief Load font atlas cache from disk
bool loadFontAtlas () bool loadFontAtlas ()
{ {
// open font atlas
fs::File fp; fs::File fp;
if (!fp.open (FONT_ATLAS_BIN)) if (!fp.open (FONT_ATLAS_BIN))
return false; return false;
// initialize font atlas
auto const atlas = ImGui::GetIO ().Fonts; auto const atlas = ImGui::GetIO ().Fonts;
atlas->Clear (); atlas->Clear ();
atlas->TexWidth = fp.read<std::uint16_t> (); atlas->TexWidth = fp.read<std::uint16_t> ();
@ -1405,9 +1446,11 @@ bool loadFontAtlas ()
atlas->TexPixelsAlpha8 = atlas->TexPixelsAlpha8 =
reinterpret_cast<unsigned char *> (IM_ALLOC (atlas->TexWidth * atlas->TexHeight)); reinterpret_cast<unsigned char *> (IM_ALLOC (atlas->TexWidth * atlas->TexHeight));
// read pixel data
if (!fp.readAll (atlas->TexPixelsAlpha8, atlas->TexWidth * atlas->TexHeight)) if (!fp.readAll (atlas->TexPixelsAlpha8, atlas->TexWidth * atlas->TexHeight))
return false; return false;
// initialize font config
ImFontConfig config; ImFontConfig config;
config.FontData = nullptr; config.FontData = nullptr;
config.FontDataSize = 0; config.FontDataSize = 0;
@ -1428,23 +1471,28 @@ bool loadFontAtlas ()
config.EllipsisChar = 0x2026; config.EllipsisChar = 0x2026;
std::memset (config.Name, 0, sizeof (config.Name)); std::memset (config.Name, 0, sizeof (config.Name));
// create font
auto const font = IM_NEW (ImFont); auto const font = IM_NEW (ImFont);
config.DstFont = font; config.DstFont = font;
// add config and font to atlas
atlas->ConfigData.push_back (config); atlas->ConfigData.push_back (config);
atlas->Fonts.push_back (font); atlas->Fonts.push_back (font);
atlas->CustomRectIds[0] = atlas->AddCustomRectRegular (0x80000000, 108 * 2 + 1, 27); atlas->CustomRectIds[0] = atlas->AddCustomRectRegular (0x80000000, 108 * 2 + 1, 27);
atlas->CustomRects[0].X = 0; atlas->CustomRects[0].X = 0;
atlas->CustomRects[0].Y = 0; atlas->CustomRects[0].Y = 0;
// read some font metrics
font->FallbackAdvanceX = fp.read<float> (); font->FallbackAdvanceX = fp.read<float> ();
font->FontSize = fp.read<float> (); font->FontSize = fp.read<float> ();
// decode glyph metadata
auto const glyphCount = fp.read<std::uint16_t> (); auto const glyphCount = fp.read<std::uint16_t> ();
for (unsigned i = 0; i < glyphCount; ++i) for (unsigned i = 0; i < glyphCount; ++i)
{ {
ImFontGlyph glyph; ImFontGlyph glyph;
// read glyph metadata
glyph.Codepoint = fp.read<ImWchar> (); glyph.Codepoint = fp.read<ImWchar> ();
glyph.AdvanceX = fp.read<float> (); glyph.AdvanceX = fp.read<float> ();
glyph.X0 = fp.read<float> (); glyph.X0 = fp.read<float> ();
@ -1456,17 +1504,21 @@ bool loadFontAtlas ()
glyph.U1 = fp.read<float> (); glyph.U1 = fp.read<float> ();
glyph.V1 = fp.read<float> (); glyph.V1 = fp.read<float> ();
// add glyph to font
font->Glyphs.push_back (glyph); font->Glyphs.push_back (glyph);
font->MetricsTotalSurface += font->MetricsTotalSurface +=
static_cast<int> ((glyph.U1 - glyph.U0) * atlas->TexWidth + 1.99f) * static_cast<int> ((glyph.U1 - glyph.U0) * atlas->TexWidth + 1.99f) *
static_cast<int> ((glyph.V1 - glyph.V0) * atlas->TexHeight + 1.99f); static_cast<int> ((glyph.V1 - glyph.V0) * atlas->TexHeight + 1.99f);
} }
// build font lookup table
font->BuildLookupTable (); font->BuildLookupTable ();
// read display offsets
font->DisplayOffset.x = fp.read<float> (); font->DisplayOffset.x = fp.read<float> ();
font->DisplayOffset.y = fp.read<float> (); font->DisplayOffset.y = fp.read<float> ();
// finalize font atlas
font->ContainerAtlas = atlas; font->ContainerAtlas = atlas;
font->ConfigData = &atlas->ConfigData[0]; font->ConfigData = &atlas->ConfigData[0];
font->ConfigDataCount = 1; font->ConfigDataCount = 1;
@ -1479,6 +1531,7 @@ bool loadFontAtlas ()
return true; return true;
} }
/// \brief Store font atlas cache to disk
bool saveFontAtlas () bool saveFontAtlas ()
{ {
auto const atlas = ImGui::GetIO ().Fonts; auto const atlas = ImGui::GetIO ().Fonts;
@ -1488,24 +1541,30 @@ bool saveFontAtlas ()
int height; int height;
atlas->GetTexDataAsAlpha8 (&pixels, &width, &height); atlas->GetTexDataAsAlpha8 (&pixels, &width, &height);
// create font atlas cache
fs::File fp; fs::File fp;
if (!fp.open (FONT_ATLAS_BIN, "wb")) if (!fp.open (FONT_ATLAS_BIN, "wb"))
return false; return false;
// save atlas dimensions
fp.write<std::uint16_t> (width); fp.write<std::uint16_t> (width);
fp.write<std::uint16_t> (height); fp.write<std::uint16_t> (height);
// write pixel data
if (!fp.writeAll (pixels, width * height)) if (!fp.writeAll (pixels, width * height))
return false; return false;
auto const font = atlas->ConfigData[0].DstFont; auto const font = atlas->ConfigData[0].DstFont;
// write some font metrics
fp.write (font->FallbackAdvanceX); fp.write (font->FallbackAdvanceX);
fp.write (font->FontSize); fp.write (font->FontSize);
// encode glyph metadata
fp.write<std::uint16_t> (font->Glyphs.size ()); fp.write<std::uint16_t> (font->Glyphs.size ());
for (auto const &glyph : font->Glyphs) for (auto const &glyph : font->Glyphs)
{ {
// write glyph metadata
fp.write (glyph.Codepoint); fp.write (glyph.Codepoint);
fp.write (glyph.AdvanceX); fp.write (glyph.AdvanceX);
fp.write (glyph.X0); fp.write (glyph.X0);
@ -1518,6 +1577,7 @@ bool saveFontAtlas ()
fp.write (glyph.V1); fp.write (glyph.V1);
} }
// write remaining font metrics
fp.write (font->DisplayOffset.x); fp.write (font->DisplayOffset.x);
fp.write (font->DisplayOffset.y); fp.write (font->DisplayOffset.y);
fp.write (font->Ascent); fp.write (font->Ascent);
@ -1529,44 +1589,71 @@ bool saveFontAtlas ()
bool imgui::nx::init () bool imgui::nx::init ()
{ {
u64 languageCode; ImGuiIO &io = ImGui::GetIO ();
auto rc = setInitialize ();
if (R_FAILED (rc))
return false;
rc = setGetSystemLanguage (&languageCode); if (!loadFontAtlas ())
if (R_FAILED (rc))
{ {
// get system language
u64 languageCode;
auto rc = setInitialize ();
if (R_FAILED (rc))
return false;
rc = setGetSystemLanguage (&languageCode);
if (R_FAILED (rc))
{
setExit ();
return false;
}
setExit (); setExit ();
return false;
// get fonts for system language
std::vector<PlFontData> fonts (PlSharedFontType_Total);
s32 numFonts = 0;
rc = plGetSharedFont (languageCode, fonts.data (), fonts.size (), &numFonts);
if (R_FAILED (rc))
return false;
fonts.resize (numFonts);
// add fonts
ImFontConfig config;
config.MergeMode = false;
config.FontDataOwnedByAtlas = false;
for (auto const &font : fonts)
{
io.Fonts->AddFontFromMemoryTTF (font.address, font.size, 14.0f, &config, nxFontRanges);
config.MergeMode = true;
}
// build font atlas
io.Fonts->Flags |= ImFontAtlasFlags_NoPowerOfTwoHeight;
io.Fonts->Build ();
// save font atlas
saveFontAtlas ();
} }
setExit (); else
std::printf ("Loaded font atlas from disk\n");
std::vector<PlFontData> fonts (PlSharedFontType_Total);
s32 numFonts = 0;
rc = plGetSharedFont (languageCode, fonts.data (), fonts.size (), &numFonts);
if (R_FAILED (rc))
return false;
fonts.resize (numFonts);
// initialize applet hooks
appletSetFocusHandlingMode (AppletFocusHandlingMode_NoSuspend); appletSetFocusHandlingMode (AppletFocusHandlingMode_NoSuspend);
appletHook (&s_appletHookCookie, handleAppletHook, nullptr); appletHook (&s_appletHookCookie, handleAppletHook, nullptr);
handleAppletHook (AppletHookType_OnFocusState, nullptr); handleAppletHook (AppletHookType_OnFocusState, nullptr);
handleAppletHook (AppletHookType_OnOperationMode, nullptr); handleAppletHook (AppletHookType_OnOperationMode, nullptr);
ImGuiIO &io = ImGui::GetIO (); // disable imgui.ini file
io.IniFilename = nullptr; io.IniFilename = nullptr;
// setup config flags
io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen; io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
// setup platform backend
io.BackendFlags |= ImGuiBackendFlags_HasGamepad; io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
io.BackendPlatformName = "Switch";
io.BackendPlatformName = "switch"; // keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array.
// Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array.
io.KeyMap[ImGuiKey_Tab] = KBD_TAB; io.KeyMap[ImGuiKey_Tab] = KBD_TAB;
io.KeyMap[ImGuiKey_LeftArrow] = KBD_LEFT; io.KeyMap[ImGuiKey_LeftArrow] = KBD_LEFT;
io.KeyMap[ImGuiKey_RightArrow] = KBD_RIGHT; io.KeyMap[ImGuiKey_RightArrow] = KBD_RIGHT;
@ -1590,66 +1677,58 @@ bool imgui::nx::init ()
io.KeyMap[ImGuiKey_Y] = KBD_Y; io.KeyMap[ImGuiKey_Y] = KBD_Y;
io.KeyMap[ImGuiKey_Z] = KBD_Z; io.KeyMap[ImGuiKey_Z] = KBD_Z;
// initially disable mouse cursor
io.MouseDrawCursor = false; io.MouseDrawCursor = false;
// clipboard callbacks
io.SetClipboardTextFn = setClipboardText; io.SetClipboardTextFn = setClipboardText;
io.GetClipboardTextFn = getClipboardText; io.GetClipboardTextFn = getClipboardText;
io.ClipboardUserData = nullptr; io.ClipboardUserData = nullptr;
if (!loadFontAtlas ())
{
ImFontConfig config;
config.MergeMode = false;
config.FontDataOwnedByAtlas = false;
for (auto const &font : fonts)
{
io.Fonts->AddFontFromMemoryTTF (font.address, font.size, 14.0f, &config, nxFontRanges);
config.MergeMode = true;
}
io.Fonts->Flags |= ImFontAtlasFlags_NoPowerOfTwoHeight;
io.Fonts->Build ();
saveFontAtlas ();
}
else
std::printf ("Loaded font atlas from disk\n");
return true; return true;
} }
void imgui::nx::newFrame () void imgui::nx::newFrame ()
{ {
ImGuiIO &io = ImGui::GetIO (); ImGuiIO &io = ImGui::GetIO ();
// check that font was built
IM_ASSERT (io.Fonts->IsBuilt () && IM_ASSERT (io.Fonts->IsBuilt () &&
"Font atlas not built! It is generally built by the renderer back-end. Missing call " "Font atlas not built! It is generally built by the renderer back-end. Missing call "
"to renderer _NewFrame() function?"); "to renderer _NewFrame() function?");
// setup display metrics
io.DisplaySize = ImVec2 (s_width, s_height); io.DisplaySize = ImVec2 (s_width, s_height);
io.DisplayFramebufferScale = ImVec2 (1.0f, 1.0f); io.DisplayFramebufferScale = ImVec2 (1.0f, 1.0f);
// Setup time step // time step
static auto const start = std::chrono::high_resolution_clock::now (); static auto const start = platform::steady_clock::now ();
static auto prev = start; static auto prev = start;
auto const now = std::chrono::high_resolution_clock::now (); auto const now = platform::steady_clock::now ();
io.DeltaTime = std::chrono::duration<float> (now - prev).count (); io.DeltaTime = std::chrono::duration<float> (now - prev).count ();
prev = now; prev = now;
updateMouseButtons (io); if (s_focused)
updateMousePos (io); {
updateTouch (io); // update inputs
updateGamepads (io); updateMouseButtons (io);
updateKeyboard (io); updateMousePos (io);
updateTouch (io);
updateGamepads (io);
updateKeyboard (io);
}
// whether to draw mouse cursor
io.MouseDrawCursor = s_showMouse; io.MouseDrawCursor = s_showMouse;
// Clamp mouse to screen // clamp mouse to screen
s_mousePos.x = std::clamp (s_mousePos.x, 0.0f, s_width); s_mousePos.x = std::clamp (s_mousePos.x, 0.0f, s_width);
s_mousePos.y = std::clamp (s_mousePos.y, 0.0f, s_height); s_mousePos.y = std::clamp (s_mousePos.y, 0.0f, s_height);
} }
void imgui::nx::exit () void imgui::nx::exit ()
{ {
// deinitialize applet hooks
appletUnhook (&s_appletHookCookie); appletUnhook (&s_appletHookCookie);
} }

View File

@ -23,9 +23,11 @@
#include <unistd.h> #include <unistd.h>
#ifndef NDEBUG #ifndef NDEBUG
/// \brief nxlink socket fd
static int s_fd = -1; static int s_fd = -1;
#endif #endif
/// \brief Socket initialization configuration
static SocketInitConfig const s_socketInitConfig = { static SocketInitConfig const s_socketInitConfig = {
.bsdsockets_version = 1, .bsdsockets_version = 1,
@ -43,10 +45,13 @@ static SocketInitConfig const s_socketInitConfig = {
.bsd_service_type = BsdServiceType_User, .bsd_service_type = BsdServiceType_User,
}; };
/// \brief Number of FS sessions
u32 __nx_fs_num_sessions = 3; u32 __nx_fs_num_sessions = 3;
/// \brief Called before main ()
void userAppInit () void userAppInit ()
{ {
// disable immediate app close
appletLockExit (); appletLockExit ();
romfsInit (); romfsInit ();

View File

@ -32,14 +32,8 @@
bool platform::init () bool platform::init ()
{ {
IMGUI_CHECKVERSION ();
ImGui::CreateContext ();
if (!imgui::nx::init ()) if (!imgui::nx::init ())
{
ImGui::DestroyContext ();
return false; return false;
}
imgui::deko3d::init (); imgui::deko3d::init ();
@ -77,20 +71,22 @@ void platform::exit ()
{ {
imgui::nx::exit (); imgui::nx::exit ();
imgui::deko3d::exit (); imgui::deko3d::exit ();
ImGui::DestroyContext ();
} }
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
/// \brief Platform thread pimpl
class platform::Thread::privateData_t class platform::Thread::privateData_t
{ {
public: public:
privateData_t () = default; privateData_t () = default;
/// \brief Parameterized constructor
/// \param func_ Thread entry point
privateData_t (std::function<void ()> func_) : thread (func_) privateData_t (std::function<void ()> func_) : thread (func_)
{ {
} }
/// \brief Underlying thread
std::thread thread; std::thread thread;
}; };
@ -128,12 +124,16 @@ void platform::Thread::sleep (std::chrono::milliseconds const timeout_)
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
#define USE_STD_MUTEX 1 #define USE_STD_MUTEX 1
/// \brief Platform mutex pimpl
class platform::Mutex::privateData_t class platform::Mutex::privateData_t
{ {
public: public:
#if USE_STD_MUTEX #if USE_STD_MUTEX
/// \brief Underlying mutex
std::mutex mutex; std::mutex mutex;
#else #else
/// \brief Underlying mutex
::Mutex mutex; ::Mutex mutex;
#endif #endif
}; };

View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB