#include "gbscreen.h" #include #include "sm83.h" #include "mem.h" #include "audio_viewer.h" //#include "zx_ula.h" #include #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); if ((LCDC & 0x1) == 0) { for (int i=0; i<160; ++i) line_buffer[i]=0; return; } const uint8_t SCY = mem::readMem(0xff42); const uint8_t SCX = mem::readMem(0xff43); const uint8_t BGP = mem::readMem(0xff47); 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) { uint8_t index = (a&0x80 ? 1 : 0) + (b&0x80 ? 2 : 0 ); line_buffer[pi++] = (BGP >> (index*2)) & 0x3; } else { ox--; } a=a<<1; b=b<<1; if (pi==160) return; } tx = (tx+1)&0x1f; } } void fill_line_buffer_win(uint8_t LY) { const uint8_t LCDC = mem::readMem(0xff40); if ((LCDC & 0x21) != 0x21) return; const uint8_t WY = mem::readMem(0xff4a); if (LY> 3; const uint8_t ly = uint8_t(LY-WY) & 0x7; uint8_t ox = WX<7 ? 7-WX : 0; uint16_t tx = 0; uint16_t base_tilemap_address = LCDC&0x40 ? 0x9c00 : 0x9800; int pi = WX<7 ? 0 : WX-7; 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) { uint8_t index = (a&0x80 ? 1 : 0) + (b&0x80 ? 2 : 0 ); line_buffer[pi++] = (BGP >> (index*2)) & 0x3; } 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); const uint8_t OBP[2] = { mem::readMem(0xff48), mem::readMem(0xff49) }; 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] = 255; 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) & 0xf; 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 ? (height-1)-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? const uint8_t ppos = 1 << ( o->attr&0x20 ? i : 7-i); const uint8_t val = (a&ppos ? 1 : 0) + (b&ppos ? 2 : 0 ); // agafem el pixel que toca if (val) { // Si el pixel no es transparent... const uint8_t color = (OBP[(o->attr>>4)&1] >> (val*2)) & 0x3; pixels[o->x+i-8] = color | 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]!=255) { // 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=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); redraw(); } 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); } audio_viewer::refresh(); } 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; } }