From 0532e04a9e273f2a9033a28cdbce1030a91c5bb4 Mon Sep 17 00:00:00 2001 From: Daniel Hader Date: Sun, 23 Aug 2026 20:25:36 -0500 Subject: initial commit working tile demo --- .gitignore | 5 ++ Makefile | 160 ++++++++++++++++++++++++++++++++++++++++++++++ source/input.hpp | 45 +++++++++++++ source/main.cpp | 57 +++++++++++++++++ source/types.hpp | 18 ++++++ source/util.hpp | 191 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 476 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 source/input.hpp create mode 100644 source/main.cpp create mode 100644 source/types.hpp create mode 100644 source/util.hpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3de8727 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.elf +*.gba +*.sav +build/ +#*# \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f7abf52 --- /dev/null +++ b/Makefile @@ -0,0 +1,160 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITARM)),) +$(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") +endif + +include $(DEVKITARM)/gba_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 +# DATA is a list of directories containing binary data +# GRAPHICS is a list of directories containing files to be processed by grit +# +# All directories are specified relative to the project directory where +# the makefile is found +# +#--------------------------------------------------------------------------------- +TARGET := $(notdir $(CURDIR)) +BUILD := build +SOURCES := source +INCLUDES := include +DATA := +MUSIC := + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -mthumb -mthumb-interwork +CFLAGS := -g -Wall -O2\ + -mcpu=arm7tdmi -mtune=arm7tdmi\ + $(ARCH) + +CFLAGS += $(INCLUDE) +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=c++20 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +#--------------------------------------------------------------------------------- +# any extra libraries we wish to link with the project +#--------------------------------------------------------------------------------- +LIBS := #-lmm -lgba + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(LIBGBA) + +#--------------------------------------------------------------------------------- +# 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)/*.*))) + +ifneq ($(strip $(MUSIC)),) + export AUDIOFILES := $(foreach dir,$(notdir $(wildcard $(MUSIC)/*.*)),$(CURDIR)/$(MUSIC)/$(dir)) + BINFILES += soundbank.bin +endif + +#--------------------------------------------------------------------------------- +# 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_SOURCES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export OFILES := $(OFILES_BIN) $(OFILES_SOURCES) + +export HFILES := $(addsuffix .h,$(subst .,_,$(BINFILES))) + +export INCLUDE := $(foreach dir,$(INCLUDES),-iquote $(CURDIR)/$(dir)) \ + $(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).gba + + +#--------------------------------------------------------------------------------- +else + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- + +$(OUTPUT).gba : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +$(OFILES_SOURCES) : $(HFILES) + +#--------------------------------------------------------------------------------- +# The bin2o rule should be copied and modified +# for each extension used in the data directories +#--------------------------------------------------------------------------------- + +#--------------------------------------------------------------------------------- +# rule to build soundbank from music files +#--------------------------------------------------------------------------------- +soundbank.bin soundbank.h : $(AUDIOFILES) +#--------------------------------------------------------------------------------- + @mmutil $^ -osoundbank.bin -hsoundbank.h + +#--------------------------------------------------------------------------------- +# This rule links in binary data with the .bin extension +#--------------------------------------------------------------------------------- +%.bin.o %_bin.h : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + + +-include $(DEPSDIR)/*.d +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/source/input.hpp b/source/input.hpp new file mode 100644 index 0000000..2a2a3a2 --- /dev/null +++ b/source/input.hpp @@ -0,0 +1,45 @@ +#include "types.hpp" + + + +typedef u16 KeyFlags; + +enum Key : KeyFlags { + A = 0x0001, + B = 0x0002, + Select = 0x0004, + Start = 0x0008, + Right = 0x0010, + Left = 0x0020, + Up = 0x0040, + Down = 0x0080, + R = 0x0100, + L = 0x0200, +}; + +constexpr KeyFlags KEY_MASK = 0x03FF; + +volatile KeyFlags* const REG_KEYINPUT = reinterpret_cast(0x04000130); + +static inline KeyFlags &keyflags_curr() { + static KeyFlags keybits_curr = 0; + return keybits_curr; +} + +static inline KeyFlags &keyflags_prev() { + static KeyFlags keybits_prev = 0; + return keybits_prev; +} + +static inline void keyflags_poll() { + KeyFlags &prev = keyflags_prev(); + KeyFlags &curr = keyflags_curr(); + + prev = curr; + curr = ~*REG_KEYINPUT & KEY_MASK; +} + +static inline bool key_is_down(Key key) { return keyflags_curr() & key; } +static inline bool key_is_up(Key key) { return ~keyflags_curr() & key; } +static inline bool key_was_down(Key key) { return keyflags_prev() & key; } +static inline bool key_was_up(Key key) { return ~keyflags_prev() & key; } diff --git a/source/main.cpp b/source/main.cpp new file mode 100644 index 0000000..ec6be22 --- /dev/null +++ b/source/main.cpp @@ -0,0 +1,57 @@ +#include "types.hpp" +#include "util.hpp" +#include "input.hpp" + +ScreenEntry *bg0_map = screenblock_mem[28]; + +void bad_vsync() { + while (*REG_VCOUNT >= 160); + while (*REG_VCOUNT < 160); +} + +int main() { + auto bg_flags = BGControlFlags() + .set_charblock_base(0) + .set_screenblock_base(0) + .set_size(BGControlSize::Regular_64x64); + + set_bg_control(0, bg_flags); + set_bg_offsets(0, 0, 0); + + const Tile4 tiles[2] = { + {{0x11111111, 0x01111111, 0x01111111, 0x01111111, 0x01111111, 0x01111111, 0x01111111, 0x00000001}}, + {{0x00000000, 0x00100100, 0x01100110, 0x00011000, 0x00011000, 0x01100110, 0x00100100, 0x00000000}}, + }; + + set_tile(0, 0, tiles[0]); + set_tile(0, 1, tiles[1]); + + set_bg_palette(0, 1, Color::from_rgb(31, 0, 0)); + set_bg_palette(1, 1, Color::from_rgb(31, 0, 0)); + set_bg_palette(2, 1, Color::from_rgb(31, 0, 0)); + set_bg_palette(3, 1, Color::from_rgb(31, 0, 0)); + + ScreenEntry *screen_entry_ptr = bg0_map; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 32*32; j++) { + *screen_entry_ptr++ = ScreenEntry().set_palette_bank(i); + } + } + + set_display_control(Mode0, BG0); + + s16 x = 0, y = 0; + while (1) { + bad_vsync(); + keyflags_poll(); + + if (key_is_down(Key::Left)) x -= 1; + if (key_is_down(Key::Right)) x += 1; + if (key_is_down(Key::Up)) y -= 1; + if (key_is_down(Key::Down)) y += 1; + + set_bg_offsets(0, x, y); + } + + return 0; +} diff --git a/source/types.hpp b/source/types.hpp new file mode 100644 index 0000000..222b11c --- /dev/null +++ b/source/types.hpp @@ -0,0 +1,18 @@ +#ifndef TYPES_HPP +#define TYPES_HPP + +typedef signed char s8; +typedef signed short s16; +typedef signed int s32; + +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned int u32; + +typedef volatile unsigned char vu8; +typedef volatile unsigned short vu16; +typedef volatile unsigned int vu32; + +typedef u16 color_t; + +#endif diff --git a/source/util.hpp b/source/util.hpp new file mode 100644 index 0000000..7b39376 --- /dev/null +++ b/source/util.hpp @@ -0,0 +1,191 @@ +#ifndef UTIL_HPP +#define UTIL_HPP + +#include "types.hpp" +#include +#include + +constexpr u32 SCREEN_WIDTH = 240; +constexpr u32 SCREEN_HEIGHT = 160; + +vu16* const REG_VCOUNT = reinterpret_cast(0x04000006); + +enum DisplayControlMode : u16 { + Mode0 = 0x0000, + Mode1 = 0x0001, + Mode2 = 0x0002, + Mode3 = 0x0003, + Mode4 = 0x0004, + Mode5 = 0x0005, +}; + +enum DisplayControlLayer : u16 { + BG0 = 0x0100, + BG1 = 0x0200, + BG2 = 0x0400, + BG3 = 0x0800, + BG4 = 0x1000, +}; + +typedef u16 DisplayControlFlags; + +constexpr DisplayControlFlags operator|(DisplayControlMode mode, DisplayControlLayer layer) { + return static_cast(static_cast(mode) | static_cast(layer)); +} + +static inline void set_display_control(DisplayControlMode mode, DisplayControlLayer layer) { + static volatile DisplayControlFlags* const display_control = reinterpret_cast(0x04000000); + *display_control = mode | layer; +} + +enum BGControlColorMode : u16 { + Mode_4bpp = 0x0000, + Mode_8bpp = 0x0080, +}; + +enum BGControlSize : u16 { + Regular_32x32 = 0x0000, + Regular_64x32 = 0x4000, + Regular_32x64 = 0x8000, + Regular_64x64 = 0xc000, + Affine_16x16 = 0x0000, + Affine_32x32 = 0x4000, + Affine_64x64 = 0x8000, + Affine_128x128 = 0xc000, +}; + +struct BGControlFlags { + u16 flags; + + constexpr BGControlFlags() : flags(0) {} + + constexpr BGControlFlags &set_priority(u16 priority) { + assert(priority < 4); + this->flags = (this->flags & 0xfffc) | priority; + return *this; + } + + constexpr BGControlFlags &set_charblock_base(u16 block) { + assert(block < 4); + this->flags = (this->flags & 0xfff3) | (block << 2); + return *this; + } + + constexpr BGControlFlags &set_mosaic(u16 enabled) { + this->flags = (this->flags & 0xffbf) | (!!enabled << 6); + return *this; + } + + constexpr BGControlFlags &set_color_mode(BGControlColorMode mode) { + this->flags = (this->flags & 0xff7f) | mode; + return *this; + } + + constexpr BGControlFlags &set_screenblock_base(u16 block) { + assert(block < 32); + this->flags = (this->flags & 0xe0ff) | (block << 8); + return *this; + } + + constexpr BGControlFlags &set_affine_wrapping(u16 enabled) { + this->flags = (this->flags & 0xdfff) | (!!enabled << 13); + return *this; + } + + constexpr BGControlFlags &set_size(BGControlSize size) { + this->flags = (this->flags & 0x3fff) | size; + return *this; + } + + constexpr operator u16() const { return this->flags; } +}; + +static inline void set_bg_control(u16 bg, BGControlFlags flags) { + assert(bg < 4); + + vu16* const reg = reinterpret_cast(0x04000008 + 2*bg); + *reg = flags; +} + +static inline void set_bg_offsets(u16 bg, u16 h_offset, u16 v_offset) { + vu16* const h_reg = reinterpret_cast(0x04000010 + 4*bg); + vu16* const v_reg = reinterpret_cast(0x04000012 + 4*bg); + + *h_reg = h_offset; + *v_reg = v_offset; +} + +struct Color { + u16 data; + + static constexpr Color from_rgb(u16 r, u16 g, u16 b) { + assert(r < 32 && g < 32 && b < 32); + return Color { .data = u16(r | (g << 5) | (b << 10)) }; + } +}; + +typedef Color PaletteBank[16]; + +struct Tile4 { u32 data[8]; }; +typedef Tile4 CharBlock4[512]; + +template +static inline void volatile_copy(volatile T* dst, const T* src) { + using U = std::conditional_t<(sizeof(T) % sizeof(u32) == 0) && (alignof(T) % alignof(u32) == 0), u32, u16>; + static_assert(sizeof(T) % sizeof(U) == 0); + static_assert(alignof(T) % alignof(U) == 0); + auto *d = reinterpret_cast(dst); + auto *s = reinterpret_cast(src); + for (unsigned i = 0; i < sizeof(T) / sizeof(U); i++) { + d[i] = s[i]; + } +} + +static inline void set_tile(u16 block, u16 index, const Tile4 &tile) { + // TODO bounds check + volatile CharBlock4 *const blocks = reinterpret_cast(0x06000000); + volatile_copy(&blocks[block][index], &tile); +} + +static inline void set_bg_palette(u32 bank, u32 index, const Color &color) { + volatile PaletteBank *const banks = reinterpret_cast(0x05000000); + volatile_copy(&banks[bank][index], &color); +} + +// pair of screen entry bytes +struct ScreenEntry { + u16 data; + + constexpr ScreenEntry() : data(0) {} + constexpr ScreenEntry &set_tile_index(u16 tid) { + assert(tid < 1024); + this->data = (this->data & ~0x03ff) | tid; + return *this; + } + + constexpr ScreenEntry &set_hflip(u16 flip) { + this->data = (this->data & ~0x0400) | (!!flip << 10); + return *this; + } + + constexpr ScreenEntry &set_vflip(u16 flip) { + this->data = (this->data & ~0x0800) | (!!flip << 11); + return *this; + } + + constexpr ScreenEntry &set_palette_bank(u16 bank) { + assert(bank < 16); + this->data = (this->data & ~0xf000) | (bank << 12); + return *this; + } + + constexpr operator u16() const { return this->data; } +}; + +typedef ScreenEntry ScreenBlock[1024]; + +CharBlock4 *const tile4_mem = reinterpret_cast(0x06000000); +PaletteBank *const palette_bg_bank = reinterpret_cast(0x05000000); +ScreenBlock *const screenblock_mem = reinterpret_cast(0x06000000); + +#endif -- cgit v1.2.3