@@ -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]; | ||||