| @@ -8,7 +8,6 @@ | |||||
| #include "src/config.h" | #include "src/config.h" | ||||
| #include "src/disassembler.h" | #include "src/disassembler.h" | ||||
| #include "src/emulator.h" | #include "src/emulator.h" | ||||
| #include "src/gamegear.h" | |||||
| #include "src/logging.h" | #include "src/logging.h" | ||||
| #include "src/rom.h" | #include "src/rom.h" | ||||
| @@ -35,21 +34,16 @@ int main(int argc, char *argv[]) | |||||
| retval = disassemble_file(config->src_path, config->dst_path); | retval = disassemble_file(config->src_path, config->dst_path); | ||||
| retval = retval ? EXIT_SUCCESS : EXIT_FAILURE; | retval = retval ? EXIT_SUCCESS : EXIT_FAILURE; | ||||
| } else { | } else { | ||||
| ROM *rom; | |||||
| ROM rom; | |||||
| const char* errmsg; | const char* errmsg; | ||||
| if ((errmsg = rom_open(&rom, config->rom_path))) { | if ((errmsg = rom_open(&rom, config->rom_path))) { | ||||
| ERROR("couldn't load ROM image '%s': %s", config->rom_path, errmsg) | ERROR("couldn't load ROM image '%s': %s", config->rom_path, errmsg) | ||||
| retval = EXIT_FAILURE; | retval = EXIT_FAILURE; | ||||
| } else { | } else { | ||||
| GameGear *gg = gamegear_create(); | |||||
| printf("crater: emulating: %s\n", rom->name); | |||||
| gamegear_load(gg, rom); | |||||
| emulate(gg); | |||||
| gamegear_destroy(gg); | |||||
| rom_close(rom); | |||||
| printf("crater: emulating: %s\n", rom.name); | |||||
| emulate(&rom, config->fullscreen, config->scale); | |||||
| rom_close(&rom); | |||||
| } | } | ||||
| } | } | ||||
| @@ -38,7 +38,7 @@ static void print_help(const char *arg1) | |||||
| " -g, --debug show logging information while running; add twice (-gg)\n" | " -g, --debug show logging information while running; add twice (-gg)\n" | ||||
| " to show more detailed logs, including an emulator trace\n" | " to show more detailed logs, including an emulator trace\n" | ||||
| " -s, --scale <n> scale the game screen by an integer factor\n" | " -s, --scale <n> scale the game screen by an integer factor\n" | ||||
| " (applies to windowed mode only)\n" | |||||
| " (applies to windowed mode only; defaults to 4)\n" | |||||
| " -a, --assemble <in> [<out>] convert z80 assembly source code into a\n" | " -a, --assemble <in> [<out>] convert z80 assembly source code into a\n" | ||||
| " binary file that can be run by crater\n" | " binary file that can be run by crater\n" | ||||
| " -d, --disassemble <in> [<out>] convert a binary file into z80 assembly\n" | " -d, --disassemble <in> [<out>] convert a binary file into z80 assembly\n" | ||||
| @@ -307,7 +307,7 @@ static int parse_args(Config *config, int argc, char *argv[]) | |||||
| filename based on the input file, replacing its extension with ".gg" or | filename based on the input file, replacing its extension with ".gg" or | ||||
| ".asm" (or adding it, if none is present). | ".asm" (or adding it, if none is present). | ||||
| */ | */ | ||||
| static void guess_assembler_output_file(Config* config) | |||||
| static void guess_assembler_output_file(Config *config) | |||||
| { | { | ||||
| char *src = config->src_path, *ptr = src + strlen(src) - 1, | char *src = config->src_path, *ptr = src + strlen(src) - 1, | ||||
| *ext = config->assemble ? ".gg" : ".asm"; | *ext = config->assemble ? ".gg" : ".asm"; | ||||
| @@ -326,28 +326,39 @@ static void guess_assembler_output_file(Config* config) | |||||
| /* | /* | ||||
| Ensure that the combination of arguments in a config object are valid. | Ensure that the combination of arguments in a config object are valid. | ||||
| Some modifications are made in the case of missing arguments, like guessing | |||||
| the filenames for assembler output files. | |||||
| */ | */ | ||||
| static bool sanity_check(Config* config) | |||||
| static bool sanity_check(Config *config) | |||||
| { | { | ||||
| bool assembler = config->assemble || config->disassemble; | bool assembler = config->assemble || config->disassemble; | ||||
| if (config->fullscreen && config->scale > 1) { | |||||
| if (config->fullscreen && config->scale) { | |||||
| ERROR("cannot specify a scale in fullscreen mode") | ERROR("cannot specify a scale in fullscreen mode") | ||||
| return false; | return false; | ||||
| } else if (config->assemble && config->disassemble) { | } else if (config->assemble && config->disassemble) { | ||||
| ERROR("cannot assemble and disassemble at the same time") | ERROR("cannot assemble and disassemble at the same time") | ||||
| return false; | return false; | ||||
| } else if (assembler && (config->fullscreen || config->scale > 1)) { | |||||
| } else if (assembler && (config->fullscreen || config->scale)) { | |||||
| ERROR("cannot specify emulator options in assembler mode") | ERROR("cannot specify emulator options in assembler mode") | ||||
| return false; | return false; | ||||
| } else if (assembler && !config->src_path) { | } else if (assembler && !config->src_path) { | ||||
| ERROR("assembler mode requires an input file") | ERROR("assembler mode requires an input file") | ||||
| return false; | return false; | ||||
| } | } | ||||
| return true; | |||||
| } | |||||
| /* | |||||
| Set some default values for missing arguments. | |||||
| Some additional sanity checking is done. | |||||
| */ | |||||
| static bool set_defaults(Config *config) | |||||
| { | |||||
| bool assembler = config->assemble || config->disassemble; | |||||
| if (!config->scale) { | |||||
| config->scale = 4; | |||||
| } | |||||
| if (assembler && !config->dst_path) { | if (assembler && !config->dst_path) { | ||||
| guess_assembler_output_file(config); | guess_assembler_output_file(config); | ||||
| } | } | ||||
| @@ -355,7 +366,6 @@ static bool sanity_check(Config* config) | |||||
| ERROR("refusing to overwrite the assembler input file; pass -r to override") | ERROR("refusing to overwrite the assembler input file; pass -r to override") | ||||
| return false; | return false; | ||||
| } | } | ||||
| return true; | return true; | ||||
| } | } | ||||
| @@ -376,14 +386,14 @@ int config_create(Config** config_ptr, int argc, char* argv[]) | |||||
| config->assemble = false; | config->assemble = false; | ||||
| config->disassemble = false; | config->disassemble = false; | ||||
| config->fullscreen = false; | config->fullscreen = false; | ||||
| config->scale = 1; | |||||
| config->scale = 0; | |||||
| config->rom_path = NULL; | config->rom_path = NULL; | ||||
| config->src_path = NULL; | config->src_path = NULL; | ||||
| config->dst_path = NULL; | config->dst_path = NULL; | ||||
| config->overwrite = false; | config->overwrite = false; | ||||
| retval = parse_args(config, argc, argv); | retval = parse_args(config, argc, argv); | ||||
| if (retval == CONFIG_OK && !sanity_check(config)) | |||||
| if (retval == CONFIG_OK && !(sanity_check(config) && set_defaults(config))) | |||||
| retval = CONFIG_EXIT_FAILURE; | retval = CONFIG_EXIT_FAILURE; | ||||
| if (retval != CONFIG_OK) { | if (retval != CONFIG_OK) { | ||||
| config_destroy(config); | config_destroy(config); | ||||
| @@ -406,7 +406,7 @@ static bool write_disassembly(const char *path, char **lines) | |||||
| */ | */ | ||||
| bool disassemble_file(const char *src_path, const char *dst_path) | bool disassemble_file(const char *src_path, const char *dst_path) | ||||
| { | { | ||||
| ROM *rom; | |||||
| ROM rom; | |||||
| const char *errmsg; | const char *errmsg; | ||||
| char **lines; | char **lines; | ||||
| @@ -416,8 +416,8 @@ bool disassemble_file(const char *src_path, const char *dst_path) | |||||
| return false; | return false; | ||||
| } | } | ||||
| lines = disassemble(rom); | |||||
| rom_close(rom); | |||||
| lines = disassemble(&rom); | |||||
| rom_close(&rom); | |||||
| DEBUG("Writing output file") | DEBUG("Writing output file") | ||||
| return write_disassembly(dst_path, lines); | return write_disassembly(dst_path, lines); | ||||
| @@ -2,105 +2,155 @@ | |||||
| Released under the terms of the MIT License. See LICENSE for details. */ | Released under the terms of the MIT License. See LICENSE for details. */ | ||||
| #include <signal.h> | #include <signal.h> | ||||
| #include <stdint.h> | |||||
| #include <SDL.h> | #include <SDL.h> | ||||
| #include "emulator.h" | #include "emulator.h" | ||||
| #include "gamegear.h" | |||||
| #include "logging.h" | #include "logging.h" | ||||
| #include "util.h" | |||||
| #define SCREEN_WIDTH 3 * (160 + 96) // TODO: define elsewhere; use scale | |||||
| #define SCREEN_HEIGHT 3 * (144 + 48) | |||||
| typedef struct { | |||||
| GameGear *gg; | |||||
| SDL_Window *window; | |||||
| SDL_Renderer *renderer; | |||||
| SDL_Texture *texture; | |||||
| uint32_t *pixels; | |||||
| } Emulator; | |||||
| static GameGear *global_gg; | |||||
| static SDL_Window *window; | |||||
| SDL_Renderer* renderer; | |||||
| static Emulator emu; | |||||
| /* | /* | ||||
| Signal handler for SIGINT. | |||||
| Signal handler for SIGINT. Tells the GameGear to power off, if it exists. | |||||
| */ | */ | ||||
| static void handle_sigint(int sig) | static void handle_sigint(int sig) | ||||
| { | { | ||||
| (void) sig; | (void) sig; | ||||
| if (global_gg) | |||||
| gamegear_power_off(global_gg); | |||||
| if (emu.gg) | |||||
| gamegear_power_off(emu.gg); // Safe! | |||||
| } | } | ||||
| /* | /* | ||||
| TODO: ... | |||||
| Set up SDL for drawing the game. | |||||
| */ | */ | ||||
| static void setup_graphics() | |||||
| static void setup_graphics(bool fullscreen, unsigned scale) | |||||
| { | { | ||||
| if (SDL_Init(SDL_INIT_VIDEO) < 0) | if (SDL_Init(SDL_INIT_VIDEO) < 0) | ||||
| FATAL("SDL failed to initialize: %s", SDL_GetError()); | FATAL("SDL failed to initialize: %s", SDL_GetError()); | ||||
| SDL_CreateWindowAndRenderer(SCREEN_WIDTH, SCREEN_HEIGHT, | |||||
| SDL_WINDOW_BORDERLESS/* |SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE ? */, | |||||
| &window, &renderer); | |||||
| uint32_t flags; | |||||
| if (fullscreen) | |||||
| flags = SDL_WINDOW_FULLSCREEN_DESKTOP; | |||||
| else | |||||
| flags = SDL_WINDOW_BORDERLESS|SDL_WINDOW_RESIZABLE; | |||||
| SDL_CreateWindowAndRenderer( | |||||
| scale * GG_SCREEN_WIDTH, scale * GG_SCREEN_HEIGHT, | |||||
| flags, &emu.window, &emu.renderer); | |||||
| if (!window) | |||||
| if (!emu.window) | |||||
| FATAL("SDL failed to create a window: %s", SDL_GetError()); | FATAL("SDL failed to create a window: %s", SDL_GetError()); | ||||
| if (!renderer) | |||||
| if (!emu.renderer) | |||||
| FATAL("SDL failed to create a renderer: %s", SDL_GetError()); | FATAL("SDL failed to create a renderer: %s", SDL_GetError()); | ||||
| SDL_SetRenderDrawColor(renderer, 0x33, 0x33, 0x33, 0xFF); | |||||
| SDL_RenderClear(renderer); | |||||
| SDL_RenderPresent(renderer); | |||||
| emu.texture = SDL_CreateTexture(emu.renderer, SDL_PIXELFORMAT_ARGB8888, | |||||
| SDL_TEXTUREACCESS_STREAMING, GG_SCREEN_WIDTH, GG_SCREEN_HEIGHT); | |||||
| if (!emu.texture) | |||||
| FATAL("SDL failed to create a texture: %s", SDL_GetError()); | |||||
| emu.pixels = cr_malloc( | |||||
| sizeof(uint32_t) * GG_SCREEN_WIDTH * GG_SCREEN_HEIGHT); | |||||
| SDL_RenderSetLogicalSize(emu.renderer, GG_SCREEN_WIDTH, GG_SCREEN_HEIGHT); | |||||
| SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest"); | |||||
| SDL_SetWindowTitle(emu.window, "crater"); | |||||
| SDL_ShowCursor(SDL_DISABLE); | |||||
| SDL_SetRenderDrawColor(emu.renderer, 0x00, 0x00, 0x00, 0xFF); | |||||
| SDL_RenderClear(emu.renderer); | |||||
| SDL_RenderPresent(emu.renderer); | |||||
| } | } | ||||
| /* | /* | ||||
| GameGear callback: handle SDL logic at the end of a frame. | |||||
| Actually send the pixel data to the screen. | |||||
| */ | */ | ||||
| static void draw_frame(GameGear *gg) | |||||
| static void draw_frame() | |||||
| { | { | ||||
| SDL_RenderPresent(renderer); | |||||
| SDL_UpdateTexture(emu.texture, NULL, emu.pixels, | |||||
| GG_SCREEN_WIDTH * sizeof(uint32_t)); | |||||
| SDL_SetRenderDrawColor(emu.renderer, 0x00, 0x00, 0x00, 0xFF); | |||||
| SDL_RenderClear(emu.renderer); | |||||
| SDL_RenderCopy(emu.renderer, emu.texture, NULL, NULL); | |||||
| SDL_RenderPresent(emu.renderer); | |||||
| } | |||||
| /* | |||||
| Handle SDL events, mainly quit events and button presses. | |||||
| */ | |||||
| static void handle_events(GameGear *gg) | |||||
| { | |||||
| SDL_Event e; | SDL_Event e; | ||||
| while (SDL_PollEvent(&e)) { | while (SDL_PollEvent(&e)) { | ||||
| if (e.type == SDL_QUIT) { | if (e.type == SDL_QUIT) { | ||||
| gamegear_power_off(gg); | gamegear_power_off(gg); | ||||
| return; | return; | ||||
| } | } | ||||
| // TODO: buttons | |||||
| } | } | ||||
| } | |||||
| SDL_SetRenderDrawColor(renderer, 0x33, 0x33, 0x33, 0xFF); | |||||
| SDL_RenderClear(renderer); | |||||
| /* | |||||
| GameGear callback: Draw the current frame and handle SDL event logic. | |||||
| */ | |||||
| static void frame_callback(GameGear *gg) | |||||
| { | |||||
| draw_frame(); | |||||
| handle_events(gg); | |||||
| } | } | ||||
| /* | /* | ||||
| TODO: ... | |||||
| Clean up SDL stuff allocated in setup_graphics(). | |||||
| */ | */ | ||||
| static void cleanup_graphics() | static void cleanup_graphics() | ||||
| { | { | ||||
| SDL_DestroyRenderer(renderer); | |||||
| SDL_DestroyWindow(window); | |||||
| window = NULL; | |||||
| renderer = NULL; | |||||
| free(emu.pixels); | |||||
| SDL_DestroyTexture(emu.texture); | |||||
| SDL_DestroyRenderer(emu.renderer); | |||||
| SDL_DestroyWindow(emu.window); | |||||
| SDL_Quit(); | SDL_Quit(); | ||||
| emu.window = NULL; | |||||
| emu.renderer = NULL; | |||||
| emu.texture = NULL; | |||||
| } | } | ||||
| /* | /* | ||||
| Emulate a Game Gear. Handle I/O with the host computer. | |||||
| Emulate a ROM in a Game Gear while handling I/O with the host computer. | |||||
| Block until emulation is finished. | Block until emulation is finished. | ||||
| */ | */ | ||||
| void emulate(GameGear *gg) | |||||
| void emulate(ROM *rom, bool fullscreen, unsigned scale) | |||||
| { | { | ||||
| global_gg = gg; | |||||
| emu.gg = gamegear_create(); | |||||
| signal(SIGINT, handle_sigint); | signal(SIGINT, handle_sigint); | ||||
| gamegear_set_callback(gg, draw_frame); | |||||
| setup_graphics(); | |||||
| setup_graphics(fullscreen, scale); | |||||
| gamegear_attach_callback(emu.gg, frame_callback); | |||||
| gamegear_attach_display(emu.gg, emu.pixels); | |||||
| gamegear_load(emu.gg, rom); | |||||
| gamegear_simulate(gg); | |||||
| gamegear_simulate(emu.gg); | |||||
| if (gamegear_get_exception(gg)) | |||||
| ERROR("caught exception: %s", gamegear_get_exception(gg)) | |||||
| if (gamegear_get_exception(emu.gg)) | |||||
| ERROR("caught exception: %s", gamegear_get_exception(emu.gg)) | |||||
| else | else | ||||
| WARN("caught signal, stopping...") | WARN("caught signal, stopping...") | ||||
| if (DEBUG_LEVEL) | if (DEBUG_LEVEL) | ||||
| gamegear_print_state(gg); | |||||
| gamegear_print_state(emu.gg); | |||||
| cleanup_graphics(); | cleanup_graphics(); | ||||
| gamegear_clear_callback(gg); | |||||
| signal(SIGINT, SIG_DFL); | signal(SIGINT, SIG_DFL); | ||||
| global_gg = NULL; | |||||
| gamegear_destroy(emu.gg); | |||||
| emu.gg = NULL; | |||||
| } | } | ||||
| @@ -3,8 +3,10 @@ | |||||
| #pragma once | #pragma once | ||||
| #include "gamegear.h" | |||||
| #include <stdbool.h> | |||||
| #include "rom.h" | |||||
| /* Functions */ | /* Functions */ | ||||
| void emulate(GameGear*); | |||||
| void emulate(ROM*, bool, unsigned); | |||||
| @@ -18,6 +18,11 @@ | |||||
| /* | /* | ||||
| Create and return a pointer to a new GameGear object. | Create and return a pointer to a new GameGear object. | ||||
| The GameGear operates in headless mode by default (i.e., without any | |||||
| noticeable output). You'll probably want to attach a frame-completion | |||||
| callback with gamegear_attach_callback() and a display with | |||||
| gamegear_attach_display(). | |||||
| */ | */ | ||||
| GameGear* gamegear_create() | GameGear* gamegear_create() | ||||
| { | { | ||||
| @@ -93,18 +98,34 @@ void gamegear_power_off(GameGear *gg) | |||||
| /* | /* | ||||
| Set a callback to be triggered whenever the GameGear completes a frame. | Set a callback to be triggered whenever the GameGear completes a frame. | ||||
| The callback is passed a reference to the GameGear object. | |||||
| */ | */ | ||||
| void gamegear_set_callback(GameGear *gg, GGFrameCallback callback) | |||||
| void gamegear_attach_callback(GameGear *gg, GGFrameCallback callback) | |||||
| { | { | ||||
| gg->callback = callback; | gg->callback = callback; | ||||
| } | } | ||||
| /* | /* | ||||
| Reset the GameGear's frame callback function. | |||||
| Set a display to written to whenever the GameGear draws a pixel. | |||||
| The array must be (GG_SCREEN_WIDTH * GG_SCREEN_HEIGHT) pixels large, where | |||||
| each pixel is a 32-bit integer in ARGB order (i.e., A is the top 8 bits). | |||||
| */ | |||||
| void gamegear_attach_display(GameGear *gg, uint32_t *pixels) | |||||
| { | |||||
| gg->vdp.pixels = pixels; | |||||
| } | |||||
| /* | |||||
| Reset any callbacks or displays attached to the GameGear. | |||||
| This returns the GameGear to headless mode. | |||||
| */ | */ | ||||
| void gamegear_clear_callback(GameGear *gg) | |||||
| void gamegear_detach(GameGear *gg) | |||||
| { | { | ||||
| gg->callback = NULL; | gg->callback = NULL; | ||||
| gg->vdp.pixels = NULL; | |||||
| } | } | ||||
| /* | /* | ||||
| @@ -12,6 +12,9 @@ | |||||
| #include "rom.h" | #include "rom.h" | ||||
| #include "z80.h" | #include "z80.h" | ||||
| #define GG_SCREEN_WIDTH (160 + 96) | |||||
| #define GG_SCREEN_HEIGHT (144 + 48) | |||||
| #define GG_FPS 60 | #define GG_FPS 60 | ||||
| #define GG_EXC_BUFF_SIZE 128 | #define GG_EXC_BUFF_SIZE 128 | ||||
| @@ -37,8 +40,11 @@ GameGear* gamegear_create(); | |||||
| void gamegear_destroy(GameGear*); | void gamegear_destroy(GameGear*); | ||||
| void gamegear_load(GameGear*, const ROM*); | void gamegear_load(GameGear*, const ROM*); | ||||
| void gamegear_simulate(GameGear*); | void gamegear_simulate(GameGear*); | ||||
| void gamegear_power_off(GameGear*); | |||||
| void gamegear_set_callback(GameGear*, GGFrameCallback); | |||||
| void gamegear_clear_callback(GameGear*); | |||||
| void gamegear_power_off(GameGear*); // TODO: generic "gamegear_input()" with a power-off option | |||||
| void gamegear_attach_callback(GameGear*, GGFrameCallback); | |||||
| void gamegear_attach_display(GameGear*, uint32_t*); | |||||
| void gamegear_detach(GameGear*); | |||||
| const char* gamegear_get_exception(GameGear*); | const char* gamegear_get_exception(GameGear*); | ||||
| void gamegear_print_state(const GameGear*); | void gamegear_print_state(const GameGear*); | ||||
| @@ -154,15 +154,13 @@ static bool find_and_read_header(ROM *rom) | |||||
| } | } | ||||
| /* | /* | ||||
| Create and load a ROM image located at the given path. | |||||
| Load a ROM image located at the given path. | |||||
| rom_ptr will point to the new object if created successfully, and NULL will | |||||
| be returned. Otherwise, rom_ptr will not be modified and an error string | |||||
| will be returned. The error string should not be freed. | |||||
| NULL will be returned if the ROM is opened successfully. Otherwise, and an | |||||
| error string will be returned. The error string should not be freed. | |||||
| */ | */ | ||||
| const char* rom_open(ROM **rom_ptr, const char *path) | |||||
| const char* rom_open(ROM *rom, const char *path) | |||||
| { | { | ||||
| ROM *rom; | |||||
| FILE *fp; | FILE *fp; | ||||
| struct stat st; | struct stat st; | ||||
| @@ -178,8 +176,6 @@ const char* rom_open(ROM **rom_ptr, const char *path) | |||||
| return (st.st_mode & S_IFDIR) ? rom_err_isdir : rom_err_notfile; | return (st.st_mode & S_IFDIR) ? rom_err_isdir : rom_err_notfile; | ||||
| } | } | ||||
| rom = cr_malloc(sizeof(ROM)); | |||||
| // Set defaults: | // Set defaults: | ||||
| rom->name = NULL; | rom->name = NULL; | ||||
| rom->data = NULL; | rom->data = NULL; | ||||
| @@ -227,18 +223,16 @@ const char* rom_open(ROM **rom_ptr, const char *path) | |||||
| return rom_err_sms; | return rom_err_sms; | ||||
| } | } | ||||
| *rom_ptr = rom; | |||||
| return NULL; | return NULL; | ||||
| } | } | ||||
| /* | /* | ||||
| Free a ROM object previously created with rom_open(). | |||||
| Free memory previously allocated by the ROM during rom_open(). | |||||
| */ | */ | ||||
| void rom_close(ROM *rom) | void rom_close(ROM *rom) | ||||
| { | { | ||||
| free(rom->name); | free(rom->name); | ||||
| free(rom->data); | free(rom->data); | ||||
| free(rom); | |||||
| } | } | ||||
| /* | /* | ||||
| @@ -42,7 +42,7 @@ typedef struct { | |||||
| /* Functions */ | /* Functions */ | ||||
| const char* rom_open(ROM**, const char*); | |||||
| const char* rom_open(ROM*, const char*); | |||||
| void rom_close(ROM*); | void rom_close(ROM*); | ||||
| const char* rom_product(const ROM*); | const char* rom_product(const ROM*); | ||||
| const char* rom_region(const ROM*); | const char* rom_region(const ROM*); | ||||
| @@ -12,13 +12,16 @@ | |||||
| #define CODE_REG_WRITE 2 | #define CODE_REG_WRITE 2 | ||||
| #define CODE_CRAM_WRITE 3 | #define CODE_CRAM_WRITE 3 | ||||
| extern SDL_Renderer* renderer; | |||||
| /* | /* | ||||
| Initialize the Video Display Processor (VDP). | Initialize the Video Display Processor (VDP). | ||||
| The VDP will write to its pixels array whenever it draws a scanline. It | |||||
| defaults to NULL, but you should set it to something if you want to see its | |||||
| output. | |||||
| */ | */ | ||||
| void vdp_init(VDP *vdp) | void vdp_init(VDP *vdp) | ||||
| { | { | ||||
| vdp->pixels = NULL; | |||||
| vdp->vram = cr_malloc(sizeof(uint8_t) * VDP_VRAM_SIZE); | vdp->vram = cr_malloc(sizeof(uint8_t) * VDP_VRAM_SIZE); | ||||
| vdp->cram = cr_malloc(sizeof(uint8_t) * VDP_CRAM_SIZE); | vdp->cram = cr_malloc(sizeof(uint8_t) * VDP_CRAM_SIZE); | ||||
| memset(vdp->vram, 0x00, VDP_VRAM_SIZE); | memset(vdp->vram, 0x00, VDP_VRAM_SIZE); | ||||
| @@ -105,19 +108,16 @@ static uint8_t get_backdrop_addr(const VDP *vdp) | |||||
| /* | /* | ||||
| TODO: ... | TODO: ... | ||||
| */ | */ | ||||
| static void draw_pixel(uint8_t y, uint8_t x, uint16_t color) | |||||
| static void draw_pixel(VDP *vdp, uint8_t y, uint8_t x, uint16_t color) | |||||
| { | { | ||||
| uint8_t r = 0x11 * (color & 0x000F); | uint8_t r = 0x11 * (color & 0x000F); | ||||
| uint8_t g = 0x11 * ((color & 0x00F0) >> 4); | uint8_t g = 0x11 * ((color & 0x00F0) >> 4); | ||||
| uint8_t b = 0x11 * ((color & 0x0F00) >> 8); | uint8_t b = 0x11 * ((color & 0x0F00) >> 8); | ||||
| SDL_SetRenderDrawColor(renderer, r, g, b, 0xFF); | |||||
| uint32_t argb = (0xFF << 24) + (r << 16) + (g << 8) + b; | |||||
| uint8_t i, j; | |||||
| for (i = 0; i < 3; i++) { | |||||
| for (j = 0; j < 3; j++) | |||||
| SDL_RenderDrawPoint(renderer, 3 * x + i, 3 * y + j); | |||||
| } | |||||
| // TODO | |||||
| vdp->pixels[y * (160 + 96) + x] = argb; | |||||
| } | } | ||||
| /* | /* | ||||
| @@ -154,7 +154,7 @@ static void draw_background(VDP *vdp) | |||||
| (((bp3 >> i) & 1) << 3); | (((bp3 >> i) & 1) << 3); | ||||
| color = vdp->cram[2 * idx] + (vdp->cram[2 * idx + 1] << 8); | color = vdp->cram[2 * idx] + (vdp->cram[2 * idx + 1] << 8); | ||||
| offx = hflip ? (col * 8 + (7 - i)) : (col * 8 + i); | offx = hflip ? (col * 8 + (7 - i)) : (col * 8 + i); | ||||
| draw_pixel(vdp->v_counter, offx, color); | |||||
| draw_pixel(vdp, vdp->v_counter, offx, color); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -181,6 +181,9 @@ static void draw_sprites(VDP *vdp) | |||||
| */ | */ | ||||
| static void draw_scanline(VDP *vdp) | static void draw_scanline(VDP *vdp) | ||||
| { | { | ||||
| if (!vdp->pixels) | |||||
| return; | |||||
| draw_background(vdp); | draw_background(vdp); | ||||
| draw_sprites(vdp); | draw_sprites(vdp); | ||||
| } | } | ||||
| @@ -14,6 +14,8 @@ | |||||
| /* Structs */ | /* Structs */ | ||||
| typedef struct { | typedef struct { | ||||
| uint32_t *pixels; | |||||
| uint8_t *vram; | uint8_t *vram; | ||||
| uint8_t *cram; | uint8_t *cram; | ||||
| uint8_t regs[VDP_REGS]; | uint8_t regs[VDP_REGS]; | ||||