Documentation and minor refactors
12
.gitignore
vendored
@ -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
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 791 B After Width: | Height: | Size: 791 B |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
26
Makefile
@ -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
|
||||
|
17
Makefile.3ds
@ -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 ...
|
||||
|
@ -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 \
|
||||
|
@ -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
|
||||
|
74
include/fs.h
@ -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};
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
@ -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 = {};
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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,
|
||||
|
@ -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 ();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 ();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 ());
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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_));
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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 = '?';
|
||||
}
|
||||
|
@ -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 ();
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
@ -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);
|
||||
}
|
@ -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 ();
|
@ -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
|
||||
};
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |