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
*.smdh
.gdb_history
build.*/
ftpd
romfs.3ds/*.t3x
romfs.switch/*.zst
romfs.switch/shaders/*.dksh
3ds/build
3ds/romfs/*.t3x
linux/build
linux/ftpd
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:
@$(MAKE) -f Makefile.switch nxlink
3dslink: 3dsx
@/opt/devkitpro/tools/bin/3dslink $(TARGET)-3ds.3dsx
3dslink:
@$(MAKE) -f Makefile.3ds 3dslink
format:
@clang-format -style=file -i $(filter-out \
include/imgui.h \
source/pc/imgui_impl_glfw.cpp \
source/pc/imgui_impl_glfw.h \
source/pc/imgui_impl_opengl3.cpp \
source/pc/imgui_impl_opengl3.h \
source/pc/KHR/khrplatform.h \
source/pc/glad.c \
source/pc/glad/glad.h \
source/linux/imgui_impl_glfw.cpp \
source/linux/imgui_impl_glfw.h \
source/linux/imgui_impl_opengl3.cpp \
source/linux/imgui_impl_opengl3.h \
source/linux/KHR/khrplatform.h \
source/linux/glad.c \
source/linux/glad/glad.h \
source/imgui/imgui.cpp \
source/imgui/imgui_demo.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))
release: release-3ds release-nro
@xz -c <$(TARGET)-3ds.3dsx >ftpd.3dsx.xz
@echo xz -c <$(TARGET)-3ds.cia >ftpd.cia.xz
@echo xz -c <$(TARGET)-nx.nro >ftpd.nro.xz
@xz -c <3ds/$(TARGET).3dsx >ftpd.3dsx.xz
@xz -c <3ds/$(TARGET).cia >ftpd.cia.xz
@xz -c <switch/$(TARGET).nro >ftpd.nro.xz
nro:
@$(MAKE) -f Makefile.switch all
@ -64,7 +64,7 @@ release-cia: release-3dsx
@$(MAKE) DEFINES=-NDEBUG -f Makefile.3ds cia
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
@$(MAKE) DEFINES=-DNDEBUG -f Makefile.3ds 3dsx
@$(MAKE) DEFINES=-DNDEBUG -f Makefile.3ds cia

View File

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

View File

@ -1,12 +1,12 @@
TARGET := ftpd
BUILD := build.linux
TARGET := linux/ftpd
BUILD := linux/build
CFILES := $(wildcard source/pc/*.c)
CFILES := $(wildcard source/linux/*.c)
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))
CPPFLAGS := -g -Wall -pthread -Iinclude -Isource/pc \
CPPFLAGS := -g -Wall -pthread -Iinclude -Isource/linux \
`pkg-config --cflags gl glfw3` \
-DSTATUS_STRING="\"ftpd v$(VERSION)\"" \
-DIMGUI_DISABLE_INCLUDE_IMCONFIG_H=1 \

View File

@ -42,13 +42,13 @@ APP_AUTHOR := mtheall, TuxSH, WinterMute
ICON := meta/ftpd.jpg
APP_VERSION := $(VERSION)
TARGET := $(notdir $(CURDIR))-nx
BUILD := build.switch
SOURCES := source source/imgui source/nx
TARGET := switch/$(notdir $(CURDIR))
BUILD := switch/build
SOURCES := source source/imgui source/switch
DATA := data
INCLUDES := include
GRAPHICS := gfx.switch
ROMFS := romfs.switch
GRAPHICS := switch/gfx
ROMFS := switch/romfs
# Output folders for autogenerated files in romfs
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
# rules for different file extensions
#---------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR)))
ifneq ($(TOPDIR)/$(BUILD),$(CURDIR))
#---------------------------------------------------------------------------------
export OUTPUT := $(CURDIR)/$(TARGET)
@ -193,7 +193,7 @@ all: $(ROMFS_TARGETS) | $(BUILD)
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile.switch
nxlink: all
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile.switch nxlink
@nxlink -s $(OUTPUT).nro
$(BUILD):
@mkdir -p $@
@ -260,9 +260,6 @@ ifeq ($(strip $(APP_JSON)),)
all : $(OUTPUT).nro
nxlink: $(OUTPUT).nro
@nxlink -s $(OUTPUT).nro
ifeq ($(strip $(NO_NACP)),)
$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp $(ROMFS_DEPS)
else

View File

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

View File

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

View File

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

View File

@ -23,29 +23,66 @@
#include <cstddef>
#include <memory>
/// \brief I/O buffer
/// [unusable][usedArea][freeArea]
class IOBuffer
{
public:
~IOBuffer ();
/// \brief Parameterized constructor
/// \param size_ Buffer size
IOBuffer (std::size_t size_);
/// \brief Get pointer to writable area
char *freeArea () const;
/// \brief Get size of writable area
std::size_t freeSize () const;
void markFree (std::size_t size_);
/// \brief Get pointer to readable area
char *usedArea () const;
/// \brief Get size of readable area
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_);
/// \brief Whether usedArea is empty
bool empty () const;
/// \brief Get buffer capacity
std::size_t capacity () const;
/// \brief Clear buffer; usedArea becomes empty
/// [unusable][usedArea][++++++freeArea]
/// becomes
/// [freeArea++++++++++++++++++++++++++]
void clear ();
/// \brief Move usedArea to the beginning of the buffer
/// [unusable][usedArea][freeArea]
/// becomes
/// [usedArea][freeArea++++++++++]
void coalesce ();
private:
/// \brief Buffer
std::unique_ptr<char[]> m_buffer;
/// \brief Buffer size
std::size_t const m_size;
/// \brief Start of usedArea
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 WeakLog = std::weak_ptr<Log>;
/// \brief Log object
class Log
{
public:
/// \brief Log level
enum Level
{
DEBUG,
@ -46,36 +48,72 @@ public:
~Log ();
/// \brief Draw log
void draw ();
/// \brief Create log
static SharedLog create ();
/// \brief Bind log
/// \param log_ Log to bind
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_, ...);
/// \brief Add info message to bound log
/// \param fmt_ Message format
__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_, ...);
/// \brief Add command message to bound log
/// \param fmt_ Message format
__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_, ...);
/// \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_);
/// \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_);
private:
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_);
/// \brief Log message
struct Message
{
/// \brief Parameterized constructor
/// \param level_ Log level
/// \param message_ Log message
Message (Level const level_, std::string message_)
: level (level_), message (std::move (message_))
{
}
/// \brief Log level
Level level;
/// \brief Log message
std::string message;
};
/// \brief Log messages
std::vector<Message> m_messages;
/// \brief Log lock
platform::Mutex m_lock;
};

View File

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

View File

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

View File

@ -30,53 +30,115 @@ class Socket;
using UniqueSocket = std::unique_ptr<Socket>;
using SharedSocket = std::shared_ptr<Socket>;
/// \brief Socket object
class Socket
{
public:
/// \brief Poll info
struct PollInfo
{
/// \brief Socket to poll
std::reference_wrapper<Socket> socket;
/// \brief Input events
int events;
/// \brief Output events
int revents;
};
~Socket ();
/// \brief Accept connection
UniqueSocket accept ();
/// \brief Whether socket is at out-of-band mark
int atMark ();
/// \brief Bind socket to address
/// \param addr_ Address to bind
bool bind (SockAddr const &addr_);
/// \brief Connect to a peer
/// \param addr_ Peer address
bool connect (SockAddr const &addr_);
/// \brief Listen for connections
/// \param backlog_ Queue size for incoming connections
bool listen (int backlog_);
/// \brief Shutdown socket
/// \param how_ Type of shutdown (\sa ::shutdown)
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_);
/// \brief Set non-blocking
/// \param nonBlocking_ Whether to set non-blocking
bool setNonBlocking (bool nonBlocking_ = true);
/// \brief Set reuse address in subsequent bind
/// \param reuse_ Whether to reuse address
bool setReuseAddress (bool reuse_ = true);
/// \brief Set recv buffer size
/// \param size_ Buffer size
bool setRecvBufferSize (std::size_t size_);
/// \brief Set send buffer size
/// \param size_ Buffer 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);
/// \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);
/// \brief Write data
/// \param buffer_ Input buffer
/// \param size_ Size to write
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_);
/// \brief Local name
SockAddr const &sockName () const;
/// \brief Peer name
SockAddr const &peerName () const;
/// \brief Create socket
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_);
int fd () const
{
return m_fd;
}
private:
Socket () = delete;
/// \brief Parameterized constructor
/// \param fd_ Socket 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 (Socket const &that_) = delete;
@ -87,11 +149,17 @@ private:
Socket &operator= (Socket &&that_) = delete;
/// \param Local name
SockAddr m_sockName;
/// \param Peer name
SockAddr m_peerName;
/// \param Socket fd
int const m_fd;
/// \param Whether listening
bool m_listening : 1;
/// \param Whether connected
bool m_connected : 1;
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,26 +18,28 @@
; You should have received a copy of the GNU General Public License
; 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]
; Constants
; constants
; [1.0, 0.0, 1.0/255.0, 0.0]
.constf constants(1.0, 0.0, 0.00392156862745, 0.0)
; Outputs
; outputs
.out outPos position
.out outUv texcoord0
.out outColor color
; Inputs (defined as aliases for convenience)
; inputs (defined as aliases for convenience)
.alias inPos v0
.alias inUv v1
.alias inColor v2
.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.zw, constants.yx
@ -56,6 +58,6 @@
; outColor = inColor
mov outColor, r1
; We're finished
; we're finished
end
.end

View File

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

View File

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

View File

@ -55,8 +55,13 @@ using namespace std::chrono_literals;
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_)
{
// look for \r\n or \n delimiter
auto const end = &buffer_[size_];
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};
}
/// \brief Decode path
/// \param buffer_ Buffer to decode
/// \param size_ Size of buffer
void decodePath (char *const buffer_, std::size_t const 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)
{
// check if the buffer has \n
@ -104,13 +115,16 @@ std::string encodePath (std::string_view const buffer_, bool const quotes_ = fal
} while (p);
}
// if nothing needs escaping, return it as-is
if (!lf && !numQuotes)
return std::string (buffer_);
// reserve output buffer
std::string path (buffer_.size () + numQuotes, '\0');
auto in = buffer_.data ();
auto out = path.data ();
// encode into the output buffer
while (in < end)
{
if (*in == '\n')
@ -132,6 +146,8 @@ std::string encodePath (std::string_view const buffer_, bool const quotes_ = fal
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_)
{
// remove last path component
@ -142,6 +158,8 @@ std::string dirName (std::string_view const path_)
return dir;
}
/// \brief Resolve path
/// \param path_ Path to resolve
std::string resolvePath (std::string_view const path_)
{
assert (!path_.empty ());
@ -209,6 +227,9 @@ std::string resolvePath (std::string_view const path_)
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_)
{
// 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_);
}
/// \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_)
{
return resolvePath (buildPath (cwd_, args_));

View File

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

View File

@ -36,8 +36,13 @@
namespace
{
/// \brief GLFW main window
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)window_;
@ -49,6 +54,13 @@ void windowResize (GLFWwindow *const window_, int const width_, int const height
}
#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_,
GLenum const type_,
GLuint const id_,
@ -63,19 +75,21 @@ void logCallback (GLenum const source_,
(void)severity_;
(void)length_;
(void)userParam_;
// std::fprintf (stderr, "%s\n", message_);
std::fprintf (stderr, "%s\n", message_);
}
#endif
}
bool platform::init ()
{
// initialize GLFW
if (!glfwInit ())
{
std::fprintf (stderr, "Failed to initialize GLFW\n");
return false;
}
// use OpenGL 4.3 Core Profile
glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint (GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
@ -87,6 +101,7 @@ bool platform::init ()
glfwWindowHint (GLFW_DEPTH_BITS, 24);
glfwWindowHint (GLFW_STENCIL_BITS, 8);
// create GLFW window
s_mainWindow.reset (glfwCreateWindow (1280, 720, "Test Game", nullptr, nullptr));
if (!s_mainWindow)
{
@ -95,12 +110,14 @@ bool platform::init ()
return false;
}
// enable vsync
glfwSwapInterval (1);
// create context
glfwMakeContextCurrent (s_mainWindow.get ());
glfwSetFramebufferSizeCallback (s_mainWindow.get (), windowResize);
// load OpenGL
if (!gladLoadGL ())
{
std::fprintf (stderr, "gladLoadGL: failed\n");
@ -134,12 +151,10 @@ bool platform::init ()
std::printf ("Renderer: %s\n", glGetString (GL_RENDERER));
std::printf ("OpenGL Version: %s\n", glGetString (GL_VERSION));
IMGUI_CHECKVERSION ();
ImGui::CreateContext ();
ImGui_ImplGlfw_InitForOpenGL (s_mainWindow.get (), true);
ImGui_ImplOpenGL3_Init ("#version 150");
// disable imgui.ini file
ImGuiIO &io = ImGui::GetIO ();
io.IniFilename = nullptr;
@ -169,10 +184,6 @@ void platform::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);
glClear (GL_COLOR_BUFFER_BIT);
@ -183,22 +194,24 @@ void platform::render ()
void platform::exit ()
{
ImGui::DestroyContext ();
s_mainWindow.reset ();
glfwTerminate ();
}
///////////////////////////////////////////////////////////////////////////
/// \brief Platform thread pimpl
class platform::Thread::privateData_t
{
public:
privateData_t () = default;
/// \brief Parameterized constructor
/// \param func_ Thread entry point
privateData_t (std::function<void ()> func_) : thread (func_)
{
}
/// \brief Underlying thread object
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
{
public:
/// \brief Underlying mutex
std::mutex mutex;
};

View File

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

View File

@ -29,14 +29,21 @@
int main (int argc_, char *argv_[])
{
IMGUI_CHECKVERSION ();
ImGui::CreateContext ();
if (!platform::init ())
{
ImGui::DestroyContext ();
return EXIT_FAILURE;
}
auto &style = ImGui::GetStyle ();
style.WindowRounding = 0.0f;
#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
auto server = FtpServer::create (5000);
@ -48,5 +55,7 @@ int main (int argc_, char *argv_[])
platform::render ();
}
server.reset ();
platform::exit ();
ImGui::DestroyContext ();
}

View File

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

View File

@ -38,8 +38,10 @@ Socket::~Socket ()
{
if (m_listening)
Log::info ("Stop listening on [%s]:%u\n", m_sockName.name (), m_sockName.port ());
if (m_connected)
Log::info ("Closing connection to [%s]:%u\n", m_peerName.name (), m_peerName.port ());
if (::close (m_fd) != 0)
Log::error ("close: %s\n", std::strerror (errno));
}
@ -66,7 +68,7 @@ UniqueSocket Socket::accept ()
if (fd < 0)
{
Log::error ("accept: %s\n", std::strerror (errno));
return {nullptr, {}};
return nullptr;
}
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)
{
// get socket name due to request for ephemeral port
socklen_t addrLen = sizeof (struct sockaddr_storage);
if (::getsockname (m_fd, m_sockName, &addrLen) != 0)
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)
{
Log::error ("fcntl(F_SETFL): %s\n", std::strerror (errno));
Log::error ("fcntl(F_SETFL, %d): %s\n", flags, std::strerror (errno));
return false;
}
@ -212,7 +215,8 @@ bool Socket::setReuseAddress (bool const reuse_)
int reuse = reuse_;
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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB