works but slow

This commit is contained in:
JimmyZ 2017-09-24 10:16:02 +08:00
commit 5cf3c1ada5
9 changed files with 3651 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.vs
build
*.elf
*.nds
*.sln
*.vcxproj*

138
Makefile Normal file
View File

@ -0,0 +1,138 @@
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITARM)),)
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>devkitARM")
endif
include $(DEVKITARM)/ds_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
# INCLUDES is a list of directories containing extra header files
#---------------------------------------------------------------------------------
TARGET := $(shell basename $(CURDIR))
BUILD := build
SOURCES := source
DATA :=
INCLUDES := include
GRAPHICS := data
#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
ARCH := -mthumb -mthumb-interwork
CFLAGS := -g -Wall -O2\
-march=armv5te -mtune=arm946e-s -fomit-frame-pointer\
-ffast-math \
$(ARCH)
CFLAGS += $(INCLUDE) -DARM9
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=ds_arm9.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
#---------------------------------------------------------------------------------
# any extra libraries we wish to link with the project
#---------------------------------------------------------------------------------
LIBS := -lnds9
#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing
# include and lib
#---------------------------------------------------------------------------------
LIBDIRS := $(LIBNDS)
#---------------------------------------------------------------------------------
# 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 VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir)) \
$(foreach dir,$(GRAPHICS),$(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)/*.*)))
PNGFILES := $(foreach dir,$(GRAPHICS),$(notdir $(wildcard $(dir)/*.png)))
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#---------------------------------------------------------------------------------
export LD := $(CC)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
export LD := $(CXX)
#---------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------
export OFILES := $(addsuffix .o,$(BINFILES)) \
$(PNGFILES:.png=.o) \
$(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
.PHONY: $(BUILD) clean
#---------------------------------------------------------------------------------
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
#---------------------------------------------------------------------------------
clean:
@echo clean ...
@rm -fr $(BUILD) $(TARGET).elf $(TARGET).nds $(TARGET).ds.gba
#---------------------------------------------------------------------------------
else
DEPENDS := $(OFILES:.o=.d)
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
$(OUTPUT).nds : $(OUTPUT).elf
$(OUTPUT).elf : $(OFILES)
#---------------------------------------------------------------------------------
%.bin.o : %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
#---------------------------------------------------------------------------------
%.s %.h : %.png %.grit
#---------------------------------------------------------------------------------
grit $< -fts -o$*
-include $(DEPENDS)
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------

7
source/font.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#define FONT_WIDTH 6
#define FONT_HEIGHT 10
extern const unsigned char font[];

3079
source/font_6x10.c Normal file

File diff suppressed because it is too large Load Diff

114
source/main.c Normal file
View File

@ -0,0 +1,114 @@
#include <nds.h>
#include "term256.h"
#include "term256ext.h"
// show ANSI color palette, 8 rows
void show_ansi256_color_table(u16 *fb, unsigned width, unsigned height) {
// 4 pixels at a time, there might be some overlaps, but we don't care
width >>= 1;
unsigned row_height = height / 8;
// first row, 16 standard and intense colors
unsigned block_width = width / 16;
for (unsigned i = 0; i < 16; ++i) {
u16 c2 = i | (i << 8);
unsigned start_x = i * block_width;
for (unsigned y = 0; y < row_height; ++y) {
for (unsigned x = start_x; x < start_x + block_width; ++x){
fb[y * width + x] = c2;
}
}
}
// six rows, 6x6x6 RGB colors
block_width = width / 36;
for (unsigned i = 0; i < 6; ++i) {
for (unsigned j = 0; j < 36; ++j) {
unsigned c = 16 + i * 36 + j;
u16 c2 = c | (c << 8);
unsigned start_x = j * block_width;
unsigned start_y = (i + 1) * row_height;
for (unsigned y = start_y; y < start_y + row_height; ++y) {
for (unsigned x = start_x; x < start_x + block_width; ++x){
fb[y * width + x] = c2;
}
}
}
}
// final row, 24 grayscales
block_width = width / 24;
for (unsigned i = 0; i < 24; ++i) {
unsigned c = 232 + i;
u32 c2 = c | (c << 8);
unsigned start_x = i * block_width;
for (unsigned y = row_height * 7; y < height; ++y) {
for (unsigned x = start_x; x < start_x + block_width; ++x){
fb[y * width + x] = c2;
}
}
}
}
unsigned wait_key(unsigned wanted) {
while (1) {
swiWaitForVBlank();
scanKeys();
unsigned keys = keysDown();
if (keys & wanted) {
return keys;
}
}
}
term_t t0, t1;
int main(void)
{
videoSetModeSub(MODE_3_2D);
vramSetBankC(VRAM_C_SUB_BG);
u16 *fb0 = bgGetGfxPtr(bgInitSub(3, BgType_Bmp8, BgSize_B8_256x256, 0, 0));
generate_ansi256_palette(BG_PALETTE_SUB);
term_init(&t0, fb0);
select_term(&t0);
iprtf("This an experimental 256 color terminal emulator\n"
"\tWidth:\t%u\n\tHeight:\t%u\n\tChars:\t%u\n"
" \n"
"press A to loop, B to quit",
TERM_WIDTH, TERM_HEIGHT, TERM_BUF_LEN);
videoSetMode(MODE_3_2D);
vramSetBankA(VRAM_A_MAIN_BG);
u16 *fb1 = bgGetGfxPtr(bgInit(3, BgType_Bmp8, BgSize_B8_256x256, 0, 0));
dmaCopy(BG_PALETTE_SUB, BG_PALETTE, 256 * 2);
term_init(&t1, fb1);
select_term(&t1);
// scroll test
while (true) {
if (wait_key(KEY_A | KEY_B) == KEY_B) {
break;
}
for (unsigned i = 0; i < 16; ++i) {
iprtf("%x\n", i);
}
prt("...\rcarriage return test\n");
// color test
for (unsigned i = 0; i < 256; ++i) {
term_ctl(&t1, TERM_COLOR, i);
iprtf("%02x", i);
}
// color and font face test
term_ctl(&t1, TERM_COLOR, 15);
term_ctl(&t1, TERM_BG_COLOR, 0);
prt("\na quick brown fox jumps over the lazy dog,\n");
term_ctl(&t1, TERM_COLOR, 0);
term_ctl(&t1, TERM_BG_COLOR, 15);
prt("A QUICK BROWN FOX JUMPS OVER THE LAZY DOG.\n");
if (wait_key(KEY_A | KEY_B) == KEY_B) {
break;
}
show_ansi256_color_table(fb1, SCREEN_WIDTH, SCREEN_HEIGHT);
}
return 0;
}

228
source/term256.c Normal file
View File

@ -0,0 +1,228 @@
#include <nds.h>
#include <assert.h>
#include "term256.h"
#ifdef _MSC_VER
#define ITCM_CODE
#define UNROLL
#else
#define UNROLL __attribute__((optimize("unroll-loops")))
#endif
#define RGB(r,g,b) RGB15(r, g, b)
#define RBITS 5
#define RMAX (1 << RBITS)
#define GBITS 5
#define GMAX (1 << GBITS)
#define BBITS 5
#define BMAX (1 << BBITS)
// https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
void generate_ansi256_palette(color_t *p) {
// maybe I should use round to improve precision
// standard colors
*p++ = RGB(0, 0, 0);
*p++ = RGB(RMAX >> 1, 0, 0);
*p++ = RGB(0, GMAX >> 1, 0);
*p++ = RGB(RMAX >> 1, GMAX >> 1, 0);
*p++ = RGB(0, 0, BMAX >> 1);
*p++ = RGB(RMAX >> 1, 0, BMAX >> 1);
*p++ = RGB(0, GMAX >> 1, BMAX >> 1);
*p++ = RGB((RMAX * 3) >> 2, (GMAX * 3) >> 2, (BMAX * 3) >> 2);
// high-intensity colors
*p++ = RGB(RMAX >> 1, GMAX >> 1, BMAX >> 1);
*p++ = RGB(RMAX - 1, 0, 0);
*p++ = RGB(0, GMAX - 1, 0);
*p++ = RGB(RMAX - 1, GMAX - 1, 0);
*p++ = RGB(0, 0, BMAX - 1);
*p++ = RGB(RMAX - 1, 0, BMAX - 1);
*p++ = RGB(0, GMAX - 1, BMAX - 1);
*p++ = RGB(RMAX - 1, GMAX - 1, BMAX - 1);
// 216 colors, 6 * 6 * 6
unsigned rsteps[6] = { 0, (RMAX - 1) / 5, (RMAX - 1) * 2 / 5,
(RMAX - 1) * 3 / 5, (RMAX - 1) * 4 / 5, RMAX - 1 };
unsigned gsteps[6] = { 0, (GMAX - 1) / 5, (GMAX - 1) * 2 / 5,
(GMAX - 1) * 3 / 5, (GMAX - 1) * 4 / 5, GMAX - 1 };
unsigned bsteps[6] = { 0, (BMAX - 1) / 5, (BMAX - 1) * 2 / 5,
(BMAX - 1) * 3 / 5, (BMAX - 1) * 4 / 5, BMAX - 1 };
// how do I tell gcc not to unroll this?
for (unsigned r = 0; r < 6; ++r) {
for (unsigned g = 0; g < 6; ++g) {
for (unsigned b = 0; b < 6; ++b) {
*p++ = RGB(rsteps[r], gsteps[g], bsteps[b]);
}
}
}
// 24 gray scales, not including full white and full black, so 25 steps
for (unsigned j = 1; j < 25; ++j) {
*p++ = RGB((RMAX - 1) * j / 25, (GMAX - 1) * j / 25, (BMAX - 1) * j / 25);
}
}
static_assert(FONT_WIDTH == 6, "this thing can only handle FONT_WIDTH == 6");
#ifdef ARM9
ITCM_CODE
#endif
UNROLL
static inline void write_char(u16 *fb, unsigned x, unsigned y, unsigned char c, unsigned char color, unsigned char bg_color) {
const unsigned char *g = &font[c * FONT_HEIGHT];
for (unsigned fy = 0; fy < FONT_HEIGHT; ++fy) {
unsigned char l = g[fy]; // line fy of the glyph
u16 *p = &fb[((y + fy) * SCREEN_WIDTH + x) / 2];
*p++ = (l & 0x80 ? color : bg_color) | ((l & 0x40 ? color : bg_color) << 8);
*p++ = (l & 0x20 ? color : bg_color) | ((l & 0x10 ? color : bg_color) << 8);
*p = (l & 0x08 ? color : bg_color) | ((l & 0x04 ? color : bg_color) << 8);
}
}
void term_rst(term_t *t, u8 fg, u8 bg) {
/* so memset just fail?
memset(t->c, 0, sizeof(TERM_BUF_LEN));
memset(t->fg, fg, sizeof(TERM_BUF_LEN));
memset(t->bg, bg, sizeof(TERM_BUF_LEN));
for (unsigned i = 0; i < TERM_BUF_LEN; ++i) {
if (t->fg[i] != fg) {
iprintf("fg[%u] == %u\n", i, t->fg[i]);
break;
}
}
*/
for (unsigned i = 0; i < TERM_BUF_LEN; ++i) {
t->c[i] = 0;
t->fg[i] = fg;
t->bg[i] = bg;
}
t->cur = 0;
t->cur_fg = fg;
t->cur_bg = bg;
t->update_region_a = 0;
t->update_region_b = 0;
t->needs_full_update = 0;
}
void term_init(term_t *t, u16 *fb) {
t->fb = fb;
term_rst(t, 15, 0);
}
// void wait_key(unsigned key);
#ifdef ARM9
ITCM_CODE
#endif
void term_update_fb(term_t *t) {
if (t->needs_full_update) {
unsigned offset = 0;
unsigned y = 0;
for (unsigned ty = 0; ty < TERM_HEIGHT; ++ty) {
unsigned x = 0;
for (unsigned tx = 0; tx < TERM_WIDTH; ++tx) {
// wait_key(KEY_A);
// iprintf("%u,%c,%u,%u,%u,%u,%u,%u\n", offset, t->c[offset], t->fg[offset], t->bg[offset], ty, tx, y, x);
write_char(t->fb, x, y, t->c[offset], t->fg[offset], t->bg[offset]);
x += FONT_WIDTH;
++offset;
}
y += FONT_HEIGHT;
}
t->needs_full_update = 0;
t->update_region_a = t->update_region_b = t->cur;
} else if (t->update_region_a < t->update_region_b) {
// TODO: partial updates
}
}
void scroll(term_t *t) {
unsigned i;
u8 *p0 = t->c, *p0s = p0 + TERM_WIDTH;
u8 *p1 = t->fg, *p1s = p1 + TERM_WIDTH;
u8 *p2 = t->bg, *p2s = p2 + TERM_WIDTH;
for (i = 0; i < TERM_BUF_LEN - TERM_WIDTH; ++i){
*p0++ = *p0s++;
*p1++ = *p1s++;
*p2++ = *p2s++;
}
// fill the new line with cursor fg/bg
for (i = 0; i < TERM_WIDTH; ++i) {
*p0++ = 0;
*p1++ = t->cur_fg;
*p2++ = t->cur_bg;
}
t->cur -= TERM_WIDTH;
}
void term_ctl(term_t *t, int ctl_code, int ctl_param) {
switch (ctl_code) {
case TERM_COLOR:
t->cur_fg = ctl_param;
break;
case TERM_BG_COLOR:
t->cur_bg = ctl_param;
break;
case TERM_MOVE_X: {
unsigned x_pos = t->cur % TERM_WIDTH;
unsigned min_cur = t->cur - x_pos;
unsigned max_cur = t->cur - x_pos + TERM_WIDTH - 1;
t->cur += ctl_param;
if (t->cur < min_cur) {
t->cur = min_cur;
} else if(t->cur > max_cur){
t->cur = max_cur;
}
break;
} case TERM_MOVE_Y: {
unsigned min_cur = t->cur % TERM_WIDTH;
unsigned max_cur = TERM_WIDTH * (TERM_HEIGHT - 1) + min_cur;
t->cur += ctl_param * TERM_HEIGHT;
if (t->cur < min_cur) {
t->cur = min_cur;
} else if(t->cur > max_cur){
t->cur = max_cur;
}
break;
} default:
break;
}
}
#define TAB_WIDTH 4
void term_prt(term_t * t, const char *s) {
// I'm too lazy to parse ANSI escape code
// use term_ctl instead
unsigned char c;
while ((c = *(const unsigned char*)s) != 0) {
if (c == '\n') {
unsigned tx = t->cur % TERM_WIDTH;
if (tx != 0) {
// do not allow empty line
// also, new line never causes scroll
t->cur += TERM_WIDTH - tx;
}
} else if (c == '\r') {
t->cur -= t->cur % TERM_WIDTH;
} else if (c == '\t') {
unsigned tx = t->cur % TERM_WIDTH;
unsigned new_tx = tx + TAB_WIDTH;
new_tx -= new_tx % TAB_WIDTH;
if (new_tx > TERM_WIDTH) {
new_tx = TERM_WIDTH;
}
t->cur += new_tx - tx;
} else {
if (t->cur == TERM_BUF_LEN) {
scroll(t);
}
t->c[t->cur] = c;
t->fg[t->cur] = t->cur_fg;
t->bg[t->cur] = t->cur_bg;
++t->cur;
}
++s;
}
// TODO: partial updates
t->needs_full_update = 1;
term_update_fb(t);
}

