Documentation and minor refactors
12
.gitignore
vendored
@ -10,8 +10,10 @@
|
|||||||
*.pfs0
|
*.pfs0
|
||||||
*.smdh
|
*.smdh
|
||||||
.gdb_history
|
.gdb_history
|
||||||
build.*/
|
3ds/build
|
||||||
ftpd
|
3ds/romfs/*.t3x
|
||||||
romfs.3ds/*.t3x
|
linux/build
|
||||||
romfs.switch/*.zst
|
linux/ftpd
|
||||||
romfs.switch/shaders/*.dksh
|
switch/build
|
||||||
|
switch/romfs/*.zst
|
||||||
|
switch/romfs/shaders/*.dksh
|
||||||
|
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:
|
nxlink:
|
||||||
@$(MAKE) -f Makefile.switch nxlink
|
@$(MAKE) -f Makefile.switch nxlink
|
||||||
|
|
||||||
3dslink: 3dsx
|
3dslink:
|
||||||
@/opt/devkitpro/tools/bin/3dslink $(TARGET)-3ds.3dsx
|
@$(MAKE) -f Makefile.3ds 3dslink
|
||||||
|
|
||||||
format:
|
format:
|
||||||
@clang-format -style=file -i $(filter-out \
|
@clang-format -style=file -i $(filter-out \
|
||||||
include/imgui.h \
|
include/imgui.h \
|
||||||
source/pc/imgui_impl_glfw.cpp \
|
source/linux/imgui_impl_glfw.cpp \
|
||||||
source/pc/imgui_impl_glfw.h \
|
source/linux/imgui_impl_glfw.h \
|
||||||
source/pc/imgui_impl_opengl3.cpp \
|
source/linux/imgui_impl_opengl3.cpp \
|
||||||
source/pc/imgui_impl_opengl3.h \
|
source/linux/imgui_impl_opengl3.h \
|
||||||
source/pc/KHR/khrplatform.h \
|
source/linux/KHR/khrplatform.h \
|
||||||
source/pc/glad.c \
|
source/linux/glad.c \
|
||||||
source/pc/glad/glad.h \
|
source/linux/glad/glad.h \
|
||||||
source/imgui/imgui.cpp \
|
source/imgui/imgui.cpp \
|
||||||
source/imgui/imgui_demo.cpp \
|
source/imgui/imgui_demo.cpp \
|
||||||
source/imgui/imgui_draw.cpp \
|
source/imgui/imgui_draw.cpp \
|
||||||
@ -41,9 +41,9 @@ format:
|
|||||||
$(shell find source include -type f -name \*.c -o -name \*.cpp -o -name \*.h))
|
$(shell find source include -type f -name \*.c -o -name \*.cpp -o -name \*.h))
|
||||||
|
|
||||||
release: release-3ds release-nro
|
release: release-3ds release-nro
|
||||||
@xz -c <$(TARGET)-3ds.3dsx >ftpd.3dsx.xz
|
@xz -c <3ds/$(TARGET).3dsx >ftpd.3dsx.xz
|
||||||
@echo xz -c <$(TARGET)-3ds.cia >ftpd.cia.xz
|
@xz -c <3ds/$(TARGET).cia >ftpd.cia.xz
|
||||||
@echo xz -c <$(TARGET)-nx.nro >ftpd.nro.xz
|
@xz -c <switch/$(TARGET).nro >ftpd.nro.xz
|
||||||
|
|
||||||
nro:
|
nro:
|
||||||
@$(MAKE) -f Makefile.switch all
|
@$(MAKE) -f Makefile.switch all
|
||||||
@ -64,7 +64,7 @@ release-cia: release-3dsx
|
|||||||
@$(MAKE) DEFINES=-NDEBUG -f Makefile.3ds cia
|
@$(MAKE) DEFINES=-NDEBUG -f Makefile.3ds cia
|
||||||
|
|
||||||
release-3ds:
|
release-3ds:
|
||||||
# can't let these three run in parallel with each other due to using same
|
# can't let these run in parallel with each other due to using same
|
||||||
# .elf file name
|
# .elf file name
|
||||||
@$(MAKE) DEFINES=-DNDEBUG -f Makefile.3ds 3dsx
|
@$(MAKE) DEFINES=-DNDEBUG -f Makefile.3ds 3dsx
|
||||||
@$(MAKE) DEFINES=-DNDEBUG -f Makefile.3ds cia
|
@$(MAKE) DEFINES=-DNDEBUG -f Makefile.3ds cia
|
||||||
|
17
Makefile.3ds
@ -31,13 +31,13 @@ include $(DEVKITARM)/3ds_rules
|
|||||||
# - icon.png
|
# - icon.png
|
||||||
# - <libctru folder>/default_icon.png
|
# - <libctru folder>/default_icon.png
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
TARGET := $(notdir $(CURDIR))-3ds
|
TARGET := 3ds/$(notdir $(CURDIR))
|
||||||
BUILD := build.3ds
|
BUILD := 3ds/build
|
||||||
SOURCES := source source/3ds source/imgui
|
SOURCES := source source/3ds source/imgui
|
||||||
DATA := data
|
DATA := data
|
||||||
INCLUDES := include
|
INCLUDES := include
|
||||||
GRAPHICS := gfx.3ds
|
GRAPHICS := 3ds/gfx
|
||||||
ROMFS := romfs.3ds
|
ROMFS := 3ds/romfs
|
||||||
GFXBUILD := $(ROMFS)
|
GFXBUILD := $(ROMFS)
|
||||||
|
|
||||||
APP_TITLE := ftpd
|
APP_TITLE := ftpd
|
||||||
@ -66,7 +66,7 @@ CFLAGS += $(INCLUDE) -DARM11 -D_3DS \
|
|||||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17
|
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17
|
||||||
|
|
||||||
ASFLAGS := -g $(ARCH)
|
ASFLAGS := -g $(ARCH)
|
||||||
LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(TARGET).map $(OPTIMIZE)
|
LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) $(OPTIMIZE)
|
||||||
|
|
||||||
LIBS := -lcitro3d -lctru -lm
|
LIBS := -lcitro3d -lctru -lm
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ LIBDIRS := $(CTRULIB)
|
|||||||
# no real need to edit anything past this point unless you need to add additional
|
# no real need to edit anything past this point unless you need to add additional
|
||||||
# rules for different file extensions
|
# rules for different file extensions
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
ifneq ($(BUILD),$(notdir $(CURDIR)))
|
ifneq ($(TOPDIR)/$(BUILD),$(CURDIR))
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
|
|
||||||
export OUTPUT := $(CURDIR)/$(TARGET)
|
export OUTPUT := $(CURDIR)/$(TARGET)
|
||||||
@ -165,7 +165,7 @@ ifneq ($(ROMFS),)
|
|||||||
export _3DSXFLAGS += --romfs=$(CURDIR)/$(ROMFS)
|
export _3DSXFLAGS += --romfs=$(CURDIR)/$(ROMFS)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: $(BUILD) clean all 3dsx cia
|
.PHONY: $(BUILD) clean all 3dsx cia 3dslink
|
||||||
|
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
all: $(BUILD) $(GFXBUILD) $(DEPSDIR) $(ROMFS_T3XFILES) $(T3XHFILES)
|
all: $(BUILD) $(GFXBUILD) $(DEPSDIR) $(ROMFS_T3XFILES) $(T3XHFILES)
|
||||||
@ -196,6 +196,9 @@ $(GFXBUILD)/%.t3x $(BUILD)/%.h: %.t3s
|
|||||||
cia: $(BUILD) $(GFXBUILD) $(DEPSDIR) $(ROMFS_T3XFILES) $(T3XHFILES)
|
cia: $(BUILD) $(GFXBUILD) $(DEPSDIR) $(ROMFS_T3XFILES) $(T3XHFILES)
|
||||||
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile.3ds cia
|
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile.3ds cia
|
||||||
|
|
||||||
|
3dslink: 3dsx
|
||||||
|
@3dslink $(OUTPUT).3dsx
|
||||||
|
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
clean:
|
clean:
|
||||||
@echo clean ...
|
@echo clean ...
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
TARGET := ftpd
|
TARGET := linux/ftpd
|
||||||
BUILD := build.linux
|
BUILD := linux/build
|
||||||
|
|
||||||
CFILES := $(wildcard source/pc/*.c)
|
CFILES := $(wildcard source/linux/*.c)
|
||||||
OFILES := $(patsubst source/%,$(BUILD)/%,$(CFILES:.c=.c.o))
|
OFILES := $(patsubst source/%,$(BUILD)/%,$(CFILES:.c=.c.o))
|
||||||
CXXFILES := $(wildcard source/*.cpp source/imgui/*.cpp source/pc/*.cpp)
|
CXXFILES := $(wildcard source/*.cpp source/imgui/*.cpp source/linux/*.cpp)
|
||||||
OXXFILES := $(patsubst source/%,$(BUILD)/%,$(CXXFILES:.cpp=.cpp.o))
|
OXXFILES := $(patsubst source/%,$(BUILD)/%,$(CXXFILES:.cpp=.cpp.o))
|
||||||
|
|
||||||
CPPFLAGS := -g -Wall -pthread -Iinclude -Isource/pc \
|
CPPFLAGS := -g -Wall -pthread -Iinclude -Isource/linux \
|
||||||
`pkg-config --cflags gl glfw3` \
|
`pkg-config --cflags gl glfw3` \
|
||||||
-DSTATUS_STRING="\"ftpd v$(VERSION)\"" \
|
-DSTATUS_STRING="\"ftpd v$(VERSION)\"" \
|
||||||
-DIMGUI_DISABLE_INCLUDE_IMCONFIG_H=1 \
|
-DIMGUI_DISABLE_INCLUDE_IMCONFIG_H=1 \
|
||||||
|
@ -42,13 +42,13 @@ APP_AUTHOR := mtheall, TuxSH, WinterMute
|
|||||||
ICON := meta/ftpd.jpg
|
ICON := meta/ftpd.jpg
|
||||||
APP_VERSION := $(VERSION)
|
APP_VERSION := $(VERSION)
|
||||||
|
|
||||||
TARGET := $(notdir $(CURDIR))-nx
|
TARGET := switch/$(notdir $(CURDIR))
|
||||||
BUILD := build.switch
|
BUILD := switch/build
|
||||||
SOURCES := source source/imgui source/nx
|
SOURCES := source source/imgui source/switch
|
||||||
DATA := data
|
DATA := data
|
||||||
INCLUDES := include
|
INCLUDES := include
|
||||||
GRAPHICS := gfx.switch
|
GRAPHICS := switch/gfx
|
||||||
ROMFS := romfs.switch
|
ROMFS := switch/romfs
|
||||||
|
|
||||||
# Output folders for autogenerated files in romfs
|
# Output folders for autogenerated files in romfs
|
||||||
OUT_SHADERS := shaders
|
OUT_SHADERS := shaders
|
||||||
@ -85,7 +85,7 @@ LIBDIRS := $(PORTLIBS) $(LIBNX)
|
|||||||
# no real need to edit anything past this point unless you need to add additional
|
# no real need to edit anything past this point unless you need to add additional
|
||||||
# rules for different file extensions
|
# rules for different file extensions
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
ifneq ($(BUILD),$(notdir $(CURDIR)))
|
ifneq ($(TOPDIR)/$(BUILD),$(CURDIR))
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
|
|
||||||
export OUTPUT := $(CURDIR)/$(TARGET)
|
export OUTPUT := $(CURDIR)/$(TARGET)
|
||||||
@ -193,7 +193,7 @@ all: $(ROMFS_TARGETS) | $(BUILD)
|
|||||||
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile.switch
|
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile.switch
|
||||||
|
|
||||||
nxlink: all
|
nxlink: all
|
||||||
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile.switch nxlink
|
@nxlink -s $(OUTPUT).nro
|
||||||
|
|
||||||
$(BUILD):
|
$(BUILD):
|
||||||
@mkdir -p $@
|
@mkdir -p $@
|
||||||
@ -260,9 +260,6 @@ ifeq ($(strip $(APP_JSON)),)
|
|||||||
|
|
||||||
all : $(OUTPUT).nro
|
all : $(OUTPUT).nro
|
||||||
|
|
||||||
nxlink: $(OUTPUT).nro
|
|
||||||
@nxlink -s $(OUTPUT).nro
|
|
||||||
|
|
||||||
ifeq ($(strip $(NO_NACP)),)
|
ifeq ($(strip $(NO_NACP)),)
|
||||||
$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp $(ROMFS_DEPS)
|
$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp $(ROMFS_DEPS)
|
||||||
else
|
else
|
||||||
|
74
include/fs.h
@ -29,8 +29,11 @@
|
|||||||
|
|
||||||
namespace fs
|
namespace fs
|
||||||
{
|
{
|
||||||
|
/// \brief Print size in human-readable format (KiB, MiB, etc)
|
||||||
|
/// \param size_ Size to print
|
||||||
std::string printSize (std::uint64_t size_);
|
std::string printSize (std::uint64_t size_);
|
||||||
|
|
||||||
|
/// \brief File I/O object
|
||||||
class File
|
class File
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -40,51 +43,97 @@ public:
|
|||||||
|
|
||||||
File (File const &that_) = delete;
|
File (File const &that_) = delete;
|
||||||
|
|
||||||
|
/// \brief Move constructor
|
||||||
|
/// \param that_ Object to move from
|
||||||
File (File &&that_);
|
File (File &&that_);
|
||||||
|
|
||||||
File &operator= (File const &that_) = delete;
|
File &operator= (File const &that_) = delete;
|
||||||
|
|
||||||
|
/// \brief Move assignment
|
||||||
|
/// \param that_ Object to move from
|
||||||
File &operator= (File &&that_);
|
File &operator= (File &&that_);
|
||||||
|
|
||||||
operator bool () const;
|
/// \brief bool cast operator
|
||||||
operator FILE * () const;
|
explicit operator bool () const;
|
||||||
|
|
||||||
|
/// \brief std::FILE* cast operator
|
||||||
|
operator std::FILE * () const;
|
||||||
|
|
||||||
|
/// \brief Set buffer size
|
||||||
|
/// \param size_ Buffer size
|
||||||
void setBufferSize (std::size_t size_);
|
void setBufferSize (std::size_t size_);
|
||||||
|
|
||||||
|
/// \brief Open file
|
||||||
|
/// \param path_ Path to open
|
||||||
|
/// \param mode_ Access mode (\sa std::fopen)
|
||||||
bool open (char const *path_, char const *mode_ = "rb");
|
bool open (char const *path_, char const *mode_ = "rb");
|
||||||
|
|
||||||
|
/// \brief Close file
|
||||||
void close ();
|
void close ();
|
||||||
|
|
||||||
|
/// \brief Seek to file position
|
||||||
|
/// \param pos_ File position
|
||||||
|
/// \param origin_ Reference position (\sa std::fseek)
|
||||||
ssize_t seek (std::size_t pos_, int origin_);
|
ssize_t seek (std::size_t pos_, int origin_);
|
||||||
|
|
||||||
|
/// \brief Read data
|
||||||
|
/// \param data_ Output buffer
|
||||||
|
/// \param size_ Size to read
|
||||||
|
/// \note Can return partial reads
|
||||||
ssize_t read (void *data_, std::size_t size_);
|
ssize_t read (void *data_, std::size_t size_);
|
||||||
|
|
||||||
|
/// \brief Read data
|
||||||
|
/// \param data_ Output buffer
|
||||||
|
/// \param size_ Size to read
|
||||||
|
/// \note Fails on partial reads and errors
|
||||||
bool readAll (void *data_, std::size_t size_);
|
bool readAll (void *data_, std::size_t size_);
|
||||||
|
|
||||||
|
/// \brief Write data
|
||||||
|
/// \param data_ Input data
|
||||||
|
/// \param size_ Size to write
|
||||||
|
/// \note Can return partial writes
|
||||||
ssize_t write (void const *data_, std::size_t size_);
|
ssize_t write (void const *data_, std::size_t size_);
|
||||||
|
|
||||||
|
/// \brief Write data
|
||||||
|
/// \param data_ Input data
|
||||||
|
/// \param size_ Size to write
|
||||||
|
/// \note Fails on partials writes and errors
|
||||||
bool writeAll (void const *data_, std::size_t size_);
|
bool writeAll (void const *data_, std::size_t size_);
|
||||||
|
|
||||||
|
/// \brief Read data
|
||||||
|
/// \tparam T Type to read
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T read ()
|
T read ()
|
||||||
{
|
{
|
||||||
T data;
|
T data;
|
||||||
if (read (&data, sizeof (data)) != sizeof (data))
|
if (!readAll (&data, sizeof (data)))
|
||||||
std::abort ();
|
std::abort ();
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Write data
|
||||||
|
/// \tparam T type to write
|
||||||
|
/// \param data_ Data to write
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void write (T const &data_)
|
void write (T const &data_)
|
||||||
{
|
{
|
||||||
if (write (&data_, sizeof (data_)) != sizeof (data_))
|
if (!writeAll (&data_, sizeof (data_)))
|
||||||
std::abort ();
|
std::abort ();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// \brief Underlying std::FILE*
|
||||||
std::unique_ptr<std::FILE, int (*) (std::FILE *)> m_fp{nullptr, nullptr};
|
std::unique_ptr<std::FILE, int (*) (std::FILE *)> m_fp{nullptr, nullptr};
|
||||||
|
|
||||||
|
/// \brief Buffer
|
||||||
std::unique_ptr<char[]> m_buffer;
|
std::unique_ptr<char[]> m_buffer;
|
||||||
|
|
||||||
|
/// \brief Buffer size
|
||||||
std::size_t m_bufferSize = 0;
|
std::size_t m_bufferSize = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Directory object
|
||||||
class Dir
|
class Dir
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -94,20 +143,35 @@ public:
|
|||||||
|
|
||||||
Dir (Dir const &that_) = delete;
|
Dir (Dir const &that_) = delete;
|
||||||
|
|
||||||
|
/// \brief Move constructor
|
||||||
|
/// \param that_ Object to move from
|
||||||
Dir (Dir &&that_);
|
Dir (Dir &&that_);
|
||||||
|
|
||||||
Dir &operator= (Dir const &that_) = delete;
|
Dir &operator= (Dir const &that_) = delete;
|
||||||
|
|
||||||
|
/// \brief Move assignment
|
||||||
|
/// \param that_ Object to move from
|
||||||
Dir &operator= (Dir &&that_);
|
Dir &operator= (Dir &&that_);
|
||||||
|
|
||||||
operator bool () const;
|
/// \brief bool cast operator
|
||||||
|
explicit operator bool () const;
|
||||||
|
|
||||||
|
/// \brief DIR* cast operator
|
||||||
operator DIR * () const;
|
operator DIR * () const;
|
||||||
|
|
||||||
|
/// \brief Open directory
|
||||||
|
/// \param path_ Path to open
|
||||||
bool open (char const *const path_);
|
bool open (char const *const path_);
|
||||||
|
|
||||||
|
/// \brief Close directory
|
||||||
void close ();
|
void close ();
|
||||||
|
|
||||||
|
/// \brief Read a directory entry
|
||||||
|
/// \note Returns nullptr on end-of-directory or error; check errno
|
||||||
struct dirent *read ();
|
struct dirent *read ();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// \brief Underlying DIR*
|
||||||
std::unique_ptr<DIR, int (*) (DIR *)> m_dp{nullptr, nullptr};
|
std::unique_ptr<DIR, int (*) (DIR *)> m_dp{nullptr, nullptr};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -34,40 +34,63 @@
|
|||||||
class FtpServer;
|
class FtpServer;
|
||||||
using UniqueFtpServer = std::unique_ptr<FtpServer>;
|
using UniqueFtpServer = std::unique_ptr<FtpServer>;
|
||||||
|
|
||||||
|
/// \brief FTP server
|
||||||
class FtpServer
|
class FtpServer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
~FtpServer ();
|
~FtpServer ();
|
||||||
|
|
||||||
|
/// \brief Draw server and all of its sessions
|
||||||
void draw ();
|
void draw ();
|
||||||
|
|
||||||
|
/// \brief Create server
|
||||||
|
/// \param port_ Port to listen on
|
||||||
static UniqueFtpServer create (std::uint16_t port_);
|
static UniqueFtpServer create (std::uint16_t port_);
|
||||||
|
|
||||||
|
/// \brief Update free space
|
||||||
static void updateFreeSpace ();
|
static void updateFreeSpace ();
|
||||||
|
|
||||||
|
/// \brief Server start time
|
||||||
static std::time_t startTime ();
|
static std::time_t startTime ();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// \brief Paramterized constructor
|
||||||
|
/// \param port_ Port to listen on
|
||||||
FtpServer (std::uint16_t port_);
|
FtpServer (std::uint16_t port_);
|
||||||
|
|
||||||
|
/// \brief Handle when start button is pressed
|
||||||
void handleStartButton ();
|
void handleStartButton ();
|
||||||
|
|
||||||
|
/// \brief Handle when stop button is pressed
|
||||||
void handleStopButton ();
|
void handleStopButton ();
|
||||||
|
|
||||||
|
/// \brief Server loop
|
||||||
void loop ();
|
void loop ();
|
||||||
|
|
||||||
|
/// \brief Thread entry point
|
||||||
void threadFunc ();
|
void threadFunc ();
|
||||||
|
|
||||||
|
/// \brief Thread
|
||||||
platform::Thread m_thread;
|
platform::Thread m_thread;
|
||||||
|
|
||||||
|
/// \brief Mutex
|
||||||
platform::Mutex m_lock;
|
platform::Mutex m_lock;
|
||||||
|
|
||||||
|
/// \brief Listen socket
|
||||||
UniqueSocket m_socket;
|
UniqueSocket m_socket;
|
||||||
|
|
||||||
|
/// \brief ImGui window name
|
||||||
std::string m_name;
|
std::string m_name;
|
||||||
|
|
||||||
|
/// \brief Log
|
||||||
SharedLog m_log;
|
SharedLog m_log;
|
||||||
|
|
||||||
|
/// \brief Sessions
|
||||||
std::vector<UniqueFtpSession> m_sessions;
|
std::vector<UniqueFtpSession> m_sessions;
|
||||||
|
|
||||||
std::uint16_t m_port;
|
/// \brief Port to listen on
|
||||||
|
std::uint16_t const m_port;
|
||||||
|
|
||||||
|
/// \brief Whether thread should quit
|
||||||
std::atomic<bool> m_quit;
|
std::atomic<bool> m_quit;
|
||||||
};
|
};
|
||||||
|
@ -34,33 +34,54 @@
|
|||||||
class FtpSession;
|
class FtpSession;
|
||||||
using UniqueFtpSession = std::unique_ptr<FtpSession>;
|
using UniqueFtpSession = std::unique_ptr<FtpSession>;
|
||||||
|
|
||||||
|
/// \brief FTP session
|
||||||
class FtpSession
|
class FtpSession
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
~FtpSession ();
|
~FtpSession ();
|
||||||
|
|
||||||
|
/// \brief Whether session sockets are all inactive
|
||||||
bool dead ();
|
bool dead ();
|
||||||
|
|
||||||
|
/// \brief Draw session status
|
||||||
void draw ();
|
void draw ();
|
||||||
|
|
||||||
|
/// \brief Create session
|
||||||
|
/// \param commandSocket_ Command socket
|
||||||
static UniqueFtpSession create (UniqueSocket commandSocket_);
|
static UniqueFtpSession create (UniqueSocket commandSocket_);
|
||||||
|
|
||||||
|
/// \brief Poll for activity
|
||||||
|
/// \param sessions_ Sessions to poll
|
||||||
static void poll (std::vector<UniqueFtpSession> const &sessions_);
|
static void poll (std::vector<UniqueFtpSession> const &sessions_);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
constexpr static auto COMMAND_BUFFERSIZE = 4096;
|
/// \brief Command buffer size
|
||||||
|
constexpr static auto COMMAND_BUFFERSIZE = 4096;
|
||||||
|
|
||||||
|
/// \brief Response buffer size
|
||||||
constexpr static auto RESPONSE_BUFFERSIZE = 32768;
|
constexpr static auto RESPONSE_BUFFERSIZE = 32768;
|
||||||
constexpr static auto XFER_BUFFERSIZE = 65536;
|
|
||||||
constexpr static auto FILE_BUFFERSIZE = 4 * XFER_BUFFERSIZE;
|
/// \brief Transfer buffersize
|
||||||
|
constexpr static auto XFER_BUFFERSIZE = 65536;
|
||||||
|
|
||||||
|
/// \brief File buffersize
|
||||||
|
constexpr static auto FILE_BUFFERSIZE = 4 * XFER_BUFFERSIZE;
|
||||||
|
|
||||||
#ifdef _3DS
|
#ifdef _3DS
|
||||||
constexpr static auto SOCK_BUFFERSIZE = 32768;
|
/// \brief Socket buffer size
|
||||||
|
constexpr static auto SOCK_BUFFERSIZE = 32768;
|
||||||
|
|
||||||
|
/// \brief Amount of file position history to keep
|
||||||
constexpr static auto POSITION_HISTORY = 100;
|
constexpr static auto POSITION_HISTORY = 100;
|
||||||
#else
|
#else
|
||||||
constexpr static auto SOCK_BUFFERSIZE = XFER_BUFFERSIZE;
|
/// \brief Socket buffer size
|
||||||
|
constexpr static auto SOCK_BUFFERSIZE = XFER_BUFFERSIZE;
|
||||||
|
|
||||||
|
/// \brief Amount of file position history to keep
|
||||||
constexpr static auto POSITION_HISTORY = 300;
|
constexpr static auto POSITION_HISTORY = 300;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/// \brief Session state
|
||||||
enum class State
|
enum class State
|
||||||
{
|
{
|
||||||
COMMAND,
|
COMMAND,
|
||||||
@ -68,6 +89,7 @@ private:
|
|||||||
DATA_TRANSFER,
|
DATA_TRANSFER,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// \brief Transfer file mode
|
||||||
enum class XferFileMode
|
enum class XferFileMode
|
||||||
{
|
{
|
||||||
RETR,
|
RETR,
|
||||||
@ -75,6 +97,7 @@ private:
|
|||||||
APPE,
|
APPE,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// \brief Transfer directory mode
|
||||||
enum class XferDirMode
|
enum class XferDirMode
|
||||||
{
|
{
|
||||||
LIST,
|
LIST,
|
||||||
@ -84,122 +107,322 @@ private:
|
|||||||
STAT,
|
STAT,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// \brief Parameterized constructor
|
||||||
|
/// \param commandSocket_ Command socket
|
||||||
FtpSession (UniqueSocket commandSocket_);
|
FtpSession (UniqueSocket commandSocket_);
|
||||||
|
|
||||||
|
/// \brief Set session state
|
||||||
|
/// \param state_ State to set
|
||||||
|
/// \param closePasv_ Whether to close listening socket
|
||||||
|
/// \param closeData_ Whether to close data socket
|
||||||
void setState (State state_, bool closePasv_, bool closeData_);
|
void setState (State state_, bool closePasv_, bool closeData_);
|
||||||
|
|
||||||
|
/// \brief Close data socket
|
||||||
void closeData ();
|
void closeData ();
|
||||||
|
|
||||||
|
/// \brief Change working directory
|
||||||
bool changeDir (char const *args_);
|
bool changeDir (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Accept connection as data socket
|
||||||
bool dataAccept ();
|
bool dataAccept ();
|
||||||
|
|
||||||
|
/// \brief Connect data socket
|
||||||
bool dataConnect ();
|
bool dataConnect ();
|
||||||
|
|
||||||
void updateFreeSpace ();
|
/// \brief Fill directory entry
|
||||||
|
/// \param st_ Entry status
|
||||||
|
/// \param path_ Path name
|
||||||
|
/// \param type_ MLST type
|
||||||
int fillDirent (struct stat const &st_, std::string_view path_, char const *type_ = nullptr);
|
int fillDirent (struct stat const &st_, std::string_view path_, char const *type_ = nullptr);
|
||||||
|
|
||||||
|
/// \brief Fill directory entry
|
||||||
|
/// \param path_ Path name
|
||||||
|
/// \param type_ MLST type
|
||||||
int fillDirent (std::string const &path_, char const *type_ = nullptr);
|
int fillDirent (std::string const &path_, char const *type_ = nullptr);
|
||||||
|
|
||||||
|
/// \brief Transfer file
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
/// \param mode_ Transfer file mode
|
||||||
void xferFile (char const *args_, XferFileMode mode_);
|
void xferFile (char const *args_, XferFileMode mode_);
|
||||||
|
|
||||||
|
/// \brief Transfer directory
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
/// \param mode_ Transfer directory mode
|
||||||
void xferDir (char const *args_, XferDirMode mode_, bool workaround_);
|
void xferDir (char const *args_, XferDirMode mode_, bool workaround_);
|
||||||
|
|
||||||
|
/// \brief Read command
|
||||||
|
/// \param events_ Poll events
|
||||||
void readCommand (int events_);
|
void readCommand (int events_);
|
||||||
|
|
||||||
|
/// \brief Write response
|
||||||
void writeResponse ();
|
void writeResponse ();
|
||||||
|
|
||||||
|
/// \brief Send response
|
||||||
|
/// \param fmt_ Message format
|
||||||
__attribute__ ((format (printf, 2, 3))) void sendResponse (char const *fmt_, ...);
|
__attribute__ ((format (printf, 2, 3))) void sendResponse (char const *fmt_, ...);
|
||||||
|
|
||||||
|
/// \brief Send response
|
||||||
|
/// \param response_ Response message
|
||||||
void sendResponse (std::string_view response_);
|
void sendResponse (std::string_view response_);
|
||||||
|
|
||||||
|
/// \brief Transfer function
|
||||||
bool (FtpSession::*m_transfer) () = nullptr;
|
bool (FtpSession::*m_transfer) () = nullptr;
|
||||||
|
|
||||||
|
/// \brief Transfer directory list
|
||||||
bool listTransfer ();
|
bool listTransfer ();
|
||||||
|
|
||||||
|
/// \brief Transfer download
|
||||||
bool retrieveTransfer ();
|
bool retrieveTransfer ();
|
||||||
|
|
||||||
|
/// \brief Transfer upload
|
||||||
bool storeTransfer ();
|
bool storeTransfer ();
|
||||||
|
|
||||||
|
/// \brief Mutex
|
||||||
platform::Mutex m_lock;
|
platform::Mutex m_lock;
|
||||||
|
|
||||||
|
/// \brief Command socket
|
||||||
SharedSocket m_commandSocket;
|
SharedSocket m_commandSocket;
|
||||||
|
|
||||||
|
/// \brief Data listen socker
|
||||||
UniqueSocket m_pasvSocket;
|
UniqueSocket m_pasvSocket;
|
||||||
|
|
||||||
|
/// \brief Data socket
|
||||||
SharedSocket m_dataSocket;
|
SharedSocket m_dataSocket;
|
||||||
|
|
||||||
|
/// \brief Sockets pending close
|
||||||
std::vector<SharedSocket> m_pendingCloseSocket;
|
std::vector<SharedSocket> m_pendingCloseSocket;
|
||||||
|
|
||||||
|
/// \brief Command buffer
|
||||||
IOBuffer m_commandBuffer;
|
IOBuffer m_commandBuffer;
|
||||||
|
|
||||||
|
/// \brief Response buffer
|
||||||
IOBuffer m_responseBuffer;
|
IOBuffer m_responseBuffer;
|
||||||
|
|
||||||
|
/// \brief Transfer buffer
|
||||||
IOBuffer m_xferBuffer;
|
IOBuffer m_xferBuffer;
|
||||||
|
|
||||||
SockAddr m_pasvAddr;
|
/// \brief Address from last PORT command
|
||||||
SockAddr m_portAddr;
|
SockAddr m_portAddr;
|
||||||
|
|
||||||
|
/// \brief Current working directory
|
||||||
std::string m_cwd = "/";
|
std::string m_cwd = "/";
|
||||||
|
|
||||||
|
/// \brief List working directory
|
||||||
std::string m_lwd;
|
std::string m_lwd;
|
||||||
|
|
||||||
|
/// \brief Path from RNFR command
|
||||||
std::string m_rename;
|
std::string m_rename;
|
||||||
|
|
||||||
|
/// \brief Current work item
|
||||||
std::string m_workItem;
|
std::string m_workItem;
|
||||||
|
|
||||||
|
/// \brief ImGui window name
|
||||||
std::string m_windowName;
|
std::string m_windowName;
|
||||||
|
|
||||||
|
/// \brief ImGui plot widget name
|
||||||
std::string m_plotName;
|
std::string m_plotName;
|
||||||
|
|
||||||
|
/// \brief Position from REST command
|
||||||
std::uint64_t m_restartPosition = 0;
|
std::uint64_t m_restartPosition = 0;
|
||||||
std::uint64_t m_filePosition = 0;
|
|
||||||
std::uint64_t m_fileSize = 0;
|
|
||||||
|
|
||||||
|
/// \brief Current file position
|
||||||
|
std::uint64_t m_filePosition = 0;
|
||||||
|
|
||||||
|
/// \brief File size of current transfer
|
||||||
|
std::uint64_t m_fileSize = 0;
|
||||||
|
|
||||||
|
/// \brief Last file position update timestamp
|
||||||
platform::steady_clock::time_point m_filePositionTime;
|
platform::steady_clock::time_point m_filePositionTime;
|
||||||
|
|
||||||
|
/// \brief File position history
|
||||||
std::uint64_t m_filePositionHistory[POSITION_HISTORY];
|
std::uint64_t m_filePositionHistory[POSITION_HISTORY];
|
||||||
|
|
||||||
|
/// \brief File position history deltas
|
||||||
float m_filePositionDeltas[POSITION_HISTORY];
|
float m_filePositionDeltas[POSITION_HISTORY];
|
||||||
|
|
||||||
|
/// \brief Transfer rate (EWMA low-pass filtered)
|
||||||
float m_xferRate;
|
float m_xferRate;
|
||||||
|
|
||||||
|
/// \brief Session state
|
||||||
State m_state = State::COMMAND;
|
State m_state = State::COMMAND;
|
||||||
|
|
||||||
|
/// \brief File being transferred
|
||||||
fs::File m_file;
|
fs::File m_file;
|
||||||
|
|
||||||
|
/// \brief Directory being transferred
|
||||||
fs::Dir m_dir;
|
fs::Dir m_dir;
|
||||||
|
|
||||||
|
/// \brief Directory transfer mode
|
||||||
XferDirMode m_xferDirMode;
|
XferDirMode m_xferDirMode;
|
||||||
|
|
||||||
|
/// \brief Whether previous command was PASV
|
||||||
bool m_pasv : 1;
|
bool m_pasv : 1;
|
||||||
|
/// \brief Whether previous command was PORT
|
||||||
bool m_port : 1;
|
bool m_port : 1;
|
||||||
|
/// \brief Whether receiving data
|
||||||
bool m_recv : 1;
|
bool m_recv : 1;
|
||||||
|
/// \brief Whether sending data
|
||||||
bool m_send : 1;
|
bool m_send : 1;
|
||||||
|
/// \brief Whether urgent (out-of-band) data is on the way
|
||||||
bool m_urgent : 1;
|
bool m_urgent : 1;
|
||||||
|
|
||||||
|
/// \brief Whether MLST type fact is enabled
|
||||||
bool m_mlstType : 1;
|
bool m_mlstType : 1;
|
||||||
|
/// \brief Whether MLST size fact is enabled
|
||||||
bool m_mlstSize : 1;
|
bool m_mlstSize : 1;
|
||||||
|
/// \brief Whether MLST modify fact is enabled
|
||||||
bool m_mlstModify : 1;
|
bool m_mlstModify : 1;
|
||||||
|
/// \brief Whether MLST perm fact is enabled
|
||||||
bool m_mlstPerm : 1;
|
bool m_mlstPerm : 1;
|
||||||
|
/// \brief Whether MLST unix.mode fact is enabled
|
||||||
bool m_mlstUnixMode : 1;
|
bool m_mlstUnixMode : 1;
|
||||||
|
|
||||||
|
/// \brief Abort a transfer
|
||||||
|
/// \param args_ Command arguments
|
||||||
void ABOR (char const *args_);
|
void ABOR (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Allocate space
|
||||||
|
/// \param args_ Command arguments
|
||||||
void ALLO (char const *args_);
|
void ALLO (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Append data to a file
|
||||||
|
/// \param args_ Command arguments
|
||||||
void APPE (char const *args_);
|
void APPE (char const *args_);
|
||||||
|
|
||||||
|
/// \brief CWD to parent directory
|
||||||
|
/// \param args_ Command arguments
|
||||||
void CDUP (char const *args_);
|
void CDUP (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Change working directory
|
||||||
|
/// \param args_ Command arguments
|
||||||
void CWD (char const *args_);
|
void CWD (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Delete a file
|
||||||
|
/// \param args_ Command arguments
|
||||||
void DELE (char const *args_);
|
void DELE (char const *args_);
|
||||||
|
|
||||||
|
/// \brief List server features
|
||||||
|
/// \param args_ Command arguments
|
||||||
void FEAT (char const *args_);
|
void FEAT (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Print server help
|
||||||
|
/// \param args_ Command arguments
|
||||||
void HELP (char const *args_);
|
void HELP (char const *args_);
|
||||||
|
|
||||||
|
/// \brief List directory
|
||||||
|
/// \param args_ Command arguments
|
||||||
void LIST (char const *args_);
|
void LIST (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Last modification time
|
||||||
|
/// \param args_ Command arguments
|
||||||
void MDTM (char const *args_);
|
void MDTM (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Create a directory
|
||||||
|
/// \param args_ Command arguments
|
||||||
void MKD (char const *args_);
|
void MKD (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Machine list directory
|
||||||
|
/// \param args_ Command arguments
|
||||||
void MLSD (char const *args_);
|
void MLSD (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Machine list
|
||||||
|
/// \param args_ Command arguments
|
||||||
void MLST (char const *args_);
|
void MLST (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Set transfer mode
|
||||||
|
/// \param args_ Command arguments
|
||||||
void MODE (char const *args_);
|
void MODE (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Name list
|
||||||
|
/// \param args_ Command arguments
|
||||||
void NLST (char const *args_);
|
void NLST (char const *args_);
|
||||||
|
|
||||||
|
/// \brief No-op
|
||||||
|
/// \param args_ Command arguments
|
||||||
void NOOP (char const *args_);
|
void NOOP (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Set server options
|
||||||
|
/// \param args_ Command arguments
|
||||||
void OPTS (char const *args_);
|
void OPTS (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Password
|
||||||
|
/// \param args_ Command arguments
|
||||||
void PASS (char const *args_);
|
void PASS (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Request an address to connect to for data transfers
|
||||||
|
/// \param args_ Command arguments
|
||||||
void PASV (char const *args_);
|
void PASV (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Provide an address to connect to for data transfers
|
||||||
|
/// \param args_ Command arguments
|
||||||
void PORT (char const *args_);
|
void PORT (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Print working directory
|
||||||
|
/// \param args_ Command arguments
|
||||||
void PWD (char const *args_);
|
void PWD (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Terminate session
|
||||||
|
/// \param args_ Command arguments
|
||||||
void QUIT (char const *args_);
|
void QUIT (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Restart a file transfer
|
||||||
|
/// \param args_ Command arguments
|
||||||
void REST (char const *args_);
|
void REST (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Retrieve a file
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
/// \note Requires a PASV or PORT connection
|
||||||
void RETR (char const *args_);
|
void RETR (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Remove a directory
|
||||||
|
/// \param args_ Command arguments
|
||||||
void RMD (char const *args_);
|
void RMD (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Rename from
|
||||||
|
/// \param args_ Command arguments
|
||||||
void RNFR (char const *args_);
|
void RNFR (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Rename to
|
||||||
|
/// \param args_ Command arguments
|
||||||
void RNTO (char const *args_);
|
void RNTO (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Get file size
|
||||||
|
/// \param args_ Command arguments
|
||||||
void SIZE (char const *args_);
|
void SIZE (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Get status
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
/// \note If no argument is supplied, and a transfer is occurring, get the current transfer
|
||||||
|
/// status. If no argument is supplied, and no transfer is occurring, get the server status. If
|
||||||
|
/// an argument is supplied, this is equivalent to LIST, except the data is sent over the
|
||||||
|
/// command socket.
|
||||||
void STAT (char const *args_);
|
void STAT (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Store a file
|
||||||
|
/// \param args_ Command arguments
|
||||||
void STOR (char const *args_);
|
void STOR (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Store a unique file
|
||||||
|
/// \param args_ Command arguments
|
||||||
void STOU (char const *args_);
|
void STOU (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Set file structure
|
||||||
|
/// \param args_ Command arguments
|
||||||
void STRU (char const *args_);
|
void STRU (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Identify system
|
||||||
|
/// \param args_ Command arguments
|
||||||
void SYST (char const *args_);
|
void SYST (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Set representation type
|
||||||
|
/// \param args_ Command arguments
|
||||||
void TYPE (char const *args_);
|
void TYPE (char const *args_);
|
||||||
|
|
||||||
|
/// \brief User name
|
||||||
|
/// \param args_ Command arguments
|
||||||
void USER (char const *args_);
|
void USER (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Map of command handlers
|
||||||
static std::vector<std::pair<std::string_view, void (FtpSession::*) (char const *)>> const
|
static std::vector<std::pair<std::string_view, void (FtpSession::*) (char const *)>> const
|
||||||
handlers;
|
handlers;
|
||||||
};
|
};
|
||||||
|
@ -23,29 +23,66 @@
|
|||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
/// \brief I/O buffer
|
||||||
|
/// [unusable][usedArea][freeArea]
|
||||||
class IOBuffer
|
class IOBuffer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
~IOBuffer ();
|
~IOBuffer ();
|
||||||
|
|
||||||
|
/// \brief Parameterized constructor
|
||||||
|
/// \param size_ Buffer size
|
||||||
IOBuffer (std::size_t size_);
|
IOBuffer (std::size_t size_);
|
||||||
|
|
||||||
|
/// \brief Get pointer to writable area
|
||||||
char *freeArea () const;
|
char *freeArea () const;
|
||||||
|
/// \brief Get size of writable area
|
||||||
std::size_t freeSize () const;
|
std::size_t freeSize () const;
|
||||||
void markFree (std::size_t size_);
|
|
||||||
|
|
||||||
|
/// \brief Get pointer to readable area
|
||||||
char *usedArea () const;
|
char *usedArea () const;
|
||||||
|
/// \brief Get size of readable area
|
||||||
std::size_t usedSize () const;
|
std::size_t usedSize () const;
|
||||||
|
|
||||||
|
/// \brief Consume data from the beginning of usedArea
|
||||||
|
/// \param size_ Size to consume
|
||||||
|
/// [unusable][+++++usedArea][freeArea]
|
||||||
|
/// becomes
|
||||||
|
/// [unusable+++++][usedArea][freeArea]
|
||||||
|
void markFree (std::size_t size_);
|
||||||
|
/// \brief Produce data to the end of usedArea from freeArea
|
||||||
|
/// [unusable][usedArea][++++++freeArea]
|
||||||
|
/// becomes
|
||||||
|
/// [unusable][usedArea++++++][freeArea]
|
||||||
void markUsed (std::size_t size_);
|
void markUsed (std::size_t size_);
|
||||||
|
|
||||||
|
/// \brief Whether usedArea is empty
|
||||||
bool empty () const;
|
bool empty () const;
|
||||||
|
|
||||||
|
/// \brief Get buffer capacity
|
||||||
std::size_t capacity () const;
|
std::size_t capacity () const;
|
||||||
|
|
||||||
|
/// \brief Clear buffer; usedArea becomes empty
|
||||||
|
/// [unusable][usedArea][++++++freeArea]
|
||||||
|
/// becomes
|
||||||
|
/// [freeArea++++++++++++++++++++++++++]
|
||||||
void clear ();
|
void clear ();
|
||||||
|
|
||||||
|
/// \brief Move usedArea to the beginning of the buffer
|
||||||
|
/// [unusable][usedArea][freeArea]
|
||||||
|
/// becomes
|
||||||
|
/// [usedArea][freeArea++++++++++]
|
||||||
void coalesce ();
|
void coalesce ();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// \brief Buffer
|
||||||
std::unique_ptr<char[]> m_buffer;
|
std::unique_ptr<char[]> m_buffer;
|
||||||
|
|
||||||
|
/// \brief Buffer size
|
||||||
std::size_t const m_size;
|
std::size_t const m_size;
|
||||||
|
|
||||||
|
/// \brief Start of usedArea
|
||||||
std::size_t m_start = 0;
|
std::size_t m_start = 0;
|
||||||
std::size_t m_end = 0;
|
/// \brief Start of freeArea
|
||||||
|
std::size_t m_end = 0;
|
||||||
};
|
};
|
||||||
|
@ -32,9 +32,11 @@ class Log;
|
|||||||
using SharedLog = std::shared_ptr<Log>;
|
using SharedLog = std::shared_ptr<Log>;
|
||||||
using WeakLog = std::weak_ptr<Log>;
|
using WeakLog = std::weak_ptr<Log>;
|
||||||
|
|
||||||
|
/// \brief Log object
|
||||||
class Log
|
class Log
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/// \brief Log level
|
||||||
enum Level
|
enum Level
|
||||||
{
|
{
|
||||||
DEBUG,
|
DEBUG,
|
||||||
@ -46,36 +48,72 @@ public:
|
|||||||
|
|
||||||
~Log ();
|
~Log ();
|
||||||
|
|
||||||
|
/// \brief Draw log
|
||||||
void draw ();
|
void draw ();
|
||||||
|
|
||||||
|
/// \brief Create log
|
||||||
static SharedLog create ();
|
static SharedLog create ();
|
||||||
|
|
||||||
|
/// \brief Bind log
|
||||||
|
/// \param log_ Log to bind
|
||||||
static void bind (SharedLog log_);
|
static void bind (SharedLog log_);
|
||||||
|
|
||||||
|
/// \brief Add debug message to bound log
|
||||||
|
/// \param fmt_ Message format
|
||||||
__attribute__ ((format (printf, 1, 2))) static void debug (char const *fmt_, ...);
|
__attribute__ ((format (printf, 1, 2))) static void debug (char const *fmt_, ...);
|
||||||
|
/// \brief Add info message to bound log
|
||||||
|
/// \param fmt_ Message format
|
||||||
__attribute__ ((format (printf, 1, 2))) static void info (char const *fmt_, ...);
|
__attribute__ ((format (printf, 1, 2))) static void info (char const *fmt_, ...);
|
||||||
|
/// \brief Add error message to bound log
|
||||||
|
/// \param fmt_ Message format
|
||||||
__attribute__ ((format (printf, 1, 2))) static void error (char const *fmt_, ...);
|
__attribute__ ((format (printf, 1, 2))) static void error (char const *fmt_, ...);
|
||||||
|
/// \brief Add command message to bound log
|
||||||
|
/// \param fmt_ Message format
|
||||||
__attribute__ ((format (printf, 1, 2))) static void command (char const *fmt_, ...);
|
__attribute__ ((format (printf, 1, 2))) static void command (char const *fmt_, ...);
|
||||||
|
/// \brief Add response message to bound log
|
||||||
|
/// \param fmt_ Message format
|
||||||
__attribute__ ((format (printf, 1, 2))) static void response (char const *fmt_, ...);
|
__attribute__ ((format (printf, 1, 2))) static void response (char const *fmt_, ...);
|
||||||
|
|
||||||
|
/// \brief Add log message to bound log
|
||||||
|
/// \param level_ Log level
|
||||||
|
/// \param fmt_ Message format
|
||||||
|
/// \param ap_ Message arguments
|
||||||
static void log (Level level_, char const *fmt_, va_list ap_);
|
static void log (Level level_, char const *fmt_, va_list ap_);
|
||||||
|
|
||||||
|
/// \brief Add log message to bound log
|
||||||
|
/// \param level_ Log level
|
||||||
|
/// \param message_ Message to log
|
||||||
static void log (Level level_, std::string_view message_);
|
static void log (Level level_, std::string_view message_);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Log ();
|
Log ();
|
||||||
|
|
||||||
|
/// \brief Add log message
|
||||||
|
/// \param level_ Log level
|
||||||
|
/// \param fmt_ Message format
|
||||||
|
/// \param ap_ Message arguments
|
||||||
void _log (Level level_, char const *fmt_, va_list ap_);
|
void _log (Level level_, char const *fmt_, va_list ap_);
|
||||||
|
|
||||||
|
/// \brief Log message
|
||||||
struct Message
|
struct Message
|
||||||
{
|
{
|
||||||
|
/// \brief Parameterized constructor
|
||||||
|
/// \param level_ Log level
|
||||||
|
/// \param message_ Log message
|
||||||
Message (Level const level_, std::string message_)
|
Message (Level const level_, std::string message_)
|
||||||
: level (level_), message (std::move (message_))
|
: level (level_), message (std::move (message_))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Log level
|
||||||
Level level;
|
Level level;
|
||||||
|
/// \brief Log message
|
||||||
std::string message;
|
std::string message;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// \brief Log messages
|
||||||
std::vector<Message> m_messages;
|
std::vector<Message> m_messages;
|
||||||
|
|
||||||
|
/// \brief Log lock
|
||||||
platform::Mutex m_lock;
|
platform::Mutex m_lock;
|
||||||
};
|
};
|
||||||
|
@ -31,65 +31,102 @@
|
|||||||
|
|
||||||
namespace platform
|
namespace platform
|
||||||
{
|
{
|
||||||
|
/// \brief Initialize platform
|
||||||
bool init ();
|
bool init ();
|
||||||
|
|
||||||
|
/// \brief Platform loop
|
||||||
bool loop ();
|
bool loop ();
|
||||||
|
|
||||||
|
/// \brief Platform render
|
||||||
void render ();
|
void render ();
|
||||||
|
|
||||||
|
/// \brief Deinitialize platform
|
||||||
void exit ();
|
void exit ();
|
||||||
|
|
||||||
#ifdef _3DS
|
#ifdef _3DS
|
||||||
|
/// \brief Steady clock
|
||||||
struct steady_clock
|
struct steady_clock
|
||||||
{
|
{
|
||||||
using rep = std::uint64_t;
|
/// \brief Type representing number of ticks
|
||||||
using period = std::ratio<1, SYSCLOCK_ARM11>;
|
using rep = std::uint64_t;
|
||||||
using duration = std::chrono::duration<rep, period>;
|
|
||||||
|
/// \brief Type representing ratio of clock period in seconds
|
||||||
|
using period = std::ratio<1, SYSCLOCK_ARM11>;
|
||||||
|
|
||||||
|
/// \brief Duration type
|
||||||
|
using duration = std::chrono::duration<rep, period>;
|
||||||
|
|
||||||
|
/// \brief Timestamp type
|
||||||
using time_point = std::chrono::time_point<steady_clock>;
|
using time_point = std::chrono::time_point<steady_clock>;
|
||||||
|
|
||||||
|
/// \brief Whether clock is steady
|
||||||
constexpr static bool is_steady = true;
|
constexpr static bool is_steady = true;
|
||||||
|
|
||||||
|
/// \brief Current timestamp
|
||||||
static time_point now () noexcept
|
static time_point now () noexcept
|
||||||
{
|
{
|
||||||
return time_point (duration (svcGetSystemTick ()));
|
return time_point (duration (svcGetSystemTick ()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
#else
|
#else
|
||||||
|
/// \brief Steady clock
|
||||||
using steady_clock = std::chrono::steady_clock;
|
using steady_clock = std::chrono::steady_clock;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/// \brief Platform thread
|
||||||
class Thread
|
class Thread
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
~Thread ();
|
~Thread ();
|
||||||
Thread ();
|
Thread ();
|
||||||
|
|
||||||
|
/// \brief Parameterized constructor
|
||||||
|
/// \param func_ Thread entrypoint
|
||||||
Thread (std::function<void ()> func_);
|
Thread (std::function<void ()> func_);
|
||||||
|
|
||||||
Thread (Thread const &that_) = delete;
|
Thread (Thread const &that_) = delete;
|
||||||
|
|
||||||
|
/// \brief Move constructor
|
||||||
|
/// \param that_ Object to move from
|
||||||
Thread (Thread &&that_);
|
Thread (Thread &&that_);
|
||||||
|
|
||||||
Thread &operator= (Thread const &that_) = delete;
|
Thread &operator= (Thread const &that_) = delete;
|
||||||
|
|
||||||
|
/// \brief Move assignment
|
||||||
|
/// \param that_ Object to move from
|
||||||
Thread &operator= (Thread &&that_);
|
Thread &operator= (Thread &&that_);
|
||||||
|
|
||||||
|
/// \brief Join thread
|
||||||
void join ();
|
void join ();
|
||||||
|
|
||||||
|
/// \brief Suspend current thread
|
||||||
|
/// \param timeout_ Minimum time to sleep
|
||||||
static void sleep (std::chrono::milliseconds timeout_);
|
static void sleep (std::chrono::milliseconds timeout_);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class privateData_t;
|
class privateData_t;
|
||||||
|
|
||||||
|
/// \brief pimpl
|
||||||
std::unique_ptr<privateData_t> m_d;
|
std::unique_ptr<privateData_t> m_d;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// \brief Platform mutex
|
||||||
class Mutex
|
class Mutex
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
~Mutex ();
|
~Mutex ();
|
||||||
Mutex ();
|
Mutex ();
|
||||||
|
|
||||||
|
/// \brief Lock mutex
|
||||||
void lock ();
|
void lock ();
|
||||||
|
|
||||||
|
/// \brief Unlock mutex
|
||||||
void unlock ();
|
void unlock ();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class privateData_t;
|
class privateData_t;
|
||||||
|
|
||||||
|
/// \brief pimpl
|
||||||
std::unique_ptr<privateData_t> m_d;
|
std::unique_ptr<privateData_t> m_d;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
|
/// \brief Socket address
|
||||||
class SockAddr
|
class SockAddr
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -32,39 +33,72 @@ public:
|
|||||||
|
|
||||||
SockAddr ();
|
SockAddr ();
|
||||||
|
|
||||||
|
/// \brief Copy constructor
|
||||||
|
/// \param that_ Object to copy
|
||||||
SockAddr (SockAddr const &that_);
|
SockAddr (SockAddr const &that_);
|
||||||
|
|
||||||
|
/// \brief Move constructor
|
||||||
|
/// \param that_ Object to move from
|
||||||
SockAddr (SockAddr &&that_);
|
SockAddr (SockAddr &&that_);
|
||||||
|
|
||||||
|
/// \brief Copy assignment
|
||||||
|
/// \param that_ Object to copy
|
||||||
SockAddr &operator= (SockAddr const &that_);
|
SockAddr &operator= (SockAddr const &that_);
|
||||||
|
|
||||||
|
/// \brief Move assignment
|
||||||
|
/// \param that_ Object to move from
|
||||||
SockAddr &operator= (SockAddr &&that_);
|
SockAddr &operator= (SockAddr &&that_);
|
||||||
|
|
||||||
|
/// \param Parameterized constructor
|
||||||
|
/// \param addr_ Address
|
||||||
SockAddr (struct sockaddr const &addr_);
|
SockAddr (struct sockaddr const &addr_);
|
||||||
|
|
||||||
|
/// \param Parameterized constructor
|
||||||
|
/// \param addr_ Address
|
||||||
SockAddr (struct sockaddr_in const &addr_);
|
SockAddr (struct sockaddr_in const &addr_);
|
||||||
|
|
||||||
#ifndef _3DS
|
#ifndef _3DS
|
||||||
|
/// \param Parameterized constructor
|
||||||
|
/// \param addr_ Address
|
||||||
SockAddr (struct sockaddr_in6 const &addr_);
|
SockAddr (struct sockaddr_in6 const &addr_);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/// \param Parameterized constructor
|
||||||
|
/// \param addr_ Address
|
||||||
SockAddr (struct sockaddr_storage const &addr_);
|
SockAddr (struct sockaddr_storage const &addr_);
|
||||||
|
|
||||||
|
/// \param sockaddr_in cast operator
|
||||||
operator struct sockaddr_in const & () const;
|
operator struct sockaddr_in const & () const;
|
||||||
|
|
||||||
#ifndef _3DS
|
#ifndef _3DS
|
||||||
|
/// \param sockaddr_in6 cast operator
|
||||||
operator struct sockaddr_in6 const & () const;
|
operator struct sockaddr_in6 const & () const;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/// \param sockaddr_storage cast operator
|
||||||
operator struct sockaddr_storage const & () const;
|
operator struct sockaddr_storage const & () const;
|
||||||
|
|
||||||
|
/// \param sockaddr* cast operator
|
||||||
operator struct sockaddr * ();
|
operator struct sockaddr * ();
|
||||||
|
/// \param sockaddr const* cast operator
|
||||||
operator struct sockaddr const * () const;
|
operator struct sockaddr const * () const;
|
||||||
|
|
||||||
|
/// \brief Address port
|
||||||
std::uint16_t port () const;
|
std::uint16_t port () const;
|
||||||
|
|
||||||
|
/// \brief Address name
|
||||||
|
/// \param buffer_
|
||||||
|
/// \param size_ Size of buffer_
|
||||||
|
/// \retval buffer_ success
|
||||||
|
/// \retval nullptr failure
|
||||||
char const *name (char *buffer_, std::size_t size_) const;
|
char const *name (char *buffer_, std::size_t size_) const;
|
||||||
|
|
||||||
|
/// \brief Address name
|
||||||
|
/// \retval nullptr failure
|
||||||
|
/// \note This function is not reentrant
|
||||||
char const *name () const;
|
char const *name () const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// \brief Address storage
|
||||||
struct sockaddr_storage m_addr = {};
|
struct sockaddr_storage m_addr = {};
|
||||||
};
|
};
|
||||||
|
@ -30,53 +30,115 @@ class Socket;
|
|||||||
using UniqueSocket = std::unique_ptr<Socket>;
|
using UniqueSocket = std::unique_ptr<Socket>;
|
||||||
using SharedSocket = std::shared_ptr<Socket>;
|
using SharedSocket = std::shared_ptr<Socket>;
|
||||||
|
|
||||||
|
/// \brief Socket object
|
||||||
class Socket
|
class Socket
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/// \brief Poll info
|
||||||
struct PollInfo
|
struct PollInfo
|
||||||
{
|
{
|
||||||
|
/// \brief Socket to poll
|
||||||
std::reference_wrapper<Socket> socket;
|
std::reference_wrapper<Socket> socket;
|
||||||
|
|
||||||
|
/// \brief Input events
|
||||||
int events;
|
int events;
|
||||||
|
|
||||||
|
/// \brief Output events
|
||||||
int revents;
|
int revents;
|
||||||
};
|
};
|
||||||
|
|
||||||
~Socket ();
|
~Socket ();
|
||||||
|
|
||||||
|
/// \brief Accept connection
|
||||||
UniqueSocket accept ();
|
UniqueSocket accept ();
|
||||||
|
|
||||||
|
/// \brief Whether socket is at out-of-band mark
|
||||||
int atMark ();
|
int atMark ();
|
||||||
|
|
||||||
|
/// \brief Bind socket to address
|
||||||
|
/// \param addr_ Address to bind
|
||||||
bool bind (SockAddr const &addr_);
|
bool bind (SockAddr const &addr_);
|
||||||
|
|
||||||
|
/// \brief Connect to a peer
|
||||||
|
/// \param addr_ Peer address
|
||||||
bool connect (SockAddr const &addr_);
|
bool connect (SockAddr const &addr_);
|
||||||
|
|
||||||
|
/// \brief Listen for connections
|
||||||
|
/// \param backlog_ Queue size for incoming connections
|
||||||
bool listen (int backlog_);
|
bool listen (int backlog_);
|
||||||
|
|
||||||
|
/// \brief Shutdown socket
|
||||||
|
/// \param how_ Type of shutdown (\sa ::shutdown)
|
||||||
bool shutdown (int how_);
|
bool shutdown (int how_);
|
||||||
|
|
||||||
|
/// \brief Set linger option
|
||||||
|
/// \param enable_ Whether to enable linger
|
||||||
|
/// \param time_ Linger timeout
|
||||||
bool setLinger (bool enable_, std::chrono::seconds time_);
|
bool setLinger (bool enable_, std::chrono::seconds time_);
|
||||||
|
|
||||||
|
/// \brief Set non-blocking
|
||||||
|
/// \param nonBlocking_ Whether to set non-blocking
|
||||||
bool setNonBlocking (bool nonBlocking_ = true);
|
bool setNonBlocking (bool nonBlocking_ = true);
|
||||||
|
|
||||||
|
/// \brief Set reuse address in subsequent bind
|
||||||
|
/// \param reuse_ Whether to reuse address
|
||||||
bool setReuseAddress (bool reuse_ = true);
|
bool setReuseAddress (bool reuse_ = true);
|
||||||
|
|
||||||
|
/// \brief Set recv buffer size
|
||||||
|
/// \param size_ Buffer size
|
||||||
bool setRecvBufferSize (std::size_t size_);
|
bool setRecvBufferSize (std::size_t size_);
|
||||||
|
|
||||||
|
/// \brief Set send buffer size
|
||||||
|
/// \param size_ Buffer size
|
||||||
bool setSendBufferSize (std::size_t size_);
|
bool setSendBufferSize (std::size_t size_);
|
||||||
|
|
||||||
|
/// \brief Read data
|
||||||
|
/// \param buffer_ Output buffer
|
||||||
|
/// \param size_ Size to read
|
||||||
|
/// \param oob_ Whether to read from out-of-band
|
||||||
ssize_t read (void *buffer_, std::size_t size_, bool oob_ = false);
|
ssize_t read (void *buffer_, std::size_t size_, bool oob_ = false);
|
||||||
|
|
||||||
|
/// \brief Read data
|
||||||
|
/// \param buffer_ Output buffer
|
||||||
|
/// \param size_ Size to read
|
||||||
|
/// \param oob_ Whether to read from out-of-band
|
||||||
ssize_t read (IOBuffer &buffer_, bool oob_ = false);
|
ssize_t read (IOBuffer &buffer_, bool oob_ = false);
|
||||||
|
|
||||||
|
/// \brief Write data
|
||||||
|
/// \param buffer_ Input buffer
|
||||||
|
/// \param size_ Size to write
|
||||||
ssize_t write (void const *buffer_, std::size_t size_);
|
ssize_t write (void const *buffer_, std::size_t size_);
|
||||||
|
|
||||||
|
/// \brief Write data
|
||||||
|
/// \param buffer_ Input buffer
|
||||||
|
/// \param size_ Size to write
|
||||||
ssize_t write (IOBuffer &buffer_);
|
ssize_t write (IOBuffer &buffer_);
|
||||||
|
|
||||||
|
/// \brief Local name
|
||||||
SockAddr const &sockName () const;
|
SockAddr const &sockName () const;
|
||||||
|
/// \brief Peer name
|
||||||
SockAddr const &peerName () const;
|
SockAddr const &peerName () const;
|
||||||
|
|
||||||
|
/// \brief Create socket
|
||||||
static UniqueSocket create ();
|
static UniqueSocket create ();
|
||||||
|
|
||||||
|
/// \brief Poll sockets
|
||||||
|
/// \param info_ Poll info
|
||||||
|
/// \param count_ Number of poll entries
|
||||||
|
/// \param timeout_ Poll timeout
|
||||||
static int poll (PollInfo *info_, std::size_t count_, std::chrono::milliseconds timeout_);
|
static int poll (PollInfo *info_, std::size_t count_, std::chrono::milliseconds timeout_);
|
||||||
|
|
||||||
int fd () const
|
|
||||||
{
|
|
||||||
return m_fd;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Socket () = delete;
|
Socket () = delete;
|
||||||
|
|
||||||
|
/// \brief Parameterized constructor
|
||||||
|
/// \param fd_ Socket fd
|
||||||
Socket (int fd_);
|
Socket (int fd_);
|
||||||
|
|
||||||
|
/// \brief Parameterized constructor
|
||||||
|
/// \param fd_ Socket fd
|
||||||
|
/// \param sockName_ Local name
|
||||||
|
/// \param peerName_ Peer name
|
||||||
Socket (int fd_, SockAddr const &sockName_, SockAddr const &peerName_);
|
Socket (int fd_, SockAddr const &sockName_, SockAddr const &peerName_);
|
||||||
|
|
||||||
Socket (Socket const &that_) = delete;
|
Socket (Socket const &that_) = delete;
|
||||||
@ -87,11 +149,17 @@ private:
|
|||||||
|
|
||||||
Socket &operator= (Socket &&that_) = delete;
|
Socket &operator= (Socket &&that_) = delete;
|
||||||
|
|
||||||
|
/// \param Local name
|
||||||
SockAddr m_sockName;
|
SockAddr m_sockName;
|
||||||
|
/// \param Peer name
|
||||||
SockAddr m_peerName;
|
SockAddr m_peerName;
|
||||||
|
|
||||||
|
/// \param Socket fd
|
||||||
int const m_fd;
|
int const m_fd;
|
||||||
|
|
||||||
|
/// \param Whether listening
|
||||||
bool m_listening : 1;
|
bool m_listening : 1;
|
||||||
|
|
||||||
|
/// \param Whether connected
|
||||||
bool m_connected : 1;
|
bool m_connected : 1;
|
||||||
};
|
};
|
||||||
|
@ -33,37 +33,59 @@
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
/// \brief 3DS font glyph ranges
|
||||||
std::vector<ImWchar> s_fontRanges;
|
std::vector<ImWchar> s_fontRanges;
|
||||||
|
|
||||||
|
/// \brief Clear color
|
||||||
constexpr auto CLEAR_COLOR = 0x204B7AFF;
|
constexpr auto CLEAR_COLOR = 0x204B7AFF;
|
||||||
|
|
||||||
|
/// \brief Display transfer flags
|
||||||
constexpr auto DISPLAY_TRANSFER_FLAGS =
|
constexpr auto DISPLAY_TRANSFER_FLAGS =
|
||||||
GX_TRANSFER_FLIP_VERT (0) | GX_TRANSFER_OUT_TILED (0) | GX_TRANSFER_RAW_COPY (0) |
|
GX_TRANSFER_FLIP_VERT (0) | GX_TRANSFER_OUT_TILED (0) | GX_TRANSFER_RAW_COPY (0) |
|
||||||
GX_TRANSFER_IN_FORMAT (GX_TRANSFER_FMT_RGBA8) | GX_TRANSFER_OUT_FORMAT (GX_TRANSFER_FMT_RGB8) |
|
GX_TRANSFER_IN_FORMAT (GX_TRANSFER_FMT_RGBA8) | GX_TRANSFER_OUT_FORMAT (GX_TRANSFER_FMT_RGB8) |
|
||||||
GX_TRANSFER_SCALING (GX_TRANSFER_SCALE_NO);
|
GX_TRANSFER_SCALING (GX_TRANSFER_SCALE_NO);
|
||||||
|
|
||||||
C3D_RenderTarget *s_top = nullptr;
|
/// \brief Top screen render target
|
||||||
|
C3D_RenderTarget *s_top = nullptr;
|
||||||
|
/// \brief Bottom screen render target
|
||||||
C3D_RenderTarget *s_bottom = nullptr;
|
C3D_RenderTarget *s_bottom = nullptr;
|
||||||
|
|
||||||
|
/// \brief Vertex shader
|
||||||
DVLB_s *s_vsh = nullptr;
|
DVLB_s *s_vsh = nullptr;
|
||||||
|
/// \brief Vertex shader program
|
||||||
shaderProgram_s s_program;
|
shaderProgram_s s_program;
|
||||||
|
|
||||||
|
/// \brief Projection matrix uniform location
|
||||||
int s_projLocation;
|
int s_projLocation;
|
||||||
|
/// \brief Top screen projection matrix
|
||||||
C3D_Mtx s_projTop;
|
C3D_Mtx s_projTop;
|
||||||
|
/// \brief Bottom screen projection matrix
|
||||||
C3D_Mtx s_projBottom;
|
C3D_Mtx s_projBottom;
|
||||||
|
|
||||||
|
/// \brief System font textures
|
||||||
std::vector<C3D_Tex> s_fontTextures;
|
std::vector<C3D_Tex> s_fontTextures;
|
||||||
|
/// \brief Text scale
|
||||||
float s_textScale;
|
float s_textScale;
|
||||||
|
|
||||||
|
/// \brief Scissor test bounds
|
||||||
std::uint32_t s_boundScissor[4];
|
std::uint32_t s_boundScissor[4];
|
||||||
|
/// \brief Currently bound vertex data
|
||||||
ImDrawVert *s_boundVtxData;
|
ImDrawVert *s_boundVtxData;
|
||||||
|
/// \brief Currently bound texture
|
||||||
C3D_Tex *s_boundTexture;
|
C3D_Tex *s_boundTexture;
|
||||||
|
|
||||||
|
/// \brief Vertex data buffer
|
||||||
ImDrawVert *s_vtxData = nullptr;
|
ImDrawVert *s_vtxData = nullptr;
|
||||||
|
/// \brief Size of vertex data buffer
|
||||||
std::size_t s_vtxSize = 0;
|
std::size_t s_vtxSize = 0;
|
||||||
ImDrawIdx *s_idxData = nullptr;
|
/// \brief Index data buffer
|
||||||
|
ImDrawIdx *s_idxData = nullptr;
|
||||||
|
/// \brief Size of index data buffer
|
||||||
std::size_t s_idxSize = 0;
|
std::size_t s_idxSize = 0;
|
||||||
|
|
||||||
|
/// \brief Get code point from glyph index
|
||||||
|
/// \param font_ Font to search
|
||||||
|
/// \param glyphIndex_ Glyph index
|
||||||
std::uint32_t fontCodePointFromGlyphIndex (CFNT_s *const font_, int const glyphIndex_)
|
std::uint32_t fontCodePointFromGlyphIndex (CFNT_s *const font_, int const glyphIndex_)
|
||||||
{
|
{
|
||||||
for (auto cmap = fontGetInfo (font_)->cmap; cmap; cmap = cmap->next)
|
for (auto cmap = fontGetInfo (font_)->cmap; cmap; cmap = cmap->next)
|
||||||
@ -100,8 +122,11 @@ std::uint32_t fontCodePointFromGlyphIndex (CFNT_s *const font_, int const glyphI
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Setup render state
|
||||||
|
/// \param screen_ Whether top or bottom screen
|
||||||
void setupRenderState (gfxScreen_t const screen_)
|
void setupRenderState (gfxScreen_t const screen_)
|
||||||
{
|
{
|
||||||
|
// disable face culling
|
||||||
C3D_CullFace (GPU_CULL_NONE);
|
C3D_CullFace (GPU_CULL_NONE);
|
||||||
|
|
||||||
// configure attributes for user with vertex shader
|
// configure attributes for user with vertex shader
|
||||||
@ -111,14 +136,18 @@ void setupRenderState (gfxScreen_t const screen_)
|
|||||||
AttrInfo_AddLoader (attrInfo, 1, GPU_FLOAT, 2); // v1 = inUv
|
AttrInfo_AddLoader (attrInfo, 1, GPU_FLOAT, 2); // v1 = inUv
|
||||||
AttrInfo_AddLoader (attrInfo, 2, GPU_UNSIGNED_BYTE, 4); // v2 = inColor
|
AttrInfo_AddLoader (attrInfo, 2, GPU_UNSIGNED_BYTE, 4); // v2 = inColor
|
||||||
|
|
||||||
|
// clear bindings
|
||||||
std::memset (s_boundScissor, 0xFF, sizeof (s_boundScissor));
|
std::memset (s_boundScissor, 0xFF, sizeof (s_boundScissor));
|
||||||
s_boundVtxData = nullptr;
|
s_boundVtxData = nullptr;
|
||||||
s_boundTexture = nullptr;
|
s_boundTexture = nullptr;
|
||||||
|
|
||||||
|
// bind program
|
||||||
C3D_BindProgram (&s_program);
|
C3D_BindProgram (&s_program);
|
||||||
|
|
||||||
|
// enable depth test
|
||||||
C3D_DepthTest (true, GPU_GREATER, GPU_WRITE_COLOR);
|
C3D_DepthTest (true, GPU_GREATER, GPU_WRITE_COLOR);
|
||||||
|
|
||||||
|
// enable alpha blending
|
||||||
C3D_AlphaBlend (GPU_BLEND_ADD,
|
C3D_AlphaBlend (GPU_BLEND_ADD,
|
||||||
GPU_BLEND_ADD,
|
GPU_BLEND_ADD,
|
||||||
GPU_SRC_ALPHA,
|
GPU_SRC_ALPHA,
|
||||||
@ -126,6 +155,7 @@ void setupRenderState (gfxScreen_t const screen_)
|
|||||||
GPU_SRC_ALPHA,
|
GPU_SRC_ALPHA,
|
||||||
GPU_ONE_MINUS_SRC_ALPHA);
|
GPU_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
|
// apply projection matrix
|
||||||
if (screen_ == GFX_TOP)
|
if (screen_ == GFX_TOP)
|
||||||
C3D_FVUnifMtx4x4 (GPU_VERTEX_SHADER, s_projLocation, &s_projTop);
|
C3D_FVUnifMtx4x4 (GPU_VERTEX_SHADER, s_projLocation, &s_projTop);
|
||||||
else
|
else
|
||||||
@ -135,36 +165,46 @@ void setupRenderState (gfxScreen_t const screen_)
|
|||||||
|
|
||||||
void imgui::citro3d::init ()
|
void imgui::citro3d::init ()
|
||||||
{
|
{
|
||||||
// Setup back-end capabilities flags
|
// setup back-end capabilities flags
|
||||||
ImGuiIO &io = ImGui::GetIO ();
|
ImGuiIO &io = ImGui::GetIO ();
|
||||||
|
|
||||||
io.BackendRendererName = "citro3d";
|
io.BackendRendererName = "citro3d";
|
||||||
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
|
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
|
||||||
|
|
||||||
|
// initialize citro3d
|
||||||
C3D_Init (C3D_DEFAULT_CMDBUF_SIZE);
|
C3D_Init (C3D_DEFAULT_CMDBUF_SIZE);
|
||||||
|
|
||||||
|
// create top screen render target
|
||||||
s_top = C3D_RenderTargetCreate (240, 400, GPU_RB_RGBA8, GPU_RB_DEPTH24_STENCIL8);
|
s_top = C3D_RenderTargetCreate (240, 400, GPU_RB_RGBA8, GPU_RB_DEPTH24_STENCIL8);
|
||||||
C3D_RenderTargetSetOutput (s_top, GFX_TOP, GFX_LEFT, DISPLAY_TRANSFER_FLAGS);
|
C3D_RenderTargetSetOutput (s_top, GFX_TOP, GFX_LEFT, DISPLAY_TRANSFER_FLAGS);
|
||||||
|
|
||||||
|
// create bottom screen render target
|
||||||
s_bottom = C3D_RenderTargetCreate (240, 320, GPU_RB_RGBA8, GPU_RB_DEPTH24_STENCIL8);
|
s_bottom = C3D_RenderTargetCreate (240, 320, GPU_RB_RGBA8, GPU_RB_DEPTH24_STENCIL8);
|
||||||
C3D_RenderTargetSetOutput (s_bottom, GFX_BOTTOM, GFX_LEFT, DISPLAY_TRANSFER_FLAGS);
|
C3D_RenderTargetSetOutput (s_bottom, GFX_BOTTOM, GFX_LEFT, DISPLAY_TRANSFER_FLAGS);
|
||||||
|
|
||||||
|
// load vertex shader
|
||||||
s_vsh = DVLB_ParseFile (
|
s_vsh = DVLB_ParseFile (
|
||||||
const_cast<std::uint32_t *> (reinterpret_cast<std::uint32_t const *> (vshader_shbin)),
|
const_cast<std::uint32_t *> (reinterpret_cast<std::uint32_t const *> (vshader_shbin)),
|
||||||
vshader_shbin_size);
|
vshader_shbin_size);
|
||||||
|
|
||||||
|
// initialize vertex shader program
|
||||||
shaderProgramInit (&s_program);
|
shaderProgramInit (&s_program);
|
||||||
shaderProgramSetVsh (&s_program, &s_vsh->DVLE[0]);
|
shaderProgramSetVsh (&s_program, &s_vsh->DVLE[0]);
|
||||||
|
|
||||||
|
// get projection matrix uniform location
|
||||||
s_projLocation = shaderInstanceGetUniformLocation (s_program.vertexShader, "proj");
|
s_projLocation = shaderInstanceGetUniformLocation (s_program.vertexShader, "proj");
|
||||||
|
|
||||||
|
// initialize projection matrices
|
||||||
Mtx_OrthoTilt (&s_projTop, 0.0f, 800.0f, 480.0f, 0.0f, -1.0f, 1.0f, false);
|
Mtx_OrthoTilt (&s_projTop, 0.0f, 800.0f, 480.0f, 0.0f, -1.0f, 1.0f, false);
|
||||||
Mtx_OrthoTilt (&s_projBottom, 80.0f, 720.0f, 960.0f, 480.0f, -1.0f, 1.0f, false);
|
Mtx_OrthoTilt (&s_projBottom, 80.0f, 720.0f, 960.0f, 480.0f, -1.0f, 1.0f, false);
|
||||||
|
|
||||||
|
// allocate vertex data buffer
|
||||||
s_vtxSize = 65536;
|
s_vtxSize = 65536;
|
||||||
s_vtxData = reinterpret_cast<ImDrawVert *> (linearAlloc (sizeof (ImDrawVert) * s_vtxSize));
|
s_vtxData = reinterpret_cast<ImDrawVert *> (linearAlloc (sizeof (ImDrawVert) * s_vtxSize));
|
||||||
if (!s_vtxData)
|
if (!s_vtxData)
|
||||||
svcBreak (USERBREAK_PANIC);
|
svcBreak (USERBREAK_PANIC);
|
||||||
|
|
||||||
|
// allocate index data buffer
|
||||||
s_idxSize = 65536;
|
s_idxSize = 65536;
|
||||||
s_idxData = reinterpret_cast<ImDrawIdx *> (linearAlloc (sizeof (ImDrawIdx) * s_idxSize));
|
s_idxData = reinterpret_cast<ImDrawIdx *> (linearAlloc (sizeof (ImDrawIdx) * s_idxSize));
|
||||||
if (!s_idxData)
|
if (!s_idxData)
|
||||||
@ -184,6 +224,7 @@ void imgui::citro3d::init ()
|
|||||||
|
|
||||||
s_textScale = 30.0f / glyphInfo->cellHeight;
|
s_textScale = 30.0f / glyphInfo->cellHeight;
|
||||||
|
|
||||||
|
// use system font sheets as citro3d textures
|
||||||
for (unsigned i = 0; i < glyphInfo->nSheets; ++i)
|
for (unsigned i = 0; i < glyphInfo->nSheets; ++i)
|
||||||
{
|
{
|
||||||
auto &tex = s_fontTextures[i];
|
auto &tex = s_fontTextures[i];
|
||||||
@ -201,9 +242,11 @@ void imgui::citro3d::init ()
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
// create texture for ImGui's white pixel
|
||||||
auto &tex = s_fontTextures[glyphInfo->nSheets];
|
auto &tex = s_fontTextures[glyphInfo->nSheets];
|
||||||
C3D_TexInit (&tex, 8, 8, GPU_A4);
|
C3D_TexInit (&tex, 8, 8, GPU_A4);
|
||||||
|
|
||||||
|
// fill texture with full alpha
|
||||||
std::uint32_t size;
|
std::uint32_t size;
|
||||||
auto data = C3D_Tex2DGetImagePtr (&tex, 0, &size);
|
auto data = C3D_Tex2DGetImagePtr (&tex, 0, &size);
|
||||||
if (!data || !size)
|
if (!data || !size)
|
||||||
@ -211,10 +254,12 @@ void imgui::citro3d::init ()
|
|||||||
std::memset (data, 0xFF, size);
|
std::memset (data, 0xFF, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get alternate character glyph
|
||||||
ImWchar alterChar = fontCodePointFromGlyphIndex (font, fontInfo->alterCharIndex);
|
ImWchar alterChar = fontCodePointFromGlyphIndex (font, fontInfo->alterCharIndex);
|
||||||
if (!alterChar)
|
if (!alterChar)
|
||||||
alterChar = '?';
|
alterChar = '?';
|
||||||
|
|
||||||
|
// collect character map
|
||||||
std::vector<ImWchar> charSet;
|
std::vector<ImWchar> charSet;
|
||||||
for (auto cmap = fontInfo->cmap; cmap; cmap = cmap->next)
|
for (auto cmap = fontInfo->cmap; cmap; cmap = cmap->next)
|
||||||
{
|
{
|
||||||
@ -242,9 +287,11 @@ void imgui::citro3d::init ()
|
|||||||
if (charSet.empty ())
|
if (charSet.empty ())
|
||||||
svcBreak (USERBREAK_PANIC);
|
svcBreak (USERBREAK_PANIC);
|
||||||
|
|
||||||
|
// deduplicate character map
|
||||||
std::sort (std::begin (charSet), std::end (charSet));
|
std::sort (std::begin (charSet), std::end (charSet));
|
||||||
charSet.erase (std::unique (std::begin (charSet), std::end (charSet)), std::end (charSet));
|
charSet.erase (std::unique (std::begin (charSet), std::end (charSet)), std::end (charSet));
|
||||||
|
|
||||||
|
// fill in font glyph ranges
|
||||||
auto it = std::begin (charSet);
|
auto it = std::begin (charSet);
|
||||||
ImWchar start = *it++;
|
ImWchar start = *it++;
|
||||||
ImWchar prev = start;
|
ImWchar prev = start;
|
||||||
@ -262,8 +309,11 @@ void imgui::citro3d::init ()
|
|||||||
}
|
}
|
||||||
s_fontRanges.emplace_back (start);
|
s_fontRanges.emplace_back (start);
|
||||||
s_fontRanges.emplace_back (prev);
|
s_fontRanges.emplace_back (prev);
|
||||||
|
|
||||||
|
// terminate glyph ranges
|
||||||
s_fontRanges.emplace_back (0);
|
s_fontRanges.emplace_back (0);
|
||||||
|
|
||||||
|
// initialize font atlas
|
||||||
auto const atlas = ImGui::GetIO ().Fonts;
|
auto const atlas = ImGui::GetIO ().Fonts;
|
||||||
atlas->Clear ();
|
atlas->Clear ();
|
||||||
atlas->TexWidth = glyphInfo->sheetWidth;
|
atlas->TexWidth = glyphInfo->sheetWidth;
|
||||||
@ -272,6 +322,7 @@ void imgui::citro3d::init ()
|
|||||||
atlas->TexUvWhitePixel = ImVec2 (0.5f / 8.0f, glyphInfo->nSheets + 0.5f / 8.0f);
|
atlas->TexUvWhitePixel = ImVec2 (0.5f / 8.0f, glyphInfo->nSheets + 0.5f / 8.0f);
|
||||||
atlas->TexPixelsAlpha8 = static_cast<unsigned char *> (IM_ALLOC (1)); // dummy allocation
|
atlas->TexPixelsAlpha8 = static_cast<unsigned char *> (IM_ALLOC (1)); // dummy allocation
|
||||||
|
|
||||||
|
// initialize font config
|
||||||
ImFontConfig config;
|
ImFontConfig config;
|
||||||
config.FontData = nullptr;
|
config.FontData = nullptr;
|
||||||
config.FontDataSize = 0;
|
config.FontDataSize = 0;
|
||||||
@ -292,19 +343,20 @@ void imgui::citro3d::init ()
|
|||||||
config.EllipsisChar = 0x2026;
|
config.EllipsisChar = 0x2026;
|
||||||
std::memset (config.Name, 0, sizeof (config.Name));
|
std::memset (config.Name, 0, sizeof (config.Name));
|
||||||
|
|
||||||
|
// create font
|
||||||
auto const imFont = IM_NEW (ImFont);
|
auto const imFont = IM_NEW (ImFont);
|
||||||
config.DstFont = imFont;
|
config.DstFont = imFont;
|
||||||
|
|
||||||
|
// add config and font to atlas
|
||||||
atlas->ConfigData.push_back (config);
|
atlas->ConfigData.push_back (config);
|
||||||
atlas->Fonts.push_back (imFont);
|
atlas->Fonts.push_back (imFont);
|
||||||
// atlas->CustomRectIds[0] = atlas->AddCustomRectRegular (0x80000000, 108 * 2 + 1, 27);
|
|
||||||
// atlas->CustomRects[0].X = 0;
|
|
||||||
// atlas->CustomRects[0].Y = 0;
|
|
||||||
atlas->SetTexID (s_fontTextures.data ());
|
atlas->SetTexID (s_fontTextures.data ());
|
||||||
|
|
||||||
|
// initialize font metrics
|
||||||
imFont->FallbackAdvanceX = fontInfo->defaultWidth.charWidth;
|
imFont->FallbackAdvanceX = fontInfo->defaultWidth.charWidth;
|
||||||
imFont->FontSize = fontInfo->lineFeed;
|
imFont->FontSize = fontInfo->lineFeed;
|
||||||
|
|
||||||
|
// add glyphs to font
|
||||||
fontGlyphPos_s glyphPos;
|
fontGlyphPos_s glyphPos;
|
||||||
for (auto const &code : charSet)
|
for (auto const &code : charSet)
|
||||||
{
|
{
|
||||||
@ -312,6 +364,7 @@ void imgui::citro3d::init ()
|
|||||||
if (glyphIndex < 0)
|
if (glyphIndex < 0)
|
||||||
svcBreak (USERBREAK_PANIC);
|
svcBreak (USERBREAK_PANIC);
|
||||||
|
|
||||||
|
// calculate glyph metrics
|
||||||
fontCalcGlyphPos (&glyphPos,
|
fontCalcGlyphPos (&glyphPos,
|
||||||
font,
|
font,
|
||||||
glyphIndex,
|
glyphIndex,
|
||||||
@ -319,8 +372,8 @@ void imgui::citro3d::init ()
|
|||||||
1.0f,
|
1.0f,
|
||||||
1.0f);
|
1.0f);
|
||||||
|
|
||||||
|
// convert to ImGui font metrics
|
||||||
ImFontGlyph glyph;
|
ImFontGlyph glyph;
|
||||||
|
|
||||||
glyph.Codepoint = code;
|
glyph.Codepoint = code;
|
||||||
glyph.AdvanceX = glyphPos.xAdvance;
|
glyph.AdvanceX = glyphPos.xAdvance;
|
||||||
glyph.X0 = glyphPos.vtxcoord.left;
|
glyph.X0 = glyphPos.vtxcoord.left;
|
||||||
@ -332,41 +385,48 @@ void imgui::citro3d::init ()
|
|||||||
glyph.U1 = glyphPos.texcoord.right;
|
glyph.U1 = glyphPos.texcoord.right;
|
||||||
glyph.V1 = glyphPos.sheetIndex + glyphPos.texcoord.bottom;
|
glyph.V1 = glyphPos.sheetIndex + glyphPos.texcoord.bottom;
|
||||||
|
|
||||||
|
// add glyph to font
|
||||||
imFont->Glyphs.push_back (glyph);
|
imFont->Glyphs.push_back (glyph);
|
||||||
imFont->MetricsTotalSurface +=
|
imFont->MetricsTotalSurface +=
|
||||||
static_cast<int> ((glyph.U1 - glyph.U0) * atlas->TexWidth + 1.99f) *
|
static_cast<int> ((glyph.U1 - glyph.U0) * atlas->TexWidth + 1.99f) *
|
||||||
static_cast<int> ((glyph.V1 - glyph.V0) * atlas->TexHeight + 1.99f);
|
static_cast<int> ((glyph.V1 - glyph.V0) * atlas->TexHeight + 1.99f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// build lookup table
|
||||||
imFont->BuildLookupTable ();
|
imFont->BuildLookupTable ();
|
||||||
|
|
||||||
|
// finalize font
|
||||||
imFont->DisplayOffset.x = 0.0f;
|
imFont->DisplayOffset.x = 0.0f;
|
||||||
imFont->DisplayOffset.y = fontInfo->ascent;
|
imFont->DisplayOffset.y = fontInfo->ascent;
|
||||||
|
|
||||||
imFont->ContainerAtlas = atlas;
|
imFont->ContainerAtlas = atlas;
|
||||||
imFont->ConfigData = &atlas->ConfigData[0];
|
imFont->ConfigData = &atlas->ConfigData[0];
|
||||||
imFont->ConfigDataCount = 1;
|
imFont->ConfigDataCount = 1;
|
||||||
imFont->FallbackChar = alterChar;
|
imFont->FallbackChar = alterChar;
|
||||||
imFont->EllipsisChar = config.EllipsisChar;
|
imFont->EllipsisChar = config.EllipsisChar;
|
||||||
imFont->Scale = 1.0f;
|
imFont->Scale = s_textScale;
|
||||||
imFont->Ascent = fontInfo->ascent;
|
imFont->Ascent = fontInfo->ascent;
|
||||||
imFont->Descent = 0.0f;
|
imFont->Descent = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void imgui::citro3d::exit ()
|
void imgui::citro3d::exit ()
|
||||||
{
|
{
|
||||||
|
// free vertex/index data buffers
|
||||||
linearFree (s_idxData);
|
linearFree (s_idxData);
|
||||||
linearFree (s_vtxData);
|
linearFree (s_vtxData);
|
||||||
|
|
||||||
|
// delete ImGui white pixel texture
|
||||||
assert (!s_fontTextures.empty ());
|
assert (!s_fontTextures.empty ());
|
||||||
C3D_TexDelete (&s_fontTextures.back ());
|
C3D_TexDelete (&s_fontTextures.back ());
|
||||||
|
|
||||||
|
// free shader program
|
||||||
shaderProgramFree (&s_program);
|
shaderProgramFree (&s_program);
|
||||||
DVLB_Free (s_vsh);
|
DVLB_Free (s_vsh);
|
||||||
|
|
||||||
|
// free render targets
|
||||||
C3D_RenderTargetDelete (s_bottom);
|
C3D_RenderTargetDelete (s_bottom);
|
||||||
C3D_RenderTargetDelete (s_top);
|
C3D_RenderTargetDelete (s_top);
|
||||||
|
|
||||||
|
// deinitialize citro3d
|
||||||
C3D_Fini ();
|
C3D_Fini ();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -377,9 +437,12 @@ void imgui::citro3d::newFrame ()
|
|||||||
void imgui::citro3d::render ()
|
void imgui::citro3d::render ()
|
||||||
{
|
{
|
||||||
C3D_FrameBegin (C3D_FRAME_SYNCDRAW);
|
C3D_FrameBegin (C3D_FRAME_SYNCDRAW);
|
||||||
|
|
||||||
|
// clear frame/depth buffers
|
||||||
C3D_RenderTargetClear (s_top, C3D_CLEAR_ALL, CLEAR_COLOR, 0);
|
C3D_RenderTargetClear (s_top, C3D_CLEAR_ALL, CLEAR_COLOR, 0);
|
||||||
C3D_RenderTargetClear (s_bottom, C3D_CLEAR_ALL, CLEAR_COLOR, 0);
|
C3D_RenderTargetClear (s_bottom, C3D_CLEAR_ALL, CLEAR_COLOR, 0);
|
||||||
|
|
||||||
|
// get draw data
|
||||||
auto const drawData = ImGui::GetDrawData ();
|
auto const drawData = ImGui::GetDrawData ();
|
||||||
if (drawData->CmdListsCount <= 0)
|
if (drawData->CmdListsCount <= 0)
|
||||||
{
|
{
|
||||||
@ -387,6 +450,7 @@ void imgui::citro3d::render ()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get framebuffer dimensions
|
||||||
unsigned width = drawData->DisplaySize.x * drawData->FramebufferScale.x;
|
unsigned width = drawData->DisplaySize.x * drawData->FramebufferScale.x;
|
||||||
unsigned height = drawData->DisplaySize.y * drawData->FramebufferScale.y;
|
unsigned height = drawData->DisplaySize.y * drawData->FramebufferScale.y;
|
||||||
if (width <= 0 || height <= 0)
|
if (width <= 0 || height <= 0)
|
||||||
@ -395,6 +459,7 @@ void imgui::citro3d::render ()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if we need to grow vertex data buffer
|
||||||
if (s_vtxSize < static_cast<std::size_t> (drawData->TotalVtxCount))
|
if (s_vtxSize < static_cast<std::size_t> (drawData->TotalVtxCount))
|
||||||
{
|
{
|
||||||
linearFree (s_vtxData);
|
linearFree (s_vtxData);
|
||||||
@ -406,6 +471,7 @@ void imgui::citro3d::render ()
|
|||||||
svcBreak (USERBREAK_PANIC);
|
svcBreak (USERBREAK_PANIC);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if we need to grow index data buffer
|
||||||
if (s_idxSize < static_cast<std::size_t> (drawData->TotalIdxCount))
|
if (s_idxSize < static_cast<std::size_t> (drawData->TotalIdxCount))
|
||||||
{
|
{
|
||||||
// add 10% to avoid growing many frames in a row
|
// add 10% to avoid growing many frames in a row
|
||||||
@ -415,7 +481,7 @@ void imgui::citro3d::render ()
|
|||||||
svcBreak (USERBREAK_PANIC);
|
svcBreak (USERBREAK_PANIC);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Will project scissor/clipping rectangles into framebuffer space
|
// will project scissor/clipping rectangles into framebuffer space
|
||||||
// (0,0) unless using multi-viewports
|
// (0,0) unless using multi-viewports
|
||||||
auto const clipOff = drawData->DisplayPos;
|
auto const clipOff = drawData->DisplayPos;
|
||||||
// (1,1) unless using retina display which are often (2,2)
|
// (1,1) unless using retina display which are often (2,2)
|
||||||
@ -427,11 +493,14 @@ void imgui::citro3d::render ()
|
|||||||
for (int i = 0; i < drawData->CmdListsCount; ++i)
|
for (int i = 0; i < drawData->CmdListsCount; ++i)
|
||||||
{
|
{
|
||||||
auto const &cmdList = *drawData->CmdLists[i];
|
auto const &cmdList = *drawData->CmdLists[i];
|
||||||
|
|
||||||
|
// double check that we don't overrun vertex/index data buffers
|
||||||
if (s_vtxSize - offsetVtx < static_cast<std::size_t> (cmdList.VtxBuffer.Size))
|
if (s_vtxSize - offsetVtx < static_cast<std::size_t> (cmdList.VtxBuffer.Size))
|
||||||
svcBreak (USERBREAK_PANIC);
|
svcBreak (USERBREAK_PANIC);
|
||||||
if (s_idxSize - offsetIdx < static_cast<std::size_t> (cmdList.IdxBuffer.Size))
|
if (s_idxSize - offsetIdx < static_cast<std::size_t> (cmdList.IdxBuffer.Size))
|
||||||
svcBreak (USERBREAK_PANIC);
|
svcBreak (USERBREAK_PANIC);
|
||||||
|
|
||||||
|
// copy vertex/index data into buffers
|
||||||
std::memcpy (&s_vtxData[offsetVtx],
|
std::memcpy (&s_vtxData[offsetVtx],
|
||||||
cmdList.VtxBuffer.Data,
|
cmdList.VtxBuffer.Data,
|
||||||
sizeof (ImDrawVert) * cmdList.VtxBuffer.Size);
|
sizeof (ImDrawVert) * cmdList.VtxBuffer.Size);
|
||||||
@ -455,7 +524,7 @@ void imgui::citro3d::render ()
|
|||||||
offsetVtx = 0;
|
offsetVtx = 0;
|
||||||
offsetIdx = 0;
|
offsetIdx = 0;
|
||||||
|
|
||||||
// Render command lists
|
// render command lists
|
||||||
for (int i = 0; i < drawData->CmdListsCount; ++i)
|
for (int i = 0; i < drawData->CmdListsCount; ++i)
|
||||||
{
|
{
|
||||||
auto const &cmdList = *drawData->CmdLists[i];
|
auto const &cmdList = *drawData->CmdLists[i];
|
||||||
@ -463,7 +532,7 @@ void imgui::citro3d::render ()
|
|||||||
{
|
{
|
||||||
if (cmd.UserCallback)
|
if (cmd.UserCallback)
|
||||||
{
|
{
|
||||||
// User callback, registered via ImDrawList::AddCallback()
|
// user callback, registered via ImDrawList::AddCallback()
|
||||||
// (ImDrawCallback_ResetRenderState is a special callback value used by the user
|
// (ImDrawCallback_ResetRenderState is a special callback value used by the user
|
||||||
// to request the renderer to reset render state.)
|
// to request the renderer to reset render state.)
|
||||||
if (cmd.UserCallback == ImDrawCallback_ResetRenderState)
|
if (cmd.UserCallback == ImDrawCallback_ResetRenderState)
|
||||||
@ -473,7 +542,7 @@ void imgui::citro3d::render ()
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Project scissor/clipping rectangles into framebuffer space
|
// project scissor/clipping rectangles into framebuffer space
|
||||||
ImVec4 clip;
|
ImVec4 clip;
|
||||||
clip.x = (cmd.ClipRect.x - clipOff.x) * clipScale.x;
|
clip.x = (cmd.ClipRect.x - clipOff.x) * clipScale.x;
|
||||||
clip.y = (cmd.ClipRect.y - clipOff.y) * clipScale.y;
|
clip.y = (cmd.ClipRect.y - clipOff.y) * clipScale.y;
|
||||||
@ -486,6 +555,10 @@ void imgui::citro3d::render ()
|
|||||||
clip.x = 0.0f;
|
clip.x = 0.0f;
|
||||||
if (clip.y < 0.0f)
|
if (clip.y < 0.0f)
|
||||||
clip.y = 0.0f;
|
clip.y = 0.0f;
|
||||||
|
if (clip.z > width)
|
||||||
|
clip.z = width;
|
||||||
|
if (clip.w > height)
|
||||||
|
clip.z = height;
|
||||||
|
|
||||||
if (screen == GFX_TOP)
|
if (screen == GFX_TOP)
|
||||||
{
|
{
|
||||||
@ -493,12 +566,22 @@ void imgui::citro3d::render ()
|
|||||||
if (clip.y > 240.0f)
|
if (clip.y > 240.0f)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
// convert from framebuffer space to screen space (3DS screen rotation)
|
||||||
auto const x1 = std::clamp<unsigned> (240.0f - clip.w, 0, 240);
|
auto const x1 = std::clamp<unsigned> (240.0f - clip.w, 0, 240);
|
||||||
auto const y1 = std::clamp<unsigned> (400.0f - clip.z, 0, 400);
|
auto const y1 = std::clamp<unsigned> (400.0f - clip.z, 0, 400);
|
||||||
auto const x2 = std::clamp<unsigned> (240.0f - clip.y, 0, 240);
|
auto const x2 = std::clamp<unsigned> (240.0f - clip.y, 0, 240);
|
||||||
auto const y2 = std::clamp<unsigned> (400.0f - clip.x, 0, 400);
|
auto const y2 = std::clamp<unsigned> (400.0f - clip.x, 0, 400);
|
||||||
|
|
||||||
C3D_SetScissor (GPU_SCISSOR_NORMAL, x1, y1, x2, y2);
|
// check if scissor needs to be updated
|
||||||
|
if (s_boundScissor[0] != x1 || s_boundScissor[1] != y1 ||
|
||||||
|
s_boundScissor[2] != x2 || s_boundScissor[3] != y2)
|
||||||
|
{
|
||||||
|
s_boundScissor[0] = x1;
|
||||||
|
s_boundScissor[1] = y1;
|
||||||
|
s_boundScissor[2] = x2;
|
||||||
|
s_boundScissor[3] = y2;
|
||||||
|
C3D_SetScissor (GPU_SCISSOR_NORMAL, x1, y1, x2, y2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -514,11 +597,14 @@ void imgui::citro3d::render ()
|
|||||||
if (clip.x > 360.0f)
|
if (clip.x > 360.0f)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
// convert from framebuffer space to screen space
|
||||||
|
// (3DS screen rotation + bottom screen offset)
|
||||||
auto const x1 = std::clamp<unsigned> (480.0f - clip.w, 0, 240);
|
auto const x1 = std::clamp<unsigned> (480.0f - clip.w, 0, 240);
|
||||||
auto const y1 = std::clamp<unsigned> (360.0f - clip.z, 0, 320);
|
auto const y1 = std::clamp<unsigned> (360.0f - clip.z, 0, 320);
|
||||||
auto const x2 = std::clamp<unsigned> (480.0f - clip.y, 0, 240);
|
auto const x2 = std::clamp<unsigned> (480.0f - clip.y, 0, 240);
|
||||||
auto const y2 = std::clamp<unsigned> (360.0f - clip.x, 0, 320);
|
auto const y2 = std::clamp<unsigned> (360.0f - clip.x, 0, 320);
|
||||||
|
|
||||||
|
// check if scissor needs to be updated
|
||||||
if (s_boundScissor[0] != x1 || s_boundScissor[1] != y1 ||
|
if (s_boundScissor[0] != x1 || s_boundScissor[1] != y1 ||
|
||||||
s_boundScissor[2] != x2 || s_boundScissor[3] != y2)
|
s_boundScissor[2] != x2 || s_boundScissor[3] != y2)
|
||||||
{
|
{
|
||||||
@ -530,36 +616,41 @@ void imgui::citro3d::render ()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if we need to update vertex data binding
|
||||||
auto const vtxData = &s_vtxData[cmd.VtxOffset + offsetVtx];
|
auto const vtxData = &s_vtxData[cmd.VtxOffset + offsetVtx];
|
||||||
if (vtxData != s_boundVtxData)
|
if (vtxData != s_boundVtxData)
|
||||||
{
|
{
|
||||||
s_boundVtxData = &s_vtxData[cmd.VtxOffset + offsetVtx];
|
s_boundVtxData = vtxData;
|
||||||
auto const bufInfo = C3D_GetBufInfo ();
|
auto const bufInfo = C3D_GetBufInfo ();
|
||||||
BufInfo_Init (bufInfo);
|
BufInfo_Init (bufInfo);
|
||||||
BufInfo_Add (bufInfo, s_boundVtxData, sizeof (ImDrawVert), 3, 0x210);
|
BufInfo_Add (bufInfo, vtxData, sizeof (ImDrawVert), 3, 0x210);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if we need to update texture binding
|
||||||
auto tex = static_cast<C3D_Tex *> (cmd.TextureId);
|
auto tex = static_cast<C3D_Tex *> (cmd.TextureId);
|
||||||
if (tex == s_fontTextures.data ())
|
if (tex == s_fontTextures.data ())
|
||||||
{
|
{
|
||||||
assert (cmd.ElemCount % 3 == 0);
|
assert (cmd.ElemCount % 3 == 0);
|
||||||
|
|
||||||
// TODO get by idx not consecutive vtx
|
// get sheet number from uv coords
|
||||||
auto const getSheet = [] (auto const vtx_, auto const idx_) {
|
auto const getSheet = [] (auto const vtx_, auto const idx_) {
|
||||||
unsigned const sheet = std::min (
|
unsigned const sheet = std::min (
|
||||||
{vtx_[idx_[0]].uv.y, vtx_[idx_[1]].uv.y, vtx_[idx_[2]].uv.y});
|
{vtx_[idx_[0]].uv.y, vtx_[idx_[1]].uv.y, vtx_[idx_[2]].uv.y});
|
||||||
|
|
||||||
|
// assert that these three vertices use the same sheet
|
||||||
for (unsigned i = 0; i < 3; ++i)
|
for (unsigned i = 0; i < 3; ++i)
|
||||||
assert (vtx_[idx_[i]].uv.y - sheet <= 1.0f);
|
assert (vtx_[idx_[i]].uv.y - sheet <= 1.0f);
|
||||||
return sheet;
|
return sheet;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// initialize texture binding
|
||||||
unsigned boundSheet = getSheet (&s_vtxData[cmd.VtxOffset + offsetVtx],
|
unsigned boundSheet = getSheet (&s_vtxData[cmd.VtxOffset + offsetVtx],
|
||||||
&s_idxData[cmd.IdxOffset + offsetIdx]);
|
&s_idxData[cmd.IdxOffset + offsetIdx]);
|
||||||
|
C3D_TexBind (0, &s_fontTextures[boundSheet]);
|
||||||
|
|
||||||
unsigned offset = 0;
|
unsigned offset = 0;
|
||||||
|
|
||||||
C3D_TexBind (0, &s_fontTextures[boundSheet]);
|
// update texture environment for non-image drawing
|
||||||
|
|
||||||
auto const env = C3D_GetTexEnv (0);
|
auto const env = C3D_GetTexEnv (0);
|
||||||
C3D_TexEnvInit (env);
|
C3D_TexEnvInit (env);
|
||||||
C3D_TexEnvSrc (
|
C3D_TexEnvSrc (
|
||||||
@ -569,23 +660,30 @@ void imgui::citro3d::render ()
|
|||||||
env, C3D_Alpha, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR);
|
env, C3D_Alpha, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR);
|
||||||
C3D_TexEnvFunc (env, C3D_Alpha, GPU_MODULATE);
|
C3D_TexEnvFunc (env, C3D_Alpha, GPU_MODULATE);
|
||||||
|
|
||||||
|
// process one triangle at a time
|
||||||
for (unsigned i = 3; i < cmd.ElemCount; i += 3)
|
for (unsigned i = 3; i < cmd.ElemCount; i += 3)
|
||||||
{
|
{
|
||||||
|
// get sheet for this triangle
|
||||||
unsigned const sheet = getSheet (&s_vtxData[cmd.VtxOffset + offsetVtx],
|
unsigned const sheet = getSheet (&s_vtxData[cmd.VtxOffset + offsetVtx],
|
||||||
&s_idxData[cmd.IdxOffset + offsetIdx + i]);
|
&s_idxData[cmd.IdxOffset + offsetIdx + i]);
|
||||||
|
|
||||||
|
// check if we're changing textures
|
||||||
if (boundSheet != sheet)
|
if (boundSheet != sheet)
|
||||||
{
|
{
|
||||||
|
// draw everything up until now
|
||||||
C3D_DrawElements (GPU_TRIANGLES,
|
C3D_DrawElements (GPU_TRIANGLES,
|
||||||
i - offset,
|
i - offset,
|
||||||
C3D_UNSIGNED_SHORT,
|
C3D_UNSIGNED_SHORT,
|
||||||
&s_idxData[cmd.IdxOffset + offsetIdx + offset]);
|
&s_idxData[cmd.IdxOffset + offsetIdx + offset]);
|
||||||
|
|
||||||
|
// bind texture for next draw call
|
||||||
boundSheet = sheet;
|
boundSheet = sheet;
|
||||||
offset = i;
|
offset = i;
|
||||||
C3D_TexBind (0, &s_fontTextures[boundSheet]);
|
C3D_TexBind (0, &s_fontTextures[boundSheet]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// draw the final set of triangles
|
||||||
assert ((cmd.ElemCount - offset) % 3 == 0);
|
assert ((cmd.ElemCount - offset) % 3 == 0);
|
||||||
C3D_DrawElements (GPU_TRIANGLES,
|
C3D_DrawElements (GPU_TRIANGLES,
|
||||||
cmd.ElemCount - offset,
|
cmd.ElemCount - offset,
|
||||||
@ -594,9 +692,13 @@ void imgui::citro3d::render ()
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// drawing an image; check if we need to change texture binding
|
||||||
if (tex != s_boundTexture)
|
if (tex != s_boundTexture)
|
||||||
{
|
{
|
||||||
|
// bind new texture
|
||||||
C3D_TexBind (0, tex);
|
C3D_TexBind (0, tex);
|
||||||
|
|
||||||
|
// update texture environment for drawing images
|
||||||
auto const env = C3D_GetTexEnv (0);
|
auto const env = C3D_GetTexEnv (0);
|
||||||
C3D_TexEnvInit (env);
|
C3D_TexEnvInit (env);
|
||||||
C3D_TexEnvSrc (
|
C3D_TexEnvSrc (
|
||||||
@ -604,6 +706,7 @@ void imgui::citro3d::render ()
|
|||||||
C3D_TexEnvFunc (env, C3D_Both, GPU_MODULATE);
|
C3D_TexEnvFunc (env, C3D_Both, GPU_MODULATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// draw triangles
|
||||||
C3D_DrawElements (GPU_TRIANGLES,
|
C3D_DrawElements (GPU_TRIANGLES,
|
||||||
cmd.ElemCount,
|
cmd.ElemCount,
|
||||||
C3D_UNSIGNED_SHORT,
|
C3D_UNSIGNED_SHORT,
|
||||||
|
@ -24,10 +24,14 @@ namespace imgui
|
|||||||
{
|
{
|
||||||
namespace citro3d
|
namespace citro3d
|
||||||
{
|
{
|
||||||
|
/// \brief Initialize citro3d
|
||||||
void init ();
|
void init ();
|
||||||
|
/// \brief Deinitialize citro3d
|
||||||
void exit ();
|
void exit ();
|
||||||
|
|
||||||
|
/// \brief Prepare citro3d for a new frame
|
||||||
void newFrame ();
|
void newFrame ();
|
||||||
|
/// \brief Render ImGui draw list
|
||||||
void render ();
|
void render ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
#include "imgui.h"
|
#include "imgui.h"
|
||||||
|
|
||||||
#include "fs.h"
|
#include "fs.h"
|
||||||
|
#include "platform.h"
|
||||||
|
|
||||||
#include <3ds.h>
|
#include <3ds.h>
|
||||||
|
|
||||||
@ -35,32 +36,45 @@ using namespace std::chrono_literals;
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
constexpr auto SCREEN_WIDTH = 400.0f;
|
/// \brief Screen width
|
||||||
|
constexpr auto SCREEN_WIDTH = 400.0f;
|
||||||
|
/// \brief Screen height
|
||||||
constexpr auto SCREEN_HEIGHT = 480.0f;
|
constexpr auto SCREEN_HEIGHT = 480.0f;
|
||||||
|
|
||||||
|
/// \brief Clipboard
|
||||||
std::string s_clipboard;
|
std::string s_clipboard;
|
||||||
|
|
||||||
|
/// \brief Get clipboard text callback
|
||||||
|
/// \param userData_ User data
|
||||||
char const *getClipboardText (void *const userData_)
|
char const *getClipboardText (void *const userData_)
|
||||||
{
|
{
|
||||||
(void)userData_;
|
(void)userData_;
|
||||||
return s_clipboard.c_str ();
|
return s_clipboard.c_str ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Set clipboard text callback
|
||||||
|
/// \param userData_ User data
|
||||||
|
/// \param text_ Clipboard text
|
||||||
void setClipboardText (void *const userData_, char const *const text_)
|
void setClipboardText (void *const userData_, char const *const text_)
|
||||||
{
|
{
|
||||||
(void)userData_;
|
(void)userData_;
|
||||||
s_clipboard = text_;
|
s_clipboard = text_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Update touch position
|
||||||
|
/// \param io_ ImGui IO
|
||||||
void updateTouch (ImGuiIO &io_)
|
void updateTouch (ImGuiIO &io_)
|
||||||
{
|
{
|
||||||
if (!((hidKeysDown () | hidKeysHeld ()) & KEY_TOUCH))
|
// check if touchpad was touched
|
||||||
|
if (!(hidKeysHeld () & KEY_TOUCH))
|
||||||
{
|
{
|
||||||
|
// set mouse cursor off-screen
|
||||||
io_.MousePos = ImVec2 (-10.0f, -10.0f);
|
io_.MousePos = ImVec2 (-10.0f, -10.0f);
|
||||||
io_.MouseDown[0] = false;
|
io_.MouseDown[0] = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// read touch position
|
||||||
touchPosition pos;
|
touchPosition pos;
|
||||||
hidTouchRead (&pos);
|
hidTouchRead (&pos);
|
||||||
|
|
||||||
@ -69,8 +83,11 @@ void updateTouch (ImGuiIO &io_)
|
|||||||
io_.MouseDown[0] = true;
|
io_.MouseDown[0] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Update gamepad inputs
|
||||||
|
/// \param io_ ImGui IO
|
||||||
void updateGamepads (ImGuiIO &io_)
|
void updateGamepads (ImGuiIO &io_)
|
||||||
{
|
{
|
||||||
|
// clear navigation inputs
|
||||||
std::memset (io_.NavInputs, 0, sizeof (io_.NavInputs));
|
std::memset (io_.NavInputs, 0, sizeof (io_.NavInputs));
|
||||||
|
|
||||||
auto const buttonMapping = {
|
auto const buttonMapping = {
|
||||||
@ -88,6 +105,7 @@ void updateGamepads (ImGuiIO &io_)
|
|||||||
std::make_pair (KEY_DLEFT, ImGuiNavInput_DpadLeft),
|
std::make_pair (KEY_DLEFT, ImGuiNavInput_DpadLeft),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// read buttons from 3DS
|
||||||
auto const keys = hidKeysHeld ();
|
auto const keys = hidKeysHeld ();
|
||||||
for (auto const &[in, out] : buttonMapping)
|
for (auto const &[in, out] : buttonMapping)
|
||||||
{
|
{
|
||||||
@ -95,6 +113,7 @@ void updateGamepads (ImGuiIO &io_)
|
|||||||
io_.NavInputs[out] = 1.0f;
|
io_.NavInputs[out] = 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update joystick
|
||||||
circlePosition cpad;
|
circlePosition cpad;
|
||||||
auto const analogMapping = {
|
auto const analogMapping = {
|
||||||
std::make_tuple (std::ref (cpad.dx), ImGuiNavInput_LStickLeft, -0.3f, -0.9f),
|
std::make_tuple (std::ref (cpad.dx), ImGuiNavInput_LStickLeft, -0.3f, -0.9f),
|
||||||
@ -103,12 +122,12 @@ void updateGamepads (ImGuiIO &io_)
|
|||||||
std::make_tuple (std::ref (cpad.dy), ImGuiNavInput_LStickDown, -0.3f, -0.9f),
|
std::make_tuple (std::ref (cpad.dy), ImGuiNavInput_LStickDown, -0.3f, -0.9f),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// read left joystick from circle pad
|
||||||
hidCircleRead (&cpad);
|
hidCircleRead (&cpad);
|
||||||
for (auto const &[in, out, min, max] : analogMapping)
|
for (auto const &[in, out, min, max] : analogMapping)
|
||||||
{
|
{
|
||||||
auto const value = in / static_cast<float> (0x9C);
|
auto const value = in / static_cast<float> (0x9C);
|
||||||
auto const v = std::min (1.0f, (value - min) / (max - min));
|
io_.NavInputs[out] = std::clamp ((value - min) / (max - min), 0.0f, 1.0f);
|
||||||
io_.NavInputs[out] = std::max (io_.NavInputs[out], v);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,17 +136,21 @@ bool imgui::ctru::init ()
|
|||||||
{
|
{
|
||||||
ImGuiIO &io = ImGui::GetIO ();
|
ImGuiIO &io = ImGui::GetIO ();
|
||||||
|
|
||||||
|
// disable imgui.ini file
|
||||||
io.IniFilename = nullptr;
|
io.IniFilename = nullptr;
|
||||||
|
|
||||||
|
// setup config flags
|
||||||
io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen;
|
io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen;
|
||||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
|
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
|
||||||
|
|
||||||
|
// setup platform backend
|
||||||
io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
|
io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
|
||||||
|
io.BackendPlatformName = "3DS";
|
||||||
|
|
||||||
io.BackendPlatformName = "3ds";
|
// disable mouse cursor
|
||||||
|
|
||||||
io.MouseDrawCursor = false;
|
io.MouseDrawCursor = false;
|
||||||
|
|
||||||
|
// clipboard callbacks
|
||||||
io.SetClipboardTextFn = setClipboardText;
|
io.SetClipboardTextFn = setClipboardText;
|
||||||
io.GetClipboardTextFn = getClipboardText;
|
io.GetClipboardTextFn = getClipboardText;
|
||||||
io.ClipboardUserData = nullptr;
|
io.ClipboardUserData = nullptr;
|
||||||
@ -139,19 +162,21 @@ void imgui::ctru::newFrame ()
|
|||||||
{
|
{
|
||||||
ImGuiIO &io = ImGui::GetIO ();
|
ImGuiIO &io = ImGui::GetIO ();
|
||||||
|
|
||||||
|
// check that font was built
|
||||||
IM_ASSERT (io.Fonts->IsBuilt () &&
|
IM_ASSERT (io.Fonts->IsBuilt () &&
|
||||||
"Font atlas not built! It is generally built by the renderer back-end. Missing call "
|
"Font atlas not built! It is generally built by the renderer back-end. Missing call "
|
||||||
"to renderer _NewFrame() function?");
|
"to renderer _NewFrame() function?");
|
||||||
|
|
||||||
|
// setup display metrics
|
||||||
io.DisplaySize = ImVec2 (SCREEN_WIDTH * 2.0f, SCREEN_HEIGHT * 2.0f);
|
io.DisplaySize = ImVec2 (SCREEN_WIDTH * 2.0f, SCREEN_HEIGHT * 2.0f);
|
||||||
io.DisplayFramebufferScale = ImVec2 (0.5f, 0.5f);
|
io.DisplayFramebufferScale = ImVec2 (0.5f, 0.5f);
|
||||||
|
|
||||||
// Setup time step
|
// time step
|
||||||
static auto const start = svcGetSystemTick ();
|
static auto const start = platform::steady_clock::now ();
|
||||||
static auto prev = start;
|
static auto prev = start;
|
||||||
auto const now = svcGetSystemTick ();
|
auto const now = platform::steady_clock::now ();
|
||||||
|
|
||||||
io.DeltaTime = (now - prev) / static_cast<float> (SYSCLOCK_ARM11);
|
io.DeltaTime = std::chrono::duration<float> (now - prev).count ();
|
||||||
prev = now;
|
prev = now;
|
||||||
|
|
||||||
updateTouch (io);
|
updateTouch (io);
|
||||||
|
@ -24,9 +24,12 @@ namespace imgui
|
|||||||
{
|
{
|
||||||
namespace ctru
|
namespace ctru
|
||||||
{
|
{
|
||||||
|
/// \brief Initialize 3ds platform
|
||||||
bool init ();
|
bool init ();
|
||||||
|
/// \brief Deinitialize 3ds platform
|
||||||
void exit ();
|
void exit ();
|
||||||
|
|
||||||
|
/// \brief Prepare 3ds for a new frame
|
||||||
void newFrame ();
|
void newFrame ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,33 +41,45 @@
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
constexpr auto STACK_SIZE = 32768;
|
/// \brief Thread stack size
|
||||||
constexpr auto SOCU_ALIGN = 0x1000;
|
constexpr auto STACK_SIZE = 0x8000;
|
||||||
|
/// \brief soc:u buffer alignment
|
||||||
|
constexpr auto SOCU_ALIGN = 0x1000;
|
||||||
|
/// \brief soc:u buffer size
|
||||||
constexpr auto SOCU_BUFFERSIZE = 0x100000;
|
constexpr auto SOCU_BUFFERSIZE = 0x100000;
|
||||||
|
|
||||||
static_assert (SOCU_BUFFERSIZE % SOCU_ALIGN == 0);
|
static_assert (SOCU_BUFFERSIZE % SOCU_ALIGN == 0);
|
||||||
|
|
||||||
|
/// \brief Whether soc:u is active
|
||||||
bool s_socuActive = false;
|
bool s_socuActive = false;
|
||||||
|
/// \brief soc:u buffer
|
||||||
u32 *s_socuBuffer = nullptr;
|
u32 *s_socuBuffer = nullptr;
|
||||||
|
|
||||||
|
/// \brief Texture atlas
|
||||||
C3D_Tex s_gfxTexture;
|
C3D_Tex s_gfxTexture;
|
||||||
|
/// \brief Texture atlas metadata
|
||||||
Tex3DS_Texture s_gfxT3x;
|
Tex3DS_Texture s_gfxT3x;
|
||||||
|
|
||||||
|
/// \brief Start network
|
||||||
void startNetwork ()
|
void startNetwork ()
|
||||||
{
|
{
|
||||||
|
// check if already active
|
||||||
if (s_socuActive)
|
if (s_socuActive)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// get wifi status
|
||||||
std::uint32_t wifi = 0;
|
std::uint32_t wifi = 0;
|
||||||
if (R_FAILED (ACU_GetWifiStatus (&wifi)) || !wifi)
|
if (R_FAILED (ACU_GetWifiStatus (&wifi)) || !wifi)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// allocate soc:u buffer
|
||||||
if (!s_socuBuffer)
|
if (!s_socuBuffer)
|
||||||
s_socuBuffer = static_cast<u32 *> (::memalign (SOCU_ALIGN, SOCU_BUFFERSIZE));
|
s_socuBuffer = static_cast<u32 *> (::memalign (SOCU_ALIGN, SOCU_BUFFERSIZE));
|
||||||
|
|
||||||
if (!s_socuBuffer)
|
if (!s_socuBuffer)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// initialize soc:u service
|
||||||
if (R_FAILED (socInit (s_socuBuffer, SOCU_BUFFERSIZE)))
|
if (R_FAILED (socInit (s_socuBuffer, SOCU_BUFFERSIZE)))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -75,27 +87,34 @@ void startNetwork ()
|
|||||||
Log::info ("Wifi connected\n");
|
Log::info ("Wifi connected\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Draw citro3d logo
|
||||||
void drawLogo ()
|
void drawLogo ()
|
||||||
{
|
{
|
||||||
|
// get citro3d logo subtexture
|
||||||
auto subTex = Tex3DS_GetSubTexture (s_gfxT3x, gfx_c3dlogo_idx);
|
auto subTex = Tex3DS_GetSubTexture (s_gfxT3x, gfx_c3dlogo_idx);
|
||||||
|
|
||||||
|
// get framebuffer metrics
|
||||||
ImGuiIO &io = ImGui::GetIO ();
|
ImGuiIO &io = ImGui::GetIO ();
|
||||||
auto const screenWidth = io.DisplaySize.x;
|
auto const screenWidth = io.DisplaySize.x;
|
||||||
auto const screenHeight = io.DisplaySize.y;
|
auto const screenHeight = io.DisplaySize.y;
|
||||||
auto const logoWidth = subTex->width / io.DisplayFramebufferScale.x;
|
auto const logoWidth = subTex->width / io.DisplayFramebufferScale.x;
|
||||||
auto const logoHeight = subTex->height / io.DisplayFramebufferScale.y;
|
auto const logoHeight = subTex->height / io.DisplayFramebufferScale.y;
|
||||||
|
|
||||||
|
// calculate top screen coords
|
||||||
auto const x1 = (screenWidth - logoWidth) / 2.0f;
|
auto const x1 = (screenWidth - logoWidth) / 2.0f;
|
||||||
auto const x2 = x1 + logoWidth;
|
auto const x2 = x1 + logoWidth;
|
||||||
auto const y1 = (screenHeight / 2.0f - logoHeight) / 2.0f;
|
auto const y1 = (screenHeight / 2.0f - logoHeight) / 2.0f;
|
||||||
auto const y2 = y1 + logoHeight;
|
auto const y2 = y1 + logoHeight;
|
||||||
|
|
||||||
|
// calculate uv coords
|
||||||
auto const uv1 = ImVec2 (subTex->left, subTex->top);
|
auto const uv1 = ImVec2 (subTex->left, subTex->top);
|
||||||
auto const uv2 = ImVec2 (subTex->right, subTex->bottom);
|
auto const uv2 = ImVec2 (subTex->right, subTex->bottom);
|
||||||
|
|
||||||
|
// draw to top screen
|
||||||
ImGui::GetBackgroundDrawList ()->AddImage (
|
ImGui::GetBackgroundDrawList ()->AddImage (
|
||||||
&s_gfxTexture, ImVec2 (x1, y1), ImVec2 (x2, y2), uv1, uv2);
|
&s_gfxTexture, ImVec2 (x1, y1), ImVec2 (x2, y2), uv1, uv2);
|
||||||
|
|
||||||
|
// draw to bottom screen
|
||||||
ImGui::GetBackgroundDrawList ()->AddImage (&s_gfxTexture,
|
ImGui::GetBackgroundDrawList ()->AddImage (&s_gfxTexture,
|
||||||
ImVec2 (x1, y1 + screenHeight / 2.0f),
|
ImVec2 (x1, y1 + screenHeight / 2.0f),
|
||||||
ImVec2 (x2, y2 + screenHeight / 2.0f),
|
ImVec2 (x2, y2 + screenHeight / 2.0f),
|
||||||
@ -103,6 +122,7 @@ void drawLogo ()
|
|||||||
uv2);
|
uv2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Draw status
|
||||||
void drawStatus ()
|
void drawStatus ()
|
||||||
{
|
{
|
||||||
constexpr unsigned batteryLevels[] = {
|
constexpr unsigned batteryLevels[] = {
|
||||||
@ -121,6 +141,7 @@ void drawStatus ()
|
|||||||
gfx_wifi3_idx,
|
gfx_wifi3_idx,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// get battery charging state or level
|
||||||
static u8 charging = 0;
|
static u8 charging = 0;
|
||||||
static u8 level = 5;
|
static u8 level = 5;
|
||||||
PTMU_GetBatteryChargeState (&charging);
|
PTMU_GetBatteryChargeState (&charging);
|
||||||
@ -136,33 +157,43 @@ void drawStatus ()
|
|||||||
|
|
||||||
auto const screenWidth = io.DisplaySize.x;
|
auto const screenWidth = io.DisplaySize.x;
|
||||||
|
|
||||||
|
// calculate battery icon metrics
|
||||||
auto const battery =
|
auto const battery =
|
||||||
Tex3DS_GetSubTexture (s_gfxT3x, charging ? gfx_batteryCharge_idx : batteryLevels[level]);
|
Tex3DS_GetSubTexture (s_gfxT3x, charging ? gfx_batteryCharge_idx : batteryLevels[level]);
|
||||||
auto const batteryWidth = battery->width / io.DisplayFramebufferScale.x;
|
auto const batteryWidth = battery->width / io.DisplayFramebufferScale.x;
|
||||||
auto const batteryHeight = battery->height / io.DisplayFramebufferScale.y;
|
auto const batteryHeight = battery->height / io.DisplayFramebufferScale.y;
|
||||||
|
|
||||||
|
// calculate battery icon position
|
||||||
auto const p1 = ImVec2 (screenWidth - batteryWidth, 0.0f);
|
auto const p1 = ImVec2 (screenWidth - batteryWidth, 0.0f);
|
||||||
auto const p2 = ImVec2 (screenWidth, batteryHeight);
|
auto const p2 = ImVec2 (screenWidth, batteryHeight);
|
||||||
|
|
||||||
|
// calculate battery icon uv coords
|
||||||
auto const uv1 = ImVec2 (battery->left, battery->top);
|
auto const uv1 = ImVec2 (battery->left, battery->top);
|
||||||
auto const uv2 = ImVec2 (battery->right, battery->bottom);
|
auto const uv2 = ImVec2 (battery->right, battery->bottom);
|
||||||
|
|
||||||
|
// draw battery icon
|
||||||
ImGui::GetForegroundDrawList ()->AddImage (&s_gfxTexture, p1, p2, uv1, uv2);
|
ImGui::GetForegroundDrawList ()->AddImage (&s_gfxTexture, p1, p2, uv1, uv2);
|
||||||
|
|
||||||
|
// get wifi strength
|
||||||
auto const wifiStrength = osGetWifiStrength ();
|
auto const wifiStrength = osGetWifiStrength ();
|
||||||
|
|
||||||
|
// calculate wifi icon metrics
|
||||||
auto const wifi = Tex3DS_GetSubTexture (s_gfxT3x, wifiLevels[wifiStrength]);
|
auto const wifi = Tex3DS_GetSubTexture (s_gfxT3x, wifiLevels[wifiStrength]);
|
||||||
auto const wifiWidth = wifi->width / io.DisplayFramebufferScale.x;
|
auto const wifiWidth = wifi->width / io.DisplayFramebufferScale.x;
|
||||||
auto const wifiHeight = wifi->height / io.DisplayFramebufferScale.y;
|
auto const wifiHeight = wifi->height / io.DisplayFramebufferScale.y;
|
||||||
|
|
||||||
|
// calculate wifi icon position
|
||||||
auto const p3 = ImVec2 (p1.x - wifiWidth - 4.0f, 0.0f);
|
auto const p3 = ImVec2 (p1.x - wifiWidth - 4.0f, 0.0f);
|
||||||
auto const p4 = ImVec2 (p1.x - 4.0f, wifiHeight);
|
auto const p4 = ImVec2 (p1.x - 4.0f, wifiHeight);
|
||||||
|
|
||||||
|
// calculate wifi icon uv coords
|
||||||
auto const uv3 = ImVec2 (wifi->left, wifi->top);
|
auto const uv3 = ImVec2 (wifi->left, wifi->top);
|
||||||
auto const uv4 = ImVec2 (wifi->right, wifi->bottom);
|
auto const uv4 = ImVec2 (wifi->right, wifi->bottom);
|
||||||
|
|
||||||
|
// draw wifi icon
|
||||||
ImGui::GetForegroundDrawList ()->AddImage (&s_gfxTexture, p3, p4, uv3, uv4);
|
ImGui::GetForegroundDrawList ()->AddImage (&s_gfxTexture, p3, p4, uv3, uv4);
|
||||||
|
|
||||||
|
// draw current timestamp
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
auto const now = std::time (nullptr);
|
auto const now = std::time (nullptr);
|
||||||
std::strftime (buffer, sizeof (buffer), "%H:%M:%S", std::localtime (&now));
|
std::strftime (buffer, sizeof (buffer), "%H:%M:%S", std::localtime (&now));
|
||||||
@ -173,6 +204,7 @@ void drawStatus ()
|
|||||||
|
|
||||||
bool platform::init ()
|
bool platform::init ()
|
||||||
{
|
{
|
||||||
|
// enable New 3DS speedup
|
||||||
osSetSpeedupEnable (true);
|
osSetSpeedupEnable (true);
|
||||||
|
|
||||||
acInit ();
|
acInit ();
|
||||||
@ -187,18 +219,13 @@ bool platform::init ()
|
|||||||
std::setvbuf (stderr, nullptr, _IOLBF, 0);
|
std::setvbuf (stderr, nullptr, _IOLBF, 0);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
IMGUI_CHECKVERSION ();
|
|
||||||
ImGui::CreateContext ();
|
|
||||||
|
|
||||||
if (!imgui::ctru::init ())
|
if (!imgui::ctru::init ())
|
||||||
{
|
|
||||||
ImGui::DestroyContext ();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
imgui::citro3d::init ();
|
imgui::citro3d::init ();
|
||||||
|
|
||||||
{
|
{
|
||||||
|
// load texture atlas
|
||||||
fs::File file;
|
fs::File file;
|
||||||
if (!file.open ("romfs:/gfx.t3x"))
|
if (!file.open ("romfs:/gfx.t3x"))
|
||||||
svcBreak (USERBREAK_PANIC);
|
svcBreak (USERBREAK_PANIC);
|
||||||
@ -206,9 +233,9 @@ bool platform::init ()
|
|||||||
s_gfxT3x = Tex3DS_TextureImportStdio (file, &s_gfxTexture, nullptr, true);
|
s_gfxT3x = Tex3DS_TextureImportStdio (file, &s_gfxTexture, nullptr, true);
|
||||||
if (!s_gfxT3x)
|
if (!s_gfxT3x)
|
||||||
svcBreak (USERBREAK_PANIC);
|
svcBreak (USERBREAK_PANIC);
|
||||||
}
|
|
||||||
|
|
||||||
C3D_TexSetFilter (&s_gfxTexture, GPU_LINEAR, GPU_LINEAR);
|
C3D_TexSetFilter (&s_gfxTexture, GPU_LINEAR, GPU_LINEAR);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -247,8 +274,6 @@ void platform::exit ()
|
|||||||
Tex3DS_TextureFree (s_gfxT3x);
|
Tex3DS_TextureFree (s_gfxT3x);
|
||||||
C3D_TexDelete (&s_gfxTexture);
|
C3D_TexDelete (&s_gfxTexture);
|
||||||
|
|
||||||
ImGui::DestroyContext ();
|
|
||||||
|
|
||||||
imgui::citro3d::exit ();
|
imgui::citro3d::exit ();
|
||||||
imgui::ctru::exit ();
|
imgui::ctru::exit ();
|
||||||
|
|
||||||
@ -262,6 +287,7 @@ void platform::exit ()
|
|||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
/// \brief Platform thread pimpl
|
||||||
class platform::Thread::privateData_t
|
class platform::Thread::privateData_t
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -271,6 +297,8 @@ public:
|
|||||||
threadFree (thread);
|
threadFree (thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Parameterized constructor
|
||||||
|
/// \param func_ Thread entry point
|
||||||
privateData_t (std::function<void ()> func_) : thread (nullptr)
|
privateData_t (std::function<void ()> func_) : thread (nullptr)
|
||||||
{
|
{
|
||||||
s32 priority = 0x30;
|
s32 priority = 0x30;
|
||||||
@ -280,13 +308,18 @@ public:
|
|||||||
assert (thread);
|
assert (thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Underlying thread entry point
|
||||||
|
/// \param arg_ Thread pimpl object
|
||||||
static void threadFunc (void *const arg_)
|
static void threadFunc (void *const arg_)
|
||||||
{
|
{
|
||||||
|
// call passed-in entry point
|
||||||
auto const t = static_cast<privateData_t *> (arg_);
|
auto const t = static_cast<privateData_t *> (arg_);
|
||||||
t->func ();
|
t->func ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Underlying thread
|
||||||
::Thread thread = nullptr;
|
::Thread thread = nullptr;
|
||||||
|
/// \brief Thread entry point
|
||||||
std::function<void ()> func;
|
std::function<void ()> func;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -323,9 +356,11 @@ void platform::Thread::sleep (std::chrono::milliseconds const timeout_)
|
|||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
/// \brief Platform mutex pimpl
|
||||||
class platform::Mutex::privateData_t
|
class platform::Mutex::privateData_t
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/// \brief Underlying mutex
|
||||||
LightLock mutex;
|
LightLock mutex;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -18,26 +18,28 @@
|
|||||||
; You should have received a copy of the GNU General Public License
|
; You should have received a copy of the GNU General Public License
|
||||||
; along with this program. If not, see <https://www.gnu.org/licenses/>.
|
; along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
; Example PICA200 vertex shader
|
; ImGui PICA200 vertex shader
|
||||||
|
|
||||||
; Uniforms
|
; uniforms
|
||||||
|
; Projection matrix
|
||||||
.fvec proj[4]
|
.fvec proj[4]
|
||||||
|
|
||||||
; Constants
|
; constants
|
||||||
|
; [1.0, 0.0, 1.0/255.0, 0.0]
|
||||||
.constf constants(1.0, 0.0, 0.00392156862745, 0.0)
|
.constf constants(1.0, 0.0, 0.00392156862745, 0.0)
|
||||||
|
|
||||||
; Outputs
|
; outputs
|
||||||
.out outPos position
|
.out outPos position
|
||||||
.out outUv texcoord0
|
.out outUv texcoord0
|
||||||
.out outColor color
|
.out outColor color
|
||||||
|
|
||||||
; Inputs (defined as aliases for convenience)
|
; inputs (defined as aliases for convenience)
|
||||||
.alias inPos v0
|
.alias inPos v0
|
||||||
.alias inUv v1
|
.alias inUv v1
|
||||||
.alias inColor v2
|
.alias inColor v2
|
||||||
|
|
||||||
.proc main
|
.proc main
|
||||||
; Force inPos.z = 0.0, inPos.w = 1.0
|
; force inPos.z = 0.0, inPos.w = 1.0
|
||||||
mov r0.xy, inPos.xy
|
mov r0.xy, inPos.xy
|
||||||
mov r0.zw, constants.yx
|
mov r0.zw, constants.yx
|
||||||
|
|
||||||
@ -56,6 +58,6 @@
|
|||||||
; outColor = inColor
|
; outColor = inColor
|
||||||
mov outColor, r1
|
mov outColor, r1
|
||||||
|
|
||||||
; We're finished
|
; we're finished
|
||||||
end
|
end
|
||||||
.end
|
.end
|
||||||
|
@ -45,28 +45,34 @@ std::string fs::printSize (std::uint64_t const size_)
|
|||||||
// clang-format on
|
// clang-format on
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
|
// get the integral portion of the number
|
||||||
auto const whole = size_ / bin;
|
auto const whole = size_ / bin;
|
||||||
if (size_ >= 100 * bin)
|
if (size_ >= 100 * bin)
|
||||||
{
|
{
|
||||||
|
// >= 100, print xxxXiB
|
||||||
std::sprintf (buffer, "%" PRIu64 "%s", whole, name);
|
std::sprintf (buffer, "%" PRIu64 "%s", whole, name);
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get the fractional portion of the number
|
||||||
auto const frac = size_ - whole * bin;
|
auto const frac = size_ - whole * bin;
|
||||||
if (size_ >= 10 * bin)
|
if (size_ >= 10 * bin)
|
||||||
{
|
{
|
||||||
|
// >= 10, print xx.xXiB
|
||||||
std::sprintf (buffer, "%" PRIu64 ".%" PRIu64 "%s", whole, frac * 10 / bin, name);
|
std::sprintf (buffer, "%" PRIu64 ".%" PRIu64 "%s", whole, frac * 10 / bin, name);
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (size_ >= 1000 * (bin / KiB))
|
if (size_ >= 1000 * (bin / KiB))
|
||||||
{
|
{
|
||||||
|
// >= 1000 of lesser bin, print x.xxXiB
|
||||||
std::sprintf (buffer, "%" PRIu64 ".%02" PRIu64 "%s", whole, frac * 100 / bin, name);
|
std::sprintf (buffer, "%" PRIu64 ".%02" PRIu64 "%s", whole, frac * 100 / bin, name);
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::sprintf (buffer, "%" PRIu64, size_);
|
// < 1KiB, just print the number
|
||||||
|
std::sprintf (buffer, "%" PRIu64 "B", size_);
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,5 +213,6 @@ void fs::Dir::close ()
|
|||||||
|
|
||||||
struct dirent *fs::Dir::read ()
|
struct dirent *fs::Dir::read ()
|
||||||
{
|
{
|
||||||
|
errno = 0;
|
||||||
return ::readdir (m_dp.get ());
|
return ::readdir (m_dp.get ());
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
#ifndef _3DS
|
#ifndef _3DS
|
||||||
|
/// \todo Investigate multithreading on 3DS
|
||||||
#define MULTITHREADED 1
|
#define MULTITHREADED 1
|
||||||
#else
|
#else
|
||||||
#define MULTITHREADED 0
|
#define MULTITHREADED 0
|
||||||
@ -43,17 +44,24 @@ using namespace std::chrono_literals;
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
auto const s_startTime = std::chrono::system_clock::to_time_t (std::chrono::system_clock::now ());
|
/// \brief Application start time
|
||||||
|
auto const s_startTime = std::time (nullptr);
|
||||||
|
|
||||||
|
/// \brief Mutex for s_freeSpace
|
||||||
platform::Mutex s_lock;
|
platform::Mutex s_lock;
|
||||||
|
|
||||||
|
/// \brief Free space string
|
||||||
std::string s_freeSpace;
|
std::string s_freeSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
FtpServer::~FtpServer ()
|
FtpServer::~FtpServer ()
|
||||||
{
|
{
|
||||||
|
#if MULTITHREADED
|
||||||
m_quit = true;
|
m_quit = true;
|
||||||
|
|
||||||
m_thread.join ();
|
m_thread.join ();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
FtpServer::FtpServer (std::uint16_t const port_)
|
FtpServer::FtpServer (std::uint16_t const port_)
|
||||||
@ -118,6 +126,7 @@ void FtpServer::draw ()
|
|||||||
ImGui::Separator ();
|
ImGui::Separator ();
|
||||||
|
|
||||||
#ifdef _3DS
|
#ifdef _3DS
|
||||||
|
// Fill rest of top screen window
|
||||||
ImGui::BeginChild ("Logs", ImVec2 (0, 0), false, ImGuiWindowFlags_HorizontalScrollbar);
|
ImGui::BeginChild ("Logs", ImVec2 (0, 0), false, ImGuiWindowFlags_HorizontalScrollbar);
|
||||||
#else
|
#else
|
||||||
ImGui::BeginChild ("Logs", ImVec2 (0, 200), false, ImGuiWindowFlags_HorizontalScrollbar);
|
ImGui::BeginChild ("Logs", ImVec2 (0, 200), false, ImGuiWindowFlags_HorizontalScrollbar);
|
||||||
@ -214,6 +223,7 @@ void FtpServer::handleStopButton ()
|
|||||||
void FtpServer::loop ()
|
void FtpServer::loop ()
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
|
// poll listen socket
|
||||||
auto const lock = std::scoped_lock (m_lock);
|
auto const lock = std::scoped_lock (m_lock);
|
||||||
if (m_socket)
|
if (m_socket)
|
||||||
{
|
{
|
||||||
@ -227,6 +237,7 @@ void FtpServer::loop ()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remove dead sessions
|
||||||
for (auto it = std::begin (m_sessions); it != std::end (m_sessions);)
|
for (auto it = std::begin (m_sessions); it != std::end (m_sessions);)
|
||||||
{
|
{
|
||||||
auto const &session = *it;
|
auto const &session = *it;
|
||||||
@ -236,9 +247,11 @@ void FtpServer::loop ()
|
|||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// poll sessions
|
||||||
if (!m_sessions.empty ())
|
if (!m_sessions.empty ())
|
||||||
FtpSession::poll (m_sessions);
|
FtpSession::poll (m_sessions);
|
||||||
#if MULTITHREADED
|
#if MULTITHREADED
|
||||||
|
// avoid busy polling in background thread
|
||||||
else
|
else
|
||||||
platform::Thread::sleep (16ms);
|
platform::Thread::sleep (16ms);
|
||||||
#endif
|
#endif
|
||||||
@ -246,6 +259,7 @@ void FtpServer::loop ()
|
|||||||
|
|
||||||
void FtpServer::threadFunc ()
|
void FtpServer::threadFunc ()
|
||||||
{
|
{
|
||||||
|
// bind log for this thread
|
||||||
Log::bind (m_log);
|
Log::bind (m_log);
|
||||||
|
|
||||||
while (!m_quit)
|
while (!m_quit)
|
||||||
|
@ -55,8 +55,13 @@ using namespace std::chrono_literals;
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
/// \brief Parse command
|
||||||
|
/// \param buffer_ Buffer to parse
|
||||||
|
/// \param size_ Size of buffer
|
||||||
|
/// \returns {delimiterPos, nextPos}
|
||||||
std::pair<char *, char *> parseCommand (char *const buffer_, std::size_t const size_)
|
std::pair<char *, char *> parseCommand (char *const buffer_, std::size_t const size_)
|
||||||
{
|
{
|
||||||
|
// look for \r\n or \n delimiter
|
||||||
auto const end = &buffer_[size_];
|
auto const end = &buffer_[size_];
|
||||||
for (auto p = buffer_; p < end; ++p)
|
for (auto p = buffer_; p < end; ++p)
|
||||||
{
|
{
|
||||||
@ -70,6 +75,9 @@ std::pair<char *, char *> parseCommand (char *const buffer_, std::size_t const s
|
|||||||
return {nullptr, nullptr};
|
return {nullptr, nullptr};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Decode path
|
||||||
|
/// \param buffer_ Buffer to decode
|
||||||
|
/// \param size_ Size of buffer
|
||||||
void decodePath (char *const buffer_, std::size_t const size_)
|
void decodePath (char *const buffer_, std::size_t const size_)
|
||||||
{
|
{
|
||||||
auto const end = &buffer_[size_];
|
auto const end = &buffer_[size_];
|
||||||
@ -81,6 +89,9 @@ void decodePath (char *const buffer_, std::size_t const size_)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Encode path
|
||||||
|
/// \param buffer_ Buffer to encode
|
||||||
|
/// \param quotes_ Whether to encode quotes
|
||||||
std::string encodePath (std::string_view const buffer_, bool const quotes_ = false)
|
std::string encodePath (std::string_view const buffer_, bool const quotes_ = false)
|
||||||
{
|
{
|
||||||
// check if the buffer has \n
|
// check if the buffer has \n
|
||||||
@ -104,13 +115,16 @@ std::string encodePath (std::string_view const buffer_, bool const quotes_ = fal
|
|||||||
} while (p);
|
} while (p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if nothing needs escaping, return it as-is
|
||||||
if (!lf && !numQuotes)
|
if (!lf && !numQuotes)
|
||||||
return std::string (buffer_);
|
return std::string (buffer_);
|
||||||
|
|
||||||
|
// reserve output buffer
|
||||||
std::string path (buffer_.size () + numQuotes, '\0');
|
std::string path (buffer_.size () + numQuotes, '\0');
|
||||||
auto in = buffer_.data ();
|
auto in = buffer_.data ();
|
||||||
auto out = path.data ();
|
auto out = path.data ();
|
||||||
|
|
||||||
|
// encode into the output buffer
|
||||||
while (in < end)
|
while (in < end)
|
||||||
{
|
{
|
||||||
if (*in == '\n')
|
if (*in == '\n')
|
||||||
@ -132,6 +146,8 @@ std::string encodePath (std::string_view const buffer_, bool const quotes_ = fal
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Get parent directory name of a path
|
||||||
|
/// \param path_ Path to get parent of
|
||||||
std::string dirName (std::string_view const path_)
|
std::string dirName (std::string_view const path_)
|
||||||
{
|
{
|
||||||
// remove last path component
|
// remove last path component
|
||||||
@ -142,6 +158,8 @@ std::string dirName (std::string_view const path_)
|
|||||||
return dir;
|
return dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Resolve path
|
||||||
|
/// \param path_ Path to resolve
|
||||||
std::string resolvePath (std::string_view const path_)
|
std::string resolvePath (std::string_view const path_)
|
||||||
{
|
{
|
||||||
assert (!path_.empty ());
|
assert (!path_.empty ());
|
||||||
@ -209,6 +227,9 @@ std::string resolvePath (std::string_view const path_)
|
|||||||
return outPath;
|
return outPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Build path from a parent and child
|
||||||
|
/// \param cwd_ Parent directory
|
||||||
|
/// \param args_ Child component
|
||||||
std::string buildPath (std::string_view const cwd_, std::string_view const args_)
|
std::string buildPath (std::string_view const cwd_, std::string_view const args_)
|
||||||
{
|
{
|
||||||
// absolute path
|
// absolute path
|
||||||
@ -222,6 +243,9 @@ std::string buildPath (std::string_view const cwd_, std::string_view const args_
|
|||||||
return std::string (cwd_) + '/' + std::string (args_);
|
return std::string (cwd_) + '/' + std::string (args_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Build resolved path from a parent and child
|
||||||
|
/// \param cwd_ Parent directory
|
||||||
|
/// \param args_ Child component
|
||||||
std::string buildResolvedPath (std::string_view const cwd_, std::string_view const args_)
|
std::string buildResolvedPath (std::string_view const cwd_, std::string_view const args_)
|
||||||
{
|
{
|
||||||
return resolvePath (buildPath (cwd_, args_));
|
return resolvePath (buildPath (cwd_, args_));
|
||||||
|
@ -29,6 +29,7 @@ IOBuffer::~IOBuffer () = default;
|
|||||||
IOBuffer::IOBuffer (std::size_t const size_)
|
IOBuffer::IOBuffer (std::size_t const size_)
|
||||||
: m_buffer (std::make_unique<char[]> (size_)), m_size (size_)
|
: m_buffer (std::make_unique<char[]> (size_)), m_size (size_)
|
||||||
{
|
{
|
||||||
|
assert (size_ > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
char *IOBuffer::freeArea () const
|
char *IOBuffer::freeArea () const
|
||||||
|
@ -36,8 +36,13 @@
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
/// \brief GLFW main window
|
||||||
std::unique_ptr<GLFWwindow, void (*) (GLFWwindow *)> s_mainWindow (nullptr, glfwDestroyWindow);
|
std::unique_ptr<GLFWwindow, void (*) (GLFWwindow *)> s_mainWindow (nullptr, glfwDestroyWindow);
|
||||||
|
|
||||||
|
/// \brief Window resize callback
|
||||||
|
/// \param window_ GLFW window
|
||||||
|
/// \param width_ New window width
|
||||||
|
/// \param height_ New window height
|
||||||
void windowResize (GLFWwindow *const window_, int const width_, int const height_)
|
void windowResize (GLFWwindow *const window_, int const width_, int const height_)
|
||||||
{
|
{
|
||||||
(void)window_;
|
(void)window_;
|
||||||
@ -49,6 +54,13 @@ void windowResize (GLFWwindow *const window_, int const width_, int const height
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
|
/// \brief GL log callback
|
||||||
|
/// \param source_ Message source
|
||||||
|
/// \param type_ Message type
|
||||||
|
/// \param id_ Message id
|
||||||
|
/// \param severity_ Message severity
|
||||||
|
/// \param length_ Message length
|
||||||
|
/// \param userParam_ User parameter
|
||||||
void logCallback (GLenum const source_,
|
void logCallback (GLenum const source_,
|
||||||
GLenum const type_,
|
GLenum const type_,
|
||||||
GLuint const id_,
|
GLuint const id_,
|
||||||
@ -63,19 +75,21 @@ void logCallback (GLenum const source_,
|
|||||||
(void)severity_;
|
(void)severity_;
|
||||||
(void)length_;
|
(void)length_;
|
||||||
(void)userParam_;
|
(void)userParam_;
|
||||||
// std::fprintf (stderr, "%s\n", message_);
|
std::fprintf (stderr, "%s\n", message_);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool platform::init ()
|
bool platform::init ()
|
||||||
{
|
{
|
||||||
|
// initialize GLFW
|
||||||
if (!glfwInit ())
|
if (!glfwInit ())
|
||||||
{
|
{
|
||||||
std::fprintf (stderr, "Failed to initialize GLFW\n");
|
std::fprintf (stderr, "Failed to initialize GLFW\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// use OpenGL 4.3 Core Profile
|
||||||
glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4);
|
glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4);
|
||||||
glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 3);
|
glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 3);
|
||||||
glfwWindowHint (GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
glfwWindowHint (GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||||
@ -87,6 +101,7 @@ bool platform::init ()
|
|||||||
glfwWindowHint (GLFW_DEPTH_BITS, 24);
|
glfwWindowHint (GLFW_DEPTH_BITS, 24);
|
||||||
glfwWindowHint (GLFW_STENCIL_BITS, 8);
|
glfwWindowHint (GLFW_STENCIL_BITS, 8);
|
||||||
|
|
||||||
|
// create GLFW window
|
||||||
s_mainWindow.reset (glfwCreateWindow (1280, 720, "Test Game", nullptr, nullptr));
|
s_mainWindow.reset (glfwCreateWindow (1280, 720, "Test Game", nullptr, nullptr));
|
||||||
if (!s_mainWindow)
|
if (!s_mainWindow)
|
||||||
{
|
{
|
||||||
@ -95,12 +110,14 @@ bool platform::init ()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// enable vsync
|
||||||
glfwSwapInterval (1);
|
glfwSwapInterval (1);
|
||||||
|
|
||||||
// create context
|
// create context
|
||||||
glfwMakeContextCurrent (s_mainWindow.get ());
|
glfwMakeContextCurrent (s_mainWindow.get ());
|
||||||
glfwSetFramebufferSizeCallback (s_mainWindow.get (), windowResize);
|
glfwSetFramebufferSizeCallback (s_mainWindow.get (), windowResize);
|
||||||
|
|
||||||
|
// load OpenGL
|
||||||
if (!gladLoadGL ())
|
if (!gladLoadGL ())
|
||||||
{
|
{
|
||||||
std::fprintf (stderr, "gladLoadGL: failed\n");
|
std::fprintf (stderr, "gladLoadGL: failed\n");
|
||||||
@ -134,12 +151,10 @@ bool platform::init ()
|
|||||||
std::printf ("Renderer: %s\n", glGetString (GL_RENDERER));
|
std::printf ("Renderer: %s\n", glGetString (GL_RENDERER));
|
||||||
std::printf ("OpenGL Version: %s\n", glGetString (GL_VERSION));
|
std::printf ("OpenGL Version: %s\n", glGetString (GL_VERSION));
|
||||||
|
|
||||||
IMGUI_CHECKVERSION ();
|
|
||||||
ImGui::CreateContext ();
|
|
||||||
|
|
||||||
ImGui_ImplGlfw_InitForOpenGL (s_mainWindow.get (), true);
|
ImGui_ImplGlfw_InitForOpenGL (s_mainWindow.get (), true);
|
||||||
ImGui_ImplOpenGL3_Init ("#version 150");
|
ImGui_ImplOpenGL3_Init ("#version 150");
|
||||||
|
|
||||||
|
// disable imgui.ini file
|
||||||
ImGuiIO &io = ImGui::GetIO ();
|
ImGuiIO &io = ImGui::GetIO ();
|
||||||
io.IniFilename = nullptr;
|
io.IniFilename = nullptr;
|
||||||
|
|
||||||
@ -169,10 +184,6 @@ void platform::render ()
|
|||||||
{
|
{
|
||||||
ImGui::Render ();
|
ImGui::Render ();
|
||||||
|
|
||||||
int width;
|
|
||||||
int height;
|
|
||||||
glfwGetFramebufferSize (s_mainWindow.get (), &width, &height);
|
|
||||||
glViewport (0, 0, width, height);
|
|
||||||
glClearColor (0.45f, 0.55f, 0.60f, 1.00f);
|
glClearColor (0.45f, 0.55f, 0.60f, 1.00f);
|
||||||
glClear (GL_COLOR_BUFFER_BIT);
|
glClear (GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
@ -183,22 +194,24 @@ void platform::render ()
|
|||||||
|
|
||||||
void platform::exit ()
|
void platform::exit ()
|
||||||
{
|
{
|
||||||
ImGui::DestroyContext ();
|
|
||||||
|
|
||||||
s_mainWindow.reset ();
|
s_mainWindow.reset ();
|
||||||
glfwTerminate ();
|
glfwTerminate ();
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
/// \brief Platform thread pimpl
|
||||||
class platform::Thread::privateData_t
|
class platform::Thread::privateData_t
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
privateData_t () = default;
|
privateData_t () = default;
|
||||||
|
|
||||||
|
/// \brief Parameterized constructor
|
||||||
|
/// \param func_ Thread entry point
|
||||||
privateData_t (std::function<void ()> func_) : thread (func_)
|
privateData_t (std::function<void ()> func_) : thread (func_)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Underlying thread object
|
||||||
std::thread thread;
|
std::thread thread;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -235,9 +248,11 @@ void platform::Thread::sleep (std::chrono::milliseconds const timeout_)
|
|||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
/// \brief Platform mutex pimpl
|
||||||
class platform::Mutex::privateData_t
|
class platform::Mutex::privateData_t
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/// \brief Underlying mutex
|
||||||
std::mutex mutex;
|
std::mutex mutex;
|
||||||
};
|
};
|
||||||
|
|
@ -27,12 +27,17 @@
|
|||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
#ifdef _3DS
|
#ifdef _3DS
|
||||||
|
/// \brief Maximum number of log messages to keep
|
||||||
constexpr auto MAX_LOGS = 250;
|
constexpr auto MAX_LOGS = 250;
|
||||||
#else
|
#else
|
||||||
|
/// \brief Maximum number of log messages to keep
|
||||||
constexpr auto MAX_LOGS = 10000;
|
constexpr auto MAX_LOGS = 10000;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/// \brief Bound log
|
||||||
thread_local WeakLog s_log;
|
thread_local WeakLog s_log;
|
||||||
|
|
||||||
|
/// \brief Message prefix
|
||||||
static char const *const s_prefix[] = {
|
static char const *const s_prefix[] = {
|
||||||
[Log::DEBUG] = "[DEBUG]",
|
[Log::DEBUG] = "[DEBUG]",
|
||||||
[Log::INFO] = "[INFO]",
|
[Log::INFO] = "[INFO]",
|
||||||
@ -59,19 +64,11 @@ void Log::draw ()
|
|||||||
}
|
}
|
||||||
|
|
||||||
static ImVec4 const s_colors[] = {
|
static ImVec4 const s_colors[] = {
|
||||||
[Log::DEBUG] = ImVec4 (1.0f, 1.0f, 0.4f, 1.0f),
|
[Log::DEBUG] = ImVec4 (1.0f, 1.0f, 0.4f, 1.0f), // yellow
|
||||||
[Log::INFO] = ImVec4 (1.0f, 1.0f, 1.0f, 1.0f),
|
[Log::INFO] = ImVec4 (1.0f, 1.0f, 1.0f, 1.0f), // white
|
||||||
[Log::ERROR] = ImVec4 (1.0f, 0.4f, 0.4f, 1.0f),
|
[Log::ERROR] = ImVec4 (1.0f, 0.4f, 0.4f, 1.0f), // red
|
||||||
[Log::COMMAND] = ImVec4 (0.4f, 1.0f, 0.4f, 1.0f),
|
[Log::COMMAND] = ImVec4 (0.4f, 1.0f, 0.4f, 1.0f), // green
|
||||||
[Log::RESPONSE] = ImVec4 (0.4f, 1.0f, 1.0f, 1.0f),
|
[Log::RESPONSE] = ImVec4 (0.4f, 1.0f, 1.0f, 1.0f), // cyan
|
||||||
};
|
|
||||||
|
|
||||||
static char const *const s_prefix[] = {
|
|
||||||
[Log::DEBUG] = "[DEBUG]",
|
|
||||||
[Log::INFO] = "[INFO]",
|
|
||||||
[Log::ERROR] = "[ERROR]",
|
|
||||||
[Log::COMMAND] = "[COMMAND]",
|
|
||||||
[Log::RESPONSE] = "[RESPONSE]",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for (auto const &message : m_messages)
|
for (auto const &message : m_messages)
|
||||||
@ -83,7 +80,7 @@ void Log::draw ()
|
|||||||
ImGui::PopStyleColor ();
|
ImGui::PopStyleColor ();
|
||||||
}
|
}
|
||||||
|
|
||||||
// auto scroll if scroll bar is at end
|
// auto-scroll if scroll bar is at end
|
||||||
if (ImGui::GetScrollY () >= ImGui::GetScrollMaxY ())
|
if (ImGui::GetScrollY () >= ImGui::GetScrollMaxY ())
|
||||||
ImGui::SetScrollHereY (1.0f);
|
ImGui::SetScrollHereY (1.0f);
|
||||||
}
|
}
|
||||||
@ -183,6 +180,7 @@ void Log::log (Level const level_, std::string_view const message_)
|
|||||||
auto msg = std::string (message_);
|
auto msg = std::string (message_);
|
||||||
for (auto &c : msg)
|
for (auto &c : msg)
|
||||||
{
|
{
|
||||||
|
// replace nul-characters with ? to avoid truncation
|
||||||
if (c == '\0')
|
if (c == '\0')
|
||||||
c = '?';
|
c = '?';
|
||||||
}
|
}
|
||||||
|
@ -29,14 +29,21 @@
|
|||||||
|
|
||||||
int main (int argc_, char *argv_[])
|
int main (int argc_, char *argv_[])
|
||||||
{
|
{
|
||||||
|
IMGUI_CHECKVERSION ();
|
||||||
|
ImGui::CreateContext ();
|
||||||
|
|
||||||
if (!platform::init ())
|
if (!platform::init ())
|
||||||
|
{
|
||||||
|
ImGui::DestroyContext ();
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
auto &style = ImGui::GetStyle ();
|
auto &style = ImGui::GetStyle ();
|
||||||
style.WindowRounding = 0.0f;
|
style.WindowRounding = 0.0f;
|
||||||
|
|
||||||
#ifdef _3DS
|
#ifdef _3DS
|
||||||
style.Colors[ImGuiCol_WindowBg].w = 0.5f;
|
// citro3d logo doesn't quite show with the default transparency
|
||||||
|
style.Colors[ImGuiCol_WindowBg].w = 0.8f;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto server = FtpServer::create (5000);
|
auto server = FtpServer::create (5000);
|
||||||
@ -48,5 +55,7 @@ int main (int argc_, char *argv_[])
|
|||||||
platform::render ();
|
platform::render ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server.reset ();
|
||||||
platform::exit ();
|
platform::exit ();
|
||||||
|
ImGui::DestroyContext ();
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
@ -51,18 +52,24 @@ SockAddr::SockAddr (struct sockaddr const &addr_)
|
|||||||
std::memcpy (&m_addr, &addr_, sizeof (struct sockaddr_in6));
|
std::memcpy (&m_addr, &addr_, sizeof (struct sockaddr_in6));
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
default:
|
||||||
|
std::abort ();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SockAddr::SockAddr (struct sockaddr_in const &addr_)
|
SockAddr::SockAddr (struct sockaddr_in const &addr_)
|
||||||
: SockAddr (reinterpret_cast<struct sockaddr const &> (addr_))
|
: SockAddr (reinterpret_cast<struct sockaddr const &> (addr_))
|
||||||
{
|
{
|
||||||
|
assert (m_addr.ss_family == AF_INET);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef _3DS
|
#ifndef _3DS
|
||||||
SockAddr::SockAddr (struct sockaddr_in6 const &addr_)
|
SockAddr::SockAddr (struct sockaddr_in6 const &addr_)
|
||||||
: SockAddr (reinterpret_cast<struct sockaddr const &> (addr_))
|
: SockAddr (reinterpret_cast<struct sockaddr const &> (addr_))
|
||||||
{
|
{
|
||||||
|
assert (m_addr.ss_family == AF_INET6);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -111,9 +118,11 @@ std::uint16_t SockAddr::port () const
|
|||||||
case AF_INET6:
|
case AF_INET6:
|
||||||
return ntohs (reinterpret_cast<struct sockaddr_in6 const *> (&m_addr)->sin6_port);
|
return ntohs (reinterpret_cast<struct sockaddr_in6 const *> (&m_addr)->sin6_port);
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
default:
|
||||||
|
std::abort ();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
char const *SockAddr::name (char *buffer_, std::size_t size_) const
|
char const *SockAddr::name (char *buffer_, std::size_t size_) const
|
||||||
@ -133,9 +142,11 @@ char const *SockAddr::name (char *buffer_, std::size_t size_) const
|
|||||||
buffer_,
|
buffer_,
|
||||||
size_);
|
size_);
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
default:
|
||||||
|
std::abort ();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
char const *SockAddr::name () const
|
char const *SockAddr::name () const
|
||||||
|
@ -38,8 +38,10 @@ Socket::~Socket ()
|
|||||||
{
|
{
|
||||||
if (m_listening)
|
if (m_listening)
|
||||||
Log::info ("Stop listening on [%s]:%u\n", m_sockName.name (), m_sockName.port ());
|
Log::info ("Stop listening on [%s]:%u\n", m_sockName.name (), m_sockName.port ());
|
||||||
|
|
||||||
if (m_connected)
|
if (m_connected)
|
||||||
Log::info ("Closing connection to [%s]:%u\n", m_peerName.name (), m_peerName.port ());
|
Log::info ("Closing connection to [%s]:%u\n", m_peerName.name (), m_peerName.port ());
|
||||||
|
|
||||||
if (::close (m_fd) != 0)
|
if (::close (m_fd) != 0)
|
||||||
Log::error ("close: %s\n", std::strerror (errno));
|
Log::error ("close: %s\n", std::strerror (errno));
|
||||||
}
|
}
|
||||||
@ -66,7 +68,7 @@ UniqueSocket Socket::accept ()
|
|||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
{
|
{
|
||||||
Log::error ("accept: %s\n", std::strerror (errno));
|
Log::error ("accept: %s\n", std::strerror (errno));
|
||||||
return {nullptr, {}};
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log::info ("Accepted connection from [%s]:%u\n", addr.name (), addr.port ());
|
Log::info ("Accepted connection from [%s]:%u\n", addr.name (), addr.port ());
|
||||||
@ -112,6 +114,7 @@ bool Socket::bind (SockAddr const &addr_)
|
|||||||
|
|
||||||
if (addr_.port () == 0)
|
if (addr_.port () == 0)
|
||||||
{
|
{
|
||||||
|
// get socket name due to request for ephemeral port
|
||||||
socklen_t addrLen = sizeof (struct sockaddr_storage);
|
socklen_t addrLen = sizeof (struct sockaddr_storage);
|
||||||
if (::getsockname (m_fd, m_sockName, &addrLen) != 0)
|
if (::getsockname (m_fd, m_sockName, &addrLen) != 0)
|
||||||
Log::error ("getsockname: %s\n", std::strerror (errno));
|
Log::error ("getsockname: %s\n", std::strerror (errno));
|
||||||
@ -200,7 +203,7 @@ bool Socket::setNonBlocking (bool const nonBlocking_)
|
|||||||
|
|
||||||
if (::fcntl (m_fd, F_SETFL, flags) != 0)
|
if (::fcntl (m_fd, F_SETFL, flags) != 0)
|
||||||
{
|
{
|
||||||
Log::error ("fcntl(F_SETFL): %s\n", std::strerror (errno));
|
Log::error ("fcntl(F_SETFL, %d): %s\n", flags, std::strerror (errno));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,7 +215,8 @@ bool Socket::setReuseAddress (bool const reuse_)
|
|||||||
int reuse = reuse_;
|
int reuse = reuse_;
|
||||||
if (::setsockopt (m_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof (reuse)) != 0)
|
if (::setsockopt (m_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof (reuse)) != 0)
|
||||||
{
|
{
|
||||||
Log::error ("setsockopt(SO_REUSEADDR, %u): %s\n", reuse_, std::strerror (errno));
|
Log::error (
|
||||||
|
"setsockopt(SO_REUSEADDR, %s): %s\n", reuse_ ? "yes" : "no", std::strerror (errno));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,27 +48,39 @@
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
constexpr auto LOGO_WIDTH = 500;
|
/// \brief deko3d logo width
|
||||||
|
constexpr auto LOGO_WIDTH = 500;
|
||||||
|
/// \brief deko3d logo height
|
||||||
constexpr auto LOGO_HEIGHT = 493;
|
constexpr auto LOGO_HEIGHT = 493;
|
||||||
|
|
||||||
|
/// \brief Number of framebuffers
|
||||||
constexpr auto FB_NUM = 2u;
|
constexpr auto FB_NUM = 2u;
|
||||||
|
|
||||||
constexpr auto CMDBUF_SIZE = 1024 * 1024;
|
/// \brief Command buffer size
|
||||||
constexpr auto DATABUF_SIZE = 1024 * 1024;
|
constexpr auto CMDBUF_SIZE = 1024 * 1024;
|
||||||
|
/// \brief Data buffer size
|
||||||
|
constexpr auto DATABUF_SIZE = 1024 * 1024;
|
||||||
|
/// \brief Index buffer size
|
||||||
constexpr auto INDEXBUF_SIZE = 1024 * 1024;
|
constexpr auto INDEXBUF_SIZE = 1024 * 1024;
|
||||||
|
/// \brief Image buffer size
|
||||||
constexpr auto IMAGEBUF_SIZE = 16 * 1024 * 1024;
|
constexpr auto IMAGEBUF_SIZE = 16 * 1024 * 1024;
|
||||||
|
|
||||||
|
/// \brief Vertex shader UBO
|
||||||
struct VertUBO
|
struct VertUBO
|
||||||
{
|
{
|
||||||
|
/// \brief Projection matrix
|
||||||
glm::mat4 projMtx;
|
glm::mat4 projMtx;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// \brief Fragment shader UBO
|
||||||
struct FragUBO
|
struct FragUBO
|
||||||
{
|
{
|
||||||
|
/// \brief Whether drawing a font or not
|
||||||
std::uint32_t font;
|
std::uint32_t font;
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr std::array VertexAttribState = {
|
/// \brief Vertex attribute state
|
||||||
|
constexpr std::array VERTEX_ATTRIB_STATE = {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
DkVtxAttribState{0, 0, offsetof (ImDrawVert, pos), DkVtxAttribSize_2x32, DkVtxAttribType_Float, 0},
|
DkVtxAttribState{0, 0, offsetof (ImDrawVert, pos), DkVtxAttribSize_2x32, DkVtxAttribType_Float, 0},
|
||||||
DkVtxAttribState{0, 0, offsetof (ImDrawVert, uv), DkVtxAttribSize_2x32, DkVtxAttribType_Float, 0},
|
DkVtxAttribState{0, 0, offsetof (ImDrawVert, uv), DkVtxAttribSize_2x32, DkVtxAttribType_Float, 0},
|
||||||
@ -76,58 +88,95 @@ constexpr std::array VertexAttribState = {
|
|||||||
// clang-format on
|
// clang-format on
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr std::array VertexBufferState = {
|
/// \brief Vertex buffer state
|
||||||
|
constexpr std::array VERTEX_BUFFER_STATE = {
|
||||||
DkVtxBufferState{sizeof (ImDrawVert), 0},
|
DkVtxBufferState{sizeof (ImDrawVert), 0},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// \brief deko3d device
|
||||||
dk::UniqueDevice s_device;
|
dk::UniqueDevice s_device;
|
||||||
|
|
||||||
|
/// \brief Depth buffer memblock
|
||||||
dk::UniqueMemBlock s_depthMemBlock;
|
dk::UniqueMemBlock s_depthMemBlock;
|
||||||
|
/// \brief Depth buffer image
|
||||||
dk::Image s_depthBuffer;
|
dk::Image s_depthBuffer;
|
||||||
|
|
||||||
|
/// \brief Framebuffer memblock
|
||||||
dk::UniqueMemBlock s_fbMemBlock;
|
dk::UniqueMemBlock s_fbMemBlock;
|
||||||
|
/// \brief Framebuffer images
|
||||||
dk::Image s_frameBuffers[FB_NUM];
|
dk::Image s_frameBuffers[FB_NUM];
|
||||||
|
|
||||||
|
/// \brief Font image
|
||||||
dk::Image s_fontTexture;
|
dk::Image s_fontTexture;
|
||||||
|
/// \brief deko3d logo image
|
||||||
dk::Image s_logoTexture;
|
dk::Image s_logoTexture;
|
||||||
|
|
||||||
|
/// \brief deko3d swapchain
|
||||||
dk::UniqueSwapchain s_swapchain;
|
dk::UniqueSwapchain s_swapchain;
|
||||||
|
|
||||||
|
/// \brief Shader code memblock
|
||||||
dk::UniqueMemBlock s_codeMemBlock;
|
dk::UniqueMemBlock s_codeMemBlock;
|
||||||
|
/// \brief Shaders (vertex, fragment)
|
||||||
dk::Shader s_shaders[2];
|
dk::Shader s_shaders[2];
|
||||||
|
|
||||||
|
/// \brief UBO memblock
|
||||||
dk::UniqueMemBlock s_uboMemBlock;
|
dk::UniqueMemBlock s_uboMemBlock;
|
||||||
|
|
||||||
|
/// \brief Vertex data memblock
|
||||||
dk::UniqueMemBlock s_vtxMemBlock[FB_NUM];
|
dk::UniqueMemBlock s_vtxMemBlock[FB_NUM];
|
||||||
|
/// \brief Index data memblock
|
||||||
dk::UniqueMemBlock s_idxMemBlock[FB_NUM];
|
dk::UniqueMemBlock s_idxMemBlock[FB_NUM];
|
||||||
|
/// \brief Command buffer memblock
|
||||||
dk::UniqueMemBlock s_cmdMemBlock[FB_NUM];
|
dk::UniqueMemBlock s_cmdMemBlock[FB_NUM];
|
||||||
|
/// \brief Command buffers
|
||||||
dk::UniqueCmdBuf s_cmdBuf[FB_NUM];
|
dk::UniqueCmdBuf s_cmdBuf[FB_NUM];
|
||||||
|
|
||||||
|
/// \brief Image memblock
|
||||||
dk::UniqueMemBlock s_imageMemBlock;
|
dk::UniqueMemBlock s_imageMemBlock;
|
||||||
|
/// \brief Image/Sampler descriptor memblock
|
||||||
dk::UniqueMemBlock s_descriptorMemBlock;
|
dk::UniqueMemBlock s_descriptorMemBlock;
|
||||||
|
|
||||||
|
/// \brief deko3d queue
|
||||||
dk::UniqueQueue s_queue;
|
dk::UniqueQueue s_queue;
|
||||||
|
|
||||||
constexpr auto MAX_SAMPLERS = 1;
|
/// \brief Maximum number of samplers
|
||||||
constexpr auto MAX_IMAGES = 2;
|
constexpr auto MAX_SAMPLERS = 1;
|
||||||
|
/// \brief Maximum number of images
|
||||||
|
constexpr auto MAX_IMAGES = 2;
|
||||||
|
/// \brief Sample descriptors
|
||||||
dk::SamplerDescriptor *s_samplerDescriptors = nullptr;
|
dk::SamplerDescriptor *s_samplerDescriptors = nullptr;
|
||||||
dk::ImageDescriptor *s_imageDescriptors = nullptr;
|
/// \brief Image descriptors
|
||||||
|
dk::ImageDescriptor *s_imageDescriptors = nullptr;
|
||||||
|
|
||||||
|
/// \brief Currently bound image descriptor
|
||||||
std::uintptr_t s_boundDescriptor = 0;
|
std::uintptr_t s_boundDescriptor = 0;
|
||||||
|
|
||||||
unsigned s_width = 0;
|
/// \brief Framebuffer width
|
||||||
|
unsigned s_width = 0;
|
||||||
|
/// \brief Framebuffer height
|
||||||
unsigned s_height = 0;
|
unsigned s_height = 0;
|
||||||
|
|
||||||
|
/// \brief Align value
|
||||||
|
/// \tparam T Value type
|
||||||
|
/// \tparam U Alignment type
|
||||||
|
/// \param size_ Value to align
|
||||||
|
/// \param align_ Alignment
|
||||||
template <typename T, typename U>
|
template <typename T, typename U>
|
||||||
constexpr inline std::uint32_t align (T const &size_, U const &align_)
|
constexpr inline std::uint32_t align (T const &size_, U const &align_)
|
||||||
{
|
{
|
||||||
return static_cast<std::uint32_t> (size_ + align_ - 1) & ~(align_ - 1);
|
return static_cast<std::uint32_t> (size_ + align_ - 1) & ~(align_ - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Rebuild swapchain
|
||||||
|
/// \param width_ Framebuffer width
|
||||||
|
/// \param height_ Framebuffer height
|
||||||
|
/// \note This assumes the first call is the largest a framebuffer will ever be
|
||||||
void rebuildSwapchain (unsigned const width_, unsigned const height_)
|
void rebuildSwapchain (unsigned const width_, unsigned const height_)
|
||||||
{
|
{
|
||||||
|
// destroy old swapchain
|
||||||
s_swapchain = nullptr;
|
s_swapchain = nullptr;
|
||||||
|
|
||||||
|
// create new depth buffer image layout
|
||||||
dk::ImageLayout depthLayout;
|
dk::ImageLayout depthLayout;
|
||||||
dk::ImageLayoutMaker{s_device}
|
dk::ImageLayoutMaker{s_device}
|
||||||
.setFlags (DkImageFlags_UsageRender | DkImageFlags_HwCompression)
|
.setFlags (DkImageFlags_UsageRender | DkImageFlags_HwCompression)
|
||||||
@ -138,6 +187,7 @@ void rebuildSwapchain (unsigned const width_, unsigned const height_)
|
|||||||
auto const depthAlign = depthLayout.getAlignment ();
|
auto const depthAlign = depthLayout.getAlignment ();
|
||||||
auto const depthSize = depthLayout.getSize ();
|
auto const depthSize = depthLayout.getSize ();
|
||||||
|
|
||||||
|
// create depth buffer memblock
|
||||||
if (!s_depthMemBlock)
|
if (!s_depthMemBlock)
|
||||||
{
|
{
|
||||||
s_depthMemBlock = dk::MemBlockMaker{s_device,
|
s_depthMemBlock = dk::MemBlockMaker{s_device,
|
||||||
@ -148,6 +198,7 @@ void rebuildSwapchain (unsigned const width_, unsigned const height_)
|
|||||||
|
|
||||||
s_depthBuffer.initialize (depthLayout, s_depthMemBlock, 0);
|
s_depthBuffer.initialize (depthLayout, s_depthMemBlock, 0);
|
||||||
|
|
||||||
|
// create framebuffer image layout
|
||||||
dk::ImageLayout fbLayout;
|
dk::ImageLayout fbLayout;
|
||||||
dk::ImageLayoutMaker{s_device}
|
dk::ImageLayoutMaker{s_device}
|
||||||
.setFlags (
|
.setFlags (
|
||||||
@ -159,6 +210,7 @@ void rebuildSwapchain (unsigned const width_, unsigned const height_)
|
|||||||
auto const fbAlign = fbLayout.getAlignment ();
|
auto const fbAlign = fbLayout.getAlignment ();
|
||||||
auto const fbSize = fbLayout.getSize ();
|
auto const fbSize = fbLayout.getSize ();
|
||||||
|
|
||||||
|
// create framebuffer memblock
|
||||||
if (!s_fbMemBlock)
|
if (!s_fbMemBlock)
|
||||||
{
|
{
|
||||||
s_fbMemBlock = dk::MemBlockMaker{s_device,
|
s_fbMemBlock = dk::MemBlockMaker{s_device,
|
||||||
@ -167,6 +219,7 @@ void rebuildSwapchain (unsigned const width_, unsigned const height_)
|
|||||||
.create ();
|
.create ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initialize swapchain images
|
||||||
std::array<DkImage const *, FB_NUM> swapchainImages;
|
std::array<DkImage const *, FB_NUM> swapchainImages;
|
||||||
for (unsigned i = 0; i < FB_NUM; ++i)
|
for (unsigned i = 0; i < FB_NUM; ++i)
|
||||||
{
|
{
|
||||||
@ -174,18 +227,26 @@ void rebuildSwapchain (unsigned const width_, unsigned const height_)
|
|||||||
s_frameBuffers[i].initialize (fbLayout, s_fbMemBlock, i * fbSize);
|
s_frameBuffers[i].initialize (fbLayout, s_fbMemBlock, i * fbSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// create swapchain
|
||||||
s_swapchain = dk::SwapchainMaker{s_device, nwindowGetDefault (), swapchainImages}.create ();
|
s_swapchain = dk::SwapchainMaker{s_device, nwindowGetDefault (), swapchainImages}.create ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Load shader code
|
||||||
void loadShaders ()
|
void loadShaders ()
|
||||||
{
|
{
|
||||||
|
/// \brief Shader file descriptor
|
||||||
struct ShaderFile
|
struct ShaderFile
|
||||||
{
|
{
|
||||||
|
/// \brief Parameterized constructor
|
||||||
|
/// \param shader_ Shader object
|
||||||
|
/// \param path_ Path to source code
|
||||||
ShaderFile (dk::Shader &shader_, char const *const path_)
|
ShaderFile (dk::Shader &shader_, char const *const path_)
|
||||||
: shader (shader_), path (path_), size (getSize (path_))
|
: shader (shader_), path (path_), size (getSize (path_))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Get size of a file
|
||||||
|
/// \param path_ Path to file
|
||||||
static std::size_t getSize (char const *const path_)
|
static std::size_t getSize (char const *const path_)
|
||||||
{
|
{
|
||||||
struct stat st;
|
struct stat st;
|
||||||
@ -199,14 +260,18 @@ void loadShaders ()
|
|||||||
return st.st_size;
|
return st.st_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Shader object
|
||||||
dk::Shader &shader;
|
dk::Shader &shader;
|
||||||
|
/// \brief Path to source code
|
||||||
char const *const path;
|
char const *const path;
|
||||||
|
/// \brief Source code file size
|
||||||
std::size_t const size;
|
std::size_t const size;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto shaderFiles = {ShaderFile{s_shaders[0], "romfs:/shaders/imgui_vsh.dksh"},
|
auto shaderFiles = {ShaderFile{s_shaders[0], "romfs:/shaders/imgui_vsh.dksh"},
|
||||||
ShaderFile{s_shaders[1], "romfs:/shaders/imgui_fsh.dksh"}};
|
ShaderFile{s_shaders[1], "romfs:/shaders/imgui_fsh.dksh"}};
|
||||||
|
|
||||||
|
// calculate total size of shaders
|
||||||
auto const codeSize = std::accumulate (std::begin (shaderFiles),
|
auto const codeSize = std::accumulate (std::begin (shaderFiles),
|
||||||
std::end (shaderFiles),
|
std::end (shaderFiles),
|
||||||
DK_SHADER_CODE_UNUSABLE_SIZE,
|
DK_SHADER_CODE_UNUSABLE_SIZE,
|
||||||
@ -214,6 +279,7 @@ void loadShaders ()
|
|||||||
return sum_ + align (file_.size, DK_SHADER_CODE_ALIGNMENT);
|
return sum_ + align (file_.size, DK_SHADER_CODE_ALIGNMENT);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// create shader code memblock
|
||||||
s_codeMemBlock = dk::MemBlockMaker{s_device, align (codeSize, DK_MEMBLOCK_ALIGNMENT)}
|
s_codeMemBlock = dk::MemBlockMaker{s_device, align (codeSize, DK_MEMBLOCK_ALIGNMENT)}
|
||||||
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached |
|
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached |
|
||||||
DkMemBlockFlags_Code)
|
DkMemBlockFlags_Code)
|
||||||
@ -222,6 +288,7 @@ void loadShaders ()
|
|||||||
auto const addr = static_cast<std::uint8_t *> (s_codeMemBlock.getCpuAddr ());
|
auto const addr = static_cast<std::uint8_t *> (s_codeMemBlock.getCpuAddr ());
|
||||||
std::size_t offset = 0;
|
std::size_t offset = 0;
|
||||||
|
|
||||||
|
// read shaders into memblock
|
||||||
for (auto &file : shaderFiles)
|
for (auto &file : shaderFiles)
|
||||||
{
|
{
|
||||||
std::uint32_t const codeOffset = offset;
|
std::uint32_t const codeOffset = offset;
|
||||||
@ -245,13 +312,18 @@ void loadShaders ()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Setup render state
|
||||||
|
/// \param slot_ Swapchain slot
|
||||||
|
/// \param drawData_ Data to draw
|
||||||
|
/// \param width_ Framebuffer width
|
||||||
|
/// \param height_ Framebuffer height
|
||||||
DkCmdList setupRenderState (int const slot_,
|
DkCmdList setupRenderState (int const slot_,
|
||||||
ImDrawData *const drawData_,
|
ImDrawData *const drawData_,
|
||||||
unsigned const width_,
|
unsigned const width_,
|
||||||
unsigned const height_)
|
unsigned const height_)
|
||||||
{
|
{
|
||||||
// Setup viewport, orthographic projection matrix
|
// setup viewport, orthographic projection matrix
|
||||||
// Our visible imgui space lies from drawData_->DisplayPos (top left) to
|
// our visible imgui space lies from drawData_->DisplayPos (top left) to
|
||||||
// drawData_->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single
|
// drawData_->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single
|
||||||
// viewport apps.
|
// viewport apps.
|
||||||
auto const L = drawData_->DisplayPos.x;
|
auto const L = drawData_->DisplayPos.x;
|
||||||
@ -262,6 +334,7 @@ DkCmdList setupRenderState (int const slot_,
|
|||||||
VertUBO vertUBO;
|
VertUBO vertUBO;
|
||||||
vertUBO.projMtx = glm::orthoRH_ZO (L, R, B, T, -1.0f, 1.0f);
|
vertUBO.projMtx = glm::orthoRH_ZO (L, R, B, T, -1.0f, 1.0f);
|
||||||
|
|
||||||
|
// create command buffer to initialize/reset render state
|
||||||
s_cmdBuf[slot_].setViewports (0, DkViewport{0.0f, 0.0f, width_, height_});
|
s_cmdBuf[slot_].setViewports (0, DkViewport{0.0f, 0.0f, width_, height_});
|
||||||
s_cmdBuf[slot_].bindShaders (DkStageFlag_GraphicsMask, {&s_shaders[0], &s_shaders[1]});
|
s_cmdBuf[slot_].bindShaders (DkStageFlag_GraphicsMask, {&s_shaders[0], &s_shaders[1]});
|
||||||
s_cmdBuf[slot_].bindUniformBuffer (DkStage_Vertex,
|
s_cmdBuf[slot_].bindUniformBuffer (DkStage_Vertex,
|
||||||
@ -286,8 +359,8 @@ DkCmdList setupRenderState (int const slot_,
|
|||||||
DkBlendFactor_InvSrcAlpha,
|
DkBlendFactor_InvSrcAlpha,
|
||||||
DkBlendFactor_InvSrcAlpha,
|
DkBlendFactor_InvSrcAlpha,
|
||||||
DkBlendFactor_Zero));
|
DkBlendFactor_Zero));
|
||||||
s_cmdBuf[slot_].bindVtxAttribState (VertexAttribState);
|
s_cmdBuf[slot_].bindVtxAttribState (VERTEX_ATTRIB_STATE);
|
||||||
s_cmdBuf[slot_].bindVtxBufferState (VertexBufferState);
|
s_cmdBuf[slot_].bindVtxBufferState (VERTEX_BUFFER_STATE);
|
||||||
|
|
||||||
return s_cmdBuf[slot_].finishList ();
|
return s_cmdBuf[slot_].finishList ();
|
||||||
}
|
}
|
||||||
@ -295,17 +368,21 @@ DkCmdList setupRenderState (int const slot_,
|
|||||||
|
|
||||||
void imgui::deko3d::init ()
|
void imgui::deko3d::init ()
|
||||||
{
|
{
|
||||||
// Setup back-end capabilities flags
|
// setup back-end capabilities flags
|
||||||
ImGuiIO &io = ImGui::GetIO ();
|
ImGuiIO &io = ImGui::GetIO ();
|
||||||
|
|
||||||
io.BackendRendererName = "deko3d";
|
io.BackendRendererName = "deko3d";
|
||||||
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
|
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
|
||||||
|
|
||||||
|
// defer initialization to first newFrame ()
|
||||||
}
|
}
|
||||||
|
|
||||||
void imgui::deko3d::exit ()
|
void imgui::deko3d::exit ()
|
||||||
{
|
{
|
||||||
|
// wait for queue to be idle
|
||||||
s_queue.waitIdle ();
|
s_queue.waitIdle ();
|
||||||
|
|
||||||
|
// clean up all of the deko3d objects
|
||||||
s_queue = nullptr;
|
s_queue = nullptr;
|
||||||
s_descriptorMemBlock = nullptr;
|
s_descriptorMemBlock = nullptr;
|
||||||
s_imageMemBlock = nullptr;
|
s_imageMemBlock = nullptr;
|
||||||
@ -331,12 +408,16 @@ void imgui::deko3d::newFrame ()
|
|||||||
if (s_device)
|
if (s_device)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// create deko3d device
|
||||||
s_device = dk::DeviceMaker{}.create ();
|
s_device = dk::DeviceMaker{}.create ();
|
||||||
|
|
||||||
|
// initialize swapchain with maximum resolution
|
||||||
rebuildSwapchain (1920, 1080);
|
rebuildSwapchain (1920, 1080);
|
||||||
|
|
||||||
|
// load shader code
|
||||||
loadShaders ();
|
loadShaders ();
|
||||||
|
|
||||||
|
// create UBO memblock
|
||||||
s_uboMemBlock = dk::MemBlockMaker{s_device,
|
s_uboMemBlock = dk::MemBlockMaker{s_device,
|
||||||
align (align (sizeof (VertUBO), DK_UNIFORM_BUF_ALIGNMENT) +
|
align (align (sizeof (VertUBO), DK_UNIFORM_BUF_ALIGNMENT) +
|
||||||
align (sizeof (FragUBO), DK_UNIFORM_BUF_ALIGNMENT),
|
align (sizeof (FragUBO), DK_UNIFORM_BUF_ALIGNMENT),
|
||||||
@ -344,32 +425,38 @@ void imgui::deko3d::newFrame ()
|
|||||||
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
|
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
|
||||||
.create ();
|
.create ();
|
||||||
|
|
||||||
|
// create memblocks for each framebuffer slot
|
||||||
for (std::size_t i = 0; i < FB_NUM; ++i)
|
for (std::size_t i = 0; i < FB_NUM; ++i)
|
||||||
{
|
{
|
||||||
|
// create vertex data memblock
|
||||||
s_vtxMemBlock[i] = dk::MemBlockMaker{s_device, align (DATABUF_SIZE, DK_MEMBLOCK_ALIGNMENT)}
|
s_vtxMemBlock[i] = dk::MemBlockMaker{s_device, align (DATABUF_SIZE, DK_MEMBLOCK_ALIGNMENT)}
|
||||||
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
|
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
|
||||||
.create ();
|
.create ();
|
||||||
|
|
||||||
|
// create index data memblock
|
||||||
s_idxMemBlock[i] = dk::MemBlockMaker{s_device, align (INDEXBUF_SIZE, DK_MEMBLOCK_ALIGNMENT)}
|
s_idxMemBlock[i] = dk::MemBlockMaker{s_device, align (INDEXBUF_SIZE, DK_MEMBLOCK_ALIGNMENT)}
|
||||||
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
|
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
|
||||||
.create ();
|
.create ();
|
||||||
|
|
||||||
|
// create command buffer memblock
|
||||||
s_cmdMemBlock[i] = dk::MemBlockMaker{s_device, align (CMDBUF_SIZE, DK_MEMBLOCK_ALIGNMENT)}
|
s_cmdMemBlock[i] = dk::MemBlockMaker{s_device, align (CMDBUF_SIZE, DK_MEMBLOCK_ALIGNMENT)}
|
||||||
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
|
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
|
||||||
.create ();
|
.create ();
|
||||||
|
|
||||||
|
// create command buffer
|
||||||
s_cmdBuf[i] = dk::CmdBufMaker{s_device}.create ();
|
s_cmdBuf[i] = dk::CmdBufMaker{s_device}.create ();
|
||||||
|
|
||||||
s_cmdBuf[i].addMemory (s_cmdMemBlock[i], 0, s_cmdMemBlock[i].getSize ());
|
s_cmdBuf[i].addMemory (s_cmdMemBlock[i], 0, s_cmdMemBlock[i].getSize ());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// create queue
|
||||||
s_queue = dk::QueueMaker{s_device}.setFlags (DkQueueFlags_Graphics).create ();
|
s_queue = dk::QueueMaker{s_device}.setFlags (DkQueueFlags_Graphics).create ();
|
||||||
|
|
||||||
|
// create image memblock
|
||||||
s_imageMemBlock = dk::MemBlockMaker{s_device, align (IMAGEBUF_SIZE, DK_MEMBLOCK_ALIGNMENT)}
|
s_imageMemBlock = dk::MemBlockMaker{s_device, align (IMAGEBUF_SIZE, DK_MEMBLOCK_ALIGNMENT)}
|
||||||
.setFlags (DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image)
|
.setFlags (DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image)
|
||||||
.create ();
|
.create ();
|
||||||
|
|
||||||
// Build texture atlas
|
// get texture atlas
|
||||||
ImGuiIO &io = ImGui::GetIO ();
|
ImGuiIO &io = ImGui::GetIO ();
|
||||||
io.Fonts->SetTexID (nullptr);
|
io.Fonts->SetTexID (nullptr);
|
||||||
unsigned char *pixels;
|
unsigned char *pixels;
|
||||||
@ -377,12 +464,14 @@ void imgui::deko3d::newFrame ()
|
|||||||
int height;
|
int height;
|
||||||
io.Fonts->GetTexDataAsAlpha8 (&pixels, &width, &height);
|
io.Fonts->GetTexDataAsAlpha8 (&pixels, &width, &height);
|
||||||
|
|
||||||
|
// create memblock for transfer
|
||||||
dk::UniqueMemBlock memBlock =
|
dk::UniqueMemBlock memBlock =
|
||||||
dk::MemBlockMaker{s_device, align (width * height, DK_MEMBLOCK_ALIGNMENT)}
|
dk::MemBlockMaker{s_device, align (width * height, DK_MEMBLOCK_ALIGNMENT)}
|
||||||
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
|
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
|
||||||
.create ();
|
.create ();
|
||||||
std::memcpy (memBlock.getCpuAddr (), pixels, width * height);
|
std::memcpy (memBlock.getCpuAddr (), pixels, width * height);
|
||||||
|
|
||||||
|
// create image/sampler memblock
|
||||||
static_assert (sizeof (dk::ImageDescriptor) == DK_IMAGE_DESCRIPTOR_ALIGNMENT);
|
static_assert (sizeof (dk::ImageDescriptor) == DK_IMAGE_DESCRIPTOR_ALIGNMENT);
|
||||||
static_assert (sizeof (dk::SamplerDescriptor) == DK_SAMPLER_DESCRIPTOR_ALIGNMENT);
|
static_assert (sizeof (dk::SamplerDescriptor) == DK_SAMPLER_DESCRIPTOR_ALIGNMENT);
|
||||||
static_assert (DK_IMAGE_DESCRIPTOR_ALIGNMENT == DK_SAMPLER_DESCRIPTOR_ALIGNMENT);
|
static_assert (DK_IMAGE_DESCRIPTOR_ALIGNMENT == DK_SAMPLER_DESCRIPTOR_ALIGNMENT);
|
||||||
@ -391,17 +480,22 @@ void imgui::deko3d::newFrame ()
|
|||||||
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
|
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
|
||||||
.create ();
|
.create ();
|
||||||
|
|
||||||
|
// get cpu address for descriptors
|
||||||
s_samplerDescriptors =
|
s_samplerDescriptors =
|
||||||
static_cast<dk::SamplerDescriptor *> (s_descriptorMemBlock.getCpuAddr ());
|
static_cast<dk::SamplerDescriptor *> (s_descriptorMemBlock.getCpuAddr ());
|
||||||
s_imageDescriptors =
|
s_imageDescriptors =
|
||||||
reinterpret_cast<dk::ImageDescriptor *> (&s_samplerDescriptors[MAX_SAMPLERS]);
|
reinterpret_cast<dk::ImageDescriptor *> (&s_samplerDescriptors[MAX_SAMPLERS]);
|
||||||
|
|
||||||
|
// initialize sampler descriptor
|
||||||
s_samplerDescriptors[0].initialize (
|
s_samplerDescriptors[0].initialize (
|
||||||
dk::Sampler{}
|
dk::Sampler{}
|
||||||
.setFilter (DkFilter_Linear, DkFilter_Linear)
|
.setFilter (DkFilter_Linear, DkFilter_Linear)
|
||||||
.setWrapMode (DkWrapMode_ClampToEdge, DkWrapMode_ClampToEdge, DkWrapMode_ClampToEdge));
|
.setWrapMode (DkWrapMode_ClampToEdge, DkWrapMode_ClampToEdge, DkWrapMode_ClampToEdge));
|
||||||
|
|
||||||
|
// use command buffer 0 for initialization
|
||||||
auto &cmdBuf = s_cmdBuf[0];
|
auto &cmdBuf = s_cmdBuf[0];
|
||||||
|
|
||||||
|
// initialize texture atlas image layout
|
||||||
dk::ImageLayout layout;
|
dk::ImageLayout layout;
|
||||||
dk::ImageLayoutMaker{s_device}
|
dk::ImageLayoutMaker{s_device}
|
||||||
.setFlags (0)
|
.setFlags (0)
|
||||||
@ -409,21 +503,25 @@ void imgui::deko3d::newFrame ()
|
|||||||
.setDimensions (width, height)
|
.setDimensions (width, height)
|
||||||
.initialize (layout);
|
.initialize (layout);
|
||||||
|
|
||||||
|
// initialize font texture atlas image descriptor
|
||||||
s_fontTexture.initialize (layout, s_imageMemBlock, 0);
|
s_fontTexture.initialize (layout, s_imageMemBlock, 0);
|
||||||
s_imageDescriptors[0].initialize (s_fontTexture);
|
s_imageDescriptors[0].initialize (s_fontTexture);
|
||||||
|
|
||||||
|
// copy font texture atlas to image view
|
||||||
dk::ImageView imageView{s_fontTexture};
|
dk::ImageView imageView{s_fontTexture};
|
||||||
cmdBuf.copyBufferToImage ({memBlock.getGpuAddr ()}, imageView, {0, 0, 0, width, height, 1});
|
cmdBuf.copyBufferToImage ({memBlock.getGpuAddr ()}, imageView, {0, 0, 0, width, height, 1});
|
||||||
|
|
||||||
|
// bind image/sampler descriptors
|
||||||
cmdBuf.bindSamplerDescriptorSet (s_descriptorMemBlock.getGpuAddr (), MAX_SAMPLERS);
|
cmdBuf.bindSamplerDescriptorSet (s_descriptorMemBlock.getGpuAddr (), MAX_SAMPLERS);
|
||||||
cmdBuf.bindImageDescriptorSet (
|
cmdBuf.bindImageDescriptorSet (
|
||||||
s_descriptorMemBlock.getGpuAddr () + MAX_SAMPLERS * sizeof (dk::SamplerDescriptor),
|
s_descriptorMemBlock.getGpuAddr () + MAX_SAMPLERS * sizeof (dk::SamplerDescriptor),
|
||||||
MAX_IMAGES);
|
MAX_IMAGES);
|
||||||
|
|
||||||
|
// submit commands while we get the next image ready to transfer
|
||||||
s_queue.submitCommands (cmdBuf.finishList ());
|
s_queue.submitCommands (cmdBuf.finishList ());
|
||||||
s_queue.waitIdle ();
|
|
||||||
|
|
||||||
{
|
{
|
||||||
|
// read the deko3d logo
|
||||||
auto const path = "romfs:/deko3d.rgba.zst";
|
auto const path = "romfs:/deko3d.rgba.zst";
|
||||||
|
|
||||||
struct stat st;
|
struct stat st;
|
||||||
@ -456,10 +554,13 @@ void imgui::deko3d::newFrame ()
|
|||||||
std::abort ();
|
std::abort ();
|
||||||
}
|
}
|
||||||
|
|
||||||
memBlock = dk::MemBlockMaker{s_device, align (size, DK_MEMBLOCK_ALIGNMENT)}
|
// create memblock for transfer
|
||||||
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
|
dk::UniqueMemBlock memBlock =
|
||||||
.create ();
|
dk::MemBlockMaker{s_device, align (size, DK_MEMBLOCK_ALIGNMENT)}
|
||||||
|
.setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
|
||||||
|
.create ();
|
||||||
|
|
||||||
|
// decompress into transfer memblock
|
||||||
auto const decoded =
|
auto const decoded =
|
||||||
ZSTD_decompress (memBlock.getCpuAddr (), size, buffer.data (), st.st_size);
|
ZSTD_decompress (memBlock.getCpuAddr (), size, buffer.data (), st.st_size);
|
||||||
if (ZSTD_isError (decoded))
|
if (ZSTD_isError (decoded))
|
||||||
@ -468,6 +569,7 @@ void imgui::deko3d::newFrame ()
|
|||||||
std::abort ();
|
std::abort ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initialize deko3d logo texture image layout
|
||||||
dk::ImageLayout layout;
|
dk::ImageLayout layout;
|
||||||
dk::ImageLayoutMaker{s_device}
|
dk::ImageLayoutMaker{s_device}
|
||||||
.setFlags (0)
|
.setFlags (0)
|
||||||
@ -475,32 +577,41 @@ void imgui::deko3d::newFrame ()
|
|||||||
.setDimensions (LOGO_WIDTH, LOGO_HEIGHT)
|
.setDimensions (LOGO_WIDTH, LOGO_HEIGHT)
|
||||||
.initialize (layout);
|
.initialize (layout);
|
||||||
|
|
||||||
|
// initialize deko3d logo texture image descriptor
|
||||||
auto const offset = align (width * height, DK_IMAGE_LINEAR_STRIDE_ALIGNMENT);
|
auto const offset = align (width * height, DK_IMAGE_LINEAR_STRIDE_ALIGNMENT);
|
||||||
s_logoTexture.initialize (layout, s_imageMemBlock, offset);
|
s_logoTexture.initialize (layout, s_imageMemBlock, offset);
|
||||||
s_imageDescriptors[1].initialize (s_logoTexture);
|
s_imageDescriptors[1].initialize (s_logoTexture);
|
||||||
|
|
||||||
|
// copy deko3d logo texture to image view
|
||||||
dk::ImageView imageView{s_logoTexture};
|
dk::ImageView imageView{s_logoTexture};
|
||||||
cmdBuf.copyBufferToImage (
|
cmdBuf.copyBufferToImage (
|
||||||
{memBlock.getGpuAddr ()}, imageView, {0, 0, 0, LOGO_WIDTH, LOGO_HEIGHT, 1});
|
{memBlock.getGpuAddr ()}, imageView, {0, 0, 0, LOGO_WIDTH, LOGO_HEIGHT, 1});
|
||||||
|
|
||||||
|
// submit commands to transfer deko3d logo texture
|
||||||
s_queue.submitCommands (cmdBuf.finishList ());
|
s_queue.submitCommands (cmdBuf.finishList ());
|
||||||
|
|
||||||
|
// wait for commands to complete before releasing memblocks
|
||||||
s_queue.waitIdle ();
|
s_queue.waitIdle ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reset command buffer
|
||||||
cmdBuf.clear ();
|
cmdBuf.clear ();
|
||||||
}
|
}
|
||||||
|
|
||||||
void imgui::deko3d::render ()
|
void imgui::deko3d::render ()
|
||||||
{
|
{
|
||||||
|
// get ImGui draw data
|
||||||
auto const drawData = ImGui::GetDrawData ();
|
auto const drawData = ImGui::GetDrawData ();
|
||||||
if (drawData->CmdListsCount <= 0)
|
if (drawData->CmdListsCount <= 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// get framebuffer dimensions
|
||||||
unsigned width = drawData->DisplaySize.x * drawData->FramebufferScale.x;
|
unsigned width = drawData->DisplaySize.x * drawData->FramebufferScale.x;
|
||||||
unsigned height = drawData->DisplaySize.y * drawData->FramebufferScale.y;
|
unsigned height = drawData->DisplaySize.y * drawData->FramebufferScale.y;
|
||||||
if (width <= 0 || height <= 0)
|
if (width <= 0 || height <= 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// check if we need to rebuild the swapchain
|
||||||
if (width != s_width || height != s_height)
|
if (width != s_width || height != s_height)
|
||||||
{
|
{
|
||||||
s_width = width;
|
s_width = width;
|
||||||
@ -510,9 +621,11 @@ void imgui::deko3d::render ()
|
|||||||
rebuildSwapchain (width, height);
|
rebuildSwapchain (width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get image from queue
|
||||||
auto const slot = s_queue.acquireImage (s_swapchain);
|
auto const slot = s_queue.acquireImage (s_swapchain);
|
||||||
s_cmdBuf[slot].clear ();
|
s_cmdBuf[slot].clear ();
|
||||||
|
|
||||||
|
// bind frame/depth buffers and clear them
|
||||||
dk::ImageView colorTarget{s_frameBuffers[slot]};
|
dk::ImageView colorTarget{s_frameBuffers[slot]};
|
||||||
dk::ImageView depthTarget{s_depthBuffer};
|
dk::ImageView depthTarget{s_depthBuffer};
|
||||||
s_cmdBuf[slot].bindRenderTargets (&colorTarget, &depthTarget);
|
s_cmdBuf[slot].bindRenderTargets (&colorTarget, &depthTarget);
|
||||||
@ -520,21 +633,22 @@ void imgui::deko3d::render ()
|
|||||||
s_cmdBuf[slot].clearDepthStencil (true, 1.0f, 0xFF, 0);
|
s_cmdBuf[slot].clearDepthStencil (true, 1.0f, 0xFF, 0);
|
||||||
s_queue.submitCommands (s_cmdBuf[slot].finishList ());
|
s_queue.submitCommands (s_cmdBuf[slot].finishList ());
|
||||||
|
|
||||||
// Setup desired render state
|
// setup desired render state
|
||||||
auto const setupCmd = setupRenderState (slot, drawData, width, height);
|
auto const setupCmd = setupRenderState (slot, drawData, width, height);
|
||||||
s_queue.submitCommands (setupCmd);
|
s_queue.submitCommands (setupCmd);
|
||||||
|
|
||||||
|
// start with bogus descriptor binding so it'll be updated before first draw call
|
||||||
s_boundDescriptor = ~static_cast<std::uintptr_t> (0);
|
s_boundDescriptor = ~static_cast<std::uintptr_t> (0);
|
||||||
|
|
||||||
// Will project scissor/clipping rectangles into framebuffer space
|
// will project scissor/clipping rectangles into framebuffer space
|
||||||
// (0,0) unless using multi-viewports
|
// (0,0) unless using multi-viewports
|
||||||
auto const clipOff = drawData->DisplayPos;
|
auto const clipOff = drawData->DisplayPos;
|
||||||
// (1,1) unless using retina display which are often (2,2)
|
// (1,1) unless using retina display which are often (2,2)
|
||||||
auto const clipScale = drawData->FramebufferScale;
|
auto const clipScale = drawData->FramebufferScale;
|
||||||
|
|
||||||
|
// check if we need to grow vertex data memblock
|
||||||
if (s_vtxMemBlock[slot].getSize () < drawData->TotalVtxCount * sizeof (ImDrawVert))
|
if (s_vtxMemBlock[slot].getSize () < drawData->TotalVtxCount * sizeof (ImDrawVert))
|
||||||
{
|
{
|
||||||
s_vtxMemBlock[slot] = nullptr;
|
|
||||||
s_vtxMemBlock[slot] =
|
s_vtxMemBlock[slot] =
|
||||||
dk::MemBlockMaker{s_device,
|
dk::MemBlockMaker{s_device,
|
||||||
align (drawData->TotalVtxCount * sizeof (ImDrawVert), DK_MEMBLOCK_ALIGNMENT)}
|
align (drawData->TotalVtxCount * sizeof (ImDrawVert), DK_MEMBLOCK_ALIGNMENT)}
|
||||||
@ -542,9 +656,9 @@ void imgui::deko3d::render ()
|
|||||||
.create ();
|
.create ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if we need to grow index data memblock
|
||||||
if (s_idxMemBlock[slot].getSize () < drawData->TotalIdxCount * sizeof (ImDrawIdx))
|
if (s_idxMemBlock[slot].getSize () < drawData->TotalIdxCount * sizeof (ImDrawIdx))
|
||||||
{
|
{
|
||||||
s_idxMemBlock[slot] = nullptr;
|
|
||||||
s_idxMemBlock[slot] =
|
s_idxMemBlock[slot] =
|
||||||
dk::MemBlockMaker{s_device,
|
dk::MemBlockMaker{s_device,
|
||||||
align (drawData->TotalIdxCount * sizeof (ImDrawIdx), DK_MEMBLOCK_ALIGNMENT)}
|
align (drawData->TotalIdxCount * sizeof (ImDrawIdx), DK_MEMBLOCK_ALIGNMENT)}
|
||||||
@ -552,20 +666,24 @@ void imgui::deko3d::render ()
|
|||||||
.create ();
|
.create ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get base cpu addresses
|
||||||
auto const cpuVtx = static_cast<std::uint8_t *> (s_vtxMemBlock[slot].getCpuAddr ());
|
auto const cpuVtx = static_cast<std::uint8_t *> (s_vtxMemBlock[slot].getCpuAddr ());
|
||||||
auto const cpuIdx = static_cast<std::uint8_t *> (s_idxMemBlock[slot].getCpuAddr ());
|
auto const cpuIdx = static_cast<std::uint8_t *> (s_idxMemBlock[slot].getCpuAddr ());
|
||||||
|
|
||||||
|
// get base gpu addresses
|
||||||
auto const gpuVtx = s_vtxMemBlock[slot].getGpuAddr ();
|
auto const gpuVtx = s_vtxMemBlock[slot].getGpuAddr ();
|
||||||
auto const gpuIdx = s_idxMemBlock[slot].getGpuAddr ();
|
auto const gpuIdx = s_idxMemBlock[slot].getGpuAddr ();
|
||||||
|
|
||||||
|
// get memblock sizes
|
||||||
auto const sizeVtx = s_vtxMemBlock[slot].getSize ();
|
auto const sizeVtx = s_vtxMemBlock[slot].getSize ();
|
||||||
auto const sizeIdx = s_idxMemBlock[slot].getSize ();
|
auto const sizeIdx = s_idxMemBlock[slot].getSize ();
|
||||||
|
|
||||||
|
// bind vertex/index data memblocks
|
||||||
static_assert (sizeof (ImDrawIdx) == 2);
|
static_assert (sizeof (ImDrawIdx) == 2);
|
||||||
s_cmdBuf[slot].bindVtxBuffer (0, gpuVtx, sizeVtx);
|
s_cmdBuf[slot].bindVtxBuffer (0, gpuVtx, sizeVtx);
|
||||||
s_cmdBuf[slot].bindIdxBuffer (DkIdxFormat_Uint16, gpuIdx);
|
s_cmdBuf[slot].bindIdxBuffer (DkIdxFormat_Uint16, gpuIdx);
|
||||||
|
|
||||||
// Render command lists
|
// render command lists
|
||||||
std::size_t offsetVtx = 0;
|
std::size_t offsetVtx = 0;
|
||||||
std::size_t offsetIdx = 0;
|
std::size_t offsetIdx = 0;
|
||||||
for (int i = 0; i < drawData->CmdListsCount; ++i)
|
for (int i = 0; i < drawData->CmdListsCount; ++i)
|
||||||
@ -575,6 +693,7 @@ void imgui::deko3d::render ()
|
|||||||
auto const vtxSize = cmdList.VtxBuffer.Size * sizeof (ImDrawVert);
|
auto const vtxSize = cmdList.VtxBuffer.Size * sizeof (ImDrawVert);
|
||||||
auto const idxSize = cmdList.IdxBuffer.Size * sizeof (ImDrawIdx);
|
auto const idxSize = cmdList.IdxBuffer.Size * sizeof (ImDrawIdx);
|
||||||
|
|
||||||
|
// double check that we don't overrun vertex data memblock
|
||||||
if (sizeVtx - offsetVtx < vtxSize)
|
if (sizeVtx - offsetVtx < vtxSize)
|
||||||
{
|
{
|
||||||
std::fprintf (stderr, "Not enough vertex buffer\n");
|
std::fprintf (stderr, "Not enough vertex buffer\n");
|
||||||
@ -582,6 +701,7 @@ void imgui::deko3d::render ()
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// double check that we don't overrun index data memblock
|
||||||
if (sizeIdx - offsetIdx < idxSize)
|
if (sizeIdx - offsetIdx < idxSize)
|
||||||
{
|
{
|
||||||
std::fprintf (stderr, "Not enough index buffer\n");
|
std::fprintf (stderr, "Not enough index buffer\n");
|
||||||
@ -589,6 +709,7 @@ void imgui::deko3d::render ()
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// copy vertex/index data into memblocks
|
||||||
std::memcpy (cpuVtx + offsetVtx, cmdList.VtxBuffer.Data, vtxSize);
|
std::memcpy (cpuVtx + offsetVtx, cmdList.VtxBuffer.Data, vtxSize);
|
||||||
std::memcpy (cpuIdx + offsetIdx, cmdList.IdxBuffer.Data, idxSize);
|
std::memcpy (cpuIdx + offsetIdx, cmdList.IdxBuffer.Data, idxSize);
|
||||||
|
|
||||||
@ -596,9 +717,10 @@ void imgui::deko3d::render ()
|
|||||||
{
|
{
|
||||||
if (cmd.UserCallback)
|
if (cmd.UserCallback)
|
||||||
{
|
{
|
||||||
|
// submit commands to preserve ordering
|
||||||
s_queue.submitCommands (s_cmdBuf[slot].finishList ());
|
s_queue.submitCommands (s_cmdBuf[slot].finishList ());
|
||||||
|
|
||||||
// User callback, registered via ImDrawList::AddCallback()
|
// user callback, registered via ImDrawList::AddCallback()
|
||||||
// (ImDrawCallback_ResetRenderState is a special callback value used by the user to
|
// (ImDrawCallback_ResetRenderState is a special callback value used by the user to
|
||||||
// request the renderer to reset render state.)
|
// request the renderer to reset render state.)
|
||||||
if (cmd.UserCallback == ImDrawCallback_ResetRenderState)
|
if (cmd.UserCallback == ImDrawCallback_ResetRenderState)
|
||||||
@ -608,53 +730,65 @@ void imgui::deko3d::render ()
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Project scissor/clipping rectangles into framebuffer space
|
// project scissor/clipping rectangles into framebuffer space
|
||||||
ImVec4 clip;
|
ImVec4 clip;
|
||||||
clip.x = (cmd.ClipRect.x - clipOff.x) * clipScale.x;
|
clip.x = (cmd.ClipRect.x - clipOff.x) * clipScale.x;
|
||||||
clip.y = (cmd.ClipRect.y - clipOff.y) * clipScale.y;
|
clip.y = (cmd.ClipRect.y - clipOff.y) * clipScale.y;
|
||||||
clip.z = (cmd.ClipRect.z - clipOff.x) * clipScale.x;
|
clip.z = (cmd.ClipRect.z - clipOff.x) * clipScale.x;
|
||||||
clip.w = (cmd.ClipRect.w - clipOff.y) * clipScale.y;
|
clip.w = (cmd.ClipRect.w - clipOff.y) * clipScale.y;
|
||||||
|
|
||||||
if (clip.x < width && clip.y < height && clip.z >= 0.0f && clip.w >= 0.0f)
|
if (clip.x >= width || clip.y >= height || clip.z < 0.0f || clip.w < 0.0f)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// keep scissor coordinates inside viewport
|
||||||
|
if (clip.x < 0.0f)
|
||||||
|
clip.x = 0.0f;
|
||||||
|
if (clip.y < 0.0f)
|
||||||
|
clip.y = 0.0f;
|
||||||
|
if (clip.z > width)
|
||||||
|
clip.z = width;
|
||||||
|
if (clip.w > height)
|
||||||
|
clip.z = height;
|
||||||
|
|
||||||
|
// apply scissor boundaries
|
||||||
|
s_cmdBuf[slot].setScissors (
|
||||||
|
0, DkScissor{clip.x, clip.y, clip.z - clip.x, clip.w - clip.y});
|
||||||
|
|
||||||
|
// get image descriptor
|
||||||
|
auto const descriptor = reinterpret_cast<std::uintptr_t> (cmd.TextureId);
|
||||||
|
if (descriptor >= MAX_IMAGES)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// check if we need to bind a new texture
|
||||||
|
if (descriptor != s_boundDescriptor)
|
||||||
{
|
{
|
||||||
if (clip.x < 0.0f)
|
s_boundDescriptor = descriptor;
|
||||||
clip.x = 0.0f;
|
|
||||||
if (clip.y < 0.0f)
|
|
||||||
clip.y = 0.0f;
|
|
||||||
|
|
||||||
s_cmdBuf[slot].setScissors (
|
// bind the new texture
|
||||||
0, DkScissor{clip.x, clip.y, clip.z - clip.x, clip.w - clip.y});
|
s_cmdBuf[slot].bindTextures (
|
||||||
|
DkStage_Fragment, 0, dkMakeTextureHandle (descriptor, 0));
|
||||||
|
|
||||||
auto const descriptor = reinterpret_cast<std::uintptr_t> (cmd.TextureId);
|
// check if this is the font texture atlas image descriptor
|
||||||
if (descriptor >= MAX_IMAGES)
|
FragUBO fragUBO;
|
||||||
continue;
|
fragUBO.font = (descriptor == 0);
|
||||||
|
|
||||||
if (descriptor != s_boundDescriptor)
|
// update fragment shader UBO
|
||||||
{
|
s_cmdBuf[slot].pushConstants (
|
||||||
s_boundDescriptor = descriptor;
|
s_uboMemBlock.getGpuAddr () +
|
||||||
|
align (sizeof (VertUBO), DK_UNIFORM_BUF_ALIGNMENT),
|
||||||
s_cmdBuf[slot].bindTextures (
|
align (sizeof (FragUBO), DK_UNIFORM_BUF_ALIGNMENT),
|
||||||
DkStage_Fragment, 0, dkMakeTextureHandle (descriptor, 0));
|
0,
|
||||||
|
sizeof (FragUBO),
|
||||||
FragUBO fragUBO;
|
&fragUBO);
|
||||||
fragUBO.font = (descriptor == 0);
|
|
||||||
|
|
||||||
s_cmdBuf[slot].pushConstants (
|
|
||||||
s_uboMemBlock.getGpuAddr () +
|
|
||||||
align (sizeof (VertUBO), DK_UNIFORM_BUF_ALIGNMENT),
|
|
||||||
align (sizeof (FragUBO), DK_UNIFORM_BUF_ALIGNMENT),
|
|
||||||
0,
|
|
||||||
sizeof (FragUBO),
|
|
||||||
&fragUBO);
|
|
||||||
}
|
|
||||||
|
|
||||||
s_cmdBuf[slot].drawIndexed (DkPrimitive_Triangles,
|
|
||||||
cmd.ElemCount,
|
|
||||||
1,
|
|
||||||
cmd.IdxOffset + offsetIdx / sizeof (ImDrawIdx),
|
|
||||||
cmd.VtxOffset + offsetVtx / sizeof (ImDrawVert),
|
|
||||||
0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// draw the draw list
|
||||||
|
s_cmdBuf[slot].drawIndexed (DkPrimitive_Triangles,
|
||||||
|
cmd.ElemCount,
|
||||||
|
1,
|
||||||
|
cmd.IdxOffset + offsetIdx / sizeof (ImDrawIdx),
|
||||||
|
cmd.VtxOffset + offsetVtx / sizeof (ImDrawVert),
|
||||||
|
0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -662,10 +796,14 @@ void imgui::deko3d::render ()
|
|||||||
offsetIdx += idxSize;
|
offsetIdx += idxSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wait for fragments to be completed before discarding depth/stencil buffer
|
||||||
s_cmdBuf[slot].barrier (DkBarrier_Fragments, 0);
|
s_cmdBuf[slot].barrier (DkBarrier_Fragments, 0);
|
||||||
s_cmdBuf[slot].discardDepthStencil ();
|
s_cmdBuf[slot].discardDepthStencil ();
|
||||||
|
|
||||||
|
// submit final commands
|
||||||
s_queue.submitCommands (s_cmdBuf[slot].finishList ());
|
s_queue.submitCommands (s_cmdBuf[slot].finishList ());
|
||||||
|
|
||||||
|
// present image
|
||||||
s_queue.presentImage (s_swapchain, slot);
|
s_queue.presentImage (s_swapchain, slot);
|
||||||
}
|
}
|
||||||
|
|
@ -33,6 +33,7 @@ layout (location = 0) out vec4 outColor;
|
|||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
|
// font texture is single-channel (alpha)
|
||||||
if (ubo.font != 0)
|
if (ubo.font != 0)
|
||||||
outColor = vtxColor * vec4 (vec3 (1.0), texture (tex, vtxUv).r);
|
outColor = vtxColor * vec4 (vec3 (1.0), texture (tex, vtxUv).r);
|
||||||
else
|
else
|
@ -23,6 +23,7 @@
|
|||||||
#include "imgui.h"
|
#include "imgui.h"
|
||||||
|
|
||||||
#include "fs.h"
|
#include "fs.h"
|
||||||
|
#include "platform.h"
|
||||||
|
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
|
|
||||||
@ -35,23 +36,32 @@ using namespace std::chrono_literals;
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
/// \brief Font atlas cache file
|
||||||
constexpr auto FONT_ATLAS_BIN = "ftpd-font.bin";
|
constexpr auto FONT_ATLAS_BIN = "ftpd-font.bin";
|
||||||
|
|
||||||
bool s_mouseJustPressed[IM_ARRAYSIZE (ImGuiIO::MouseDown)];
|
/// \brief Last mouse update timestamp
|
||||||
|
std::chrono::steady_clock::time_point s_lastMouseUpdate;
|
||||||
std::chrono::high_resolution_clock::time_point s_lastMouseUpdate;
|
|
||||||
|
|
||||||
|
/// \brief Whether application is focused
|
||||||
bool s_focused = true;
|
bool s_focused = true;
|
||||||
float s_width = 1280.0f;
|
|
||||||
|
/// \brief Framebuffer width
|
||||||
|
float s_width = 1280.0f;
|
||||||
|
/// \brief Framebuffer height
|
||||||
float s_height = 720.0f;
|
float s_height = 720.0f;
|
||||||
|
|
||||||
|
/// \brief Whether to show mouse
|
||||||
float s_showMouse = false;
|
float s_showMouse = false;
|
||||||
|
/// \brief Mouse position
|
||||||
ImVec2 s_mousePos = ImVec2 (0.0f, 0.0f);
|
ImVec2 s_mousePos = ImVec2 (0.0f, 0.0f);
|
||||||
|
|
||||||
|
/// \brief Clipboard
|
||||||
std::string s_clipboard;
|
std::string s_clipboard;
|
||||||
|
|
||||||
|
/// \brief Applet hook cookie
|
||||||
AppletHookCookie s_appletHookCookie;
|
AppletHookCookie s_appletHookCookie;
|
||||||
|
|
||||||
|
/// \brief System font glyph ranges
|
||||||
ImWchar const nxFontRanges[] = {
|
ImWchar const nxFontRanges[] = {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
0x0020, 0x007e, 0x00a0, 0x017f, 0x0192, 0x0192, 0x01c0, 0x01c0,
|
0x0020, 0x007e, 0x00a0, 0x017f, 0x0192, 0x0192, 0x01c0, 0x01c0,
|
||||||
@ -1189,12 +1199,16 @@ ImWchar const nxFontRanges[] = {
|
|||||||
// clang-format on
|
// clang-format on
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// \brief Handle applet hook
|
||||||
|
/// \param hook_ Callback reason
|
||||||
|
/// \param param_ User param
|
||||||
void handleAppletHook (AppletHookType const hook_, void *const param_)
|
void handleAppletHook (AppletHookType const hook_, void *const param_)
|
||||||
{
|
{
|
||||||
(void)param_;
|
(void)param_;
|
||||||
switch (hook_)
|
switch (hook_)
|
||||||
{
|
{
|
||||||
case AppletHookType_OnFocusState:
|
case AppletHookType_OnFocusState:
|
||||||
|
// grab focus state
|
||||||
s_focused = (appletGetFocusState () == AppletFocusState_Focused);
|
s_focused = (appletGetFocusState () == AppletFocusState_Focused);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -1203,15 +1217,18 @@ void handleAppletHook (AppletHookType const hook_, void *const param_)
|
|||||||
{
|
{
|
||||||
default:
|
default:
|
||||||
case AppletOperationMode_Handheld:
|
case AppletOperationMode_Handheld:
|
||||||
|
// use handheld mode resolution (720p)
|
||||||
s_width = 1280.0f;
|
s_width = 1280.0f;
|
||||||
s_height = 720.0f;
|
s_height = 720.0f;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AppletOperationMode_Docked:
|
case AppletOperationMode_Docked:
|
||||||
|
// use docked mode resolution (1080p)
|
||||||
#if 0
|
#if 0
|
||||||
s_width = 1920.0f;
|
s_width = 1920.0f;
|
||||||
s_height = 1080.0f;
|
s_height = 1080.0f;
|
||||||
#else
|
#else
|
||||||
|
/// \todo check if we'd rather use framebuffer scale
|
||||||
s_width = 1280.0f;
|
s_width = 1280.0f;
|
||||||
s_height = 720.0f;
|
s_height = 720.0f;
|
||||||
#endif
|
#endif
|
||||||
@ -1224,30 +1241,43 @@ void handleAppletHook (AppletHookType const hook_, void *const param_)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Get clipboard text callback
|
||||||
|
/// \param userData_ User data
|
||||||
char const *getClipboardText (void *const userData_)
|
char const *getClipboardText (void *const userData_)
|
||||||
{
|
{
|
||||||
(void)userData_;
|
(void)userData_;
|
||||||
return s_clipboard.c_str ();
|
return s_clipboard.c_str ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Set clipboard text callback
|
||||||
|
/// \param userData_ User data
|
||||||
|
/// \param text_ Clipboard text
|
||||||
void setClipboardText (void *const userData_, char const *const text_)
|
void setClipboardText (void *const userData_, char const *const text_)
|
||||||
{
|
{
|
||||||
(void)userData_;
|
(void)userData_;
|
||||||
s_clipboard = text_;
|
s_clipboard = text_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Move mouse cursor
|
||||||
|
/// \param io_ ImGui IO
|
||||||
|
/// \param pos_ New mouse position
|
||||||
|
/// \param force_ Whether to ignore prior mouse position
|
||||||
void moveMouse (ImGuiIO &io_, ImVec2 const &pos_, bool const force_ = false)
|
void moveMouse (ImGuiIO &io_, ImVec2 const &pos_, bool const force_ = false)
|
||||||
{
|
{
|
||||||
auto const now = std::chrono::high_resolution_clock::now ();
|
// get update timestamp
|
||||||
|
auto const now = std::chrono::steady_clock::now ();
|
||||||
|
|
||||||
|
// check if the mouse position has been updated
|
||||||
if (!force_ && pos_.x == s_mousePos.x && pos_.y == s_mousePos.y)
|
if (!force_ && pos_.x == s_mousePos.x && pos_.y == s_mousePos.y)
|
||||||
{
|
{
|
||||||
|
// stop displaying mouse cursor after inactivity timeout
|
||||||
if (now - s_lastMouseUpdate > 1s)
|
if (now - s_lastMouseUpdate > 1s)
|
||||||
s_showMouse = false;
|
s_showMouse = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update mouse position
|
||||||
s_showMouse = true;
|
s_showMouse = true;
|
||||||
s_lastMouseUpdate = now;
|
s_lastMouseUpdate = now;
|
||||||
s_mousePos = pos_;
|
s_mousePos = pos_;
|
||||||
@ -1255,27 +1285,27 @@ void moveMouse (ImGuiIO &io_, ImVec2 const &pos_, bool const force_ = false)
|
|||||||
io_.MousePos = s_mousePos;
|
io_.MousePos = s_mousePos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Update mouse buttons
|
||||||
|
/// \param io_ ImGui IO
|
||||||
void updateMouseButtons (ImGuiIO &io_)
|
void updateMouseButtons (ImGuiIO &io_)
|
||||||
{
|
{
|
||||||
|
// read mouse buttons
|
||||||
auto const buttons = hidMouseButtonsHeld ();
|
auto const buttons = hidMouseButtonsHeld ();
|
||||||
|
|
||||||
for (std::size_t i = 0; i < IM_ARRAYSIZE (io_.MouseDown); ++i)
|
for (std::size_t i = 0; i < IM_ARRAYSIZE (io_.MouseDown); ++i)
|
||||||
{
|
{
|
||||||
// If a mouse press event came, always pass it as "mouse held this frame", so we don't miss
|
io_.MouseDown[i] = buttons & BIT (i);
|
||||||
// click-release events that are shorter than 1 frame.
|
|
||||||
io_.MouseDown[i] = s_mouseJustPressed[i] || (buttons & BIT (i));
|
|
||||||
s_mouseJustPressed[i] = false;
|
|
||||||
|
|
||||||
|
// force mouse cursor to show on click
|
||||||
if (io_.MouseDown[i])
|
if (io_.MouseDown[i])
|
||||||
moveMouse (io_, s_mousePos, true);
|
moveMouse (io_, s_mousePos, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Update mouse position
|
||||||
|
/// \param io_ ImGui IO
|
||||||
void updateMousePos (ImGuiIO &io_)
|
void updateMousePos (ImGuiIO &io_)
|
||||||
{
|
{
|
||||||
if (!s_focused)
|
|
||||||
return;
|
|
||||||
|
|
||||||
MousePosition pos;
|
MousePosition pos;
|
||||||
hidMouseRead (&pos);
|
hidMouseRead (&pos);
|
||||||
|
|
||||||
@ -1286,25 +1316,30 @@ void updateMousePos (ImGuiIO &io_)
|
|||||||
io_, ImVec2 (s_mousePos.x + 2.0f * pos.velocityX, s_mousePos.y + 2.0f * pos.velocityY));
|
io_, ImVec2 (s_mousePos.x + 2.0f * pos.velocityX, s_mousePos.y + 2.0f * pos.velocityY));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Update touch position
|
||||||
|
/// \param io_ ImGui IO
|
||||||
void updateTouch (ImGuiIO &io_)
|
void updateTouch (ImGuiIO &io_)
|
||||||
{
|
{
|
||||||
if (!s_focused)
|
// read touch positions
|
||||||
return;
|
|
||||||
|
|
||||||
auto const touchCount = hidTouchCount ();
|
auto const touchCount = hidTouchCount ();
|
||||||
if (touchCount < 1)
|
if (touchCount < 1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// use first touch position
|
||||||
touchPosition pos;
|
touchPosition pos;
|
||||||
hidTouchRead (&pos, 0);
|
hidTouchRead (&pos, 0);
|
||||||
|
|
||||||
|
// set mouse position to touch point; force hide mouse cursor
|
||||||
moveMouse (io_, ImVec2 (pos.px, pos.py));
|
moveMouse (io_, ImVec2 (pos.px, pos.py));
|
||||||
io_.MouseDown[0] = true;
|
io_.MouseDown[0] = true;
|
||||||
s_showMouse = false;
|
s_showMouse = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Update gamepad inputs
|
||||||
|
/// \param io_ ImGui IO
|
||||||
void updateGamepads (ImGuiIO &io_)
|
void updateGamepads (ImGuiIO &io_)
|
||||||
{
|
{
|
||||||
|
// clear navigation inputs
|
||||||
std::memset (io_.NavInputs, 0, sizeof (io_.NavInputs));
|
std::memset (io_.NavInputs, 0, sizeof (io_.NavInputs));
|
||||||
|
|
||||||
auto const buttonMapping = {
|
auto const buttonMapping = {
|
||||||
@ -1322,6 +1357,7 @@ void updateGamepads (ImGuiIO &io_)
|
|||||||
std::make_pair (KEY_DLEFT, ImGuiNavInput_DpadLeft),
|
std::make_pair (KEY_DLEFT, ImGuiNavInput_DpadLeft),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// read buttons from primary controller
|
||||||
auto const keys = hidKeysHeld (CONTROLLER_P1_AUTO);
|
auto const keys = hidKeysHeld (CONTROLLER_P1_AUTO);
|
||||||
for (auto const &[in, out] : buttonMapping)
|
for (auto const &[in, out] : buttonMapping)
|
||||||
{
|
{
|
||||||
@ -1329,7 +1365,7 @@ void updateGamepads (ImGuiIO &io_)
|
|||||||
io_.NavInputs[out] = 1.0f;
|
io_.NavInputs[out] = 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use ZR/ZL as Mouse0/Mouse1, respectively
|
// use ZR/ZL as left-click/right-click, respectively
|
||||||
if (keys & KEY_ZR)
|
if (keys & KEY_ZR)
|
||||||
{
|
{
|
||||||
io_.MouseDown[0] = true;
|
io_.MouseDown[0] = true;
|
||||||
@ -1341,6 +1377,7 @@ void updateGamepads (ImGuiIO &io_)
|
|||||||
moveMouse (io_, s_mousePos, true);
|
moveMouse (io_, s_mousePos, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update joystick
|
||||||
JoystickPosition js;
|
JoystickPosition js;
|
||||||
auto const analogMapping = {
|
auto const analogMapping = {
|
||||||
std::make_tuple (std::ref (js.dx), ImGuiNavInput_LStickLeft, -0.3f, -0.9f),
|
std::make_tuple (std::ref (js.dx), ImGuiNavInput_LStickLeft, -0.3f, -0.9f),
|
||||||
@ -1349,15 +1386,15 @@ void updateGamepads (ImGuiIO &io_)
|
|||||||
std::make_tuple (std::ref (js.dy), ImGuiNavInput_LStickDown, -0.3f, -0.9f),
|
std::make_tuple (std::ref (js.dy), ImGuiNavInput_LStickDown, -0.3f, -0.9f),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// read left joystick from primary controller
|
||||||
hidJoystickRead (&js, CONTROLLER_P1_AUTO, JOYSTICK_LEFT);
|
hidJoystickRead (&js, CONTROLLER_P1_AUTO, JOYSTICK_LEFT);
|
||||||
for (auto const &[in, out, min, max] : analogMapping)
|
for (auto const &[in, out, min, max] : analogMapping)
|
||||||
{
|
{
|
||||||
auto const value = in / static_cast<float> (JOYSTICK_MAX);
|
auto const value = in / static_cast<float> (JOYSTICK_MAX);
|
||||||
auto const v = std::min (1.0f, (value - min) / (max - min));
|
io_.NavInputs[out] = std::clamp ((value - min) / (max - min), 0.0f, 1.0f);
|
||||||
io_.NavInputs[out] = std::max (io_.NavInputs[out], v);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use right stick as mouse
|
// use right stick as mouse
|
||||||
auto scale = 5.0f;
|
auto scale = 5.0f;
|
||||||
if (keys & KEY_L)
|
if (keys & KEY_L)
|
||||||
scale = 1.0f;
|
scale = 1.0f;
|
||||||
@ -1365,37 +1402,41 @@ void updateGamepads (ImGuiIO &io_)
|
|||||||
scale = 20.0f;
|
scale = 20.0f;
|
||||||
hidJoystickRead (&js, CONTROLLER_P1_AUTO, JOYSTICK_RIGHT);
|
hidJoystickRead (&js, CONTROLLER_P1_AUTO, JOYSTICK_RIGHT);
|
||||||
|
|
||||||
|
// move mouse
|
||||||
moveMouse (io_,
|
moveMouse (io_,
|
||||||
ImVec2 (s_mousePos.x + js.dx / static_cast<float> (JOYSTICK_MAX) * scale,
|
ImVec2 (s_mousePos.x + js.dx / static_cast<float> (JOYSTICK_MAX) * scale,
|
||||||
s_mousePos.y - js.dy / static_cast<float> (JOYSTICK_MAX) * scale));
|
s_mousePos.y - js.dy / static_cast<float> (JOYSTICK_MAX) * scale));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Update keyboard inputs
|
||||||
|
/// \param io_ ImGui IO
|
||||||
void updateKeyboard (ImGuiIO &io_)
|
void updateKeyboard (ImGuiIO &io_)
|
||||||
{
|
{
|
||||||
io_.KeyCtrl =
|
io_.KeyCtrl =
|
||||||
hidKeyboardModifierHeld (static_cast<HidKeyboardModifier> (KBD_MOD_LCTRL | KBD_MOD_RCTRL));
|
hidKeyboardModifierHeld (static_cast<HidKeyboardModifier> (KBD_MOD_LCTRL | KBD_MOD_RCTRL));
|
||||||
|
|
||||||
io_.KeyShift = hidKeyboardModifierHeld (
|
io_.KeyShift = hidKeyboardModifierHeld (
|
||||||
static_cast<HidKeyboardModifier> (KBD_MOD_LSHIFT | KBD_MOD_RSHIFT));
|
static_cast<HidKeyboardModifier> (KBD_MOD_LSHIFT | KBD_MOD_RSHIFT));
|
||||||
|
|
||||||
io_.KeyAlt =
|
io_.KeyAlt =
|
||||||
hidKeyboardModifierHeld (static_cast<HidKeyboardModifier> (KBD_MOD_LALT | KBD_MOD_RALT));
|
hidKeyboardModifierHeld (static_cast<HidKeyboardModifier> (KBD_MOD_LALT | KBD_MOD_RALT));
|
||||||
|
|
||||||
io_.KeySuper =
|
io_.KeySuper =
|
||||||
hidKeyboardModifierHeld (static_cast<HidKeyboardModifier> (KBD_MOD_LMETA | KBD_MOD_RMETA));
|
hidKeyboardModifierHeld (static_cast<HidKeyboardModifier> (KBD_MOD_LMETA | KBD_MOD_RMETA));
|
||||||
|
|
||||||
for (int i = 0; i < 256; ++i)
|
for (int i = 0; i < 256; ++i)
|
||||||
io_.KeysDown[i] = hidKeyboardHeld (static_cast<HidKeyboardScancode> (i));
|
io_.KeysDown[i] = hidKeyboardHeld (static_cast<HidKeyboardScancode> (i));
|
||||||
|
|
||||||
if (!io_.WantTextInput)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// io_.AddInputCharacter (c);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Load font atlas cache from disk
|
||||||
bool loadFontAtlas ()
|
bool loadFontAtlas ()
|
||||||
{
|
{
|
||||||
|
// open font atlas
|
||||||
fs::File fp;
|
fs::File fp;
|
||||||
if (!fp.open (FONT_ATLAS_BIN))
|
if (!fp.open (FONT_ATLAS_BIN))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// initialize font atlas
|
||||||
auto const atlas = ImGui::GetIO ().Fonts;
|
auto const atlas = ImGui::GetIO ().Fonts;
|
||||||
atlas->Clear ();
|
atlas->Clear ();
|
||||||
atlas->TexWidth = fp.read<std::uint16_t> ();
|
atlas->TexWidth = fp.read<std::uint16_t> ();
|
||||||
@ -1405,9 +1446,11 @@ bool loadFontAtlas ()
|
|||||||
atlas->TexPixelsAlpha8 =
|
atlas->TexPixelsAlpha8 =
|
||||||
reinterpret_cast<unsigned char *> (IM_ALLOC (atlas->TexWidth * atlas->TexHeight));
|
reinterpret_cast<unsigned char *> (IM_ALLOC (atlas->TexWidth * atlas->TexHeight));
|
||||||
|
|
||||||
|
// read pixel data
|
||||||
if (!fp.readAll (atlas->TexPixelsAlpha8, atlas->TexWidth * atlas->TexHeight))
|
if (!fp.readAll (atlas->TexPixelsAlpha8, atlas->TexWidth * atlas->TexHeight))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// initialize font config
|
||||||
ImFontConfig config;
|
ImFontConfig config;
|
||||||
config.FontData = nullptr;
|
config.FontData = nullptr;
|
||||||
config.FontDataSize = 0;
|
config.FontDataSize = 0;
|
||||||
@ -1428,23 +1471,28 @@ bool loadFontAtlas ()
|
|||||||
config.EllipsisChar = 0x2026;
|
config.EllipsisChar = 0x2026;
|
||||||
std::memset (config.Name, 0, sizeof (config.Name));
|
std::memset (config.Name, 0, sizeof (config.Name));
|
||||||
|
|
||||||
|
// create font
|
||||||
auto const font = IM_NEW (ImFont);
|
auto const font = IM_NEW (ImFont);
|
||||||
config.DstFont = font;
|
config.DstFont = font;
|
||||||
|
|
||||||
|
// add config and font to atlas
|
||||||
atlas->ConfigData.push_back (config);
|
atlas->ConfigData.push_back (config);
|
||||||
atlas->Fonts.push_back (font);
|
atlas->Fonts.push_back (font);
|
||||||
atlas->CustomRectIds[0] = atlas->AddCustomRectRegular (0x80000000, 108 * 2 + 1, 27);
|
atlas->CustomRectIds[0] = atlas->AddCustomRectRegular (0x80000000, 108 * 2 + 1, 27);
|
||||||
atlas->CustomRects[0].X = 0;
|
atlas->CustomRects[0].X = 0;
|
||||||
atlas->CustomRects[0].Y = 0;
|
atlas->CustomRects[0].Y = 0;
|
||||||
|
|
||||||
|
// read some font metrics
|
||||||
font->FallbackAdvanceX = fp.read<float> ();
|
font->FallbackAdvanceX = fp.read<float> ();
|
||||||
font->FontSize = fp.read<float> ();
|
font->FontSize = fp.read<float> ();
|
||||||
|
|
||||||
|
// decode glyph metadata
|
||||||
auto const glyphCount = fp.read<std::uint16_t> ();
|
auto const glyphCount = fp.read<std::uint16_t> ();
|
||||||
for (unsigned i = 0; i < glyphCount; ++i)
|
for (unsigned i = 0; i < glyphCount; ++i)
|
||||||
{
|
{
|
||||||
ImFontGlyph glyph;
|
ImFontGlyph glyph;
|
||||||
|
|
||||||
|
// read glyph metadata
|
||||||
glyph.Codepoint = fp.read<ImWchar> ();
|
glyph.Codepoint = fp.read<ImWchar> ();
|
||||||
glyph.AdvanceX = fp.read<float> ();
|
glyph.AdvanceX = fp.read<float> ();
|
||||||
glyph.X0 = fp.read<float> ();
|
glyph.X0 = fp.read<float> ();
|
||||||
@ -1456,17 +1504,21 @@ bool loadFontAtlas ()
|
|||||||
glyph.U1 = fp.read<float> ();
|
glyph.U1 = fp.read<float> ();
|
||||||
glyph.V1 = fp.read<float> ();
|
glyph.V1 = fp.read<float> ();
|
||||||
|
|
||||||
|
// add glyph to font
|
||||||
font->Glyphs.push_back (glyph);
|
font->Glyphs.push_back (glyph);
|
||||||
font->MetricsTotalSurface +=
|
font->MetricsTotalSurface +=
|
||||||
static_cast<int> ((glyph.U1 - glyph.U0) * atlas->TexWidth + 1.99f) *
|
static_cast<int> ((glyph.U1 - glyph.U0) * atlas->TexWidth + 1.99f) *
|
||||||
static_cast<int> ((glyph.V1 - glyph.V0) * atlas->TexHeight + 1.99f);
|
static_cast<int> ((glyph.V1 - glyph.V0) * atlas->TexHeight + 1.99f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// build font lookup table
|
||||||
font->BuildLookupTable ();
|
font->BuildLookupTable ();
|
||||||
|
|
||||||
|
// read display offsets
|
||||||
font->DisplayOffset.x = fp.read<float> ();
|
font->DisplayOffset.x = fp.read<float> ();
|
||||||
font->DisplayOffset.y = fp.read<float> ();
|
font->DisplayOffset.y = fp.read<float> ();
|
||||||
|
|
||||||
|
// finalize font atlas
|
||||||
font->ContainerAtlas = atlas;
|
font->ContainerAtlas = atlas;
|
||||||
font->ConfigData = &atlas->ConfigData[0];
|
font->ConfigData = &atlas->ConfigData[0];
|
||||||
font->ConfigDataCount = 1;
|
font->ConfigDataCount = 1;
|
||||||
@ -1479,6 +1531,7 @@ bool loadFontAtlas ()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Store font atlas cache to disk
|
||||||
bool saveFontAtlas ()
|
bool saveFontAtlas ()
|
||||||
{
|
{
|
||||||
auto const atlas = ImGui::GetIO ().Fonts;
|
auto const atlas = ImGui::GetIO ().Fonts;
|
||||||
@ -1488,24 +1541,30 @@ bool saveFontAtlas ()
|
|||||||
int height;
|
int height;
|
||||||
atlas->GetTexDataAsAlpha8 (&pixels, &width, &height);
|
atlas->GetTexDataAsAlpha8 (&pixels, &width, &height);
|
||||||
|
|
||||||
|
// create font atlas cache
|
||||||
fs::File fp;
|
fs::File fp;
|
||||||
if (!fp.open (FONT_ATLAS_BIN, "wb"))
|
if (!fp.open (FONT_ATLAS_BIN, "wb"))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// save atlas dimensions
|
||||||
fp.write<std::uint16_t> (width);
|
fp.write<std::uint16_t> (width);
|
||||||
fp.write<std::uint16_t> (height);
|
fp.write<std::uint16_t> (height);
|
||||||
|
|
||||||
|
// write pixel data
|
||||||
if (!fp.writeAll (pixels, width * height))
|
if (!fp.writeAll (pixels, width * height))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
auto const font = atlas->ConfigData[0].DstFont;
|
auto const font = atlas->ConfigData[0].DstFont;
|
||||||
|
|
||||||
|
// write some font metrics
|
||||||
fp.write (font->FallbackAdvanceX);
|
fp.write (font->FallbackAdvanceX);
|
||||||
fp.write (font->FontSize);
|
fp.write (font->FontSize);
|
||||||
|
|
||||||
|
// encode glyph metadata
|
||||||
fp.write<std::uint16_t> (font->Glyphs.size ());
|
fp.write<std::uint16_t> (font->Glyphs.size ());
|
||||||
for (auto const &glyph : font->Glyphs)
|
for (auto const &glyph : font->Glyphs)
|
||||||
{
|
{
|
||||||
|
// write glyph metadata
|
||||||
fp.write (glyph.Codepoint);
|
fp.write (glyph.Codepoint);
|
||||||
fp.write (glyph.AdvanceX);
|
fp.write (glyph.AdvanceX);
|
||||||
fp.write (glyph.X0);
|
fp.write (glyph.X0);
|
||||||
@ -1518,6 +1577,7 @@ bool saveFontAtlas ()
|
|||||||
fp.write (glyph.V1);
|
fp.write (glyph.V1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// write remaining font metrics
|
||||||
fp.write (font->DisplayOffset.x);
|
fp.write (font->DisplayOffset.x);
|
||||||
fp.write (font->DisplayOffset.y);
|
fp.write (font->DisplayOffset.y);
|
||||||
fp.write (font->Ascent);
|
fp.write (font->Ascent);
|
||||||
@ -1529,44 +1589,71 @@ bool saveFontAtlas ()
|
|||||||
|
|
||||||
bool imgui::nx::init ()
|
bool imgui::nx::init ()
|
||||||
{
|
{
|
||||||
u64 languageCode;
|
ImGuiIO &io = ImGui::GetIO ();
|
||||||
auto rc = setInitialize ();
|
|
||||||
if (R_FAILED (rc))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
rc = setGetSystemLanguage (&languageCode);
|
if (!loadFontAtlas ())
|
||||||
if (R_FAILED (rc))
|
|
||||||
{
|
{
|
||||||
|
// get system language
|
||||||
|
u64 languageCode;
|
||||||
|
auto rc = setInitialize ();
|
||||||
|
if (R_FAILED (rc))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
rc = setGetSystemLanguage (&languageCode);
|
||||||
|
if (R_FAILED (rc))
|
||||||
|
{
|
||||||
|
setExit ();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
setExit ();
|
setExit ();
|
||||||
return false;
|
|
||||||
|
// get fonts for system language
|
||||||
|
std::vector<PlFontData> fonts (PlSharedFontType_Total);
|
||||||
|
s32 numFonts = 0;
|
||||||
|
rc = plGetSharedFont (languageCode, fonts.data (), fonts.size (), &numFonts);
|
||||||
|
if (R_FAILED (rc))
|
||||||
|
return false;
|
||||||
|
fonts.resize (numFonts);
|
||||||
|
|
||||||
|
// add fonts
|
||||||
|
ImFontConfig config;
|
||||||
|
config.MergeMode = false;
|
||||||
|
config.FontDataOwnedByAtlas = false;
|
||||||
|
for (auto const &font : fonts)
|
||||||
|
{
|
||||||
|
io.Fonts->AddFontFromMemoryTTF (font.address, font.size, 14.0f, &config, nxFontRanges);
|
||||||
|
config.MergeMode = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// build font atlas
|
||||||
|
io.Fonts->Flags |= ImFontAtlasFlags_NoPowerOfTwoHeight;
|
||||||
|
io.Fonts->Build ();
|
||||||
|
|
||||||
|
// save font atlas
|
||||||
|
saveFontAtlas ();
|
||||||
}
|
}
|
||||||
setExit ();
|
else
|
||||||
|
std::printf ("Loaded font atlas from disk\n");
|
||||||
std::vector<PlFontData> fonts (PlSharedFontType_Total);
|
|
||||||
s32 numFonts = 0;
|
|
||||||
rc = plGetSharedFont (languageCode, fonts.data (), fonts.size (), &numFonts);
|
|
||||||
if (R_FAILED (rc))
|
|
||||||
return false;
|
|
||||||
fonts.resize (numFonts);
|
|
||||||
|
|
||||||
|
// initialize applet hooks
|
||||||
appletSetFocusHandlingMode (AppletFocusHandlingMode_NoSuspend);
|
appletSetFocusHandlingMode (AppletFocusHandlingMode_NoSuspend);
|
||||||
appletHook (&s_appletHookCookie, handleAppletHook, nullptr);
|
appletHook (&s_appletHookCookie, handleAppletHook, nullptr);
|
||||||
handleAppletHook (AppletHookType_OnFocusState, nullptr);
|
handleAppletHook (AppletHookType_OnFocusState, nullptr);
|
||||||
handleAppletHook (AppletHookType_OnOperationMode, nullptr);
|
handleAppletHook (AppletHookType_OnOperationMode, nullptr);
|
||||||
|
|
||||||
ImGuiIO &io = ImGui::GetIO ();
|
// disable imgui.ini file
|
||||||
|
|
||||||
io.IniFilename = nullptr;
|
io.IniFilename = nullptr;
|
||||||
|
|
||||||
|
// setup config flags
|
||||||
io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen;
|
io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen;
|
||||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
|
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
|
||||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||||
|
|
||||||
|
// setup platform backend
|
||||||
io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
|
io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
|
||||||
|
io.BackendPlatformName = "Switch";
|
||||||
|
|
||||||
io.BackendPlatformName = "switch";
|
// keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array.
|
||||||
|
|
||||||
// Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array.
|
|
||||||
io.KeyMap[ImGuiKey_Tab] = KBD_TAB;
|
io.KeyMap[ImGuiKey_Tab] = KBD_TAB;
|
||||||
io.KeyMap[ImGuiKey_LeftArrow] = KBD_LEFT;
|
io.KeyMap[ImGuiKey_LeftArrow] = KBD_LEFT;
|
||||||
io.KeyMap[ImGuiKey_RightArrow] = KBD_RIGHT;
|
io.KeyMap[ImGuiKey_RightArrow] = KBD_RIGHT;
|
||||||
@ -1590,66 +1677,58 @@ bool imgui::nx::init ()
|
|||||||
io.KeyMap[ImGuiKey_Y] = KBD_Y;
|
io.KeyMap[ImGuiKey_Y] = KBD_Y;
|
||||||
io.KeyMap[ImGuiKey_Z] = KBD_Z;
|
io.KeyMap[ImGuiKey_Z] = KBD_Z;
|
||||||
|
|
||||||
|
// initially disable mouse cursor
|
||||||
io.MouseDrawCursor = false;
|
io.MouseDrawCursor = false;
|
||||||
|
|
||||||
|
// clipboard callbacks
|
||||||
io.SetClipboardTextFn = setClipboardText;
|
io.SetClipboardTextFn = setClipboardText;
|
||||||
io.GetClipboardTextFn = getClipboardText;
|
io.GetClipboardTextFn = getClipboardText;
|
||||||
io.ClipboardUserData = nullptr;
|
io.ClipboardUserData = nullptr;
|
||||||
|
|
||||||
if (!loadFontAtlas ())
|
|
||||||
{
|
|
||||||
ImFontConfig config;
|
|
||||||
config.MergeMode = false;
|
|
||||||
config.FontDataOwnedByAtlas = false;
|
|
||||||
|
|
||||||
for (auto const &font : fonts)
|
|
||||||
{
|
|
||||||
io.Fonts->AddFontFromMemoryTTF (font.address, font.size, 14.0f, &config, nxFontRanges);
|
|
||||||
config.MergeMode = true;
|
|
||||||
}
|
|
||||||
io.Fonts->Flags |= ImFontAtlasFlags_NoPowerOfTwoHeight;
|
|
||||||
io.Fonts->Build ();
|
|
||||||
|
|
||||||
saveFontAtlas ();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
std::printf ("Loaded font atlas from disk\n");
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void imgui::nx::newFrame ()
|
void imgui::nx::newFrame ()
|
||||||
{
|
{
|
||||||
ImGuiIO &io = ImGui::GetIO ();
|
ImGuiIO &io = ImGui::GetIO ();
|
||||||
|
|
||||||
|
// check that font was built
|
||||||
IM_ASSERT (io.Fonts->IsBuilt () &&
|
IM_ASSERT (io.Fonts->IsBuilt () &&
|
||||||
"Font atlas not built! It is generally built by the renderer back-end. Missing call "
|
"Font atlas not built! It is generally built by the renderer back-end. Missing call "
|
||||||
"to renderer _NewFrame() function?");
|
"to renderer _NewFrame() function?");
|
||||||
|
|
||||||
|
// setup display metrics
|
||||||
io.DisplaySize = ImVec2 (s_width, s_height);
|
io.DisplaySize = ImVec2 (s_width, s_height);
|
||||||
io.DisplayFramebufferScale = ImVec2 (1.0f, 1.0f);
|
io.DisplayFramebufferScale = ImVec2 (1.0f, 1.0f);
|
||||||
|
|
||||||
// Setup time step
|
// time step
|
||||||
static auto const start = std::chrono::high_resolution_clock::now ();
|
static auto const start = platform::steady_clock::now ();
|
||||||
static auto prev = start;
|
static auto prev = start;
|
||||||
auto const now = std::chrono::high_resolution_clock::now ();
|
auto const now = platform::steady_clock::now ();
|
||||||
|
|
||||||
io.DeltaTime = std::chrono::duration<float> (now - prev).count ();
|
io.DeltaTime = std::chrono::duration<float> (now - prev).count ();
|
||||||
prev = now;
|
prev = now;
|
||||||
|
|
||||||
updateMouseButtons (io);
|
if (s_focused)
|
||||||
updateMousePos (io);
|
{
|
||||||
updateTouch (io);
|
// update inputs
|
||||||
updateGamepads (io);
|
updateMouseButtons (io);
|
||||||
updateKeyboard (io);
|
updateMousePos (io);
|
||||||
|
updateTouch (io);
|
||||||
|
updateGamepads (io);
|
||||||
|
updateKeyboard (io);
|
||||||
|
}
|
||||||
|
|
||||||
|
// whether to draw mouse cursor
|
||||||
io.MouseDrawCursor = s_showMouse;
|
io.MouseDrawCursor = s_showMouse;
|
||||||
|
|
||||||
// Clamp mouse to screen
|
// clamp mouse to screen
|
||||||
s_mousePos.x = std::clamp (s_mousePos.x, 0.0f, s_width);
|
s_mousePos.x = std::clamp (s_mousePos.x, 0.0f, s_width);
|
||||||
s_mousePos.y = std::clamp (s_mousePos.y, 0.0f, s_height);
|
s_mousePos.y = std::clamp (s_mousePos.y, 0.0f, s_height);
|
||||||
}
|
}
|
||||||
|
|
||||||
void imgui::nx::exit ()
|
void imgui::nx::exit ()
|
||||||
{
|
{
|
||||||
|
// deinitialize applet hooks
|
||||||
appletUnhook (&s_appletHookCookie);
|
appletUnhook (&s_appletHookCookie);
|
||||||
}
|
}
|
@ -23,9 +23,11 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
|
/// \brief nxlink socket fd
|
||||||
static int s_fd = -1;
|
static int s_fd = -1;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/// \brief Socket initialization configuration
|
||||||
static SocketInitConfig const s_socketInitConfig = {
|
static SocketInitConfig const s_socketInitConfig = {
|
||||||
.bsdsockets_version = 1,
|
.bsdsockets_version = 1,
|
||||||
|
|
||||||
@ -43,10 +45,13 @@ static SocketInitConfig const s_socketInitConfig = {
|
|||||||
.bsd_service_type = BsdServiceType_User,
|
.bsd_service_type = BsdServiceType_User,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// \brief Number of FS sessions
|
||||||
u32 __nx_fs_num_sessions = 3;
|
u32 __nx_fs_num_sessions = 3;
|
||||||
|
|
||||||
|
/// \brief Called before main ()
|
||||||
void userAppInit ()
|
void userAppInit ()
|
||||||
{
|
{
|
||||||
|
// disable immediate app close
|
||||||
appletLockExit ();
|
appletLockExit ();
|
||||||
|
|
||||||
romfsInit ();
|
romfsInit ();
|
@ -32,14 +32,8 @@
|
|||||||
|
|
||||||
bool platform::init ()
|
bool platform::init ()
|
||||||
{
|
{
|
||||||
IMGUI_CHECKVERSION ();
|
|
||||||
ImGui::CreateContext ();
|
|
||||||
|
|
||||||
if (!imgui::nx::init ())
|
if (!imgui::nx::init ())
|
||||||
{
|
|
||||||
ImGui::DestroyContext ();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
imgui::deko3d::init ();
|
imgui::deko3d::init ();
|
||||||
|
|
||||||
@ -77,20 +71,22 @@ void platform::exit ()
|
|||||||
{
|
{
|
||||||
imgui::nx::exit ();
|
imgui::nx::exit ();
|
||||||
imgui::deko3d::exit ();
|
imgui::deko3d::exit ();
|
||||||
|
|
||||||
ImGui::DestroyContext ();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
/// \brief Platform thread pimpl
|
||||||
class platform::Thread::privateData_t
|
class platform::Thread::privateData_t
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
privateData_t () = default;
|
privateData_t () = default;
|
||||||
|
|
||||||
|
/// \brief Parameterized constructor
|
||||||
|
/// \param func_ Thread entry point
|
||||||
privateData_t (std::function<void ()> func_) : thread (func_)
|
privateData_t (std::function<void ()> func_) : thread (func_)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Underlying thread
|
||||||
std::thread thread;
|
std::thread thread;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -128,12 +124,16 @@ void platform::Thread::sleep (std::chrono::milliseconds const timeout_)
|
|||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
#define USE_STD_MUTEX 1
|
#define USE_STD_MUTEX 1
|
||||||
|
|
||||||
|
/// \brief Platform mutex pimpl
|
||||||
class platform::Mutex::privateData_t
|
class platform::Mutex::privateData_t
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
#if USE_STD_MUTEX
|
#if USE_STD_MUTEX
|
||||||
|
/// \brief Underlying mutex
|
||||||
std::mutex mutex;
|
std::mutex mutex;
|
||||||
#else
|
#else
|
||||||
|
/// \brief Underlying mutex
|
||||||
::Mutex mutex;
|
::Mutex mutex;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |