diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c98b7c..aedfac7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,20 +6,36 @@ endif() set(CMAKE_C_COMPILER psx-gcc) set(CMAKE_CXX_COMPILER psx-g++) +set(CMAKE_AR mipsel-unknown-elf-ar) set(CMAKE_SYSTEM_NAME Generic) set(CMAKE_CROSSCOMPILING 1) project(pinboid C) -add_executable(${PROJECT_NAME} - "src/main.c" +add_executable(${PROJECT_NAME} "src/main.c") + +set(modules + "res" + "src/game" + "src/gfx" + "src/system" ) + +set(mode pal) # pal or ntsc + +foreach(c ${modules}) + add_subdirectory(${c}) +endforeach() + +target_link_libraries(${PROJECT_NAME} PUBLIC init game) + target_link_directories(${PROJECT_NAME} PUBLIC $ENV{PSXSDK_PATH}/lib) target_compile_options(${PROJECT_NAME} PUBLIC -DFIXMATH_FAST_SIN -D_PAL_MODE_ - -DPSXSDK_DEBUG -DNO_CDDA -DNO_INTRO -Wall -g3 -Og) + -DPSXSDK_DEBUG -DNO_CDDA -DNO_INTRO -Wall -g3 -Og -ffunction-sections + -fdata-sections) target_link_libraries(${PROJECT_NAME} PUBLIC -lpsx -lfixmath) target_include_directories(${PROJECT_NAME} PRIVATE . $ENV{PSXSDK_PATH}/include) -set(cdroot cdimg) +set(cdroot ${CMAKE_SOURCE_DIR}/cdimg) add_custom_target(exe ALL elf2exe ${PROJECT_NAME} ${cdroot}/${PROJECT_NAME}.exe -mark="A homebrew game created with PSXSDK" DEPENDS ${PROJECT_NAME}) diff --git a/src/game/CMakeLists.txt b/src/game/CMakeLists.txt new file mode 100644 index 0000000..89cd1f0 --- /dev/null +++ b/src/game/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(game "src/game.c") +target_include_directories(game PUBLIC "inc") +target_link_libraries(game PRIVATE gfx) diff --git a/src/game/inc/game.h b/src/game/inc/game.h new file mode 100644 index 0000000..8275d54 --- /dev/null +++ b/src/game/inc/game.h @@ -0,0 +1,15 @@ +#ifndef GAME_H +#define GAME_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +int game(void); + +#ifdef __cplusplus +} +#endif + +#endif /* GAME_H */ diff --git a/src/game/src/game.c b/src/game/src/game.c new file mode 100644 index 0000000..b17e534 --- /dev/null +++ b/src/game/src/game.c @@ -0,0 +1,24 @@ +#include +#include + +static int init(void) +{ + struct gfx_sprite s, s2; + + if (gfx_sprite_from_file("cdrom:\\block.TIM;1", &s)) + return -1; + + gfx_sprite_sort(&s); + s2 = s; + gfx_sprite_sort(&s2); + gfx_draw(); + return 0; +} + +int game(void) +{ + if (init()) + return -1; + + return 0; +} diff --git a/src/gfx/CMakeLists.txt b/src/gfx/CMakeLists.txt new file mode 100644 index 0000000..43ab7ae --- /dev/null +++ b/src/gfx/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(gfx + "src/init.c" + "src/sort.c" + "src/sprite.c" +) +target_include_directories(gfx PUBLIC "inc") + +if(mode STREQUAL "pal") + target_compile_definitions(gfx PRIVATE VIDEO_MODE=VMODE_PAL) +elseif(mode STREQUAL "ntsc") + target_compile_definitions(gfx PRIVATE VIDEO_MODE=VMODE_NTSC) +else() + message(FATAL_ERROR "unknown video mode") +endif() diff --git a/src/gfx/inc/gfx.h b/src/gfx/inc/gfx.h new file mode 100644 index 0000000..849fc8e --- /dev/null +++ b/src/gfx/inc/gfx.h @@ -0,0 +1,82 @@ +#ifndef GFX_H +#define GFX_H + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +enum +{ + SCREEN_X = 368, + SCREEN_Y = 240 +}; + +/* 0-3 Texture page X Base (N*64) (ie. in 64-halfword steps) ;GPUSTAT.0-3 + 4 Texture page Y Base (N*256) (ie. 0 or 256) ;GPUSTAT.4 + 5-6 Semi Transparency (0=B/2+F/2, 1=B+F, 2=B-F, 3=B+F/4) ;GPUSTAT.5-6 + 7-8 Texture page colors (0=4bit, 1=8bit, 2=15bit, 3=Reserved);GPUSTAT.7-8 + 9 Dither 24bit to 15bit (0=Off/strip LSBs, 1=Dither Enabled) ;GPUSTAT.9 + 10 Drawing to display area (0=Prohibited, 1=Allowed) ;GPUSTAT.10 + 11 Texture Disable (0=Normal, 1=Disable if GP1(09h).Bit0=1) ;GPUSTAT.15 + (Above might be chipselect for (absent) second VRAM chip?) + 12 Textured Rectangle X-Flip (BIOS does set this bit on power-up...?) + 13 Textured Rectangle Y-Flip (BIOS does set it equal to GPUSTAT.13...?) + 14-23 Not used (should be 0) + 24-31 Command (E1h)*/ +union gfx_common +{ + struct + { + uint32_t tpagex :4; + uint32_t tpagey :1; + uint32_t stp :2; + uint32_t bpp :2; + uint32_t dither :1; + uint32_t draw_to_disp :1; + uint32_t disable :1; + uint32_t xflip :1; + uint32_t yflip :1; + uint32_t :10; + uint8_t cmd; + }; + + uint32_t mask; +}; + +union gfx_sznext +{ + struct + { + uint32_t next :24; + uint32_t sz :8; + }; + + uint32_t cmd_next; +}; + +struct gfx_sprite +{ + union gfx_sznext sznext; + union gfx_common common; + uint8_t r, g, b; + uint8_t cmd; + uint16_t x, y; + uint16_t clutid; + uint8_t u, v; + uint16_t w, h; +}; + +int gfx_init(void); +void gfx_draw(void); +void gfx_sprite_init(struct gfx_sprite *s); +void gfx_sprite_sort(struct gfx_sprite *s); +int gfx_sprite_from_file(const char *path, struct gfx_sprite *s); + +#ifdef __cplusplus +} +#endif + +#endif /* GFX_H */ diff --git a/src/gfx/src/init.c b/src/gfx/src/init.c new file mode 100644 index 0000000..7447f77 --- /dev/null +++ b/src/gfx/src/init.c @@ -0,0 +1,12 @@ +#include +#include + +int gfx_init(void) +{ + GsInit(); + GsClearMem(); + GsSetVideoMode(SCREEN_X, SCREEN_Y, VMODE_PAL); + GsSetDrawEnv(&(GsDrawEnv){.w = SCREEN_X, .h = SCREEN_Y}); + GsSetDispEnv(&(GsDispEnv){}); + return 0; +} diff --git a/src/gfx/src/sort.c b/src/gfx/src/sort.c new file mode 100644 index 0000000..aa35854 --- /dev/null +++ b/src/gfx/src/sort.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +static union gfx_sznext *first, *last; + +static void add_to_list(union gfx_sznext *const p) +{ + p->next = 0; + + if (!first) + first = p; + else if (last) + last->next = (uint32_t)p; + + last = p; +} + +void gfx_sprite_sort(struct gfx_sprite *const s) +{ + add_to_list(&s->sznext); +} + +void gfx_draw(void) +{ + /* Wait for the GPU to finish drawing primitives. */ + while (!(GPU_CONTROL_PORT & (1 << 0x1a))) + ; + + /* Wait for the GPU to be free. */ + while (!(GPU_CONTROL_PORT & (1 << 0x1c))) + ; + + static uint32_t term = 0xffffff; + + add_to_list((union gfx_sznext *)&term); + + void gpu_ctrl(unsigned int command, unsigned int param); + + gpu_ctrl(4, 2); + D2_MADR = (uint32_t)first; + D2_BCR = 0; + D2_CHCR = (1 << 0xa) | 1 | (1 << 0x18); + first = NULL; +} diff --git a/src/gfx/src/sprite.c b/src/gfx/src/sprite.c new file mode 100644 index 0000000..967598b --- /dev/null +++ b/src/gfx/src/sprite.c @@ -0,0 +1,232 @@ +#include +#include +#include +#include +#include +#include +#include + +void gfx_sprite_init(struct gfx_sprite *const s) +{ + extern unsigned int draw_mode_packet; + + memset(s, 0, sizeof *s); + s->sznext.sz = (sizeof (*s) - sizeof s->sznext) / sizeof (uint32_t); + s->common.mask = draw_mode_packet; + s->cmd = 0x64; + s->r = s->g = s->b = NORMAL_LUMINANCE; +} + +static void transfer_init(const uint16_t x, const uint16_t y, + const uint16_t w, const uint16_t h) +{ + while (!(GPU_CONTROL_PORT & (1<<0x1c))) + ; + + GPU_CONTROL_PORT = 0x04000000; + GPU_DATA_PORT = 0x01000000; + GPU_DATA_PORT = 0xE6000000; + GPU_DATA_PORT = 0xA0000000; + GPU_DATA_PORT = (y << 16) | x; + GPU_DATA_PORT = (h << 16) | w; +} + +struct tim_pos +{ + uint16_t x, y, w, h; +}; + +static int transfer(const char *const path, const size_t sz, FILE *const f) +{ + const size_t rem = sz % sizeof (uint32_t); + + if (sz >= sizeof (uint32_t)) + { + for (size_t i = 0; i < sz / sizeof (uint32_t); i++) + { + uint32_t pix; + + if (!fread(&pix, sizeof pix, 1, f)) + { + printf("%s: could not read CLUT word %zu/%zu\n", + path, i, sz / sizeof pix); + return -1; + } + + GPU_DATA_PORT = pix; + } + } + + if (rem) + { + uint32_t pix = 0; + + if (!fread(&pix, rem, 1, f)) + { + printf("%s: failed reading remaining %zu bytes\n", path, rem); + return -1; + } + + GPU_DATA_PORT = pix; + } + + return 0; +} + +static int upload_clut(const char *const path, struct gfx_sprite *const s, + FILE *const f) +{ + int ret = -1; + + struct + { + uint32_t sz; + struct tim_pos pos; + uint16_t colors; + uint16_t num; + } clut; + + if (!fread(&clut, sizeof clut, 1, f)) + { + printf("%s: No CLUT pos data found\n", path); + return -1; + } + + transfer_init(clut.pos.x, clut.pos.y, clut.pos.w, clut.pos.h); + + const size_t sz = clut.sz - sizeof clut + sizeof clut.sz; + + if (transfer(path, sz, f)) + return -1; + + s->clutid = get_clutid(clut.pos.x, clut.pos.y); + return 0; +} + +enum bpp +{ + BPP_4 = 0, + BPP_8 = 1, + BPP_16 = 2, + BPP_24 = 4 +}; + +static int upload_img(const char const *path, struct gfx_sprite *const s, + const enum bpp bpp, FILE *const f) +{ + struct tim_pos imgpos; + + if (!fread(&imgpos, sizeof imgpos, 1, f)) + { + printf("%s: No CLUT pos data found\n", path); + return -1; + } + + transfer_init(imgpos.x, imgpos.y, imgpos.w, imgpos.h); + + /* ftell and fseek are limited to 2 GiB when sizeof (long) == 4, + * but that's already fine for PSX games. */ + const long off = ftell(f); + fseek(f, 0, SEEK_END); + const long sz = ftell(f) - off; + fseek(f, off, SEEK_SET); + + if (transfer(path, sz, f)) + return -1; + + switch (bpp) + { + case BPP_4: + s->w = imgpos.w * 4; + break; + + case BPP_8: + s->w = imgpos.w * 2; + break; + + case BPP_16: + s->w = imgpos.w; + break; + + case BPP_24: + s->w = imgpos.w + (imgpos.w / 2); + break; + } + + s->h = imgpos.h; + + enum + { + VRAM_X = 1024, + VRAM_Y = 512, + TPAGE_WIDTH = 64 + }; + + s->common.tpagex = imgpos.x / TPAGE_WIDTH; + s->common.tpagey = imgpos.y / (VRAM_Y / 2); + s->u = imgpos.x % TPAGE_WIDTH; + s->v = imgpos.y % (VRAM_Y / 2); + return 0; +} + +int gfx_sprite_from_file(const char *const path, struct gfx_sprite *const s) +{ + int ret = -1; + FILE *f = NULL; + + if (!path) + { + errno = EINVAL; + goto end; + } + + f = fopen(path, "rb"); + + if (!f) + { + printf("could not open %s: %s\n", path, strerror(errno)); + goto end; + } + + gfx_sprite_init(s); + + struct + { + uint32_t version; + uint32_t bpp :3; + uint32_t has_clut :1; + uint32_t :28; + } h; + + enum {VERSION_ID = 0x10}; + + if (!fread(&h, sizeof h, 1, f)) + { + printf("%s: TIM header not found\n", path); + goto end; + } + else if (h.version != VERSION_ID) + { + printf("%s: invalid TIM header 0x%08X\n", h.version); + goto end; + } + else if (h.bpp == BPP_24) + { + printf("%s: 24-bit mode unsupported\n", path); + goto end; + } + else if (h.has_clut && upload_clut(path, s, f)) + goto end; + else if (upload_img(path, s, h.bpp, f)) + goto end; + + s->common.bpp = h.bpp ? __builtin_ctz(h.bpp) + 1 : 0; + ret = 0; +end: + if (f) + { + fclose(f); + } + + return ret; +} diff --git a/src/main.c b/src/main.c index 974ac09..be8671d 100644 --- a/src/main.c +++ b/src/main.c @@ -1,6 +1,11 @@ +#include +#include #include int main(void) { + if (system_init() || game()) + return EXIT_FAILURE; + return 0; } diff --git a/src/system/CMakeLists.txt b/src/system/CMakeLists.txt new file mode 100644 index 0000000..5ff8971 --- /dev/null +++ b/src/system/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(init "src/init.c") +target_include_directories(init PUBLIC "inc") +target_link_libraries(init PUBLIC gfx) diff --git a/src/system/inc/init.h b/src/system/inc/init.h new file mode 100644 index 0000000..0560efe --- /dev/null +++ b/src/system/inc/init.h @@ -0,0 +1,15 @@ +#ifndef INIT_H +#define INIT_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +int system_init(void); + +#ifdef __cplusplus +} +#endif + +#endif /* INIT_H */ diff --git a/src/system/src/init.c b/src/system/src/init.c new file mode 100644 index 0000000..97f960a --- /dev/null +++ b/src/system/src/init.c @@ -0,0 +1,17 @@ +#include +#include + +static void vblank(void *const arg) +{ +} + +int system_init(void) +{ + PSX_InitEx(0); + SetVBlankHandler(vblank); + + if (gfx_init()) + return -1; + + return 0; +}