38
source/term256.h Normal file
View File

@ -0,0 +1,38 @@
#pragma once
#include <nds.h>
#include "font.h"
#define TERM_WIDTH (SCREEN_WIDTH / FONT_WIDTH)
#define TERM_HEIGHT (SCREEN_HEIGHT / FONT_HEIGHT)
#define TERM_BUF_LEN (TERM_WIDTH * TERM_HEIGHT)
typedef u16 color_t;
void generate_ansi256_palette(color_t *p);
typedef struct {
u16 *fb;
u8 c[TERM_BUF_LEN];
u8 fg[TERM_BUF_LEN];
u8 bg[TERM_BUF_LEN];
unsigned cur;
u8 cur_fg;
u8 cur_bg;
unsigned update_region_a;
unsigned update_region_b;
int needs_full_update;
}term_t;
enum {
TERM_COLOR,
TERM_BG_COLOR,
TERM_MOVE_X,
TERM_MOVE_Y,
};
void term_init(term_t *t, u16 *fb);
void term_prt(term_t * t, const char *string);
void term_ctl(term_t *t, int ctl_code, int ctl_param);

34
source/term256ext.c Normal file
View File

@ -0,0 +1,34 @@
#include <stdio.h>
#include <stdarg.h>
#include "term256.h"
static term_t *current_term;
void select_term(term_t *t) {
current_term = t;
}
// do not print too long
#define STR_BUF_LEN 0x200
char str_buf[STR_BUF_LEN];
void iprtf(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vsniprintf(str_buf, STR_BUF_LEN, fmt, args);
va_end(args);
term_prt(current_term, str_buf);
}
void prtf(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vsnprintf(str_buf, STR_BUF_LEN, fmt, args);
va_end(args);
term_prt(current_term, str_buf);
}
void prt(const char *s) {
term_prt(current_term, s);
}

7
source/term256ext.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
void select_term(term_t *t);
void iprtf(const char *fmt, ...);
void prtf(const char *fmt, ...);
void prt(const char *s);