437 lines
13 KiB
C++
437 lines
13 KiB
C++
#include "gbscreen.h"
|
|
#include "sm83.h"
|
|
#include "mem.h"
|
|
//#include "zx_ula.h"
|
|
#include <SDL2/SDL.h>
|
|
#include "ui_window.h"
|
|
#include "debug.h"
|
|
#include "ui.h"
|
|
|
|
namespace gbscreen
|
|
{
|
|
uint32_t palette[4] = {
|
|
//0xFFFFFF, 0xFF0000, 0x00FF00, 0x0000FF
|
|
0xFFFFFF, 0xAAAAAA, 0x555555, 0x000000
|
|
};
|
|
SDL_Window *win = nullptr;
|
|
SDL_Renderer *ren = nullptr;
|
|
SDL_Texture *tex = nullptr;
|
|
SDL_Texture *uitex = nullptr;
|
|
|
|
uint32_t t_states_total = 70224;
|
|
uint32_t t_states_per_scanline = 456;
|
|
uint32_t vsync_lines = 10;
|
|
|
|
uint8_t zoom = 2;
|
|
bool fullscreen = false;
|
|
bool full_refresh = true;
|
|
int fullscreen_scale = 1;
|
|
SDL_Rect dest_rect;
|
|
|
|
//uint32_t time=0;
|
|
|
|
uint32_t t_screen = 0;
|
|
|
|
uint8_t gb_pixels[160*144];
|
|
uint8_t *ptr_pixel = gb_pixels;
|
|
|
|
uint16_t dots_in_scanline = 0;
|
|
uint8_t line_buffer[160];
|
|
|
|
bool eventHandler(SDL_Event *e)
|
|
{
|
|
if (e->type==SDL_WINDOWEVENT) {
|
|
if (e->window.event==SDL_WINDOWEVENT_CLOSE) {
|
|
return false;
|
|
} else if ((e->window.event==SDL_WINDOWEVENT_SHOWN) || (e->window.event==SDL_WINDOWEVENT_EXPOSED)) {
|
|
redraw();
|
|
}
|
|
}
|
|
if (!debug::debugging()) {
|
|
if (debug::paused()) {
|
|
if (e->type == SDL_KEYDOWN) {
|
|
if (e->key.keysym.scancode==SDL_SCANCODE_ESCAPE) {
|
|
const uint8_t dt = sm83::step();
|
|
debug::cont();
|
|
gbscreen::refresh(dt);
|
|
}
|
|
}
|
|
} else {
|
|
if (e->type == SDL_KEYDOWN) {
|
|
if (e->key.keysym.scancode==SDL_SCANCODE_ESCAPE) {
|
|
debug::pause();
|
|
gbscreen::redraw();
|
|
} else if (e->key.keysym.scancode==SDL_SCANCODE_F1) {
|
|
gbscreen::decZoom();
|
|
} else if (e->key.keysym.scancode==SDL_SCANCODE_F2) {
|
|
gbscreen::incZoom();
|
|
} else if (e->key.keysym.scancode==SDL_SCANCODE_F3) {
|
|
gbscreen::toggleFullscreen();
|
|
} else if (e->key.keysym.scancode==SDL_SCANCODE_F6) {
|
|
//zx_tape::play();
|
|
} else if (e->key.keysym.scancode==SDL_SCANCODE_F7) {
|
|
//zx_tape::rewind();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (e->type == SDL_MOUSEMOTION) {
|
|
SDL_ShowCursor(true);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void reinit()
|
|
{
|
|
if (win) ui::window::unregisterWindow(SDL_GetWindowID(win));
|
|
|
|
if (tex) SDL_DestroyTexture(tex);
|
|
if (uitex) SDL_DestroyTexture(uitex);
|
|
if (ren) SDL_DestroyRenderer(ren);
|
|
if (win) SDL_DestroyWindow(win);
|
|
|
|
const int z = fullscreen ? 1 : zoom;
|
|
win = SDL_CreateWindow("Gameboy Screen", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 160*z, 144*z, fullscreen?SDL_WINDOW_FULLSCREEN_DESKTOP:SDL_WINDOW_SHOWN);
|
|
ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED);
|
|
tex = SDL_CreateTexture(ren, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 160, 144);
|
|
uitex = ui::createtexture(ren);
|
|
|
|
ui::window::registerWindow(SDL_GetWindowID(win), eventHandler);
|
|
|
|
if (fullscreen)
|
|
{
|
|
int w, h;
|
|
SDL_GetWindowSize(win, &w, &h);
|
|
fullscreen_scale = h/144;
|
|
dest_rect.w = 160 * fullscreen_scale;
|
|
dest_rect.h = 144 * fullscreen_scale;
|
|
dest_rect.x = (w - dest_rect.w)/2;
|
|
dest_rect.y = (h - dest_rect.h)/2;
|
|
}
|
|
else
|
|
{
|
|
dest_rect.x = dest_rect.y = 0;
|
|
dest_rect.w = 160 * zoom;
|
|
dest_rect.h = 144 * zoom;
|
|
}
|
|
|
|
focus();
|
|
}
|
|
|
|
void init(int mode)
|
|
{
|
|
reinit();
|
|
}
|
|
|
|
void focus()
|
|
{
|
|
if (win)
|
|
{
|
|
SDL_RaiseWindow(win);
|
|
redraw();
|
|
}
|
|
}
|
|
|
|
void fill_line_buffer_bkg(uint8_t LY)
|
|
{
|
|
const uint8_t LCDC = mem::readMem(0xff40);
|
|
const uint8_t SCY = mem::readMem(0xff42);
|
|
const uint8_t SCX = mem::readMem(0xff43);
|
|
const uint16_t ty = uint8_t(SCY+LY) >> 3;
|
|
const uint8_t ly = uint8_t(SCY+LY) & 0x7;
|
|
uint16_t tx = SCX >> 3;
|
|
uint8_t ox = SCX & 0x7;
|
|
|
|
uint16_t base_tilemap_address = LCDC&0x8 ? 0x9c00 : 0x9800;
|
|
|
|
int pi = 0;
|
|
while(true) {
|
|
uint16_t tilemap_address = base_tilemap_address + tx + (ty<<5);
|
|
uint16_t tile = mem::readMem(tilemap_address);
|
|
uint16_t base_tile_address = 0x8000;
|
|
if ( ((LCDC&0x10)==0) && (tile<128) ) base_tile_address = 0x9000;
|
|
uint16_t tile_address = base_tile_address + (tile<<4) + (ly*2);
|
|
|
|
uint8_t a = mem::readMem(tile_address);
|
|
uint8_t b = mem::readMem(tile_address+1);
|
|
for (int i=0; i<8; ++i) {
|
|
if (ox==0) {
|
|
line_buffer[pi++] = (a&0x80 ? 1 : 0) + (b&0x80 ? 2 : 0 );
|
|
} else {
|
|
ox--;
|
|
}
|
|
a=a<<1; b=b<<1;
|
|
if (pi==160) return;
|
|
}
|
|
tx = (tx+1)&0x1f;
|
|
}
|
|
}
|
|
|
|
struct oam_entry_t
|
|
{
|
|
uint8_t y, x, tile, attr;
|
|
};
|
|
oam_entry_t *oam = nullptr;
|
|
|
|
void fill_line_buffer_obj(uint8_t LY)
|
|
{
|
|
const uint8_t LCDC = mem::readMem(0xff40);
|
|
if ((LCDC & 0x2) == 0) return;
|
|
|
|
oam = (oam_entry_t*)mem::rawPtr(0xfe00);
|
|
const uint8_t height = (LCDC & 0x4) ? 16 : 8;
|
|
uint8_t obj_list[10] = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 };
|
|
int num_obj_found=0;
|
|
int obj=0;
|
|
// Revisem els 40 posibles sprites o fins a trobar 10...
|
|
while (obj<40 && num_obj_found<10) {
|
|
// Si el sprite està tocant la linea actual l'afegim a la llista
|
|
if ( (LY+16 >= (oam[obj].y)) && (LY+16 < (oam[obj].y+height)) ) {
|
|
obj_list[num_obj_found]=obj;
|
|
num_obj_found++;
|
|
}
|
|
obj++;
|
|
}
|
|
// Pintem els sprites en el buffer de sprites
|
|
uint8_t pixels[160];
|
|
uint8_t x_pos[160];
|
|
for (int i=0;i<160;++i) { pixels[i] = 0; x_pos[i] = 255; }
|
|
obj=0;
|
|
while (obj_list[obj] != 255) {
|
|
oam_entry_t *o = &oam[obj_list[obj]];
|
|
const uint8_t ly = uint8_t(LY-o->y) & 0x7;
|
|
|
|
uint16_t tile = height==8 ? o->tile : o->tile & 0xFE; // si es dos tiles de alt, el primer sempre comença en numero parell
|
|
uint8_t yflip = o->attr&0x40 ? 8-ly : ly; // està invertit verticalment?
|
|
uint16_t tile_address = 0x8000 + (tile<<4) + (yflip*2);
|
|
|
|
uint8_t a = mem::readMem(tile_address);
|
|
uint8_t b = mem::readMem(tile_address+1);
|
|
|
|
for (int i=0; i<8; ++i) { // Per a cada pixel de la linea del tile...
|
|
if (o->x+i>=168) break; // Si ja estem fora de la pantalla per la dreta, eixim del bucle
|
|
if (o->x+i>=8) { // Si està dins de la pantalla...
|
|
if (x_pos[o->x+i-8]>o->x) { // Si te una x menor que la que tenía
|
|
//uint8_t xflip = o->attr&0x20 ? 8 : 0; // està invertit horitzontalment?
|
|
uint8_t ppos = 1 << ( o->attr&0x20 ? i : 7-i);
|
|
uint8_t val = (a&ppos ? 1 : 0) + (b&ppos ? 2 : 0 ); // agafem el pixel que toca
|
|
if (val) { // Si el pixel no es transparent...
|
|
pixels[o->x+i-8] = val | o->attr&0x80; // el pintem al buffer, amb el flag de prioritat respecte al BKG
|
|
x_pos[o->x+i-8] = o->x; // I apuntem la seua x per a comparar després
|
|
}
|
|
}
|
|
}
|
|
}
|
|
obj++;
|
|
}
|
|
// Per últim, volquem els pixels que toque al buffer de linea
|
|
for (int i=0; i<160; ++i) {
|
|
if (pixels[i]>0) { // si el pixel no es transparent...
|
|
if ( !(pixels[i]&0x80) || (line_buffer[i]==0) ) { // Si te prioritat o el color de fondo es 0...
|
|
line_buffer[i] = pixels[i]&0x03; // pintem el pixel (llevant el flag de prioritat)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void refresh(const uint32_t dt, const bool full)
|
|
{
|
|
const uint8_t LCDC = mem::readMem(0xff40);
|
|
if ((LCDC&0x80)==0) return;
|
|
|
|
uint8_t STAT = mem::readMem(0xff41);
|
|
uint8_t LY = mem::readMem(0xff44);
|
|
const uint8_t LYC = mem::readMem(0xff45);
|
|
for (int i=0;i<dt;++i)
|
|
{
|
|
// Açò va volcant els pixels del line_buffer en pantalla
|
|
if ( (STAT&0x3)==3) {
|
|
uint16_t current_pixel = dots_in_scanline-80;
|
|
if (current_pixel<160) {
|
|
//*(ptr_pixel++) = line_buffer[current_pixel];
|
|
gb_pixels[current_pixel+LY*160] = line_buffer[current_pixel];
|
|
}
|
|
}
|
|
|
|
// gestió de en quin dot i linea estem, i tot el que ha de passar
|
|
bool stat_interrupt = false;
|
|
dots_in_scanline++;
|
|
if ( (dots_in_scanline==80) && (LY<144) )
|
|
{
|
|
STAT = (STAT & 0xFC) | 0x3; // Set mode 3
|
|
}
|
|
else if ( (dots_in_scanline==252) && (LY<144) )
|
|
{
|
|
STAT = (STAT & 0xFC); // Set mode 0
|
|
if (STAT&0x08) stat_interrupt = true;
|
|
}
|
|
else if (dots_in_scanline==456)
|
|
{
|
|
dots_in_scanline = 0;
|
|
LY++;
|
|
if (LY==144)
|
|
{
|
|
STAT = (STAT & 0xFC) | 0x01; // Set mode 1
|
|
mem::writeMem(0xff41, STAT);
|
|
mem::writeMem(0xff44, LY);
|
|
sm83::interrupt(INTERRUPT_VBLANK);
|
|
if (STAT&0x10) stat_interrupt = true;
|
|
}
|
|
else
|
|
{
|
|
if (LY<144)
|
|
{
|
|
STAT = (STAT & 0xFC) | 0x02; // Set mode 2
|
|
if (STAT&0x20) stat_interrupt = true;
|
|
fill_line_buffer_bkg(LY);
|
|
fill_line_buffer_obj(LY);
|
|
}
|
|
else if (LY==154)
|
|
{
|
|
LY=0;
|
|
}
|
|
}
|
|
if (LY==LYC)
|
|
{
|
|
STAT = (STAT & 0xFB) | 0x40;
|
|
if (STAT&0x04) stat_interrupt = true;
|
|
}
|
|
else
|
|
{
|
|
STAT = (STAT & 0xFB);
|
|
}
|
|
}
|
|
|
|
if (stat_interrupt)
|
|
{
|
|
mem::writeMem(0xff41, STAT);
|
|
mem::writeMem(0xff44, LY);
|
|
sm83::interrupt(INTERRUPT_LCD);
|
|
}
|
|
|
|
t_screen++;
|
|
if (t_screen>=t_states_total)
|
|
{
|
|
t_screen=0;
|
|
ptr_pixel = gb_pixels;
|
|
redraw();
|
|
if (!full) sm83::interrupt(INTERRUPT_VBLANK);
|
|
}
|
|
}
|
|
mem::writeMem(0xff41, STAT);
|
|
mem::writeMem(0xff44, LY);
|
|
}
|
|
|
|
void fullrefresh()
|
|
{
|
|
uint32_t tmp = t_screen;
|
|
t_screen = 0;
|
|
//uint8_t * tmp_ptr = ptr_pixel;
|
|
//ptr_pixel = gb_pixels;
|
|
refresh(t_states_total, true);
|
|
//ptr_pixel = tmp_ptr;
|
|
t_screen = tmp;
|
|
}
|
|
|
|
void debugrefresh(const uint32_t dt)
|
|
{
|
|
if (full_refresh) fullrefresh(); else refresh(dt);
|
|
}
|
|
|
|
void redraw(const bool present)
|
|
{
|
|
//if (zx_tape::getplaying() && zx_tape::getOption(ZXTAPE_OPTION_FAST_LOAD)) return;
|
|
|
|
ui::setrenderer(ren, uitex);
|
|
|
|
Uint32* pixels;
|
|
int pitch;
|
|
SDL_LockTexture(tex, NULL, (void**)&pixels, &pitch);
|
|
for (int i=0; i<160*144;++i) *(pixels++) = palette[gb_pixels[i]];
|
|
SDL_UnlockTexture(tex);
|
|
|
|
if (fullscreen)
|
|
{
|
|
SDL_SetRenderDrawColor(ren, 0, 0, 0, 255);
|
|
SDL_RenderClear(ren);
|
|
}
|
|
|
|
// Pintem la textura a pantalla
|
|
SDL_RenderCopy(ren, tex, NULL, &dest_rect);
|
|
|
|
if (present)
|
|
SDL_RenderPresent(ren);
|
|
else
|
|
{
|
|
SDL_SetRenderDrawBlendMode(ren, SDL_BLENDMODE_BLEND);
|
|
SDL_SetRenderDrawColor(ren, 0, 0, 0, 128);
|
|
SDL_Rect rect {0,0,160*zoom,144*zoom};
|
|
if (fullscreen) SDL_GetWindowSize(win, &rect.w, &rect.h);
|
|
SDL_RenderFillRect(ren, &rect);
|
|
}
|
|
|
|
}
|
|
|
|
void present()
|
|
{
|
|
SDL_RenderPresent(ren);
|
|
}
|
|
|
|
void setTitle(const char* title)
|
|
{
|
|
char tmp[256];
|
|
strcpy(tmp, "Gameboy Screen");
|
|
strcat(tmp, title);
|
|
SDL_SetWindowTitle(win, tmp);
|
|
}
|
|
|
|
void setZoom(const int value)
|
|
{
|
|
if (value < 1) return;
|
|
SDL_DisplayMode dm;
|
|
SDL_GetCurrentDisplayMode(0, &dm);
|
|
|
|
if (160*value > dm.w) return;
|
|
if (144*value > dm.h) return;
|
|
|
|
zoom = value;
|
|
reinit();
|
|
}
|
|
|
|
void incZoom()
|
|
{
|
|
setZoom(zoom+1);
|
|
}
|
|
|
|
void decZoom()
|
|
{
|
|
setZoom(zoom-1);
|
|
}
|
|
|
|
void toggleFullscreen()
|
|
{
|
|
fullscreen = !fullscreen;
|
|
reinit();
|
|
}
|
|
|
|
const bool getFullscreen()
|
|
{
|
|
return fullscreen;
|
|
}
|
|
|
|
void toggleFullRefresh()
|
|
{
|
|
full_refresh = !full_refresh;
|
|
}
|
|
|
|
const bool getFullRefresh()
|
|
{
|
|
return full_refresh;
|
|
}
|
|
|
|
SDL_Renderer *getrenderer()
|
|
{
|
|
return ren;
|
|
}
|
|
}
|