diff --git a/graphics/opengl/gpu_console/Makefile b/graphics/opengl/gpu_console/Makefile new file mode 100644 index 0000000..d21b996 --- /dev/null +++ b/graphics/opengl/gpu_console/Makefile @@ -0,0 +1,193 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITPRO)/libnx/switch_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +# EXEFS_SRC is the optional input directory containing data copied into exefs, if anything this normally should only contain "main.npdm". +# ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional) +# +# NO_ICON: if set to anything, do not use icon. +# NO_NACP: if set to anything, no .nacp file is generated. +# APP_TITLE is the name of the app stored in the .nacp file (Optional) +# APP_AUTHOR is the author of the app stored in the .nacp file (Optional) +# APP_VERSION is the version of the app stored in the .nacp file (Optional) +# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional) +# ICON is the filename of the icon (.jpg), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - .jpg +# - icon.jpg +# - /default_icon.jpg +#--------------------------------------------------------------------------------- +TARGET := $(notdir $(CURDIR)) +BUILD := build +SOURCES := source +DATA := data +INCLUDES := include +EXEFS_SRC := exefs_src +#ROMFS := romfs + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE + +CFLAGS := -g -Wall -O2 -ffunction-sections \ + $(ARCH) $(DEFINES) + +CFLAGS += $(INCLUDE) -D__SWITCH__ + +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -lglad -lEGL -lglapi -ldrm_nouveau -lnx + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) + + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) +export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) +export OFILES := $(OFILES_BIN) $(OFILES_SRC) +export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC) + +ifeq ($(strip $(ICON)),) + icons := $(wildcard *.jpg) + ifneq (,$(findstring $(TARGET).jpg,$(icons))) + export APP_ICON := $(TOPDIR)/$(TARGET).jpg + else + ifneq (,$(findstring icon.jpg,$(icons))) + export APP_ICON := $(TOPDIR)/icon.jpg + endif + endif +else + export APP_ICON := $(TOPDIR)/$(ICON) +endif + +ifeq ($(strip $(NO_ICON)),) + export NROFLAGS += --icon=$(APP_ICON) +endif + +ifeq ($(strip $(NO_NACP)),) + export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp +endif + +ifneq ($(APP_TITLEID),) + export NACPFLAGS += --titleid=$(APP_TITLEID) +endif + +ifneq ($(ROMFS),) + export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS) +endif + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- +all: $(BUILD) + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(TARGET).pfs0 $(TARGET).nso $(TARGET).nro $(TARGET).nacp $(TARGET).elf + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(OUTPUT).pfs0 $(OUTPUT).nro + +$(OUTPUT).pfs0 : $(OUTPUT).nso + +$(OUTPUT).nso : $(OUTPUT).elf + +ifeq ($(strip $(NO_NACP)),) +$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp +else +$(OUTPUT).nro : $(OUTPUT).elf +endif + +$(OUTPUT).elf : $(OFILES) + +$(OFILES_SRC) : $(HFILES_BIN) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o %_bin.h : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/graphics/opengl/gpu_console/source/gpu_console.cpp b/graphics/opengl/gpu_console/source/gpu_console.cpp new file mode 100644 index 0000000..ab2ec98 --- /dev/null +++ b/graphics/opengl/gpu_console/source/gpu_console.cpp @@ -0,0 +1,465 @@ +// Sample GPU/OpenGL powered console renderer implementation. +// Please note that this implementation is incomplete, and only normal/bold colors are implemented. +// Reverse colors, faint colors, underline or strikethrough are not implemented. +// Nevertheless, it should suffice for most purposes. + +#include +#include +#include +#include + +#include // EGL library +#include // EGL extensions +#include // glad library (OpenGL loader) + +#define TRACE(...) ((void)0) + +static const char* const vertexShaderSource = R"text( +#version 330 core +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +out gl_PerVertex +{ + vec4 gl_Position; +}; + +layout (location = 0) in int inAttr; + +layout (location = 0) out vec4 outColor; +layout (location = 1) out vec3 outUV; + +uniform ivec2 dimensions; +uniform vec4 palettes[16] = vec4[]( + vec4(0.0, 0.0, 0.0, 1.0), + vec4(0.5, 0.0, 0.0, 1.0), + vec4(0.0, 0.5, 0.0, 1.0), + vec4(0.5, 0.5, 0.0, 1.0), + vec4(0.0, 0.0, 0.5, 1.0), + vec4(0.5, 0.0, 0.5, 1.0), + vec4(0.0, 0.5, 0.5, 1.0), + vec4(0.75, 0.75, 0.75, 1.0), + vec4(0.5, 0.5, 0.5, 1.0), + vec4(1.0, 0.0, 0.0, 1.0), + vec4(0.0, 1.0, 0.0, 1.0), + vec4(1.0, 1.0, 0.0, 1.0), + vec4(0.0, 0.0, 1.0, 1.0), + vec4(1.0, 0.0, 1.0, 1.0), + vec4(0.0, 1.0, 1.0, 1.0), + vec4(1.0, 1.0, 1.0, 1.0) +); + +const vec2 builtin_vertices[] = vec2[]( + vec2(0.0, 0.0), + vec2(0.0, -1.0), + vec2(1.0, -1.0), + vec2(0.0, 0.0), + vec2(1.0, -1.0), + vec2(1.0, 0.0) +); + +void main() +{ + // Extract data from the attribute + float tileId = float(inAttr & 0x3FF); + bool hFlip = ((inAttr >> 10) & 1) != 0; + bool vFlip = ((inAttr >> 11) & 1) != 0; + int palId = (inAttr >> 12) & 0xF; + + vec2 vtxData = builtin_vertices[gl_VertexID]; + + // Position + float tileRow = floor(float(gl_InstanceID) / dimensions.x); + float tileCol = float(gl_InstanceID) - tileRow*dimensions.x; + vec2 basePos; + basePos.x = 2.0 * tileCol / dimensions.x - 1.0; + basePos.y = 2.0 * (1.0 - tileRow / dimensions.y) - 1.0; + + vec2 offsetPos = vec2(2.0) / vec2(dimensions.xy); + gl_Position.xy = basePos + offsetPos * vtxData; + gl_Position.zw = vec2(0.0, 1.0); + + // Color + outColor = palettes[palId]; + + // UVs + if (hFlip) + vtxData.x = 1.0 - vtxData.x; + if (vFlip) + vtxData.y = -1.0 - vtxData.y; + outUV.xy = vec2(0.0,1.0) + vtxData; + outUV.z = tileId; +} +)text"; + +static const char* const fragmentShaderSource = R"text( +#version 330 core +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +layout (location = 0) in vec4 inColor; +layout (location = 1) in vec3 inUV; + +layout (location = 0) out vec4 outColor; + +uniform sampler2DArray tileset; + +void main() +{ + float alpha = inColor.a * texture(tileset, inUV).r; + if (alpha < 0.1) discard; + outColor.rgb = inColor.rgb; + outColor.a = alpha; +} +)text"; + +namespace +{ + +struct GpuConsole : public ConsoleRenderer +{ + constexpr GpuConsole() : + ConsoleRenderer{ _init, _deinit, _drawChar, _scrollWindow, _flushAndSwap }, + s_display{}, s_context{}, s_surface{}, + s_tilemapVsh{}, s_tilemapFsh{}, s_tilemapPipeline{}, + s_tilemapVao{}, s_tilemapVbo{}, s_tilemap{}, + s_tilesetTex{} + { } + + bool init(PrintConsole* con); + void deinit(PrintConsole* con); + void drawChar(PrintConsole* con, int x, int y, int c); + void scrollWindow(PrintConsole* con); + void flushAndSwap(PrintConsole* con); + +private: + static GpuConsole* _get(PrintConsole* con) + { + return static_cast(con->renderer); + } + + static bool _init(PrintConsole* con) + { + return _get(con)->init(con); + } + + static void _deinit(PrintConsole* con) + { + _get(con)->deinit(con); + } + + static void _drawChar(PrintConsole* con, int x, int y, int c) + { + _get(con)->drawChar(con, x, y, c); + } + + static void _scrollWindow(PrintConsole* con) + { + _get(con)->scrollWindow(con); + } + + static void _flushAndSwap(PrintConsole* con) + { + _get(con)->flushAndSwap(con); + } + + EGLDisplay s_display; + EGLContext s_context; + EGLSurface s_surface; + + bool initEgl(); + void deinitEgl(); + + GLuint s_tilemapVsh, s_tilemapFsh; + GLuint s_tilemapPipeline; + GLuint s_tilemapVao, s_tilemapVbo; + uint16_t* s_tilemap; + + GLuint s_tilesetTex; +}; + +constexpr uint16_t MakeTilemapEntry(unsigned tileId, bool hFlip, bool vFlip, unsigned palId) +{ + uint16_t ent = 0; + ent |= (tileId & 0x3FF); + if (hFlip) + ent |= 1u << 10; + if (vFlip) + ent |= 1u << 11; + ent |= (palId & 0xF) << 12; + return ent; +} + +GLuint loadShaderProgram(GLenum type, const char* source) +{ + GLint success; + GLchar msg[512]; + + GLuint handle = glCreateShaderProgramv(type, 1, &source); + glGetProgramiv(handle, GL_LINK_STATUS, &success); + if (success == GL_FALSE) + { + glGetProgramInfoLog(handle, sizeof(msg), nullptr, msg); + TRACE("Shader error: %s", msg); + glDeleteProgram(handle); + handle = 0; + } + + return handle; +} + +} + +bool GpuConsole::init(PrintConsole* con) +{ + // Initialize EGL and load GL routines + if (!initEgl()) + return false; + gladLoadGL(); + + // Load our shaders + s_tilemapVsh = loadShaderProgram(GL_VERTEX_SHADER, vertexShaderSource); + s_tilemapFsh = loadShaderProgram(GL_FRAGMENT_SHADER, fragmentShaderSource); + + // Configure tilemap dimensions + glProgramUniform2i(s_tilemapVsh, glGetUniformLocation(s_tilemapVsh, "dimensions"), + con->consoleWidth, con->consoleHeight + ); + + // Create a program pipeline and attach the programs to their respective stages + glGenProgramPipelines(1, &s_tilemapPipeline); + glUseProgramStages(s_tilemapPipeline, GL_VERTEX_SHADER_BIT, s_tilemapVsh); + glUseProgramStages(s_tilemapPipeline, GL_FRAGMENT_SHADER_BIT, s_tilemapFsh); + + // Create a VAO and a VBO for the tilemap + glGenVertexArrays(1, &s_tilemapVao); + glBindVertexArray(s_tilemapVao); + + // Allocate the tilemap data + glGenBuffers(1, &s_tilemapVbo); + glBindBuffer(GL_ARRAY_BUFFER, s_tilemapVbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(uint16_t)*con->consoleWidth*con->consoleHeight, nullptr, GL_DYNAMIC_DRAW); + + // Configure the only vertex attribute (which is per-instance) + glVertexAttribIPointer(0, 1, GL_UNSIGNED_SHORT, sizeof(uint16_t), (void*)0); + glVertexAttribDivisor(0, 1); + glEnableVertexAttribArray(0); + + // We're done with the VBO/VAO, unbind them + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + + // Allocate the tilemap and clear it + s_tilemap = new uint16_t[con->consoleWidth*con->consoleHeight]; + memset(s_tilemap, 0, sizeof(uint16_t)*con->consoleWidth*con->consoleHeight); + + // Unpack 1bpp tileset into a texture image OpenGL can load + uint8_t* tileset = new uint8_t[con->font.numChars*con->font.tileWidth*con->font.tileHeight]; + unsigned bytesPerRow = (con->font.tileWidth+7)/8; + for (unsigned i = 0; i < con->font.numChars; i ++) + { + const uint8_t* tile = (const uint8_t*)con->font.gfx + con->font.tileHeight*bytesPerRow*i; + uint8_t* data = &tileset[con->font.tileWidth*con->font.tileHeight*i]; + for (unsigned j = 0; j < con->font.tileHeight; j ++) + { + //const uint8_t* row = &tile[bytesPerRow*(con->font.tileHeight-1-j)]; + const uint8_t* row = &tile[bytesPerRow*(con->font.tileHeight-j)]; + uint8_t c = 0; + for (unsigned k = 0; k < con->font.tileWidth; k ++) + { + if (!(k & 7)) + c = *--row; + *data++ = (c & 0x80) ? 0xFF : 0x00; + c <<= 1; + } + } + } + + // Create tileset texture from the unpacked tileset image + glGenTextures(1, &s_tilesetTex); + glActiveTexture(GL_TEXTURE0); // activate the texture unit first before binding texture + glBindTexture(GL_TEXTURE_2D_ARRAY, s_tilesetTex); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // can also use GL_LINEAR here + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_R8, con->font.tileWidth, con->font.tileHeight, con->font.numChars, 0, GL_RED, GL_UNSIGNED_BYTE, tileset); + delete[] tileset; + + // Bind the texture unit to the fragment shader + glProgramUniform1i(s_tilemapFsh, glGetUniformLocation(s_tilemapFsh, "tileset"), 0); // texunit 0 + + // Other miscellaneous init + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + //glEnable(GL_BLEND); + //glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // Configure viewport + glViewport(0, 0, 1280, 720); + + return true; +} + +void GpuConsole::deinit(PrintConsole* con) +{ + glDeleteTextures(1, &s_tilesetTex); + glDeleteBuffers(1, &s_tilemapVbo); + glDeleteVertexArrays(1, &s_tilemapVao); + glDeleteProgramPipelines(1, &s_tilemapPipeline); + glDeleteProgram(s_tilemapFsh); + glDeleteProgram(s_tilemapVsh); + delete[] s_tilemap; + deinitEgl(); +} + +void GpuConsole::drawChar(PrintConsole* con, int x, int y, int c) +{ + int writingColor = con->fg; + int screenColor = con->bg; + + if (con->flags & CONSOLE_COLOR_BOLD) { + writingColor += 8; + } else if (con->flags & CONSOLE_COLOR_FAINT) { + // Not supported yet + //writingColor += 16; + } + + if (con->flags & CONSOLE_COLOR_REVERSE) { + int tmp = writingColor; + writingColor = screenColor; + screenColor = tmp; + } + + s_tilemap[y*con->consoleWidth+x] = MakeTilemapEntry(c, false, false, writingColor); +} + +void GpuConsole::scrollWindow(PrintConsole* con) +{ + for (int y = 0; y < con->windowHeight-1; y ++) + memcpy( + &s_tilemap[(con->windowY+y+0)*con->consoleWidth + con->windowX], + &s_tilemap[(con->windowY+y+1)*con->consoleWidth + con->windowX], + sizeof(uint16_t)*con->windowWidth); +} + +void GpuConsole::flushAndSwap(PrintConsole* con) +{ + // Clear the framebuffer + glClearColor(0x10/255.0f, 0x10/255.0f, 0x10/255.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + // Update tilemap + glBindBuffer(GL_ARRAY_BUFFER, s_tilemapVbo); + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(uint16_t)*con->consoleWidth*con->consoleHeight, s_tilemap); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + // Draw the tilemap + glBindProgramPipeline(s_tilemapPipeline); + glBindVertexArray(s_tilemapVao); + glDrawArraysInstanced(GL_TRIANGLES, 0, 6, con->consoleWidth*con->consoleHeight); + glBindVertexArray(0); + + // Swap buffers + eglSwapBuffers(s_display, s_surface); +} + +bool GpuConsole::initEgl() +{ + // Connect to the EGL default display + s_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (!s_display) + { + TRACE("Could not connect to display! error: %d", eglGetError()); + goto _fail0; + } + + // Initialize the EGL display connection + eglInitialize(s_display, nullptr, nullptr); + + // Select OpenGL (Core) as the desired graphics API + if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE) + { + TRACE("Could not set API! error: %d", eglGetError()); + goto _fail1; + } + + // Get an appropriate EGL framebuffer configuration + EGLConfig config; + EGLint numConfigs; + static const EGLint framebufferAttributeList[] = + { + EGL_RED_SIZE, 1, + EGL_GREEN_SIZE, 1, + EGL_BLUE_SIZE, 1, + EGL_NONE + }; + eglChooseConfig(s_display, framebufferAttributeList, &config, 1, &numConfigs); + if (numConfigs == 0) + { + TRACE("No config found! error: %d", eglGetError()); + goto _fail1; + } + + // Create an EGL window surface + s_surface = eglCreateWindowSurface(s_display, config, (char*)"", nullptr); + if (!s_surface) + { + TRACE("Surface creation failed! error: %d", eglGetError()); + goto _fail1; + } + + // Create an EGL rendering context + static const EGLint contextAttributeList[] = + { + EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR, + EGL_CONTEXT_MAJOR_VERSION_KHR, 4, + EGL_CONTEXT_MINOR_VERSION_KHR, 3, + EGL_NONE + }; + s_context = eglCreateContext(s_display, config, EGL_NO_CONTEXT, contextAttributeList); + if (!s_context) + { + TRACE("Context creation failed! error: %d", eglGetError()); + goto _fail2; + } + + // Connect the context to the surface + eglMakeCurrent(s_display, s_surface, s_surface, s_context); + return true; + +_fail2: + eglDestroySurface(s_display, s_surface); + s_surface = nullptr; +_fail1: + eglTerminate(s_display); + s_display = nullptr; +_fail0: + return false; +} + +void GpuConsole::deinitEgl() +{ + if (s_display) + { + eglMakeCurrent(s_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if (s_context) + { + eglDestroyContext(s_display, s_context); + s_context = nullptr; + } + if (s_surface) + { + eglDestroySurface(s_display, s_surface); + s_surface = nullptr; + } + eglTerminate(s_display); + s_display = nullptr; + } +} + +extern "C" ConsoleRenderer* getDefaultConsoleRenderer(void) +{ + static GpuConsole s_gpuConsole; + return &s_gpuConsole; +} diff --git a/graphics/opengl/gpu_console/source/main.c b/graphics/opengl/gpu_console/source/main.c new file mode 100644 index 0000000..83c1525 --- /dev/null +++ b/graphics/opengl/gpu_console/source/main.c @@ -0,0 +1,64 @@ +#include +#include + +#include + +int main(int argc, char **argv) +{ + consoleInit(NULL); + + // clear screen and home cursor + printf( CONSOLE_ESC(2J) ); + + // Set print co-ordinates + // /x1b[row;columnH + printf(CONSOLE_ESC(10;10H) "VT52 codes demo"); + + // move cursor up + // /x1b[linesA + printf(CONSOLE_ESC(10A)"Line 0"); + + // move cursor left + // /x1b[columnsD + printf(CONSOLE_ESC(28D)"Column 0"); + + // move cursor down + // /x1b[linesB + printf(CONSOLE_ESC(19B)"Line 19"); + + // move cursor right + // /x1b[columnsC + printf(CONSOLE_ESC(5C)"Column 20"); + + printf("\n"); + + // Color codes and attributes + for(int i=0; i<8; i++) + { + printf( CONSOLE_ESC(%1$d;1m) /* Set color */ + "Default " + CONSOLE_ESC(2m) "Light " + "\n" + CONSOLE_ESC(0m) /* revert attributes*/ + , i + 30); + } + + // Main loop + while(appletMainLoop()) + { + //Scan all the inputs. This should be done once for each frame + hidScanInput(); + + // Your code goes here + + //hidKeysDown returns information about which buttons have been just pressed (and they weren't in the previous frame) + u64 kDown = hidKeysDown(CONTROLLER_P1_AUTO); + + if (kDown & KEY_PLUS) break; // break in order to return to hbmenu + + consoleUpdate(NULL); + } + + consoleExit(NULL); + return 0; +}