From 459987c418a1c728008569ea3356eb2fe7dd2587 Mon Sep 17 00:00:00 2001 From: pjgat09 Date: Fri, 9 Aug 2013 02:31:26 +0000 Subject: [PATCH] m64p: Removed code that looked at the Glide64mk2 ini file --- .../dll/mupen64plus-video-glide64mk2.dll | Bin 1059840 -> 1058816 bytes .../src/Glide64/Main.cpp | 5435 +++++------ .../src/Glide64/rdp.cpp | 8607 ++++++++--------- 3 files changed, 7036 insertions(+), 7006 deletions(-) diff --git a/BizHawk.MultiClient/output/dll/mupen64plus-video-glide64mk2.dll b/BizHawk.MultiClient/output/dll/mupen64plus-video-glide64mk2.dll index 9ba9b41ba5099db5d9958aa4c15cbe407bb802b4..be417a8d5c1aa4ca416cf375cf44a12f10fdc2c6 100644 GIT binary patch delta 184758 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

e$^bpzgV^{p{9_7qG@&=Io?U; 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 delta 186047 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�gk=~ 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

$$=_f-S??A0^YVBg{GK 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 -#elif defined(__MSC__) -#include -#define PATH_MAX MAX_PATH -#endif -#include "osal_dynamiclib.h" -#ifdef TEXTURE_FILTER // Hiroshi Morii -#include -int ghq_dmptex_toggle_key = 0; -#endif -#if defined(__MINGW32__) -#define swprintf _snwprintf -#define vswprintf _vsnwprintf -#endif - -#define G64_VERSION "G64 Mk2" -#define RELTIME "Date: " __DATE__// " Time: " __TIME__ - -#ifdef EXT_LOGGING -std::ofstream extlog; -#endif - -#ifdef LOGGING -std::ofstream loga; -#endif - -#ifdef RDP_LOGGING -int log_open = FALSE; -std::ofstream rdp_log; -#endif - -#ifdef RDP_ERROR_LOG -int elog_open = FALSE; -std::ofstream rdp_err; -#endif - -GFX_INFO gfx; - -/* definitions of pointers to Core config functions */ -ptr_ConfigOpenSection ConfigOpenSection = NULL; -ptr_ConfigSetParameter ConfigSetParameter = NULL; -ptr_ConfigGetParameter ConfigGetParameter = NULL; -ptr_ConfigGetParameterHelp ConfigGetParameterHelp = NULL; -ptr_ConfigSetDefaultInt ConfigSetDefaultInt = NULL; -ptr_ConfigSetDefaultFloat ConfigSetDefaultFloat = NULL; -ptr_ConfigSetDefaultBool ConfigSetDefaultBool = NULL; -ptr_ConfigSetDefaultString ConfigSetDefaultString = NULL; -ptr_ConfigGetParamInt ConfigGetParamInt = NULL; -ptr_ConfigGetParamFloat ConfigGetParamFloat = NULL; -ptr_ConfigGetParamBool ConfigGetParamBool = NULL; -ptr_ConfigGetParamString ConfigGetParamString = NULL; - -ptr_ConfigGetSharedDataFilepath ConfigGetSharedDataFilepath = NULL; -ptr_ConfigGetUserConfigPath ConfigGetUserConfigPath = NULL; -ptr_ConfigGetUserDataPath ConfigGetUserDataPath = NULL; -ptr_ConfigGetUserCachePath ConfigGetUserCachePath = NULL; - -/* definitions of pointers to Core video extension functions */ -ptr_VidExt_Init CoreVideo_Init = NULL; -ptr_VidExt_Quit CoreVideo_Quit = NULL; -ptr_VidExt_ListFullscreenModes CoreVideo_ListFullscreenModes = NULL; -ptr_VidExt_SetVideoMode CoreVideo_SetVideoMode = NULL; -ptr_VidExt_SetCaption CoreVideo_SetCaption = NULL; -ptr_VidExt_ToggleFullScreen CoreVideo_ToggleFullScreen = NULL; -ptr_VidExt_ResizeWindow CoreVideo_ResizeWindow = NULL; -ptr_VidExt_GL_GetProcAddress CoreVideo_GL_GetProcAddress = NULL; -ptr_VidExt_GL_SetAttribute CoreVideo_GL_SetAttribute = NULL; -ptr_VidExt_GL_SwapBuffers CoreVideo_GL_SwapBuffers = NULL; -int to_fullscreen = FALSE; -int fullscreen = FALSE; -int romopen = FALSE; -GrContext_t gfx_context = 0; -int debugging = FALSE; -int exception = FALSE; - -int evoodoo = 0; -int ev_fullscreen = 0; - -#ifdef __WINDOWS__ -#define WINPROC_OVERRIDE -#endif - -#ifdef WINPROC_OVERRIDE -LRESULT CALLBACK WndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); -WNDPROC oldWndProc = NULL; -WNDPROC myWndProc = NULL; -#endif - -#ifdef ALTTAB_FIX -HHOOK hhkLowLevelKybd = NULL; -LRESULT CALLBACK LowLevelKeyboardProc(int nCode, - WPARAM wParam, LPARAM lParam); -#endif - -#ifdef PERFORMANCE -int64 perf_cur; -int64 perf_next; -#endif - -#ifdef FPS -LARGE_INTEGER perf_freq; -LARGE_INTEGER fps_last; -LARGE_INTEGER fps_next; -float fps = 0.0f; -wxUint32 fps_count = 0; - -wxUint32 vi_count = 0; -float vi = 0.0f; - -wxUint32 region = 0; - -float ntsc_percent = 0.0f; -float pal_percent = 0.0f; - -#endif - -// ref rate -// 60=0x0, 70=0x1, 72=0x2, 75=0x3, 80=0x4, 90=0x5, 100=0x6, 85=0x7, 120=0x8, none=0xff - -unsigned long BMASK = 0x7FFFFF; -// Reality display processor structure -RDP rdp; - -SETTINGS settings = { FALSE, 640, 480, GR_RESOLUTION_640x480, 0 }; - -HOTKEY_INFO hotkey_info; - -VOODOO voodoo = {0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0 - }; - -GrTexInfo fontTex; -GrTexInfo cursorTex; -wxUint32 offset_font = 0; -wxUint32 offset_cursor = 0; -wxUint32 offset_textures = 0; -wxUint32 offset_texbuf1 = 0; - -int capture_screen = 0; -char capture_path[256]; - -SDL_sem *mutexProcessDList = SDL_CreateSemaphore(1); - -// SOME FUNCTION DEFINITIONS - -static void DrawFrameBuffer (); - - -void (*renderCallback)(int) = NULL; -static void (*l_DebugCallback)(void *, int, const char *) = NULL; -static void *l_DebugCallContext = NULL; - -void _ChangeSize () -{ - rdp.scale_1024 = settings.scr_res_x / 1024.0f; - rdp.scale_768 = settings.scr_res_y / 768.0f; - -// float res_scl_x = (float)settings.res_x / 320.0f; - float res_scl_y = (float)settings.res_y / 240.0f; - - wxUint32 scale_x = *gfx.VI_X_SCALE_REG & 0xFFF; - if (!scale_x) return; - wxUint32 scale_y = *gfx.VI_Y_SCALE_REG & 0xFFF; - if (!scale_y) return; - - float fscale_x = (float)scale_x / 1024.0f; - float fscale_y = (float)scale_y / 2048.0f; - - wxUint32 dwHStartReg = *gfx.VI_H_START_REG; - wxUint32 dwVStartReg = *gfx.VI_V_START_REG; - - wxUint32 hstart = dwHStartReg >> 16; - wxUint32 hend = dwHStartReg & 0xFFFF; - - // dunno... but sometimes this happens - if (hend == hstart) hend = (int)(*gfx.VI_WIDTH_REG / fscale_x); - - wxUint32 vstart = dwVStartReg >> 16; - wxUint32 vend = dwVStartReg & 0xFFFF; - - rdp.vi_width = (hend - hstart) * fscale_x; - rdp.vi_height = (vend - vstart) * fscale_y * 1.0126582f; - float aspect = (settings.adjust_aspect && (fscale_y > fscale_x) && (rdp.vi_width > rdp.vi_height)) ? fscale_x/fscale_y : 1.0f; - -#ifdef LOGGING - sprintf (out_buf, "hstart: %d, hend: %d, vstart: %d, vend: %d\n", hstart, hend, vstart, vend); - LOG (out_buf); - sprintf (out_buf, "size: %d x %d\n", (int)rdp.vi_width, (int)rdp.vi_height); - LOG (out_buf); -#endif - - rdp.scale_x = (float)settings.res_x / rdp.vi_width; - if (region > 0 && settings.pal230) - { - // odd... but pal games seem to want 230 as height... - rdp.scale_y = res_scl_y * (230.0f / rdp.vi_height) * aspect; - } - else - { - rdp.scale_y = (float)settings.res_y / rdp.vi_height * aspect; - } - // rdp.offset_x = settings.offset_x * res_scl_x; - // rdp.offset_y = settings.offset_y * res_scl_y; - //rdp.offset_x = 0; - // rdp.offset_y = 0; - rdp.offset_y = ((float)settings.res_y - rdp.vi_height * rdp.scale_y) * 0.5f; - if (((wxUint32)rdp.vi_width <= (*gfx.VI_WIDTH_REG)/2) && (rdp.vi_width > rdp.vi_height)) - rdp.scale_y *= 0.5f; - - rdp.scissor_o.ul_x = 0; - rdp.scissor_o.ul_y = 0; - rdp.scissor_o.lr_x = (wxUint32)rdp.vi_width; - rdp.scissor_o.lr_y = (wxUint32)rdp.vi_height; - - rdp.update |= UPDATE_VIEWPORT | UPDATE_SCISSOR; -} - -void ChangeSize () -{ - if (debugging) - { - _ChangeSize (); - return; - } - switch (settings.aspectmode) - { - case 0: //4:3 - if (settings.scr_res_x >= settings.scr_res_y * 4.0f / 3.0f) { - settings.res_y = settings.scr_res_y; - settings.res_x = (wxUint32)(settings.res_y * 4.0f / 3.0f); - } else { - settings.res_x = settings.scr_res_x; - settings.res_y = (wxUint32)(settings.res_x / 4.0f * 3.0f); - } - break; - case 1: //16:9 - if (settings.scr_res_x >= settings.scr_res_y * 16.0f / 9.0f) { - settings.res_y = settings.scr_res_y; - settings.res_x = (wxUint32)(settings.res_y * 16.0f / 9.0f); - } else { - settings.res_x = settings.scr_res_x; - settings.res_y = (wxUint32)(settings.res_x / 16.0f * 9.0f); - } - break; - default: //stretch or original - settings.res_x = settings.scr_res_x; - settings.res_y = settings.scr_res_y; - } - _ChangeSize (); - rdp.offset_x = (settings.scr_res_x - settings.res_x) / 2.0f; - float offset_y = (settings.scr_res_y - settings.res_y) / 2.0f; - settings.res_x += (wxUint32)rdp.offset_x; - settings.res_y += (wxUint32)offset_y; - rdp.offset_y += offset_y; - if (settings.aspectmode == 3) // original - { - rdp.scale_x = rdp.scale_y = 1.0f; - rdp.offset_x = (settings.scr_res_x - rdp.vi_width) / 2.0f; - rdp.offset_y = (settings.scr_res_y - rdp.vi_height) / 2.0f; - } - // settings.res_x = settings.scr_res_x; - // settings.res_y = settings.scr_res_y; -} - -void ConfigWrapper() -{ - char strConfigWrapperExt[] = "grConfigWrapperExt"; - GRCONFIGWRAPPEREXT grConfigWrapperExt = (GRCONFIGWRAPPEREXT)grGetProcAddress(strConfigWrapperExt); - if (grConfigWrapperExt) - grConfigWrapperExt(settings.wrpResolution, settings.wrpVRAM * 1024 * 1024, settings.wrpFBO, settings.wrpAnisotropic); -} -/* -static wxConfigBase * OpenIni() -{ - wxConfigBase * ini = wxConfigBase::Get(false); - if (!ini) - { - if (iniName.IsEmpty()) - iniName = pluginPath + wxT("/Glide64mk2.ini"); - if (wxFileExists(iniName)) - { - wxFileInputStream is(iniName); - wxFileConfig * fcfg = new wxFileConfig(is, wxConvISO8859_1); - wxConfigBase::Set(fcfg); - ini = fcfg; - } - } - if (!ini) - wxMessageBox(_T("Can not find ini file! Plugin will not run properly."), _T("File not found"), wxOK|wxICON_EXCLAMATION); - return ini; -} -*/ -#ifndef OLDAPI -void WriteLog(m64p_msg_level level, const char *msg, ...) -{ - char buf[1024]; - va_list args; - va_start(args, msg); - vsnprintf(buf, 1023, msg, args); - buf[1023]='\0'; - va_end(args); - if (l_DebugCallback) - { - l_DebugCallback(l_DebugCallContext, level, buf); - } -} -#endif - -void ReadSettings () -{ - // LOG("ReadSettings\n"); - if (!Config_Open()) - { - ERRLOG("Could not open configuration!"); - return; - } - - settings.card_id = (BYTE)Config_ReadInt ("card_id", "Card ID", 0, TRUE, FALSE); - //settings.lang_id not needed - // depth_bias = -Config_ReadInt ("depth_bias", "Depth bias level", 0, TRUE, FALSE); - settings.res_data = 0; - settings.scr_res_x = settings.res_x = Config_ReadScreenInt("ScreenWidth"); - settings.scr_res_y = settings.res_y = Config_ReadScreenInt("ScreenHeight"); - - settings.vsync = (BOOL)Config_ReadInt ("vsync", "Vertical sync", 0); - settings.ssformat = (BOOL)Config_ReadInt("ssformat", "TODO:ssformat", 0); - //settings.fast_crc = (BOOL)Config_ReadInt ("fast_crc", "Fast CRC", 0); - - settings.show_fps = (BYTE)Config_ReadInt ("show_fps", "Display performance stats (add together desired flags): 1=FPS counter, 2=VI/s counter, 4=% speed, 8=FPS transparent", 0, TRUE, FALSE); - settings.clock = (BOOL)Config_ReadInt ("clock", "Clock enabled", 0); - settings.clock_24_hr = (BOOL)Config_ReadInt ("clock_24_hr", "Clock is 24-hour", 0); - // settings.advanced_options only good for GUI config - // settings.texenh_options = only good for GUI config - //settings.use_hotkeys = ini->Read(_T("hotkeys"), 1l); - - settings.wrpResolution = (BYTE)Config_ReadInt ("wrpResolution", "Wrapper resolution", 0, TRUE, FALSE); - settings.wrpVRAM = (BYTE)Config_ReadInt ("wrpVRAM", "Wrapper VRAM", 0, TRUE, FALSE); - settings.wrpFBO = (BOOL)Config_ReadInt ("wrpFBO", "Wrapper FBO", 1, TRUE, TRUE); - settings.wrpAnisotropic = (BOOL)Config_ReadInt ("wrpAnisotropic", "Wrapper Anisotropic Filtering", 0, TRUE, TRUE); - -#ifndef _ENDUSER_RELEASE_ - settings.autodetect_ucode = (BOOL)Config_ReadInt ("autodetect_ucode", "Auto-detect microcode", 1); - settings.ucode = (wxUint32)Config_ReadInt ("ucode", "Force microcode", 2, TRUE, FALSE); - settings.wireframe = (BOOL)Config_ReadInt ("wireframe", "Wireframe display", 0); - settings.wfmode = (int)Config_ReadInt ("wfmode", "Wireframe mode: 0=Normal colors, 1=Vertex colors, 2=Red only", 1, TRUE, FALSE); - - settings.logging = (BOOL)Config_ReadInt ("logging", "Logging", 0); - settings.log_clear = (BOOL)Config_ReadInt ("log_clear", "", 0); - - settings.run_in_window = (BOOL)Config_ReadInt ("run_in_window", "", 0); - - settings.elogging = (BOOL)Config_ReadInt ("elogging", "", 0); - settings.filter_cache = (BOOL)Config_ReadInt ("filter_cache", "Filter cache", 0); - settings.unk_as_red = (BOOL)Config_ReadInt ("unk_as_red", "Display unknown combines as red", 0); - settings.log_unk = (BOOL)Config_ReadInt ("log_unk", "Log unknown combines", 0); - settings.unk_clear = (BOOL)Config_ReadInt ("unk_clear", "", 0); -#else - settings.autodetect_ucode = TRUE; - settings.ucode = 2; - settings.wireframe = FALSE; - settings.wfmode = 0; - settings.logging = FALSE; - settings.log_clear = FALSE; - settings.run_in_window = FALSE; - settings.elogging = FALSE; - settings.filter_cache = FALSE; - settings.unk_as_red = FALSE; - settings.log_unk = FALSE; - settings.unk_clear = FALSE; -#endif - -#ifdef TEXTURE_FILTER - - // settings.ghq_fltr range is 0 through 6 - // Filters:\nApply a filter to either smooth or sharpen textures.\nThere are 4 different smoothing filters and 2 different sharpening filters.\nThe higher the number, the stronger the effect,\ni.e. \"Smoothing filter 4\" will have a much more noticeable effect than \"Smoothing filter 1\".\nBe aware that performance may have an impact depending on the game and/or the PC.\n[Recommended: your preference] - // _("None"), - // _("Smooth filtering 1"), - // _("Smooth filtering 2"), - // _("Smooth filtering 3"), - // _("Smooth filtering 4"), - // _("Sharp filtering 1"), - // _("Sharp filtering 2") - -// settings.ghq_cmpr 0=S3TC and 1=FXT1 - -//settings.ghq_ent is ___ -// "Texture enhancement:\n7 different filters are selectable here, each one with a distinctive look.\nBe aware of possible performance impacts.\n\nIMPORTANT: 'Store' mode - saves textures in cache 'as is'. It can improve performance in games, which load many textures.\nDisable 'Ignore backgrounds' option for better result.\n\n[Recommended: your preference]" - - - - settings.ghq_fltr = Config_ReadInt ("ghq_fltr", "Texture Enhancement: Smooth/Sharpen Filters", 0, TRUE, FALSE); - settings.ghq_cmpr = Config_ReadInt ("ghq_cmpr", "Texture Compression: 0 for S3TC, 1 for FXT1", 0, TRUE, FALSE); - settings.ghq_enht = Config_ReadInt ("ghq_enht", "Texture Enhancement: More filters", 0, TRUE, FALSE); - settings.ghq_hirs = Config_ReadInt ("ghq_hirs", "Hi-res texture pack format (0 for none, 1 for Rice)", 0, TRUE, FALSE); - settings.ghq_enht_cmpr = Config_ReadInt ("ghq_enht_cmpr", "Compress texture cache with S3TC or FXT1", 0, TRUE, TRUE); - settings.ghq_enht_tile = Config_ReadInt ("ghq_enht_tile", "Tile textures (saves memory but could cause issues)", 0, TRUE, FALSE); - settings.ghq_enht_f16bpp = Config_ReadInt ("ghq_enht_f16bpp", "Force 16bpp textures (saves ram but lower quality)", 0, TRUE, TRUE); - settings.ghq_enht_gz = Config_ReadInt ("ghq_enht_gz", "Compress texture cache", 1, TRUE, TRUE); - settings.ghq_enht_nobg = Config_ReadInt ("ghq_enht_nobg", "Don't enhance textures for backgrounds", 0, TRUE, TRUE); - settings.ghq_hirs_cmpr = Config_ReadInt ("ghq_hirs_cmpr", "Enable S3TC and FXT1 compression", 0, TRUE, TRUE); - settings.ghq_hirs_tile = Config_ReadInt ("ghq_hirs_tile", "Tile hi-res textures (saves memory but could cause issues)", 0, TRUE, TRUE); - settings.ghq_hirs_f16bpp = Config_ReadInt ("ghq_hirs_f16bpp", "Force 16bpp hi-res textures (saves ram but lower quality)", 0, TRUE, TRUE); - settings.ghq_hirs_gz = Config_ReadInt ("ghq_hirs_gz", "Compress hi-res texture cache", 1, TRUE, TRUE); - settings.ghq_hirs_altcrc = Config_ReadInt ("ghq_hirs_altcrc", "Alternative CRC calculation -- emulates Rice bug", 1, TRUE, TRUE); - settings.ghq_cache_save = Config_ReadInt ("ghq_cache_save", "Save tex cache to disk", 1, TRUE, TRUE); - settings.ghq_cache_size = Config_ReadInt ("ghq_cache_size", "Texture Cache Size (MB)", 128, TRUE, FALSE); - settings.ghq_hirs_let_texartists_fly = Config_ReadInt ("ghq_hirs_let_texartists_fly", "Use full alpha channel -- could cause issues for some tex packs", 0, TRUE, TRUE); - settings.ghq_hirs_dump = Config_ReadInt ("ghq_hirs_dump", "Dump textures", 0, FALSE, TRUE); -#endif - //TODO-PORT: remove? - ConfigWrapper(); -} - -void ReadSpecialSettings (const char * name) -{ - // char buf [256]; - // sprintf(buf, "ReadSpecialSettings. Name: %s\n", name); - // LOG(buf); - settings.hacks = 0; - - //detect games which require special hacks - if (strstr(name, (const char *)"ZELDA") || strstr(name, (const char *)"MASK")) - settings.hacks |= hack_Zelda; - else if (strstr(name, (const char *)"ROADSTERS TROPHY")) - settings.hacks |= hack_Zelda; - else if (strstr(name, (const char *)"Diddy Kong Racing")) - settings.hacks |= hack_Diddy; - else if (strstr(name, (const char *)"Tonic Trouble")) - settings.hacks |= hack_Tonic; - else if (strstr(name, (const char *)"All") && strstr(name, (const char *)"Star") && strstr(name, (const char *)"Baseball")) - settings.hacks |= hack_ASB; - else if (strstr(name, (const char *)"Beetle") || strstr(name, (const char *)"BEETLE") || strstr(name, (const char *)"HSV")) - settings.hacks |= hack_BAR; - else if (strstr(name, (const char *)"I S S 64") || strstr(name, (const char *)"J WORLD SOCCER3") || strstr(name, (const char *)"PERFECT STRIKER") || strstr(name, (const char *)"RONALDINHO SOCCER")) - settings.hacks |= hack_ISS64; - else if (strstr(name, (const char *)"MARIOKART64")) - settings.hacks |= hack_MK64; - else if (strstr(name, (const char *)"NITRO64")) - settings.hacks |= hack_WCWnitro; - else if (strstr(name, (const char *)"CHOPPER_ATTACK") || strstr(name, (const char *)"WILD CHOPPERS")) - settings.hacks |= hack_Chopper; - else if (strstr(name, (const char *)"Resident Evil II") || strstr(name, (const char *)"BioHazard II")) - settings.hacks |= hack_RE2; - else if (strstr(name, (const char *)"YOSHI STORY")) - settings.hacks |= hack_Yoshi; - else if (strstr(name, (const char *)"F-Zero X") || strstr(name, (const char *)"F-ZERO X")) - settings.hacks |= hack_Fzero; - else if (strstr(name, (const char *)"PAPER MARIO") || strstr(name, (const char *)"MARIO STORY")) - settings.hacks |= hack_PMario; - else if (strstr(name, (const char *)"TOP GEAR RALLY 2")) - settings.hacks |= hack_TGR2; - else if (strstr(name, (const char *)"TOP GEAR RALLY")) - settings.hacks |= hack_TGR; - else if (strstr(name, (const char *)"Top Gear Hyper Bike")) - settings.hacks |= hack_Hyperbike; - else if (strstr(name, (const char *)"Killer Instinct Gold") || strstr(name, (const char *)"KILLER INSTINCT GOLD")) - settings.hacks |= hack_KI; - else if (strstr(name, (const char *)"Knockout Kings 2000")) - settings.hacks |= hack_Knockout; - else if (strstr(name, (const char *)"LEGORacers")) - settings.hacks |= hack_Lego; - else if (strstr(name, (const char *)"OgreBattle64")) - settings.hacks |= hack_Ogre64; - else if (strstr(name, (const char *)"Pilot Wings64")) - settings.hacks |= hack_Pilotwings; - else if (strstr(name, (const char *)"Supercross")) - settings.hacks |= hack_Supercross; - else if (strstr(name, (const char *)"STARCRAFT 64")) - settings.hacks |= hack_Starcraft; - else if (strstr(name, (const char *)"BANJO KAZOOIE 2") || strstr(name, (const char *)"BANJO TOOIE")) - settings.hacks |= hack_Banjo2; - else if (strstr(name, (const char *)"FIFA: RTWC 98") || strstr(name, (const char *)"RoadToWorldCup98")) - settings.hacks |= hack_Fifa98; - else if (strstr(name, (const char *)"Mega Man 64") || strstr(name, (const char *)"RockMan Dash")) - settings.hacks |= hack_Megaman; - else if (strstr(name, (const char *)"MISCHIEF MAKERS") || strstr(name, (const char *)"TROUBLE MAKERS")) - settings.hacks |= hack_Makers; - else if (strstr(name, (const char *)"GOLDENEYE")) - settings.hacks |= hack_GoldenEye; - else if (strstr(name, (const char *)"PUZZLE LEAGUE")) - settings.hacks |= hack_PPL; - - Ini * ini = Ini::OpenIni(); - if (!ini) - return; - ini->SetPath(name); - - ini->Read(_T("alt_tex_size"), &(settings.alt_tex_size)); - ini->Read(_T("use_sts1_only"), &(settings.use_sts1_only)); - ini->Read(_T("force_calc_sphere"), &(settings.force_calc_sphere)); - ini->Read(_T("correct_viewport"), &(settings.correct_viewport)); - ini->Read(_T("increase_texrect_edge"), &(settings.increase_texrect_edge)); - ini->Read(_T("decrease_fillrect_edge"), &(settings.decrease_fillrect_edge)); - if (ini->Read(_T("texture_correction"), -1) == 0) settings.texture_correction = 0; - else settings.texture_correction = 1; - if (ini->Read(_T("pal230"), -1) == 1) settings.pal230 = 1; - else settings.pal230 = 0; - ini->Read(_T("stipple_mode"), &(settings.stipple_mode)); - int stipple_pattern = ini->Read(_T("stipple_pattern"), -1); - if (stipple_pattern > 0) settings.stipple_pattern = (wxUint32)stipple_pattern; - ini->Read(_T("force_microcheck"), &(settings.force_microcheck)); - ini->Read(_T("force_quad3d"), &(settings.force_quad3d)); - ini->Read(_T("clip_zmin"), &(settings.clip_zmin)); - ini->Read(_T("clip_zmax"), &(settings.clip_zmax)); - ini->Read(_T("fast_crc"), &(settings.fast_crc)); - ini->Read(_T("adjust_aspect"), &(settings.adjust_aspect), 1); - ini->Read(_T("zmode_compare_less"), &(settings.zmode_compare_less)); - ini->Read(_T("old_style_adither"), &(settings.old_style_adither)); - ini->Read(_T("n64_z_scale"), &(settings.n64_z_scale)); - if (settings.n64_z_scale) - ZLUT_init(); - - //frame buffer - int optimize_texrect = ini->Read(_T("optimize_texrect"), -1); - int ignore_aux_copy = ini->Read(_T("ignore_aux_copy"), -1); - int hires_buf_clear = ini->Read(_T("hires_buf_clear"), -1); - int read_alpha = ini->Read(_T("fb_read_alpha"), -1); - int useless_is_useless = ini->Read(_T("useless_is_useless"), -1); - int fb_crc_mode = ini->Read(_T("fb_crc_mode"), -1); - - if (optimize_texrect > 0) settings.frame_buffer |= fb_optimize_texrect; - else if (optimize_texrect == 0) settings.frame_buffer &= ~fb_optimize_texrect; - if (ignore_aux_copy > 0) settings.frame_buffer |= fb_ignore_aux_copy; - else if (ignore_aux_copy == 0) settings.frame_buffer &= ~fb_ignore_aux_copy; - if (hires_buf_clear > 0) settings.frame_buffer |= fb_hwfbe_buf_clear; - else if (hires_buf_clear == 0) settings.frame_buffer &= ~fb_hwfbe_buf_clear; - if (read_alpha > 0) settings.frame_buffer |= fb_read_alpha; - else if (read_alpha == 0) settings.frame_buffer &= ~fb_read_alpha; - if (useless_is_useless > 0) settings.frame_buffer |= fb_useless_is_useless; - else settings.frame_buffer &= ~fb_useless_is_useless; - if (fb_crc_mode >= 0) settings.fb_crc_mode = (SETTINGS::FBCRCMODE)fb_crc_mode; - - // if (settings.custom_ini) - { - ini->Read(_T("filtering"), &(settings.filtering)); - ini->Read(_T("fog"), &(settings.fog)); - ini->Read(_T("buff_clear"), &(settings.buff_clear)); - ini->Read(_T("swapmode"), &(settings.swapmode)); - ini->Read(_T("aspect"), &(settings.aspectmode)); - ini->Read(_T("lodmode"), &(settings.lodmode)); - /* - TODO-port: fix resolutions - int resolution; - if (ini->Read(_T("resolution"), &resolution)) - { - settings.res_data = (wxUint32)resolution; - if (settings.res_data >= 0x18) settings.res_data = 12; - settings.scr_res_x = settings.res_x = resolutions[settings.res_data][0]; - settings.scr_res_y = settings.res_y = resolutions[settings.res_data][1]; - } - */ - - PackedScreenResolution tmpRes = Config_ReadScreenSettings(); - settings.res_data = tmpRes.resolution; - settings.scr_res_x = settings.res_x = tmpRes.width; - settings.scr_res_y = settings.res_y = tmpRes.height; - - //frame buffer - int smart_read = ini->Read(_T("fb_smart"), -1); - int hires = ini->Read(_T("fb_hires"), -1); - int read_always = ini->Read(_T("fb_read_always"), -1); - int read_back_to_screen = ini->Read(_T("read_back_to_screen"), -1); - int cpu_write_hack = ini->Read(_T("detect_cpu_write"), -1); - int get_fbinfo = ini->Read(_T("fb_get_info"), -1); - int depth_render = ini->Read(_T("fb_render"), -1); - - if (smart_read > 0) settings.frame_buffer |= fb_emulation; - else if (smart_read == 0) settings.frame_buffer &= ~fb_emulation; - if (hires > 0) settings.frame_buffer |= fb_hwfbe; - else if (hires == 0) settings.frame_buffer &= ~fb_hwfbe; - if (read_always > 0) settings.frame_buffer |= fb_ref; - else if (read_always == 0) settings.frame_buffer &= ~fb_ref; - if (read_back_to_screen == 1) settings.frame_buffer |= fb_read_back_to_screen; - else if (read_back_to_screen == 2) settings.frame_buffer |= fb_read_back_to_screen2; - else if (read_back_to_screen == 0) settings.frame_buffer &= ~(fb_read_back_to_screen|fb_read_back_to_screen2); - if (cpu_write_hack > 0) settings.frame_buffer |= fb_cpu_write_hack; - else if (cpu_write_hack == 0) settings.frame_buffer &= ~fb_cpu_write_hack; - if (get_fbinfo > 0) settings.frame_buffer |= fb_get_info; - else if (get_fbinfo == 0) settings.frame_buffer &= ~fb_get_info; - if (depth_render > 0) settings.frame_buffer |= fb_depth_render; - else if (depth_render == 0) settings.frame_buffer &= ~fb_depth_render; - settings.frame_buffer |= fb_motionblur; - } - settings.flame_corona = (settings.hacks & hack_Zelda) && !fb_depth_render_enabled; -} - -//TODO-PORT: more ini stuff -void WriteSettings (bool saveEmulationSettings) -{ -/* - wxConfigBase * ini = OpenIni(); - if (!ini || !ini->HasGroup(_T("/SETTINGS"))) - return; - ini->SetPath(_T("/SETTINGS")); - - ini->Write(_T("card_id"), settings.card_id); - ini->Write(_T("lang_id"), settings.lang_id); - ini->Write(_T("resolution"), (int)settings.res_data); - ini->Write(_T("ssformat"), settings.ssformat); - ini->Write(_T("vsync"), settings.vsync); - ini->Write(_T("show_fps"), settings.show_fps); - ini->Write(_T("clock"), settings.clock); - ini->Write(_T("clock_24_hr"), settings.clock_24_hr); - ini->Write(_T("advanced_options"), settings.advanced_options); - ini->Write(_T("texenh_options"), settings.texenh_options); - - ini->Write(_T("wrpResolution"), settings.wrpResolution); - ini->Write(_T("wrpVRAM"), settings.wrpVRAM); - ini->Write(_T("wrpFBO"), settings.wrpFBO); - ini->Write(_T("wrpAnisotropic"), settings.wrpAnisotropic); - -#ifndef _ENDUSER_RELEASE_ - ini->Write(_T("autodetect_ucode"), settings.autodetect_ucode); - ini->Write(_T("ucode"), (int)settings.ucode); - ini->Write(_T("wireframe"), settings.wireframe); - ini->Write(_T("wfmode"), settings.wfmode); - ini->Write(_T("logging"), settings.logging); - ini->Write(_T("log_clear"), settings.log_clear); - ini->Write(_T("run_in_window"), settings.run_in_window); - ini->Write(_T("elogging"), settings.elogging); - ini->Write(_T("filter_cache"), settings.filter_cache); - ini->Write(_T("unk_as_red"), settings.unk_as_red); - ini->Write(_T("log_unk"), settings.log_unk); - ini->Write(_T("unk_clear"), settings.unk_clear); -#endif //_ENDUSER_RELEASE_ - -#ifdef TEXTURE_FILTER - ini->Write(_T("ghq_fltr"), settings.ghq_fltr); - ini->Write(_T("ghq_cmpr"), settings.ghq_cmpr); - ini->Write(_T("ghq_enht"), settings.ghq_enht); - ini->Write(_T("ghq_hirs"), settings.ghq_hirs); - ini->Write(_T("ghq_enht_cmpr"), settings.ghq_enht_cmpr); - ini->Write(_T("ghq_enht_tile"), settings.ghq_enht_tile); - ini->Write(_T("ghq_enht_f16bpp"), settings.ghq_enht_f16bpp); - ini->Write(_T("ghq_enht_gz"), settings.ghq_enht_gz); - ini->Write(_T("ghq_enht_nobg"), settings.ghq_enht_nobg); - ini->Write(_T("ghq_hirs_cmpr"), settings.ghq_hirs_cmpr); - ini->Write(_T("ghq_hirs_tile"), settings.ghq_hirs_tile); - ini->Write(_T("ghq_hirs_f16bpp"), settings.ghq_hirs_f16bpp); - ini->Write(_T("ghq_hirs_gz"), settings.ghq_hirs_gz); - ini->Write(_T("ghq_hirs_altcrc"), settings.ghq_hirs_altcrc); - ini->Write(_T("ghq_cache_save"), settings.ghq_cache_save); - ini->Write(_T("ghq_cache_size"), settings.ghq_cache_size); - ini->Write(_T("ghq_hirs_let_texartists_fly"), settings.ghq_hirs_let_texartists_fly); - ini->Write(_T("ghq_hirs_dump"), settings.ghq_hirs_dump); -#endif - - if (saveEmulationSettings) - { - if (romopen) - { - wxString S = _T("/"); - ini->SetPath(S+rdp.RomName); - } - else - ini->SetPath(_T("/DEFAULT")); - ini->Write(_T("filtering"), settings.filtering); - ini->Write(_T("fog"), settings.fog); - ini->Write(_T("buff_clear"), settings.buff_clear); - ini->Write(_T("swapmode"), settings.swapmode); - ini->Write(_T("lodmode"), settings.lodmode); - ini->Write(_T("aspect"), settings.aspectmode); - - ini->Write(_T("fb_read_always"), settings.frame_buffer&fb_ref ? 1 : 0l); - ini->Write(_T("fb_smart"), settings.frame_buffer & fb_emulation ? 1 : 0l); - // ini->Write("motionblur", settings.frame_buffer & fb_motionblur ? 1 : 0); - ini->Write(_T("fb_hires"), settings.frame_buffer & fb_hwfbe ? 1 : 0l); - ini->Write(_T("fb_get_info"), settings.frame_buffer & fb_get_info ? 1 : 0l); - ini->Write(_T("fb_render"), settings.frame_buffer & fb_depth_render ? 1 : 0l); - ini->Write(_T("detect_cpu_write"), settings.frame_buffer & fb_cpu_write_hack ? 1 : 0l); - if (settings.frame_buffer & fb_read_back_to_screen) - ini->Write(_T("read_back_to_screen"), 1); - else if (settings.frame_buffer & fb_read_back_to_screen2) - ini->Write(_T("read_back_to_screen"), 2); - else - ini->Write(_T("read_back_to_screen"), 0l); - } - - wxFileOutputStream os(iniName); - ((wxFileConfig*)ini)->Save(os); -*/ -} - -GRTEXBUFFEREXT grTextureBufferExt = NULL; -GRTEXBUFFEREXT grTextureAuxBufferExt = NULL; -GRAUXBUFFEREXT grAuxBufferExt = NULL; -GRSTIPPLE grStippleModeExt = NULL; -GRSTIPPLE grStipplePatternExt = NULL; -FxBool (FX_CALL *grKeyPressed)(FxU32) = NULL; - -int GetTexAddrUMA(int tmu, int texsize) -{ - int addr = voodoo.tex_min_addr[0] + voodoo.tmem_ptr[0]; - voodoo.tmem_ptr[0] += texsize; - voodoo.tmem_ptr[1] = voodoo.tmem_ptr[0]; - return addr; -} -int GetTexAddrNonUMA(int tmu, int texsize) -{ - int addr = voodoo.tex_min_addr[tmu] + voodoo.tmem_ptr[tmu]; - voodoo.tmem_ptr[tmu] += texsize; - return addr; -} -GETTEXADDR GetTexAddr = GetTexAddrNonUMA; - -// guLoadTextures - used to load the cursor and font textures -void guLoadTextures () -{ - if (grTextureBufferExt) - { - int tbuf_size = 0; - if (voodoo.max_tex_size <= 256) - { - grTextureBufferExt( GR_TMU1, voodoo.tex_min_addr[GR_TMU1], GR_LOD_LOG2_256, GR_LOD_LOG2_256, - GR_ASPECT_LOG2_1x1, GR_TEXFMT_RGB_565, GR_MIPMAPLEVELMASK_BOTH ); - tbuf_size = 8 * grTexCalcMemRequired(GR_LOD_LOG2_256, GR_LOD_LOG2_256, - GR_ASPECT_LOG2_1x1, GR_TEXFMT_RGB_565); - } - else if (settings.scr_res_x <= 1024) - { - grTextureBufferExt( GR_TMU0, voodoo.tex_min_addr[GR_TMU0], GR_LOD_LOG2_1024, GR_LOD_LOG2_1024, - GR_ASPECT_LOG2_1x1, GR_TEXFMT_RGB_565, GR_MIPMAPLEVELMASK_BOTH ); - tbuf_size = grTexCalcMemRequired(GR_LOD_LOG2_1024, GR_LOD_LOG2_1024, - GR_ASPECT_LOG2_1x1, GR_TEXFMT_RGB_565); - grRenderBuffer( GR_BUFFER_TEXTUREBUFFER_EXT ); - grBufferClear (0, 0, 0xFFFF); - grRenderBuffer( GR_BUFFER_BACKBUFFER ); - } - else - { - grTextureBufferExt( GR_TMU0, voodoo.tex_min_addr[GR_TMU0], GR_LOD_LOG2_2048, GR_LOD_LOG2_2048, - GR_ASPECT_LOG2_1x1, GR_TEXFMT_RGB_565, GR_MIPMAPLEVELMASK_BOTH ); - tbuf_size = grTexCalcMemRequired(GR_LOD_LOG2_2048, GR_LOD_LOG2_2048, - GR_ASPECT_LOG2_1x1, GR_TEXFMT_RGB_565); - grRenderBuffer( GR_BUFFER_TEXTUREBUFFER_EXT ); - grBufferClear (0, 0, 0xFFFF); - grRenderBuffer( GR_BUFFER_BACKBUFFER ); - } - - rdp.texbufs[0].tmu = GR_TMU0; - rdp.texbufs[0].begin = voodoo.tex_min_addr[GR_TMU0]; - rdp.texbufs[0].end = rdp.texbufs[0].begin+tbuf_size; - rdp.texbufs[0].count = 0; - rdp.texbufs[0].clear_allowed = TRUE; - offset_font = tbuf_size; - if (voodoo.num_tmu > 1) - { - rdp.texbufs[1].tmu = GR_TMU1; - rdp.texbufs[1].begin = voodoo.tex_UMA ? rdp.texbufs[0].end : voodoo.tex_min_addr[GR_TMU1]; - rdp.texbufs[1].end = rdp.texbufs[1].begin+tbuf_size; - rdp.texbufs[1].count = 0; - rdp.texbufs[1].clear_allowed = TRUE; - if (voodoo.tex_UMA) - offset_font += tbuf_size; - else - offset_texbuf1 = tbuf_size; - } - } - else - offset_font = 0; - -#include "font.h" - wxUint32 *data = (wxUint32*)font; - wxUint32 cur; - - // ** Font texture ** - wxUint8 *tex8 = (wxUint8*)malloc(256*64); - - fontTex.smallLodLog2 = fontTex.largeLodLog2 = GR_LOD_LOG2_256; - fontTex.aspectRatioLog2 = GR_ASPECT_LOG2_4x1; - fontTex.format = GR_TEXFMT_ALPHA_8; - fontTex.data = tex8; - - // Decompression: [1-bit inverse alpha --> 8-bit alpha] - wxUint32 i,b; - for (i=0; i<0x200; i++) - { - // cur = ~*(data++), byteswapped -#ifdef __VISUALC__ - cur = _byteswap_ulong(~*(data++)); -#else - cur = ~*(data++); - cur = ((cur&0xFF)<<24)|(((cur>>8)&0xFF)<<16)|(((cur>>16)&0xFF)<<8)|((cur>>24)&0xFF); -#endif - - for (b=0x80000000; b!=0; b>>=1) - { - if (cur&b) *tex8 = 0xFF; - else *tex8 = 0x00; - tex8 ++; - } - } - - grTexDownloadMipMap (GR_TMU0, - voodoo.tex_min_addr[GR_TMU0] + offset_font, - GR_MIPMAPLEVELMASK_BOTH, - &fontTex); - - offset_cursor = offset_font + grTexTextureMemRequired (GR_MIPMAPLEVELMASK_BOTH, &fontTex); - - free (fontTex.data); - - // ** Cursor texture ** -#include "cursor.h" - data = (wxUint32*)cursor; - - wxUint16 *tex16 = (wxUint16*)malloc(32*32*2); - - cursorTex.smallLodLog2 = cursorTex.largeLodLog2 = GR_LOD_LOG2_32; - cursorTex.aspectRatioLog2 = GR_ASPECT_LOG2_1x1; - cursorTex.format = GR_TEXFMT_ARGB_1555; - cursorTex.data = tex16; - - // Conversion: [16-bit 1555 (swapped) --> 16-bit 1555] - for (i=0; i<0x200; i++) - { - cur = *(data++); - *(tex16++) = (wxUint16)(((cur&0x000000FF)<<8)|((cur&0x0000FF00)>>8)); - *(tex16++) = (wxUint16)(((cur&0x00FF0000)>>8)|((cur&0xFF000000)>>24)); - } - - grTexDownloadMipMap (GR_TMU0, - voodoo.tex_min_addr[GR_TMU0] + offset_cursor, - GR_MIPMAPLEVELMASK_BOTH, - &cursorTex); - - // Round to higher 16 - offset_textures = ((offset_cursor + grTexTextureMemRequired (GR_MIPMAPLEVELMASK_BOTH, &cursorTex)) - & 0xFFFFFFF0) + 16; - free (cursorTex.data); -} - -#ifdef TEXTURE_FILTER -void DisplayLoadProgress(const wchar_t *format, ...) -{ - va_list args; - wchar_t wbuf[INFO_BUF]; - char buf[INFO_BUF]; - - // process input - va_start(args, format); - vswprintf(wbuf, INFO_BUF, format, args); - va_end(args); - - // XXX: convert to multibyte - wcstombs(buf, wbuf, INFO_BUF); - - if (fullscreen) - { - float x; - set_message_combiner (); - output (382, 380, 1, "LOADING TEXTURES. PLEASE WAIT..."); - int len = min (strlen(buf)*8, 1024); - x = (1024-len)/2.0f; - output (x, 360, 1, buf); - grBufferSwap (0); - grColorMask (FXTRUE, FXTRUE); - grBufferClear (0, 0, 0xFFFF); - } -} -#endif - -int InitGfx () -{ -#ifdef TEXTURE_FILTER - wchar_t romname[256]; - wchar_t foldername[PATH_MAX + 64]; - wchar_t cachename[PATH_MAX + 64]; -#endif - if (fullscreen) - ReleaseGfx (); - - OPEN_RDP_LOG (); // doesn't matter if opens again; it will check for it - OPEN_RDP_E_LOG (); - VLOG ("InitGfx ()\n"); - - debugging = FALSE; - rdp_reset (); - - // Initialize Glide - grGlideInit (); - - // Select the Glide device - grSstSelect (settings.card_id); - - // Is mirroring allowed? - const char *extensions = grGetString (GR_EXTENSION); - - // Check which SST we are using and initialize stuff - // Hiroshi Morii - enum { - GR_SSTTYPE_VOODOO = 0, - GR_SSTTYPE_SST96 = 1, - GR_SSTTYPE_AT3D = 2, - GR_SSTTYPE_Voodoo2 = 3, - GR_SSTTYPE_Banshee = 4, - GR_SSTTYPE_Voodoo3 = 5, - GR_SSTTYPE_Voodoo4 = 6, - GR_SSTTYPE_Voodoo5 = 7 - }; - const char *hardware = grGetString(GR_HARDWARE); - unsigned int SST_type = GR_SSTTYPE_VOODOO; - if (strstr(hardware, "Rush")) { - SST_type = GR_SSTTYPE_SST96; - } else if (strstr(hardware, "Voodoo2")) { - SST_type = GR_SSTTYPE_Voodoo2; - } else if (strstr(hardware, "Voodoo Banshee")) { - SST_type = GR_SSTTYPE_Banshee; - } else if (strstr(hardware, "Voodoo3")) { - SST_type = GR_SSTTYPE_Voodoo3; - } else if (strstr(hardware, "Voodoo4")) { - SST_type = GR_SSTTYPE_Voodoo4; - } else if (strstr(hardware, "Voodoo5")) { - SST_type = GR_SSTTYPE_Voodoo5; - } - // 2Mb Texture boundary - voodoo.has_2mb_tex_boundary = (SST_type < GR_SSTTYPE_Banshee) && !evoodoo; - // use UMA if available - voodoo.tex_UMA = FALSE; - //* - if (strstr(extensions, " TEXUMA ")) { - // we get better texture cache hits with UMA on - grEnable(GR_TEXTURE_UMA_EXT); - voodoo.tex_UMA = TRUE; - LOG ("Using TEXUMA extension.\n"); - } - //*/ -//TODO-PORT: fullscreen stuff - wxUint32 res_data = settings.res_data; - char strWrapperFullScreenResolutionExt[] = "grWrapperFullScreenResolutionExt"; - if (ev_fullscreen) - { - GRWRAPPERFULLSCREENRESOLUTIONEXT grWrapperFullScreenResolutionExt = - (GRWRAPPERFULLSCREENRESOLUTIONEXT)grGetProcAddress(strWrapperFullScreenResolutionExt); - if (grWrapperFullScreenResolutionExt) { - wxUint32 _width, _height = 0; - settings.res_data = grWrapperFullScreenResolutionExt(&_width, &_height); - settings.scr_res_x = settings.res_x = _width; - settings.scr_res_y = settings.res_y = _height; - } - res_data = settings.res_data; - } - else if (evoodoo) - { - GRWRAPPERFULLSCREENRESOLUTIONEXT grWrapperFullScreenResolutionExt = - (GRWRAPPERFULLSCREENRESOLUTIONEXT)grGetProcAddress(strWrapperFullScreenResolutionExt); - if (grWrapperFullScreenResolutionExt != NULL) - { -/* - TODO-port: fix resolutions - settings.res_data = settings.res_data_org; - settings.scr_res_x = settings.res_x = resolutions[settings.res_data][0]; - settings.scr_res_y = settings.res_y = resolutions[settings.res_data][1]; -*/ - } - res_data = settings.res_data | 0x80000000; - } - - gfx_context = 0; - - // Select the window - - if (fb_hwfbe_enabled) - { - char strSstWinOpenExt[] ="grSstWinOpenExt"; - GRWINOPENEXT grSstWinOpenExt = (GRWINOPENEXT)grGetProcAddress(strSstWinOpenExt); - if (grSstWinOpenExt) - gfx_context = grSstWinOpenExt ((FxU32)NULL, - res_data, - GR_REFRESH_60Hz, - GR_COLORFORMAT_RGBA, - GR_ORIGIN_UPPER_LEFT, - fb_emulation_enabled?GR_PIXFMT_RGB_565:GR_PIXFMT_ARGB_8888, //32b color is not compatible with fb emulation - 2, // Double-buffering - 1); // 1 auxillary buffer - } - if (!gfx_context) - gfx_context = grSstWinOpen ((FxU32)NULL, - res_data, - GR_REFRESH_60Hz, - GR_COLORFORMAT_RGBA, - GR_ORIGIN_UPPER_LEFT, - 2, // Double-buffering - 1); // 1 auxillary buffer - - if (!gfx_context) - { - ERRLOG("Error setting display mode"); - // grSstWinClose (gfx_context); - grGlideShutdown (); - return FALSE; - } - - fullscreen = TRUE; - to_fullscreen = FALSE; - - - // get the # of TMUs available - grGet (GR_NUM_TMU, 4, (FxI32*)&voodoo.num_tmu); - // get maximal texture size - grGet (GR_MAX_TEXTURE_SIZE, 4, (FxI32*)&voodoo.max_tex_size); - voodoo.sup_large_tex = (voodoo.max_tex_size > 256 && !(settings.hacks & hack_PPL)); - - //num_tmu = 1; - if (voodoo.tex_UMA) - { - GetTexAddr = GetTexAddrUMA; - voodoo.tex_min_addr[0] = voodoo.tex_min_addr[1] = grTexMinAddress(GR_TMU0); - voodoo.tex_max_addr[0] = voodoo.tex_max_addr[1] = grTexMaxAddress(GR_TMU0); - } - else - { - GetTexAddr = GetTexAddrNonUMA; - voodoo.tex_min_addr[0] = grTexMinAddress(GR_TMU0); - voodoo.tex_min_addr[1] = grTexMinAddress(GR_TMU1); - voodoo.tex_max_addr[0] = grTexMaxAddress(GR_TMU0); - voodoo.tex_max_addr[1] = grTexMaxAddress(GR_TMU1); - } - - if (strstr (extensions, "TEXMIRROR") && !(settings.hacks&hack_Zelda)) //zelda's trees suffer from hardware mirroring - voodoo.sup_mirroring = 1; - else - voodoo.sup_mirroring = 0; - - if (strstr (extensions, "TEXFMT")) //VSA100 texture format extension - voodoo.sup_32bit_tex = TRUE; - else - voodoo.sup_32bit_tex = FALSE; - - voodoo.gamma_correction = 0; - if (strstr(extensions, "GETGAMMA")) - grGet(GR_GAMMA_TABLE_ENTRIES, sizeof(voodoo.gamma_table_size), &voodoo.gamma_table_size); - - if (fb_hwfbe_enabled) - { - if (char * extstr = (char*)strstr(extensions, "TEXTUREBUFFER")) - { - if (!strncmp(extstr, "TEXTUREBUFFER", 13)) - { - char strTextureBufferExt[] = "grTextureBufferExt"; - grTextureBufferExt = (GRTEXBUFFEREXT) grGetProcAddress(strTextureBufferExt); - char strTextureAuxBufferExt[] = "grTextureAuxBufferExt"; - grTextureAuxBufferExt = (GRTEXBUFFEREXT) grGetProcAddress(strTextureAuxBufferExt); - char strAuxBufferExt[] = "grAuxBufferExt"; - grAuxBufferExt = (GRAUXBUFFEREXT) grGetProcAddress(strAuxBufferExt); - } - } - else - settings.frame_buffer &= ~fb_hwfbe; - } - else - grTextureBufferExt = 0; - - grStippleModeExt = (GRSTIPPLE)grStippleMode; - grStipplePatternExt = (GRSTIPPLE)grStipplePattern; - - if (grStipplePatternExt) - grStipplePatternExt(settings.stipple_pattern); - -// char strKeyPressedExt[] = "grKeyPressedExt"; -// grKeyPressed = (FxBool (FX_CALL *)(FxU32))grGetProcAddress (strKeyPressedExt); - - InitCombine(); - -#ifdef SIMULATE_VOODOO1 - voodoo.num_tmu = 1; - voodoo.sup_mirroring = 0; -#endif - -#ifdef SIMULATE_BANSHEE - voodoo.num_tmu = 1; - voodoo.sup_mirroring = 1; -#endif - - grCoordinateSpace (GR_WINDOW_COORDS); - grVertexLayout (GR_PARAM_XY, offsetof(VERTEX,x), GR_PARAM_ENABLE); - grVertexLayout (GR_PARAM_Q, offsetof(VERTEX,q), GR_PARAM_ENABLE); - grVertexLayout (GR_PARAM_Z, offsetof(VERTEX,z), GR_PARAM_ENABLE); - grVertexLayout (GR_PARAM_ST0, offsetof(VERTEX,coord[0]), GR_PARAM_ENABLE); - grVertexLayout (GR_PARAM_ST1, offsetof(VERTEX,coord[2]), GR_PARAM_ENABLE); - grVertexLayout (GR_PARAM_PARGB, offsetof(VERTEX,b), GR_PARAM_ENABLE); - - grCullMode(GR_CULL_NEGATIVE); - - if (settings.fog) //"FOGCOORD" extension - { - if (strstr (extensions, "FOGCOORD")) - { - GrFog_t fog_t[64]; - guFogGenerateLinear (fog_t, 0.0f, 255.0f);//(float)rdp.fog_multiplier + (float)rdp.fog_offset);//256.0f); - - for (int i = 63; i > 0; i--) - { - if (fog_t[i] - fog_t[i-1] > 63) - { - fog_t[i-1] = fog_t[i] - 63; - } - } - fog_t[0] = 0; - // for (int f = 0; f < 64; f++) - // { - // FRDP("fog[%d]=%d->%f\n", f, fog_t[f], guFogTableIndexToW(f)); - // } - grFogTable (fog_t); - grVertexLayout (GR_PARAM_FOG_EXT, offsetof(VERTEX,f), GR_PARAM_ENABLE); - } - else //not supported - settings.fog = FALSE; - } - - grDepthBufferMode (GR_DEPTHBUFFER_ZBUFFER); - grDepthBufferFunction(GR_CMP_LESS); - grDepthMask(FXTRUE); - - settings.res_x = settings.scr_res_x; - settings.res_y = settings.scr_res_y; - ChangeSize (); - - guLoadTextures (); - ClearCache (); - - grCullMode (GR_CULL_DISABLE); - grDepthBufferMode (GR_DEPTHBUFFER_ZBUFFER); - grDepthBufferFunction (GR_CMP_ALWAYS); - grRenderBuffer(GR_BUFFER_BACKBUFFER); - grColorMask (FXTRUE, FXTRUE); - grDepthMask (FXTRUE); - grBufferClear (0, 0, 0xFFFF); - grBufferSwap (0); - grBufferClear (0, 0, 0xFFFF); - grDepthMask (FXFALSE); - grTexFilterMode (0, GR_TEXTUREFILTER_BILINEAR, GR_TEXTUREFILTER_BILINEAR); - grTexFilterMode (1, GR_TEXTUREFILTER_BILINEAR, GR_TEXTUREFILTER_BILINEAR); - grTexClampMode (0, GR_TEXTURECLAMP_CLAMP, GR_TEXTURECLAMP_CLAMP); - grTexClampMode (1, GR_TEXTURECLAMP_CLAMP, GR_TEXTURECLAMP_CLAMP); - grClipWindow (0, 0, settings.scr_res_x, settings.scr_res_y); - rdp.update |= UPDATE_SCISSOR | UPDATE_COMBINE | UPDATE_ZBUF_ENABLED | UPDATE_CULL_MODE; - -#ifdef TEXTURE_FILTER // Hiroshi Morii - if (!settings.ghq_use) - { - settings.ghq_use = settings.ghq_fltr || settings.ghq_enht /*|| settings.ghq_cmpr*/ || settings.ghq_hirs; - if (settings.ghq_use) - { - /* Plugin path */ - int options = texfltr[settings.ghq_fltr]|texenht[settings.ghq_enht]|texcmpr[settings.ghq_cmpr]|texhirs[settings.ghq_hirs]; - if (settings.ghq_enht_cmpr) - options |= COMPRESS_TEX; - if (settings.ghq_hirs_cmpr) - options |= COMPRESS_HIRESTEX; - // if (settings.ghq_enht_tile) - // options |= TILE_TEX; - if (settings.ghq_hirs_tile) - options |= TILE_HIRESTEX; - if (settings.ghq_enht_f16bpp) - options |= FORCE16BPP_TEX; - if (settings.ghq_hirs_f16bpp) - options |= FORCE16BPP_HIRESTEX; - if (settings.ghq_enht_gz) - options |= GZ_TEXCACHE; - if (settings.ghq_hirs_gz) - options |= GZ_HIRESTEXCACHE; - if (settings.ghq_cache_save) - options |= (DUMP_TEXCACHE|DUMP_HIRESTEXCACHE); - if (settings.ghq_hirs_let_texartists_fly) - options |= LET_TEXARTISTS_FLY; - if (settings.ghq_hirs_dump) - options |= DUMP_TEX; - - ghq_dmptex_toggle_key = 0; - - swprintf(romname, sizeof(romname) / sizeof(*romname), L"%hs", rdp.RomName); - swprintf(foldername, sizeof(foldername) / sizeof(*foldername), L"%hs", ConfigGetUserDataPath()); - swprintf(cachename, sizeof(cachename) / sizeof(*cachename), L"%hs", ConfigGetUserCachePath()); - - settings.ghq_use = (int)ext_ghq_init(voodoo.max_tex_size, // max texture width supported by hardware - voodoo.max_tex_size, // max texture height supported by hardware - voodoo.sup_32bit_tex?32:16, // max texture bpp supported by hardware - options, - settings.ghq_cache_size * 1024*1024, // cache texture to system memory - foldername, - cachename, - romname, // name of ROM. must be no longer than 256 characters - DisplayLoadProgress); - } - } - if (settings.ghq_use && strstr (extensions, "TEXMIRROR")) - voodoo.sup_mirroring = 1; -#endif - - return TRUE; -} - -void ReleaseGfx () -{ - VLOG("ReleaseGfx ()\n"); - - // Restore gamma settings - if (voodoo.gamma_correction) - { - if (voodoo.gamma_table_r) - grLoadGammaTable(voodoo.gamma_table_size, voodoo.gamma_table_r, voodoo.gamma_table_g, voodoo.gamma_table_b); - else - guGammaCorrectionRGB(1.3f, 1.3f, 1.3f); //1.3f is default 3dfx gamma for everything but desktop - voodoo.gamma_correction = 0; - } - - // Release graphics - grSstWinClose (gfx_context); - - // Shutdown glide - grGlideShutdown(); - - fullscreen = FALSE; - rdp.window_changed = TRUE; -} - -// new API code begins here! - -#ifdef __cplusplus -extern "C" { -#endif - -EXPORT void CALL ReadScreen2(void *dest, int *width, int *height, int front) -{ - VLOG("CALL ReadScreen2 ()\n"); - *width = settings.res_x; - *height = settings.res_y; - if (dest) - { - BYTE * line = (BYTE*)dest; - if (!fullscreen) - { - for (wxUint32 y=0; y - if (settings.ghq_use) - { - ext_ghq_shutdown(); - settings.ghq_use = 0; - } -#endif - if (fullscreen) - ReleaseGfx (); - ZLUT_release(); - ClearCache (); - delete[] voodoo.gamma_table_r; - voodoo.gamma_table_r = 0; - delete[] voodoo.gamma_table_g; - voodoo.gamma_table_g = 0; - delete[] voodoo.gamma_table_b; - voodoo.gamma_table_b = 0; -} - -/****************************************************************** -Function: DllTest -Purpose: This function is optional function that is provided -to allow the user to test the dll -input: a handle to the window that calls this function -output: none -*******************************************************************/ -void CALL DllTest ( HWND hParent ) -{ -} - -/****************************************************************** -Function: DrawScreen -Purpose: This function is called when the emulator receives a -WM_PAINT message. This allows the gfx to fit in when -it is being used in the desktop. -input: none -output: none -*******************************************************************/ -void CALL DrawScreen (void) -{ - VLOG ("DrawScreen ()\n"); -} - -/****************************************************************** -Function: GetDllInfo -Purpose: This function allows the emulator to gather information -about the dll by filling in the PluginInfo structure. -input: a pointer to a PLUGIN_INFO stucture that needs to be -filled by the function. (see def above) -output: none -*******************************************************************/ -void CALL GetDllInfo ( PLUGIN_INFO * PluginInfo ) -{ - VLOG ("GetDllInfo ()\n"); - PluginInfo->Version = 0x0103; // Set to 0x0103 - PluginInfo->Type = PLUGIN_TYPE_GFX; // Set to PLUGIN_TYPE_GFX - sprintf (PluginInfo->Name, "Glide64mk2 " G64_VERSION RELTIME); // Name of the DLL - - // If DLL supports memory these memory options then set them to TRUE or FALSE - // if it does not support it - PluginInfo->NormalMemory = TRUE; // a normal wxUint8 array - PluginInfo->MemoryBswaped = TRUE; // a normal wxUint8 array where the memory has been pre - // bswap on a dword (32 bits) boundry -} - -#ifndef WIN32 -BOOL WINAPI QueryPerformanceCounter(PLARGE_INTEGER counter) -{ - struct timeval tv; - - /* generic routine */ - gettimeofday( &tv, NULL ); - counter->QuadPart = (LONGLONG)tv.tv_usec + (LONGLONG)tv.tv_sec * 1000000; - return TRUE; -} - -BOOL WINAPI QueryPerformanceFrequency(PLARGE_INTEGER frequency) -{ - frequency->s.LowPart= 1000000; - frequency->s.HighPart= 0; - return TRUE; -} -#endif - -/****************************************************************** -Function: InitiateGFX -Purpose: This function is called when the DLL is started to give -information from the emulator that the n64 graphics -uses. This is not called from the emulation thread. -Input: Gfx_Info is passed to this function which is defined -above. -Output: TRUE on success -FALSE on failure to initialise - -** note on interrupts **: -To generate an interrupt set the appropriate bit in MI_INTR_REG -and then call the function CheckInterrupts to tell the emulator -that there is a waiting interrupt. -*******************************************************************/ - -EXPORT int CALL InitiateGFX (GFX_INFO Gfx_Info) -{ - VLOG ("InitiateGFX (*)\n"); - voodoo.num_tmu = 2; - - // Assume scale of 1 for debug purposes - rdp.scale_x = 1.0f; - rdp.scale_y = 1.0f; - - memset (&settings, 0, sizeof(SETTINGS)); - ReadSettings (); - char name[21] = "DEFAULT"; - ReadSpecialSettings (name); - settings.res_data_org = settings.res_data; -#ifdef FPS - QueryPerformanceFrequency (&perf_freq); - QueryPerformanceCounter (&fps_last); -#endif - - debug_init (); // Initialize debugger - - gfx = Gfx_Info; - -#ifdef WINPROC_OVERRIDE - // [H.Morii] inject our own winproc so that "alt-enter to fullscreen" - // message is shown when the emulator window is activated. - WNDPROC curWndProc = (WNDPROC)GetWindowLong(gfx.hWnd, GWL_WNDPROC); - if (curWndProc && curWndProc != (WNDPROC)WndProc) { - oldWndProc = (WNDPROC)SetWindowLong (gfx.hWnd, GWL_WNDPROC, (long)WndProc); - } -#endif - - util_init (); - math_init (); - TexCacheInit (); - CRC_BuildTable(); - CountCombine(); - if (fb_depth_render_enabled) - ZLUT_init(); - - char strConfigWrapperExt[] = "grConfigWrapperExt"; - GRCONFIGWRAPPEREXT grConfigWrapperExt = (GRCONFIGWRAPPEREXT)grGetProcAddress(strConfigWrapperExt); - if (grConfigWrapperExt) - grConfigWrapperExt(settings.wrpResolution, settings.wrpVRAM * 1024 * 1024, settings.wrpFBO, settings.wrpAnisotropic); - - grGlideInit (); - grSstSelect (0); - const char *extensions = grGetString (GR_EXTENSION); - grGlideShutdown (); - if (strstr (extensions, "EVOODOO")) - { - evoodoo = 1; - voodoo.has_2mb_tex_boundary = 0; - } - else { - evoodoo = 0; - voodoo.has_2mb_tex_boundary = 1; - } - - return TRUE; -} - -/****************************************************************** -Function: MoveScreen -Purpose: This function is called in response to the emulator -receiving a WM_MOVE passing the xpos and ypos passed -from that message. -input: xpos - the x-coordinate of the upper-left corner of the -client area of the window. -ypos - y-coordinate of the upper-left corner of the -client area of the window. -output: none -*******************************************************************/ -EXPORT void CALL MoveScreen (int xpos, int ypos) -{ - rdp.window_changed = TRUE; -} - -/****************************************************************** -Function: ResizeVideoOutput -Purpose: This function is called to force us to resize our output OpenGL window. - This is currently unsupported, and should never be called because we do - not pass the RESIZABLE flag to VidExt_SetVideoMode when initializing. -input: new width and height -output: none -*******************************************************************/ -EXPORT void CALL ResizeVideoOutput(int Width, int Height) -{ -} - -/****************************************************************** -Function: RomClosed -Purpose: This function is called when a rom is closed. -input: none -output: none -*******************************************************************/ -EXPORT void CALL RomClosed (void) -{ - VLOG ("RomClosed ()\n"); - - CLOSE_RDP_LOG (); - CLOSE_RDP_E_LOG (); - rdp.window_changed = TRUE; - romopen = FALSE; - if (fullscreen && evoodoo) - ReleaseGfx (); -} - -static void CheckDRAMSize() -{ - wxUint32 test; - GLIDE64_TRY - { - test = gfx.RDRAM[0x007FFFFF] + 1; - } - GLIDE64_CATCH - { - test = 0; - } - if (test) - BMASK = 0x7FFFFF; - else - BMASK = WMASK; -#ifdef LOGGING - sprintf (out_buf, "Detected RDRAM size: %08lx\n", BMASK); - LOG (out_buf); -#endif -} - -/****************************************************************** -Function: RomOpen -Purpose: This function is called when a rom is open. (from the -emulation thread) -input: none -output: none -*******************************************************************/ -EXPORT int CALL RomOpen (void) -{ - VLOG ("RomOpen ()\n"); - no_dlist = true; - romopen = TRUE; - ucode_error_report = TRUE; // allowed to report ucode errors - rdp_reset (); - - // Get the country code & translate to NTSC(0) or PAL(1) - wxUint16 code = ((wxUint16*)gfx.HEADER)[0x1F^1]; - - if (code == 0x4400) region = 1; // Germany (PAL) - if (code == 0x4500) region = 0; // USA (NTSC) - if (code == 0x4A00) region = 0; // Japan (NTSC) - if (code == 0x5000) region = 1; // Europe (PAL) - if (code == 0x5500) region = 0; // Australia (NTSC) - - char name[21] = "DEFAULT"; - ReadSpecialSettings (name); - - // get the name of the ROM - for (int i=0; i<20; i++) - name[i] = gfx.HEADER[(32+i)^3]; - name[20] = 0; - - // remove all trailing spaces - while (name[strlen(name)-1] == ' ') - name[strlen(name)-1] = 0; - - strncpy(rdp.RomName, name, sizeof(name)); - ReadSpecialSettings (name); - ClearCache (); - - CheckDRAMSize(); - - OPEN_RDP_LOG (); - OPEN_RDP_E_LOG (); - - - // ** EVOODOO EXTENSIONS ** - if (!fullscreen) - { - grGlideInit (); - grSstSelect (0); - } - const char *extensions = grGetString (GR_EXTENSION); - if (!fullscreen) - { - grGlideShutdown (); - - if (strstr (extensions, "EVOODOO")) - evoodoo = 1; - else - evoodoo = 0; - - if (evoodoo) - InitGfx (); - } - - if (strstr (extensions, "ROMNAME")) - { - char strSetRomName[] = "grSetRomName"; - void (FX_CALL *grSetRomName)(char*); - grSetRomName = (void (FX_CALL *)(char*))grGetProcAddress (strSetRomName); - grSetRomName (name); - } - // ** - return true; -} - -/****************************************************************** -Function: ShowCFB -Purpose: Useally once Dlists are started being displayed, cfb is -ignored. This function tells the dll to start displaying -them again. -input: none -output: none -*******************************************************************/ -bool no_dlist = true; -EXPORT void CALL ShowCFB (void) -{ - no_dlist = true; - VLOG ("ShowCFB ()\n"); -} - -EXPORT void CALL SetRenderingCallback(void (*callback)(int)) -{ - VLOG("CALL SetRenderingCallback (*)\n"); - renderCallback = callback; -} - -void drawViRegBG() -{ - LRDP("drawViRegBG\n"); - const wxUint32 VIwidth = *gfx.VI_WIDTH_REG; - FB_TO_SCREEN_INFO fb_info; - fb_info.width = VIwidth; - fb_info.height = (wxUint32)rdp.vi_height; - if (fb_info.height == 0) - { - LRDP("Image height = 0 - skipping\n"); - return; - } - fb_info.ul_x = 0; - - fb_info.lr_x = VIwidth - 1; - // fb_info.lr_x = (wxUint32)rdp.vi_width - 1; - fb_info.ul_y = 0; - fb_info.lr_y = fb_info.height - 1; - fb_info.opaque = 1; - fb_info.addr = *gfx.VI_ORIGIN_REG; - fb_info.size = *gfx.VI_STATUS_REG & 3; - rdp.last_bg = fb_info.addr; - - bool drawn = DrawFrameBufferToScreen(fb_info); - if (settings.hacks&hack_Lego && drawn) - { - rdp.updatescreen = 1; - newSwapBuffers (); - DrawFrameBufferToScreen(fb_info); - } -} - -} - -void drawNoFullscreenMessage(); - -void DrawFrameBuffer () -{ - if (!fullscreen) - { - drawNoFullscreenMessage(); - } - if (to_fullscreen) - GoToFullScreen(); - - if (fullscreen) - { - grDepthMask (FXTRUE); - grColorMask (FXTRUE, FXTRUE); - grBufferClear (0, 0, 0xFFFF); - drawViRegBG(); - } -} - -extern "C" { -/****************************************************************** -Function: UpdateScreen -Purpose: This function is called in response to a vsync of the -screen were the VI bit in MI_INTR_REG has already been -set -input: none -output: none -*******************************************************************/ -wxUint32 update_screen_count = 0; -EXPORT void CALL UpdateScreen (void) -{ -#ifdef LOG_KEY - if (CheckKeyPressed(G64_VK_SPACE, 0x0001)) - { - LOG ("KEY!!!\n"); - } -#endif - char out_buf[128]; - sprintf (out_buf, "UpdateScreen (). Origin: %08x, Old origin: %08x, width: %d\n", *gfx.VI_ORIGIN_REG, rdp.vi_org_reg, *gfx.VI_WIDTH_REG); - VLOG (out_buf); - LRDP(out_buf); - - wxUint32 width = (*gfx.VI_WIDTH_REG) << 1; - if (fullscreen && (*gfx.VI_ORIGIN_REG > width)) - update_screen_count++; -//TODO-PORT: wx times -#ifdef FPS - // vertical interrupt has occurred, increment counter - vi_count ++; - - // Check frames per second - LARGE_INTEGER difference; - QueryPerformanceCounter (&fps_next); - difference.QuadPart = fps_next.QuadPart - fps_last.QuadPart; - float diff_secs = (float)((double)difference.QuadPart / (double)perf_freq.QuadPart); - if (diff_secs > 0.5f) - { - fps = (float)(fps_count / diff_secs); - vi = (float)(vi_count / diff_secs); - ntsc_percent = vi / 0.6f; - pal_percent = vi / 0.5f; - fps_last = fps_next; - fps_count = 0; - vi_count = 0; - } -#endif - //* - wxUint32 limit = (settings.hacks&hack_Lego) ? 15 : 30; - if ((settings.frame_buffer&fb_cpu_write_hack) && (update_screen_count > limit) && (rdp.last_bg == 0)) - { - LRDP("DirectCPUWrite hack!\n"); - update_screen_count = 0; - no_dlist = true; - ClearCache (); - UpdateScreen(); - return; - } - //*/ - //* - if( no_dlist ) - { - if( *gfx.VI_ORIGIN_REG > width ) - { - ChangeSize (); - LRDP("ChangeSize done\n"); - DrawFrameBuffer(); - LRDP("DrawFrameBuffer done\n"); - rdp.updatescreen = 1; - newSwapBuffers (); - } - return; - } - //*/ - if (settings.swapmode == 0) - newSwapBuffers (); -} - -static void DrawWholeFrameBufferToScreen() -{ - static wxUint32 toScreenCI = 0; - if (rdp.ci_width < 200) - return; - if (rdp.cimg == toScreenCI) - return; - toScreenCI = rdp.cimg; - FB_TO_SCREEN_INFO fb_info; - fb_info.addr = rdp.cimg; - fb_info.size = rdp.ci_size; - fb_info.width = rdp.ci_width; - fb_info.height = rdp.ci_height; - if (fb_info.height == 0) - return; - fb_info.ul_x = 0; - fb_info.lr_x = rdp.ci_width-1; - fb_info.ul_y = 0; - fb_info.lr_y = rdp.ci_height-1; - fb_info.opaque = 0; - DrawFrameBufferToScreen(fb_info); - if (!(settings.frame_buffer & fb_ref)) - memset(gfx.RDRAM+rdp.cimg, 0, (rdp.ci_width*rdp.ci_height)<>1); -} - -static void GetGammaTable() -{ - char strGetGammaTableExt[] = "grGetGammaTableExt"; - void (FX_CALL *grGetGammaTableExt)(FxU32, FxU32*, FxU32*, FxU32*) = - (void (FX_CALL *)(FxU32, FxU32*, FxU32*, FxU32*))grGetProcAddress(strGetGammaTableExt); - if (grGetGammaTableExt) - { - voodoo.gamma_table_r = new FxU32[voodoo.gamma_table_size]; - voodoo.gamma_table_g = new FxU32[voodoo.gamma_table_size]; - voodoo.gamma_table_b = new FxU32[voodoo.gamma_table_size]; - grGetGammaTableExt(voodoo.gamma_table_size, voodoo.gamma_table_r, voodoo.gamma_table_g, voodoo.gamma_table_b); - } -} - -} -wxUint32 curframe = 0; -void newSwapBuffers() -{ - if (!rdp.updatescreen) - return; - - rdp.updatescreen = 0; - - LRDP("swapped\n"); - - // Allow access to the whole screen - if (fullscreen) - { - rdp.update |= UPDATE_SCISSOR | UPDATE_COMBINE | UPDATE_ZBUF_ENABLED | UPDATE_CULL_MODE; - grClipWindow (0, 0, settings.scr_res_x, settings.scr_res_y); - grDepthBufferFunction (GR_CMP_ALWAYS); - grDepthMask (FXFALSE); - grCullMode (GR_CULL_DISABLE); - - if ((settings.show_fps & 0xF) || settings.clock) - set_message_combiner (); -#ifdef FPS - float y = (float)settings.res_y; - if (settings.show_fps & 0x0F) - { - if (settings.show_fps & 4) - { - if (region) // PAL - output (0, y, 0, "%d%% ", (int)pal_percent); - else - output (0, y, 0, "%d%% ", (int)ntsc_percent); - y -= 16; - } - if (settings.show_fps & 2) - { - output (0, y, 0, "VI/s: %.02f ", vi); - y -= 16; - } - if (settings.show_fps & 1) - output (0, y, 0, "FPS: %.02f ", fps); - } -#endif - - if (settings.clock) - { - if (settings.clock_24_hr) - { - time_t ltime; - time (<ime); - tm *cur_time = localtime (<ime); - - sprintf (out_buf, "%.2d:%.2d:%.2d", cur_time->tm_hour, cur_time->tm_min, cur_time->tm_sec); - } - else - { - char ampm[] = "AM"; - time_t ltime; - - time (<ime); - tm *cur_time = localtime (<ime); - - if (cur_time->tm_hour >= 12) - { - strcpy (ampm, "PM"); - if (cur_time->tm_hour != 12) - cur_time->tm_hour -= 12; - } - if (cur_time->tm_hour == 0) - cur_time->tm_hour = 12; - - if (cur_time->tm_hour >= 10) - sprintf (out_buf, "%.5s %s", asctime(cur_time) + 11, ampm); - else - sprintf (out_buf, " %.4s %s", asctime(cur_time) + 12, ampm); - } - output ((float)(settings.res_x - 68), y, 0, out_buf, 0); - } - //hotkeys - if (CheckKeyPressed(G64_VK_BACK, 0x0001)) - { - hotkey_info.hk_filtering = 100; - if (settings.filtering < 2) - settings.filtering++; - else - settings.filtering = 0; - } - if ((abs((int)(frame_count - curframe)) > 3 ) && CheckKeyPressed(G64_VK_ALT, 0x8000)) //alt + - { - if (CheckKeyPressed(G64_VK_B, 0x8000)) //b - { - hotkey_info.hk_motionblur = 100; - hotkey_info.hk_ref = 0; - curframe = frame_count; - settings.frame_buffer ^= fb_motionblur; - } - else if (CheckKeyPressed(G64_VK_V, 0x8000)) //v - { - hotkey_info.hk_ref = 100; - hotkey_info.hk_motionblur = 0; - curframe = frame_count; - settings.frame_buffer ^= fb_ref; - } - } - if (settings.buff_clear && (hotkey_info.hk_ref || hotkey_info.hk_motionblur || hotkey_info.hk_filtering)) - { - set_message_combiner (); - char buf[256]; - buf[0] = 0; - char * message = 0; - if (hotkey_info.hk_ref) - { - if (settings.frame_buffer & fb_ref) - message = strcat(buf, "FB READ ALWAYS: ON"); - else - message = strcat(buf, "FB READ ALWAYS: OFF"); - hotkey_info.hk_ref--; - } - if (hotkey_info.hk_motionblur) - { - if (settings.frame_buffer & fb_motionblur) - message = strcat(buf, " MOTION BLUR: ON"); - else - message = strcat(buf, " MOTION BLUR: OFF"); - hotkey_info.hk_motionblur--; - } - if (hotkey_info.hk_filtering) - { - switch (settings.filtering) - { - case 0: - message = strcat(buf, " FILTERING MODE: AUTOMATIC"); - break; - case 1: - message = strcat(buf, " FILTERING MODE: FORCE BILINEAR"); - break; - case 2: - message = strcat(buf, " FILTERING MODE: FORCE POINT-SAMPLED"); - break; - } - hotkey_info.hk_filtering--; - } - output (120.0f, (float)settings.res_y, 0, message, 0); - } - } - #ifdef OLDAPI - if (capture_screen) - { - //char path[256]; - // Make the directory if it doesn't exist - if (!wxDirExists(capture_path)) - wxMkdir(capture_path); - wxString path; - wxString romName = rdp.RomName; - romName.Replace(wxT(" "), wxT("_"), true); - romName.Replace(wxT(":"), wxT(";"), true); - - for (int i=1; ; i++) - { - path = capture_path; - path += wxT("Glide64mk2_"); - path += romName; - path += wxT("_"); - if (i < 10) - path += wxT("0"); - path << i << wxT(".") << ScreenShotFormats[settings.ssformat].extension; - if (!wxFileName::FileExists(path)) - break; - } - - const wxUint32 offset_x = (wxUint32)rdp.offset_x; - const wxUint32 offset_y = (wxUint32)rdp.offset_y; - const wxUint32 image_width = settings.scr_res_x - offset_x*2; - const wxUint32 image_height = settings.scr_res_y - offset_y*2; - - GrLfbInfo_t info; - info.size = sizeof(GrLfbInfo_t); - if (grLfbLock (GR_LFB_READ_ONLY, - GR_BUFFER_BACKBUFFER, - GR_LFBWRITEMODE_565, - GR_ORIGIN_UPPER_LEFT, - FXFALSE, - &info)) - { - wxUint8 *ssimg = (wxUint8*)malloc(image_width * image_height * 3); // will be free in wxImage destructor - int sspos = 0; - wxUint32 offset_src = info.strideInBytes * offset_y; - - // Copy the screen - if (info.writeMode == GR_LFBWRITEMODE_8888) - { - wxUint32 col; - for (wxUint32 y = 0; y < image_height; y++) - { - wxUint32 *ptr = (wxUint32*)((wxUint8*)info.lfbPtr + offset_src); - ptr += offset_x; - for (wxUint32 x = 0; x < image_width; x++) - { - col = *(ptr++); - ssimg[sspos++] = (wxUint8)((col >> 16) & 0xFF); - ssimg[sspos++] = (wxUint8)((col >> 8) & 0xFF); - ssimg[sspos++] = (wxUint8)(col & 0xFF); - } - offset_src += info.strideInBytes; - } - } - else - { - wxUint16 col; - for (wxUint32 y = 0; y < image_height; y++) - { - wxUint16 *ptr = (wxUint16*)((wxUint8*)info.lfbPtr + offset_src); - ptr += offset_x; - for (wxUint32 x = 0; x < image_width; x++) - { - col = *(ptr++); - ssimg[sspos++] = (wxUint8)((float)(col >> 11) / 31.0f * 255.0f); - ssimg[sspos++] = (wxUint8)((float)((col >> 5) & 0x3F) / 63.0f * 255.0f); - ssimg[sspos++] = (wxUint8)((float)(col & 0x1F) / 31.0f * 255.0f); - } - offset_src += info.strideInBytes; - } - } - // Unlock the backbuffer - grLfbUnlock (GR_LFB_READ_ONLY, GR_BUFFER_BACKBUFFER); - wxImage screenshot(image_width, image_height, ssimg); - screenshot.SaveFile(path, ScreenShotFormats[settings.ssformat].type); - capture_screen = 0; - } - } - #endif - - // Capture the screen if debug capture is set - if (_debugger.capture) - { - // Allocate the screen - _debugger.screen = new wxUint8 [(settings.res_x*settings.res_y) << 1]; - - // Lock the backbuffer (already rendered) - GrLfbInfo_t info; - info.size = sizeof(GrLfbInfo_t); - while (!grLfbLock (GR_LFB_READ_ONLY, - GR_BUFFER_BACKBUFFER, - GR_LFBWRITEMODE_565, - GR_ORIGIN_UPPER_LEFT, - FXFALSE, - &info)); - - wxUint32 offset_src=0, offset_dst=0; - - // Copy the screen - for (wxUint32 y=0; y> 19) & 0x1F); - g = (wxUint8)((col >> 10) & 0x3F); - b = (wxUint8)((col >> 3) & 0x1F); - dst[x] = (r<<11)|(g<<5)|b; - } - } - else - { - memcpy (_debugger.screen + offset_dst, (wxUint8*)info.lfbPtr + offset_src, settings.res_x << 1); - } - offset_dst += settings.res_x << 1; - offset_src += info.strideInBytes; - } - - // Unlock the backbuffer - grLfbUnlock (GR_LFB_READ_ONLY, GR_BUFFER_BACKBUFFER); - } - - if (fullscreen && debugging) - { - debug_keys (); - debug_cacheviewer (); - debug_mouse (); - } - - if (settings.frame_buffer & fb_read_back_to_screen) - DrawWholeFrameBufferToScreen(); - - if (fullscreen) - { - if (fb_hwfbe_enabled && !(settings.hacks&hack_RE2) && !evoodoo) - grAuxBufferExt( GR_BUFFER_AUXBUFFER ); - grBufferSwap (settings.vsync); - fps_count ++; - if (*gfx.VI_STATUS_REG&0x08) //gamma correction is used - { - if (!voodoo.gamma_correction) - { - if (voodoo.gamma_table_size && !voodoo.gamma_table_r) - GetGammaTable(); //save initial gamma tables - guGammaCorrectionRGB(2.0f, 2.0f, 2.0f); //with gamma=2.0 gamma table is the same, as in N64 - voodoo.gamma_correction = 1; - } - } - else - { - if (voodoo.gamma_correction) - { - if (voodoo.gamma_table_r) - grLoadGammaTable(voodoo.gamma_table_size, voodoo.gamma_table_r, voodoo.gamma_table_g, voodoo.gamma_table_b); - else - guGammaCorrectionRGB(1.3f, 1.3f, 1.3f); //1.3f is default 3dfx gamma for everything but desktop - voodoo.gamma_correction = 0; - } - } - } - - if (_debugger.capture) - debug_capture (); - - if (fullscreen) - { - if (debugging || settings.wireframe || settings.buff_clear || (settings.hacks&hack_PPL && settings.ucode == 6)) - { - if (settings.hacks&hack_RE2 && fb_depth_render_enabled) - grDepthMask (FXFALSE); - else - grDepthMask (FXTRUE); - grBufferClear (0, 0, 0xFFFF); - } - /* //let the game to clear the buffers - else - { - grDepthMask (FXTRUE); - grColorMask (FXFALSE, FXFALSE); - grBufferClear (0, 0, 0xFFFF); - grColorMask (FXTRUE, FXTRUE); - } - */ - } - - if (settings.frame_buffer & fb_read_back_to_screen2) - DrawWholeFrameBufferToScreen(); - - frame_count ++; - - // Open/close debugger? - if (CheckKeyPressed(G64_VK_SCROLL, 0x0001)) - { - if (!debugging) - { - //if (settings.scr_res_x == 1024 && settings.scr_res_y == 768) - { - debugging = 1; - - // Recalculate screen size, don't resize screen - settings.res_x = (wxUint32)(settings.scr_res_x * 0.625f); - settings.res_y = (wxUint32)(settings.scr_res_y * 0.625f); - - ChangeSize (); - } - } - else - { - debugging = 0; - - settings.res_x = settings.scr_res_x; - settings.res_y = settings.scr_res_y; - - ChangeSize (); - } - } - - // Debug capture? - if (/*fullscreen && */debugging && CheckKeyPressed(G64_VK_INSERT, 0x0001)) - { - _debugger.capture = 1; - } -} - -extern "C" -{ - -/****************************************************************** -Function: ViStatusChanged -Purpose: This function is called to notify the dll that the -ViStatus registers value has been changed. -input: none -output: none -*******************************************************************/ -EXPORT void CALL ViStatusChanged (void) -{ -} - -/****************************************************************** -Function: ViWidthChanged -Purpose: This function is called to notify the dll that the -ViWidth registers value has been changed. -input: none -output: none -*******************************************************************/ -EXPORT void CALL ViWidthChanged (void) -{ -} - -#ifdef WINPROC_OVERRIDE -LRESULT CALLBACK WndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -{ - switch (msg) - { - case WM_ACTIVATEAPP: - if (wParam == TRUE && !fullscreen) rdp.window_changed = TRUE; - break; - case WM_PAINT: - if (!fullscreen) rdp.window_changed = TRUE; - break; - - /* case WM_DESTROY: - SetWindowLong (gfx.hWnd, GWL_WNDPROC, (long)oldWndProc); - break;*/ - } - - return CallWindowProc(oldWndProc, hwnd, msg, wParam, lParam); -} -#endif - -} - -int CheckKeyPressed(int key, int mask) -{ -static Glide64Keys g64Keys; - if (settings.use_hotkeys == 0) - return 0; -#ifdef __WINDOWS__ - return (GetAsyncKeyState(g64Keys[key]) & mask); -#else - if (grKeyPressed) - return grKeyPressed(g64Keys[key]); -#endif - return 0; -} - - -#ifdef ALTTAB_FIX -int k_ctl=0, k_alt=0, k_del=0; - -LRESULT CALLBACK LowLevelKeyboardProc(int nCode, - WPARAM wParam, LPARAM lParam) -{ - if (!fullscreen) return CallNextHookEx(NULL, nCode, wParam, lParam); - - int TabKey = FALSE; - - PKBDLLHOOKSTRUCT p; - - if (nCode == HC_ACTION) - { - switch (wParam) { -case WM_KEYUP: case WM_SYSKEYUP: - p = (PKBDLLHOOKSTRUCT) lParam; - if (p->vkCode == 162) k_ctl = 0; - if (p->vkCode == 164) k_alt = 0; - if (p->vkCode == 46) k_del = 0; - goto do_it; - -case WM_KEYDOWN: case WM_SYSKEYDOWN: - p = (PKBDLLHOOKSTRUCT) lParam; - if (p->vkCode == 162) k_ctl = 1; - if (p->vkCode == 164) k_alt = 1; - if (p->vkCode == 46) k_del = 1; - goto do_it; - -do_it: - TabKey = - ((p->vkCode == VK_TAB) && ((p->flags & LLKHF_ALTDOWN) != 0)) || - ((p->vkCode == VK_ESCAPE) && ((p->flags & LLKHF_ALTDOWN) != 0)) || - ((p->vkCode == VK_ESCAPE) && ((GetKeyState(VK_CONTROL) & 0x8000) != 0)) || - (k_ctl && k_alt && k_del); - - break; - } - } - - if (TabKey) - { - k_ctl = 0; - k_alt = 0; - k_del = 0; - ReleaseGfx (); - } - - return CallNextHookEx(NULL, nCode, wParam, lParam); -} -#endif - -// -// DllMain - called when the DLL is loaded, use this to get the DLL's instance -// -#ifdef OLDAPI -class wxDLLApp : public wxApp -{ -public: - virtual bool OnInit(); -}; - -IMPLEMENT_APP_NO_MAIN(wxDLLApp) - -bool wxDLLApp::OnInit() -{ - if (mutexProcessDList == NULL) - mutexProcessDList = new wxMutex(wxMUTEX_DEFAULT); - wxImage::AddHandler(new wxPNGHandler); - wxImage::AddHandler(new wxJPEGHandler); - return true; -} - -#ifndef __WINDOWS__ -int __attribute__ ((constructor)) DllLoad(void); -int __attribute__ ((destructor)) DllUnload(void); -#endif - -// Called when the library is loaded and before dlopen() returns -int DllLoad(void) -{ - int argc = 0; - char **argv = NULL; - wxEntryStart(argc, argv); - if (wxTheApp) - return wxTheApp->CallOnInit() ? TRUE : FALSE; - return 0; -} - -// Called when the library is unloaded and before dlclose() returns -int DllUnload(void) -{ - if ( wxTheApp ) - wxTheApp->OnExit(); - wxEntryCleanup(); - return TRUE; -} - -#ifdef __WINDOWS__ -extern "C" int WINAPI DllMain (HINSTANCE hinstDLL, - wxUint32 fdwReason, - LPVOID lpReserved) -{ - sprintf (out_buf, "DllMain (%08lx - %d)\n", hinstDLL, fdwReason); - LOG (out_buf); - - if (fdwReason == DLL_PROCESS_ATTACH) - { - wxSetInstance(hinstDLL); - return DllLoad(); - } - else if (fdwReason == DLL_PROCESS_DETACH) - { - if (GFXWindow != NULL) - GFXWindow->SetHWND(NULL); - return DllUnload(); - } - return TRUE; -} - -void CALL ReadScreen(void **dest, int *width, int *height) -{ - *width = settings.res_x; - *height = settings.res_y; - wxUint8 * buff = (wxUint8*)malloc(settings.res_x * settings.res_y * 3); - wxUint8 * line = buff; - *dest = (void*)buff; - - if (!fullscreen) - { - for (wxUint32 y=0; y> 16) & 0xFF); - g = (wxUint8)((col >> 8) & 0xFF); - b = (wxUint8)(col & 0xFF); - line[x*3] = b; - line[x*3+1] = g; - line[x*3+2] = r; - } - line += settings.res_x * 3; - offset_src -= info.strideInBytes; - } - } - else - { - wxUint16 col; - for (wxUint32 y=0; y> 11) / 31.0f * 255.0f); - g = (wxUint8)((float)((col >> 5) & 0x3F) / 63.0f * 255.0f); - b = (wxUint8)((float)(col & 0x1F) / 31.0f * 255.0f); - line[x*3] = b; - line[x*3+1] = g; - line[x*3+2] = r; - } - line += settings.res_x * 3; - offset_src -= info.strideInBytes; - } - } - // Unlock the frontbuffer - grLfbUnlock (GR_LFB_READ_ONLY, GR_BUFFER_FRONTBUFFER); - } - LOG ("ReadScreen. Success.\n"); -} -#endif -#endif +/* +* Glide64 - Glide video plugin for Nintendo 64 emulators. +* Copyright (c) 2002 Dave2001 +* Copyright (c) 2003-2009 Sergey 'Gonetz' Lipski +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +//**************************************************************** +// +// Glide64 - Glide Plugin for Nintendo 64 emulators +// Project started on December 29th, 2001 +// +// Authors: +// Dave2001, original author, founded the project in 2001, left it in 2002 +// Gugaman, joined the project in 2002, left it in 2002 +// Sergey 'Gonetz' Lipski, joined the project in 2002, main author since fall of 2002 +// Hiroshi 'KoolSmoky' Morii, joined the project in 2007 +// +//**************************************************************** +// +// To modify Glide64: +// * Write your name and (optional)email, commented by your work, so I know who did it, and so that you can find which parts you modified when it comes time to send it to me. +// * Do NOT send me the whole project or file that you modified. Take out your modified code sections, and tell me where to put them. If people sent the whole thing, I would have many different versions, but no idea how to combine them all. +// +//**************************************************************** + +#include "Gfx_1.3.h" +#include "Config.h" +#include "Util.h" +#include "3dmath.h" +#include "Debugger.h" +#include "Combine.h" +#include "TexCache.h" +#include "CRC.h" +#include "FBtoScreen.h" +#include "DepthBufferRender.h" + +#if defined(__GNUC__) +#include +#elif defined(__MSC__) +#include +#define PATH_MAX MAX_PATH +#endif +#include "osal_dynamiclib.h" +#ifdef TEXTURE_FILTER // Hiroshi Morii +#include +int ghq_dmptex_toggle_key = 0; +#endif +#if defined(__MINGW32__) +#define swprintf _snwprintf +#define vswprintf _vsnwprintf +#endif + +#define G64_VERSION "G64 Mk2" +#define RELTIME "Date: " __DATE__// " Time: " __TIME__ + +#ifdef EXT_LOGGING +std::ofstream extlog; +#endif + +#ifdef LOGGING +std::ofstream loga; +#endif + +#ifdef RDP_LOGGING +int log_open = FALSE; +std::ofstream rdp_log; +#endif + +#ifdef RDP_ERROR_LOG +int elog_open = FALSE; +std::ofstream rdp_err; +#endif + +GFX_INFO gfx; + +/* definitions of pointers to Core config functions */ +ptr_ConfigOpenSection ConfigOpenSection = NULL; +ptr_ConfigSetParameter ConfigSetParameter = NULL; +ptr_ConfigGetParameter ConfigGetParameter = NULL; +ptr_ConfigGetParameterHelp ConfigGetParameterHelp = NULL; +ptr_ConfigSetDefaultInt ConfigSetDefaultInt = NULL; +ptr_ConfigSetDefaultFloat ConfigSetDefaultFloat = NULL; +ptr_ConfigSetDefaultBool ConfigSetDefaultBool = NULL; +ptr_ConfigSetDefaultString ConfigSetDefaultString = NULL; +ptr_ConfigGetParamInt ConfigGetParamInt = NULL; +ptr_ConfigGetParamFloat ConfigGetParamFloat = NULL; +ptr_ConfigGetParamBool ConfigGetParamBool = NULL; +ptr_ConfigGetParamString ConfigGetParamString = NULL; + +ptr_ConfigGetSharedDataFilepath ConfigGetSharedDataFilepath = NULL; +ptr_ConfigGetUserConfigPath ConfigGetUserConfigPath = NULL; +ptr_ConfigGetUserDataPath ConfigGetUserDataPath = NULL; +ptr_ConfigGetUserCachePath ConfigGetUserCachePath = NULL; + +/* definitions of pointers to Core video extension functions */ +ptr_VidExt_Init CoreVideo_Init = NULL; +ptr_VidExt_Quit CoreVideo_Quit = NULL; +ptr_VidExt_ListFullscreenModes CoreVideo_ListFullscreenModes = NULL; +ptr_VidExt_SetVideoMode CoreVideo_SetVideoMode = NULL; +ptr_VidExt_SetCaption CoreVideo_SetCaption = NULL; +ptr_VidExt_ToggleFullScreen CoreVideo_ToggleFullScreen = NULL; +ptr_VidExt_ResizeWindow CoreVideo_ResizeWindow = NULL; +ptr_VidExt_GL_GetProcAddress CoreVideo_GL_GetProcAddress = NULL; +ptr_VidExt_GL_SetAttribute CoreVideo_GL_SetAttribute = NULL; +ptr_VidExt_GL_SwapBuffers CoreVideo_GL_SwapBuffers = NULL; +int to_fullscreen = FALSE; +int fullscreen = FALSE; +int romopen = FALSE; +GrContext_t gfx_context = 0; +int debugging = FALSE; +int exception = FALSE; + +int evoodoo = 0; +int ev_fullscreen = 0; + +enum { + NONE, + ASB, + BANJO2, + BAR, + CHOPPER, + DIDDY, + FIFA98, + FZERO, + GOLDENEYE, + HYPERBIKE, + ISS64, + KI, + KNOCKOUT, + LEGO, + MK64, + MEGAMAN, + MAKERS, + WCWNITRO, + OGRE64, + PILOTWINGS, + PMARIO, + PPL, + RE2, + STARCRAFT, + SUPERCROSS, + TGR, + TGR2, + TONIC, + YOSHI, + ZELDA +}; + +#ifdef __WINDOWS__ +#define WINPROC_OVERRIDE +#endif + +#ifdef WINPROC_OVERRIDE +LRESULT CALLBACK WndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); +WNDPROC oldWndProc = NULL; +WNDPROC myWndProc = NULL; +#endif + +#ifdef ALTTAB_FIX +HHOOK hhkLowLevelKybd = NULL; +LRESULT CALLBACK LowLevelKeyboardProc(int nCode, + WPARAM wParam, LPARAM lParam); +#endif + +#ifdef PERFORMANCE +int64 perf_cur; +int64 perf_next; +#endif + +#ifdef FPS +LARGE_INTEGER perf_freq; +LARGE_INTEGER fps_last; +LARGE_INTEGER fps_next; +float fps = 0.0f; +wxUint32 fps_count = 0; + +wxUint32 vi_count = 0; +float vi = 0.0f; + +wxUint32 region = 0; + +float ntsc_percent = 0.0f; +float pal_percent = 0.0f; + +#endif + +// ref rate +// 60=0x0, 70=0x1, 72=0x2, 75=0x3, 80=0x4, 90=0x5, 100=0x6, 85=0x7, 120=0x8, none=0xff + +unsigned long BMASK = 0x7FFFFF; +// Reality display processor structure +RDP rdp; + +SETTINGS settings = { FALSE, 640, 480, GR_RESOLUTION_640x480, 0 }; + +HOTKEY_INFO hotkey_info; + +VOODOO voodoo = {0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 + }; + +GrTexInfo fontTex; +GrTexInfo cursorTex; +wxUint32 offset_font = 0; +wxUint32 offset_cursor = 0; +wxUint32 offset_textures = 0; +wxUint32 offset_texbuf1 = 0; + +int capture_screen = 0; +char capture_path[256]; + +SDL_sem *mutexProcessDList = SDL_CreateSemaphore(1); + +// SOME FUNCTION DEFINITIONS + +static void DrawFrameBuffer (); + + +void (*renderCallback)(int) = NULL; +static void (*l_DebugCallback)(void *, int, const char *) = NULL; +static void *l_DebugCallContext = NULL; + +void _ChangeSize () +{ + rdp.scale_1024 = settings.scr_res_x / 1024.0f; + rdp.scale_768 = settings.scr_res_y / 768.0f; + +// float res_scl_x = (float)settings.res_x / 320.0f; + float res_scl_y = (float)settings.res_y / 240.0f; + + wxUint32 scale_x = *gfx.VI_X_SCALE_REG & 0xFFF; + if (!scale_x) return; + wxUint32 scale_y = *gfx.VI_Y_SCALE_REG & 0xFFF; + if (!scale_y) return; + + float fscale_x = (float)scale_x / 1024.0f; + float fscale_y = (float)scale_y / 2048.0f; + + wxUint32 dwHStartReg = *gfx.VI_H_START_REG; + wxUint32 dwVStartReg = *gfx.VI_V_START_REG; + + wxUint32 hstart = dwHStartReg >> 16; + wxUint32 hend = dwHStartReg & 0xFFFF; + + // dunno... but sometimes this happens + if (hend == hstart) hend = (int)(*gfx.VI_WIDTH_REG / fscale_x); + + wxUint32 vstart = dwVStartReg >> 16; + wxUint32 vend = dwVStartReg & 0xFFFF; + + rdp.vi_width = (hend - hstart) * fscale_x; + rdp.vi_height = (vend - vstart) * fscale_y * 1.0126582f; + float aspect = (settings.adjust_aspect && (fscale_y > fscale_x) && (rdp.vi_width > rdp.vi_height)) ? fscale_x/fscale_y : 1.0f; + +#ifdef LOGGING + sprintf (out_buf, "hstart: %d, hend: %d, vstart: %d, vend: %d\n", hstart, hend, vstart, vend); + LOG (out_buf); + sprintf (out_buf, "size: %d x %d\n", (int)rdp.vi_width, (int)rdp.vi_height); + LOG (out_buf); +#endif + + rdp.scale_x = (float)settings.res_x / rdp.vi_width; + if (region > 0 && settings.pal230) + { + // odd... but pal games seem to want 230 as height... + rdp.scale_y = res_scl_y * (230.0f / rdp.vi_height) * aspect; + } + else + { + rdp.scale_y = (float)settings.res_y / rdp.vi_height * aspect; + } + // rdp.offset_x = settings.offset_x * res_scl_x; + // rdp.offset_y = settings.offset_y * res_scl_y; + //rdp.offset_x = 0; + // rdp.offset_y = 0; + rdp.offset_y = ((float)settings.res_y - rdp.vi_height * rdp.scale_y) * 0.5f; + if (((wxUint32)rdp.vi_width <= (*gfx.VI_WIDTH_REG)/2) && (rdp.vi_width > rdp.vi_height)) + rdp.scale_y *= 0.5f; + + rdp.scissor_o.ul_x = 0; + rdp.scissor_o.ul_y = 0; + rdp.scissor_o.lr_x = (wxUint32)rdp.vi_width; + rdp.scissor_o.lr_y = (wxUint32)rdp.vi_height; + + rdp.update |= UPDATE_VIEWPORT | UPDATE_SCISSOR; +} + +void ChangeSize () +{ + if (debugging) + { + _ChangeSize (); + return; + } + switch (settings.aspectmode) + { + case 0: //4:3 + if (settings.scr_res_x >= settings.scr_res_y * 4.0f / 3.0f) { + settings.res_y = settings.scr_res_y; + settings.res_x = (wxUint32)(settings.res_y * 4.0f / 3.0f); + } else { + settings.res_x = settings.scr_res_x; + settings.res_y = (wxUint32)(settings.res_x / 4.0f * 3.0f); + } + break; + case 1: //16:9 + if (settings.scr_res_x >= settings.scr_res_y * 16.0f / 9.0f) { + settings.res_y = settings.scr_res_y; + settings.res_x = (wxUint32)(settings.res_y * 16.0f / 9.0f); + } else { + settings.res_x = settings.scr_res_x; + settings.res_y = (wxUint32)(settings.res_x / 16.0f * 9.0f); + } + break; + default: //stretch or original + settings.res_x = settings.scr_res_x; + settings.res_y = settings.scr_res_y; + } + _ChangeSize (); + rdp.offset_x = (settings.scr_res_x - settings.res_x) / 2.0f; + float offset_y = (settings.scr_res_y - settings.res_y) / 2.0f; + settings.res_x += (wxUint32)rdp.offset_x; + settings.res_y += (wxUint32)offset_y; + rdp.offset_y += offset_y; + if (settings.aspectmode == 3) // original + { + rdp.scale_x = rdp.scale_y = 1.0f; + rdp.offset_x = (settings.scr_res_x - rdp.vi_width) / 2.0f; + rdp.offset_y = (settings.scr_res_y - rdp.vi_height) / 2.0f; + } + // settings.res_x = settings.scr_res_x; + // settings.res_y = settings.scr_res_y; +} + +void ConfigWrapper() +{ + char strConfigWrapperExt[] = "grConfigWrapperExt"; + GRCONFIGWRAPPEREXT grConfigWrapperExt = (GRCONFIGWRAPPEREXT)grGetProcAddress(strConfigWrapperExt); + if (grConfigWrapperExt) + grConfigWrapperExt(settings.wrpResolution, settings.wrpVRAM * 1024 * 1024, settings.wrpFBO, settings.wrpAnisotropic); +} +/* +static wxConfigBase * OpenIni() +{ + wxConfigBase * ini = wxConfigBase::Get(false); + if (!ini) + { + if (iniName.IsEmpty()) + iniName = pluginPath + wxT("/Glide64mk2.ini"); + if (wxFileExists(iniName)) + { + wxFileInputStream is(iniName); + wxFileConfig * fcfg = new wxFileConfig(is, wxConvISO8859_1); + wxConfigBase::Set(fcfg); + ini = fcfg; + } + } + if (!ini) + wxMessageBox(_T("Can not find ini file! Plugin will not run properly."), _T("File not found"), wxOK|wxICON_EXCLAMATION); + return ini; +} +*/ +#ifndef OLDAPI +void WriteLog(m64p_msg_level level, const char *msg, ...) +{ + char buf[1024]; + va_list args; + va_start(args, msg); + vsnprintf(buf, 1023, msg, args); + buf[1023]='\0'; + va_end(args); + if (l_DebugCallback) + { + l_DebugCallback(l_DebugCallContext, level, buf); + } +} +#endif + +void ReadSettings () +{ + // LOG("ReadSettings\n"); + if (!Config_Open()) + { + ERRLOG("Could not open configuration!"); + return; + } + + settings.card_id = (BYTE)Config_ReadInt ("card_id", "Card ID", 0, TRUE, FALSE); + //settings.lang_id not needed + // depth_bias = -Config_ReadInt ("depth_bias", "Depth bias level", 0, TRUE, FALSE); + settings.res_data = 0; + settings.scr_res_x = settings.res_x = Config_ReadScreenInt("ScreenWidth"); + settings.scr_res_y = settings.res_y = Config_ReadScreenInt("ScreenHeight"); + + settings.vsync = (BOOL)Config_ReadInt ("vsync", "Vertical sync", 0); + settings.ssformat = (BOOL)Config_ReadInt("ssformat", "TODO:ssformat", 0); + //settings.fast_crc = (BOOL)Config_ReadInt ("fast_crc", "Fast CRC", 0); + + settings.show_fps = (BYTE)Config_ReadInt ("show_fps", "Display performance stats (add together desired flags): 1=FPS counter, 2=VI/s counter, 4=% speed, 8=FPS transparent", 0, TRUE, FALSE); + settings.clock = (BOOL)Config_ReadInt ("clock", "Clock enabled", 0); + settings.clock_24_hr = (BOOL)Config_ReadInt ("clock_24_hr", "Clock is 24-hour", 0); + // settings.advanced_options only good for GUI config + // settings.texenh_options = only good for GUI config + //settings.use_hotkeys = ini->Read(_T("hotkeys"), 1l); + + settings.wrpResolution = (BYTE)Config_ReadInt ("wrpResolution", "Wrapper resolution", 0, TRUE, FALSE); + settings.wrpVRAM = (BYTE)Config_ReadInt ("wrpVRAM", "Wrapper VRAM", 0, TRUE, FALSE); + settings.wrpFBO = (BOOL)Config_ReadInt ("wrpFBO", "Wrapper FBO", 1, TRUE, TRUE); + settings.wrpAnisotropic = (BOOL)Config_ReadInt ("wrpAnisotropic", "Wrapper Anisotropic Filtering", 0, TRUE, TRUE); + +#ifndef _ENDUSER_RELEASE_ + settings.autodetect_ucode = (BOOL)Config_ReadInt ("autodetect_ucode", "Auto-detect microcode", 1); + settings.ucode = (wxUint32)Config_ReadInt ("ucode", "Force microcode", 2, TRUE, FALSE); + settings.wireframe = (BOOL)Config_ReadInt ("wireframe", "Wireframe display", 0); + settings.wfmode = (int)Config_ReadInt ("wfmode", "Wireframe mode: 0=Normal colors, 1=Vertex colors, 2=Red only", 1, TRUE, FALSE); + + settings.logging = (BOOL)Config_ReadInt ("logging", "Logging", 0); + settings.log_clear = (BOOL)Config_ReadInt ("log_clear", "", 0); + + settings.run_in_window = (BOOL)Config_ReadInt ("run_in_window", "", 0); + + settings.elogging = (BOOL)Config_ReadInt ("elogging", "", 0); + settings.filter_cache = (BOOL)Config_ReadInt ("filter_cache", "Filter cache", 0); + settings.unk_as_red = (BOOL)Config_ReadInt ("unk_as_red", "Display unknown combines as red", 0); + settings.log_unk = (BOOL)Config_ReadInt ("log_unk", "Log unknown combines", 0); + settings.unk_clear = (BOOL)Config_ReadInt ("unk_clear", "", 0); +#else + settings.autodetect_ucode = TRUE; + settings.ucode = 2; + settings.wireframe = FALSE; + settings.wfmode = 0; + settings.logging = FALSE; + settings.log_clear = FALSE; + settings.run_in_window = FALSE; + settings.elogging = FALSE; + settings.filter_cache = FALSE; + settings.unk_as_red = FALSE; + settings.log_unk = FALSE; + settings.unk_clear = FALSE; +#endif + +#ifdef TEXTURE_FILTER + + // settings.ghq_fltr range is 0 through 6 + // Filters:\nApply a filter to either smooth or sharpen textures.\nThere are 4 different smoothing filters and 2 different sharpening filters.\nThe higher the number, the stronger the effect,\ni.e. \"Smoothing filter 4\" will have a much more noticeable effect than \"Smoothing filter 1\".\nBe aware that performance may have an impact depending on the game and/or the PC.\n[Recommended: your preference] + // _("None"), + // _("Smooth filtering 1"), + // _("Smooth filtering 2"), + // _("Smooth filtering 3"), + // _("Smooth filtering 4"), + // _("Sharp filtering 1"), + // _("Sharp filtering 2") + +// settings.ghq_cmpr 0=S3TC and 1=FXT1 + +//settings.ghq_ent is ___ +// "Texture enhancement:\n7 different filters are selectable here, each one with a distinctive look.\nBe aware of possible performance impacts.\n\nIMPORTANT: 'Store' mode - saves textures in cache 'as is'. It can improve performance in games, which load many textures.\nDisable 'Ignore backgrounds' option for better result.\n\n[Recommended: your preference]" + + + + settings.ghq_fltr = Config_ReadInt ("ghq_fltr", "Texture Enhancement: Smooth/Sharpen Filters", 0, TRUE, FALSE); + settings.ghq_cmpr = Config_ReadInt ("ghq_cmpr", "Texture Compression: 0 for S3TC, 1 for FXT1", 0, TRUE, FALSE); + settings.ghq_enht = Config_ReadInt ("ghq_enht", "Texture Enhancement: More filters", 0, TRUE, FALSE); + settings.ghq_hirs = Config_ReadInt ("ghq_hirs", "Hi-res texture pack format (0 for none, 1 for Rice)", 0, TRUE, FALSE); + settings.ghq_enht_cmpr = Config_ReadInt ("ghq_enht_cmpr", "Compress texture cache with S3TC or FXT1", 0, TRUE, TRUE); + settings.ghq_enht_tile = Config_ReadInt ("ghq_enht_tile", "Tile textures (saves memory but could cause issues)", 0, TRUE, FALSE); + settings.ghq_enht_f16bpp = Config_ReadInt ("ghq_enht_f16bpp", "Force 16bpp textures (saves ram but lower quality)", 0, TRUE, TRUE); + settings.ghq_enht_gz = Config_ReadInt ("ghq_enht_gz", "Compress texture cache", 1, TRUE, TRUE); + settings.ghq_enht_nobg = Config_ReadInt ("ghq_enht_nobg", "Don't enhance textures for backgrounds", 0, TRUE, TRUE); + settings.ghq_hirs_cmpr = Config_ReadInt ("ghq_hirs_cmpr", "Enable S3TC and FXT1 compression", 0, TRUE, TRUE); + settings.ghq_hirs_tile = Config_ReadInt ("ghq_hirs_tile", "Tile hi-res textures (saves memory but could cause issues)", 0, TRUE, TRUE); + settings.ghq_hirs_f16bpp = Config_ReadInt ("ghq_hirs_f16bpp", "Force 16bpp hi-res textures (saves ram but lower quality)", 0, TRUE, TRUE); + settings.ghq_hirs_gz = Config_ReadInt ("ghq_hirs_gz", "Compress hi-res texture cache", 1, TRUE, TRUE); + settings.ghq_hirs_altcrc = Config_ReadInt ("ghq_hirs_altcrc", "Alternative CRC calculation -- emulates Rice bug", 1, TRUE, TRUE); + settings.ghq_cache_save = Config_ReadInt ("ghq_cache_save", "Save tex cache to disk", 1, TRUE, TRUE); + settings.ghq_cache_size = Config_ReadInt ("ghq_cache_size", "Texture Cache Size (MB)", 128, TRUE, FALSE); + settings.ghq_hirs_let_texartists_fly = Config_ReadInt ("ghq_hirs_let_texartists_fly", "Use full alpha channel -- could cause issues for some tex packs", 0, TRUE, TRUE); + settings.ghq_hirs_dump = Config_ReadInt ("ghq_hirs_dump", "Dump textures", 0, FALSE, TRUE); +#endif + //TODO-PORT: remove? + ConfigWrapper(); +} + +void ReadSpecialSettings (const char * name) +{ + // char buf [256]; + // sprintf(buf, "ReadSpecialSettings. Name: %s\n", name); + // LOG(buf); + + int EnableHacksForGame = (int)Config_ReadInt ("enable_hacks_for_game", "???", 0, TRUE, FALSE); + + switch (EnableHacksForGame) + { + case NONE: + break; + case ASB: + settings.hacks |= hack_ASB; + break; + case BANJO2: + settings.hacks |= hack_Banjo2; + break; + case BAR: + settings.hacks |= hack_BAR; + break; + case CHOPPER: + settings.hacks |= hack_Chopper; + break; + case DIDDY: + settings.hacks |= hack_Diddy; + break; + case FIFA98: + settings.hacks |= hack_Fifa98; + break; + case FZERO: + settings.hacks |= hack_Fzero; + break; + case GOLDENEYE: + settings.hacks |= hack_GoldenEye; + break; + case HYPERBIKE: + settings.hacks |= hack_Hyperbike; + break; + case ISS64: + settings.hacks |= hack_ISS64; + break; + case KI: + settings.hacks |= hack_KI; + break; + case KNOCKOUT: + settings.hacks |= hack_Knockout; + break; + case LEGO: + settings.hacks |= hack_Lego; + break; + case MK64: + settings.hacks |= hack_MK64; + break; + case MEGAMAN: + settings.hacks |= hack_Megaman; + break; + case MAKERS: + settings.hacks |= hack_Makers; + break; + case WCWNITRO: + settings.hacks |= hack_WCWnitro; + break; + case OGRE64: + settings.hacks |= hack_Ogre64; + break; + case PILOTWINGS: + settings.hacks |= hack_Pilotwings; + break; + case PMARIO: + settings.hacks |= hack_PMario; + break; + case PPL: + settings.hacks |= hack_PPL; + break; + case RE2: + settings.hacks |= hack_RE2; + break; + case STARCRAFT: + settings.hacks |= hack_Starcraft; + break; + case SUPERCROSS: + settings.hacks |= hack_Supercross; + break; + case TGR: + settings.hacks |= hack_TGR; + break; + case TGR2: + settings.hacks |= hack_TGR2; + break; + case TONIC: + settings.hacks |= hack_Tonic; + break; + case YOSHI: + settings.hacks |= hack_Yoshi; + break; + case ZELDA: + settings.hacks |= hack_Zelda; + break; + } + + settings.alt_tex_size = (BOOL)Config_ReadInt("alt_tex_size", "???", TRUE); + settings.use_sts1_only = (BOOL)Config_ReadInt("use_sts1_only", "???", FALSE); + settings.force_calc_sphere = (BOOL)Config_ReadInt("force_calc_sphere", "???", FALSE); + settings.correct_viewport = (BOOL)Config_ReadInt("correct_viewport", "???", FALSE); + settings.increase_texrect_edge = (BOOL)Config_ReadInt("increase_texrect_edge", "???", FALSE); + settings.decrease_fillrect_edge = (BOOL)Config_ReadInt("decrease_fillrect_edge", "???", FALSE); + settings.texture_correction = (BOOL)Config_ReadInt("texture_correction", "???", TRUE); + + settings.force_microcheck = (BOOL)Config_ReadInt("force_microcheck", "???", FALSE); + settings.fog = (BOOL)Config_ReadInt("fog", "???", TRUE); + settings.buff_clear = (BOOL)Config_ReadInt("buff_clear", "???", TRUE); + settings.swapmode = (int)Config_ReadInt("swapmode", "???", 1, TRUE, FALSE); + int stipple_pattern = (int)Config_ReadInt("stipple_pattern", "???", 1041204192, TRUE, FALSE); + if (stipple_pattern > 0) settings.stipple_pattern = (wxUint32)stipple_pattern; + settings.stipple_mode = (int)Config_ReadInt("stipple_mode", "???", 2, TRUE, FALSE); + settings.lodmode = (int)Config_ReadInt("lodmode", "???", 0, TRUE, FALSE); + settings.filtering = (int)Config_ReadInt("filtering", "???", 0, TRUE, FALSE); + settings.pal230 = (BOOL)Config_ReadInt("pal230", "???", FALSE); + settings.texture_correction = (BOOL)Config_ReadInt("texture_correction", "???", TRUE); + settings.n64_z_scale = (BOOL)Config_ReadInt("n64_z_scale", "???", FALSE); + settings.old_style_adither = (BOOL)Config_ReadInt("old_style_adither", "???", FALSE); + settings.zmode_compare_less = (BOOL)Config_ReadInt("zmode_compare_less", "???", FALSE); + settings.adjust_aspect = (BOOL)Config_ReadInt("adjust_aspect", "???", TRUE); + settings.clip_zmax = (BOOL)Config_ReadInt("clip_zmax", "???", TRUE); + settings.clip_zmin = (BOOL)Config_ReadInt("clip_zmin", "???", FALSE); + settings.force_quad3d = (BOOL)Config_ReadInt("force_quad3d", "???", FALSE); + settings.aspectmode = (int)Config_ReadInt("aspectmode", "???", 0, TRUE, FALSE); + settings.fast_crc = (BOOL)Config_ReadInt("fast_crc", "???", TRUE); + + if (settings.n64_z_scale) + ZLUT_init(); + + //frame buffer + int optimize_texrect = (BOOL)Config_ReadInt("optimize_texrect", "???", TRUE); + int ignore_aux_copy = (BOOL)Config_ReadInt("ignore_aux_copy", "???", FALSE); + int hires_buf_clear = (BOOL)Config_ReadInt("hires_buf_clear", "???", TRUE); + int read_alpha = (BOOL)Config_ReadInt("fb_read_alpha", "???", FALSE); + int useless_is_useless = (BOOL)Config_ReadInt("useless_is_useless", "???", FALSE); + int fb_crc_mode = (int)Config_ReadInt("fb_crc_mode", "???", 1, TRUE, FALSE); + + if (optimize_texrect > 0) settings.frame_buffer |= fb_optimize_texrect; + else if (optimize_texrect == 0) settings.frame_buffer &= ~fb_optimize_texrect; + if (ignore_aux_copy > 0) settings.frame_buffer |= fb_ignore_aux_copy; + else if (ignore_aux_copy == 0) settings.frame_buffer &= ~fb_ignore_aux_copy; + if (hires_buf_clear > 0) settings.frame_buffer |= fb_hwfbe_buf_clear; + else if (hires_buf_clear == 0) settings.frame_buffer &= ~fb_hwfbe_buf_clear; + if (read_alpha > 0) settings.frame_buffer |= fb_read_alpha; + else if (read_alpha == 0) settings.frame_buffer &= ~fb_read_alpha; + if (useless_is_useless > 0) settings.frame_buffer |= fb_useless_is_useless; + else settings.frame_buffer &= ~fb_useless_is_useless; + if (fb_crc_mode >= 0) settings.fb_crc_mode = (SETTINGS::FBCRCMODE)fb_crc_mode; + + //frame buffer + int smart_read = (BOOL)Config_ReadInt("fb_smart", "???", FALSE); + int hires = (BOOL)Config_ReadInt("fb_hires", "???", TRUE); + int read_always = (BOOL)Config_ReadInt("fb_read_always", "???", FALSE); + int read_back_to_screen = (int)Config_ReadInt("read_back_to_screen", "???", 0, TRUE, FALSE); + int cpu_write_hack = (BOOL)Config_ReadInt("detect_cpu_write", "???", FALSE); + int get_fbinfo = (BOOL)Config_ReadInt("fb_get_info", "???", FALSE); + int depth_render = (BOOL)Config_ReadInt("fb_render", "???", TRUE); + + if (smart_read > 0) settings.frame_buffer |= fb_emulation; + else if (smart_read == 0) settings.frame_buffer &= ~fb_emulation; + if (hires > 0) settings.frame_buffer |= fb_hwfbe; + else if (hires == 0) settings.frame_buffer &= ~fb_hwfbe; + if (read_always > 0) settings.frame_buffer |= fb_ref; + else if (read_always == 0) settings.frame_buffer &= ~fb_ref; + if (read_back_to_screen == 1) settings.frame_buffer |= fb_read_back_to_screen; + else if (read_back_to_screen == 2) settings.frame_buffer |= fb_read_back_to_screen2; + else if (read_back_to_screen == 0) settings.frame_buffer &= ~(fb_read_back_to_screen|fb_read_back_to_screen2); + if (cpu_write_hack > 0) settings.frame_buffer |= fb_cpu_write_hack; + else if (cpu_write_hack == 0) settings.frame_buffer &= ~fb_cpu_write_hack; + if (get_fbinfo > 0) settings.frame_buffer |= fb_get_info; + else if (get_fbinfo == 0) settings.frame_buffer &= ~fb_get_info; + if (depth_render > 0) settings.frame_buffer |= fb_depth_render; + else if (depth_render == 0) settings.frame_buffer &= ~fb_depth_render; + settings.frame_buffer |= fb_motionblur; + + settings.flame_corona = (settings.hacks & hack_Zelda) && !fb_depth_render_enabled; +} + +//TODO-PORT: more ini stuff +void WriteSettings (bool saveEmulationSettings) +{ +/* + wxConfigBase * ini = OpenIni(); + if (!ini || !ini->HasGroup(_T("/SETTINGS"))) + return; + ini->SetPath(_T("/SETTINGS")); + + ini->Write(_T("card_id"), settings.card_id); + ini->Write(_T("lang_id"), settings.lang_id); + ini->Write(_T("resolution"), (int)settings.res_data); + ini->Write(_T("ssformat"), settings.ssformat); + ini->Write(_T("vsync"), settings.vsync); + ini->Write(_T("show_fps"), settings.show_fps); + ini->Write(_T("clock"), settings.clock); + ini->Write(_T("clock_24_hr"), settings.clock_24_hr); + ini->Write(_T("advanced_options"), settings.advanced_options); + ini->Write(_T("texenh_options"), settings.texenh_options); + + ini->Write(_T("wrpResolution"), settings.wrpResolution); + ini->Write(_T("wrpVRAM"), settings.wrpVRAM); + ini->Write(_T("wrpFBO"), settings.wrpFBO); + ini->Write(_T("wrpAnisotropic"), settings.wrpAnisotropic); + +#ifndef _ENDUSER_RELEASE_ + ini->Write(_T("autodetect_ucode"), settings.autodetect_ucode); + ini->Write(_T("ucode"), (int)settings.ucode); + ini->Write(_T("wireframe"), settings.wireframe); + ini->Write(_T("wfmode"), settings.wfmode); + ini->Write(_T("logging"), settings.logging); + ini->Write(_T("log_clear"), settings.log_clear); + ini->Write(_T("run_in_window"), settings.run_in_window); + ini->Write(_T("elogging"), settings.elogging); + ini->Write(_T("filter_cache"), settings.filter_cache); + ini->Write(_T("unk_as_red"), settings.unk_as_red); + ini->Write(_T("log_unk"), settings.log_unk); + ini->Write(_T("unk_clear"), settings.unk_clear); +#endif //_ENDUSER_RELEASE_ + +#ifdef TEXTURE_FILTER + ini->Write(_T("ghq_fltr"), settings.ghq_fltr); + ini->Write(_T("ghq_cmpr"), settings.ghq_cmpr); + ini->Write(_T("ghq_enht"), settings.ghq_enht); + ini->Write(_T("ghq_hirs"), settings.ghq_hirs); + ini->Write(_T("ghq_enht_cmpr"), settings.ghq_enht_cmpr); + ini->Write(_T("ghq_enht_tile"), settings.ghq_enht_tile); + ini->Write(_T("ghq_enht_f16bpp"), settings.ghq_enht_f16bpp); + ini->Write(_T("ghq_enht_gz"), settings.ghq_enht_gz); + ini->Write(_T("ghq_enht_nobg"), settings.ghq_enht_nobg); + ini->Write(_T("ghq_hirs_cmpr"), settings.ghq_hirs_cmpr); + ini->Write(_T("ghq_hirs_tile"), settings.ghq_hirs_tile); + ini->Write(_T("ghq_hirs_f16bpp"), settings.ghq_hirs_f16bpp); + ini->Write(_T("ghq_hirs_gz"), settings.ghq_hirs_gz); + ini->Write(_T("ghq_hirs_altcrc"), settings.ghq_hirs_altcrc); + ini->Write(_T("ghq_cache_save"), settings.ghq_cache_save); + ini->Write(_T("ghq_cache_size"), settings.ghq_cache_size); + ini->Write(_T("ghq_hirs_let_texartists_fly"), settings.ghq_hirs_let_texartists_fly); + ini->Write(_T("ghq_hirs_dump"), settings.ghq_hirs_dump); +#endif + + if (saveEmulationSettings) + { + if (romopen) + { + wxString S = _T("/"); + ini->SetPath(S+rdp.RomName); + } + else + ini->SetPath(_T("/DEFAULT")); + ini->Write(_T("filtering"), settings.filtering); + ini->Write(_T("fog"), settings.fog); + ini->Write(_T("buff_clear"), settings.buff_clear); + ini->Write(_T("swapmode"), settings.swapmode); + ini->Write(_T("lodmode"), settings.lodmode); + ini->Write(_T("aspect"), settings.aspectmode); + + ini->Write(_T("fb_read_always"), settings.frame_buffer&fb_ref ? 1 : 0l); + ini->Write(_T("fb_smart"), settings.frame_buffer & fb_emulation ? 1 : 0l); + // ini->Write("motionblur", settings.frame_buffer & fb_motionblur ? 1 : 0); + ini->Write(_T("fb_hires"), settings.frame_buffer & fb_hwfbe ? 1 : 0l); + ini->Write(_T("fb_get_info"), settings.frame_buffer & fb_get_info ? 1 : 0l); + ini->Write(_T("fb_render"), settings.frame_buffer & fb_depth_render ? 1 : 0l); + ini->Write(_T("detect_cpu_write"), settings.frame_buffer & fb_cpu_write_hack ? 1 : 0l); + if (settings.frame_buffer & fb_read_back_to_screen) + ini->Write(_T("read_back_to_screen"), 1); + else if (settings.frame_buffer & fb_read_back_to_screen2) + ini->Write(_T("read_back_to_screen"), 2); + else + ini->Write(_T("read_back_to_screen"), 0l); + } + + wxFileOutputStream os(iniName); + ((wxFileConfig*)ini)->Save(os); +*/ +} + +GRTEXBUFFEREXT grTextureBufferExt = NULL; +GRTEXBUFFEREXT grTextureAuxBufferExt = NULL; +GRAUXBUFFEREXT grAuxBufferExt = NULL; +GRSTIPPLE grStippleModeExt = NULL; +GRSTIPPLE grStipplePatternExt = NULL; +FxBool (FX_CALL *grKeyPressed)(FxU32) = NULL; + +int GetTexAddrUMA(int tmu, int texsize) +{ + int addr = voodoo.tex_min_addr[0] + voodoo.tmem_ptr[0]; + voodoo.tmem_ptr[0] += texsize; + voodoo.tmem_ptr[1] = voodoo.tmem_ptr[0]; + return addr; +} +int GetTexAddrNonUMA(int tmu, int texsize) +{ + int addr = voodoo.tex_min_addr[tmu] + voodoo.tmem_ptr[tmu]; + voodoo.tmem_ptr[tmu] += texsize; + return addr; +} +GETTEXADDR GetTexAddr = GetTexAddrNonUMA; + +// guLoadTextures - used to load the cursor and font textures +void guLoadTextures () +{ + if (grTextureBufferExt) + { + int tbuf_size = 0; + if (voodoo.max_tex_size <= 256) + { + grTextureBufferExt( GR_TMU1, voodoo.tex_min_addr[GR_TMU1], GR_LOD_LOG2_256, GR_LOD_LOG2_256, + GR_ASPECT_LOG2_1x1, GR_TEXFMT_RGB_565, GR_MIPMAPLEVELMASK_BOTH ); + tbuf_size = 8 * grTexCalcMemRequired(GR_LOD_LOG2_256, GR_LOD_LOG2_256, + GR_ASPECT_LOG2_1x1, GR_TEXFMT_RGB_565); + } + else if (settings.scr_res_x <= 1024) + { + grTextureBufferExt( GR_TMU0, voodoo.tex_min_addr[GR_TMU0], GR_LOD_LOG2_1024, GR_LOD_LOG2_1024, + GR_ASPECT_LOG2_1x1, GR_TEXFMT_RGB_565, GR_MIPMAPLEVELMASK_BOTH ); + tbuf_size = grTexCalcMemRequired(GR_LOD_LOG2_1024, GR_LOD_LOG2_1024, + GR_ASPECT_LOG2_1x1, GR_TEXFMT_RGB_565); + grRenderBuffer( GR_BUFFER_TEXTUREBUFFER_EXT ); + grBufferClear (0, 0, 0xFFFF); + grRenderBuffer( GR_BUFFER_BACKBUFFER ); + } + else + { + grTextureBufferExt( GR_TMU0, voodoo.tex_min_addr[GR_TMU0], GR_LOD_LOG2_2048, GR_LOD_LOG2_2048, + GR_ASPECT_LOG2_1x1, GR_TEXFMT_RGB_565, GR_MIPMAPLEVELMASK_BOTH ); + tbuf_size = grTexCalcMemRequired(GR_LOD_LOG2_2048, GR_LOD_LOG2_2048, + GR_ASPECT_LOG2_1x1, GR_TEXFMT_RGB_565); + grRenderBuffer( GR_BUFFER_TEXTUREBUFFER_EXT ); + grBufferClear (0, 0, 0xFFFF); + grRenderBuffer( GR_BUFFER_BACKBUFFER ); + } + + rdp.texbufs[0].tmu = GR_TMU0; + rdp.texbufs[0].begin = voodoo.tex_min_addr[GR_TMU0]; + rdp.texbufs[0].end = rdp.texbufs[0].begin+tbuf_size; + rdp.texbufs[0].count = 0; + rdp.texbufs[0].clear_allowed = TRUE; + offset_font = tbuf_size; + if (voodoo.num_tmu > 1) + { + rdp.texbufs[1].tmu = GR_TMU1; + rdp.texbufs[1].begin = voodoo.tex_UMA ? rdp.texbufs[0].end : voodoo.tex_min_addr[GR_TMU1]; + rdp.texbufs[1].end = rdp.texbufs[1].begin+tbuf_size; + rdp.texbufs[1].count = 0; + rdp.texbufs[1].clear_allowed = TRUE; + if (voodoo.tex_UMA) + offset_font += tbuf_size; + else + offset_texbuf1 = tbuf_size; + } + } + else + offset_font = 0; + +#include "font.h" + wxUint32 *data = (wxUint32*)font; + wxUint32 cur; + + // ** Font texture ** + wxUint8 *tex8 = (wxUint8*)malloc(256*64); + + fontTex.smallLodLog2 = fontTex.largeLodLog2 = GR_LOD_LOG2_256; + fontTex.aspectRatioLog2 = GR_ASPECT_LOG2_4x1; + fontTex.format = GR_TEXFMT_ALPHA_8; + fontTex.data = tex8; + + // Decompression: [1-bit inverse alpha --> 8-bit alpha] + wxUint32 i,b; + for (i=0; i<0x200; i++) + { + // cur = ~*(data++), byteswapped +#ifdef __VISUALC__ + cur = _byteswap_ulong(~*(data++)); +#else + cur = ~*(data++); + cur = ((cur&0xFF)<<24)|(((cur>>8)&0xFF)<<16)|(((cur>>16)&0xFF)<<8)|((cur>>24)&0xFF); +#endif + + for (b=0x80000000; b!=0; b>>=1) + { + if (cur&b) *tex8 = 0xFF; + else *tex8 = 0x00; + tex8 ++; + } + } + + grTexDownloadMipMap (GR_TMU0, + voodoo.tex_min_addr[GR_TMU0] + offset_font, + GR_MIPMAPLEVELMASK_BOTH, + &fontTex); + + offset_cursor = offset_font + grTexTextureMemRequired (GR_MIPMAPLEVELMASK_BOTH, &fontTex); + + free (fontTex.data); + + // ** Cursor texture ** +#include "cursor.h" + data = (wxUint32*)cursor; + + wxUint16 *tex16 = (wxUint16*)malloc(32*32*2); + + cursorTex.smallLodLog2 = cursorTex.largeLodLog2 = GR_LOD_LOG2_32; + cursorTex.aspectRatioLog2 = GR_ASPECT_LOG2_1x1; + cursorTex.format = GR_TEXFMT_ARGB_1555; + cursorTex.data = tex16; + + // Conversion: [16-bit 1555 (swapped) --> 16-bit 1555] + for (i=0; i<0x200; i++) + { + cur = *(data++); + *(tex16++) = (wxUint16)(((cur&0x000000FF)<<8)|((cur&0x0000FF00)>>8)); + *(tex16++) = (wxUint16)(((cur&0x00FF0000)>>8)|((cur&0xFF000000)>>24)); + } + + grTexDownloadMipMap (GR_TMU0, + voodoo.tex_min_addr[GR_TMU0] + offset_cursor, + GR_MIPMAPLEVELMASK_BOTH, + &cursorTex); + + // Round to higher 16 + offset_textures = ((offset_cursor + grTexTextureMemRequired (GR_MIPMAPLEVELMASK_BOTH, &cursorTex)) + & 0xFFFFFFF0) + 16; + free (cursorTex.data); +} + +#ifdef TEXTURE_FILTER +void DisplayLoadProgress(const wchar_t *format, ...) +{ + va_list args; + wchar_t wbuf[INFO_BUF]; + char buf[INFO_BUF]; + + // process input + va_start(args, format); + vswprintf(wbuf, INFO_BUF, format, args); + va_end(args); + + // XXX: convert to multibyte + wcstombs(buf, wbuf, INFO_BUF); + + if (fullscreen) + { + float x; + set_message_combiner (); + output (382, 380, 1, "LOADING TEXTURES. PLEASE WAIT..."); + int len = min (strlen(buf)*8, 1024); + x = (1024-len)/2.0f; + output (x, 360, 1, buf); + grBufferSwap (0); + grColorMask (FXTRUE, FXTRUE); + grBufferClear (0, 0, 0xFFFF); + } +} +#endif + +int InitGfx () +{ +#ifdef TEXTURE_FILTER + wchar_t romname[256]; + wchar_t foldername[PATH_MAX + 64]; + wchar_t cachename[PATH_MAX + 64]; +#endif + if (fullscreen) + ReleaseGfx (); + + OPEN_RDP_LOG (); // doesn't matter if opens again; it will check for it + OPEN_RDP_E_LOG (); + VLOG ("InitGfx ()\n"); + + debugging = FALSE; + rdp_reset (); + + // Initialize Glide + grGlideInit (); + + // Select the Glide device + grSstSelect (settings.card_id); + + // Is mirroring allowed? + const char *extensions = grGetString (GR_EXTENSION); + + // Check which SST we are using and initialize stuff + // Hiroshi Morii + enum { + GR_SSTTYPE_VOODOO = 0, + GR_SSTTYPE_SST96 = 1, + GR_SSTTYPE_AT3D = 2, + GR_SSTTYPE_Voodoo2 = 3, + GR_SSTTYPE_Banshee = 4, + GR_SSTTYPE_Voodoo3 = 5, + GR_SSTTYPE_Voodoo4 = 6, + GR_SSTTYPE_Voodoo5 = 7 + }; + const char *hardware = grGetString(GR_HARDWARE); + unsigned int SST_type = GR_SSTTYPE_VOODOO; + if (strstr(hardware, "Rush")) { + SST_type = GR_SSTTYPE_SST96; + } else if (strstr(hardware, "Voodoo2")) { + SST_type = GR_SSTTYPE_Voodoo2; + } else if (strstr(hardware, "Voodoo Banshee")) { + SST_type = GR_SSTTYPE_Banshee; + } else if (strstr(hardware, "Voodoo3")) { + SST_type = GR_SSTTYPE_Voodoo3; + } else if (strstr(hardware, "Voodoo4")) { + SST_type = GR_SSTTYPE_Voodoo4; + } else if (strstr(hardware, "Voodoo5")) { + SST_type = GR_SSTTYPE_Voodoo5; + } + // 2Mb Texture boundary + voodoo.has_2mb_tex_boundary = (SST_type < GR_SSTTYPE_Banshee) && !evoodoo; + // use UMA if available + voodoo.tex_UMA = FALSE; + //* + if (strstr(extensions, " TEXUMA ")) { + // we get better texture cache hits with UMA on + grEnable(GR_TEXTURE_UMA_EXT); + voodoo.tex_UMA = TRUE; + LOG ("Using TEXUMA extension.\n"); + } + //*/ +//TODO-PORT: fullscreen stuff + wxUint32 res_data = settings.res_data; + char strWrapperFullScreenResolutionExt[] = "grWrapperFullScreenResolutionExt"; + if (ev_fullscreen) + { + GRWRAPPERFULLSCREENRESOLUTIONEXT grWrapperFullScreenResolutionExt = + (GRWRAPPERFULLSCREENRESOLUTIONEXT)grGetProcAddress(strWrapperFullScreenResolutionExt); + if (grWrapperFullScreenResolutionExt) { + wxUint32 _width, _height = 0; + settings.res_data = grWrapperFullScreenResolutionExt(&_width, &_height); + settings.scr_res_x = settings.res_x = _width; + settings.scr_res_y = settings.res_y = _height; + } + res_data = settings.res_data; + } + else if (evoodoo) + { + GRWRAPPERFULLSCREENRESOLUTIONEXT grWrapperFullScreenResolutionExt = + (GRWRAPPERFULLSCREENRESOLUTIONEXT)grGetProcAddress(strWrapperFullScreenResolutionExt); + if (grWrapperFullScreenResolutionExt != NULL) + { +/* + TODO-port: fix resolutions + settings.res_data = settings.res_data_org; + settings.scr_res_x = settings.res_x = resolutions[settings.res_data][0]; + settings.scr_res_y = settings.res_y = resolutions[settings.res_data][1]; +*/ + } + res_data = settings.res_data | 0x80000000; + } + + gfx_context = 0; + + // Select the window + + if (fb_hwfbe_enabled) + { + char strSstWinOpenExt[] ="grSstWinOpenExt"; + GRWINOPENEXT grSstWinOpenExt = (GRWINOPENEXT)grGetProcAddress(strSstWinOpenExt); + if (grSstWinOpenExt) + gfx_context = grSstWinOpenExt ((FxU32)NULL, + res_data, + GR_REFRESH_60Hz, + GR_COLORFORMAT_RGBA, + GR_ORIGIN_UPPER_LEFT, + fb_emulation_enabled?GR_PIXFMT_RGB_565:GR_PIXFMT_ARGB_8888, //32b color is not compatible with fb emulation + 2, // Double-buffering + 1); // 1 auxillary buffer + } + if (!gfx_context) + gfx_context = grSstWinOpen ((FxU32)NULL, + res_data, + GR_REFRESH_60Hz, + GR_COLORFORMAT_RGBA, + GR_ORIGIN_UPPER_LEFT, + 2, // Double-buffering + 1); // 1 auxillary buffer + + if (!gfx_context) + { + ERRLOG("Error setting display mode"); + // grSstWinClose (gfx_context); + grGlideShutdown (); + return FALSE; + } + + fullscreen = TRUE; + to_fullscreen = FALSE; + + + // get the # of TMUs available + grGet (GR_NUM_TMU, 4, (FxI32*)&voodoo.num_tmu); + // get maximal texture size + grGet (GR_MAX_TEXTURE_SIZE, 4, (FxI32*)&voodoo.max_tex_size); + voodoo.sup_large_tex = (voodoo.max_tex_size > 256 && !(settings.hacks & hack_PPL)); + + //num_tmu = 1; + if (voodoo.tex_UMA) + { + GetTexAddr = GetTexAddrUMA; + voodoo.tex_min_addr[0] = voodoo.tex_min_addr[1] = grTexMinAddress(GR_TMU0); + voodoo.tex_max_addr[0] = voodoo.tex_max_addr[1] = grTexMaxAddress(GR_TMU0); + } + else + { + GetTexAddr = GetTexAddrNonUMA; + voodoo.tex_min_addr[0] = grTexMinAddress(GR_TMU0); + voodoo.tex_min_addr[1] = grTexMinAddress(GR_TMU1); + voodoo.tex_max_addr[0] = grTexMaxAddress(GR_TMU0); + voodoo.tex_max_addr[1] = grTexMaxAddress(GR_TMU1); + } + + if (strstr (extensions, "TEXMIRROR") && !(settings.hacks&hack_Zelda)) //zelda's trees suffer from hardware mirroring + voodoo.sup_mirroring = 1; + else + voodoo.sup_mirroring = 0; + + if (strstr (extensions, "TEXFMT")) //VSA100 texture format extension + voodoo.sup_32bit_tex = TRUE; + else + voodoo.sup_32bit_tex = FALSE; + + voodoo.gamma_correction = 0; + if (strstr(extensions, "GETGAMMA")) + grGet(GR_GAMMA_TABLE_ENTRIES, sizeof(voodoo.gamma_table_size), &voodoo.gamma_table_size); + + if (fb_hwfbe_enabled) + { + if (char * extstr = (char*)strstr(extensions, "TEXTUREBUFFER")) + { + if (!strncmp(extstr, "TEXTUREBUFFER", 13)) + { + char strTextureBufferExt[] = "grTextureBufferExt"; + grTextureBufferExt = (GRTEXBUFFEREXT) grGetProcAddress(strTextureBufferExt); + char strTextureAuxBufferExt[] = "grTextureAuxBufferExt"; + grTextureAuxBufferExt = (GRTEXBUFFEREXT) grGetProcAddress(strTextureAuxBufferExt); + char strAuxBufferExt[] = "grAuxBufferExt"; + grAuxBufferExt = (GRAUXBUFFEREXT) grGetProcAddress(strAuxBufferExt); + } + } + else + settings.frame_buffer &= ~fb_hwfbe; + } + else + grTextureBufferExt = 0; + + grStippleModeExt = (GRSTIPPLE)grStippleMode; + grStipplePatternExt = (GRSTIPPLE)grStipplePattern; + + if (grStipplePatternExt) + grStipplePatternExt(settings.stipple_pattern); + +// char strKeyPressedExt[] = "grKeyPressedExt"; +// grKeyPressed = (FxBool (FX_CALL *)(FxU32))grGetProcAddress (strKeyPressedExt); + + InitCombine(); + +#ifdef SIMULATE_VOODOO1 + voodoo.num_tmu = 1; + voodoo.sup_mirroring = 0; +#endif + +#ifdef SIMULATE_BANSHEE + voodoo.num_tmu = 1; + voodoo.sup_mirroring = 1; +#endif + + grCoordinateSpace (GR_WINDOW_COORDS); + grVertexLayout (GR_PARAM_XY, offsetof(VERTEX,x), GR_PARAM_ENABLE); + grVertexLayout (GR_PARAM_Q, offsetof(VERTEX,q), GR_PARAM_ENABLE); + grVertexLayout (GR_PARAM_Z, offsetof(VERTEX,z), GR_PARAM_ENABLE); + grVertexLayout (GR_PARAM_ST0, offsetof(VERTEX,coord[0]), GR_PARAM_ENABLE); + grVertexLayout (GR_PARAM_ST1, offsetof(VERTEX,coord[2]), GR_PARAM_ENABLE); + grVertexLayout (GR_PARAM_PARGB, offsetof(VERTEX,b), GR_PARAM_ENABLE); + + grCullMode(GR_CULL_NEGATIVE); + + if (settings.fog) //"FOGCOORD" extension + { + if (strstr (extensions, "FOGCOORD")) + { + GrFog_t fog_t[64]; + guFogGenerateLinear (fog_t, 0.0f, 255.0f);//(float)rdp.fog_multiplier + (float)rdp.fog_offset);//256.0f); + + for (int i = 63; i > 0; i--) + { + if (fog_t[i] - fog_t[i-1] > 63) + { + fog_t[i-1] = fog_t[i] - 63; + } + } + fog_t[0] = 0; + // for (int f = 0; f < 64; f++) + // { + // FRDP("fog[%d]=%d->%f\n", f, fog_t[f], guFogTableIndexToW(f)); + // } + grFogTable (fog_t); + grVertexLayout (GR_PARAM_FOG_EXT, offsetof(VERTEX,f), GR_PARAM_ENABLE); + } + else //not supported + settings.fog = FALSE; + } + + grDepthBufferMode (GR_DEPTHBUFFER_ZBUFFER); + grDepthBufferFunction(GR_CMP_LESS); + grDepthMask(FXTRUE); + + settings.res_x = settings.scr_res_x; + settings.res_y = settings.scr_res_y; + ChangeSize (); + + guLoadTextures (); + ClearCache (); + + grCullMode (GR_CULL_DISABLE); + grDepthBufferMode (GR_DEPTHBUFFER_ZBUFFER); + grDepthBufferFunction (GR_CMP_ALWAYS); + grRenderBuffer(GR_BUFFER_BACKBUFFER); + grColorMask (FXTRUE, FXTRUE); + grDepthMask (FXTRUE); + grBufferClear (0, 0, 0xFFFF); + grBufferSwap (0); + grBufferClear (0, 0, 0xFFFF); + grDepthMask (FXFALSE); + grTexFilterMode (0, GR_TEXTUREFILTER_BILINEAR, GR_TEXTUREFILTER_BILINEAR); + grTexFilterMode (1, GR_TEXTUREFILTER_BILINEAR, GR_TEXTUREFILTER_BILINEAR); + grTexClampMode (0, GR_TEXTURECLAMP_CLAMP, GR_TEXTURECLAMP_CLAMP); + grTexClampMode (1, GR_TEXTURECLAMP_CLAMP, GR_TEXTURECLAMP_CLAMP); + grClipWindow (0, 0, settings.scr_res_x, settings.scr_res_y); + rdp.update |= UPDATE_SCISSOR | UPDATE_COMBINE | UPDATE_ZBUF_ENABLED | UPDATE_CULL_MODE; + +#ifdef TEXTURE_FILTER // Hiroshi Morii + if (!settings.ghq_use) + { + settings.ghq_use = settings.ghq_fltr || settings.ghq_enht /*|| settings.ghq_cmpr*/ || settings.ghq_hirs; + if (settings.ghq_use) + { + /* Plugin path */ + int options = texfltr[settings.ghq_fltr]|texenht[settings.ghq_enht]|texcmpr[settings.ghq_cmpr]|texhirs[settings.ghq_hirs]; + if (settings.ghq_enht_cmpr) + options |= COMPRESS_TEX; + if (settings.ghq_hirs_cmpr) + options |= COMPRESS_HIRESTEX; + // if (settings.ghq_enht_tile) + // options |= TILE_TEX; + if (settings.ghq_hirs_tile) + options |= TILE_HIRESTEX; + if (settings.ghq_enht_f16bpp) + options |= FORCE16BPP_TEX; + if (settings.ghq_hirs_f16bpp) + options |= FORCE16BPP_HIRESTEX; + if (settings.ghq_enht_gz) + options |= GZ_TEXCACHE; + if (settings.ghq_hirs_gz) + options |= GZ_HIRESTEXCACHE; + if (settings.ghq_cache_save) + options |= (DUMP_TEXCACHE|DUMP_HIRESTEXCACHE); + if (settings.ghq_hirs_let_texartists_fly) + options |= LET_TEXARTISTS_FLY; + if (settings.ghq_hirs_dump) + options |= DUMP_TEX; + + ghq_dmptex_toggle_key = 0; + + swprintf(romname, sizeof(romname) / sizeof(*romname), L"%hs", rdp.RomName); + swprintf(foldername, sizeof(foldername) / sizeof(*foldername), L"%hs", ConfigGetUserDataPath()); + swprintf(cachename, sizeof(cachename) / sizeof(*cachename), L"%hs", ConfigGetUserCachePath()); + + settings.ghq_use = (int)ext_ghq_init(voodoo.max_tex_size, // max texture width supported by hardware + voodoo.max_tex_size, // max texture height supported by hardware + voodoo.sup_32bit_tex?32:16, // max texture bpp supported by hardware + options, + settings.ghq_cache_size * 1024*1024, // cache texture to system memory + foldername, + cachename, + romname, // name of ROM. must be no longer than 256 characters + DisplayLoadProgress); + } + } + if (settings.ghq_use && strstr (extensions, "TEXMIRROR")) + voodoo.sup_mirroring = 1; +#endif + + return TRUE; +} + +void ReleaseGfx () +{ + VLOG("ReleaseGfx ()\n"); + + // Restore gamma settings + if (voodoo.gamma_correction) + { + if (voodoo.gamma_table_r) + grLoadGammaTable(voodoo.gamma_table_size, voodoo.gamma_table_r, voodoo.gamma_table_g, voodoo.gamma_table_b); + else + guGammaCorrectionRGB(1.3f, 1.3f, 1.3f); //1.3f is default 3dfx gamma for everything but desktop + voodoo.gamma_correction = 0; + } + + // Release graphics + grSstWinClose (gfx_context); + + // Shutdown glide + grGlideShutdown(); + + fullscreen = FALSE; + rdp.window_changed = TRUE; +} + +// new API code begins here! + +#ifdef __cplusplus +extern "C" { +#endif + +EXPORT void CALL ReadScreen2(void *dest, int *width, int *height, int front) +{ + VLOG("CALL ReadScreen2 ()\n"); + *width = settings.res_x; + *height = settings.res_y; + if (dest) + { + BYTE * line = (BYTE*)dest; + if (!fullscreen) + { + for (wxUint32 y=0; y + if (settings.ghq_use) + { + ext_ghq_shutdown(); + settings.ghq_use = 0; + } +#endif + if (fullscreen) + ReleaseGfx (); + ZLUT_release(); + ClearCache (); + delete[] voodoo.gamma_table_r; + voodoo.gamma_table_r = 0; + delete[] voodoo.gamma_table_g; + voodoo.gamma_table_g = 0; + delete[] voodoo.gamma_table_b; + voodoo.gamma_table_b = 0; +} + +/****************************************************************** +Function: DllTest +Purpose: This function is optional function that is provided +to allow the user to test the dll +input: a handle to the window that calls this function +output: none +*******************************************************************/ +void CALL DllTest ( HWND hParent ) +{ +} + +/****************************************************************** +Function: DrawScreen +Purpose: This function is called when the emulator receives a +WM_PAINT message. This allows the gfx to fit in when +it is being used in the desktop. +input: none +output: none +*******************************************************************/ +void CALL DrawScreen (void) +{ + VLOG ("DrawScreen ()\n"); +} + +/****************************************************************** +Function: GetDllInfo +Purpose: This function allows the emulator to gather information +about the dll by filling in the PluginInfo structure. +input: a pointer to a PLUGIN_INFO stucture that needs to be +filled by the function. (see def above) +output: none +*******************************************************************/ +void CALL GetDllInfo ( PLUGIN_INFO * PluginInfo ) +{ + VLOG ("GetDllInfo ()\n"); + PluginInfo->Version = 0x0103; // Set to 0x0103 + PluginInfo->Type = PLUGIN_TYPE_GFX; // Set to PLUGIN_TYPE_GFX + sprintf (PluginInfo->Name, "Glide64mk2 " G64_VERSION RELTIME); // Name of the DLL + + // If DLL supports memory these memory options then set them to TRUE or FALSE + // if it does not support it + PluginInfo->NormalMemory = TRUE; // a normal wxUint8 array + PluginInfo->MemoryBswaped = TRUE; // a normal wxUint8 array where the memory has been pre + // bswap on a dword (32 bits) boundry +} + +#ifndef WIN32 +BOOL WINAPI QueryPerformanceCounter(PLARGE_INTEGER counter) +{ + struct timeval tv; + + /* generic routine */ + gettimeofday( &tv, NULL ); + counter->QuadPart = (LONGLONG)tv.tv_usec + (LONGLONG)tv.tv_sec * 1000000; + return TRUE; +} + +BOOL WINAPI QueryPerformanceFrequency(PLARGE_INTEGER frequency) +{ + frequency->s.LowPart= 1000000; + frequency->s.HighPart= 0; + return TRUE; +} +#endif + +/****************************************************************** +Function: InitiateGFX +Purpose: This function is called when the DLL is started to give +information from the emulator that the n64 graphics +uses. This is not called from the emulation thread. +Input: Gfx_Info is passed to this function which is defined +above. +Output: TRUE on success +FALSE on failure to initialise + +** note on interrupts **: +To generate an interrupt set the appropriate bit in MI_INTR_REG +and then call the function CheckInterrupts to tell the emulator +that there is a waiting interrupt. +*******************************************************************/ + +EXPORT int CALL InitiateGFX (GFX_INFO Gfx_Info) +{ + VLOG ("InitiateGFX (*)\n"); + voodoo.num_tmu = 2; + + // Assume scale of 1 for debug purposes + rdp.scale_x = 1.0f; + rdp.scale_y = 1.0f; + + memset (&settings, 0, sizeof(SETTINGS)); + ReadSettings (); + char name[21] = "DEFAULT"; + ReadSpecialSettings (name); + settings.res_data_org = settings.res_data; +#ifdef FPS + QueryPerformanceFrequency (&perf_freq); + QueryPerformanceCounter (&fps_last); +#endif + + debug_init (); // Initialize debugger + + gfx = Gfx_Info; + +#ifdef WINPROC_OVERRIDE + // [H.Morii] inject our own winproc so that "alt-enter to fullscreen" + // message is shown when the emulator window is activated. + WNDPROC curWndProc = (WNDPROC)GetWindowLong(gfx.hWnd, GWL_WNDPROC); + if (curWndProc && curWndProc != (WNDPROC)WndProc) { + oldWndProc = (WNDPROC)SetWindowLong (gfx.hWnd, GWL_WNDPROC, (long)WndProc); + } +#endif + + util_init (); + math_init (); + TexCacheInit (); + CRC_BuildTable(); + CountCombine(); + if (fb_depth_render_enabled) + ZLUT_init(); + + char strConfigWrapperExt[] = "grConfigWrapperExt"; + GRCONFIGWRAPPEREXT grConfigWrapperExt = (GRCONFIGWRAPPEREXT)grGetProcAddress(strConfigWrapperExt); + if (grConfigWrapperExt) + grConfigWrapperExt(settings.wrpResolution, settings.wrpVRAM * 1024 * 1024, settings.wrpFBO, settings.wrpAnisotropic); + + grGlideInit (); + grSstSelect (0); + const char *extensions = grGetString (GR_EXTENSION); + grGlideShutdown (); + if (strstr (extensions, "EVOODOO")) + { + evoodoo = 1; + voodoo.has_2mb_tex_boundary = 0; + } + else { + evoodoo = 0; + voodoo.has_2mb_tex_boundary = 1; + } + + return TRUE; +} + +/****************************************************************** +Function: MoveScreen +Purpose: This function is called in response to the emulator +receiving a WM_MOVE passing the xpos and ypos passed +from that message. +input: xpos - the x-coordinate of the upper-left corner of the +client area of the window. +ypos - y-coordinate of the upper-left corner of the +client area of the window. +output: none +*******************************************************************/ +EXPORT void CALL MoveScreen (int xpos, int ypos) +{ + rdp.window_changed = TRUE; +} + +/****************************************************************** +Function: ResizeVideoOutput +Purpose: This function is called to force us to resize our output OpenGL window. + This is currently unsupported, and should never be called because we do + not pass the RESIZABLE flag to VidExt_SetVideoMode when initializing. +input: new width and height +output: none +*******************************************************************/ +EXPORT void CALL ResizeVideoOutput(int Width, int Height) +{ +} + +/****************************************************************** +Function: RomClosed +Purpose: This function is called when a rom is closed. +input: none +output: none +*******************************************************************/ +EXPORT void CALL RomClosed (void) +{ + VLOG ("RomClosed ()\n"); + + CLOSE_RDP_LOG (); + CLOSE_RDP_E_LOG (); + rdp.window_changed = TRUE; + romopen = FALSE; + if (fullscreen && evoodoo) + ReleaseGfx (); +} + +static void CheckDRAMSize() +{ + wxUint32 test; + GLIDE64_TRY + { + test = gfx.RDRAM[0x007FFFFF] + 1; + } + GLIDE64_CATCH + { + test = 0; + } + if (test) + BMASK = 0x7FFFFF; + else + BMASK = WMASK; +#ifdef LOGGING + sprintf (out_buf, "Detected RDRAM size: %08lx\n", BMASK); + LOG (out_buf); +#endif +} + +/****************************************************************** +Function: RomOpen +Purpose: This function is called when a rom is open. (from the +emulation thread) +input: none +output: none +*******************************************************************/ +EXPORT int CALL RomOpen (void) +{ + VLOG ("RomOpen ()\n"); + no_dlist = true; + romopen = TRUE; + ucode_error_report = TRUE; // allowed to report ucode errors + rdp_reset (); + + // Get the country code & translate to NTSC(0) or PAL(1) + wxUint16 code = ((wxUint16*)gfx.HEADER)[0x1F^1]; + + if (code == 0x4400) region = 1; // Germany (PAL) + if (code == 0x4500) region = 0; // USA (NTSC) + if (code == 0x4A00) region = 0; // Japan (NTSC) + if (code == 0x5000) region = 1; // Europe (PAL) + if (code == 0x5500) region = 0; // Australia (NTSC) + + char name[21] = "DEFAULT"; + ReadSpecialSettings (name); + + // get the name of the ROM + for (int i=0; i<20; i++) + name[i] = gfx.HEADER[(32+i)^3]; + name[20] = 0; + + // remove all trailing spaces + while (name[strlen(name)-1] == ' ') + name[strlen(name)-1] = 0; + + strncpy(rdp.RomName, name, sizeof(name)); + ReadSpecialSettings (name); + ClearCache (); + + CheckDRAMSize(); + + OPEN_RDP_LOG (); + OPEN_RDP_E_LOG (); + + + // ** EVOODOO EXTENSIONS ** + if (!fullscreen) + { + grGlideInit (); + grSstSelect (0); + } + const char *extensions = grGetString (GR_EXTENSION); + if (!fullscreen) + { + grGlideShutdown (); + + if (strstr (extensions, "EVOODOO")) + evoodoo = 1; + else + evoodoo = 0; + + if (evoodoo) + InitGfx (); + } + + if (strstr (extensions, "ROMNAME")) + { + char strSetRomName[] = "grSetRomName"; + void (FX_CALL *grSetRomName)(char*); + grSetRomName = (void (FX_CALL *)(char*))grGetProcAddress (strSetRomName); + grSetRomName (name); + } + // ** + return true; +} + +/****************************************************************** +Function: ShowCFB +Purpose: Useally once Dlists are started being displayed, cfb is +ignored. This function tells the dll to start displaying +them again. +input: none +output: none +*******************************************************************/ +bool no_dlist = true; +EXPORT void CALL ShowCFB (void) +{ + no_dlist = true; + VLOG ("ShowCFB ()\n"); +} + +EXPORT void CALL SetRenderingCallback(void (*callback)(int)) +{ + VLOG("CALL SetRenderingCallback (*)\n"); + renderCallback = callback; +} + +void drawViRegBG() +{ + LRDP("drawViRegBG\n"); + const wxUint32 VIwidth = *gfx.VI_WIDTH_REG; + FB_TO_SCREEN_INFO fb_info; + fb_info.width = VIwidth; + fb_info.height = (wxUint32)rdp.vi_height; + if (fb_info.height == 0) + { + LRDP("Image height = 0 - skipping\n"); + return; + } + fb_info.ul_x = 0; + + fb_info.lr_x = VIwidth - 1; + // fb_info.lr_x = (wxUint32)rdp.vi_width - 1; + fb_info.ul_y = 0; + fb_info.lr_y = fb_info.height - 1; + fb_info.opaque = 1; + fb_info.addr = *gfx.VI_ORIGIN_REG; + fb_info.size = *gfx.VI_STATUS_REG & 3; + rdp.last_bg = fb_info.addr; + + bool drawn = DrawFrameBufferToScreen(fb_info); + if (settings.hacks&hack_Lego && drawn) + { + rdp.updatescreen = 1; + newSwapBuffers (); + DrawFrameBufferToScreen(fb_info); + } +} + +} + +void drawNoFullscreenMessage(); + +void DrawFrameBuffer () +{ + if (!fullscreen) + { + drawNoFullscreenMessage(); + } + if (to_fullscreen) + GoToFullScreen(); + + if (fullscreen) + { + grDepthMask (FXTRUE); + grColorMask (FXTRUE, FXTRUE); + grBufferClear (0, 0, 0xFFFF); + drawViRegBG(); + } +} + +extern "C" { +/****************************************************************** +Function: UpdateScreen +Purpose: This function is called in response to a vsync of the +screen were the VI bit in MI_INTR_REG has already been +set +input: none +output: none +*******************************************************************/ +wxUint32 update_screen_count = 0; +EXPORT void CALL UpdateScreen (void) +{ +#ifdef LOG_KEY + if (CheckKeyPressed(G64_VK_SPACE, 0x0001)) + { + LOG ("KEY!!!\n"); + } +#endif + char out_buf[128]; + sprintf (out_buf, "UpdateScreen (). Origin: %08x, Old origin: %08x, width: %d\n", *gfx.VI_ORIGIN_REG, rdp.vi_org_reg, *gfx.VI_WIDTH_REG); + VLOG (out_buf); + LRDP(out_buf); + + wxUint32 width = (*gfx.VI_WIDTH_REG) << 1; + if (fullscreen && (*gfx.VI_ORIGIN_REG > width)) + update_screen_count++; +//TODO-PORT: wx times +#ifdef FPS + // vertical interrupt has occurred, increment counter + vi_count ++; + + // Check frames per second + LARGE_INTEGER difference; + QueryPerformanceCounter (&fps_next); + difference.QuadPart = fps_next.QuadPart - fps_last.QuadPart; + float diff_secs = (float)((double)difference.QuadPart / (double)perf_freq.QuadPart); + if (diff_secs > 0.5f) + { + fps = (float)(fps_count / diff_secs); + vi = (float)(vi_count / diff_secs); + ntsc_percent = vi / 0.6f; + pal_percent = vi / 0.5f; + fps_last = fps_next; + fps_count = 0; + vi_count = 0; + } +#endif + //* + wxUint32 limit = (settings.hacks&hack_Lego) ? 15 : 30; + if ((settings.frame_buffer&fb_cpu_write_hack) && (update_screen_count > limit) && (rdp.last_bg == 0)) + { + LRDP("DirectCPUWrite hack!\n"); + update_screen_count = 0; + no_dlist = true; + ClearCache (); + UpdateScreen(); + return; + } + //*/ + //* + if( no_dlist ) + { + if( *gfx.VI_ORIGIN_REG > width ) + { + ChangeSize (); + LRDP("ChangeSize done\n"); + DrawFrameBuffer(); + LRDP("DrawFrameBuffer done\n"); + rdp.updatescreen = 1; + newSwapBuffers (); + } + return; + } + //*/ + if (settings.swapmode == 0) + newSwapBuffers (); +} + +static void DrawWholeFrameBufferToScreen() +{ + static wxUint32 toScreenCI = 0; + if (rdp.ci_width < 200) + return; + if (rdp.cimg == toScreenCI) + return; + toScreenCI = rdp.cimg; + FB_TO_SCREEN_INFO fb_info; + fb_info.addr = rdp.cimg; + fb_info.size = rdp.ci_size; + fb_info.width = rdp.ci_width; + fb_info.height = rdp.ci_height; + if (fb_info.height == 0) + return; + fb_info.ul_x = 0; + fb_info.lr_x = rdp.ci_width-1; + fb_info.ul_y = 0; + fb_info.lr_y = rdp.ci_height-1; + fb_info.opaque = 0; + DrawFrameBufferToScreen(fb_info); + if (!(settings.frame_buffer & fb_ref)) + memset(gfx.RDRAM+rdp.cimg, 0, (rdp.ci_width*rdp.ci_height)<>1); +} + +static void GetGammaTable() +{ + char strGetGammaTableExt[] = "grGetGammaTableExt"; + void (FX_CALL *grGetGammaTableExt)(FxU32, FxU32*, FxU32*, FxU32*) = + (void (FX_CALL *)(FxU32, FxU32*, FxU32*, FxU32*))grGetProcAddress(strGetGammaTableExt); + if (grGetGammaTableExt) + { + voodoo.gamma_table_r = new FxU32[voodoo.gamma_table_size]; + voodoo.gamma_table_g = new FxU32[voodoo.gamma_table_size]; + voodoo.gamma_table_b = new FxU32[voodoo.gamma_table_size]; + grGetGammaTableExt(voodoo.gamma_table_size, voodoo.gamma_table_r, voodoo.gamma_table_g, voodoo.gamma_table_b); + } +} + +} +wxUint32 curframe = 0; +void newSwapBuffers() +{ + if (!rdp.updatescreen) + return; + + rdp.updatescreen = 0; + + LRDP("swapped\n"); + + // Allow access to the whole screen + if (fullscreen) + { + rdp.update |= UPDATE_SCISSOR | UPDATE_COMBINE | UPDATE_ZBUF_ENABLED | UPDATE_CULL_MODE; + grClipWindow (0, 0, settings.scr_res_x, settings.scr_res_y); + grDepthBufferFunction (GR_CMP_ALWAYS); + grDepthMask (FXFALSE); + grCullMode (GR_CULL_DISABLE); + + if ((settings.show_fps & 0xF) || settings.clock) + set_message_combiner (); +#ifdef FPS + float y = (float)settings.res_y; + if (settings.show_fps & 0x0F) + { + if (settings.show_fps & 4) + { + if (region) // PAL + output (0, y, 0, "%d%% ", (int)pal_percent); + else + output (0, y, 0, "%d%% ", (int)ntsc_percent); + y -= 16; + } + if (settings.show_fps & 2) + { + output (0, y, 0, "VI/s: %.02f ", vi); + y -= 16; + } + if (settings.show_fps & 1) + output (0, y, 0, "FPS: %.02f ", fps); + } +#endif + + if (settings.clock) + { + if (settings.clock_24_hr) + { + time_t ltime; + time (<ime); + tm *cur_time = localtime (<ime); + + sprintf (out_buf, "%.2d:%.2d:%.2d", cur_time->tm_hour, cur_time->tm_min, cur_time->tm_sec); + } + else + { + char ampm[] = "AM"; + time_t ltime; + + time (<ime); + tm *cur_time = localtime (<ime); + + if (cur_time->tm_hour >= 12) + { + strcpy (ampm, "PM"); + if (cur_time->tm_hour != 12) + cur_time->tm_hour -= 12; + } + if (cur_time->tm_hour == 0) + cur_time->tm_hour = 12; + + if (cur_time->tm_hour >= 10) + sprintf (out_buf, "%.5s %s", asctime(cur_time) + 11, ampm); + else + sprintf (out_buf, " %.4s %s", asctime(cur_time) + 12, ampm); + } + output ((float)(settings.res_x - 68), y, 0, out_buf, 0); + } + //hotkeys + if (CheckKeyPressed(G64_VK_BACK, 0x0001)) + { + hotkey_info.hk_filtering = 100; + if (settings.filtering < 2) + settings.filtering++; + else + settings.filtering = 0; + } + if ((abs((int)(frame_count - curframe)) > 3 ) && CheckKeyPressed(G64_VK_ALT, 0x8000)) //alt + + { + if (CheckKeyPressed(G64_VK_B, 0x8000)) //b + { + hotkey_info.hk_motionblur = 100; + hotkey_info.hk_ref = 0; + curframe = frame_count; + settings.frame_buffer ^= fb_motionblur; + } + else if (CheckKeyPressed(G64_VK_V, 0x8000)) //v + { + hotkey_info.hk_ref = 100; + hotkey_info.hk_motionblur = 0; + curframe = frame_count; + settings.frame_buffer ^= fb_ref; + } + } + if (settings.buff_clear && (hotkey_info.hk_ref || hotkey_info.hk_motionblur || hotkey_info.hk_filtering)) + { + set_message_combiner (); + char buf[256]; + buf[0] = 0; + char * message = 0; + if (hotkey_info.hk_ref) + { + if (settings.frame_buffer & fb_ref) + message = strcat(buf, "FB READ ALWAYS: ON"); + else + message = strcat(buf, "FB READ ALWAYS: OFF"); + hotkey_info.hk_ref--; + } + if (hotkey_info.hk_motionblur) + { + if (settings.frame_buffer & fb_motionblur) + message = strcat(buf, " MOTION BLUR: ON"); + else + message = strcat(buf, " MOTION BLUR: OFF"); + hotkey_info.hk_motionblur--; + } + if (hotkey_info.hk_filtering) + { + switch (settings.filtering) + { + case 0: + message = strcat(buf, " FILTERING MODE: AUTOMATIC"); + break; + case 1: + message = strcat(buf, " FILTERING MODE: FORCE BILINEAR"); + break; + case 2: + message = strcat(buf, " FILTERING MODE: FORCE POINT-SAMPLED"); + break; + } + hotkey_info.hk_filtering--; + } + output (120.0f, (float)settings.res_y, 0, message, 0); + } + } + #ifdef OLDAPI + if (capture_screen) + { + //char path[256]; + // Make the directory if it doesn't exist + if (!wxDirExists(capture_path)) + wxMkdir(capture_path); + wxString path; + wxString romName = rdp.RomName; + romName.Replace(wxT(" "), wxT("_"), true); + romName.Replace(wxT(":"), wxT(";"), true); + + for (int i=1; ; i++) + { + path = capture_path; + path += wxT("Glide64mk2_"); + path += romName; + path += wxT("_"); + if (i < 10) + path += wxT("0"); + path << i << wxT(".") << ScreenShotFormats[settings.ssformat].extension; + if (!wxFileName::FileExists(path)) + break; + } + + const wxUint32 offset_x = (wxUint32)rdp.offset_x; + const wxUint32 offset_y = (wxUint32)rdp.offset_y; + const wxUint32 image_width = settings.scr_res_x - offset_x*2; + const wxUint32 image_height = settings.scr_res_y - offset_y*2; + + GrLfbInfo_t info; + info.size = sizeof(GrLfbInfo_t); + if (grLfbLock (GR_LFB_READ_ONLY, + GR_BUFFER_BACKBUFFER, + GR_LFBWRITEMODE_565, + GR_ORIGIN_UPPER_LEFT, + FXFALSE, + &info)) + { + wxUint8 *ssimg = (wxUint8*)malloc(image_width * image_height * 3); // will be free in wxImage destructor + int sspos = 0; + wxUint32 offset_src = info.strideInBytes * offset_y; + + // Copy the screen + if (info.writeMode == GR_LFBWRITEMODE_8888) + { + wxUint32 col; + for (wxUint32 y = 0; y < image_height; y++) + { + wxUint32 *ptr = (wxUint32*)((wxUint8*)info.lfbPtr + offset_src); + ptr += offset_x; + for (wxUint32 x = 0; x < image_width; x++) + { + col = *(ptr++); + ssimg[sspos++] = (wxUint8)((col >> 16) & 0xFF); + ssimg[sspos++] = (wxUint8)((col >> 8) & 0xFF); + ssimg[sspos++] = (wxUint8)(col & 0xFF); + } + offset_src += info.strideInBytes; + } + } + else + { + wxUint16 col; + for (wxUint32 y = 0; y < image_height; y++) + { + wxUint16 *ptr = (wxUint16*)((wxUint8*)info.lfbPtr + offset_src); + ptr += offset_x; + for (wxUint32 x = 0; x < image_width; x++) + { + col = *(ptr++); + ssimg[sspos++] = (wxUint8)((float)(col >> 11) / 31.0f * 255.0f); + ssimg[sspos++] = (wxUint8)((float)((col >> 5) & 0x3F) / 63.0f * 255.0f); + ssimg[sspos++] = (wxUint8)((float)(col & 0x1F) / 31.0f * 255.0f); + } + offset_src += info.strideInBytes; + } + } + // Unlock the backbuffer + grLfbUnlock (GR_LFB_READ_ONLY, GR_BUFFER_BACKBUFFER); + wxImage screenshot(image_width, image_height, ssimg); + screenshot.SaveFile(path, ScreenShotFormats[settings.ssformat].type); + capture_screen = 0; + } + } + #endif + + // Capture the screen if debug capture is set + if (_debugger.capture) + { + // Allocate the screen + _debugger.screen = new wxUint8 [(settings.res_x*settings.res_y) << 1]; + + // Lock the backbuffer (already rendered) + GrLfbInfo_t info; + info.size = sizeof(GrLfbInfo_t); + while (!grLfbLock (GR_LFB_READ_ONLY, + GR_BUFFER_BACKBUFFER, + GR_LFBWRITEMODE_565, + GR_ORIGIN_UPPER_LEFT, + FXFALSE, + &info)); + + wxUint32 offset_src=0, offset_dst=0; + + // Copy the screen + for (wxUint32 y=0; y> 19) & 0x1F); + g = (wxUint8)((col >> 10) & 0x3F); + b = (wxUint8)((col >> 3) & 0x1F); + dst[x] = (r<<11)|(g<<5)|b; + } + } + else + { + memcpy (_debugger.screen + offset_dst, (wxUint8*)info.lfbPtr + offset_src, settings.res_x << 1); + } + offset_dst += settings.res_x << 1; + offset_src += info.strideInBytes; + } + + // Unlock the backbuffer + grLfbUnlock (GR_LFB_READ_ONLY, GR_BUFFER_BACKBUFFER); + } + + if (fullscreen && debugging) + { + debug_keys (); + debug_cacheviewer (); + debug_mouse (); + } + + if (settings.frame_buffer & fb_read_back_to_screen) + DrawWholeFrameBufferToScreen(); + + if (fullscreen) + { + if (fb_hwfbe_enabled && !(settings.hacks&hack_RE2) && !evoodoo) + grAuxBufferExt( GR_BUFFER_AUXBUFFER ); + grBufferSwap (settings.vsync); + fps_count ++; + if (*gfx.VI_STATUS_REG&0x08) //gamma correction is used + { + if (!voodoo.gamma_correction) + { + if (voodoo.gamma_table_size && !voodoo.gamma_table_r) + GetGammaTable(); //save initial gamma tables + guGammaCorrectionRGB(2.0f, 2.0f, 2.0f); //with gamma=2.0 gamma table is the same, as in N64 + voodoo.gamma_correction = 1; + } + } + else + { + if (voodoo.gamma_correction) + { + if (voodoo.gamma_table_r) + grLoadGammaTable(voodoo.gamma_table_size, voodoo.gamma_table_r, voodoo.gamma_table_g, voodoo.gamma_table_b); + else + guGammaCorrectionRGB(1.3f, 1.3f, 1.3f); //1.3f is default 3dfx gamma for everything but desktop + voodoo.gamma_correction = 0; + } + } + } + + if (_debugger.capture) + debug_capture (); + + if (fullscreen) + { + if (debugging || settings.wireframe || settings.buff_clear || (settings.hacks&hack_PPL && settings.ucode == 6)) + { + if (settings.hacks&hack_RE2 && fb_depth_render_enabled) + grDepthMask (FXFALSE); + else + grDepthMask (FXTRUE); + grBufferClear (0, 0, 0xFFFF); + } + /* //let the game to clear the buffers + else + { + grDepthMask (FXTRUE); + grColorMask (FXFALSE, FXFALSE); + grBufferClear (0, 0, 0xFFFF); + grColorMask (FXTRUE, FXTRUE); + } + */ + } + + if (settings.frame_buffer & fb_read_back_to_screen2) + DrawWholeFrameBufferToScreen(); + + frame_count ++; + + // Open/close debugger? + if (CheckKeyPressed(G64_VK_SCROLL, 0x0001)) + { + if (!debugging) + { + //if (settings.scr_res_x == 1024 && settings.scr_res_y == 768) + { + debugging = 1; + + // Recalculate screen size, don't resize screen + settings.res_x = (wxUint32)(settings.scr_res_x * 0.625f); + settings.res_y = (wxUint32)(settings.scr_res_y * 0.625f); + + ChangeSize (); + } + } + else + { + debugging = 0; + + settings.res_x = settings.scr_res_x; + settings.res_y = settings.scr_res_y; + + ChangeSize (); + } + } + + // Debug capture? + if (/*fullscreen && */debugging && CheckKeyPressed(G64_VK_INSERT, 0x0001)) + { + _debugger.capture = 1; + } +} + +extern "C" +{ + +/****************************************************************** +Function: ViStatusChanged +Purpose: This function is called to notify the dll that the +ViStatus registers value has been changed. +input: none +output: none +*******************************************************************/ +EXPORT void CALL ViStatusChanged (void) +{ +} + +/****************************************************************** +Function: ViWidthChanged +Purpose: This function is called to notify the dll that the +ViWidth registers value has been changed. +input: none +output: none +*******************************************************************/ +EXPORT void CALL ViWidthChanged (void) +{ +} + +#ifdef WINPROC_OVERRIDE +LRESULT CALLBACK WndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) + { + case WM_ACTIVATEAPP: + if (wParam == TRUE && !fullscreen) rdp.window_changed = TRUE; + break; + case WM_PAINT: + if (!fullscreen) rdp.window_changed = TRUE; + break; + + /* case WM_DESTROY: + SetWindowLong (gfx.hWnd, GWL_WNDPROC, (long)oldWndProc); + break;*/ + } + + return CallWindowProc(oldWndProc, hwnd, msg, wParam, lParam); +} +#endif + +} + +int CheckKeyPressed(int key, int mask) +{ +static Glide64Keys g64Keys; + if (settings.use_hotkeys == 0) + return 0; +#ifdef __WINDOWS__ + return (GetAsyncKeyState(g64Keys[key]) & mask); +#else + if (grKeyPressed) + return grKeyPressed(g64Keys[key]); +#endif + return 0; +} + + +#ifdef ALTTAB_FIX +int k_ctl=0, k_alt=0, k_del=0; + +LRESULT CALLBACK LowLevelKeyboardProc(int nCode, + WPARAM wParam, LPARAM lParam) +{ + if (!fullscreen) return CallNextHookEx(NULL, nCode, wParam, lParam); + + int TabKey = FALSE; + + PKBDLLHOOKSTRUCT p; + + if (nCode == HC_ACTION) + { + switch (wParam) { +case WM_KEYUP: case WM_SYSKEYUP: + p = (PKBDLLHOOKSTRUCT) lParam; + if (p->vkCode == 162) k_ctl = 0; + if (p->vkCode == 164) k_alt = 0; + if (p->vkCode == 46) k_del = 0; + goto do_it; + +case WM_KEYDOWN: case WM_SYSKEYDOWN: + p = (PKBDLLHOOKSTRUCT) lParam; + if (p->vkCode == 162) k_ctl = 1; + if (p->vkCode == 164) k_alt = 1; + if (p->vkCode == 46) k_del = 1; + goto do_it; + +do_it: + TabKey = + ((p->vkCode == VK_TAB) && ((p->flags & LLKHF_ALTDOWN) != 0)) || + ((p->vkCode == VK_ESCAPE) && ((p->flags & LLKHF_ALTDOWN) != 0)) || + ((p->vkCode == VK_ESCAPE) && ((GetKeyState(VK_CONTROL) & 0x8000) != 0)) || + (k_ctl && k_alt && k_del); + + break; + } + } + + if (TabKey) + { + k_ctl = 0; + k_alt = 0; + k_del = 0; + ReleaseGfx (); + } + + return CallNextHookEx(NULL, nCode, wParam, lParam); +} +#endif + +// +// DllMain - called when the DLL is loaded, use this to get the DLL's instance +// +#ifdef OLDAPI +class wxDLLApp : public wxApp +{ +public: + virtual bool OnInit(); +}; + +IMPLEMENT_APP_NO_MAIN(wxDLLApp) + +bool wxDLLApp::OnInit() +{ + if (mutexProcessDList == NULL) + mutexProcessDList = new wxMutex(wxMUTEX_DEFAULT); + wxImage::AddHandler(new wxPNGHandler); + wxImage::AddHandler(new wxJPEGHandler); + return true; +} + +#ifndef __WINDOWS__ +int __attribute__ ((constructor)) DllLoad(void); +int __attribute__ ((destructor)) DllUnload(void); +#endif + +// Called when the library is loaded and before dlopen() returns +int DllLoad(void) +{ + int argc = 0; + char **argv = NULL; + wxEntryStart(argc, argv); + if (wxTheApp) + return wxTheApp->CallOnInit() ? TRUE : FALSE; + return 0; +} + +// Called when the library is unloaded and before dlclose() returns +int DllUnload(void) +{ + if ( wxTheApp ) + wxTheApp->OnExit(); + wxEntryCleanup(); + return TRUE; +} + +#ifdef __WINDOWS__ +extern "C" int WINAPI DllMain (HINSTANCE hinstDLL, + wxUint32 fdwReason, + LPVOID lpReserved) +{ + sprintf (out_buf, "DllMain (%08lx - %d)\n", hinstDLL, fdwReason); + LOG (out_buf); + + if (fdwReason == DLL_PROCESS_ATTACH) + { + wxSetInstance(hinstDLL); + return DllLoad(); + } + else if (fdwReason == DLL_PROCESS_DETACH) + { + if (GFXWindow != NULL) + GFXWindow->SetHWND(NULL); + return DllUnload(); + } + return TRUE; +} + +void CALL ReadScreen(void **dest, int *width, int *height) +{ + *width = settings.res_x; + *height = settings.res_y; + wxUint8 * buff = (wxUint8*)malloc(settings.res_x * settings.res_y * 3); + wxUint8 * line = buff; + *dest = (void*)buff; + + if (!fullscreen) + { + for (wxUint32 y=0; y> 16) & 0xFF); + g = (wxUint8)((col >> 8) & 0xFF); + b = (wxUint8)(col & 0xFF); + line[x*3] = b; + line[x*3+1] = g; + line[x*3+2] = r; + } + line += settings.res_x * 3; + offset_src -= info.strideInBytes; + } + } + else + { + wxUint16 col; + for (wxUint32 y=0; y> 11) / 31.0f * 255.0f); + g = (wxUint8)((float)((col >> 5) & 0x3F) / 63.0f * 255.0f); + b = (wxUint8)((float)(col & 0x1F) / 31.0f * 255.0f); + line[x*3] = b; + line[x*3+1] = g; + line[x*3+2] = r; + } + line += settings.res_x * 3; + offset_src -= info.strideInBytes; + } + } + // Unlock the frontbuffer + grLfbUnlock (GR_LFB_READ_ONLY, GR_BUFFER_FRONTBUFFER); + } + LOG ("ReadScreen. Success.\n"); +} +#endif +#endif diff --git a/libmupen64plus/mupen64plus-video-glide64mk2/src/Glide64/rdp.cpp b/libmupen64plus/mupen64plus-video-glide64mk2/src/Glide64/rdp.cpp index 5d10a2297c..c8746f289b 100644 --- a/libmupen64plus/mupen64plus-video-glide64mk2/src/Glide64/rdp.cpp +++ b/libmupen64plus/mupen64plus-video-glide64mk2/src/Glide64/rdp.cpp @@ -1,151 +1,150 @@ -/* -* Glide64 - Glide video plugin for Nintendo 64 emulators. -* Copyright (c) 2002 Dave2001 -* Copyright (c) 2003-2009 Sergey 'Gonetz' Lipski -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -//**************************************************************** -// -// Glide64 - Glide Plugin for Nintendo 64 emulators -// Project started on December 29th, 2001 -// -// Authors: -// Dave2001, original author, founded the project in 2001, left it in 2002 -// Gugaman, joined the project in 2002, left it in 2002 -// Sergey 'Gonetz' Lipski, joined the project in 2002, main author since fall of 2002 -// Hiroshi 'KoolSmoky' Morii, joined the project in 2007 -// -//**************************************************************** -// -// To modify Glide64: -// * Write your name and (optional)email, commented by your work, so I know who did it, and so that you can find which parts you modified when it comes time to send it to me. -// * Do NOT send me the whole project or file that you modified. Take out your modified code sections, and tell me where to put them. If people sent the whole thing, I would have many different versions, but no idea how to combine them all. -// -//**************************************************************** - -#include -#include "Gfx_1.3.h" -#include "m64p.h" -#include "Ini.h" -#include "Config.h" -#include "3dmath.h" -#include "Util.h" -#include "Debugger.h" -#include "Combine.h" -#include "TexCache.h" -#include "TexBuffer.h" -#include "FBtoScreen.h" -#include "CRC.h" - -/* -const int NumOfFormats = 3; -SCREEN_SHOT_FORMAT ScreenShotFormats[NumOfFormats] = { {wxT("BMP"), wxT("bmp"), wxBITMAP_TYPE_BMP}, {wxT("PNG"), wxT("png"), wxBITMAP_TYPE_PNG}, {wxT("JPEG"), wxT("jpeg"), wxBITMAP_TYPE_JPEG} }; -*/ -const char *ACmp[] = { "NONE", "THRESHOLD", "UNKNOWN", "DITHER" }; - -const char *Mode0[] = { "COMBINED", "TEXEL0", - "TEXEL1", "PRIMITIVE", - "SHADE", "ENVIORNMENT", - "1", "NOISE", - "0", "0", - "0", "0", - "0", "0", - "0", "0" }; - -const char *Mode1[] = { "COMBINED", "TEXEL0", - "TEXEL1", "PRIMITIVE", - "SHADE", "ENVIORNMENT", - "CENTER", "K4", - "0", "0", - "0", "0", - "0", "0", - "0", "0" }; - -const char *Mode2[] = { "COMBINED", "TEXEL0", - "TEXEL1", "PRIMITIVE", - "SHADE", "ENVIORNMENT", - "SCALE", "COMBINED_ALPHA", - "T0_ALPHA", "T1_ALPHA", - "PRIM_ALPHA", "SHADE_ALPHA", - "ENV_ALPHA", "LOD_FRACTION", - "PRIM_LODFRAC", "K5", - "0", "0", - "0", "0", - "0", "0", - "0", "0", - "0", "0", - "0", "0", - "0", "0", - "0", "0" }; - -const char *Mode3[] = { "COMBINED", "TEXEL0", - "TEXEL1", "PRIMITIVE", - "SHADE", "ENVIORNMENT", - "1", "0" }; - -const char *Alpha0[] = { "COMBINED", "TEXEL0", - "TEXEL1", "PRIMITIVE", - "SHADE", "ENVIORNMENT", - "1", "0" }; - -#define Alpha1 Alpha0 -const char *Alpha2[] = { "LOD_FRACTION", "TEXEL0", - "TEXEL1", "PRIMITIVE", - "SHADE", "ENVIORNMENT", - "PRIM_LODFRAC", "0" }; -#define Alpha3 Alpha0 - -const char *FBLa[] = { "G_BL_CLR_IN", "G_BL_CLR_MEM", "G_BL_CLR_BL", "G_BL_CLR_FOG" }; -const char *FBLb[] = { "G_BL_A_IN", "G_BL_A_FOG", "G_BL_A_SHADE", "G_BL_0" }; -const char *FBLc[] = { "G_BL_CLR_IN", "G_BL_CLR_MEM", "G_BL_CLR_BL", "G_BL_CLR_FOG"}; -const char *FBLd[] = { "G_BL_1MA", "G_BL_A_MEM", "G_BL_1", "G_BL_0" }; - -const char *str_zs[] = { "G_ZS_PIXEL", "G_ZS_PRIM" }; - -const char *str_yn[] = { "NO", "YES" }; -const char *str_offon[] = { "OFF", "ON" }; - -const char *str_cull[] = { "DISABLE", "FRONT", "BACK", "BOTH" }; - -// I=intensity probably -const char *str_format[] = { "RGBA", "YUV", "CI", "IA", "I", "?", "?", "?" }; -const char *str_size[] = { "4bit", "8bit", "16bit", "32bit" }; -const char *str_cm[] = { "WRAP/NO CLAMP", "MIRROR/NO CLAMP", "WRAP/CLAMP", "MIRROR/CLAMP" }; -const char *str_lod[] = { "1", "2", "4", "8", "16", "32", "64", "128", "256", "512", "1024", "2048" }; -const char *str_aspect[] = { "1x8", "1x4", "1x2", "1x1", "2x1", "4x1", "8x1" }; - -const char *str_filter[] = { "Point Sampled", "Average (box)", "Bilinear" }; - -const char *str_tlut[] = { "TT_NONE", "TT_UNKNOWN", "TT_RGBA_16", "TT_IA_16" }; - -const char *str_dither[] = { "Pattern", "~Pattern", "Noise", "None" }; - -const char *CIStatus[] = { "ci_main", "ci_zimg", "ci_unknown", "ci_useless", - "ci_old_copy", "ci_copy", "ci_copy_self", - "ci_zcopy", "ci_aux", "ci_aux_copy" }; - +/* +* Glide64 - Glide video plugin for Nintendo 64 emulators. +* Copyright (c) 2002 Dave2001 +* Copyright (c) 2003-2009 Sergey 'Gonetz' Lipski +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +//**************************************************************** +// +// Glide64 - Glide Plugin for Nintendo 64 emulators +// Project started on December 29th, 2001 +// +// Authors: +// Dave2001, original author, founded the project in 2001, left it in 2002 +// Gugaman, joined the project in 2002, left it in 2002 +// Sergey 'Gonetz' Lipski, joined the project in 2002, main author since fall of 2002 +// Hiroshi 'KoolSmoky' Morii, joined the project in 2007 +// +//**************************************************************** +// +// To modify Glide64: +// * Write your name and (optional)email, commented by your work, so I know who did it, and so that you can find which parts you modified when it comes time to send it to me. +// * Do NOT send me the whole project or file that you modified. Take out your modified code sections, and tell me where to put them. If people sent the whole thing, I would have many different versions, but no idea how to combine them all. +// +//**************************************************************** + +#include +#include "Gfx_1.3.h" +#include "m64p.h" +#include "Config.h" +#include "3dmath.h" +#include "Util.h" +#include "Debugger.h" +#include "Combine.h" +#include "TexCache.h" +#include "TexBuffer.h" +#include "FBtoScreen.h" +#include "CRC.h" + +/* +const int NumOfFormats = 3; +SCREEN_SHOT_FORMAT ScreenShotFormats[NumOfFormats] = { {wxT("BMP"), wxT("bmp"), wxBITMAP_TYPE_BMP}, {wxT("PNG"), wxT("png"), wxBITMAP_TYPE_PNG}, {wxT("JPEG"), wxT("jpeg"), wxBITMAP_TYPE_JPEG} }; +*/ +const char *ACmp[] = { "NONE", "THRESHOLD", "UNKNOWN", "DITHER" }; + +const char *Mode0[] = { "COMBINED", "TEXEL0", + "TEXEL1", "PRIMITIVE", + "SHADE", "ENVIORNMENT", + "1", "NOISE", + "0", "0", + "0", "0", + "0", "0", + "0", "0" }; + +const char *Mode1[] = { "COMBINED", "TEXEL0", + "TEXEL1", "PRIMITIVE", + "SHADE", "ENVIORNMENT", + "CENTER", "K4", + "0", "0", + "0", "0", + "0", "0", + "0", "0" }; + +const char *Mode2[] = { "COMBINED", "TEXEL0", + "TEXEL1", "PRIMITIVE", + "SHADE", "ENVIORNMENT", + "SCALE", "COMBINED_ALPHA", + "T0_ALPHA", "T1_ALPHA", + "PRIM_ALPHA", "SHADE_ALPHA", + "ENV_ALPHA", "LOD_FRACTION", + "PRIM_LODFRAC", "K5", + "0", "0", + "0", "0", + "0", "0", + "0", "0", + "0", "0", + "0", "0", + "0", "0", + "0", "0" }; + +const char *Mode3[] = { "COMBINED", "TEXEL0", + "TEXEL1", "PRIMITIVE", + "SHADE", "ENVIORNMENT", + "1", "0" }; + +const char *Alpha0[] = { "COMBINED", "TEXEL0", + "TEXEL1", "PRIMITIVE", + "SHADE", "ENVIORNMENT", + "1", "0" }; + +#define Alpha1 Alpha0 +const char *Alpha2[] = { "LOD_FRACTION", "TEXEL0", + "TEXEL1", "PRIMITIVE", + "SHADE", "ENVIORNMENT", + "PRIM_LODFRAC", "0" }; +#define Alpha3 Alpha0 + +const char *FBLa[] = { "G_BL_CLR_IN", "G_BL_CLR_MEM", "G_BL_CLR_BL", "G_BL_CLR_FOG" }; +const char *FBLb[] = { "G_BL_A_IN", "G_BL_A_FOG", "G_BL_A_SHADE", "G_BL_0" }; +const char *FBLc[] = { "G_BL_CLR_IN", "G_BL_CLR_MEM", "G_BL_CLR_BL", "G_BL_CLR_FOG"}; +const char *FBLd[] = { "G_BL_1MA", "G_BL_A_MEM", "G_BL_1", "G_BL_0" }; + +const char *str_zs[] = { "G_ZS_PIXEL", "G_ZS_PRIM" }; + +const char *str_yn[] = { "NO", "YES" }; +const char *str_offon[] = { "OFF", "ON" }; + +const char *str_cull[] = { "DISABLE", "FRONT", "BACK", "BOTH" }; + +// I=intensity probably +const char *str_format[] = { "RGBA", "YUV", "CI", "IA", "I", "?", "?", "?" }; +const char *str_size[] = { "4bit", "8bit", "16bit", "32bit" }; +const char *str_cm[] = { "WRAP/NO CLAMP", "MIRROR/NO CLAMP", "WRAP/CLAMP", "MIRROR/CLAMP" }; +const char *str_lod[] = { "1", "2", "4", "8", "16", "32", "64", "128", "256", "512", "1024", "2048" }; +const char *str_aspect[] = { "1x8", "1x4", "1x2", "1x1", "2x1", "4x1", "8x1" }; + +const char *str_filter[] = { "Point Sampled", "Average (box)", "Bilinear" }; + +const char *str_tlut[] = { "TT_NONE", "TT_UNKNOWN", "TT_RGBA_16", "TT_IA_16" }; + +const char *str_dither[] = { "Pattern", "~Pattern", "Noise", "None" }; + +const char *CIStatus[] = { "ci_main", "ci_zimg", "ci_unknown", "ci_useless", + "ci_old_copy", "ci_copy", "ci_copy_self", + "ci_zcopy", "ci_aux", "ci_aux_copy" }; + typedef struct { int ucode; int crc; -} UcodeData; - +} UcodeData; + static UcodeData UcodeList[] = -{ +{ {0, 0x006bd77f}, {2, 0x03044b84}, {2, 0x030f4b84}, @@ -290,165 +289,165 @@ static UcodeData UcodeList[] = {1, 0xef54ee35}, {21, 0xf9893f70}, {1, 0xfb816260}, - {21, 0xff372492} -}; - -//static variables - -char out_buf[2048]; - -wxUint32 frame_count; // frame counter - -int ucode_error_report = TRUE; -int wrong_tile = -1; - -// ** RDP graphics functions ** -static void undef(); -static void spnoop(); - -static void rdp_noop(); -static void rdp_texrect(); -//static void rdp_texrectflip(); -static void rdp_loadsync(); -static void rdp_pipesync(); -static void rdp_tilesync(); -static void rdp_fullsync(); -static void rdp_setkeygb(); -static void rdp_setkeyr(); -static void rdp_setconvert(); -static void rdp_setscissor(); -static void rdp_setprimdepth(); -static void rdp_setothermode(); -static void rdp_loadtlut(); -static void rdp_settilesize(); -static void rdp_loadblock(); -static void rdp_loadtile(); -static void rdp_settile(); -static void rdp_fillrect(); -static void rdp_setfillcolor(); -static void rdp_setfogcolor(); -static void rdp_setblendcolor(); -static void rdp_setprimcolor(); -static void rdp_setenvcolor(); -static void rdp_setcombine(); -static void rdp_settextureimage(); -static void rdp_setdepthimage(); -static void rdp_setcolorimage(); -static void rdp_trifill(); -static void rdp_trishade(); -static void rdp_tritxtr(); -static void rdp_trishadetxtr(); -static void rdp_trifillz(); -static void rdp_trishadez(); -static void rdp_tritxtrz(); -static void rdp_trishadetxtrz(); -static void rdphalf_1(); -static void rdphalf_2(); -static void rdphalf_cont(); - -static void rsp_reserved0(); -static void rsp_reserved1(); -static void rsp_reserved2(); -static void rsp_reserved3(); - -static void ys_memrect(); - -wxUint8 microcode[4096]; -wxUint32 uc_crc; -void microcheck (); - -// ** UCODE FUNCTIONS ** -#include "ucode00.h" -#include "ucode01.h" -#include "ucode02.h" -#include "ucode03.h" -#include "ucode04.h" -#include "ucode05.h" -#include "ucode06.h" -#include "ucode07.h" -#include "ucode08.h" -#include "ucode09.h" -#include "ucode.h" -#include "ucode09rdp.h" -#include "turbo3D.h" - -static int reset = 0; -static int old_ucode = -1; - -void RDP::Reset() -{ - memset(this, 0, sizeof(RDP_Base)); - // set all vertex numbers - for (int i=0; i>2; i++) - { - uc_crc += ((wxUint32*)microcode)[i]; - } - - FRDP_E ("crc: %08lx\n", uc_crc); - -#ifdef LOG_UCODE - std::ofstream ucf; - ucf.open ("ucode.txt", std::ios::out | std::ios::binary); - char d; - for (i=0; i<0x400000; i++) - { - d = ((char*)gfx.RDRAM)[i^3]; - ucf.write (&d, 1); - } - ucf.close (); -#endif - - FRDP("ucode = %08lx\n", uc_crc); - - int uc = LookupUcode(uc_crc); - - if (uc == -2 && ucode_error_report) - { - settings.ucode = Config_ReadInt("ucode", "Force microcode", 0, TRUE, FALSE); - - ReleaseGfx (); - ERRLOG("Error: uCode crc not found in INI, using currently selected uCode\n\n%08lx", (unsigned long)uc_crc); - - ucode_error_report = FALSE; // don't report any more ucode errors from this game - } - else if (uc == -1 && ucode_error_report) - { - settings.ucode = 2; //ini->Read(_T("/SETTINGS/ucode"), 0); - - ReleaseGfx (); - ERRLOG("Error: Unsupported uCode!\n\ncrc: %08lx", (unsigned long)uc_crc); - - ucode_error_report = FALSE; // don't report any more ucode errors from this game - } - else - { - old_ucode = settings.ucode; - settings.ucode = uc; - FRDP("microcheck: old ucode: %d, new ucode: %d\n", old_ucode, uc); - if (uc_crc == 0x8d5735b2 || uc_crc == 0xb1821ed3 || uc_crc == 0x1118b3e0) //F3DLP.Rej ucode. perspective texture correction is not implemented - { - rdp.Persp_en = 1; - rdp.persp_supported = FALSE; - } - else if (settings.texture_correction) - rdp.persp_supported = TRUE; - } -} - -#ifdef __WINDOWS__ -static void GetClientSize(int * width, int * height) -{ -#ifdef __WINDOWS__ - RECT win_rect; - GetClientRect (gfx.hWnd, &win_rect); - *width = win_rect.right; - *height = win_rect.bottom; -#else - GFXWindow->GetClientSize(width, height); -#endif -} -#endif - -void drawNoFullscreenMessage() -{ -//need to find, how to do it on non-windows OS -//the code below will compile on any OS -//but it works only on windows, because -//I don't know, how to initialize GFXWindow on other OS -#ifdef __WINDOWS__ - LOG ("drawNoFullscreenMessage ()\n"); - if (rdp.window_changed) - { - rdp.window_changed = FALSE; - int width, height; - GetClientSize(&width, &height); - - wxClientDC dc(GFXWindow); - dc.SetBrush(*wxMEDIUM_GREY_BRUSH); - dc.SetTextForeground(*wxWHITE); - dc.SetBackgroundMode(wxTRANSPARENT); - dc.DrawRectangle(0, 0, width, height); - - wxCoord w, h; - wxString text = wxT("Glide64mk2"); - dc.GetTextExtent(text, &w, &h); - wxCoord x = (width - w)/2; - wxCoord y = height/2 - h*4; - dc.DrawText(text, x, y); - - text = wxT("Gfx cannot be drawn in windowed mode"); - dc.GetTextExtent(text, &w, &h); - x = (width - w)/2; - y = height/2 - h; - dc.DrawText(text, x, y); - - text = wxT("Press Alt+Enter to switch to fullscreen"); - dc.GetTextExtent(text, &w, &h); - x = (width - w)/2; - y = (height - h)/2 + h*2; - dc.DrawText(text, x, y); - } -#endif -} - -static wxUint32 d_ul_x, d_ul_y, d_lr_x, d_lr_y; - -static void DrawPartFrameBufferToScreen() -{ - FB_TO_SCREEN_INFO fb_info; - fb_info.addr = rdp.cimg; - fb_info.size = rdp.ci_size; - fb_info.width = rdp.ci_width; - fb_info.height = rdp.ci_height; - fb_info.ul_x = d_ul_x; - fb_info.lr_x = d_lr_x; - fb_info.ul_y = d_ul_y; - fb_info.lr_y = d_lr_y; - fb_info.opaque = 0; - DrawFrameBufferToScreen(fb_info); - memset(gfx.RDRAM+rdp.cimg, 0, (rdp.ci_width*rdp.ci_height)<>1); -} - -#define RGBA16TO32(color) \ - ((color&1)?0xFF:0) | \ - ((wxUint32)((float)((color&0xF800) >> 11) / 31.0f * 255.0f) << 24) | \ - ((wxUint32)((float)((color&0x07C0) >> 6) / 31.0f * 255.0f) << 16) | \ - ((wxUint32)((float)((color&0x003E) >> 1) / 31.0f * 255.0f) << 8) - -static void CopyFrameBuffer (GrBuffer_t buffer = GR_BUFFER_BACKBUFFER) -{ - if (!fullscreen) - return; - FRDP ("CopyFrameBuffer: %08lx... ", rdp.cimg); - - // don't bother to write the stuff in asm... the slow part is the read from video card, - // not the copy. - - wxUint32 width = rdp.ci_width;//*gfx.VI_WIDTH_REG; - wxUint32 height; - if (fb_emulation_enabled && !(settings.hacks&hack_PPL)) - { - int ind = (rdp.ci_count > 0)?rdp.ci_count-1:0; - height = rdp.frame_buffers[ind].height; - } - else - { - height = rdp.ci_lower_bound; - if (settings.hacks&hack_PPL) - height -= rdp.ci_upper_bound; - } - FRDP ("width: %d, height: %d... ", width, height); - - if (rdp.scale_x < 1.1f) - { - wxUint16 * ptr_src = new wxUint16[width*height]; - if (grLfbReadRegion(buffer, - (wxUint32)rdp.offset_x, - (wxUint32)rdp.offset_y,//rdp.ci_upper_bound, - width, - height, - width<<1, - ptr_src)) - { - wxUint16 *ptr_dst = (wxUint16*)(gfx.RDRAM+rdp.cimg); - wxUint32 *ptr_dst32 = (wxUint32*)(gfx.RDRAM+rdp.cimg); - wxUint16 c; - - for (wxUint32 y=0; y 0) - c = (c&0xFFC0) | ((c&0x001F) << 1) | 1; - } - else - { - c = (c&0xFFC0) | ((c&0x001F) << 1) | 1; - } - if (rdp.ci_size == 2) - ptr_dst[(x + y * width)^1] = c; - else - ptr_dst32[x + y * width] = RGBA16TO32(c); - } - } - LRDP("ReadRegion. Framebuffer copy complete.\n"); - } - else - { - LRDP("Framebuffer copy failed.\n"); - } - delete[] ptr_src; - } - else - { - if (rdp.motionblur && fb_hwfbe_enabled) - { - return; - } - else - { - float scale_x = (settings.scr_res_x - rdp.offset_x*2.0f) / max(width, rdp.vi_width); - float scale_y = (settings.scr_res_y - rdp.offset_y*2.0f) / max(height, rdp.vi_height); - - FRDP("width: %d, height: %d, ul_y: %d, lr_y: %d, scale_x: %f, scale_y: %f, ci_width: %d, ci_height: %d\n",width, height, rdp.ci_upper_bound, rdp.ci_lower_bound, scale_x, scale_y, rdp.ci_width, rdp.ci_height); - GrLfbInfo_t info; - info.size = sizeof(GrLfbInfo_t); - - if (grLfbLock (GR_LFB_READ_ONLY, - buffer, - GR_LFBWRITEMODE_565, - GR_ORIGIN_UPPER_LEFT, - FXFALSE, - &info)) - { - wxUint16 *ptr_src = (wxUint16*)info.lfbPtr; - wxUint16 *ptr_dst = (wxUint16*)(gfx.RDRAM+rdp.cimg); - wxUint32 *ptr_dst32 = (wxUint32*)(gfx.RDRAM+rdp.cimg); - wxUint16 c; - wxUint32 stride = info.strideInBytes>>1; - - int read_alpha = settings.frame_buffer & fb_read_alpha; - if ((settings.hacks&hack_PMario) && rdp.frame_buffers[rdp.ci_count-1].status != ci_aux) - read_alpha = FALSE; - int x_start = 0, y_start = 0, x_end = width, y_end = height; - if (settings.hacks&hack_BAR) - { - x_start = 80, y_start = 24, x_end = 240, y_end = 86; - } - for (int y=y_start; y 0) - SwapOK = TRUE; - rdp.updatescreen = 1; - - rdp.tri_n = 0; // 0 triangles so far this frame - rdp.debug_n = 0; - - rdp.model_i = 0; // 0 matrices so far in stack - //stack_size can be less then 32! Important for Silicon Vally. Thanks Orkin! - rdp.model_stack_size = min(32, (*(wxUint32*)(gfx.DMEM+0x0FE4))>>6); - if (rdp.model_stack_size == 0) - rdp.model_stack_size = 32; - rdp.Persp_en = TRUE; - rdp.fb_drawn = rdp.fb_drawn_front = FALSE; - rdp.update = 0x7FFFFFFF; // All but clear cache - rdp.geom_mode = 0; - rdp.acmp = 0; - rdp.maincimg[1] = rdp.maincimg[0]; - rdp.skip_drawing = FALSE; - rdp.s2dex_tex_loaded = FALSE; - rdp.bg_image_height = 0xFFFF; - fbreads_front = fbreads_back = 0; - rdp.fog_multiplier = rdp.fog_offset = 0; - rdp.zsrc = 0; - if (rdp.vi_org_reg != *gfx.VI_ORIGIN_REG) - rdp.tlut_mode = 0; //is it correct? - rdp.scissor_set = FALSE; - ucode5_texshiftaddr = ucode5_texshiftcount = 0; - cpu_fb_write = FALSE; - cpu_fb_read_called = FALSE; - cpu_fb_write_called = FALSE; - cpu_fb_ignore = FALSE; - d_ul_x = 0xffff; - d_ul_y = 0xffff; - d_lr_x = 0; - d_lr_y = 0; - depth_buffer_fog = TRUE; - - //analize possible frame buffer usage - if (fb_emulation_enabled) - DetectFrameBufferUsage(); - if (!(settings.hacks&hack_Lego) || rdp.num_of_ci > 1) - rdp.last_bg = 0; - //* End of set states *// - - // Get the start of the display list and the length of it - wxUint32 dlist_start = *(wxUint32*)(gfx.DMEM+0xFF0); - wxUint32 dlist_length = *(wxUint32*)(gfx.DMEM+0xFF4); - FRDP("--- NEW DLIST --- crc: %08lx, ucode: %d, fbuf: %08lx, fbuf_width: %d, dlist start: %08lx, dlist_length: %d, x_scale: %f, y_scale: %f\n", uc_crc, settings.ucode, *gfx.VI_ORIGIN_REG, *gfx.VI_WIDTH_REG, dlist_start, dlist_length, (*gfx.VI_X_SCALE_REG & 0xFFF)/1024.0f, (*gfx.VI_Y_SCALE_REG & 0xFFF)/1024.0f); - FRDP_E("--- NEW DLIST --- crc: %08lx, ucode: %d, fbuf: %08lx\n", uc_crc, settings.ucode, *gfx.VI_ORIGIN_REG); - - if (cpu_fb_write == TRUE) - DrawPartFrameBufferToScreen(); - if ((settings.hacks&hack_Tonic) && dlist_length < 16) - { - rdp_fullsync(); - FRDP_E("DLIST is too short!\n"); - return; - } - - // Start executing at the start of the display list - rdp.pc_i = 0; - rdp.pc[rdp.pc_i] = dlist_start; - rdp.dl_count = -1; - rdp.halt = 0; - wxUint32 a; - - // catches exceptions so that it doesn't freeze -#ifdef CATCH_EXCEPTIONS - try { -#endif - if (settings.ucode == ucode_Turbo3d) - { - Turbo3D(); - } - else - { - // MAIN PROCESSING LOOP - do { - - // Get the address of the next command - a = rdp.pc[rdp.pc_i] & BMASK; - - // Load the next command and its input - rdp.cmd0 = ((wxUint32*)gfx.RDRAM)[a>>2]; // \ Current command, 64 bit - rdp.cmd1 = ((wxUint32*)gfx.RDRAM)[(a>>2)+1]; // / - // cmd2 and cmd3 are filled only when needed, by the function that needs them - - // Output the address before the command -#ifdef LOG_COMMANDS - FRDP ("%08lx (c0:%08lx, c1:%08lx): ", a, rdp.cmd0, rdp.cmd1); -#else - FRDP ("%08lx: ", a); -#endif - - // Go to the next instruction - rdp.pc[rdp.pc_i] = (a+8) & BMASK; - -#ifdef PERFORMANCE - perf_cur = wxDateTime::UNow(); -#endif - // Process this instruction - gfx_instruction[settings.ucode][rdp.cmd0>>24] (); - - // check DL counter - if (rdp.dl_count != -1) - { - rdp.dl_count --; - if (rdp.dl_count == 0) - { - rdp.dl_count = -1; - - LRDP("End of DL\n"); - rdp.pc_i --; - } - } - -#ifdef PERFORMANCE - perf_next = wxDateTime::UNow(); - sprintf (out_buf, "perf %08lx: %016I64d\n", a-8, (perf_next-perf_cur).Format(_T("%l")).mb_str()); - rdp_log << out_buf; -#endif - - } while (!rdp.halt); - } -#ifdef CATCH_EXCEPTIONS - } catch (...) { - - if (fullscreen) - { - ReleaseGfx (); - rdp_reset (); -#ifdef TEXTURE_FILTER - if (settings.ghq_use) - { - ext_ghq_shutdown(); - settings.ghq_use = 0; - } -#endif - } - ERRLOG("The GFX plugin caused an exception and has been disabled."); - exception = TRUE; - return; - } -#endif - - if (fb_emulation_enabled) - { - rdp.scale_x = rdp.scale_x_bak; - rdp.scale_y = rdp.scale_y_bak; - } - if (settings.frame_buffer & fb_ref) - CopyFrameBuffer (); - if (rdp.cur_image) - CloseTextureBuffer(rdp.read_whole_frame && ((settings.hacks&hack_PMario) || rdp.swap_ci_index >= 0)); - - if ((settings.hacks&hack_TGR2) && rdp.vi_org_reg != *gfx.VI_ORIGIN_REG && CI_SET) - { - newSwapBuffers (); - CI_SET = FALSE; - } - LRDP("ProcessDList end\n"); -} - -#ifdef __cplusplus -} -#endif - -// undef - undefined instruction, always ignore -static void undef() -{ - FRDP_E("** undefined ** (%08lx)\n", rdp.cmd0); - FRDP("** undefined ** (%08lx) - IGNORED\n", rdp.cmd0); -#ifdef _ENDUSER_RELEASE_ - *gfx.MI_INTR_REG |= 0x20; - gfx.CheckInterrupts(); - rdp.halt = 1; -#endif -} - -// spnoop - no operation, always ignore -static void spnoop() -{ - LRDP("spnoop\n"); -} - -// noop - no operation, always ignore -static void rdp_noop() -{ - LRDP("noop\n"); -} - -static void ys_memrect () -{ - wxUint32 tile = (wxUint16)((rdp.cmd1 & 0x07000000) >> 24); - - wxUint32 lr_x = (wxUint16)((rdp.cmd0 & 0x00FFF000) >> 14); - wxUint32 lr_y = (wxUint16)((rdp.cmd0 & 0x00000FFF) >> 2); - wxUint32 ul_x = (wxUint16)((rdp.cmd1 & 0x00FFF000) >> 14); - wxUint32 ul_y = (wxUint16)((rdp.cmd1 & 0x00000FFF) >> 2); - - if (lr_y > rdp.scissor_o.lr_y) - lr_y = rdp.scissor_o.lr_y; - wxUint32 off_x = ((rdp.cmd2 & 0xFFFF0000) >> 16) >> 5; - wxUint32 off_y = (rdp.cmd2 & 0x0000FFFF) >> 5; - - FRDP ("memrect (%d, %d, %d, %d), ci_width: %d", ul_x, ul_y, lr_x, lr_y, rdp.ci_width); - if (off_x > 0) - FRDP (" off_x: %d", off_x); - if (off_y > 0) - FRDP (" off_y: %d", off_y); - LRDP("\n"); - - wxUint32 y, width = lr_x - ul_x; - wxUint32 tex_width = rdp.tiles[tile].line << 3; - wxUint8 * texaddr = gfx.RDRAM + rdp.addr[rdp.tiles[tile].t_mem] + tex_width*off_y + off_x; - wxUint8 * fbaddr = gfx.RDRAM + rdp.cimg + ul_x; - - for (y = ul_y; y < lr_y; y++) { - wxUint8 *src = texaddr + (y - ul_y) * tex_width; - wxUint8 *dst = fbaddr + y * rdp.ci_width; - memcpy (dst, src, width); - } -} - -static void pm_palette_mod () -{ - wxUint8 envr = (wxUint8)((float)((rdp.env_color >> 24)&0xFF)/255.0f*31.0f); - wxUint8 envg = (wxUint8)((float)((rdp.env_color >> 16)&0xFF)/255.0f*31.0f); - wxUint8 envb = (wxUint8)((float)((rdp.env_color >> 8)&0xFF)/255.0f*31.0f); - wxUint16 env16 = (wxUint16)((envr<<11)|(envg<<6)|(envb<<1)|1); - wxUint8 prmr = (wxUint8)((float)((rdp.prim_color >> 24)&0xFF)/255.0f*31.0f); - wxUint8 prmg = (wxUint8)((float)((rdp.prim_color >> 16)&0xFF)/255.0f*31.0f); - wxUint8 prmb = (wxUint8)((float)((rdp.prim_color >> 8)&0xFF)/255.0f*31.0f); - wxUint16 prim16 = (wxUint16)((prmr<<11)|(prmg<<6)|(prmb<<1)|1); - wxUint16 * dst = (wxUint16*)(gfx.RDRAM+rdp.cimg); - for (int i = 0; i < 16; i++) - { - dst[i^1] = (rdp.pal_8[i]&1) ? prim16 : env16; - } - LRDP("Texrect palette modification\n"); -} - -static void pd_zcopy () -{ - wxUint16 ul_x = (wxUint16)((rdp.cmd1 & 0x00FFF000) >> 14); - wxUint16 lr_x = (wxUint16)((rdp.cmd0 & 0x00FFF000) >> 14) + 1; - wxUint16 ul_u = (wxUint16)((rdp.cmd2 & 0xFFFF0000) >> 21) + 1; - wxUint16 *ptr_dst = (wxUint16*)(gfx.RDRAM+rdp.cimg); - wxUint16 width = lr_x - ul_x; - wxUint16 * ptr_src = ((wxUint16*)rdp.tmem)+ul_u; - wxUint16 c; - for (wxUint16 x=0; x> 8); - ptr_dst[(ul_x+x)^1] = c; - // FRDP("dst[%d]=%04lx \n", (x + ul_x)^1, c); - } -} - -static void DrawDepthBufferFog() -{ - if (rdp.zi_width < 200) - return; - FB_TO_SCREEN_INFO fb_info; - fb_info.addr = rdp.zimg; - fb_info.size = 2; - fb_info.width = rdp.zi_width; - fb_info.height = rdp.ci_height; - fb_info.ul_x = rdp.scissor_o.ul_x; - fb_info.lr_x = rdp.scissor_o.lr_x; - fb_info.ul_y = rdp.scissor_o.ul_y; - fb_info.lr_y = rdp.scissor_o.lr_y; - fb_info.opaque = 0; - DrawDepthBufferToScreen(fb_info); -} - -static void rdp_texrect() -{ - if (!rdp.LLE) - { - wxUint32 a = rdp.pc[rdp.pc_i]; - wxUint8 cmdHalf1 = gfx.RDRAM[a+3]; - wxUint8 cmdHalf2 = gfx.RDRAM[a+11]; - a >>= 2; - if ((cmdHalf1 == 0xE1 && cmdHalf2 == 0xF1) || (cmdHalf1 == 0xB4 && cmdHalf2 == 0xB3) || (cmdHalf1 == 0xB3 && cmdHalf2 == 0xB2)) - { - //gSPTextureRectangle - rdp.cmd2 = ((wxUint32*)gfx.RDRAM)[a+1]; - rdp.cmd3 = ((wxUint32*)gfx.RDRAM)[a+3]; - rdp.pc[rdp.pc_i] += 16; - } - else - { - //gDPTextureRectangle - if (settings.hacks&hack_ASB) - rdp.cmd2 = 0; - else - rdp.cmd2 = ((wxUint32*)gfx.RDRAM)[a+0]; - rdp.cmd3 = ((wxUint32*)gfx.RDRAM)[a+1]; - rdp.pc[rdp.pc_i] += 8; - } - } - if ((settings.hacks&hack_Yoshi) && settings.ucode == ucode_S2DEX) - { - ys_memrect(); - return; - } - - if (rdp.skip_drawing || (!fb_emulation_enabled && (rdp.cimg == rdp.zimg))) - { - if ((settings.hacks&hack_PMario) && rdp.ci_status == ci_useless) - { - pm_palette_mod (); - } - else - { - LRDP("Texrect skipped\n"); - } - return; - } - - if ((settings.ucode == ucode_CBFD) && rdp.cur_image && rdp.cur_image->format) - { - //FRDP("Wrong Texrect. texaddr: %08lx, cimg: %08lx, cimg_end: %08lx\n", rdp.timg.addr, rdp.maincimg[1].addr, rdp.maincimg[1].addr+rdp.ci_width*rdp.ci_height*rdp.ci_size); - LRDP("Shadow texrect is skipped.\n"); - rdp.tri_n += 2; - return; - } - - if ((settings.ucode == ucode_PerfectDark) && (rdp.frame_buffers[rdp.ci_count-1].status == ci_zcopy)) - { - pd_zcopy (); - LRDP("Depth buffer copied.\n"); - rdp.tri_n += 2; - return; - } - - if ((rdp.othermode_l >> 16) == 0x3c18 && rdp.cycle1 == 0x03ffffff && rdp.cycle2 == 0x01ff1fff) //depth image based fog - { - if (!depth_buffer_fog) - return; - if (settings.fog) - DrawDepthBufferFog(); - depth_buffer_fog = FALSE; - return; - } - - // FRDP ("rdp.cycle1 %08lx, rdp.cycle2 %08lx\n", rdp.cycle1, rdp.cycle2); - - float ul_x, ul_y, lr_x, lr_y; - if (rdp.cycle_mode == 2) - { - ul_x = max(0.0f, (short)((rdp.cmd1 & 0x00FFF000) >> 14)); - ul_y = max(0.0f, (short)((rdp.cmd1 & 0x00000FFF) >> 2)); - lr_x = max(0.0f, (short)((rdp.cmd0 & 0x00FFF000) >> 14)); - lr_y = max(0.0f, (short)((rdp.cmd0 & 0x00000FFF) >> 2)); - } - else - { - ul_x = max(0.0f, ((short)((rdp.cmd1 & 0x00FFF000) >> 12)) / 4.0f); - ul_y = max(0.0f, ((short)(rdp.cmd1 & 0x00000FFF)) / 4.0f); - lr_x = max(0.0f, ((short)((rdp.cmd0 & 0x00FFF000) >> 12)) / 4.0f); - lr_y = max(0.0f, ((short)(rdp.cmd0 & 0x00000FFF)) / 4.0f); - } - - if (ul_x >= lr_x) - { - FRDP("Wrong Texrect: ul_x: %f, lr_x: %f\n", ul_x, lr_x); - return; - } - - if (rdp.cycle_mode > 1) - { - lr_x += 1.0f; - lr_y += 1.0f; - } else if (lr_y - ul_y < 1.0f) - lr_y = ceil(lr_y); - - if (settings.increase_texrect_edge) - { - if (floor(lr_x) != lr_x) - lr_x = ceil(lr_x); - if (floor(lr_y) != lr_y) - lr_y = ceil(lr_y); - } - - //* - if (rdp.tbuff_tex && (settings.frame_buffer & fb_optimize_texrect)) - { - LRDP("Attempt to optimize texrect\n"); - if (!rdp.tbuff_tex->drawn) - { - DRAWIMAGE d; - d.imageX = 0; - d.imageW = (wxUint16)rdp.tbuff_tex->width; - d.frameX = (wxUint16)ul_x; - d.frameW = (wxUint16)(rdp.tbuff_tex->width); - - d.imageY = 0; - d.imageH = (wxUint16)rdp.tbuff_tex->height; - d.frameY = (wxUint16)ul_y; - d.frameH = (wxUint16)(rdp.tbuff_tex->height); - FRDP("texrect. ul_x: %d, ul_y: %d, lr_x: %d, lr_y: %d, width: %d, height: %d\n", ul_x, ul_y, lr_x, lr_y, rdp.tbuff_tex->width, rdp.tbuff_tex->height); - d.scaleX = 1.0f; - d.scaleY = 1.0f; - DrawHiresImage(d, rdp.tbuff_tex->width == rdp.ci_width); - rdp.tbuff_tex->drawn = TRUE; - } - return; - } - //*/ - // framebuffer workaround for Zelda: MM LOT - if ((rdp.othermode_l & 0xFFFF0000) == 0x0f5a0000) - return; - - /*Gonetz*/ - //hack for Zelda MM. it removes black texrects which cover all geometry in "Link meets Zelda" cut scene - if ((settings.hacks&hack_Zelda) && rdp.timg.addr >= rdp.cimg && rdp.timg.addr < rdp.ci_end) - { - FRDP("Wrong Texrect. texaddr: %08lx, cimg: %08lx, cimg_end: %08lx\n", rdp.cur_cache[0]->addr, rdp.cimg, rdp.cimg+rdp.ci_width*rdp.ci_height*2); - rdp.tri_n += 2; - return; - } - //* - //hack for Banjo2. it removes black texrects under Banjo - if (!fb_hwfbe_enabled && ((rdp.cycle1 << 16) | (rdp.cycle2 & 0xFFFF)) == 0xFFFFFFFF && (rdp.othermode_l & 0xFFFF0000) == 0x00500000) - { - rdp.tri_n += 2; - return; - } - //*/ - //* - //remove motion blur in night vision - if ((settings.ucode == ucode_PerfectDark) && (rdp.maincimg[1].addr != rdp.maincimg[0].addr) && (rdp.timg.addr >= rdp.maincimg[1].addr) && (rdp.timg.addr < (rdp.maincimg[1].addr+rdp.ci_width*rdp.ci_height*rdp.ci_size))) - { - if (fb_emulation_enabled) - if (rdp.frame_buffers[rdp.ci_count-1].status == ci_copy_self) - { - //FRDP("Wrong Texrect. texaddr: %08lx, cimg: %08lx, cimg_end: %08lx\n", rdp.timg.addr, rdp.maincimg[1], rdp.maincimg[1]+rdp.ci_width*rdp.ci_height*rdp.ci_size); - LRDP("Wrong Texrect.\n"); - rdp.tri_n += 2; - return; - } - } - //*/ - - int i; - - wxUint32 tile = (wxUint16)((rdp.cmd1 & 0x07000000) >> 24); - - rdp.texrecting = 1; - - wxUint32 prev_tile = rdp.cur_tile; - rdp.cur_tile = tile; - - const float Z = set_sprite_combine_mode (); - - rdp.texrecting = 0; - - if (!rdp.cur_cache[0]) - { - rdp.cur_tile = prev_tile; - rdp.tri_n += 2; - return; - } - // **** - // ** Texrect offset by Gugaman ** - // - //integer representation of texture coordinate. - //needed to detect and avoid overflow after shifting - wxInt32 off_x_i = (rdp.cmd2 >> 16) & 0xFFFF; - wxInt32 off_y_i = rdp.cmd2 & 0xFFFF; - float dsdx = (float)((short)((rdp.cmd3 & 0xFFFF0000) >> 16)) / 1024.0f; - float dtdy = (float)((short)(rdp.cmd3 & 0x0000FFFF)) / 1024.0f; - if (off_x_i & 0x8000) //check for sign bit - off_x_i |= ~0xffff; //make it negative - //the same as for off_x_i - if (off_y_i & 0x8000) - off_y_i |= ~0xffff; - - if (rdp.cycle_mode == 2) - dsdx /= 4.0f; - - float s_ul_x = ul_x * rdp.scale_x + rdp.offset_x; - float s_lr_x = lr_x * rdp.scale_x + rdp.offset_x; - float s_ul_y = ul_y * rdp.scale_y + rdp.offset_y; - float s_lr_y = lr_y * rdp.scale_y + rdp.offset_y; - - FRDP("texrect (%.2f, %.2f, %.2f, %.2f), tile: %d, #%d, #%d\n", ul_x, ul_y, lr_x, lr_y, tile, rdp.tri_n, rdp.tri_n+1); - FRDP ("(%f, %f) -> (%f, %f), s: (%d, %d) -> (%d, %d)\n", s_ul_x, s_ul_y, s_lr_x, s_lr_y, rdp.scissor.ul_x, rdp.scissor.ul_y, rdp.scissor.lr_x, rdp.scissor.lr_y); - FRDP("\toff_x: %f, off_y: %f, dsdx: %f, dtdy: %f\n", off_x_i/32.0f, off_y_i/32.0f, dsdx, dtdy); - - float off_size_x; - float off_size_y; - - if ( ((rdp.cmd0>>24)&0xFF) == 0xE5 ) //texrectflip - { -#ifdef TEXTURE_FILTER - if (rdp.cur_cache[0]->is_hires_tex) - { - off_size_x = (float)((lr_y - ul_y) * dsdx); - off_size_y = (float)((lr_x - ul_x) * dtdy); - } - else -#endif - { - off_size_x = (lr_y - ul_y - 1) * dsdx; - off_size_y = (lr_x - ul_x - 1) * dtdy; - } - } - else - { -#ifdef TEXTURE_FILTER - if (rdp.cur_cache[0]->is_hires_tex) - { - off_size_x = (float)((lr_x - ul_x) * dsdx); - off_size_y = (float)((lr_y - ul_y) * dtdy); - } - else -#endif - { - off_size_x = (lr_x - ul_x - 1) * dsdx; - off_size_y = (lr_y - ul_y - 1) * dtdy; - } - } - - struct { - float ul_u, ul_v, lr_u, lr_v; - } texUV[2]; //struct for texture coordinates - //angrylion's macro, helps to cut overflowed values. - #define SIGN16(x) (((x) & 0x8000) ? ((x) | ~0xffff) : ((x) & 0xffff)) - - //calculate texture coordinates - for (int i = 0; i < 2; i++) - { - if (rdp.cur_cache[i] && (rdp.tex & (i+1))) - { - float sx = 1, sy = 1; - int x_i = off_x_i, y_i = off_y_i; - TILE & tile = rdp.tiles[rdp.cur_tile + i]; - //shifting - if (tile.shift_s) - { - if (tile.shift_s > 10) - { - wxUint8 iShift = (16 - tile.shift_s); - x_i <<= iShift; - sx = (float)(1 << iShift); - } - else - { - wxUint8 iShift = tile.shift_s; - x_i >>= iShift; - sx = 1.0f/(float)(1 << iShift); - } - } - if (tile.shift_t) - { - if (tile.shift_t > 10) - { - wxUint8 iShift = (16 - tile.shift_t); - y_i <<= iShift; - sy = (float)(1 << iShift); - } - else - { - wxUint8 iShift = tile.shift_t; - y_i >>= iShift; - sy = 1.0f/(float)(1 << iShift); - } - } - - if (rdp.aTBuffTex[i]) //hwfbe texture - { - float t0_off_x; - float t0_off_y; - if (off_x_i + off_y_i == 0) - { - t0_off_x = tile.ul_s; - t0_off_y = tile.ul_t; - } - else - { - t0_off_x = off_x_i/32.0f; - t0_off_y = off_y_i/32.0f; - } - t0_off_x += rdp.aTBuffTex[i]->u_shift;// + tile.ul_s; //commented for Paper Mario motion blur - t0_off_y += rdp.aTBuffTex[i]->v_shift;// + tile.ul_t; - texUV[i].ul_u = t0_off_x * sx; - texUV[i].ul_v = t0_off_y * sy; - - texUV[i].lr_u = texUV[i].ul_u + off_size_x * sx; - texUV[i].lr_v = texUV[i].ul_v + off_size_y * sy; - - texUV[i].ul_u *= rdp.aTBuffTex[i]->u_scale; - texUV[i].ul_v *= rdp.aTBuffTex[i]->v_scale; - texUV[i].lr_u *= rdp.aTBuffTex[i]->u_scale; - texUV[i].lr_v *= rdp.aTBuffTex[i]->v_scale; - FRDP("tbuff_tex[%d] ul_u: %f, ul_v: %f, lr_u: %f, lr_v: %f\n", - i, texUV[i].ul_u, texUV[i].ul_v, texUV[i].lr_u, texUV[i].lr_v); - } - else //common case - { - //kill 10.5 format overflow by SIGN16 macro - texUV[i].ul_u = SIGN16(x_i) / 32.0f; - texUV[i].ul_v = SIGN16(y_i) / 32.0f; - - texUV[i].ul_u -= tile.f_ul_s; - texUV[i].ul_v -= tile.f_ul_t; - - texUV[i].lr_u = texUV[i].ul_u + off_size_x * sx; - texUV[i].lr_v = texUV[i].ul_v + off_size_y * sy; - - texUV[i].ul_u = rdp.cur_cache[i]->c_off + rdp.cur_cache[i]->c_scl_x * texUV[i].ul_u; - texUV[i].lr_u = rdp.cur_cache[i]->c_off + rdp.cur_cache[i]->c_scl_x * texUV[i].lr_u; - texUV[i].ul_v = rdp.cur_cache[i]->c_off + rdp.cur_cache[i]->c_scl_y * texUV[i].ul_v; - texUV[i].lr_v = rdp.cur_cache[i]->c_off + rdp.cur_cache[i]->c_scl_y * texUV[i].lr_v; - } - } - else - { - texUV[i].ul_u = texUV[i].ul_v = texUV[i].lr_u = texUV[i].lr_v = 0; - } - } - rdp.cur_tile = prev_tile; - - // **** - - FRDP (" scissor: (%d, %d) -> (%d, %d)\n", rdp.scissor.ul_x, rdp.scissor.ul_y, rdp.scissor.lr_x, rdp.scissor.lr_y); - - CCLIP2 (s_ul_x, s_lr_x, texUV[0].ul_u, texUV[0].lr_u, texUV[1].ul_u, texUV[1].lr_u, (float)rdp.scissor.ul_x, (float)rdp.scissor.lr_x); - CCLIP2 (s_ul_y, s_lr_y, texUV[0].ul_v, texUV[0].lr_v, texUV[1].ul_v, texUV[1].lr_v, (float)rdp.scissor.ul_y, (float)rdp.scissor.lr_y); - - FRDP (" draw at: (%f, %f) -> (%f, %f)\n", s_ul_x, s_ul_y, s_lr_x, s_lr_y); - - VERTEX vstd[4] = { - { s_ul_x, s_ul_y, Z, 1.0f, texUV[0].ul_u, texUV[0].ul_v, texUV[1].ul_u, texUV[1].ul_v, {0, 0, 0, 0}, 255 }, - { s_lr_x, s_ul_y, Z, 1.0f, texUV[0].lr_u, texUV[0].ul_v, texUV[1].lr_u, texUV[1].ul_v, {0, 0, 0, 0}, 255 }, - { s_ul_x, s_lr_y, Z, 1.0f, texUV[0].ul_u, texUV[0].lr_v, texUV[1].ul_u, texUV[1].lr_v, {0, 0, 0, 0}, 255 }, - { s_lr_x, s_lr_y, Z, 1.0f, texUV[0].lr_u, texUV[0].lr_v, texUV[1].lr_u, texUV[1].lr_v, {0, 0, 0, 0}, 255 } }; - - if ( ((rdp.cmd0>>24)&0xFF) == 0xE5 ) //texrectflip - { - vstd[1].u0 = texUV[0].ul_u; - vstd[1].v0 = texUV[0].lr_v; - vstd[1].u1 = texUV[1].ul_u; - vstd[1].v1 = texUV[1].lr_v; - - vstd[2].u0 = texUV[0].lr_u; - vstd[2].v0 = texUV[0].ul_v; - vstd[2].u1 = texUV[1].lr_u; - vstd[2].v1 = texUV[1].ul_v; - } - - VERTEX *vptr = vstd; - int n_vertices = 4; - - VERTEX *vnew = 0; - // for (int j =0; j < 4; j++) - // FRDP("v[%d] u0: %f, v0: %f, u1: %f, v1: %f\n", j, vstd[j].u0, vstd[j].v0, vstd[j].u1, vstd[j].v1); - - - if (!rdp.aTBuffTex[0] && rdp.cur_cache[0]->splits != 1) - { - // ** LARGE TEXTURE HANDLING ** - // *VERY* simple algebra for texrects - float min_u, min_x, max_u, max_x; - if (vstd[0].u0 < vstd[1].u0) - { - min_u = vstd[0].u0; - min_x = vstd[0].x; - max_u = vstd[1].u0; - max_x = vstd[1].x; - } - else - { - min_u = vstd[1].u0; - min_x = vstd[1].x; - max_u = vstd[0].u0; - max_x = vstd[0].x; - } - - int start_u_256, end_u_256; - start_u_256 = (int)min_u >> 8; - end_u_256 = (int)max_u >> 8; - //FRDP(" min_u: %f, max_u: %f start: %d, end: %d\n", min_u, max_u, start_u_256, end_u_256); - - int splitheight = rdp.cur_cache[0]->splitheight; - - int num_verts_line = 2 + ((end_u_256-start_u_256)<<1); - n_vertices = num_verts_line << 1; - vnew = new VERTEX [n_vertices]; - vptr = vnew; - - vnew[0] = vstd[0]; - vnew[0].u0 -= 256.0f * start_u_256; - vnew[0].v0 += splitheight * start_u_256; - vnew[0].u1 -= 256.0f * start_u_256; - vnew[0].v1 += splitheight * start_u_256; - vnew[1] = vstd[2]; - vnew[1].u0 -= 256.0f * start_u_256; - vnew[1].v0 += splitheight * start_u_256; - vnew[1].u1 -= 256.0f * start_u_256; - vnew[1].v1 += splitheight * start_u_256; - vnew[n_vertices-2] = vstd[1]; - vnew[n_vertices-2].u0 -= 256.0f * end_u_256; - vnew[n_vertices-2].v0 += splitheight * end_u_256; - vnew[n_vertices-2].u1 -= 256.0f * end_u_256; - vnew[n_vertices-2].v1 += splitheight * end_u_256; - vnew[n_vertices-1] = vstd[3]; - vnew[n_vertices-1].u0 -= 256.0f * end_u_256; - vnew[n_vertices-1].v0 += splitheight * end_u_256; - vnew[n_vertices-1].u1 -= 256.0f * end_u_256; - vnew[n_vertices-1].v1 += splitheight * end_u_256; - - // find the equation of the line of u,x - float m = (max_x - min_x) / (max_u - min_u); // m = delta x / delta u - float b = min_x - m * min_u; // b = y - m * x - - for (i=start_u_256; i 12) - { - float texbound = (float)(splitheight << 1); - for (int k = 0; k < n_vertices; k ++) - { - if (vnew[k].v0 > texbound) - vnew[k].v0 = (float)fmod(vnew[k].v0, texbound); - } - } - //*/ - } - - AllowShadeMods (vptr, n_vertices); - for (i=0; i= RDP::fog_blend) - { - float fog; - if (rdp.fog_mode == RDP::fog_blend) - fog = 1.0f/max(1, rdp.fog_color&0xFF); - else - fog = 1.0f/max(1, (~rdp.fog_color)&0xFF); - for (i = 0; i < n_vertices; i++) - { - vptr[i].f = fog; - } - grFogMode (GR_FOG_WITH_TABLE_ON_FOGCOORD_EXT); - } - - ConvertCoordsConvert (vptr, n_vertices); - - if (settings.wireframe) - { - SetWireframeCol (); - grDrawLine (&vstd[0], &vstd[2]); - grDrawLine (&vstd[2], &vstd[1]); - grDrawLine (&vstd[1], &vstd[0]); - grDrawLine (&vstd[2], &vstd[3]); - grDrawLine (&vstd[3], &vstd[1]); - } - else - { - grDrawVertexArrayContiguous (GR_TRIANGLE_STRIP, n_vertices, vptr, sizeof(VERTEX)); - } - - if (_debugger.capture) - { - VERTEX vl[3]; - vl[0] = vstd[0]; - vl[1] = vstd[2]; - vl[2] = vstd[1]; - add_tri (vl, 3, TRI_TEXRECT); - rdp.tri_n ++; - vl[0] = vstd[2]; - vl[1] = vstd[3]; - vl[2] = vstd[1]; - add_tri (vl, 3, TRI_TEXRECT); - rdp.tri_n ++; - } - else - rdp.tri_n += 2; - } - else - { - rdp.tri_n += 2; - } - - delete[] vnew; -} - -static void rdp_loadsync() -{ - LRDP("loadsync - ignored\n"); -} - -static void rdp_pipesync() -{ - LRDP("pipesync - ignored\n"); -} - -static void rdp_tilesync() -{ - LRDP("tilesync - ignored\n"); -} - -static void rdp_fullsync() -{ - // Set an interrupt to allow the game to continue - *gfx.MI_INTR_REG |= 0x20; - gfx.CheckInterrupts(); - LRDP("fullsync\n"); -} - -static void rdp_setkeygb() -{ - wxUint32 sB = rdp.cmd1&0xFF; - wxUint32 cB = (rdp.cmd1>>8)&0xFF; - wxUint32 sG = (rdp.cmd1>>16)&0xFF; - wxUint32 cG = (rdp.cmd1>>24)&0xFF; - rdp.SCALE = (rdp.SCALE&0xFF0000FF) | (sG<<16) | (sB<<8); - rdp.CENTER = (rdp.CENTER&0xFF0000FF) | (cG<<16) | (cB<<8); - FRDP("setkeygb. cG=%02lx, sG=%02lx, cB=%02lx, sB=%02lx\n", cG, sG, cB, sB); -} - -static void rdp_setkeyr() -{ - wxUint32 sR = rdp.cmd1&0xFF; - wxUint32 cR = (rdp.cmd1>>8)&0xFF; - rdp.SCALE = (rdp.SCALE&0x00FFFFFF) | (sR<<24); - rdp.CENTER = (rdp.CENTER&0x00FFFFFF) | (cR<<24); - FRDP("setkeyr. cR=%02lx, sR=%02lx\n", cR, sR); -} - -static void rdp_setconvert() -{ - /* - rdp.YUV_C0 = 1.1647f ; - rdp.YUV_C1 = 0.79931f ; - rdp.YUV_C2 = -0.1964f ; - rdp.YUV_C3 = -0.40651f; - rdp.YUV_C4 = 1.014f ; - */ - rdp.K4 = (wxUint8)(rdp.cmd1>>9)&0x1FF; - rdp.K5 = (wxUint8)(rdp.cmd1&0x1FF); - // RDP_E("setconvert - IGNORED\n"); - FRDP("setconvert. K4=%02lx K5=%02lx\n", rdp.K4, rdp.K5); -} - -// -// setscissor - sets the screen clipping rectangle -// - -static void rdp_setscissor() -{ - // clipper resolution is 320x240, scale based on computer resolution - rdp.scissor_o.ul_x = /*min(*/(wxUint32)(((rdp.cmd0 & 0x00FFF000) >> 14))/*, 320)*/; - rdp.scissor_o.ul_y = /*min(*/(wxUint32)(((rdp.cmd0 & 0x00000FFF) >> 2))/*, 240)*/; - rdp.scissor_o.lr_x = /*min(*/(wxUint32)(((rdp.cmd1 & 0x00FFF000) >> 14))/*, 320)*/; - rdp.scissor_o.lr_y = /*min(*/(wxUint32)(((rdp.cmd1 & 0x00000FFF) >> 2))/*, 240)*/; - - rdp.ci_upper_bound = rdp.scissor_o.ul_y; - rdp.ci_lower_bound = rdp.scissor_o.lr_y; - rdp.scissor_set = TRUE; - - FRDP("setscissor: (%d,%d) -> (%d,%d)\n", rdp.scissor_o.ul_x, rdp.scissor_o.ul_y, - rdp.scissor_o.lr_x, rdp.scissor_o.lr_y); - - rdp.update |= UPDATE_SCISSOR; - - if (rdp.view_scale[0] == 0) //viewport is not set? - { - rdp.view_scale[0] = (rdp.scissor_o.lr_x>>1)*rdp.scale_x; - rdp.view_scale[1] = (rdp.scissor_o.lr_y>>1)*-rdp.scale_y; - rdp.view_trans[0] = rdp.view_scale[0]; - rdp.view_trans[1] = -rdp.view_scale[1]; - rdp.update |= UPDATE_VIEWPORT; - } -} - -static void rdp_setprimdepth() -{ - rdp.prim_depth = (wxUint16)((rdp.cmd1 >> 16) & 0x7FFF); - rdp.prim_dz = (wxUint16)(rdp.cmd1 & 0x7FFF); - - FRDP("setprimdepth: %d\n", rdp.prim_depth); -} - -static void rdp_setothermode() -{ -#define F3DEX2_SETOTHERMODE(cmd,sft,len,data) { \ - rdp.cmd0 = (cmd<<24) | ((32-(sft)-(len))<<8) | (((len)-1)); \ - rdp.cmd1 = data; \ - gfx_instruction[settings.ucode][cmd] (); \ -} -#define SETOTHERMODE(cmd,sft,len,data) { \ - rdp.cmd0 = (cmd<<24) | ((sft)<<8) | (len); \ - rdp.cmd1 = data; \ - gfx_instruction[settings.ucode][cmd] (); \ -} - - LRDP("rdp_setothermode\n"); - - if ((settings.ucode == ucode_F3DEX2) || (settings.ucode == ucode_CBFD)) - { - int cmd0 = rdp.cmd0; - F3DEX2_SETOTHERMODE(0xE2, 0, 32, rdp.cmd1); // SETOTHERMODE_L - F3DEX2_SETOTHERMODE(0xE3, 0, 32, cmd0 & 0x00FFFFFF); // SETOTHERMODE_H - } - else - { - int cmd0 = rdp.cmd0; - SETOTHERMODE(0xB9, 0, 32, rdp.cmd1); // SETOTHERMODE_L - SETOTHERMODE(0xBA, 0, 32, cmd0 & 0x00FFFFFF); // SETOTHERMODE_H - } -} - -void load_palette (wxUint32 addr, wxUint16 start, wxUint16 count) -{ - LRDP("Loading palette... "); - wxUint16 *dpal = rdp.pal_8 + start; - wxUint16 end = start+count; -#ifdef TEXTURE_FILTER - wxUint16 *spal = (wxUint16*)(gfx.RDRAM + (addr & BMASK)); -#endif - - for (wxUint16 i=start; i>= 4; - end = start + (count >> 4); - if (end == start) // it can be if count < 16 - end = start + 1; - for (wxUint16 p = start; p < end; p++) - { - rdp.pal_8_crc[p] = CRC32( 0xFFFFFFFF, &rdp.pal_8[(p << 4)], 32 ); - } - rdp.pal_256_crc = CRC32( 0xFFFFFFFF, rdp.pal_8_crc, 64 ); - LRDP("Done.\n"); -} - -static void rdp_loadtlut() -{ - wxUint32 tile = (rdp.cmd1 >> 24) & 0x07; - wxUint16 start = rdp.tiles[tile].t_mem - 256; // starting location in the palettes - // wxUint16 start = ((wxUint16)(rdp.cmd1 >> 2) & 0x3FF) + 1; - wxUint16 count = ((wxUint16)(rdp.cmd1 >> 14) & 0x3FF) + 1; // number to copy - - if (rdp.timg.addr + (count<<1) > BMASK) - count = (wxUint16)((BMASK - rdp.timg.addr) >> 1); - - if (start+count > 256) count = 256-start; - - FRDP("loadtlut: tile: %d, start: %d, count: %d, from: %08lx\n", tile, start, count, - rdp.timg.addr); - - load_palette (rdp.timg.addr, start, count); - - rdp.timg.addr += count << 1; - - if (rdp.tbuff_tex) //paranoid check. - { - //the buffer is definitely wrong, as there must be no CI frame buffers - //find and remove it - for (int i = 0; i < voodoo.num_tmu; i++) - { - for (int j = 0; j < rdp.texbufs[i].count; j++) - { - if (&(rdp.texbufs[i].images[j]) == rdp.tbuff_tex) - { - rdp.texbufs[i].count--; - if (j < rdp.texbufs[i].count) - memcpy(&(rdp.texbufs[i].images[j]), &(rdp.texbufs[i].images[j+1]), sizeof(TBUFF_COLOR_IMAGE)*(rdp.texbufs[i].count-j)); - return; - } - } - } - } -} - -int tile_set = 0; -static void rdp_settilesize() -{ - wxUint32 tile = (rdp.cmd1 >> 24) & 0x07; - rdp.last_tile_size = tile; - - rdp.tiles[tile].f_ul_s = (float)((rdp.cmd0 >> 12) & 0xFFF) / 4.0f; - rdp.tiles[tile].f_ul_t = (float)(rdp.cmd0 & 0xFFF) / 4.0f; - - int ul_s = (((wxUint16)(rdp.cmd0 >> 14)) & 0x03ff); - int ul_t = (((wxUint16)(rdp.cmd0 >> 2 )) & 0x03ff); - int lr_s = (((wxUint16)(rdp.cmd1 >> 14)) & 0x03ff); - int lr_t = (((wxUint16)(rdp.cmd1 >> 2 )) & 0x03ff); - - if (lr_s == 0 && ul_s == 0) //pokemon puzzle league set such tile size - wrong_tile = tile; - else if (wrong_tile == (int)tile) - wrong_tile = -1; - - if (settings.use_sts1_only) - { - // ** USE FIRST SETTILESIZE ONLY ** - // This option helps certain textures while using the 'Alternate texture size method', - // but may break others. (should help more than break) - - if (tile_set) - { - // coords in 10.2 format - rdp.tiles[tile].ul_s = ul_s; - rdp.tiles[tile].ul_t = ul_t; - rdp.tiles[tile].lr_s = lr_s; - rdp.tiles[tile].lr_t = lr_t; - tile_set = 0; - } - } - else - { - // coords in 10.2 format - rdp.tiles[tile].ul_s = ul_s; - rdp.tiles[tile].ul_t = ul_t; - rdp.tiles[tile].lr_s = lr_s; - rdp.tiles[tile].lr_t = lr_t; - } - - // handle wrapping - if (rdp.tiles[tile].lr_s < rdp.tiles[tile].ul_s) rdp.tiles[tile].lr_s += 0x400; - if (rdp.tiles[tile].lr_t < rdp.tiles[tile].ul_t) rdp.tiles[tile].lr_t += 0x400; - - rdp.update |= UPDATE_TEXTURE; - - rdp.first = 1; - - FRDP ("settilesize: tile: %d, ul_s: %d, ul_t: %d, lr_s: %d, lr_t: %d, f_ul_s: %f, f_ul_t: %f\n", - tile, ul_s, ul_t, lr_s, lr_t, rdp.tiles[tile].f_ul_s, rdp.tiles[tile].f_ul_t); -} - -void setTBufTex(wxUint16 t_mem, wxUint32 cnt) -{ - FRDP("setTBufTex t_mem=%d, cnt=%d\n", t_mem, cnt); - TBUFF_COLOR_IMAGE * pTbufTex = rdp.tbuff_tex; - for (int i = 0; i < 2; i++) - { - LRDP("Before: "); - if (rdp.aTBuffTex[i]) { - FRDP("rdp.aTBuffTex[%d]: tmu=%d t_mem=%d tile=%d\n", i, rdp.aTBuffTex[i]->tmu, rdp.aTBuffTex[i]->t_mem, rdp.aTBuffTex[i]->tile); - } else { - FRDP("rdp.aTBuffTex[%d]=0\n", i); - } - if ((rdp.aTBuffTex[i] == 0 && rdp.aTBuffTex[i^1] != pTbufTex) || (rdp.aTBuffTex[i] && rdp.aTBuffTex[i]->t_mem >= t_mem && rdp.aTBuffTex[i]->t_mem < t_mem + cnt)) - { - if (pTbufTex) - { - rdp.aTBuffTex[i] = pTbufTex; - rdp.aTBuffTex[i]->t_mem = t_mem; - pTbufTex = 0; - FRDP("rdp.aTBuffTex[%d] tmu=%d t_mem=%d\n", i, rdp.aTBuffTex[i]->tmu, rdp.aTBuffTex[i]->t_mem); - } - else - { - rdp.aTBuffTex[i] = 0; - FRDP("rdp.aTBuffTex[%d]=0\n", i); - } - } - } -} - -static inline void loadBlock(uint32_t *src, uint32_t *dst, uint32_t off, int dxt, int cnt) -{ - uint32_t *v5; - int v6; - uint32_t *v7; - uint32_t v8; - int v9; - uint32_t v10; - uint32_t *v11; - uint32_t v12; - uint32_t v13; - uint32_t v14; - int v15; - int v16; - uint32_t *v17; - int v18; - uint32_t v19; - uint32_t v20; - int i; - - v5 = dst; - v6 = cnt; - if ( cnt ) - { - v7 = (uint32_t *)((char *)src + (off & 0xFFFFFFFC)); - v8 = off & 3; - if ( !(off & 3) ) - goto LABEL_23; - v9 = 4 - v8; - v10 = *v7; - v11 = v7 + 1; - do - { - v10 = __ROL__(v10, 8); - --v8; - } - while ( v8 ); - do - { - v10 = __ROL__(v10, 8); - *(uint8_t *)v5 = v10; - v5 = (uint32_t *)((char *)v5 + 1); - --v9; - } - while ( v9 ); - v12 = *v11; - v7 = v11 + 1; - *v5 = bswap32(v12); - ++v5; - v6 = cnt - 1; - if ( cnt != 1 ) - { -LABEL_23: - do - { - *v5 = bswap32(*v7); - v5[1] = bswap32(v7[1]); - v7 += 2; - v5 += 2; - --v6; - } - while ( v6 ); - } - v13 = off & 3; - if ( off & 3 ) - { - v14 = *(uint32_t *)((char *)src + ((8 * cnt + off) & 0xFFFFFFFC)); - do - { - v14 = __ROL__(v14, 8); - *(uint8_t *)v5 = v14; - v5 = (uint32_t *)((char *)v5 + 1); - --v13; - } - while ( v13 ); - } - } - v15 = cnt; - v16 = 0; - v17 = dst; - v18 = 0; -dxt_test: - while ( 1 ) - { - v17 += 2; - --v15; - if ( !v15 ) - break; - v16 += dxt; - if ( v16 < 0 ) - { - while ( 1 ) - { - ++v18; - --v15; - if ( !v15 ) - goto end_dxt_test; - v16 += dxt; - if ( v16 >= 0 ) - { - for ( i = v15; v18; --v18 ) - { - v19 = *v17; - *v17 = v17[1]; - v17[1] = v19; - v17 += 2; - } - v15 = i; - goto dxt_test; - } - } - } - } -end_dxt_test: - while ( v18 ) - { - v20 = *v17; - *v17 = v17[1]; - v17[1] = v20; - v17 += 2; - --v18; - } -} - -void LoadBlock32b(wxUint32 tile, wxUint32 ul_s, wxUint32 ul_t, wxUint32 lr_s, wxUint32 dxt); -static void rdp_loadblock() -{ - if (rdp.skip_drawing) - { - LRDP("loadblock skipped\n"); - return; - } - wxUint32 tile = (wxUint32)((rdp.cmd1 >> 24) & 0x07); - wxUint32 dxt = (wxUint32)(rdp.cmd1 & 0x0FFF); - wxUint16 lr_s = (wxUint16)(rdp.cmd1 >> 14) & 0x3FF; - if (ucode5_texshiftaddr) - { - if (ucode5_texshift % ((lr_s+1)<<3)) - { - rdp.timg.addr -= ucode5_texshift; - ucode5_texshiftaddr = 0; - ucode5_texshift = 0; - ucode5_texshiftcount = 0; - } - else - ucode5_texshiftcount++; - } - - rdp.addr[rdp.tiles[tile].t_mem] = rdp.timg.addr; - - // ** DXT is used for swapping every other line - /* double fdxt = (double)0x8000000F/(double)((wxUint32)(2047/(dxt-1))); // F for error - wxUint32 _dxt = (wxUint32)fdxt;*/ - - // 0x00000800 -> 0x80000000 (so we can check the sign bit instead of the 11th bit) - wxUint32 _dxt = dxt << 20; - - wxUint32 addr = segoffset(rdp.timg.addr) & BMASK; - - // lr_s specifies number of 64-bit words to copy - // 10.2 format - wxUint16 ul_s = (wxUint16)(rdp.cmd0 >> 14) & 0x3FF; - wxUint16 ul_t = (wxUint16)(rdp.cmd0 >> 2) & 0x3FF; - - rdp.tiles[tile].ul_s = ul_s; - rdp.tiles[tile].ul_t = ul_t; - rdp.tiles[tile].lr_s = lr_s; - - rdp.timg.set_by = 0; // load block - -#ifdef TEXTURE_FILTER - LOAD_TILE_INFO &info = rdp.load_info[rdp.tiles[tile].t_mem]; - info.tile_width = lr_s; - info.dxt = dxt; -#endif - - // do a quick boundary check before copying to eliminate the possibility for exception - if (ul_s >= 512) { - lr_s = 1; // 1 so that it doesn't die on memcpy - ul_s = 511; - } - if (ul_s+lr_s > 512) - lr_s = 512-ul_s; - - if (addr+(lr_s<<3) > BMASK+1) - lr_s = (wxUint16)((BMASK-addr)>>3); - - //angrylion's advice to use ul_s in texture image offset and cnt calculations. - //Helps to fix Vigilante 8 jpeg backgrounds and logos - wxUint32 off = rdp.timg.addr + (ul_s << rdp.tiles[tile].size >> 1); - unsigned char *dst = ((unsigned char *)rdp.tmem) + (rdp.tiles[tile].t_mem<<3); - wxUint32 cnt = lr_s-ul_s+1; - if (rdp.tiles[tile].size == 3) - cnt <<= 1; - - if (((rdp.tiles[tile].t_mem + cnt) << 3) > sizeof(rdp.tmem)) { - WriteLog(M64MSG_INFO, "rdp_loadblock wanted to write %u bytes after the end of tmem", ((rdp.tiles[tile].t_mem + cnt) << 3) - sizeof(rdp.tmem)); - cnt = (sizeof(rdp.tmem) >> 3) - (rdp.tiles[tile].t_mem); - } - - if (rdp.timg.size == 3) - LoadBlock32b(tile, ul_s, ul_t, lr_s, dxt); - else - loadBlock((uint32_t *)gfx.RDRAM, (uint32_t *)dst, off, _dxt, cnt); - - rdp.timg.addr += cnt << 3; - rdp.tiles[tile].lr_t = ul_t + ((dxt*cnt)>>11); - - rdp.update |= UPDATE_TEXTURE; - - FRDP ("loadblock: tile: %d, ul_s: %d, ul_t: %d, lr_s: %d, dxt: %08lx -> %08lx\n", - tile, ul_s, ul_t, lr_s, - dxt, _dxt); - - if (fb_hwfbe_enabled) - setTBufTex(rdp.tiles[tile].t_mem, cnt); -} - - -static inline void loadTile(uint32_t *src, uint32_t *dst, int width, int height, int line, int off, uint32_t *end) -{ - uint32_t *v7; - int v8; - uint32_t *v9; - int v10; - int v11; - int v12; - uint32_t *v13; - int v14; - int v15; - uint32_t v16; - uint32_t *v17; - uint32_t v18; - int v19; - uint32_t v20; - int v21; - uint32_t v22; - int v23; - uint32_t *v24; - int v25; - int v26; - uint32_t *v27; - int v28; - int v29; - int v30; - uint32_t *v31; - - v7 = dst; - v8 = width; - v9 = src; - v10 = off; - v11 = 0; - v12 = height; - do - { - if ( end < v7 ) - break; - v31 = v7; - v30 = v8; - v29 = v12; - v28 = v11; - v27 = v9; - v26 = v10; - if ( v8 ) - { - v25 = v8; - v24 = v9; - v23 = v10; - v13 = (uint32_t *)((char *)v9 + (v10 & 0xFFFFFFFC)); - v14 = v10 & 3; - if ( !(v10 & 3) ) - goto LABEL_20; - v15 = 4 - v14; - v16 = *v13; - v17 = v13 + 1; - do - { - v16 = __ROL__(v16, 8); - --v14; - } - while ( v14 ); - do - { - v16 = __ROL__(v16, 8); - *(uint8_t *)v7 = v16; - v7 = (uint32_t *)((char *)v7 + 1); - --v15; - } - while ( v15 ); - v18 = *v17; - v13 = v17 + 1; - *v7 = bswap32(v18); - ++v7; - --v8; - if ( v8 ) - { -LABEL_20: - do - { - *v7 = bswap32(*v13); - v7[1] = bswap32(v13[1]); - v13 += 2; - v7 += 2; - --v8; - } - while ( v8 ); - } - v19 = v23 & 3; - if ( v23 & 3 ) - { - v20 = *(uint32_t *)((char *)v24 + ((8 * v25 + v23) & 0xFFFFFFFC)); - do - { - v20 = __ROL__(v20, 8); - *(uint8_t *)v7 = v20; - v7 = (uint32_t *)((char *)v7 + 1); - --v19; - } - while ( v19 ); - } - } - v9 = v27; - v21 = v29; - v8 = v30; - v11 = v28 ^ 1; - if ( v28 == 1 ) - { - v7 = v31; - if ( v30 ) - { - do - { - v22 = *v7; - *v7 = v7[1]; - v7[1] = v22; - v7 += 2; - --v8; - } - while ( v8 ); - } - v21 = v29; - v8 = v30; - } - v10 = line + v26; - v12 = v21 - 1; - } - while ( v12 ); -} - -void LoadTile32b (wxUint32 tile, wxUint32 ul_s, wxUint32 ul_t, wxUint32 width, wxUint32 height); -static void rdp_loadtile() -{ - if (rdp.skip_drawing) - { - LRDP("loadtile skipped\n"); - return; - } - rdp.timg.set_by = 1; // load tile - - wxUint32 tile = (wxUint32)((rdp.cmd1 >> 24) & 0x07); - - rdp.addr[rdp.tiles[tile].t_mem] = rdp.timg.addr; - - wxUint16 ul_s = (wxUint16)((rdp.cmd0 >> 14) & 0x03FF); - wxUint16 ul_t = (wxUint16)((rdp.cmd0 >> 2 ) & 0x03FF); - wxUint16 lr_s = (wxUint16)((rdp.cmd1 >> 14) & 0x03FF); - wxUint16 lr_t = (wxUint16)((rdp.cmd1 >> 2 ) & 0x03FF); - - if (lr_s < ul_s || lr_t < ul_t) return; - - if (wrong_tile >= 0) //there was a tile with zero length - { - rdp.tiles[wrong_tile].lr_s = lr_s; - - if (rdp.tiles[tile].size > rdp.tiles[wrong_tile].size) - rdp.tiles[wrong_tile].lr_s <<= (rdp.tiles[tile].size - rdp.tiles[wrong_tile].size); - else if (rdp.tiles[tile].size < rdp.tiles[wrong_tile].size) - rdp.tiles[wrong_tile].lr_s >>= (rdp.tiles[wrong_tile].size - rdp.tiles[tile].size); - rdp.tiles[wrong_tile].lr_t = lr_t; - rdp.tiles[wrong_tile].mask_s = rdp.tiles[wrong_tile].mask_t = 0; - // wrong_tile = -1; - } - - if (rdp.tbuff_tex)// && (rdp.tiles[tile].format == 0)) - { - FRDP("loadtile: tbuff_tex ul_s: %d, ul_t:%d\n", ul_s, ul_t); - rdp.tbuff_tex->tile_uls = ul_s; - rdp.tbuff_tex->tile_ult = ul_t; - } - - if ((settings.hacks&hack_Tonic) && tile == 7) - { - rdp.tiles[0].ul_s = ul_s; - rdp.tiles[0].ul_t = ul_t; - rdp.tiles[0].lr_s = lr_s; - rdp.tiles[0].lr_t = lr_t; - } - - wxUint32 height = lr_t - ul_t + 1; // get height - wxUint32 width = lr_s - ul_s + 1; - -#ifdef TEXTURE_FILTER - LOAD_TILE_INFO &info = rdp.load_info[rdp.tiles[tile].t_mem]; - info.tile_ul_s = ul_s; - info.tile_ul_t = ul_t; - info.tile_width = (rdp.tiles[tile].mask_s ? min((wxUint16)width, 1<> 1; - wxUint32 offs = ul_t * line_n; - offs += ul_s << rdp.tiles[tile].size >> 1; - offs += rdp.timg.addr; - if (offs >= BMASK) - return; - - if (rdp.timg.size == 3) - { - LoadTile32b(tile, ul_s, ul_t, width, height); - } - else - { - // check if points to bad location - if (offs + line_n*height > BMASK) - height = (BMASK - offs) / line_n; - if (height == 0) - return; - - wxUint32 wid_64 = rdp.tiles[tile].line; - unsigned char *dst = ((unsigned char *)rdp.tmem) + (rdp.tiles[tile].t_mem<<3); - unsigned char *end = ((unsigned char *)rdp.tmem) + 4096 - (wid_64<<3); - loadTile((uint32_t *)gfx.RDRAM, (uint32_t *)dst, wid_64, height, line_n, offs, (uint32_t *)end); - } - FRDP("loadtile: tile: %d, ul_s: %d, ul_t: %d, lr_s: %d, lr_t: %d\n", tile, - ul_s, ul_t, lr_s, lr_t); - - if (fb_hwfbe_enabled) - setTBufTex(rdp.tiles[tile].t_mem, rdp.tiles[tile].line*height); -} - -static void rdp_settile() -{ - tile_set = 1; // used to check if we only load the first settilesize - - rdp.first = 0; - - rdp.last_tile = (wxUint32)((rdp.cmd1 >> 24) & 0x07); - TILE *tile = &rdp.tiles[rdp.last_tile]; - - tile->format = (wxUint8)((rdp.cmd0 >> 21) & 0x07); - tile->size = (wxUint8)((rdp.cmd0 >> 19) & 0x03); - tile->line = (wxUint16)((rdp.cmd0 >> 9) & 0x01FF); - tile->t_mem = (wxUint16)(rdp.cmd0 & 0x1FF); - tile->palette = (wxUint8)((rdp.cmd1 >> 20) & 0x0F); - tile->clamp_t = (wxUint8)((rdp.cmd1 >> 19) & 0x01); - tile->mirror_t = (wxUint8)((rdp.cmd1 >> 18) & 0x01); - tile->mask_t = (wxUint8)((rdp.cmd1 >> 14) & 0x0F); - tile->shift_t = (wxUint8)((rdp.cmd1 >> 10) & 0x0F); - tile->clamp_s = (wxUint8)((rdp.cmd1 >> 9) & 0x01); - tile->mirror_s = (wxUint8)((rdp.cmd1 >> 8) & 0x01); - tile->mask_s = (wxUint8)((rdp.cmd1 >> 4) & 0x0F); - tile->shift_s = (wxUint8)(rdp.cmd1 & 0x0F); - - rdp.update |= UPDATE_TEXTURE; - - FRDP ("settile: tile: %d, format: %s, size: %s, line: %d, " - "t_mem: %08lx, palette: %d, clamp_t/mirror_t: %s, mask_t: %d, " - "shift_t: %d, clamp_s/mirror_s: %s, mask_s: %d, shift_s: %d\n", - rdp.last_tile, str_format[tile->format], str_size[tile->size], tile->line, - tile->t_mem, tile->palette, str_cm[(tile->clamp_t<<1)|tile->mirror_t], tile->mask_t, - tile->shift_t, str_cm[(tile->clamp_s<<1)|tile->mirror_s], tile->mask_s, tile->shift_s); - - if (fb_hwfbe_enabled && rdp.last_tile < rdp.cur_tile + 2) - { - for (int i = 0; i < 2; i++) - { - if (rdp.aTBuffTex[i]) - { - if (rdp.aTBuffTex[i]->t_mem == tile->t_mem) - { - if (rdp.aTBuffTex[i]->size == tile->size) - { - rdp.aTBuffTex[i]->tile = rdp.last_tile; - rdp.aTBuffTex[i]->info.format = tile->format == 0 ? GR_TEXFMT_RGB_565 : GR_TEXFMT_ALPHA_INTENSITY_88; - FRDP("rdp.aTBuffTex[%d] tile=%d, format=%s\n", i, rdp.last_tile, tile->format == 0 ? "RGB565" : "Alpha88"); - } - else - rdp.aTBuffTex[i] = 0; - break; - } - else if (rdp.aTBuffTex[i]->tile == rdp.last_tile) //wrong! t_mem must be the same - rdp.aTBuffTex[i] = 0; - } - } - } -} - -// -// fillrect - fills a rectangle -// - -static void rdp_fillrect() -{ - wxUint32 ul_x = ((rdp.cmd1 & 0x00FFF000) >> 14); - wxUint32 ul_y = (rdp.cmd1 & 0x00000FFF) >> 2; - wxUint32 lr_x = ((rdp.cmd0 & 0x00FFF000) >> 14) + 1; - wxUint32 lr_y = ((rdp.cmd0 & 0x00000FFF) >> 2) + 1; - if ((ul_x > lr_x) || (ul_y > lr_y)) - { - LRDP("Fillrect. Wrong coordinates. Skipped\n"); - return; - } - int pd_multiplayer = (settings.ucode == ucode_PerfectDark) && (rdp.cycle_mode == 3) && (rdp.fill_color == 0xFFFCFFFC); - if ((rdp.cimg == rdp.zimg) || (fb_emulation_enabled && rdp.frame_buffers[rdp.ci_count-1].status == ci_zimg) || pd_multiplayer) - { - LRDP("Fillrect - cleared the depth buffer\n"); - if (fullscreen) - { - if (!(settings.hacks&hack_Hyperbike) || rdp.ci_width > 64) //do not clear main depth buffer for aux depth buffers - { - update_scissor (); - grDepthMask (FXTRUE); - grColorMask (FXFALSE, FXFALSE); - grBufferClear (0, 0, rdp.fill_color ? rdp.fill_color&0xFFFF : 0xFFFF); - grColorMask (FXTRUE, FXTRUE); - rdp.update |= UPDATE_ZBUF_ENABLED; - } - //if (settings.frame_buffer&fb_depth_clear) - { - ul_x = min(max(ul_x, rdp.scissor_o.ul_x), rdp.scissor_o.lr_x); - lr_x = min(max(lr_x, rdp.scissor_o.ul_x), rdp.scissor_o.lr_x); - ul_y = min(max(ul_y, rdp.scissor_o.ul_y), rdp.scissor_o.lr_y); - lr_y = min(max(lr_y, rdp.scissor_o.ul_y), rdp.scissor_o.lr_y); - wxUint32 zi_width_in_dwords = rdp.ci_width >> 1; - ul_x >>= 1; - lr_x >>= 1; - wxUint32 * dst = (wxUint32*)(gfx.RDRAM+rdp.cimg); - dst += ul_y * zi_width_in_dwords; - for (wxUint32 y = ul_y; y < lr_y; y++) - { - for (wxUint32 x = ul_x; x < lr_x; x++) - { - dst[x] = rdp.fill_color; - } - dst += zi_width_in_dwords; - } - } - } - return; - } - - if (rdp.skip_drawing) - { - LRDP("Fillrect skipped\n"); - return; - } - - if (rdp.cur_image && (rdp.cur_image->format != 0) && (rdp.cycle_mode == 3) && (rdp.cur_image->width == lr_x - ul_x) && (rdp.cur_image->height == lr_y - ul_y)) - { - wxUint32 color = rdp.fill_color; - if (rdp.ci_size < 3) - { - color = ((color&1)?0xFF:0) | - ((wxUint32)((float)((color&0xF800) >> 11) / 31.0f * 255.0f) << 24) | - ((wxUint32)((float)((color&0x07C0) >> 6) / 31.0f * 255.0f) << 16) | - ((wxUint32)((float)((color&0x003E) >> 1) / 31.0f * 255.0f) << 8); - } - grDepthMask (FXFALSE); - grBufferClear (color, 0, 0xFFFF); - grDepthMask (FXTRUE); - rdp.update |= UPDATE_ZBUF_ENABLED; - LRDP("Fillrect - cleared the texture buffer\n"); - return; - } - - // Update scissor - if (fullscreen) - update_scissor (); - - if (settings.decrease_fillrect_edge && rdp.cycle_mode == 0) - { - lr_x--; lr_y--; - } - FRDP("fillrect (%d,%d) -> (%d,%d), cycle mode: %d, #%d, #%d\n", ul_x, ul_y, lr_x, lr_y, rdp.cycle_mode, - rdp.tri_n, rdp.tri_n+1); - - FRDP("scissor (%d,%d) -> (%d,%d)\n", rdp.scissor.ul_x, rdp.scissor.ul_y, rdp.scissor.lr_x, - rdp.scissor.lr_y); - - // KILL the floating point error with 0.01f - wxInt32 s_ul_x = (wxUint32)min(max(ul_x * rdp.scale_x + rdp.offset_x + 0.01f, rdp.scissor.ul_x), rdp.scissor.lr_x); - wxInt32 s_lr_x = (wxUint32)min(max(lr_x * rdp.scale_x + rdp.offset_x + 0.01f, rdp.scissor.ul_x), rdp.scissor.lr_x); - wxInt32 s_ul_y = (wxUint32)min(max(ul_y * rdp.scale_y + rdp.offset_y + 0.01f, rdp.scissor.ul_y), rdp.scissor.lr_y); - wxInt32 s_lr_y = (wxUint32)min(max(lr_y * rdp.scale_y + rdp.offset_y + 0.01f, rdp.scissor.ul_y), rdp.scissor.lr_y); - - if (s_lr_x < 0) s_lr_x = 0; - if (s_lr_y < 0) s_lr_y = 0; - if ((wxUint32)s_ul_x > settings.res_x) s_ul_x = settings.res_x; - if ((wxUint32)s_ul_y > settings.res_y) s_ul_y = settings.res_y; - - FRDP (" - %d, %d, %d, %d\n", s_ul_x, s_ul_y, s_lr_x, s_lr_y); - - if (fullscreen) - { - grFogMode (GR_FOG_DISABLE); - - const float Z = (rdp.cycle_mode == 3) ? 0.0f : set_sprite_combine_mode(); - - // Draw the rectangle - VERTEX v[4] = { - { (float)s_ul_x, (float)s_ul_y, Z, 1.0f, 0,0,0,0, {0,0,0,0}, 0,0, 0,0,0,0}, - { (float)s_lr_x, (float)s_ul_y, Z, 1.0f, 0,0,0,0, {0,0,0,0}, 0,0, 0,0,0,0}, - { (float)s_ul_x, (float)s_lr_y, Z, 1.0f, 0,0,0,0, {0,0,0,0}, 0,0, 0,0,0,0}, - { (float)s_lr_x, (float)s_lr_y, Z, 1.0f, 0,0,0,0, {0,0,0,0}, 0,0, 0,0,0,0} }; - - if (rdp.cycle_mode == 3) - { - wxUint32 color = rdp.fill_color; - - if ((settings.hacks&hack_PMario) && rdp.frame_buffers[rdp.ci_count-1].status == ci_aux) - { - //background of auxiliary frame buffers must have zero alpha. - //make it black, set 0 alpha to plack pixels on frame buffer read - color = 0; - } - else if (rdp.ci_size < 3) - { - color = ((color&1)?0xFF:0) | - ((wxUint32)((float)((color&0xF800) >> 11) / 31.0f * 255.0f) << 24) | - ((wxUint32)((float)((color&0x07C0) >> 6) / 31.0f * 255.0f) << 16) | - ((wxUint32)((float)((color&0x003E) >> 1) / 31.0f * 255.0f) << 8); - } - - grConstantColorValue (color); - - grColorCombine (GR_COMBINE_FUNCTION_LOCAL, - GR_COMBINE_FACTOR_NONE, - GR_COMBINE_LOCAL_CONSTANT, - GR_COMBINE_OTHER_NONE, - FXFALSE); - - grAlphaCombine (GR_COMBINE_FUNCTION_LOCAL, - GR_COMBINE_FACTOR_NONE, - GR_COMBINE_LOCAL_CONSTANT, - GR_COMBINE_OTHER_NONE, - FXFALSE); - - grAlphaBlendFunction (GR_BLEND_ONE, GR_BLEND_ZERO, GR_BLEND_ONE, GR_BLEND_ZERO); - - grAlphaTestFunction (GR_CMP_ALWAYS); - if (grStippleModeExt) - grStippleModeExt(GR_STIPPLE_DISABLE); - - grCullMode(GR_CULL_DISABLE); - grFogMode (GR_FOG_DISABLE); - grDepthBufferFunction (GR_CMP_ALWAYS); - grDepthMask (FXFALSE); - - rdp.update |= UPDATE_COMBINE | UPDATE_CULL_MODE | UPDATE_FOG_ENABLED | UPDATE_ZBUF_ENABLED; - } - else - { - wxUint32 cmb_mode_c = (rdp.cycle1 << 16) | (rdp.cycle2 & 0xFFFF); - wxUint32 cmb_mode_a = (rdp.cycle1 & 0x0FFF0000) | ((rdp.cycle2 >> 16) & 0x00000FFF); - if (cmb_mode_c == 0x9fff9fff || cmb_mode_a == 0x09ff09ff) //shade - { - AllowShadeMods (v, 4); - for (int k = 0; k < 4; k++) - apply_shade_mods (&v[k]); - } - if ((rdp.othermode_l & 0x4000) && ((rdp.othermode_l >> 16) == 0x0550)) //special blender mode for Bomberman64 - { - grAlphaCombine (GR_COMBINE_FUNCTION_LOCAL, - GR_COMBINE_FACTOR_NONE, - GR_COMBINE_LOCAL_CONSTANT, - GR_COMBINE_OTHER_NONE, - FXFALSE); - grConstantColorValue((cmb.ccolor&0xFFFFFF00)|(rdp.fog_color&0xFF)); - rdp.update |= UPDATE_COMBINE; - } - } - - if (settings.wireframe) - { - SetWireframeCol (); - grDrawLine (&v[0], &v[2]); - grDrawLine (&v[2], &v[1]); - grDrawLine (&v[1], &v[0]); - grDrawLine (&v[2], &v[3]); - grDrawLine (&v[3], &v[1]); - //grDrawLine (&v[1], &v[2]); - } - else - { - grDrawTriangle (&v[0], &v[2], &v[1]); - grDrawTriangle (&v[2], &v[3], &v[1]); - } - - if (_debugger.capture) - { - VERTEX v1[3]; - v1[0] = v[0]; - v1[1] = v[2]; - v1[2] = v[1]; - add_tri (v1, 3, TRI_FILLRECT); - rdp.tri_n ++; - v1[0] = v[2]; - v1[1] = v[3]; - add_tri (v1, 3, TRI_FILLRECT); - rdp.tri_n ++; - } - else - rdp.tri_n += 2; - } - else - { - rdp.tri_n += 2; - } -} - -// -// setfillcolor - sets the filling color -// - -static void rdp_setfillcolor() -{ - rdp.fill_color = rdp.cmd1; - rdp.update |= UPDATE_ALPHA_COMPARE | UPDATE_COMBINE; - - FRDP("setfillcolor: %08lx\n", rdp.cmd1); -} - -static void rdp_setfogcolor() -{ - rdp.fog_color = rdp.cmd1; - rdp.update |= UPDATE_COMBINE | UPDATE_FOG_ENABLED; - - FRDP("setfogcolor - %08lx\n", rdp.cmd1); -} - -static void rdp_setblendcolor() -{ - rdp.blend_color = rdp.cmd1; - rdp.update |= UPDATE_COMBINE; - - FRDP("setblendcolor: %08lx\n", rdp.cmd1); -} - -static void rdp_setprimcolor() -{ - rdp.prim_color = rdp.cmd1; - rdp.prim_lodmin = (rdp.cmd0 >> 8) & 0xFF; - rdp.prim_lodfrac = max(rdp.cmd0 & 0xFF, rdp.prim_lodmin); - rdp.update |= UPDATE_COMBINE; - - FRDP("setprimcolor: %08lx, lodmin: %d, lodfrac: %d\n", rdp.cmd1, rdp.prim_lodmin, - rdp.prim_lodfrac); -} - -static void rdp_setenvcolor() -{ - rdp.env_color = rdp.cmd1; - rdp.update |= UPDATE_COMBINE; - - FRDP("setenvcolor: %08lx\n", rdp.cmd1); -} - -static void rdp_setcombine() -{ - rdp.c_a0 = (wxUint8)((rdp.cmd0 >> 20) & 0xF); - rdp.c_b0 = (wxUint8)((rdp.cmd1 >> 28) & 0xF); - rdp.c_c0 = (wxUint8)((rdp.cmd0 >> 15) & 0x1F); - rdp.c_d0 = (wxUint8)((rdp.cmd1 >> 15) & 0x7); - rdp.c_Aa0 = (wxUint8)((rdp.cmd0 >> 12) & 0x7); - rdp.c_Ab0 = (wxUint8)((rdp.cmd1 >> 12) & 0x7); - rdp.c_Ac0 = (wxUint8)((rdp.cmd0 >> 9) & 0x7); - rdp.c_Ad0 = (wxUint8)((rdp.cmd1 >> 9) & 0x7); - - rdp.c_a1 = (wxUint8)((rdp.cmd0 >> 5) & 0xF); - rdp.c_b1 = (wxUint8)((rdp.cmd1 >> 24) & 0xF); - rdp.c_c1 = (wxUint8)((rdp.cmd0 >> 0) & 0x1F); - rdp.c_d1 = (wxUint8)((rdp.cmd1 >> 6) & 0x7); - rdp.c_Aa1 = (wxUint8)((rdp.cmd1 >> 21) & 0x7); - rdp.c_Ab1 = (wxUint8)((rdp.cmd1 >> 3) & 0x7); - rdp.c_Ac1 = (wxUint8)((rdp.cmd1 >> 18) & 0x7); - rdp.c_Ad1 = (wxUint8)((rdp.cmd1 >> 0) & 0x7); - - rdp.cycle1 = (rdp.c_a0<<0) | (rdp.c_b0<<4) | (rdp.c_c0<<8) | (rdp.c_d0<<13)| - (rdp.c_Aa0<<16)| (rdp.c_Ab0<<19)| (rdp.c_Ac0<<22)| (rdp.c_Ad0<<25); - rdp.cycle2 = (rdp.c_a1<<0) | (rdp.c_b1<<4) | (rdp.c_c1<<8) | (rdp.c_d1<<13)| - (rdp.c_Aa1<<16)| (rdp.c_Ab1<<19)| (rdp.c_Ac1<<22)| (rdp.c_Ad1<<25); - - rdp.update |= UPDATE_COMBINE; - - FRDP("setcombine\na0=%s b0=%s c0=%s d0=%s\nAa0=%s Ab0=%s Ac0=%s Ad0=%s\na1=%s b1=%s c1=%s d1=%s\nAa1=%s Ab1=%s Ac1=%s Ad1=%s\n", - Mode0[rdp.c_a0], Mode1[rdp.c_b0], Mode2[rdp.c_c0], Mode3[rdp.c_d0], - Alpha0[rdp.c_Aa0], Alpha1[rdp.c_Ab0], Alpha2[rdp.c_Ac0], Alpha3[rdp.c_Ad0], - Mode0[rdp.c_a1], Mode1[rdp.c_b1], Mode2[rdp.c_c1], Mode3[rdp.c_d1], - Alpha0[rdp.c_Aa1], Alpha1[rdp.c_Ab1], Alpha2[rdp.c_Ac1], Alpha3[rdp.c_Ad1]); -} - -// -// settextureimage - sets the source for an image copy -// - -static void rdp_settextureimage() -{ - static const char *format[] = { "RGBA", "YUV", "CI", "IA", "I", "?", "?", "?" }; - static const char *size[] = { "4bit", "8bit", "16bit", "32bit" }; - - rdp.timg.format = (wxUint8)((rdp.cmd0 >> 21) & 0x07); - rdp.timg.size = (wxUint8)((rdp.cmd0 >> 19) & 0x03); - rdp.timg.width = (wxUint16)(1 + (rdp.cmd0 & 0x00000FFF)); - rdp.timg.addr = segoffset(rdp.cmd1); - if (ucode5_texshiftaddr) - { - if (rdp.timg.format == 0) - { - wxUint16 * t = (wxUint16*)(gfx.RDRAM+ucode5_texshiftaddr); - ucode5_texshift = t[ucode5_texshiftcount^1]; - rdp.timg.addr += ucode5_texshift; - } - else - { - ucode5_texshiftaddr = 0; - ucode5_texshift = 0; - ucode5_texshiftcount = 0; - } - } - rdp.s2dex_tex_loaded = TRUE; - rdp.update |= UPDATE_TEXTURE; - - if (rdp.frame_buffers[rdp.ci_count-1].status == ci_copy_self && (rdp.timg.addr >= rdp.cimg) && (rdp.timg.addr < rdp.ci_end)) - { - if (!rdp.fb_drawn) - { - if (!rdp.cur_image) - CopyFrameBuffer(); - else - CloseTextureBuffer(TRUE); - rdp.fb_drawn = TRUE; - } - } - - if (fb_hwfbe_enabled) //search this texture among drawn texture buffers - FindTextureBuffer(rdp.timg.addr, rdp.timg.width); - - FRDP("settextureimage: format: %s, size: %s, width: %d, addr: %08lx\n", - format[rdp.timg.format], size[rdp.timg.size], - rdp.timg.width, rdp.timg.addr); -} - -static void rdp_setdepthimage() -{ - rdp.zimg = segoffset(rdp.cmd1) & BMASK; - rdp.zi_width = rdp.ci_width; - FRDP("setdepthimage - %08lx\n", rdp.zimg); -} - -int SwapOK = TRUE; -static void RestoreScale() -{ - FRDP("Return to original scale: x = %f, y = %f\n", rdp.scale_x_bak, rdp.scale_y_bak); - rdp.scale_x = rdp.scale_x_bak; - rdp.scale_y = rdp.scale_y_bak; - // update_scissor(); - rdp.view_scale[0] *= rdp.scale_x; - rdp.view_scale[1] *= rdp.scale_y; - rdp.view_trans[0] *= rdp.scale_x; - rdp.view_trans[1] *= rdp.scale_y; - rdp.update |= UPDATE_VIEWPORT | UPDATE_SCISSOR; - //* - if (fullscreen) - { - grDepthMask (FXFALSE); - grBufferClear (0, 0, 0xFFFF); - grDepthMask (FXTRUE); - } - //*/ -} - -static wxUint32 swapped_addr = 0; - -static void rdp_setcolorimage() -{ - if (fb_emulation_enabled && (rdp.num_of_ci < NUMTEXBUF)) - { - COLOR_IMAGE & cur_fb = rdp.frame_buffers[rdp.ci_count]; - COLOR_IMAGE & prev_fb = rdp.frame_buffers[rdp.ci_count?rdp.ci_count-1:0]; - COLOR_IMAGE & next_fb = rdp.frame_buffers[rdp.ci_count+1]; - switch (cur_fb.status) - { - case ci_main: - { - - if (rdp.ci_count == 0) - { - if ((rdp.ci_status == ci_aux)) //for PPL - { - float sx = rdp.scale_x; - float sy = rdp.scale_y; - rdp.scale_x = 1.0f; - rdp.scale_y = 1.0f; - CopyFrameBuffer (); - rdp.scale_x = sx; - rdp.scale_y = sy; - } - if (!fb_hwfbe_enabled) - { - if ((rdp.num_of_ci > 1) && - (next_fb.status == ci_aux) && - (next_fb.width >= cur_fb.width)) - { - rdp.scale_x = 1.0f; - rdp.scale_y = 1.0f; - } - } - else if (rdp.copy_ci_index && (settings.hacks&hack_PMario)) //tidal wave - OpenTextureBuffer(rdp.frame_buffers[rdp.main_ci_index]); - } - else if (!rdp.motionblur && fb_hwfbe_enabled && !SwapOK && (rdp.ci_count <= rdp.copy_ci_index)) - { - if (next_fb.status == ci_aux_copy) - OpenTextureBuffer(rdp.frame_buffers[rdp.main_ci_index]); - else - OpenTextureBuffer(rdp.frame_buffers[rdp.copy_ci_index]); - } - else if (fb_hwfbe_enabled && prev_fb.status == ci_aux) - { - if (rdp.motionblur) - { - rdp.cur_image = &(rdp.texbufs[rdp.cur_tex_buf].images[0]); - grRenderBuffer( GR_BUFFER_TEXTUREBUFFER_EXT ); - grTextureBufferExt( rdp.cur_image->tmu, rdp.cur_image->tex_addr, rdp.cur_image->info.smallLodLog2, rdp.cur_image->info.largeLodLog2, - rdp.cur_image->info.aspectRatioLog2, rdp.cur_image->info.format, GR_MIPMAPLEVELMASK_BOTH ); - } - else if (rdp.read_whole_frame) - { - OpenTextureBuffer(rdp.frame_buffers[rdp.main_ci_index]); - } - } - //else if (rdp.ci_status == ci_aux && !rdp.copy_ci_index) - // CloseTextureBuffer(); - - rdp.skip_drawing = FALSE; - } - break; - case ci_copy: - { - if (!rdp.motionblur || (settings.frame_buffer&fb_motionblur)) - { - if (cur_fb.width == rdp.ci_width) - { - if (CopyTextureBuffer(prev_fb, cur_fb)) - { - // if (CloseTextureBuffer(TRUE)) - //* - if ((settings.hacks&hack_Zelda) && (rdp.frame_buffers[rdp.ci_count+2].status == ci_aux) && !rdp.fb_drawn) //hack for photo camera in Zelda MM - { - CopyFrameBuffer (GR_BUFFER_TEXTUREBUFFER_EXT); - rdp.fb_drawn = TRUE; - memcpy(gfx.RDRAM+cur_fb.addr,gfx.RDRAM+rdp.cimg, (cur_fb.width*cur_fb.height)<>1); - } - //*/ - } - else - { - if (!rdp.fb_drawn || prev_fb.status == ci_copy_self) - { - CopyFrameBuffer (); - rdp.fb_drawn = TRUE; - } - memcpy(gfx.RDRAM+cur_fb.addr,gfx.RDRAM+rdp.cimg, (cur_fb.width*cur_fb.height)<>1); - } - } - else - { - CloseTextureBuffer(TRUE); - } - } - else - { - memset(gfx.RDRAM+cur_fb.addr, 0, cur_fb.width*cur_fb.height*rdp.ci_size); - } - rdp.skip_drawing = TRUE; - } - break; - case ci_aux_copy: - { - rdp.skip_drawing = FALSE; - if (CloseTextureBuffer(prev_fb.status != ci_aux_copy)) - ; - else if (!rdp.fb_drawn) - { - CopyFrameBuffer (); - rdp.fb_drawn = TRUE; - } - if (fb_hwfbe_enabled) - OpenTextureBuffer(cur_fb); - } - break; - case ci_old_copy: - { - if (!rdp.motionblur || (settings.frame_buffer&fb_motionblur)) - { - if (cur_fb.width == rdp.ci_width) - { - memcpy(gfx.RDRAM+cur_fb.addr,gfx.RDRAM+rdp.maincimg[1].addr, (cur_fb.width*cur_fb.height)<>1); - } - //rdp.skip_drawing = TRUE; - } - else - { - memset(gfx.RDRAM+cur_fb.addr, 0, (cur_fb.width*cur_fb.height)<>1); - } - } - break; - /* - else if (rdp.frame_buffers[rdp.ci_count].status == ci_main_i) - { - // CopyFrameBuffer (); - rdp.scale_x = rdp.scale_x_bak; - rdp.scale_y = rdp.scale_y_bak; - rdp.skip_drawing = FALSE; - } - */ - case ci_aux: - { - if (!fb_hwfbe_enabled && cur_fb.format != 0) - rdp.skip_drawing = TRUE; - else - { - rdp.skip_drawing = FALSE; - if (fb_hwfbe_enabled && OpenTextureBuffer(cur_fb)) - ; - else - { - if (cur_fb.format != 0) - rdp.skip_drawing = TRUE; - if (rdp.ci_count == 0) - { - // if (rdp.num_of_ci > 1) - // { - rdp.scale_x = 1.0f; - rdp.scale_y = 1.0f; - // } - } - else if (!fb_hwfbe_enabled && (prev_fb.status == ci_main) && - (prev_fb.width == cur_fb.width)) // for Pokemon Stadium - CopyFrameBuffer (); - } - } - cur_fb.status = ci_aux; - } - break; - case ci_zimg: - if (settings.ucode != ucode_PerfectDark) - { - if (fb_hwfbe_enabled && !rdp.copy_ci_index && (rdp.copy_zi_index || (settings.hacks&hack_BAR))) - { - GrLOD_t LOD = GR_LOD_LOG2_1024; - if (settings.scr_res_x > 1024) - LOD = GR_LOD_LOG2_2048; - grTextureAuxBufferExt( rdp.texbufs[0].tmu, rdp.texbufs[0].begin, LOD, LOD, - GR_ASPECT_LOG2_1x1, GR_TEXFMT_RGB_565, GR_MIPMAPLEVELMASK_BOTH ); - grAuxBufferExt( GR_BUFFER_TEXTUREAUXBUFFER_EXT ); - LRDP("rdp_setcolorimage - set texture depth buffer to TMU0\n"); - } - } - rdp.skip_drawing = TRUE; - break; - case ci_zcopy: - if (settings.ucode != ucode_PerfectDark) - { - if (fb_hwfbe_enabled && !rdp.copy_ci_index && rdp.copy_zi_index == rdp.ci_count) - { - CopyDepthBuffer(); - } - rdp.skip_drawing = TRUE; - } - break; - case ci_useless: - rdp.skip_drawing = TRUE; - break; - case ci_copy_self: - if (fb_hwfbe_enabled && (rdp.ci_count <= rdp.copy_ci_index) && (!SwapOK || settings.swapmode == 2)) - OpenTextureBuffer(cur_fb); - rdp.skip_drawing = FALSE; - break; - default: - rdp.skip_drawing = FALSE; - } - - if ((rdp.ci_count > 0) && (prev_fb.status >= ci_aux)) //for Pokemon Stadium - { - if (!fb_hwfbe_enabled && prev_fb.format == 0) - CopyFrameBuffer (); - else if ((settings.hacks&hack_Knockout) && prev_fb.width < 100) - CopyFrameBuffer (GR_BUFFER_TEXTUREBUFFER_EXT); - } - if (!fb_hwfbe_enabled && cur_fb.status == ci_copy) - { - if (!rdp.motionblur && (rdp.num_of_ci > rdp.ci_count+1) && (next_fb.status != ci_aux)) - { - RestoreScale(); - } - } - if (!fb_hwfbe_enabled && cur_fb.status == ci_aux) - { - if (cur_fb.format == 0) - { - if ((settings.hacks&hack_PPL) && (rdp.scale_x < 1.1f)) //need to put current image back to frame buffer - { - int width = cur_fb.width; - int height = cur_fb.height; - wxUint16 *ptr_dst = new wxUint16[width*height]; - wxUint16 *ptr_src = (wxUint16*)(gfx.RDRAM+cur_fb.addr); - wxUint16 c; - - for (int y=0; y> 1) | 0x8000; - ptr_dst[x + y * width] = c; - } - } - grLfbWriteRegion(GR_BUFFER_BACKBUFFER, - (wxUint32)rdp.offset_x, - (wxUint32)rdp.offset_y, - GR_LFB_SRC_FMT_555, - width, - height, - FXFALSE, - width<<1, - ptr_dst); - delete[] ptr_dst; - } - /* - else //just clear buffer - { - - grColorMask(FXTRUE, FXTRUE); - grBufferClear (0, 0, 0xFFFF); - } - */ - } - } - - if ((cur_fb.status == ci_main) && (rdp.ci_count > 0)) - { - int to_org_res = TRUE; - for (int i = rdp.ci_count + 1; i < rdp.num_of_ci; i++) - { - if ((rdp.frame_buffers[i].status != ci_main) && (rdp.frame_buffers[i].status != ci_zimg) && (rdp.frame_buffers[i].status != ci_zcopy)) - { - to_org_res = FALSE; - break; - } - } - if (to_org_res) - { - LRDP("return to original scale\n"); - rdp.scale_x = rdp.scale_x_bak; - rdp.scale_y = rdp.scale_y_bak; - if (fb_hwfbe_enabled && !rdp.read_whole_frame) - CloseTextureBuffer(); - } - if (fb_hwfbe_enabled && !rdp.read_whole_frame && (prev_fb.status >= ci_aux) && (rdp.ci_count > rdp.copy_ci_index)) - CloseTextureBuffer(); - - } - rdp.ci_status = cur_fb.status; - rdp.ci_count++; - } - - rdp.ocimg = rdp.cimg; - rdp.cimg = segoffset(rdp.cmd1) & BMASK; - rdp.ci_width = (rdp.cmd0 & 0xFFF) + 1; - if (fb_emulation_enabled) - rdp.ci_height = rdp.frame_buffers[rdp.ci_count-1].height; - else if (rdp.ci_width == 32) - rdp.ci_height = 32; - else - rdp.ci_height = rdp.scissor_o.lr_y; - if (rdp.zimg == rdp.cimg) - { - rdp.zi_width = rdp.ci_width; - // int zi_height = min((int)rdp.zi_width*3/4, (int)rdp.vi_height); - // rdp.zi_words = rdp.zi_width * zi_height; - } - wxUint32 format = (rdp.cmd0 >> 21) & 0x7; - rdp.ci_size = (rdp.cmd0 >> 19) & 0x3; - rdp.ci_end = rdp.cimg + ((rdp.ci_width*rdp.ci_height)<<(rdp.ci_size-1)); - FRDP("setcolorimage - %08lx, width: %d, height: %d, format: %d, size: %d\n", rdp.cmd1, rdp.ci_width, rdp.ci_height, format, rdp.ci_size); - FRDP("cimg: %08lx, ocimg: %08lx, SwapOK: %d\n", rdp.cimg, rdp.ocimg, SwapOK); - - if (format != 0) //can't draw into non RGBA buffer - { - if (!rdp.cur_image) - { - if (fb_hwfbe_enabled && rdp.ci_width <= 64) - OpenTextureBuffer(rdp.frame_buffers[rdp.ci_count - 1]); - else if (format > 2) - rdp.skip_drawing = TRUE; - return; - } - } - else - { - if (!fb_emulation_enabled) - rdp.skip_drawing = FALSE; - } - - CI_SET = TRUE; - if (settings.swapmode > 0) - { - if (rdp.zimg == rdp.cimg) - rdp.updatescreen = 1; - - int viSwapOK = ((settings.swapmode == 2) && (rdp.vi_org_reg == *gfx.VI_ORIGIN_REG)) ? FALSE : TRUE; - if ((rdp.zimg != rdp.cimg) && (rdp.ocimg != rdp.cimg) && SwapOK && viSwapOK && !rdp.cur_image) - { - if (fb_emulation_enabled) - rdp.maincimg[0] = rdp.frame_buffers[rdp.main_ci_index]; - else - rdp.maincimg[0].addr = rdp.cimg; - rdp.last_drawn_ci_addr = (settings.swapmode == 2) ? swapped_addr : rdp.maincimg[0].addr; - swapped_addr = rdp.cimg; - newSwapBuffers(); - rdp.vi_org_reg = *gfx.VI_ORIGIN_REG; - SwapOK = FALSE; - if (fb_hwfbe_enabled) - { - if (rdp.copy_ci_index && (rdp.frame_buffers[rdp.ci_count-1].status != ci_zimg)) - { - int idx = (rdp.frame_buffers[rdp.ci_count].status == ci_aux_copy) ? rdp.main_ci_index : rdp.copy_ci_index; - FRDP("attempt open tex buffer. status: %s, addr: %08lx\n", CIStatus[rdp.frame_buffers[idx].status], rdp.frame_buffers[idx].addr); - OpenTextureBuffer(rdp.frame_buffers[idx]); - if (rdp.frame_buffers[rdp.copy_ci_index].status == ci_main) //tidal wave - rdp.copy_ci_index = 0; - } - else if (rdp.read_whole_frame && !rdp.cur_image) - { - OpenTextureBuffer(rdp.frame_buffers[rdp.main_ci_index]); - } - } - } - } -} - -static void rsp_reserved0() -{ - if (settings.ucode == ucode_DiddyKong) - { - ucode5_texshiftaddr = segoffset(rdp.cmd1); - ucode5_texshiftcount = 0; - FRDP("uc5_texshift. addr: %08lx\n", ucode5_texshiftaddr); - } - else - { - RDP_E("reserved0 - IGNORED\n"); - LRDP("reserved0 - IGNORED\n"); - } -} - -static void rsp_reserved1() -{ - LRDP("reserved1 - ignored\n"); -} - -static void rsp_reserved2() -{ - LRDP("reserved2\n"); -} - -static void rsp_reserved3() -{ - LRDP("reserved3 - ignored\n"); -} - -void SetWireframeCol () -{ - if (!fullscreen) return; - - switch (settings.wfmode) - { - //case 0: // normal colors, don't do anything - case 1: // vertex colors - grColorCombine (GR_COMBINE_FUNCTION_LOCAL, - GR_COMBINE_FACTOR_NONE, - GR_COMBINE_LOCAL_ITERATED, - GR_COMBINE_OTHER_NONE, - FXFALSE); - grAlphaCombine (GR_COMBINE_FUNCTION_LOCAL, - GR_COMBINE_FACTOR_NONE, - GR_COMBINE_LOCAL_ITERATED, - GR_COMBINE_OTHER_NONE, - FXFALSE); - grAlphaBlendFunction (GR_BLEND_ONE, - GR_BLEND_ZERO, - GR_BLEND_ZERO, - GR_BLEND_ZERO); - grTexCombine (GR_TMU0, - GR_COMBINE_FUNCTION_ZERO, - GR_COMBINE_FACTOR_NONE, - GR_COMBINE_FUNCTION_ZERO, - GR_COMBINE_FACTOR_NONE, - FXFALSE, FXFALSE); - grTexCombine (GR_TMU1, - GR_COMBINE_FUNCTION_ZERO, - GR_COMBINE_FACTOR_NONE, - GR_COMBINE_FUNCTION_ZERO, - GR_COMBINE_FACTOR_NONE, - FXFALSE, FXFALSE); - break; - case 2: // red only - grColorCombine (GR_COMBINE_FUNCTION_LOCAL, - GR_COMBINE_FACTOR_NONE, - GR_COMBINE_LOCAL_CONSTANT, - GR_COMBINE_OTHER_NONE, - FXFALSE); - grAlphaCombine (GR_COMBINE_FUNCTION_LOCAL, - GR_COMBINE_FACTOR_NONE, - GR_COMBINE_LOCAL_CONSTANT, - GR_COMBINE_OTHER_NONE, - FXFALSE); - grConstantColorValue (0xFF0000FF); - grAlphaBlendFunction (GR_BLEND_ONE, - GR_BLEND_ZERO, - GR_BLEND_ZERO, - GR_BLEND_ZERO); - grTexCombine (GR_TMU0, - GR_COMBINE_FUNCTION_ZERO, - GR_COMBINE_FACTOR_NONE, - GR_COMBINE_FUNCTION_ZERO, - GR_COMBINE_FACTOR_NONE, - FXFALSE, FXFALSE); - grTexCombine (GR_TMU1, - GR_COMBINE_FUNCTION_ZERO, - GR_COMBINE_FACTOR_NONE, - GR_COMBINE_FUNCTION_ZERO, - GR_COMBINE_FACTOR_NONE, - FXFALSE, FXFALSE); - break; - } - - grAlphaTestFunction (GR_CMP_ALWAYS); - grCullMode (GR_CULL_DISABLE); - - rdp.update |= UPDATE_COMBINE | UPDATE_ALPHA_COMPARE; -} - -/****************************************************************** -Function: FrameBufferRead -Purpose: This function is called to notify the dll that the -frame buffer memory is beening read at the given address. -DLL should copy content from its render buffer to the frame buffer -in N64 RDRAM -DLL is responsible to maintain its own frame buffer memory addr list -DLL should copy 4KB block content back to RDRAM frame buffer. -Emulator should not call this function again if other memory -is read within the same 4KB range -input: addr rdram address -val val -size 1 = wxUint8, 2 = wxUint16, 4 = wxUint32 -output: none -*******************************************************************/ - -#ifdef __cplusplus -extern "C" { -#endif - -EXPORT void CALL FBRead(wxUint32 addr) -{ - LOG ("FBRead ()\n"); - - if (cpu_fb_ignore) - return; - if (cpu_fb_write_called) - { - cpu_fb_ignore = TRUE; - cpu_fb_write = FALSE; - return; - } - cpu_fb_read_called = TRUE; - wxUint32 a = segoffset(addr); - FRDP("FBRead. addr: %08lx\n", a); - if (!rdp.fb_drawn && (a >= rdp.cimg) && (a < rdp.ci_end)) - { - fbreads_back++; - //if (fbreads_back > 2) //&& (rdp.ci_width <= 320)) - { - CopyFrameBuffer (); - rdp.fb_drawn = TRUE; - } - } - if (!rdp.fb_drawn_front && (a >= rdp.maincimg[1].addr) && (a < rdp.maincimg[1].addr + rdp.ci_width*rdp.ci_height*2)) - { - fbreads_front++; - //if (fbreads_front > 2)//&& (rdp.ci_width <= 320)) - { - wxUint32 cimg = rdp.cimg; - rdp.cimg = rdp.maincimg[1].addr; - if (fb_emulation_enabled) - { - rdp.ci_width = rdp.maincimg[1].width; - rdp.ci_count = 0; - wxUint32 h = rdp.frame_buffers[0].height; - rdp.frame_buffers[0].height = rdp.maincimg[1].height; - CopyFrameBuffer(GR_BUFFER_FRONTBUFFER); - rdp.frame_buffers[0].height = h; - } - else - { - CopyFrameBuffer(GR_BUFFER_FRONTBUFFER); - } - rdp.cimg = cimg; - rdp.fb_drawn_front = TRUE; - } - } -} - -#if 0 -/****************************************************************** -Function: FrameBufferWriteList -Purpose: This function is called to notify the dll that the -frame buffer has been modified by CPU at the given address. -input: FrameBufferModifyEntry *plist -size = size of the plist, max = 1024 -output: none -*******************************************************************/ -EXPORT void CALL FBWList(FrameBufferModifyEntry *plist, wxUint32 size) -{ - LOG ("FBWList ()\n"); - FRDP("FBWList. size: %d\n", size); -} -#endif - -/****************************************************************** -Function: FrameBufferWrite -Purpose: This function is called to notify the dll that the -frame buffer has been modified by CPU at the given address. -input: addr rdram address -val val -size 1 = wxUint8, 2 = wxUint16, 4 = wxUint32 -output: none -*******************************************************************/ -EXPORT void CALL FBWrite(wxUint32 addr, wxUint32 size) -{ - LOG ("FBWrite ()\n"); - if (cpu_fb_ignore) - return; - if (cpu_fb_read_called) - { - cpu_fb_ignore = TRUE; - cpu_fb_write = FALSE; - return; - } - cpu_fb_write_called = TRUE; - wxUint32 a = segoffset(addr); - FRDP("FBWrite. addr: %08lx\n", a); - if (a < rdp.cimg || a > rdp.ci_end) - return; - cpu_fb_write = TRUE; - wxUint32 shift_l = (a-rdp.cimg) >> 1; - wxUint32 shift_r = shift_l+2; - - d_ul_x = min(d_ul_x, shift_l%rdp.ci_width); - d_ul_y = min(d_ul_y, shift_l/rdp.ci_width); - d_lr_x = max(d_lr_x, shift_r%rdp.ci_width); - d_lr_y = max(d_lr_y, shift_r/rdp.ci_width); -} - - -/************************************************************************ -Function: FBGetFrameBufferInfo -Purpose: This function is called by the emulator core to retrieve frame -buffer information from the video plugin in order to be able -to notify the video plugin about CPU frame buffer read/write -operations - -size: -= 1 byte -= 2 word (16 bit) <-- this is N64 default depth buffer format -= 4 dword (32 bit) - -when frame buffer information is not available yet, set all values -in the FrameBufferInfo structure to 0 - -input: FrameBufferInfo pinfo[6] -pinfo is pointed to a FrameBufferInfo structure which to be -filled in by this function -output: Values are return in the FrameBufferInfo structure -Plugin can return up to 6 frame buffer info -************************************************************************/ -///* -#if 0 -typedef struct -{ - wxUint32 addr; - wxUint32 size; - wxUint32 width; - wxUint32 height; -} FrameBufferInfo; -#endif -EXPORT void CALL FBGetFrameBufferInfo(void *p) -{ - VLOG ("FBGetFrameBufferInfo ()\n"); - FrameBufferInfo * pinfo = (FrameBufferInfo *)p; - memset(pinfo,0,sizeof(FrameBufferInfo)*6); - if (!(settings.frame_buffer&fb_get_info)) - return; - LRDP("FBGetFrameBufferInfo ()\n"); - //* - if (fb_emulation_enabled) - { - pinfo[0].addr = rdp.maincimg[1].addr; - pinfo[0].size = rdp.maincimg[1].size; - pinfo[0].width = rdp.maincimg[1].width; - pinfo[0].height = rdp.maincimg[1].height; - int info_index = 1; - for (int i = 0; i < rdp.num_of_ci && info_index < 6; i++) - { - COLOR_IMAGE & cur_fb = rdp.frame_buffers[i]; - if (cur_fb.status == ci_main || cur_fb.status == ci_copy_self || - cur_fb.status == ci_old_copy) - { - pinfo[info_index].addr = cur_fb.addr; - pinfo[info_index].size = cur_fb.size; - pinfo[info_index].width = cur_fb.width; - pinfo[info_index].height = cur_fb.height; - info_index++; - } - } - } - else - { - pinfo[0].addr = rdp.maincimg[0].addr; - pinfo[0].size = rdp.ci_size; - pinfo[0].width = rdp.ci_width; - pinfo[0].height = rdp.ci_width*3/4; - pinfo[1].addr = rdp.maincimg[1].addr; - pinfo[1].size = rdp.ci_size; - pinfo[1].width = rdp.ci_width; - pinfo[1].height = rdp.ci_width*3/4; - } - //*/ -} -#ifdef __cplusplus -} -#endif -//*/ -#include "ucodeFB.h" - -void DetectFrameBufferUsage () -{ - LRDP("DetectFrameBufferUsage\n"); - - wxUint32 dlist_start = *(wxUint32*)(gfx.DMEM+0xFF0); - wxUint32 a; - - int tidal = FALSE; - if ((settings.hacks&hack_PMario) && (rdp.copy_ci_index || rdp.frame_buffers[rdp.copy_ci_index].status == ci_copy_self)) - tidal = TRUE; - wxUint32 ci = rdp.cimg, zi = rdp.zimg; - wxUint32 ci_height = rdp.frame_buffers[(rdp.ci_count > 0)?rdp.ci_count-1:0].height; - rdp.main_ci = rdp.main_ci_end = rdp.main_ci_bg = rdp.ci_count = 0; - rdp.main_ci_index = rdp.copy_ci_index = rdp.copy_zi_index = 0; - rdp.zimg_end = 0; - rdp.tmpzimg = 0; - rdp.motionblur = FALSE; - rdp.main_ci_last_tex_addr = 0; - int previous_ci_was_read = rdp.read_previous_ci; - rdp.read_previous_ci = FALSE; - rdp.read_whole_frame = FALSE; - rdp.swap_ci_index = rdp.black_ci_index = -1; - SwapOK = TRUE; - - // Start executing at the start of the display list - rdp.pc_i = 0; - rdp.pc[rdp.pc_i] = dlist_start; - rdp.dl_count = -1; - rdp.halt = 0; - rdp.scale_x_bak = rdp.scale_x; - rdp.scale_y_bak = rdp.scale_y; - - // MAIN PROCESSING LOOP - do { - - // Get the address of the next command - a = rdp.pc[rdp.pc_i] & BMASK; - - // Load the next command and its input - rdp.cmd0 = ((wxUint32*)gfx.RDRAM)[a>>2]; // \ Current command, 64 bit - rdp.cmd1 = ((wxUint32*)gfx.RDRAM)[(a>>2)+1]; // / - - // Output the address before the command - - // Go to the next instruction - rdp.pc[rdp.pc_i] = (a+8) & BMASK; - - if (wxPtrToUInt(reinterpret_cast(gfx_instruction_lite[settings.ucode][rdp.cmd0>>24]))) - gfx_instruction_lite[settings.ucode][rdp.cmd0>>24] (); - - // check DL counter - if (rdp.dl_count != -1) - { - rdp.dl_count --; - if (rdp.dl_count == 0) - { - rdp.dl_count = -1; - - LRDP("End of DL\n"); - rdp.pc_i --; - } - } - - } while (!rdp.halt); - SwapOK = TRUE; - if (rdp.ci_count > NUMTEXBUF) //overflow - { - rdp.cimg = ci; - rdp.zimg = zi; - rdp.num_of_ci = rdp.ci_count; - rdp.scale_x = rdp.scale_x_bak; - rdp.scale_y = rdp.scale_y_bak; - return; - } - - if (rdp.black_ci_index > 0 && rdp.black_ci_index < rdp.copy_ci_index) - rdp.frame_buffers[rdp.black_ci_index].status = ci_main; - - if (rdp.frame_buffers[rdp.ci_count-1].status == ci_unknown) - { - if (rdp.ci_count > 1) - rdp.frame_buffers[rdp.ci_count-1].status = ci_aux; - else - rdp.frame_buffers[rdp.ci_count-1].status = ci_main; - } - - if ((rdp.frame_buffers[rdp.ci_count-1].status == ci_aux) && - (rdp.frame_buffers[rdp.main_ci_index].width < 320) && - (rdp.frame_buffers[rdp.ci_count-1].width > rdp.frame_buffers[rdp.main_ci_index].width)) - { - for (int i = 0; i < rdp.ci_count; i++) - { - if (rdp.frame_buffers[i].status == ci_main) - rdp.frame_buffers[i].status = ci_aux; - else if (rdp.frame_buffers[i].addr == rdp.frame_buffers[rdp.ci_count-1].addr) - rdp.frame_buffers[i].status = ci_main; - // FRDP("rdp.frame_buffers[%d].status = %d\n", i, rdp.frame_buffers[i].status); - } - rdp.main_ci_index = rdp.ci_count-1; - } - - int all_zimg = TRUE; - int i; - for (i = 0; i < rdp.ci_count; i++) - { - if (rdp.frame_buffers[i].status != ci_zimg) - { - all_zimg = FALSE; - break; - } - } - if (all_zimg) - { - for (i = 0; i < rdp.ci_count; i++) - rdp.frame_buffers[i].status = ci_main; - } - - LRDP("detect fb final results: \n"); - for (i = 0; i < rdp.ci_count; i++) - { - FRDP("rdp.frame_buffers[%d].status = %s, addr: %08lx, height: %d\n", i, CIStatus[rdp.frame_buffers[i].status], rdp.frame_buffers[i].addr, rdp.frame_buffers[i].height); - } - - rdp.cimg = ci; - rdp.zimg = zi; - rdp.num_of_ci = rdp.ci_count; - if (rdp.read_previous_ci && previous_ci_was_read) - { - if (!fb_hwfbe_enabled || !rdp.copy_ci_index) - rdp.motionblur = TRUE; - } - if (rdp.motionblur || fb_hwfbe_enabled || (rdp.frame_buffers[rdp.copy_ci_index].status == ci_aux_copy)) - { - rdp.scale_x = rdp.scale_x_bak; - rdp.scale_y = rdp.scale_y_bak; - } - - if ((rdp.read_previous_ci || previous_ci_was_read) && !rdp.copy_ci_index) - rdp.read_whole_frame = TRUE; - if (rdp.read_whole_frame) - { - if (fb_hwfbe_enabled) - { - if (rdp.read_previous_ci && !previous_ci_was_read && (settings.swapmode != 2) && (settings.ucode != ucode_PerfectDark)) - { - int ind = (rdp.ci_count > 0)?rdp.ci_count-1:0; - wxUint32 height = rdp.frame_buffers[ind].height; - rdp.frame_buffers[ind].height = ci_height; - CopyFrameBuffer(); - rdp.frame_buffers[ind].height = height; - } - if (rdp.swap_ci_index < 0) - { - rdp.texbufs[0].clear_allowed = rdp.texbufs[1].clear_allowed = TRUE; - OpenTextureBuffer(rdp.frame_buffers[rdp.main_ci_index]); - } - } - else - { - if (rdp.motionblur) - { - if (settings.frame_buffer&fb_motionblur) - CopyFrameBuffer(); - else - memset(gfx.RDRAM+rdp.cimg, 0, rdp.ci_width*rdp.ci_height*rdp.ci_size); - } - else //if (ci_width == rdp.frame_buffers[rdp.main_ci_index].width) - { - if (rdp.maincimg[0].height > 65) //for 1080 - { - rdp.cimg = rdp.maincimg[0].addr; - rdp.ci_width = rdp.maincimg[0].width; - rdp.ci_count = 0; - wxUint32 h = rdp.frame_buffers[0].height; - rdp.frame_buffers[0].height = rdp.maincimg[0].height; - CopyFrameBuffer(); - rdp.frame_buffers[0].height = h; - } - else //conker - { - CopyFrameBuffer(); - } - } - } - } - - if (fb_hwfbe_enabled) - { - for (i = 0; i < voodoo.num_tmu; i++) - { - rdp.texbufs[i].clear_allowed = TRUE; - for (int j = 0; j < 256; j++) - { - rdp.texbufs[i].images[j].drawn = FALSE; - rdp.texbufs[i].images[j].clear = TRUE; - } - } - if (tidal) - { - //LRDP("Tidal wave!\n"); - rdp.copy_ci_index = rdp.main_ci_index; - } - } - rdp.ci_count = 0; - if (settings.hacks&hack_Banjo2) - rdp.cur_tex_buf = 0; - rdp.maincimg[0] = rdp.frame_buffers[rdp.main_ci_index]; - // rdp.scale_x = rdp.scale_x_bak; - // rdp.scale_y = rdp.scale_y_bak; - LRDP("DetectFrameBufferUsage End\n"); -} - -/******************************************* - * ProcessRDPList * - ******************************************* - * based on sources of ziggy's z64 * - *******************************************/ - -static wxUint32 rdp_cmd_ptr = 0; -static wxUint32 rdp_cmd_cur = 0; -static wxUint32 rdp_cmd_data[0x1000]; - -void lle_triangle(wxUint32 w1, wxUint32 w2, int shade, int texture, int zbuffer, - wxUint32 * rdp_cmd) -{ - rdp.cur_tile = (w1 >> 16) & 0x7; - int j; - int xleft, xright, xleft_inc, xright_inc; - int r, g, b, a, z, s, t, w; - int drdx = 0, dgdx = 0, dbdx = 0, dadx = 0, dzdx = 0, dsdx = 0, dtdx = 0, dwdx = 0; - int drde = 0, dgde = 0, dbde = 0, dade = 0, dzde = 0, dsde = 0, dtde = 0, dwde = 0; - int flip = (w1 & 0x800000) ? 1 : 0; - - wxInt32 yl, ym, yh; - wxInt32 xl, xm, xh; - wxInt32 dxldy, dxhdy, dxmdy; - wxUint32 w3, w4, w5, w6, w7, w8; - - wxUint32 * shade_base = rdp_cmd + 8; - wxUint32 * texture_base = rdp_cmd + 8; - wxUint32 * zbuffer_base = rdp_cmd + 8; - - if (shade) - { - texture_base += 16; - zbuffer_base += 16; - } - if (texture) - { - zbuffer_base += 16; - } - - w3 = rdp_cmd[2]; - w4 = rdp_cmd[3]; - w5 = rdp_cmd[4]; - w6 = rdp_cmd[5]; - w7 = rdp_cmd[6]; - w8 = rdp_cmd[7]; - - yl = (w1 & 0x3fff); - ym = ((w2 >> 16) & 0x3fff); - yh = ((w2 >> 0) & 0x3fff); - xl = (wxInt32)(w3); - xh = (wxInt32)(w5); - xm = (wxInt32)(w7); - dxldy = (wxInt32)(w4); - dxhdy = (wxInt32)(w6); - dxmdy = (wxInt32)(w8); - - if (yl & (0x800<<2)) yl |= 0xfffff000<<2; - if (ym & (0x800<<2)) ym |= 0xfffff000<<2; - if (yh & (0x800<<2)) yh |= 0xfffff000<<2; - - yh &= ~3; - - r = 0xff; g = 0xff; b = 0xff; a = 0xff; z = 0xffff0000; s = 0; t = 0; w = 0x30000; - - if (shade) - { - r = (shade_base[0] & 0xffff0000) | ((shade_base[+4 ] >> 16) & 0x0000ffff); - g = ((shade_base[0 ] << 16) & 0xffff0000) | (shade_base[4 ] & 0x0000ffff); - b = (shade_base[1 ] & 0xffff0000) | ((shade_base[5 ] >> 16) & 0x0000ffff); - a = ((shade_base[1 ] << 16) & 0xffff0000) | (shade_base[5 ] & 0x0000ffff); - drdx = (shade_base[2 ] & 0xffff0000) | ((shade_base[6 ] >> 16) & 0x0000ffff); - dgdx = ((shade_base[2 ] << 16) & 0xffff0000) | (shade_base[6 ] & 0x0000ffff); - dbdx = (shade_base[3 ] & 0xffff0000) | ((shade_base[7 ] >> 16) & 0x0000ffff); - dadx = ((shade_base[3 ] << 16) & 0xffff0000) | (shade_base[7 ] & 0x0000ffff); - drde = (shade_base[8 ] & 0xffff0000) | ((shade_base[12] >> 16) & 0x0000ffff); - dgde = ((shade_base[8 ] << 16) & 0xffff0000) | (shade_base[12] & 0x0000ffff); - dbde = (shade_base[9 ] & 0xffff0000) | ((shade_base[13] >> 16) & 0x0000ffff); - dade = ((shade_base[9 ] << 16) & 0xffff0000) | (shade_base[13] & 0x0000ffff); - } - if (texture) - { - s = (texture_base[0 ] & 0xffff0000) | ((texture_base[4 ] >> 16) & 0x0000ffff); - t = ((texture_base[0 ] << 16) & 0xffff0000) | (texture_base[4 ] & 0x0000ffff); - w = (texture_base[1 ] & 0xffff0000) | ((texture_base[5 ] >> 16) & 0x0000ffff); - // w = abs(w); - dsdx = (texture_base[2 ] & 0xffff0000) | ((texture_base[6 ] >> 16) & 0x0000ffff); - dtdx = ((texture_base[2 ] << 16) & 0xffff0000) | (texture_base[6 ] & 0x0000ffff); - dwdx = (texture_base[3 ] & 0xffff0000) | ((texture_base[7 ] >> 16) & 0x0000ffff); - dsde = (texture_base[8 ] & 0xffff0000) | ((texture_base[12] >> 16) & 0x0000ffff); - dtde = ((texture_base[8 ] << 16) & 0xffff0000) | (texture_base[12] & 0x0000ffff); - dwde = (texture_base[9 ] & 0xffff0000) | ((texture_base[13] >> 16) & 0x0000ffff); - } - if (zbuffer) - { - z = zbuffer_base[0]; - dzdx = zbuffer_base[1]; - dzde = zbuffer_base[2]; - } - - xh <<= 2; xm <<= 2; xl <<= 2; - r <<= 2; g <<= 2; b <<= 2; a <<= 2; - dsde >>= 2; dtde >>= 2; dsdx >>= 2; dtdx >>= 2; - dzdx >>= 2; dzde >>= 2; - dwdx >>= 2; dwde >>= 2; - -#define XSCALE(x) (float(x)/(1<<18)) -#define YSCALE(y) (float(y)/(1<<2)) -#define ZSCALE(z) ((rdp.zsrc == 1)? float(rdp.prim_depth) : float(wxUint32(z))/0xffff0000) - //#define WSCALE(w) (rdp.Persp_en? (float(wxUint32(w) + 0x10000)/0xffff0000) : 1.0f) - //#define WSCALE(w) (rdp.Persp_en? 4294901760.0/(w + 65536) : 1.0f) -#define WSCALE(w) (rdp.Persp_en? 65536.0f/float((w+ 0xffff)>>16) : 1.0f) -#define CSCALE(c) (((c)>0x3ff0000? 0x3ff0000:((c)<0? 0 : (c)))>>18) -#define _PERSP(w) ( w ) -#define PERSP(s, w) ( ((int64_t)(s) << 20) / (_PERSP(w)? _PERSP(w):1) ) -#define SSCALE(s, _w) (rdp.Persp_en? float(PERSP(s, _w))/(1 << 10) : float(s)/(1<<21)) -#define TSCALE(s, w) (rdp.Persp_en? float(PERSP(s, w))/(1 << 10) : float(s)/(1<<21)) - - int nbVtxs = 0; - VERTEX vtxbuf[12]; - VERTEX * vtx = &vtxbuf[nbVtxs++]; - - xleft = xm; - xright = xh; - xleft_inc = dxmdy; - xright_inc = dxhdy; - - while (yh xright-0x10000))) { - xleft += xleft_inc; - xright += xright_inc; - s += dsde; t += dtde; w += dwde; - r += drde; g += dgde; b += dbde; a += dade; - z += dzde; - yh++; - } - - j = ym-yh; - if (j > 0) - { - int dx = (xleft-xright)>>16; - if ((!flip && xleft < xright) || - (flip/* && xleft > xright*/)) - { - if (shade) { - vtx->r = CSCALE(r+drdx*dx); - vtx->g = CSCALE(g+dgdx*dx); - vtx->b = CSCALE(b+dbdx*dx); - vtx->a = CSCALE(a+dadx*dx); - } - if (texture) { - vtx->ou = SSCALE(s+dsdx*dx, w+dwdx*dx); - vtx->ov = TSCALE(t+dtdx*dx, w+dwdx*dx); - } - vtx->x = XSCALE(xleft); - vtx->y = YSCALE(yh); - vtx->z = ZSCALE(z+dzdx*dx); - vtx->w = WSCALE(w+dwdx*dx); - vtx = &vtxbuf[nbVtxs++]; - } - if ((!flip/* && xleft < xright*/) || - (flip && xleft > xright)) - { - if (shade) { - vtx->r = CSCALE(r); - vtx->g = CSCALE(g); - vtx->b = CSCALE(b); - vtx->a = CSCALE(a); - } - if (texture) { - vtx->ou = SSCALE(s, w); - vtx->ov = TSCALE(t, w); - } - vtx->x = XSCALE(xright); - vtx->y = YSCALE(yh); - vtx->z = ZSCALE(z); - vtx->w = WSCALE(w); - vtx = &vtxbuf[nbVtxs++]; - } - xleft += xleft_inc*j; xright += xright_inc*j; - s += dsde*j; t += dtde*j; - if (w + dwde*j) w += dwde*j; - else w += dwde*(j-1); - r += drde*j; g += dgde*j; b += dbde*j; a += dade*j; - z += dzde*j; - // render ... - } - - if (xl != xh) - xleft = xl; - - //if (yl-ym > 0) - { - int dx = (xleft-xright)>>16; - if ((!flip && xleft <= xright) || - (flip/* && xleft >= xright*/)) - { - if (shade) { - vtx->r = CSCALE(r+drdx*dx); - vtx->g = CSCALE(g+dgdx*dx); - vtx->b = CSCALE(b+dbdx*dx); - vtx->a = CSCALE(a+dadx*dx); - } - if (texture) { - vtx->ou = SSCALE(s+dsdx*dx, w+dwdx*dx); - vtx->ov = TSCALE(t+dtdx*dx, w+dwdx*dx); - } - vtx->x = XSCALE(xleft); - vtx->y = YSCALE(ym); - vtx->z = ZSCALE(z+dzdx*dx); - vtx->w = WSCALE(w+dwdx*dx); - vtx = &vtxbuf[nbVtxs++]; - } - if ((!flip/* && xleft <= xright*/) || - (flip && xleft >= xright)) - { - if (shade) { - vtx->r = CSCALE(r); - vtx->g = CSCALE(g); - vtx->b = CSCALE(b); - vtx->a = CSCALE(a); - } - if (texture) { - vtx->ou = SSCALE(s, w); - vtx->ov = TSCALE(t, w); - } - vtx->x = XSCALE(xright); - vtx->y = YSCALE(ym); - vtx->z = ZSCALE(z); - vtx->w = WSCALE(w); - vtx = &vtxbuf[nbVtxs++]; - } - } - xleft_inc = dxldy; - xright_inc = dxhdy; - - j = yl-ym; - //j--; // ? - xleft += xleft_inc*j; xright += xright_inc*j; - s += dsde*j; t += dtde*j; w += dwde*j; - r += drde*j; g += dgde*j; b += dbde*j; a += dade*j; - z += dzde*j; - - while (yl>ym && - !((!flip && xleft < xright+0x10000) || - (flip && xleft > xright-0x10000))) { - xleft -= xleft_inc; xright -= xright_inc; - s -= dsde; t -= dtde; w -= dwde; - r -= drde; g -= dgde; b -= dbde; a -= dade; - z -= dzde; - j--; - yl--; - } - - // render ... - if (j >= 0) { - int dx = (xleft-xright)>>16; - if ((!flip && xleft <= xright) || - (flip/* && xleft >= xright*/)) - { - if (shade) { - vtx->r = CSCALE(r+drdx*dx); - vtx->g = CSCALE(g+dgdx*dx); - vtx->b = CSCALE(b+dbdx*dx); - vtx->a = CSCALE(a+dadx*dx); - } - if (texture) { - vtx->ou = SSCALE(s+dsdx*dx, w+dwdx*dx); - vtx->ov = TSCALE(t+dtdx*dx, w+dwdx*dx); - } - vtx->x = XSCALE(xleft); - vtx->y = YSCALE(yl); - vtx->z = ZSCALE(z+dzdx*dx); - vtx->w = WSCALE(w+dwdx*dx); - vtx = &vtxbuf[nbVtxs++]; - } - if ((!flip/* && xleft <= xright*/) || - (flip && xleft >= xright)) - { - if (shade) { - vtx->r = CSCALE(r); - vtx->g = CSCALE(g); - vtx->b = CSCALE(b); - vtx->a = CSCALE(a); - } - if (texture) { - vtx->ou = SSCALE(s, w); - vtx->ov = TSCALE(t, w); - } - vtx->x = XSCALE(xright); - vtx->y = YSCALE(yl); - vtx->z = ZSCALE(z); - vtx->w = WSCALE(w); - vtx = &vtxbuf[nbVtxs++]; - } - } - - if (fullscreen) - { - update (); - for (int k = 0; k < nbVtxs-1; k++) - { - VERTEX * v = &vtxbuf[k]; - v->x = v->x * rdp.scale_x + rdp.offset_x; - v->y = v->y * rdp.scale_y + rdp.offset_y; - // v->z = 1.0f;///v->w; - v->q = 1.0f/v->w; - v->u1 = v->u0 = v->ou; - v->v1 = v->v0 = v->ov; - if (rdp.tex >= 1 && rdp.cur_cache[0]) - { - if (rdp.tiles[rdp.cur_tile].shift_s) - { - if (rdp.tiles[rdp.cur_tile].shift_s > 10) - v->u0 *= (float)(1 << (16 - rdp.tiles[rdp.cur_tile].shift_s)); - else - v->u0 /= (float)(1 << rdp.tiles[rdp.cur_tile].shift_s); - } - if (rdp.tiles[rdp.cur_tile].shift_t) - { - if (rdp.tiles[rdp.cur_tile].shift_t > 10) - v->v0 *= (float)(1 << (16 - rdp.tiles[rdp.cur_tile].shift_t)); - else - v->v0 /= (float)(1 << rdp.tiles[rdp.cur_tile].shift_t); - } - - v->u0 -= rdp.tiles[rdp.cur_tile].f_ul_s; - v->v0 -= rdp.tiles[rdp.cur_tile].f_ul_t; - v->u0 = rdp.cur_cache[0]->c_off + rdp.cur_cache[0]->c_scl_x * v->u0; - v->v0 = rdp.cur_cache[0]->c_off + rdp.cur_cache[0]->c_scl_y * v->v0; - v->u0 /= v->w; - v->v0 /= v->w; - } - - if (rdp.tex >= 2 && rdp.cur_cache[1]) - { - if (rdp.tiles[rdp.cur_tile+1].shift_s) - { - if (rdp.tiles[rdp.cur_tile+1].shift_s > 10) - v->u1 *= (float)(1 << (16 - rdp.tiles[rdp.cur_tile+1].shift_s)); - else - v->u1 /= (float)(1 << rdp.tiles[rdp.cur_tile+1].shift_s); - } - if (rdp.tiles[rdp.cur_tile+1].shift_t) - { - if (rdp.tiles[rdp.cur_tile+1].shift_t > 10) - v->v1 *= (float)(1 << (16 - rdp.tiles[rdp.cur_tile+1].shift_t)); - else - v->v1 /= (float)(1 << rdp.tiles[rdp.cur_tile+1].shift_t); - } - - v->u1 -= rdp.tiles[rdp.cur_tile+1].f_ul_s; - v->v1 -= rdp.tiles[rdp.cur_tile+1].f_ul_t; - v->u1 = rdp.cur_cache[1]->c_off + rdp.cur_cache[1]->c_scl_x * v->u1; - v->v1 = rdp.cur_cache[1]->c_off + rdp.cur_cache[1]->c_scl_y * v->v1; - v->u1 /= v->w; - v->v1 /= v->w; - } - apply_shade_mods (v); - } - ConvertCoordsConvert (vtxbuf, nbVtxs); - grCullMode (GR_CULL_DISABLE); - grDrawVertexArrayContiguous (GR_TRIANGLE_STRIP, nbVtxs-1, vtxbuf, sizeof(VERTEX)); - if (_debugger.capture) - { - VERTEX vl[3]; - vl[0] = vtxbuf[0]; - vl[1] = vtxbuf[2]; - vl[2] = vtxbuf[1]; - add_tri (vl, 3, TRI_TRIANGLE); - rdp.tri_n++; - if (nbVtxs > 4) - { - vl[0] = vtxbuf[2]; - vl[1] = vtxbuf[3]; - vl[2] = vtxbuf[1]; - add_tri (vl, 3, TRI_TRIANGLE); - rdp.tri_n++; - } - } - } -} - -static void rdp_triangle(int shade, int texture, int zbuffer) -{ - lle_triangle(rdp.cmd0, rdp.cmd1, shade, texture, zbuffer, rdp_cmd_data + rdp_cmd_cur); -} - -static void rdp_trifill() -{ - rdp_triangle(0, 0, 0); - LRDP("trifill\n"); -} - -static void rdp_trishade() -{ - rdp_triangle(1, 0, 0); - LRDP("trishade\n"); -} - -static void rdp_tritxtr() -{ - rdp_triangle(0, 1, 0); - LRDP("tritxtr\n"); -} - -static void rdp_trishadetxtr() -{ - rdp_triangle(1, 1, 0); - LRDP("trishadetxtr\n"); -} - -static void rdp_trifillz() -{ - rdp_triangle(0, 0, 1); - LRDP("trifillz\n"); -} - -static void rdp_trishadez() -{ - rdp_triangle(1, 0, 1); - LRDP("trishadez\n"); -} - -static void rdp_tritxtrz() -{ - rdp_triangle(0, 1, 1); - LRDP("tritxtrz\n"); -} - -static void rdp_trishadetxtrz() -{ - rdp_triangle(1, 1, 1); - LRDP("trishadetxtrz\n"); -} - - -static rdp_instr rdp_command_table[64] = -{ - /* 0x00 */ - spnoop, undef, undef, undef, - undef, undef, undef, undef, - rdp_trifill, rdp_trifillz, rdp_tritxtr, rdp_tritxtrz, - rdp_trishade, rdp_trishadez, rdp_trishadetxtr, rdp_trishadetxtrz, - /* 0x10 */ - undef, undef, undef, undef, - undef, undef, undef, undef, - undef, undef, undef, undef, - undef, undef, undef, undef, - /* 0x20 */ - undef, undef, undef, undef, - rdp_texrect, rdp_texrect, rdp_loadsync, rdp_pipesync, - rdp_tilesync, rdp_fullsync, rdp_setkeygb, rdp_setkeyr, - rdp_setconvert, rdp_setscissor, rdp_setprimdepth, rdp_setothermode, - /* 0x30 */ - rdp_loadtlut, undef, rdp_settilesize, rdp_loadblock, - rdp_loadtile, rdp_settile, rdp_fillrect, rdp_setfillcolor, - rdp_setfogcolor, rdp_setblendcolor, rdp_setprimcolor, rdp_setenvcolor, - rdp_setcombine, rdp_settextureimage, rdp_setdepthimage, rdp_setcolorimage -}; - -static const wxUint32 rdp_command_length[64] = -{ - 8, // 0x00, No Op - 8, // 0x01, ??? - 8, // 0x02, ??? - 8, // 0x03, ??? - 8, // 0x04, ??? - 8, // 0x05, ??? - 8, // 0x06, ??? - 8, // 0x07, ??? - 32, // 0x08, Non-Shaded Triangle - 32+16, // 0x09, Non-Shaded, Z-Buffered Triangle - 32+64, // 0x0a, Textured Triangle - 32+64+16, // 0x0b, Textured, Z-Buffered Triangle - 32+64, // 0x0c, Shaded Triangle - 32+64+16, // 0x0d, Shaded, Z-Buffered Triangle - 32+64+64, // 0x0e, Shaded+Textured Triangle - 32+64+64+16,// 0x0f, Shaded+Textured, Z-Buffered Triangle - 8, // 0x10, ??? - 8, // 0x11, ??? - 8, // 0x12, ??? - 8, // 0x13, ??? - 8, // 0x14, ??? - 8, // 0x15, ??? - 8, // 0x16, ??? - 8, // 0x17, ??? - 8, // 0x18, ??? - 8, // 0x19, ??? - 8, // 0x1a, ??? - 8, // 0x1b, ??? - 8, // 0x1c, ??? - 8, // 0x1d, ??? - 8, // 0x1e, ??? - 8, // 0x1f, ??? - 8, // 0x20, ??? - 8, // 0x21, ??? - 8, // 0x22, ??? - 8, // 0x23, ??? - 16, // 0x24, Texture_Rectangle - 16, // 0x25, Texture_Rectangle_Flip - 8, // 0x26, Sync_Load - 8, // 0x27, Sync_Pipe - 8, // 0x28, Sync_Tile - 8, // 0x29, Sync_Full - 8, // 0x2a, Set_Key_GB - 8, // 0x2b, Set_Key_R - 8, // 0x2c, Set_Convert - 8, // 0x2d, Set_Scissor - 8, // 0x2e, Set_Prim_Depth - 8, // 0x2f, Set_Other_Modes - 8, // 0x30, Load_TLUT - 8, // 0x31, ??? - 8, // 0x32, Set_Tile_Size - 8, // 0x33, Load_Block - 8, // 0x34, Load_Tile - 8, // 0x35, Set_Tile - 8, // 0x36, Fill_Rectangle - 8, // 0x37, Set_Fill_Color - 8, // 0x38, Set_Fog_Color - 8, // 0x39, Set_Blend_Color - 8, // 0x3a, Set_Prim_Color - 8, // 0x3b, Set_Env_Color - 8, // 0x3c, Set_Combine - 8, // 0x3d, Set_Texture_Image - 8, // 0x3e, Set_Mask_Image - 8 // 0x3f, Set_Color_Image -}; - -#define rdram ((wxUint32*)gfx.RDRAM) -#define rsp_dmem ((wxUint32*)gfx.DMEM) - -#define dp_start (*(wxUint32*)gfx.DPC_START_REG) -#define dp_end (*(wxUint32*)gfx.DPC_END_REG) -#define dp_current (*(wxUint32*)gfx.DPC_CURRENT_REG) -#define dp_status (*(wxUint32*)gfx.DPC_STATUS_REG) - -inline wxUint32 READ_RDP_DATA(wxUint32 address) -{ - if (dp_status & 0x1) // XBUS_DMEM_DMA enabled - return rsp_dmem[(address & 0xfff)>>2]; - else - return rdram[address>>2]; -} - -static void rdphalf_1() -{ - wxUint32 cmd = rdp.cmd1 >> 24; - if (cmd >= 0xc8 && cmd <=0xcf) //triangle command - { - LRDP("rdphalf_1 - lle triangle\n"); - rdp_cmd_ptr = 0; - rdp_cmd_cur = 0; - wxUint32 a; - - do - { - rdp_cmd_data[rdp_cmd_ptr++] = rdp.cmd1; - // check DL counter - if (rdp.dl_count != -1) - { - rdp.dl_count --; - if (rdp.dl_count == 0) - { - rdp.dl_count = -1; - - LRDP("End of DL\n"); - rdp.pc_i --; - } - } - - // Get the address of the next command - a = rdp.pc[rdp.pc_i] & BMASK; - - // Load the next command and its input - rdp.cmd0 = ((wxUint32*)gfx.RDRAM)[a>>2]; // \ Current command, 64 bit - rdp.cmd1 = ((wxUint32*)gfx.RDRAM)[(a>>2)+1]; // / - - // Go to the next instruction - rdp.pc[rdp.pc_i] = (a+8) & BMASK; - - }while ((rdp.cmd0 >> 24) != 0xb3); - rdp_cmd_data[rdp_cmd_ptr++] = rdp.cmd1; - cmd = (rdp_cmd_data[rdp_cmd_cur] >> 24) & 0x3f; - rdp.cmd0 = rdp_cmd_data[rdp_cmd_cur+0]; - rdp.cmd1 = rdp_cmd_data[rdp_cmd_cur+1]; - /* - wxUint32 cmd3 = ((wxUint32*)gfx.RDRAM)[(a>>2)+2]; - if ((cmd3>>24) == 0xb4) - rglSingleTriangle = TRUE; - else - rglSingleTriangle = FALSE; - */ - rdp_command_table[cmd](); - } - else - { - LRDP("rdphalf_1 - IGNORED\n"); - } -} - -static void rdphalf_2() -{ - RDP_E("rdphalf_2 - IGNORED\n"); - LRDP("rdphalf_2 - IGNORED\n"); -} - -static void rdphalf_cont() -{ - RDP_E("rdphalf_cont - IGNORED\n"); - LRDP("rdphalf_cont - IGNORED\n"); -} - -/****************************************************************** -Function: ProcessRDPList -Purpose: This function is called when there is a Dlist to be -processed. (Low level GFX list) -input: none -output: none -*******************************************************************/ -#ifdef __cplusplus -extern "C" { -#endif -EXPORT void CALL ProcessRDPList(void) -{ - LOG ("ProcessRDPList ()\n"); - LRDP("ProcessRDPList ()\n"); - - SoftLocker lock(mutexProcessDList); - if (!lock.IsOk()) //mutex is busy - { - if (!fullscreen) - drawNoFullscreenMessage(); - // Set an interrupt to allow the game to continue - *gfx.MI_INTR_REG |= 0x20; - gfx.CheckInterrupts(); - return; - } - - wxUint32 i; - wxUint32 cmd, length, cmd_length; - rdp_cmd_ptr = 0; - rdp_cmd_cur = 0; - - if (dp_end <= dp_current) return; - length = dp_end - dp_current; - - // load command data - for (i=0; i < length; i += 4) - { - rdp_cmd_data[rdp_cmd_ptr++] = READ_RDP_DATA(dp_current + i); - if (rdp_cmd_ptr >= 0x1000) - { - FRDP("rdp_process_list: rdp_cmd_ptr overflow %x %x --> %x\n", length, dp_current, dp_end); - } - } - - dp_current = dp_end; - - cmd = (rdp_cmd_data[0] >> 24) & 0x3f; - cmd_length = (rdp_cmd_ptr + 1) * 4; - - // check if more data is needed - if (cmd_length < rdp_command_length[cmd]) - return; - rdp.LLE = TRUE; - while (rdp_cmd_cur < rdp_cmd_ptr) - { - cmd = (rdp_cmd_data[rdp_cmd_cur] >> 24) & 0x3f; - - if (((rdp_cmd_ptr-rdp_cmd_cur) * 4) < rdp_command_length[cmd]) - return; - - // execute the command - rdp.cmd0 = rdp_cmd_data[rdp_cmd_cur+0]; - rdp.cmd1 = rdp_cmd_data[rdp_cmd_cur+1]; - rdp.cmd2 = rdp_cmd_data[rdp_cmd_cur+2]; - rdp.cmd3 = rdp_cmd_data[rdp_cmd_cur+3]; - rdp_command_table[cmd](); - - rdp_cmd_cur += rdp_command_length[cmd] / 4; - }; - rdp.LLE = FALSE; - - dp_start = dp_end; - - dp_status &= ~0x0002; - - //} -} - -#ifdef __cplusplus -} -#endif - +} + +void microcheck () +{ + wxUint32 i; + uc_crc = 0; + + // Check first 3k of ucode, because the last 1k sometimes contains trash + for (i=0; i<3072>>2; i++) + { + uc_crc += ((wxUint32*)microcode)[i]; + } + + FRDP_E ("crc: %08lx\n", uc_crc); + +#ifdef LOG_UCODE + std::ofstream ucf; + ucf.open ("ucode.txt", std::ios::out | std::ios::binary); + char d; + for (i=0; i<0x400000; i++) + { + d = ((char*)gfx.RDRAM)[i^3]; + ucf.write (&d, 1); + } + ucf.close (); +#endif + + FRDP("ucode = %08lx\n", uc_crc); + + int uc = LookupUcode(uc_crc); + + if (uc == -2 && ucode_error_report) + { + settings.ucode = Config_ReadInt("ucode", "Force microcode", 0, TRUE, FALSE); + + ReleaseGfx (); + ERRLOG("Error: uCode crc not found in INI, using currently selected uCode\n\n%08lx", (unsigned long)uc_crc); + + ucode_error_report = FALSE; // don't report any more ucode errors from this game + } + else if (uc == -1 && ucode_error_report) + { + settings.ucode = 2; //ini->Read(_T("/SETTINGS/ucode"), 0); + + ReleaseGfx (); + ERRLOG("Error: Unsupported uCode!\n\ncrc: %08lx", (unsigned long)uc_crc); + + ucode_error_report = FALSE; // don't report any more ucode errors from this game + } + else + { + old_ucode = settings.ucode; + settings.ucode = uc; + FRDP("microcheck: old ucode: %d, new ucode: %d\n", old_ucode, uc); + if (uc_crc == 0x8d5735b2 || uc_crc == 0xb1821ed3 || uc_crc == 0x1118b3e0) //F3DLP.Rej ucode. perspective texture correction is not implemented + { + rdp.Persp_en = 1; + rdp.persp_supported = FALSE; + } + else if (settings.texture_correction) + rdp.persp_supported = TRUE; + } +} + +#ifdef __WINDOWS__ +static void GetClientSize(int * width, int * height) +{ +#ifdef __WINDOWS__ + RECT win_rect; + GetClientRect (gfx.hWnd, &win_rect); + *width = win_rect.right; + *height = win_rect.bottom; +#else + GFXWindow->GetClientSize(width, height); +#endif +} +#endif + +void drawNoFullscreenMessage() +{ +//need to find, how to do it on non-windows OS +//the code below will compile on any OS +//but it works only on windows, because +//I don't know, how to initialize GFXWindow on other OS +#ifdef __WINDOWS__ + LOG ("drawNoFullscreenMessage ()\n"); + if (rdp.window_changed) + { + rdp.window_changed = FALSE; + int width, height; + GetClientSize(&width, &height); + + wxClientDC dc(GFXWindow); + dc.SetBrush(*wxMEDIUM_GREY_BRUSH); + dc.SetTextForeground(*wxWHITE); + dc.SetBackgroundMode(wxTRANSPARENT); + dc.DrawRectangle(0, 0, width, height); + + wxCoord w, h; + wxString text = wxT("Glide64mk2"); + dc.GetTextExtent(text, &w, &h); + wxCoord x = (width - w)/2; + wxCoord y = height/2 - h*4; + dc.DrawText(text, x, y); + + text = wxT("Gfx cannot be drawn in windowed mode"); + dc.GetTextExtent(text, &w, &h); + x = (width - w)/2; + y = height/2 - h; + dc.DrawText(text, x, y); + + text = wxT("Press Alt+Enter to switch to fullscreen"); + dc.GetTextExtent(text, &w, &h); + x = (width - w)/2; + y = (height - h)/2 + h*2; + dc.DrawText(text, x, y); + } +#endif +} + +static wxUint32 d_ul_x, d_ul_y, d_lr_x, d_lr_y; + +static void DrawPartFrameBufferToScreen() +{ + FB_TO_SCREEN_INFO fb_info; + fb_info.addr = rdp.cimg; + fb_info.size = rdp.ci_size; + fb_info.width = rdp.ci_width; + fb_info.height = rdp.ci_height; + fb_info.ul_x = d_ul_x; + fb_info.lr_x = d_lr_x; + fb_info.ul_y = d_ul_y; + fb_info.lr_y = d_lr_y; + fb_info.opaque = 0; + DrawFrameBufferToScreen(fb_info); + memset(gfx.RDRAM+rdp.cimg, 0, (rdp.ci_width*rdp.ci_height)<>1); +} + +#define RGBA16TO32(color) \ + ((color&1)?0xFF:0) | \ + ((wxUint32)((float)((color&0xF800) >> 11) / 31.0f * 255.0f) << 24) | \ + ((wxUint32)((float)((color&0x07C0) >> 6) / 31.0f * 255.0f) << 16) | \ + ((wxUint32)((float)((color&0x003E) >> 1) / 31.0f * 255.0f) << 8) + +static void CopyFrameBuffer (GrBuffer_t buffer = GR_BUFFER_BACKBUFFER) +{ + if (!fullscreen) + return; + FRDP ("CopyFrameBuffer: %08lx... ", rdp.cimg); + + // don't bother to write the stuff in asm... the slow part is the read from video card, + // not the copy. + + wxUint32 width = rdp.ci_width;//*gfx.VI_WIDTH_REG; + wxUint32 height; + if (fb_emulation_enabled && !(settings.hacks&hack_PPL)) + { + int ind = (rdp.ci_count > 0)?rdp.ci_count-1:0; + height = rdp.frame_buffers[ind].height; + } + else + { + height = rdp.ci_lower_bound; + if (settings.hacks&hack_PPL) + height -= rdp.ci_upper_bound; + } + FRDP ("width: %d, height: %d... ", width, height); + + if (rdp.scale_x < 1.1f) + { + wxUint16 * ptr_src = new wxUint16[width*height]; + if (grLfbReadRegion(buffer, + (wxUint32)rdp.offset_x, + (wxUint32)rdp.offset_y,//rdp.ci_upper_bound, + width, + height, + width<<1, + ptr_src)) + { + wxUint16 *ptr_dst = (wxUint16*)(gfx.RDRAM+rdp.cimg); + wxUint32 *ptr_dst32 = (wxUint32*)(gfx.RDRAM+rdp.cimg); + wxUint16 c; + + for (wxUint32 y=0; y 0) + c = (c&0xFFC0) | ((c&0x001F) << 1) | 1; + } + else + { + c = (c&0xFFC0) | ((c&0x001F) << 1) | 1; + } + if (rdp.ci_size == 2) + ptr_dst[(x + y * width)^1] = c; + else + ptr_dst32[x + y * width] = RGBA16TO32(c); + } + } + LRDP("ReadRegion. Framebuffer copy complete.\n"); + } + else + { + LRDP("Framebuffer copy failed.\n"); + } + delete[] ptr_src; + } + else + { + if (rdp.motionblur && fb_hwfbe_enabled) + { + return; + } + else + { + float scale_x = (settings.scr_res_x - rdp.offset_x*2.0f) / max(width, rdp.vi_width); + float scale_y = (settings.scr_res_y - rdp.offset_y*2.0f) / max(height, rdp.vi_height); + + FRDP("width: %d, height: %d, ul_y: %d, lr_y: %d, scale_x: %f, scale_y: %f, ci_width: %d, ci_height: %d\n",width, height, rdp.ci_upper_bound, rdp.ci_lower_bound, scale_x, scale_y, rdp.ci_width, rdp.ci_height); + GrLfbInfo_t info; + info.size = sizeof(GrLfbInfo_t); + + if (grLfbLock (GR_LFB_READ_ONLY, + buffer, + GR_LFBWRITEMODE_565, + GR_ORIGIN_UPPER_LEFT, + FXFALSE, + &info)) + { + wxUint16 *ptr_src = (wxUint16*)info.lfbPtr; + wxUint16 *ptr_dst = (wxUint16*)(gfx.RDRAM+rdp.cimg); + wxUint32 *ptr_dst32 = (wxUint32*)(gfx.RDRAM+rdp.cimg); + wxUint16 c; + wxUint32 stride = info.strideInBytes>>1; + + int read_alpha = settings.frame_buffer & fb_read_alpha; + if ((settings.hacks&hack_PMario) && rdp.frame_buffers[rdp.ci_count-1].status != ci_aux) + read_alpha = FALSE; + int x_start = 0, y_start = 0, x_end = width, y_end = height; + if (settings.hacks&hack_BAR) + { + x_start = 80, y_start = 24, x_end = 240, y_end = 86; + } + for (int y=y_start; y 0) + SwapOK = TRUE; + rdp.updatescreen = 1; + + rdp.tri_n = 0; // 0 triangles so far this frame + rdp.debug_n = 0; + + rdp.model_i = 0; // 0 matrices so far in stack + //stack_size can be less then 32! Important for Silicon Vally. Thanks Orkin! + rdp.model_stack_size = min(32, (*(wxUint32*)(gfx.DMEM+0x0FE4))>>6); + if (rdp.model_stack_size == 0) + rdp.model_stack_size = 32; + rdp.Persp_en = TRUE; + rdp.fb_drawn = rdp.fb_drawn_front = FALSE; + rdp.update = 0x7FFFFFFF; // All but clear cache + rdp.geom_mode = 0; + rdp.acmp = 0; + rdp.maincimg[1] = rdp.maincimg[0]; + rdp.skip_drawing = FALSE; + rdp.s2dex_tex_loaded = FALSE; + rdp.bg_image_height = 0xFFFF; + fbreads_front = fbreads_back = 0; + rdp.fog_multiplier = rdp.fog_offset = 0; + rdp.zsrc = 0; + if (rdp.vi_org_reg != *gfx.VI_ORIGIN_REG) + rdp.tlut_mode = 0; //is it correct? + rdp.scissor_set = FALSE; + ucode5_texshiftaddr = ucode5_texshiftcount = 0; + cpu_fb_write = FALSE; + cpu_fb_read_called = FALSE; + cpu_fb_write_called = FALSE; + cpu_fb_ignore = FALSE; + d_ul_x = 0xffff; + d_ul_y = 0xffff; + d_lr_x = 0; + d_lr_y = 0; + depth_buffer_fog = TRUE; + + //analize possible frame buffer usage + if (fb_emulation_enabled) + DetectFrameBufferUsage(); + if (!(settings.hacks&hack_Lego) || rdp.num_of_ci > 1) + rdp.last_bg = 0; + //* End of set states *// + + // Get the start of the display list and the length of it + wxUint32 dlist_start = *(wxUint32*)(gfx.DMEM+0xFF0); + wxUint32 dlist_length = *(wxUint32*)(gfx.DMEM+0xFF4); + FRDP("--- NEW DLIST --- crc: %08lx, ucode: %d, fbuf: %08lx, fbuf_width: %d, dlist start: %08lx, dlist_length: %d, x_scale: %f, y_scale: %f\n", uc_crc, settings.ucode, *gfx.VI_ORIGIN_REG, *gfx.VI_WIDTH_REG, dlist_start, dlist_length, (*gfx.VI_X_SCALE_REG & 0xFFF)/1024.0f, (*gfx.VI_Y_SCALE_REG & 0xFFF)/1024.0f); + FRDP_E("--- NEW DLIST --- crc: %08lx, ucode: %d, fbuf: %08lx\n", uc_crc, settings.ucode, *gfx.VI_ORIGIN_REG); + + if (cpu_fb_write == TRUE) + DrawPartFrameBufferToScreen(); + if ((settings.hacks&hack_Tonic) && dlist_length < 16) + { + rdp_fullsync(); + FRDP_E("DLIST is too short!\n"); + return; + } + + // Start executing at the start of the display list + rdp.pc_i = 0; + rdp.pc[rdp.pc_i] = dlist_start; + rdp.dl_count = -1; + rdp.halt = 0; + wxUint32 a; + + // catches exceptions so that it doesn't freeze +#ifdef CATCH_EXCEPTIONS + try { +#endif + if (settings.ucode == ucode_Turbo3d) + { + Turbo3D(); + } + else + { + // MAIN PROCESSING LOOP + do { + + // Get the address of the next command + a = rdp.pc[rdp.pc_i] & BMASK; + + // Load the next command and its input + rdp.cmd0 = ((wxUint32*)gfx.RDRAM)[a>>2]; // \ Current command, 64 bit + rdp.cmd1 = ((wxUint32*)gfx.RDRAM)[(a>>2)+1]; // / + // cmd2 and cmd3 are filled only when needed, by the function that needs them + + // Output the address before the command +#ifdef LOG_COMMANDS + FRDP ("%08lx (c0:%08lx, c1:%08lx): ", a, rdp.cmd0, rdp.cmd1); +#else + FRDP ("%08lx: ", a); +#endif + + // Go to the next instruction + rdp.pc[rdp.pc_i] = (a+8) & BMASK; + +#ifdef PERFORMANCE + perf_cur = wxDateTime::UNow(); +#endif + // Process this instruction + gfx_instruction[settings.ucode][rdp.cmd0>>24] (); + + // check DL counter + if (rdp.dl_count != -1) + { + rdp.dl_count --; + if (rdp.dl_count == 0) + { + rdp.dl_count = -1; + + LRDP("End of DL\n"); + rdp.pc_i --; + } + } + +#ifdef PERFORMANCE + perf_next = wxDateTime::UNow(); + sprintf (out_buf, "perf %08lx: %016I64d\n", a-8, (perf_next-perf_cur).Format(_T("%l")).mb_str()); + rdp_log << out_buf; +#endif + + } while (!rdp.halt); + } +#ifdef CATCH_EXCEPTIONS + } catch (...) { + + if (fullscreen) + { + ReleaseGfx (); + rdp_reset (); +#ifdef TEXTURE_FILTER + if (settings.ghq_use) + { + ext_ghq_shutdown(); + settings.ghq_use = 0; + } +#endif + } + ERRLOG("The GFX plugin caused an exception and has been disabled."); + exception = TRUE; + return; + } +#endif + + if (fb_emulation_enabled) + { + rdp.scale_x = rdp.scale_x_bak; + rdp.scale_y = rdp.scale_y_bak; + } + if (settings.frame_buffer & fb_ref) + CopyFrameBuffer (); + if (rdp.cur_image) + CloseTextureBuffer(rdp.read_whole_frame && ((settings.hacks&hack_PMario) || rdp.swap_ci_index >= 0)); + + if ((settings.hacks&hack_TGR2) && rdp.vi_org_reg != *gfx.VI_ORIGIN_REG && CI_SET) + { + newSwapBuffers (); + CI_SET = FALSE; + } + LRDP("ProcessDList end\n"); +} + +#ifdef __cplusplus +} +#endif + +// undef - undefined instruction, always ignore +static void undef() +{ + FRDP_E("** undefined ** (%08lx)\n", rdp.cmd0); + FRDP("** undefined ** (%08lx) - IGNORED\n", rdp.cmd0); +#ifdef _ENDUSER_RELEASE_ + *gfx.MI_INTR_REG |= 0x20; + gfx.CheckInterrupts(); + rdp.halt = 1; +#endif +} + +// spnoop - no operation, always ignore +static void spnoop() +{ + LRDP("spnoop\n"); +} + +// noop - no operation, always ignore +static void rdp_noop() +{ + LRDP("noop\n"); +} + +static void ys_memrect () +{ + wxUint32 tile = (wxUint16)((rdp.cmd1 & 0x07000000) >> 24); + + wxUint32 lr_x = (wxUint16)((rdp.cmd0 & 0x00FFF000) >> 14); + wxUint32 lr_y = (wxUint16)((rdp.cmd0 & 0x00000FFF) >> 2); + wxUint32 ul_x = (wxUint16)((rdp.cmd1 & 0x00FFF000) >> 14); + wxUint32 ul_y = (wxUint16)((rdp.cmd1 & 0x00000FFF) >> 2); + + if (lr_y > rdp.scissor_o.lr_y) + lr_y = rdp.scissor_o.lr_y; + wxUint32 off_x = ((rdp.cmd2 & 0xFFFF0000) >> 16) >> 5; + wxUint32 off_y = (rdp.cmd2 & 0x0000FFFF) >> 5; + + FRDP ("memrect (%d, %d, %d, %d), ci_width: %d", ul_x, ul_y, lr_x, lr_y, rdp.ci_width); + if (off_x > 0) + FRDP (" off_x: %d", off_x); + if (off_y > 0) + FRDP (" off_y: %d", off_y); + LRDP("\n"); + + wxUint32 y, width = lr_x - ul_x; + wxUint32 tex_width = rdp.tiles[tile].line << 3; + wxUint8 * texaddr = gfx.RDRAM + rdp.addr[rdp.tiles[tile].t_mem] + tex_width*off_y + off_x; + wxUint8 * fbaddr = gfx.RDRAM + rdp.cimg + ul_x; + + for (y = ul_y; y < lr_y; y++) { + wxUint8 *src = texaddr + (y - ul_y) * tex_width; + wxUint8 *dst = fbaddr + y * rdp.ci_width; + memcpy (dst, src, width); + } +} + +static void pm_palette_mod () +{ + wxUint8 envr = (wxUint8)((float)((rdp.env_color >> 24)&0xFF)/255.0f*31.0f); + wxUint8 envg = (wxUint8)((float)((rdp.env_color >> 16)&0xFF)/255.0f*31.0f); + wxUint8 envb = (wxUint8)((float)((rdp.env_color >> 8)&0xFF)/255.0f*31.0f); + wxUint16 env16 = (wxUint16)((envr<<11)|(envg<<6)|(envb<<1)|1); + wxUint8 prmr = (wxUint8)((float)((rdp.prim_color >> 24)&0xFF)/255.0f*31.0f); + wxUint8 prmg = (wxUint8)((float)((rdp.prim_color >> 16)&0xFF)/255.0f*31.0f); + wxUint8 prmb = (wxUint8)((float)((rdp.prim_color >> 8)&0xFF)/255.0f*31.0f); + wxUint16 prim16 = (wxUint16)((prmr<<11)|(prmg<<6)|(prmb<<1)|1); + wxUint16 * dst = (wxUint16*)(gfx.RDRAM+rdp.cimg); + for (int i = 0; i < 16; i++) + { + dst[i^1] = (rdp.pal_8[i]&1) ? prim16 : env16; + } + LRDP("Texrect palette modification\n"); +} + +static void pd_zcopy () +{ + wxUint16 ul_x = (wxUint16)((rdp.cmd1 & 0x00FFF000) >> 14); + wxUint16 lr_x = (wxUint16)((rdp.cmd0 & 0x00FFF000) >> 14) + 1; + wxUint16 ul_u = (wxUint16)((rdp.cmd2 & 0xFFFF0000) >> 21) + 1; + wxUint16 *ptr_dst = (wxUint16*)(gfx.RDRAM+rdp.cimg); + wxUint16 width = lr_x - ul_x; + wxUint16 * ptr_src = ((wxUint16*)rdp.tmem)+ul_u; + wxUint16 c; + for (wxUint16 x=0; x> 8); + ptr_dst[(ul_x+x)^1] = c; + // FRDP("dst[%d]=%04lx \n", (x + ul_x)^1, c); + } +} + +static void DrawDepthBufferFog() +{ + if (rdp.zi_width < 200) + return; + FB_TO_SCREEN_INFO fb_info; + fb_info.addr = rdp.zimg; + fb_info.size = 2; + fb_info.width = rdp.zi_width; + fb_info.height = rdp.ci_height; + fb_info.ul_x = rdp.scissor_o.ul_x; + fb_info.lr_x = rdp.scissor_o.lr_x; + fb_info.ul_y = rdp.scissor_o.ul_y; + fb_info.lr_y = rdp.scissor_o.lr_y; + fb_info.opaque = 0; + DrawDepthBufferToScreen(fb_info); +} + +static void rdp_texrect() +{ + if (!rdp.LLE) + { + wxUint32 a = rdp.pc[rdp.pc_i]; + wxUint8 cmdHalf1 = gfx.RDRAM[a+3]; + wxUint8 cmdHalf2 = gfx.RDRAM[a+11]; + a >>= 2; + if ((cmdHalf1 == 0xE1 && cmdHalf2 == 0xF1) || (cmdHalf1 == 0xB4 && cmdHalf2 == 0xB3) || (cmdHalf1 == 0xB3 && cmdHalf2 == 0xB2)) + { + //gSPTextureRectangle + rdp.cmd2 = ((wxUint32*)gfx.RDRAM)[a+1]; + rdp.cmd3 = ((wxUint32*)gfx.RDRAM)[a+3]; + rdp.pc[rdp.pc_i] += 16; + } + else + { + //gDPTextureRectangle + if (settings.hacks&hack_ASB) + rdp.cmd2 = 0; + else + rdp.cmd2 = ((wxUint32*)gfx.RDRAM)[a+0]; + rdp.cmd3 = ((wxUint32*)gfx.RDRAM)[a+1]; + rdp.pc[rdp.pc_i] += 8; + } + } + if ((settings.hacks&hack_Yoshi) && settings.ucode == ucode_S2DEX) + { + ys_memrect(); + return; + } + + if (rdp.skip_drawing || (!fb_emulation_enabled && (rdp.cimg == rdp.zimg))) + { + if ((settings.hacks&hack_PMario) && rdp.ci_status == ci_useless) + { + pm_palette_mod (); + } + else + { + LRDP("Texrect skipped\n"); + } + return; + } + + if ((settings.ucode == ucode_CBFD) && rdp.cur_image && rdp.cur_image->format) + { + //FRDP("Wrong Texrect. texaddr: %08lx, cimg: %08lx, cimg_end: %08lx\n", rdp.timg.addr, rdp.maincimg[1].addr, rdp.maincimg[1].addr+rdp.ci_width*rdp.ci_height*rdp.ci_size); + LRDP("Shadow texrect is skipped.\n"); + rdp.tri_n += 2; + return; + } + + if ((settings.ucode == ucode_PerfectDark) && (rdp.frame_buffers[rdp.ci_count-1].status == ci_zcopy)) + { + pd_zcopy (); + LRDP("Depth buffer copied.\n"); + rdp.tri_n += 2; + return; + } + + if ((rdp.othermode_l >> 16) == 0x3c18 && rdp.cycle1 == 0x03ffffff && rdp.cycle2 == 0x01ff1fff) //depth image based fog + { + if (!depth_buffer_fog) + return; + if (settings.fog) + DrawDepthBufferFog(); + depth_buffer_fog = FALSE; + return; + } + + // FRDP ("rdp.cycle1 %08lx, rdp.cycle2 %08lx\n", rdp.cycle1, rdp.cycle2); + + float ul_x, ul_y, lr_x, lr_y; + if (rdp.cycle_mode == 2) + { + ul_x = max(0.0f, (short)((rdp.cmd1 & 0x00FFF000) >> 14)); + ul_y = max(0.0f, (short)((rdp.cmd1 & 0x00000FFF) >> 2)); + lr_x = max(0.0f, (short)((rdp.cmd0 & 0x00FFF000) >> 14)); + lr_y = max(0.0f, (short)((rdp.cmd0 & 0x00000FFF) >> 2)); + } + else + { + ul_x = max(0.0f, ((short)((rdp.cmd1 & 0x00FFF000) >> 12)) / 4.0f); + ul_y = max(0.0f, ((short)(rdp.cmd1 & 0x00000FFF)) / 4.0f); + lr_x = max(0.0f, ((short)((rdp.cmd0 & 0x00FFF000) >> 12)) / 4.0f); + lr_y = max(0.0f, ((short)(rdp.cmd0 & 0x00000FFF)) / 4.0f); + } + + if (ul_x >= lr_x) + { + FRDP("Wrong Texrect: ul_x: %f, lr_x: %f\n", ul_x, lr_x); + return; + } + + if (rdp.cycle_mode > 1) + { + lr_x += 1.0f; + lr_y += 1.0f; + } else if (lr_y - ul_y < 1.0f) + lr_y = ceil(lr_y); + + if (settings.increase_texrect_edge) + { + if (floor(lr_x) != lr_x) + lr_x = ceil(lr_x); + if (floor(lr_y) != lr_y) + lr_y = ceil(lr_y); + } + + //* + if (rdp.tbuff_tex && (settings.frame_buffer & fb_optimize_texrect)) + { + LRDP("Attempt to optimize texrect\n"); + if (!rdp.tbuff_tex->drawn) + { + DRAWIMAGE d; + d.imageX = 0; + d.imageW = (wxUint16)rdp.tbuff_tex->width; + d.frameX = (wxUint16)ul_x; + d.frameW = (wxUint16)(rdp.tbuff_tex->width); + + d.imageY = 0; + d.imageH = (wxUint16)rdp.tbuff_tex->height; + d.frameY = (wxUint16)ul_y; + d.frameH = (wxUint16)(rdp.tbuff_tex->height); + FRDP("texrect. ul_x: %d, ul_y: %d, lr_x: %d, lr_y: %d, width: %d, height: %d\n", ul_x, ul_y, lr_x, lr_y, rdp.tbuff_tex->width, rdp.tbuff_tex->height); + d.scaleX = 1.0f; + d.scaleY = 1.0f; + DrawHiresImage(d, rdp.tbuff_tex->width == rdp.ci_width); + rdp.tbuff_tex->drawn = TRUE; + } + return; + } + //*/ + // framebuffer workaround for Zelda: MM LOT + if ((rdp.othermode_l & 0xFFFF0000) == 0x0f5a0000) + return; + + /*Gonetz*/ + //hack for Zelda MM. it removes black texrects which cover all geometry in "Link meets Zelda" cut scene + if ((settings.hacks&hack_Zelda) && rdp.timg.addr >= rdp.cimg && rdp.timg.addr < rdp.ci_end) + { + FRDP("Wrong Texrect. texaddr: %08lx, cimg: %08lx, cimg_end: %08lx\n", rdp.cur_cache[0]->addr, rdp.cimg, rdp.cimg+rdp.ci_width*rdp.ci_height*2); + rdp.tri_n += 2; + return; + } + //* + //hack for Banjo2. it removes black texrects under Banjo + if (!fb_hwfbe_enabled && ((rdp.cycle1 << 16) | (rdp.cycle2 & 0xFFFF)) == 0xFFFFFFFF && (rdp.othermode_l & 0xFFFF0000) == 0x00500000) + { + rdp.tri_n += 2; + return; + } + //*/ + //* + //remove motion blur in night vision + if ((settings.ucode == ucode_PerfectDark) && (rdp.maincimg[1].addr != rdp.maincimg[0].addr) && (rdp.timg.addr >= rdp.maincimg[1].addr) && (rdp.timg.addr < (rdp.maincimg[1].addr+rdp.ci_width*rdp.ci_height*rdp.ci_size))) + { + if (fb_emulation_enabled) + if (rdp.frame_buffers[rdp.ci_count-1].status == ci_copy_self) + { + //FRDP("Wrong Texrect. texaddr: %08lx, cimg: %08lx, cimg_end: %08lx\n", rdp.timg.addr, rdp.maincimg[1], rdp.maincimg[1]+rdp.ci_width*rdp.ci_height*rdp.ci_size); + LRDP("Wrong Texrect.\n"); + rdp.tri_n += 2; + return; + } + } + //*/ + + int i; + + wxUint32 tile = (wxUint16)((rdp.cmd1 & 0x07000000) >> 24); + + rdp.texrecting = 1; + + wxUint32 prev_tile = rdp.cur_tile; + rdp.cur_tile = tile; + + const float Z = set_sprite_combine_mode (); + + rdp.texrecting = 0; + + if (!rdp.cur_cache[0]) + { + rdp.cur_tile = prev_tile; + rdp.tri_n += 2; + return; + } + // **** + // ** Texrect offset by Gugaman ** + // + //integer representation of texture coordinate. + //needed to detect and avoid overflow after shifting + wxInt32 off_x_i = (rdp.cmd2 >> 16) & 0xFFFF; + wxInt32 off_y_i = rdp.cmd2 & 0xFFFF; + float dsdx = (float)((short)((rdp.cmd3 & 0xFFFF0000) >> 16)) / 1024.0f; + float dtdy = (float)((short)(rdp.cmd3 & 0x0000FFFF)) / 1024.0f; + if (off_x_i & 0x8000) //check for sign bit + off_x_i |= ~0xffff; //make it negative + //the same as for off_x_i + if (off_y_i & 0x8000) + off_y_i |= ~0xffff; + + if (rdp.cycle_mode == 2) + dsdx /= 4.0f; + + float s_ul_x = ul_x * rdp.scale_x + rdp.offset_x; + float s_lr_x = lr_x * rdp.scale_x + rdp.offset_x; + float s_ul_y = ul_y * rdp.scale_y + rdp.offset_y; + float s_lr_y = lr_y * rdp.scale_y + rdp.offset_y; + + FRDP("texrect (%.2f, %.2f, %.2f, %.2f), tile: %d, #%d, #%d\n", ul_x, ul_y, lr_x, lr_y, tile, rdp.tri_n, rdp.tri_n+1); + FRDP ("(%f, %f) -> (%f, %f), s: (%d, %d) -> (%d, %d)\n", s_ul_x, s_ul_y, s_lr_x, s_lr_y, rdp.scissor.ul_x, rdp.scissor.ul_y, rdp.scissor.lr_x, rdp.scissor.lr_y); + FRDP("\toff_x: %f, off_y: %f, dsdx: %f, dtdy: %f\n", off_x_i/32.0f, off_y_i/32.0f, dsdx, dtdy); + + float off_size_x; + float off_size_y; + + if ( ((rdp.cmd0>>24)&0xFF) == 0xE5 ) //texrectflip + { +#ifdef TEXTURE_FILTER + if (rdp.cur_cache[0]->is_hires_tex) + { + off_size_x = (float)((lr_y - ul_y) * dsdx); + off_size_y = (float)((lr_x - ul_x) * dtdy); + } + else +#endif + { + off_size_x = (lr_y - ul_y - 1) * dsdx; + off_size_y = (lr_x - ul_x - 1) * dtdy; + } + } + else + { +#ifdef TEXTURE_FILTER + if (rdp.cur_cache[0]->is_hires_tex) + { + off_size_x = (float)((lr_x - ul_x) * dsdx); + off_size_y = (float)((lr_y - ul_y) * dtdy); + } + else +#endif + { + off_size_x = (lr_x - ul_x - 1) * dsdx; + off_size_y = (lr_y - ul_y - 1) * dtdy; + } + } + + struct { + float ul_u, ul_v, lr_u, lr_v; + } texUV[2]; //struct for texture coordinates + //angrylion's macro, helps to cut overflowed values. + #define SIGN16(x) (((x) & 0x8000) ? ((x) | ~0xffff) : ((x) & 0xffff)) + + //calculate texture coordinates + for (int i = 0; i < 2; i++) + { + if (rdp.cur_cache[i] && (rdp.tex & (i+1))) + { + float sx = 1, sy = 1; + int x_i = off_x_i, y_i = off_y_i; + TILE & tile = rdp.tiles[rdp.cur_tile + i]; + //shifting + if (tile.shift_s) + { + if (tile.shift_s > 10) + { + wxUint8 iShift = (16 - tile.shift_s); + x_i <<= iShift; + sx = (float)(1 << iShift); + } + else + { + wxUint8 iShift = tile.shift_s; + x_i >>= iShift; + sx = 1.0f/(float)(1 << iShift); + } + } + if (tile.shift_t) + { + if (tile.shift_t > 10) + { + wxUint8 iShift = (16 - tile.shift_t); + y_i <<= iShift; + sy = (float)(1 << iShift); + } + else + { + wxUint8 iShift = tile.shift_t; + y_i >>= iShift; + sy = 1.0f/(float)(1 << iShift); + } + } + + if (rdp.aTBuffTex[i]) //hwfbe texture + { + float t0_off_x; + float t0_off_y; + if (off_x_i + off_y_i == 0) + { + t0_off_x = tile.ul_s; + t0_off_y = tile.ul_t; + } + else + { + t0_off_x = off_x_i/32.0f; + t0_off_y = off_y_i/32.0f; + } + t0_off_x += rdp.aTBuffTex[i]->u_shift;// + tile.ul_s; //commented for Paper Mario motion blur + t0_off_y += rdp.aTBuffTex[i]->v_shift;// + tile.ul_t; + texUV[i].ul_u = t0_off_x * sx; + texUV[i].ul_v = t0_off_y * sy; + + texUV[i].lr_u = texUV[i].ul_u + off_size_x * sx; + texUV[i].lr_v = texUV[i].ul_v + off_size_y * sy; + + texUV[i].ul_u *= rdp.aTBuffTex[i]->u_scale; + texUV[i].ul_v *= rdp.aTBuffTex[i]->v_scale; + texUV[i].lr_u *= rdp.aTBuffTex[i]->u_scale; + texUV[i].lr_v *= rdp.aTBuffTex[i]->v_scale; + FRDP("tbuff_tex[%d] ul_u: %f, ul_v: %f, lr_u: %f, lr_v: %f\n", + i, texUV[i].ul_u, texUV[i].ul_v, texUV[i].lr_u, texUV[i].lr_v); + } + else //common case + { + //kill 10.5 format overflow by SIGN16 macro + texUV[i].ul_u = SIGN16(x_i) / 32.0f; + texUV[i].ul_v = SIGN16(y_i) / 32.0f; + + texUV[i].ul_u -= tile.f_ul_s; + texUV[i].ul_v -= tile.f_ul_t; + + texUV[i].lr_u = texUV[i].ul_u + off_size_x * sx; + texUV[i].lr_v = texUV[i].ul_v + off_size_y * sy; + + texUV[i].ul_u = rdp.cur_cache[i]->c_off + rdp.cur_cache[i]->c_scl_x * texUV[i].ul_u; + texUV[i].lr_u = rdp.cur_cache[i]->c_off + rdp.cur_cache[i]->c_scl_x * texUV[i].lr_u; + texUV[i].ul_v = rdp.cur_cache[i]->c_off + rdp.cur_cache[i]->c_scl_y * texUV[i].ul_v; + texUV[i].lr_v = rdp.cur_cache[i]->c_off + rdp.cur_cache[i]->c_scl_y * texUV[i].lr_v; + } + } + else + { + texUV[i].ul_u = texUV[i].ul_v = texUV[i].lr_u = texUV[i].lr_v = 0; + } + } + rdp.cur_tile = prev_tile; + + // **** + + FRDP (" scissor: (%d, %d) -> (%d, %d)\n", rdp.scissor.ul_x, rdp.scissor.ul_y, rdp.scissor.lr_x, rdp.scissor.lr_y); + + CCLIP2 (s_ul_x, s_lr_x, texUV[0].ul_u, texUV[0].lr_u, texUV[1].ul_u, texUV[1].lr_u, (float)rdp.scissor.ul_x, (float)rdp.scissor.lr_x); + CCLIP2 (s_ul_y, s_lr_y, texUV[0].ul_v, texUV[0].lr_v, texUV[1].ul_v, texUV[1].lr_v, (float)rdp.scissor.ul_y, (float)rdp.scissor.lr_y); + + FRDP (" draw at: (%f, %f) -> (%f, %f)\n", s_ul_x, s_ul_y, s_lr_x, s_lr_y); + + VERTEX vstd[4] = { + { s_ul_x, s_ul_y, Z, 1.0f, texUV[0].ul_u, texUV[0].ul_v, texUV[1].ul_u, texUV[1].ul_v, {0, 0, 0, 0}, 255 }, + { s_lr_x, s_ul_y, Z, 1.0f, texUV[0].lr_u, texUV[0].ul_v, texUV[1].lr_u, texUV[1].ul_v, {0, 0, 0, 0}, 255 }, + { s_ul_x, s_lr_y, Z, 1.0f, texUV[0].ul_u, texUV[0].lr_v, texUV[1].ul_u, texUV[1].lr_v, {0, 0, 0, 0}, 255 }, + { s_lr_x, s_lr_y, Z, 1.0f, texUV[0].lr_u, texUV[0].lr_v, texUV[1].lr_u, texUV[1].lr_v, {0, 0, 0, 0}, 255 } }; + + if ( ((rdp.cmd0>>24)&0xFF) == 0xE5 ) //texrectflip + { + vstd[1].u0 = texUV[0].ul_u; + vstd[1].v0 = texUV[0].lr_v; + vstd[1].u1 = texUV[1].ul_u; + vstd[1].v1 = texUV[1].lr_v; + + vstd[2].u0 = texUV[0].lr_u; + vstd[2].v0 = texUV[0].ul_v; + vstd[2].u1 = texUV[1].lr_u; + vstd[2].v1 = texUV[1].ul_v; + } + + VERTEX *vptr = vstd; + int n_vertices = 4; + + VERTEX *vnew = 0; + // for (int j =0; j < 4; j++) + // FRDP("v[%d] u0: %f, v0: %f, u1: %f, v1: %f\n", j, vstd[j].u0, vstd[j].v0, vstd[j].u1, vstd[j].v1); + + + if (!rdp.aTBuffTex[0] && rdp.cur_cache[0]->splits != 1) + { + // ** LARGE TEXTURE HANDLING ** + // *VERY* simple algebra for texrects + float min_u, min_x, max_u, max_x; + if (vstd[0].u0 < vstd[1].u0) + { + min_u = vstd[0].u0; + min_x = vstd[0].x; + max_u = vstd[1].u0; + max_x = vstd[1].x; + } + else + { + min_u = vstd[1].u0; + min_x = vstd[1].x; + max_u = vstd[0].u0; + max_x = vstd[0].x; + } + + int start_u_256, end_u_256; + start_u_256 = (int)min_u >> 8; + end_u_256 = (int)max_u >> 8; + //FRDP(" min_u: %f, max_u: %f start: %d, end: %d\n", min_u, max_u, start_u_256, end_u_256); + + int splitheight = rdp.cur_cache[0]->splitheight; + + int num_verts_line = 2 + ((end_u_256-start_u_256)<<1); + n_vertices = num_verts_line << 1; + vnew = new VERTEX [n_vertices]; + vptr = vnew; + + vnew[0] = vstd[0]; + vnew[0].u0 -= 256.0f * start_u_256; + vnew[0].v0 += splitheight * start_u_256; + vnew[0].u1 -= 256.0f * start_u_256; + vnew[0].v1 += splitheight * start_u_256; + vnew[1] = vstd[2]; + vnew[1].u0 -= 256.0f * start_u_256; + vnew[1].v0 += splitheight * start_u_256; + vnew[1].u1 -= 256.0f * start_u_256; + vnew[1].v1 += splitheight * start_u_256; + vnew[n_vertices-2] = vstd[1]; + vnew[n_vertices-2].u0 -= 256.0f * end_u_256; + vnew[n_vertices-2].v0 += splitheight * end_u_256; + vnew[n_vertices-2].u1 -= 256.0f * end_u_256; + vnew[n_vertices-2].v1 += splitheight * end_u_256; + vnew[n_vertices-1] = vstd[3]; + vnew[n_vertices-1].u0 -= 256.0f * end_u_256; + vnew[n_vertices-1].v0 += splitheight * end_u_256; + vnew[n_vertices-1].u1 -= 256.0f * end_u_256; + vnew[n_vertices-1].v1 += splitheight * end_u_256; + + // find the equation of the line of u,x + float m = (max_x - min_x) / (max_u - min_u); // m = delta x / delta u + float b = min_x - m * min_u; // b = y - m * x + + for (i=start_u_256; i 12) + { + float texbound = (float)(splitheight << 1); + for (int k = 0; k < n_vertices; k ++) + { + if (vnew[k].v0 > texbound) + vnew[k].v0 = (float)fmod(vnew[k].v0, texbound); + } + } + //*/ + } + + AllowShadeMods (vptr, n_vertices); + for (i=0; i= RDP::fog_blend) + { + float fog; + if (rdp.fog_mode == RDP::fog_blend) + fog = 1.0f/max(1, rdp.fog_color&0xFF); + else + fog = 1.0f/max(1, (~rdp.fog_color)&0xFF); + for (i = 0; i < n_vertices; i++) + { + vptr[i].f = fog; + } + grFogMode (GR_FOG_WITH_TABLE_ON_FOGCOORD_EXT); + } + + ConvertCoordsConvert (vptr, n_vertices); + + if (settings.wireframe) + { + SetWireframeCol (); + grDrawLine (&vstd[0], &vstd[2]); + grDrawLine (&vstd[2], &vstd[1]); + grDrawLine (&vstd[1], &vstd[0]); + grDrawLine (&vstd[2], &vstd[3]); + grDrawLine (&vstd[3], &vstd[1]); + } + else + { + grDrawVertexArrayContiguous (GR_TRIANGLE_STRIP, n_vertices, vptr, sizeof(VERTEX)); + } + + if (_debugger.capture) + { + VERTEX vl[3]; + vl[0] = vstd[0]; + vl[1] = vstd[2]; + vl[2] = vstd[1]; + add_tri (vl, 3, TRI_TEXRECT); + rdp.tri_n ++; + vl[0] = vstd[2]; + vl[1] = vstd[3]; + vl[2] = vstd[1]; + add_tri (vl, 3, TRI_TEXRECT); + rdp.tri_n ++; + } + else + rdp.tri_n += 2; + } + else + { + rdp.tri_n += 2; + } + + delete[] vnew; +} + +static void rdp_loadsync() +{ + LRDP("loadsync - ignored\n"); +} + +static void rdp_pipesync() +{ + LRDP("pipesync - ignored\n"); +} + +static void rdp_tilesync() +{ + LRDP("tilesync - ignored\n"); +} + +static void rdp_fullsync() +{ + // Set an interrupt to allow the game to continue + *gfx.MI_INTR_REG |= 0x20; + gfx.CheckInterrupts(); + LRDP("fullsync\n"); +} + +static void rdp_setkeygb() +{ + wxUint32 sB = rdp.cmd1&0xFF; + wxUint32 cB = (rdp.cmd1>>8)&0xFF; + wxUint32 sG = (rdp.cmd1>>16)&0xFF; + wxUint32 cG = (rdp.cmd1>>24)&0xFF; + rdp.SCALE = (rdp.SCALE&0xFF0000FF) | (sG<<16) | (sB<<8); + rdp.CENTER = (rdp.CENTER&0xFF0000FF) | (cG<<16) | (cB<<8); + FRDP("setkeygb. cG=%02lx, sG=%02lx, cB=%02lx, sB=%02lx\n", cG, sG, cB, sB); +} + +static void rdp_setkeyr() +{ + wxUint32 sR = rdp.cmd1&0xFF; + wxUint32 cR = (rdp.cmd1>>8)&0xFF; + rdp.SCALE = (rdp.SCALE&0x00FFFFFF) | (sR<<24); + rdp.CENTER = (rdp.CENTER&0x00FFFFFF) | (cR<<24); + FRDP("setkeyr. cR=%02lx, sR=%02lx\n", cR, sR); +} + +static void rdp_setconvert() +{ + /* + rdp.YUV_C0 = 1.1647f ; + rdp.YUV_C1 = 0.79931f ; + rdp.YUV_C2 = -0.1964f ; + rdp.YUV_C3 = -0.40651f; + rdp.YUV_C4 = 1.014f ; + */ + rdp.K4 = (wxUint8)(rdp.cmd1>>9)&0x1FF; + rdp.K5 = (wxUint8)(rdp.cmd1&0x1FF); + // RDP_E("setconvert - IGNORED\n"); + FRDP("setconvert. K4=%02lx K5=%02lx\n", rdp.K4, rdp.K5); +} + +// +// setscissor - sets the screen clipping rectangle +// + +static void rdp_setscissor() +{ + // clipper resolution is 320x240, scale based on computer resolution + rdp.scissor_o.ul_x = /*min(*/(wxUint32)(((rdp.cmd0 & 0x00FFF000) >> 14))/*, 320)*/; + rdp.scissor_o.ul_y = /*min(*/(wxUint32)(((rdp.cmd0 & 0x00000FFF) >> 2))/*, 240)*/; + rdp.scissor_o.lr_x = /*min(*/(wxUint32)(((rdp.cmd1 & 0x00FFF000) >> 14))/*, 320)*/; + rdp.scissor_o.lr_y = /*min(*/(wxUint32)(((rdp.cmd1 & 0x00000FFF) >> 2))/*, 240)*/; + + rdp.ci_upper_bound = rdp.scissor_o.ul_y; + rdp.ci_lower_bound = rdp.scissor_o.lr_y; + rdp.scissor_set = TRUE; + + FRDP("setscissor: (%d,%d) -> (%d,%d)\n", rdp.scissor_o.ul_x, rdp.scissor_o.ul_y, + rdp.scissor_o.lr_x, rdp.scissor_o.lr_y); + + rdp.update |= UPDATE_SCISSOR; + + if (rdp.view_scale[0] == 0) //viewport is not set? + { + rdp.view_scale[0] = (rdp.scissor_o.lr_x>>1)*rdp.scale_x; + rdp.view_scale[1] = (rdp.scissor_o.lr_y>>1)*-rdp.scale_y; + rdp.view_trans[0] = rdp.view_scale[0]; + rdp.view_trans[1] = -rdp.view_scale[1]; + rdp.update |= UPDATE_VIEWPORT; + } +} + +static void rdp_setprimdepth() +{ + rdp.prim_depth = (wxUint16)((rdp.cmd1 >> 16) & 0x7FFF); + rdp.prim_dz = (wxUint16)(rdp.cmd1 & 0x7FFF); + + FRDP("setprimdepth: %d\n", rdp.prim_depth); +} + +static void rdp_setothermode() +{ +#define F3DEX2_SETOTHERMODE(cmd,sft,len,data) { \ + rdp.cmd0 = (cmd<<24) | ((32-(sft)-(len))<<8) | (((len)-1)); \ + rdp.cmd1 = data; \ + gfx_instruction[settings.ucode][cmd] (); \ +} +#define SETOTHERMODE(cmd,sft,len,data) { \ + rdp.cmd0 = (cmd<<24) | ((sft)<<8) | (len); \ + rdp.cmd1 = data; \ + gfx_instruction[settings.ucode][cmd] (); \ +} + + LRDP("rdp_setothermode\n"); + + if ((settings.ucode == ucode_F3DEX2) || (settings.ucode == ucode_CBFD)) + { + int cmd0 = rdp.cmd0; + F3DEX2_SETOTHERMODE(0xE2, 0, 32, rdp.cmd1); // SETOTHERMODE_L + F3DEX2_SETOTHERMODE(0xE3, 0, 32, cmd0 & 0x00FFFFFF); // SETOTHERMODE_H + } + else + { + int cmd0 = rdp.cmd0; + SETOTHERMODE(0xB9, 0, 32, rdp.cmd1); // SETOTHERMODE_L + SETOTHERMODE(0xBA, 0, 32, cmd0 & 0x00FFFFFF); // SETOTHERMODE_H + } +} + +void load_palette (wxUint32 addr, wxUint16 start, wxUint16 count) +{ + LRDP("Loading palette... "); + wxUint16 *dpal = rdp.pal_8 + start; + wxUint16 end = start+count; +#ifdef TEXTURE_FILTER + wxUint16 *spal = (wxUint16*)(gfx.RDRAM + (addr & BMASK)); +#endif + + for (wxUint16 i=start; i>= 4; + end = start + (count >> 4); + if (end == start) // it can be if count < 16 + end = start + 1; + for (wxUint16 p = start; p < end; p++) + { + rdp.pal_8_crc[p] = CRC32( 0xFFFFFFFF, &rdp.pal_8[(p << 4)], 32 ); + } + rdp.pal_256_crc = CRC32( 0xFFFFFFFF, rdp.pal_8_crc, 64 ); + LRDP("Done.\n"); +} + +static void rdp_loadtlut() +{ + wxUint32 tile = (rdp.cmd1 >> 24) & 0x07; + wxUint16 start = rdp.tiles[tile].t_mem - 256; // starting location in the palettes + // wxUint16 start = ((wxUint16)(rdp.cmd1 >> 2) & 0x3FF) + 1; + wxUint16 count = ((wxUint16)(rdp.cmd1 >> 14) & 0x3FF) + 1; // number to copy + + if (rdp.timg.addr + (count<<1) > BMASK) + count = (wxUint16)((BMASK - rdp.timg.addr) >> 1); + + if (start+count > 256) count = 256-start; + + FRDP("loadtlut: tile: %d, start: %d, count: %d, from: %08lx\n", tile, start, count, + rdp.timg.addr); + + load_palette (rdp.timg.addr, start, count); + + rdp.timg.addr += count << 1; + + if (rdp.tbuff_tex) //paranoid check. + { + //the buffer is definitely wrong, as there must be no CI frame buffers + //find and remove it + for (int i = 0; i < voodoo.num_tmu; i++) + { + for (int j = 0; j < rdp.texbufs[i].count; j++) + { + if (&(rdp.texbufs[i].images[j]) == rdp.tbuff_tex) + { + rdp.texbufs[i].count--; + if (j < rdp.texbufs[i].count) + memcpy(&(rdp.texbufs[i].images[j]), &(rdp.texbufs[i].images[j+1]), sizeof(TBUFF_COLOR_IMAGE)*(rdp.texbufs[i].count-j)); + return; + } + } + } + } +} + +int tile_set = 0; +static void rdp_settilesize() +{ + wxUint32 tile = (rdp.cmd1 >> 24) & 0x07; + rdp.last_tile_size = tile; + + rdp.tiles[tile].f_ul_s = (float)((rdp.cmd0 >> 12) & 0xFFF) / 4.0f; + rdp.tiles[tile].f_ul_t = (float)(rdp.cmd0 & 0xFFF) / 4.0f; + + int ul_s = (((wxUint16)(rdp.cmd0 >> 14)) & 0x03ff); + int ul_t = (((wxUint16)(rdp.cmd0 >> 2 )) & 0x03ff); + int lr_s = (((wxUint16)(rdp.cmd1 >> 14)) & 0x03ff); + int lr_t = (((wxUint16)(rdp.cmd1 >> 2 )) & 0x03ff); + + if (lr_s == 0 && ul_s == 0) //pokemon puzzle league set such tile size + wrong_tile = tile; + else if (wrong_tile == (int)tile) + wrong_tile = -1; + + if (settings.use_sts1_only) + { + // ** USE FIRST SETTILESIZE ONLY ** + // This option helps certain textures while using the 'Alternate texture size method', + // but may break others. (should help more than break) + + if (tile_set) + { + // coords in 10.2 format + rdp.tiles[tile].ul_s = ul_s; + rdp.tiles[tile].ul_t = ul_t; + rdp.tiles[tile].lr_s = lr_s; + rdp.tiles[tile].lr_t = lr_t; + tile_set = 0; + } + } + else + { + // coords in 10.2 format + rdp.tiles[tile].ul_s = ul_s; + rdp.tiles[tile].ul_t = ul_t; + rdp.tiles[tile].lr_s = lr_s; + rdp.tiles[tile].lr_t = lr_t; + } + + // handle wrapping + if (rdp.tiles[tile].lr_s < rdp.tiles[tile].ul_s) rdp.tiles[tile].lr_s += 0x400; + if (rdp.tiles[tile].lr_t < rdp.tiles[tile].ul_t) rdp.tiles[tile].lr_t += 0x400; + + rdp.update |= UPDATE_TEXTURE; + + rdp.first = 1; + + FRDP ("settilesize: tile: %d, ul_s: %d, ul_t: %d, lr_s: %d, lr_t: %d, f_ul_s: %f, f_ul_t: %f\n", + tile, ul_s, ul_t, lr_s, lr_t, rdp.tiles[tile].f_ul_s, rdp.tiles[tile].f_ul_t); +} + +void setTBufTex(wxUint16 t_mem, wxUint32 cnt) +{ + FRDP("setTBufTex t_mem=%d, cnt=%d\n", t_mem, cnt); + TBUFF_COLOR_IMAGE * pTbufTex = rdp.tbuff_tex; + for (int i = 0; i < 2; i++) + { + LRDP("Before: "); + if (rdp.aTBuffTex[i]) { + FRDP("rdp.aTBuffTex[%d]: tmu=%d t_mem=%d tile=%d\n", i, rdp.aTBuffTex[i]->tmu, rdp.aTBuffTex[i]->t_mem, rdp.aTBuffTex[i]->tile); + } else { + FRDP("rdp.aTBuffTex[%d]=0\n", i); + } + if ((rdp.aTBuffTex[i] == 0 && rdp.aTBuffTex[i^1] != pTbufTex) || (rdp.aTBuffTex[i] && rdp.aTBuffTex[i]->t_mem >= t_mem && rdp.aTBuffTex[i]->t_mem < t_mem + cnt)) + { + if (pTbufTex) + { + rdp.aTBuffTex[i] = pTbufTex; + rdp.aTBuffTex[i]->t_mem = t_mem; + pTbufTex = 0; + FRDP("rdp.aTBuffTex[%d] tmu=%d t_mem=%d\n", i, rdp.aTBuffTex[i]->tmu, rdp.aTBuffTex[i]->t_mem); + } + else + { + rdp.aTBuffTex[i] = 0; + FRDP("rdp.aTBuffTex[%d]=0\n", i); + } + } + } +} + +static inline void loadBlock(uint32_t *src, uint32_t *dst, uint32_t off, int dxt, int cnt) +{ + uint32_t *v5; + int v6; + uint32_t *v7; + uint32_t v8; + int v9; + uint32_t v10; + uint32_t *v11; + uint32_t v12; + uint32_t v13; + uint32_t v14; + int v15; + int v16; + uint32_t *v17; + int v18; + uint32_t v19; + uint32_t v20; + int i; + + v5 = dst; + v6 = cnt; + if ( cnt ) + { + v7 = (uint32_t *)((char *)src + (off & 0xFFFFFFFC)); + v8 = off & 3; + if ( !(off & 3) ) + goto LABEL_23; + v9 = 4 - v8; + v10 = *v7; + v11 = v7 + 1; + do + { + v10 = __ROL__(v10, 8); + --v8; + } + while ( v8 ); + do + { + v10 = __ROL__(v10, 8); + *(uint8_t *)v5 = v10; + v5 = (uint32_t *)((char *)v5 + 1); + --v9; + } + while ( v9 ); + v12 = *v11; + v7 = v11 + 1; + *v5 = bswap32(v12); + ++v5; + v6 = cnt - 1; + if ( cnt != 1 ) + { +LABEL_23: + do + { + *v5 = bswap32(*v7); + v5[1] = bswap32(v7[1]); + v7 += 2; + v5 += 2; + --v6; + } + while ( v6 ); + } + v13 = off & 3; + if ( off & 3 ) + { + v14 = *(uint32_t *)((char *)src + ((8 * cnt + off) & 0xFFFFFFFC)); + do + { + v14 = __ROL__(v14, 8); + *(uint8_t *)v5 = v14; + v5 = (uint32_t *)((char *)v5 + 1); + --v13; + } + while ( v13 ); + } + } + v15 = cnt; + v16 = 0; + v17 = dst; + v18 = 0; +dxt_test: + while ( 1 ) + { + v17 += 2; + --v15; + if ( !v15 ) + break; + v16 += dxt; + if ( v16 < 0 ) + { + while ( 1 ) + { + ++v18; + --v15; + if ( !v15 ) + goto end_dxt_test; + v16 += dxt; + if ( v16 >= 0 ) + { + for ( i = v15; v18; --v18 ) + { + v19 = *v17; + *v17 = v17[1]; + v17[1] = v19; + v17 += 2; + } + v15 = i; + goto dxt_test; + } + } + } + } +end_dxt_test: + while ( v18 ) + { + v20 = *v17; + *v17 = v17[1]; + v17[1] = v20; + v17 += 2; + --v18; + } +} + +void LoadBlock32b(wxUint32 tile, wxUint32 ul_s, wxUint32 ul_t, wxUint32 lr_s, wxUint32 dxt); +static void rdp_loadblock() +{ + if (rdp.skip_drawing) + { + LRDP("loadblock skipped\n"); + return; + } + wxUint32 tile = (wxUint32)((rdp.cmd1 >> 24) & 0x07); + wxUint32 dxt = (wxUint32)(rdp.cmd1 & 0x0FFF); + wxUint16 lr_s = (wxUint16)(rdp.cmd1 >> 14) & 0x3FF; + if (ucode5_texshiftaddr) + { + if (ucode5_texshift % ((lr_s+1)<<3)) + { + rdp.timg.addr -= ucode5_texshift; + ucode5_texshiftaddr = 0; + ucode5_texshift = 0; + ucode5_texshiftcount = 0; + } + else + ucode5_texshiftcount++; + } + + rdp.addr[rdp.tiles[tile].t_mem] = rdp.timg.addr; + + // ** DXT is used for swapping every other line + /* double fdxt = (double)0x8000000F/(double)((wxUint32)(2047/(dxt-1))); // F for error + wxUint32 _dxt = (wxUint32)fdxt;*/ + + // 0x00000800 -> 0x80000000 (so we can check the sign bit instead of the 11th bit) + wxUint32 _dxt = dxt << 20; + + wxUint32 addr = segoffset(rdp.timg.addr) & BMASK; + + // lr_s specifies number of 64-bit words to copy + // 10.2 format + wxUint16 ul_s = (wxUint16)(rdp.cmd0 >> 14) & 0x3FF; + wxUint16 ul_t = (wxUint16)(rdp.cmd0 >> 2) & 0x3FF; + + rdp.tiles[tile].ul_s = ul_s; + rdp.tiles[tile].ul_t = ul_t; + rdp.tiles[tile].lr_s = lr_s; + + rdp.timg.set_by = 0; // load block + +#ifdef TEXTURE_FILTER + LOAD_TILE_INFO &info = rdp.load_info[rdp.tiles[tile].t_mem]; + info.tile_width = lr_s; + info.dxt = dxt; +#endif + + // do a quick boundary check before copying to eliminate the possibility for exception + if (ul_s >= 512) { + lr_s = 1; // 1 so that it doesn't die on memcpy + ul_s = 511; + } + if (ul_s+lr_s > 512) + lr_s = 512-ul_s; + + if (addr+(lr_s<<3) > BMASK+1) + lr_s = (wxUint16)((BMASK-addr)>>3); + + //angrylion's advice to use ul_s in texture image offset and cnt calculations. + //Helps to fix Vigilante 8 jpeg backgrounds and logos + wxUint32 off = rdp.timg.addr + (ul_s << rdp.tiles[tile].size >> 1); + unsigned char *dst = ((unsigned char *)rdp.tmem) + (rdp.tiles[tile].t_mem<<3); + wxUint32 cnt = lr_s-ul_s+1; + if (rdp.tiles[tile].size == 3) + cnt <<= 1; + + if (((rdp.tiles[tile].t_mem + cnt) << 3) > sizeof(rdp.tmem)) { + WriteLog(M64MSG_INFO, "rdp_loadblock wanted to write %u bytes after the end of tmem", ((rdp.tiles[tile].t_mem + cnt) << 3) - sizeof(rdp.tmem)); + cnt = (sizeof(rdp.tmem) >> 3) - (rdp.tiles[tile].t_mem); + } + + if (rdp.timg.size == 3) + LoadBlock32b(tile, ul_s, ul_t, lr_s, dxt); + else + loadBlock((uint32_t *)gfx.RDRAM, (uint32_t *)dst, off, _dxt, cnt); + + rdp.timg.addr += cnt << 3; + rdp.tiles[tile].lr_t = ul_t + ((dxt*cnt)>>11); + + rdp.update |= UPDATE_TEXTURE; + + FRDP ("loadblock: tile: %d, ul_s: %d, ul_t: %d, lr_s: %d, dxt: %08lx -> %08lx\n", + tile, ul_s, ul_t, lr_s, + dxt, _dxt); + + if (fb_hwfbe_enabled) + setTBufTex(rdp.tiles[tile].t_mem, cnt); +} + + +static inline void loadTile(uint32_t *src, uint32_t *dst, int width, int height, int line, int off, uint32_t *end) +{ + uint32_t *v7; + int v8; + uint32_t *v9; + int v10; + int v11; + int v12; + uint32_t *v13; + int v14; + int v15; + uint32_t v16; + uint32_t *v17; + uint32_t v18; + int v19; + uint32_t v20; + int v21; + uint32_t v22; + int v23; + uint32_t *v24; + int v25; + int v26; + uint32_t *v27; + int v28; + int v29; + int v30; + uint32_t *v31; + + v7 = dst; + v8 = width; + v9 = src; + v10 = off; + v11 = 0; + v12 = height; + do + { + if ( end < v7 ) + break; + v31 = v7; + v30 = v8; + v29 = v12; + v28 = v11; + v27 = v9; + v26 = v10; + if ( v8 ) + { + v25 = v8; + v24 = v9; + v23 = v10; + v13 = (uint32_t *)((char *)v9 + (v10 & 0xFFFFFFFC)); + v14 = v10 & 3; + if ( !(v10 & 3) ) + goto LABEL_20; + v15 = 4 - v14; + v16 = *v13; + v17 = v13 + 1; + do + { + v16 = __ROL__(v16, 8); + --v14; + } + while ( v14 ); + do + { + v16 = __ROL__(v16, 8); + *(uint8_t *)v7 = v16; + v7 = (uint32_t *)((char *)v7 + 1); + --v15; + } + while ( v15 ); + v18 = *v17; + v13 = v17 + 1; + *v7 = bswap32(v18); + ++v7; + --v8; + if ( v8 ) + { +LABEL_20: + do + { + *v7 = bswap32(*v13); + v7[1] = bswap32(v13[1]); + v13 += 2; + v7 += 2; + --v8; + } + while ( v8 ); + } + v19 = v23 & 3; + if ( v23 & 3 ) + { + v20 = *(uint32_t *)((char *)v24 + ((8 * v25 + v23) & 0xFFFFFFFC)); + do + { + v20 = __ROL__(v20, 8); + *(uint8_t *)v7 = v20; + v7 = (uint32_t *)((char *)v7 + 1); + --v19; + } + while ( v19 ); + } + } + v9 = v27; + v21 = v29; + v8 = v30; + v11 = v28 ^ 1; + if ( v28 == 1 ) + { + v7 = v31; + if ( v30 ) + { + do + { + v22 = *v7; + *v7 = v7[1]; + v7[1] = v22; + v7 += 2; + --v8; + } + while ( v8 ); + } + v21 = v29; + v8 = v30; + } + v10 = line + v26; + v12 = v21 - 1; + } + while ( v12 ); +} + +void LoadTile32b (wxUint32 tile, wxUint32 ul_s, wxUint32 ul_t, wxUint32 width, wxUint32 height); +static void rdp_loadtile() +{ + if (rdp.skip_drawing) + { + LRDP("loadtile skipped\n"); + return; + } + rdp.timg.set_by = 1; // load tile + + wxUint32 tile = (wxUint32)((rdp.cmd1 >> 24) & 0x07); + + rdp.addr[rdp.tiles[tile].t_mem] = rdp.timg.addr; + + wxUint16 ul_s = (wxUint16)((rdp.cmd0 >> 14) & 0x03FF); + wxUint16 ul_t = (wxUint16)((rdp.cmd0 >> 2 ) & 0x03FF); + wxUint16 lr_s = (wxUint16)((rdp.cmd1 >> 14) & 0x03FF); + wxUint16 lr_t = (wxUint16)((rdp.cmd1 >> 2 ) & 0x03FF); + + if (lr_s < ul_s || lr_t < ul_t) return; + + if (wrong_tile >= 0) //there was a tile with zero length + { + rdp.tiles[wrong_tile].lr_s = lr_s; + + if (rdp.tiles[tile].size > rdp.tiles[wrong_tile].size) + rdp.tiles[wrong_tile].lr_s <<= (rdp.tiles[tile].size - rdp.tiles[wrong_tile].size); + else if (rdp.tiles[tile].size < rdp.tiles[wrong_tile].size) + rdp.tiles[wrong_tile].lr_s >>= (rdp.tiles[wrong_tile].size - rdp.tiles[tile].size); + rdp.tiles[wrong_tile].lr_t = lr_t; + rdp.tiles[wrong_tile].mask_s = rdp.tiles[wrong_tile].mask_t = 0; + // wrong_tile = -1; + } + + if (rdp.tbuff_tex)// && (rdp.tiles[tile].format == 0)) + { + FRDP("loadtile: tbuff_tex ul_s: %d, ul_t:%d\n", ul_s, ul_t); + rdp.tbuff_tex->tile_uls = ul_s; + rdp.tbuff_tex->tile_ult = ul_t; + } + + if ((settings.hacks&hack_Tonic) && tile == 7) + { + rdp.tiles[0].ul_s = ul_s; + rdp.tiles[0].ul_t = ul_t; + rdp.tiles[0].lr_s = lr_s; + rdp.tiles[0].lr_t = lr_t; + } + + wxUint32 height = lr_t - ul_t + 1; // get height + wxUint32 width = lr_s - ul_s + 1; + +#ifdef TEXTURE_FILTER + LOAD_TILE_INFO &info = rdp.load_info[rdp.tiles[tile].t_mem]; + info.tile_ul_s = ul_s; + info.tile_ul_t = ul_t; + info.tile_width = (rdp.tiles[tile].mask_s ? min((wxUint16)width, 1<> 1; + wxUint32 offs = ul_t * line_n; + offs += ul_s << rdp.tiles[tile].size >> 1; + offs += rdp.timg.addr; + if (offs >= BMASK) + return; + + if (rdp.timg.size == 3) + { + LoadTile32b(tile, ul_s, ul_t, width, height); + } + else + { + // check if points to bad location + if (offs + line_n*height > BMASK) + height = (BMASK - offs) / line_n; + if (height == 0) + return; + + wxUint32 wid_64 = rdp.tiles[tile].line; + unsigned char *dst = ((unsigned char *)rdp.tmem) + (rdp.tiles[tile].t_mem<<3); + unsigned char *end = ((unsigned char *)rdp.tmem) + 4096 - (wid_64<<3); + loadTile((uint32_t *)gfx.RDRAM, (uint32_t *)dst, wid_64, height, line_n, offs, (uint32_t *)end); + } + FRDP("loadtile: tile: %d, ul_s: %d, ul_t: %d, lr_s: %d, lr_t: %d\n", tile, + ul_s, ul_t, lr_s, lr_t); + + if (fb_hwfbe_enabled) + setTBufTex(rdp.tiles[tile].t_mem, rdp.tiles[tile].line*height); +} + +static void rdp_settile() +{ + tile_set = 1; // used to check if we only load the first settilesize + + rdp.first = 0; + + rdp.last_tile = (wxUint32)((rdp.cmd1 >> 24) & 0x07); + TILE *tile = &rdp.tiles[rdp.last_tile]; + + tile->format = (wxUint8)((rdp.cmd0 >> 21) & 0x07); + tile->size = (wxUint8)((rdp.cmd0 >> 19) & 0x03); + tile->line = (wxUint16)((rdp.cmd0 >> 9) & 0x01FF); + tile->t_mem = (wxUint16)(rdp.cmd0 & 0x1FF); + tile->palette = (wxUint8)((rdp.cmd1 >> 20) & 0x0F); + tile->clamp_t = (wxUint8)((rdp.cmd1 >> 19) & 0x01); + tile->mirror_t = (wxUint8)((rdp.cmd1 >> 18) & 0x01); + tile->mask_t = (wxUint8)((rdp.cmd1 >> 14) & 0x0F); + tile->shift_t = (wxUint8)((rdp.cmd1 >> 10) & 0x0F); + tile->clamp_s = (wxUint8)((rdp.cmd1 >> 9) & 0x01); + tile->mirror_s = (wxUint8)((rdp.cmd1 >> 8) & 0x01); + tile->mask_s = (wxUint8)((rdp.cmd1 >> 4) & 0x0F); + tile->shift_s = (wxUint8)(rdp.cmd1 & 0x0F); + + rdp.update |= UPDATE_TEXTURE; + + FRDP ("settile: tile: %d, format: %s, size: %s, line: %d, " + "t_mem: %08lx, palette: %d, clamp_t/mirror_t: %s, mask_t: %d, " + "shift_t: %d, clamp_s/mirror_s: %s, mask_s: %d, shift_s: %d\n", + rdp.last_tile, str_format[tile->format], str_size[tile->size], tile->line, + tile->t_mem, tile->palette, str_cm[(tile->clamp_t<<1)|tile->mirror_t], tile->mask_t, + tile->shift_t, str_cm[(tile->clamp_s<<1)|tile->mirror_s], tile->mask_s, tile->shift_s); + + if (fb_hwfbe_enabled && rdp.last_tile < rdp.cur_tile + 2) + { + for (int i = 0; i < 2; i++) + { + if (rdp.aTBuffTex[i]) + { + if (rdp.aTBuffTex[i]->t_mem == tile->t_mem) + { + if (rdp.aTBuffTex[i]->size == tile->size) + { + rdp.aTBuffTex[i]->tile = rdp.last_tile; + rdp.aTBuffTex[i]->info.format = tile->format == 0 ? GR_TEXFMT_RGB_565 : GR_TEXFMT_ALPHA_INTENSITY_88; + FRDP("rdp.aTBuffTex[%d] tile=%d, format=%s\n", i, rdp.last_tile, tile->format == 0 ? "RGB565" : "Alpha88"); + } + else + rdp.aTBuffTex[i] = 0; + break; + } + else if (rdp.aTBuffTex[i]->tile == rdp.last_tile) //wrong! t_mem must be the same + rdp.aTBuffTex[i] = 0; + } + } + } +} + +// +// fillrect - fills a rectangle +// + +static void rdp_fillrect() +{ + wxUint32 ul_x = ((rdp.cmd1 & 0x00FFF000) >> 14); + wxUint32 ul_y = (rdp.cmd1 & 0x00000FFF) >> 2; + wxUint32 lr_x = ((rdp.cmd0 & 0x00FFF000) >> 14) + 1; + wxUint32 lr_y = ((rdp.cmd0 & 0x00000FFF) >> 2) + 1; + if ((ul_x > lr_x) || (ul_y > lr_y)) + { + LRDP("Fillrect. Wrong coordinates. Skipped\n"); + return; + } + int pd_multiplayer = (settings.ucode == ucode_PerfectDark) && (rdp.cycle_mode == 3) && (rdp.fill_color == 0xFFFCFFFC); + if ((rdp.cimg == rdp.zimg) || (fb_emulation_enabled && rdp.frame_buffers[rdp.ci_count-1].status == ci_zimg) || pd_multiplayer) + { + LRDP("Fillrect - cleared the depth buffer\n"); + if (fullscreen) + { + if (!(settings.hacks&hack_Hyperbike) || rdp.ci_width > 64) //do not clear main depth buffer for aux depth buffers + { + update_scissor (); + grDepthMask (FXTRUE); + grColorMask (FXFALSE, FXFALSE); + grBufferClear (0, 0, rdp.fill_color ? rdp.fill_color&0xFFFF : 0xFFFF); + grColorMask (FXTRUE, FXTRUE); + rdp.update |= UPDATE_ZBUF_ENABLED; + } + //if (settings.frame_buffer&fb_depth_clear) + { + ul_x = min(max(ul_x, rdp.scissor_o.ul_x), rdp.scissor_o.lr_x); + lr_x = min(max(lr_x, rdp.scissor_o.ul_x), rdp.scissor_o.lr_x); + ul_y = min(max(ul_y, rdp.scissor_o.ul_y), rdp.scissor_o.lr_y); + lr_y = min(max(lr_y, rdp.scissor_o.ul_y), rdp.scissor_o.lr_y); + wxUint32 zi_width_in_dwords = rdp.ci_width >> 1; + ul_x >>= 1; + lr_x >>= 1; + wxUint32 * dst = (wxUint32*)(gfx.RDRAM+rdp.cimg); + dst += ul_y * zi_width_in_dwords; + for (wxUint32 y = ul_y; y < lr_y; y++) + { + for (wxUint32 x = ul_x; x < lr_x; x++) + { + dst[x] = rdp.fill_color; + } + dst += zi_width_in_dwords; + } + } + } + return; + } + + if (rdp.skip_drawing) + { + LRDP("Fillrect skipped\n"); + return; + } + + if (rdp.cur_image && (rdp.cur_image->format != 0) && (rdp.cycle_mode == 3) && (rdp.cur_image->width == lr_x - ul_x) && (rdp.cur_image->height == lr_y - ul_y)) + { + wxUint32 color = rdp.fill_color; + if (rdp.ci_size < 3) + { + color = ((color&1)?0xFF:0) | + ((wxUint32)((float)((color&0xF800) >> 11) / 31.0f * 255.0f) << 24) | + ((wxUint32)((float)((color&0x07C0) >> 6) / 31.0f * 255.0f) << 16) | + ((wxUint32)((float)((color&0x003E) >> 1) / 31.0f * 255.0f) << 8); + } + grDepthMask (FXFALSE); + grBufferClear (color, 0, 0xFFFF); + grDepthMask (FXTRUE); + rdp.update |= UPDATE_ZBUF_ENABLED; + LRDP("Fillrect - cleared the texture buffer\n"); + return; + } + + // Update scissor + if (fullscreen) + update_scissor (); + + if (settings.decrease_fillrect_edge && rdp.cycle_mode == 0) + { + lr_x--; lr_y--; + } + FRDP("fillrect (%d,%d) -> (%d,%d), cycle mode: %d, #%d, #%d\n", ul_x, ul_y, lr_x, lr_y, rdp.cycle_mode, + rdp.tri_n, rdp.tri_n+1); + + FRDP("scissor (%d,%d) -> (%d,%d)\n", rdp.scissor.ul_x, rdp.scissor.ul_y, rdp.scissor.lr_x, + rdp.scissor.lr_y); + + // KILL the floating point error with 0.01f + wxInt32 s_ul_x = (wxUint32)min(max(ul_x * rdp.scale_x + rdp.offset_x + 0.01f, rdp.scissor.ul_x), rdp.scissor.lr_x); + wxInt32 s_lr_x = (wxUint32)min(max(lr_x * rdp.scale_x + rdp.offset_x + 0.01f, rdp.scissor.ul_x), rdp.scissor.lr_x); + wxInt32 s_ul_y = (wxUint32)min(max(ul_y * rdp.scale_y + rdp.offset_y + 0.01f, rdp.scissor.ul_y), rdp.scissor.lr_y); + wxInt32 s_lr_y = (wxUint32)min(max(lr_y * rdp.scale_y + rdp.offset_y + 0.01f, rdp.scissor.ul_y), rdp.scissor.lr_y); + + if (s_lr_x < 0) s_lr_x = 0; + if (s_lr_y < 0) s_lr_y = 0; + if ((wxUint32)s_ul_x > settings.res_x) s_ul_x = settings.res_x; + if ((wxUint32)s_ul_y > settings.res_y) s_ul_y = settings.res_y; + + FRDP (" - %d, %d, %d, %d\n", s_ul_x, s_ul_y, s_lr_x, s_lr_y); + + if (fullscreen) + { + grFogMode (GR_FOG_DISABLE); + + const float Z = (rdp.cycle_mode == 3) ? 0.0f : set_sprite_combine_mode(); + + // Draw the rectangle + VERTEX v[4] = { + { (float)s_ul_x, (float)s_ul_y, Z, 1.0f, 0,0,0,0, {0,0,0,0}, 0,0, 0,0,0,0}, + { (float)s_lr_x, (float)s_ul_y, Z, 1.0f, 0,0,0,0, {0,0,0,0}, 0,0, 0,0,0,0}, + { (float)s_ul_x, (float)s_lr_y, Z, 1.0f, 0,0,0,0, {0,0,0,0}, 0,0, 0,0,0,0}, + { (float)s_lr_x, (float)s_lr_y, Z, 1.0f, 0,0,0,0, {0,0,0,0}, 0,0, 0,0,0,0} }; + + if (rdp.cycle_mode == 3) + { + wxUint32 color = rdp.fill_color; + + if ((settings.hacks&hack_PMario) && rdp.frame_buffers[rdp.ci_count-1].status == ci_aux) + { + //background of auxiliary frame buffers must have zero alpha. + //make it black, set 0 alpha to plack pixels on frame buffer read + color = 0; + } + else if (rdp.ci_size < 3) + { + color = ((color&1)?0xFF:0) | + ((wxUint32)((float)((color&0xF800) >> 11) / 31.0f * 255.0f) << 24) | + ((wxUint32)((float)((color&0x07C0) >> 6) / 31.0f * 255.0f) << 16) | + ((wxUint32)((float)((color&0x003E) >> 1) / 31.0f * 255.0f) << 8); + } + + grConstantColorValue (color); + + grColorCombine (GR_COMBINE_FUNCTION_LOCAL, + GR_COMBINE_FACTOR_NONE, + GR_COMBINE_LOCAL_CONSTANT, + GR_COMBINE_OTHER_NONE, + FXFALSE); + + grAlphaCombine (GR_COMBINE_FUNCTION_LOCAL, + GR_COMBINE_FACTOR_NONE, + GR_COMBINE_LOCAL_CONSTANT, + GR_COMBINE_OTHER_NONE, + FXFALSE); + + grAlphaBlendFunction (GR_BLEND_ONE, GR_BLEND_ZERO, GR_BLEND_ONE, GR_BLEND_ZERO); + + grAlphaTestFunction (GR_CMP_ALWAYS); + if (grStippleModeExt) + grStippleModeExt(GR_STIPPLE_DISABLE); + + grCullMode(GR_CULL_DISABLE); + grFogMode (GR_FOG_DISABLE); + grDepthBufferFunction (GR_CMP_ALWAYS); + grDepthMask (FXFALSE); + + rdp.update |= UPDATE_COMBINE | UPDATE_CULL_MODE | UPDATE_FOG_ENABLED | UPDATE_ZBUF_ENABLED; + } + else + { + wxUint32 cmb_mode_c = (rdp.cycle1 << 16) | (rdp.cycle2 & 0xFFFF); + wxUint32 cmb_mode_a = (rdp.cycle1 & 0x0FFF0000) | ((rdp.cycle2 >> 16) & 0x00000FFF); + if (cmb_mode_c == 0x9fff9fff || cmb_mode_a == 0x09ff09ff) //shade + { + AllowShadeMods (v, 4); + for (int k = 0; k < 4; k++) + apply_shade_mods (&v[k]); + } + if ((rdp.othermode_l & 0x4000) && ((rdp.othermode_l >> 16) == 0x0550)) //special blender mode for Bomberman64 + { + grAlphaCombine (GR_COMBINE_FUNCTION_LOCAL, + GR_COMBINE_FACTOR_NONE, + GR_COMBINE_LOCAL_CONSTANT, + GR_COMBINE_OTHER_NONE, + FXFALSE); + grConstantColorValue((cmb.ccolor&0xFFFFFF00)|(rdp.fog_color&0xFF)); + rdp.update |= UPDATE_COMBINE; + } + } + + if (settings.wireframe) + { + SetWireframeCol (); + grDrawLine (&v[0], &v[2]); + grDrawLine (&v[2], &v[1]); + grDrawLine (&v[1], &v[0]); + grDrawLine (&v[2], &v[3]); + grDrawLine (&v[3], &v[1]); + //grDrawLine (&v[1], &v[2]); + } + else + { + grDrawTriangle (&v[0], &v[2], &v[1]); + grDrawTriangle (&v[2], &v[3], &v[1]); + } + + if (_debugger.capture) + { + VERTEX v1[3]; + v1[0] = v[0]; + v1[1] = v[2]; + v1[2] = v[1]; + add_tri (v1, 3, TRI_FILLRECT); + rdp.tri_n ++; + v1[0] = v[2]; + v1[1] = v[3]; + add_tri (v1, 3, TRI_FILLRECT); + rdp.tri_n ++; + } + else + rdp.tri_n += 2; + } + else + { + rdp.tri_n += 2; + } +} + +// +// setfillcolor - sets the filling color +// + +static void rdp_setfillcolor() +{ + rdp.fill_color = rdp.cmd1; + rdp.update |= UPDATE_ALPHA_COMPARE | UPDATE_COMBINE; + + FRDP("setfillcolor: %08lx\n", rdp.cmd1); +} + +static void rdp_setfogcolor() +{ + rdp.fog_color = rdp.cmd1; + rdp.update |= UPDATE_COMBINE | UPDATE_FOG_ENABLED; + + FRDP("setfogcolor - %08lx\n", rdp.cmd1); +} + +static void rdp_setblendcolor() +{ + rdp.blend_color = rdp.cmd1; + rdp.update |= UPDATE_COMBINE; + + FRDP("setblendcolor: %08lx\n", rdp.cmd1); +} + +static void rdp_setprimcolor() +{ + rdp.prim_color = rdp.cmd1; + rdp.prim_lodmin = (rdp.cmd0 >> 8) & 0xFF; + rdp.prim_lodfrac = max(rdp.cmd0 & 0xFF, rdp.prim_lodmin); + rdp.update |= UPDATE_COMBINE; + + FRDP("setprimcolor: %08lx, lodmin: %d, lodfrac: %d\n", rdp.cmd1, rdp.prim_lodmin, + rdp.prim_lodfrac); +} + +static void rdp_setenvcolor() +{ + rdp.env_color = rdp.cmd1; + rdp.update |= UPDATE_COMBINE; + + FRDP("setenvcolor: %08lx\n", rdp.cmd1); +} + +static void rdp_setcombine() +{ + rdp.c_a0 = (wxUint8)((rdp.cmd0 >> 20) & 0xF); + rdp.c_b0 = (wxUint8)((rdp.cmd1 >> 28) & 0xF); + rdp.c_c0 = (wxUint8)((rdp.cmd0 >> 15) & 0x1F); + rdp.c_d0 = (wxUint8)((rdp.cmd1 >> 15) & 0x7); + rdp.c_Aa0 = (wxUint8)((rdp.cmd0 >> 12) & 0x7); + rdp.c_Ab0 = (wxUint8)((rdp.cmd1 >> 12) & 0x7); + rdp.c_Ac0 = (wxUint8)((rdp.cmd0 >> 9) & 0x7); + rdp.c_Ad0 = (wxUint8)((rdp.cmd1 >> 9) & 0x7); + + rdp.c_a1 = (wxUint8)((rdp.cmd0 >> 5) & 0xF); + rdp.c_b1 = (wxUint8)((rdp.cmd1 >> 24) & 0xF); + rdp.c_c1 = (wxUint8)((rdp.cmd0 >> 0) & 0x1F); + rdp.c_d1 = (wxUint8)((rdp.cmd1 >> 6) & 0x7); + rdp.c_Aa1 = (wxUint8)((rdp.cmd1 >> 21) & 0x7); + rdp.c_Ab1 = (wxUint8)((rdp.cmd1 >> 3) & 0x7); + rdp.c_Ac1 = (wxUint8)((rdp.cmd1 >> 18) & 0x7); + rdp.c_Ad1 = (wxUint8)((rdp.cmd1 >> 0) & 0x7); + + rdp.cycle1 = (rdp.c_a0<<0) | (rdp.c_b0<<4) | (rdp.c_c0<<8) | (rdp.c_d0<<13)| + (rdp.c_Aa0<<16)| (rdp.c_Ab0<<19)| (rdp.c_Ac0<<22)| (rdp.c_Ad0<<25); + rdp.cycle2 = (rdp.c_a1<<0) | (rdp.c_b1<<4) | (rdp.c_c1<<8) | (rdp.c_d1<<13)| + (rdp.c_Aa1<<16)| (rdp.c_Ab1<<19)| (rdp.c_Ac1<<22)| (rdp.c_Ad1<<25); + + rdp.update |= UPDATE_COMBINE; + + FRDP("setcombine\na0=%s b0=%s c0=%s d0=%s\nAa0=%s Ab0=%s Ac0=%s Ad0=%s\na1=%s b1=%s c1=%s d1=%s\nAa1=%s Ab1=%s Ac1=%s Ad1=%s\n", + Mode0[rdp.c_a0], Mode1[rdp.c_b0], Mode2[rdp.c_c0], Mode3[rdp.c_d0], + Alpha0[rdp.c_Aa0], Alpha1[rdp.c_Ab0], Alpha2[rdp.c_Ac0], Alpha3[rdp.c_Ad0], + Mode0[rdp.c_a1], Mode1[rdp.c_b1], Mode2[rdp.c_c1], Mode3[rdp.c_d1], + Alpha0[rdp.c_Aa1], Alpha1[rdp.c_Ab1], Alpha2[rdp.c_Ac1], Alpha3[rdp.c_Ad1]); +} + +// +// settextureimage - sets the source for an image copy +// + +static void rdp_settextureimage() +{ + static const char *format[] = { "RGBA", "YUV", "CI", "IA", "I", "?", "?", "?" }; + static const char *size[] = { "4bit", "8bit", "16bit", "32bit" }; + + rdp.timg.format = (wxUint8)((rdp.cmd0 >> 21) & 0x07); + rdp.timg.size = (wxUint8)((rdp.cmd0 >> 19) & 0x03); + rdp.timg.width = (wxUint16)(1 + (rdp.cmd0 & 0x00000FFF)); + rdp.timg.addr = segoffset(rdp.cmd1); + if (ucode5_texshiftaddr) + { + if (rdp.timg.format == 0) + { + wxUint16 * t = (wxUint16*)(gfx.RDRAM+ucode5_texshiftaddr); + ucode5_texshift = t[ucode5_texshiftcount^1]; + rdp.timg.addr += ucode5_texshift; + } + else + { + ucode5_texshiftaddr = 0; + ucode5_texshift = 0; + ucode5_texshiftcount = 0; + } + } + rdp.s2dex_tex_loaded = TRUE; + rdp.update |= UPDATE_TEXTURE; + + if (rdp.frame_buffers[rdp.ci_count-1].status == ci_copy_self && (rdp.timg.addr >= rdp.cimg) && (rdp.timg.addr < rdp.ci_end)) + { + if (!rdp.fb_drawn) + { + if (!rdp.cur_image) + CopyFrameBuffer(); + else + CloseTextureBuffer(TRUE); + rdp.fb_drawn = TRUE; + } + } + + if (fb_hwfbe_enabled) //search this texture among drawn texture buffers + FindTextureBuffer(rdp.timg.addr, rdp.timg.width); + + FRDP("settextureimage: format: %s, size: %s, width: %d, addr: %08lx\n", + format[rdp.timg.format], size[rdp.timg.size], + rdp.timg.width, rdp.timg.addr); +} + +static void rdp_setdepthimage() +{ + rdp.zimg = segoffset(rdp.cmd1) & BMASK; + rdp.zi_width = rdp.ci_width; + FRDP("setdepthimage - %08lx\n", rdp.zimg); +} + +int SwapOK = TRUE; +static void RestoreScale() +{ + FRDP("Return to original scale: x = %f, y = %f\n", rdp.scale_x_bak, rdp.scale_y_bak); + rdp.scale_x = rdp.scale_x_bak; + rdp.scale_y = rdp.scale_y_bak; + // update_scissor(); + rdp.view_scale[0] *= rdp.scale_x; + rdp.view_scale[1] *= rdp.scale_y; + rdp.view_trans[0] *= rdp.scale_x; + rdp.view_trans[1] *= rdp.scale_y; + rdp.update |= UPDATE_VIEWPORT | UPDATE_SCISSOR; + //* + if (fullscreen) + { + grDepthMask (FXFALSE); + grBufferClear (0, 0, 0xFFFF); + grDepthMask (FXTRUE); + } + //*/ +} + +static wxUint32 swapped_addr = 0; + +static void rdp_setcolorimage() +{ + if (fb_emulation_enabled && (rdp.num_of_ci < NUMTEXBUF)) + { + COLOR_IMAGE & cur_fb = rdp.frame_buffers[rdp.ci_count]; + COLOR_IMAGE & prev_fb = rdp.frame_buffers[rdp.ci_count?rdp.ci_count-1:0]; + COLOR_IMAGE & next_fb = rdp.frame_buffers[rdp.ci_count+1]; + switch (cur_fb.status) + { + case ci_main: + { + + if (rdp.ci_count == 0) + { + if ((rdp.ci_status == ci_aux)) //for PPL + { + float sx = rdp.scale_x; + float sy = rdp.scale_y; + rdp.scale_x = 1.0f; + rdp.scale_y = 1.0f; + CopyFrameBuffer (); + rdp.scale_x = sx; + rdp.scale_y = sy; + } + if (!fb_hwfbe_enabled) + { + if ((rdp.num_of_ci > 1) && + (next_fb.status == ci_aux) && + (next_fb.width >= cur_fb.width)) + { + rdp.scale_x = 1.0f; + rdp.scale_y = 1.0f; + } + } + else if (rdp.copy_ci_index && (settings.hacks&hack_PMario)) //tidal wave + OpenTextureBuffer(rdp.frame_buffers[rdp.main_ci_index]); + } + else if (!rdp.motionblur && fb_hwfbe_enabled && !SwapOK && (rdp.ci_count <= rdp.copy_ci_index)) + { + if (next_fb.status == ci_aux_copy) + OpenTextureBuffer(rdp.frame_buffers[rdp.main_ci_index]); + else + OpenTextureBuffer(rdp.frame_buffers[rdp.copy_ci_index]); + } + else if (fb_hwfbe_enabled && prev_fb.status == ci_aux) + { + if (rdp.motionblur) + { + rdp.cur_image = &(rdp.texbufs[rdp.cur_tex_buf].images[0]); + grRenderBuffer( GR_BUFFER_TEXTUREBUFFER_EXT ); + grTextureBufferExt( rdp.cur_image->tmu, rdp.cur_image->tex_addr, rdp.cur_image->info.smallLodLog2, rdp.cur_image->info.largeLodLog2, + rdp.cur_image->info.aspectRatioLog2, rdp.cur_image->info.format, GR_MIPMAPLEVELMASK_BOTH ); + } + else if (rdp.read_whole_frame) + { + OpenTextureBuffer(rdp.frame_buffers[rdp.main_ci_index]); + } + } + //else if (rdp.ci_status == ci_aux && !rdp.copy_ci_index) + // CloseTextureBuffer(); + + rdp.skip_drawing = FALSE; + } + break; + case ci_copy: + { + if (!rdp.motionblur || (settings.frame_buffer&fb_motionblur)) + { + if (cur_fb.width == rdp.ci_width) + { + if (CopyTextureBuffer(prev_fb, cur_fb)) + { + // if (CloseTextureBuffer(TRUE)) + //* + if ((settings.hacks&hack_Zelda) && (rdp.frame_buffers[rdp.ci_count+2].status == ci_aux) && !rdp.fb_drawn) //hack for photo camera in Zelda MM + { + CopyFrameBuffer (GR_BUFFER_TEXTUREBUFFER_EXT); + rdp.fb_drawn = TRUE; + memcpy(gfx.RDRAM+cur_fb.addr,gfx.RDRAM+rdp.cimg, (cur_fb.width*cur_fb.height)<>1); + } + //*/ + } + else + { + if (!rdp.fb_drawn || prev_fb.status == ci_copy_self) + { + CopyFrameBuffer (); + rdp.fb_drawn = TRUE; + } + memcpy(gfx.RDRAM+cur_fb.addr,gfx.RDRAM+rdp.cimg, (cur_fb.width*cur_fb.height)<>1); + } + } + else + { + CloseTextureBuffer(TRUE); + } + } + else + { + memset(gfx.RDRAM+cur_fb.addr, 0, cur_fb.width*cur_fb.height*rdp.ci_size); + } + rdp.skip_drawing = TRUE; + } + break; + case ci_aux_copy: + { + rdp.skip_drawing = FALSE; + if (CloseTextureBuffer(prev_fb.status != ci_aux_copy)) + ; + else if (!rdp.fb_drawn) + { + CopyFrameBuffer (); + rdp.fb_drawn = TRUE; + } + if (fb_hwfbe_enabled) + OpenTextureBuffer(cur_fb); + } + break; + case ci_old_copy: + { + if (!rdp.motionblur || (settings.frame_buffer&fb_motionblur)) + { + if (cur_fb.width == rdp.ci_width) + { + memcpy(gfx.RDRAM+cur_fb.addr,gfx.RDRAM+rdp.maincimg[1].addr, (cur_fb.width*cur_fb.height)<>1); + } + //rdp.skip_drawing = TRUE; + } + else + { + memset(gfx.RDRAM+cur_fb.addr, 0, (cur_fb.width*cur_fb.height)<>1); + } + } + break; + /* + else if (rdp.frame_buffers[rdp.ci_count].status == ci_main_i) + { + // CopyFrameBuffer (); + rdp.scale_x = rdp.scale_x_bak; + rdp.scale_y = rdp.scale_y_bak; + rdp.skip_drawing = FALSE; + } + */ + case ci_aux: + { + if (!fb_hwfbe_enabled && cur_fb.format != 0) + rdp.skip_drawing = TRUE; + else + { + rdp.skip_drawing = FALSE; + if (fb_hwfbe_enabled && OpenTextureBuffer(cur_fb)) + ; + else + { + if (cur_fb.format != 0) + rdp.skip_drawing = TRUE; + if (rdp.ci_count == 0) + { + // if (rdp.num_of_ci > 1) + // { + rdp.scale_x = 1.0f; + rdp.scale_y = 1.0f; + // } + } + else if (!fb_hwfbe_enabled && (prev_fb.status == ci_main) && + (prev_fb.width == cur_fb.width)) // for Pokemon Stadium + CopyFrameBuffer (); + } + } + cur_fb.status = ci_aux; + } + break; + case ci_zimg: + if (settings.ucode != ucode_PerfectDark) + { + if (fb_hwfbe_enabled && !rdp.copy_ci_index && (rdp.copy_zi_index || (settings.hacks&hack_BAR))) + { + GrLOD_t LOD = GR_LOD_LOG2_1024; + if (settings.scr_res_x > 1024) + LOD = GR_LOD_LOG2_2048; + grTextureAuxBufferExt( rdp.texbufs[0].tmu, rdp.texbufs[0].begin, LOD, LOD, + GR_ASPECT_LOG2_1x1, GR_TEXFMT_RGB_565, GR_MIPMAPLEVELMASK_BOTH ); + grAuxBufferExt( GR_BUFFER_TEXTUREAUXBUFFER_EXT ); + LRDP("rdp_setcolorimage - set texture depth buffer to TMU0\n"); + } + } + rdp.skip_drawing = TRUE; + break; + case ci_zcopy: + if (settings.ucode != ucode_PerfectDark) + { + if (fb_hwfbe_enabled && !rdp.copy_ci_index && rdp.copy_zi_index == rdp.ci_count) + { + CopyDepthBuffer(); + } + rdp.skip_drawing = TRUE; + } + break; + case ci_useless: + rdp.skip_drawing = TRUE; + break; + case ci_copy_self: + if (fb_hwfbe_enabled && (rdp.ci_count <= rdp.copy_ci_index) && (!SwapOK || settings.swapmode == 2)) + OpenTextureBuffer(cur_fb); + rdp.skip_drawing = FALSE; + break; + default: + rdp.skip_drawing = FALSE; + } + + if ((rdp.ci_count > 0) && (prev_fb.status >= ci_aux)) //for Pokemon Stadium + { + if (!fb_hwfbe_enabled && prev_fb.format == 0) + CopyFrameBuffer (); + else if ((settings.hacks&hack_Knockout) && prev_fb.width < 100) + CopyFrameBuffer (GR_BUFFER_TEXTUREBUFFER_EXT); + } + if (!fb_hwfbe_enabled && cur_fb.status == ci_copy) + { + if (!rdp.motionblur && (rdp.num_of_ci > rdp.ci_count+1) && (next_fb.status != ci_aux)) + { + RestoreScale(); + } + } + if (!fb_hwfbe_enabled && cur_fb.status == ci_aux) + { + if (cur_fb.format == 0) + { + if ((settings.hacks&hack_PPL) && (rdp.scale_x < 1.1f)) //need to put current image back to frame buffer + { + int width = cur_fb.width; + int height = cur_fb.height; + wxUint16 *ptr_dst = new wxUint16[width*height]; + wxUint16 *ptr_src = (wxUint16*)(gfx.RDRAM+cur_fb.addr); + wxUint16 c; + + for (int y=0; y> 1) | 0x8000; + ptr_dst[x + y * width] = c; + } + } + grLfbWriteRegion(GR_BUFFER_BACKBUFFER, + (wxUint32)rdp.offset_x, + (wxUint32)rdp.offset_y, + GR_LFB_SRC_FMT_555, + width, + height, + FXFALSE, + width<<1, + ptr_dst); + delete[] ptr_dst; + } + /* + else //just clear buffer + { + + grColorMask(FXTRUE, FXTRUE); + grBufferClear (0, 0, 0xFFFF); + } + */ + } + } + + if ((cur_fb.status == ci_main) && (rdp.ci_count > 0)) + { + int to_org_res = TRUE; + for (int i = rdp.ci_count + 1; i < rdp.num_of_ci; i++) + { + if ((rdp.frame_buffers[i].status != ci_main) && (rdp.frame_buffers[i].status != ci_zimg) && (rdp.frame_buffers[i].status != ci_zcopy)) + { + to_org_res = FALSE; + break; + } + } + if (to_org_res) + { + LRDP("return to original scale\n"); + rdp.scale_x = rdp.scale_x_bak; + rdp.scale_y = rdp.scale_y_bak; + if (fb_hwfbe_enabled && !rdp.read_whole_frame) + CloseTextureBuffer(); + } + if (fb_hwfbe_enabled && !rdp.read_whole_frame && (prev_fb.status >= ci_aux) && (rdp.ci_count > rdp.copy_ci_index)) + CloseTextureBuffer(); + + } + rdp.ci_status = cur_fb.status; + rdp.ci_count++; + } + + rdp.ocimg = rdp.cimg; + rdp.cimg = segoffset(rdp.cmd1) & BMASK; + rdp.ci_width = (rdp.cmd0 & 0xFFF) + 1; + if (fb_emulation_enabled) + rdp.ci_height = rdp.frame_buffers[rdp.ci_count-1].height; + else if (rdp.ci_width == 32) + rdp.ci_height = 32; + else + rdp.ci_height = rdp.scissor_o.lr_y; + if (rdp.zimg == rdp.cimg) + { + rdp.zi_width = rdp.ci_width; + // int zi_height = min((int)rdp.zi_width*3/4, (int)rdp.vi_height); + // rdp.zi_words = rdp.zi_width * zi_height; + } + wxUint32 format = (rdp.cmd0 >> 21) & 0x7; + rdp.ci_size = (rdp.cmd0 >> 19) & 0x3; + rdp.ci_end = rdp.cimg + ((rdp.ci_width*rdp.ci_height)<<(rdp.ci_size-1)); + FRDP("setcolorimage - %08lx, width: %d, height: %d, format: %d, size: %d\n", rdp.cmd1, rdp.ci_width, rdp.ci_height, format, rdp.ci_size); + FRDP("cimg: %08lx, ocimg: %08lx, SwapOK: %d\n", rdp.cimg, rdp.ocimg, SwapOK); + + if (format != 0) //can't draw into non RGBA buffer + { + if (!rdp.cur_image) + { + if (fb_hwfbe_enabled && rdp.ci_width <= 64) + OpenTextureBuffer(rdp.frame_buffers[rdp.ci_count - 1]); + else if (format > 2) + rdp.skip_drawing = TRUE; + return; + } + } + else + { + if (!fb_emulation_enabled) + rdp.skip_drawing = FALSE; + } + + CI_SET = TRUE; + if (settings.swapmode > 0) + { + if (rdp.zimg == rdp.cimg) + rdp.updatescreen = 1; + + int viSwapOK = ((settings.swapmode == 2) && (rdp.vi_org_reg == *gfx.VI_ORIGIN_REG)) ? FALSE : TRUE; + if ((rdp.zimg != rdp.cimg) && (rdp.ocimg != rdp.cimg) && SwapOK && viSwapOK && !rdp.cur_image) + { + if (fb_emulation_enabled) + rdp.maincimg[0] = rdp.frame_buffers[rdp.main_ci_index]; + else + rdp.maincimg[0].addr = rdp.cimg; + rdp.last_drawn_ci_addr = (settings.swapmode == 2) ? swapped_addr : rdp.maincimg[0].addr; + swapped_addr = rdp.cimg; + newSwapBuffers(); + rdp.vi_org_reg = *gfx.VI_ORIGIN_REG; + SwapOK = FALSE; + if (fb_hwfbe_enabled) + { + if (rdp.copy_ci_index && (rdp.frame_buffers[rdp.ci_count-1].status != ci_zimg)) + { + int idx = (rdp.frame_buffers[rdp.ci_count].status == ci_aux_copy) ? rdp.main_ci_index : rdp.copy_ci_index; + FRDP("attempt open tex buffer. status: %s, addr: %08lx\n", CIStatus[rdp.frame_buffers[idx].status], rdp.frame_buffers[idx].addr); + OpenTextureBuffer(rdp.frame_buffers[idx]); + if (rdp.frame_buffers[rdp.copy_ci_index].status == ci_main) //tidal wave + rdp.copy_ci_index = 0; + } + else if (rdp.read_whole_frame && !rdp.cur_image) + { + OpenTextureBuffer(rdp.frame_buffers[rdp.main_ci_index]); + } + } + } + } +} + +static void rsp_reserved0() +{ + if (settings.ucode == ucode_DiddyKong) + { + ucode5_texshiftaddr = segoffset(rdp.cmd1); + ucode5_texshiftcount = 0; + FRDP("uc5_texshift. addr: %08lx\n", ucode5_texshiftaddr); + } + else + { + RDP_E("reserved0 - IGNORED\n"); + LRDP("reserved0 - IGNORED\n"); + } +} + +static void rsp_reserved1() +{ + LRDP("reserved1 - ignored\n"); +} + +static void rsp_reserved2() +{ + LRDP("reserved2\n"); +} + +static void rsp_reserved3() +{ + LRDP("reserved3 - ignored\n"); +} + +void SetWireframeCol () +{ + if (!fullscreen) return; + + switch (settings.wfmode) + { + //case 0: // normal colors, don't do anything + case 1: // vertex colors + grColorCombine (GR_COMBINE_FUNCTION_LOCAL, + GR_COMBINE_FACTOR_NONE, + GR_COMBINE_LOCAL_ITERATED, + GR_COMBINE_OTHER_NONE, + FXFALSE); + grAlphaCombine (GR_COMBINE_FUNCTION_LOCAL, + GR_COMBINE_FACTOR_NONE, + GR_COMBINE_LOCAL_ITERATED, + GR_COMBINE_OTHER_NONE, + FXFALSE); + grAlphaBlendFunction (GR_BLEND_ONE, + GR_BLEND_ZERO, + GR_BLEND_ZERO, + GR_BLEND_ZERO); + grTexCombine (GR_TMU0, + GR_COMBINE_FUNCTION_ZERO, + GR_COMBINE_FACTOR_NONE, + GR_COMBINE_FUNCTION_ZERO, + GR_COMBINE_FACTOR_NONE, + FXFALSE, FXFALSE); + grTexCombine (GR_TMU1, + GR_COMBINE_FUNCTION_ZERO, + GR_COMBINE_FACTOR_NONE, + GR_COMBINE_FUNCTION_ZERO, + GR_COMBINE_FACTOR_NONE, + FXFALSE, FXFALSE); + break; + case 2: // red only + grColorCombine (GR_COMBINE_FUNCTION_LOCAL, + GR_COMBINE_FACTOR_NONE, + GR_COMBINE_LOCAL_CONSTANT, + GR_COMBINE_OTHER_NONE, + FXFALSE); + grAlphaCombine (GR_COMBINE_FUNCTION_LOCAL, + GR_COMBINE_FACTOR_NONE, + GR_COMBINE_LOCAL_CONSTANT, + GR_COMBINE_OTHER_NONE, + FXFALSE); + grConstantColorValue (0xFF0000FF); + grAlphaBlendFunction (GR_BLEND_ONE, + GR_BLEND_ZERO, + GR_BLEND_ZERO, + GR_BLEND_ZERO); + grTexCombine (GR_TMU0, + GR_COMBINE_FUNCTION_ZERO, + GR_COMBINE_FACTOR_NONE, + GR_COMBINE_FUNCTION_ZERO, + GR_COMBINE_FACTOR_NONE, + FXFALSE, FXFALSE); + grTexCombine (GR_TMU1, + GR_COMBINE_FUNCTION_ZERO, + GR_COMBINE_FACTOR_NONE, + GR_COMBINE_FUNCTION_ZERO, + GR_COMBINE_FACTOR_NONE, + FXFALSE, FXFALSE); + break; + } + + grAlphaTestFunction (GR_CMP_ALWAYS); + grCullMode (GR_CULL_DISABLE); + + rdp.update |= UPDATE_COMBINE | UPDATE_ALPHA_COMPARE; +} + +/****************************************************************** +Function: FrameBufferRead +Purpose: This function is called to notify the dll that the +frame buffer memory is beening read at the given address. +DLL should copy content from its render buffer to the frame buffer +in N64 RDRAM +DLL is responsible to maintain its own frame buffer memory addr list +DLL should copy 4KB block content back to RDRAM frame buffer. +Emulator should not call this function again if other memory +is read within the same 4KB range +input: addr rdram address +val val +size 1 = wxUint8, 2 = wxUint16, 4 = wxUint32 +output: none +*******************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +EXPORT void CALL FBRead(wxUint32 addr) +{ + LOG ("FBRead ()\n"); + + if (cpu_fb_ignore) + return; + if (cpu_fb_write_called) + { + cpu_fb_ignore = TRUE; + cpu_fb_write = FALSE; + return; + } + cpu_fb_read_called = TRUE; + wxUint32 a = segoffset(addr); + FRDP("FBRead. addr: %08lx\n", a); + if (!rdp.fb_drawn && (a >= rdp.cimg) && (a < rdp.ci_end)) + { + fbreads_back++; + //if (fbreads_back > 2) //&& (rdp.ci_width <= 320)) + { + CopyFrameBuffer (); + rdp.fb_drawn = TRUE; + } + } + if (!rdp.fb_drawn_front && (a >= rdp.maincimg[1].addr) && (a < rdp.maincimg[1].addr + rdp.ci_width*rdp.ci_height*2)) + { + fbreads_front++; + //if (fbreads_front > 2)//&& (rdp.ci_width <= 320)) + { + wxUint32 cimg = rdp.cimg; + rdp.cimg = rdp.maincimg[1].addr; + if (fb_emulation_enabled) + { + rdp.ci_width = rdp.maincimg[1].width; + rdp.ci_count = 0; + wxUint32 h = rdp.frame_buffers[0].height; + rdp.frame_buffers[0].height = rdp.maincimg[1].height; + CopyFrameBuffer(GR_BUFFER_FRONTBUFFER); + rdp.frame_buffers[0].height = h; + } + else + { + CopyFrameBuffer(GR_BUFFER_FRONTBUFFER); + } + rdp.cimg = cimg; + rdp.fb_drawn_front = TRUE; + } + } +} + +#if 0 +/****************************************************************** +Function: FrameBufferWriteList +Purpose: This function is called to notify the dll that the +frame buffer has been modified by CPU at the given address. +input: FrameBufferModifyEntry *plist +size = size of the plist, max = 1024 +output: none +*******************************************************************/ +EXPORT void CALL FBWList(FrameBufferModifyEntry *plist, wxUint32 size) +{ + LOG ("FBWList ()\n"); + FRDP("FBWList. size: %d\n", size); +} +#endif + +/****************************************************************** +Function: FrameBufferWrite +Purpose: This function is called to notify the dll that the +frame buffer has been modified by CPU at the given address. +input: addr rdram address +val val +size 1 = wxUint8, 2 = wxUint16, 4 = wxUint32 +output: none +*******************************************************************/ +EXPORT void CALL FBWrite(wxUint32 addr, wxUint32 size) +{ + LOG ("FBWrite ()\n"); + if (cpu_fb_ignore) + return; + if (cpu_fb_read_called) + { + cpu_fb_ignore = TRUE; + cpu_fb_write = FALSE; + return; + } + cpu_fb_write_called = TRUE; + wxUint32 a = segoffset(addr); + FRDP("FBWrite. addr: %08lx\n", a); + if (a < rdp.cimg || a > rdp.ci_end) + return; + cpu_fb_write = TRUE; + wxUint32 shift_l = (a-rdp.cimg) >> 1; + wxUint32 shift_r = shift_l+2; + + d_ul_x = min(d_ul_x, shift_l%rdp.ci_width); + d_ul_y = min(d_ul_y, shift_l/rdp.ci_width); + d_lr_x = max(d_lr_x, shift_r%rdp.ci_width); + d_lr_y = max(d_lr_y, shift_r/rdp.ci_width); +} + + +/************************************************************************ +Function: FBGetFrameBufferInfo +Purpose: This function is called by the emulator core to retrieve frame +buffer information from the video plugin in order to be able +to notify the video plugin about CPU frame buffer read/write +operations + +size: += 1 byte += 2 word (16 bit) <-- this is N64 default depth buffer format += 4 dword (32 bit) + +when frame buffer information is not available yet, set all values +in the FrameBufferInfo structure to 0 + +input: FrameBufferInfo pinfo[6] +pinfo is pointed to a FrameBufferInfo structure which to be +filled in by this function +output: Values are return in the FrameBufferInfo structure +Plugin can return up to 6 frame buffer info +************************************************************************/ +///* +#if 0 +typedef struct +{ + wxUint32 addr; + wxUint32 size; + wxUint32 width; + wxUint32 height; +} FrameBufferInfo; +#endif +EXPORT void CALL FBGetFrameBufferInfo(void *p) +{ + VLOG ("FBGetFrameBufferInfo ()\n"); + FrameBufferInfo * pinfo = (FrameBufferInfo *)p; + memset(pinfo,0,sizeof(FrameBufferInfo)*6); + if (!(settings.frame_buffer&fb_get_info)) + return; + LRDP("FBGetFrameBufferInfo ()\n"); + //* + if (fb_emulation_enabled) + { + pinfo[0].addr = rdp.maincimg[1].addr; + pinfo[0].size = rdp.maincimg[1].size; + pinfo[0].width = rdp.maincimg[1].width; + pinfo[0].height = rdp.maincimg[1].height; + int info_index = 1; + for (int i = 0; i < rdp.num_of_ci && info_index < 6; i++) + { + COLOR_IMAGE & cur_fb = rdp.frame_buffers[i]; + if (cur_fb.status == ci_main || cur_fb.status == ci_copy_self || + cur_fb.status == ci_old_copy) + { + pinfo[info_index].addr = cur_fb.addr; + pinfo[info_index].size = cur_fb.size; + pinfo[info_index].width = cur_fb.width; + pinfo[info_index].height = cur_fb.height; + info_index++; + } + } + } + else + { + pinfo[0].addr = rdp.maincimg[0].addr; + pinfo[0].size = rdp.ci_size; + pinfo[0].width = rdp.ci_width; + pinfo[0].height = rdp.ci_width*3/4; + pinfo[1].addr = rdp.maincimg[1].addr; + pinfo[1].size = rdp.ci_size; + pinfo[1].width = rdp.ci_width; + pinfo[1].height = rdp.ci_width*3/4; + } + //*/ +} +#ifdef __cplusplus +} +#endif +//*/ +#include "ucodeFB.h" + +void DetectFrameBufferUsage () +{ + LRDP("DetectFrameBufferUsage\n"); + + wxUint32 dlist_start = *(wxUint32*)(gfx.DMEM+0xFF0); + wxUint32 a; + + int tidal = FALSE; + if ((settings.hacks&hack_PMario) && (rdp.copy_ci_index || rdp.frame_buffers[rdp.copy_ci_index].status == ci_copy_self)) + tidal = TRUE; + wxUint32 ci = rdp.cimg, zi = rdp.zimg; + wxUint32 ci_height = rdp.frame_buffers[(rdp.ci_count > 0)?rdp.ci_count-1:0].height; + rdp.main_ci = rdp.main_ci_end = rdp.main_ci_bg = rdp.ci_count = 0; + rdp.main_ci_index = rdp.copy_ci_index = rdp.copy_zi_index = 0; + rdp.zimg_end = 0; + rdp.tmpzimg = 0; + rdp.motionblur = FALSE; + rdp.main_ci_last_tex_addr = 0; + int previous_ci_was_read = rdp.read_previous_ci; + rdp.read_previous_ci = FALSE; + rdp.read_whole_frame = FALSE; + rdp.swap_ci_index = rdp.black_ci_index = -1; + SwapOK = TRUE; + + // Start executing at the start of the display list + rdp.pc_i = 0; + rdp.pc[rdp.pc_i] = dlist_start; + rdp.dl_count = -1; + rdp.halt = 0; + rdp.scale_x_bak = rdp.scale_x; + rdp.scale_y_bak = rdp.scale_y; + + // MAIN PROCESSING LOOP + do { + + // Get the address of the next command + a = rdp.pc[rdp.pc_i] & BMASK; + + // Load the next command and its input + rdp.cmd0 = ((wxUint32*)gfx.RDRAM)[a>>2]; // \ Current command, 64 bit + rdp.cmd1 = ((wxUint32*)gfx.RDRAM)[(a>>2)+1]; // / + + // Output the address before the command + + // Go to the next instruction + rdp.pc[rdp.pc_i] = (a+8) & BMASK; + + if (wxPtrToUInt(reinterpret_cast(gfx_instruction_lite[settings.ucode][rdp.cmd0>>24]))) + gfx_instruction_lite[settings.ucode][rdp.cmd0>>24] (); + + // check DL counter + if (rdp.dl_count != -1) + { + rdp.dl_count --; + if (rdp.dl_count == 0) + { + rdp.dl_count = -1; + + LRDP("End of DL\n"); + rdp.pc_i --; + } + } + + } while (!rdp.halt); + SwapOK = TRUE; + if (rdp.ci_count > NUMTEXBUF) //overflow + { + rdp.cimg = ci; + rdp.zimg = zi; + rdp.num_of_ci = rdp.ci_count; + rdp.scale_x = rdp.scale_x_bak; + rdp.scale_y = rdp.scale_y_bak; + return; + } + + if (rdp.black_ci_index > 0 && rdp.black_ci_index < rdp.copy_ci_index) + rdp.frame_buffers[rdp.black_ci_index].status = ci_main; + + if (rdp.frame_buffers[rdp.ci_count-1].status == ci_unknown) + { + if (rdp.ci_count > 1) + rdp.frame_buffers[rdp.ci_count-1].status = ci_aux; + else + rdp.frame_buffers[rdp.ci_count-1].status = ci_main; + } + + if ((rdp.frame_buffers[rdp.ci_count-1].status == ci_aux) && + (rdp.frame_buffers[rdp.main_ci_index].width < 320) && + (rdp.frame_buffers[rdp.ci_count-1].width > rdp.frame_buffers[rdp.main_ci_index].width)) + { + for (int i = 0; i < rdp.ci_count; i++) + { + if (rdp.frame_buffers[i].status == ci_main) + rdp.frame_buffers[i].status = ci_aux; + else if (rdp.frame_buffers[i].addr == rdp.frame_buffers[rdp.ci_count-1].addr) + rdp.frame_buffers[i].status = ci_main; + // FRDP("rdp.frame_buffers[%d].status = %d\n", i, rdp.frame_buffers[i].status); + } + rdp.main_ci_index = rdp.ci_count-1; + } + + int all_zimg = TRUE; + int i; + for (i = 0; i < rdp.ci_count; i++) + { + if (rdp.frame_buffers[i].status != ci_zimg) + { + all_zimg = FALSE; + break; + } + } + if (all_zimg) + { + for (i = 0; i < rdp.ci_count; i++) + rdp.frame_buffers[i].status = ci_main; + } + + LRDP("detect fb final results: \n"); + for (i = 0; i < rdp.ci_count; i++) + { + FRDP("rdp.frame_buffers[%d].status = %s, addr: %08lx, height: %d\n", i, CIStatus[rdp.frame_buffers[i].status], rdp.frame_buffers[i].addr, rdp.frame_buffers[i].height); + } + + rdp.cimg = ci; + rdp.zimg = zi; + rdp.num_of_ci = rdp.ci_count; + if (rdp.read_previous_ci && previous_ci_was_read) + { + if (!fb_hwfbe_enabled || !rdp.copy_ci_index) + rdp.motionblur = TRUE; + } + if (rdp.motionblur || fb_hwfbe_enabled || (rdp.frame_buffers[rdp.copy_ci_index].status == ci_aux_copy)) + { + rdp.scale_x = rdp.scale_x_bak; + rdp.scale_y = rdp.scale_y_bak; + } + + if ((rdp.read_previous_ci || previous_ci_was_read) && !rdp.copy_ci_index) + rdp.read_whole_frame = TRUE; + if (rdp.read_whole_frame) + { + if (fb_hwfbe_enabled) + { + if (rdp.read_previous_ci && !previous_ci_was_read && (settings.swapmode != 2) && (settings.ucode != ucode_PerfectDark)) + { + int ind = (rdp.ci_count > 0)?rdp.ci_count-1:0; + wxUint32 height = rdp.frame_buffers[ind].height; + rdp.frame_buffers[ind].height = ci_height; + CopyFrameBuffer(); + rdp.frame_buffers[ind].height = height; + } + if (rdp.swap_ci_index < 0) + { + rdp.texbufs[0].clear_allowed = rdp.texbufs[1].clear_allowed = TRUE; + OpenTextureBuffer(rdp.frame_buffers[rdp.main_ci_index]); + } + } + else + { + if (rdp.motionblur) + { + if (settings.frame_buffer&fb_motionblur) + CopyFrameBuffer(); + else + memset(gfx.RDRAM+rdp.cimg, 0, rdp.ci_width*rdp.ci_height*rdp.ci_size); + } + else //if (ci_width == rdp.frame_buffers[rdp.main_ci_index].width) + { + if (rdp.maincimg[0].height > 65) //for 1080 + { + rdp.cimg = rdp.maincimg[0].addr; + rdp.ci_width = rdp.maincimg[0].width; + rdp.ci_count = 0; + wxUint32 h = rdp.frame_buffers[0].height; + rdp.frame_buffers[0].height = rdp.maincimg[0].height; + CopyFrameBuffer(); + rdp.frame_buffers[0].height = h; + } + else //conker + { + CopyFrameBuffer(); + } + } + } + } + + if (fb_hwfbe_enabled) + { + for (i = 0; i < voodoo.num_tmu; i++) + { + rdp.texbufs[i].clear_allowed = TRUE; + for (int j = 0; j < 256; j++) + { + rdp.texbufs[i].images[j].drawn = FALSE; + rdp.texbufs[i].images[j].clear = TRUE; + } + } + if (tidal) + { + //LRDP("Tidal wave!\n"); + rdp.copy_ci_index = rdp.main_ci_index; + } + } + rdp.ci_count = 0; + if (settings.hacks&hack_Banjo2) + rdp.cur_tex_buf = 0; + rdp.maincimg[0] = rdp.frame_buffers[rdp.main_ci_index]; + // rdp.scale_x = rdp.scale_x_bak; + // rdp.scale_y = rdp.scale_y_bak; + LRDP("DetectFrameBufferUsage End\n"); +} + +/******************************************* + * ProcessRDPList * + ******************************************* + * based on sources of ziggy's z64 * + *******************************************/ + +static wxUint32 rdp_cmd_ptr = 0; +static wxUint32 rdp_cmd_cur = 0; +static wxUint32 rdp_cmd_data[0x1000]; + +void lle_triangle(wxUint32 w1, wxUint32 w2, int shade, int texture, int zbuffer, + wxUint32 * rdp_cmd) +{ + rdp.cur_tile = (w1 >> 16) & 0x7; + int j; + int xleft, xright, xleft_inc, xright_inc; + int r, g, b, a, z, s, t, w; + int drdx = 0, dgdx = 0, dbdx = 0, dadx = 0, dzdx = 0, dsdx = 0, dtdx = 0, dwdx = 0; + int drde = 0, dgde = 0, dbde = 0, dade = 0, dzde = 0, dsde = 0, dtde = 0, dwde = 0; + int flip = (w1 & 0x800000) ? 1 : 0; + + wxInt32 yl, ym, yh; + wxInt32 xl, xm, xh; + wxInt32 dxldy, dxhdy, dxmdy; + wxUint32 w3, w4, w5, w6, w7, w8; + + wxUint32 * shade_base = rdp_cmd + 8; + wxUint32 * texture_base = rdp_cmd + 8; + wxUint32 * zbuffer_base = rdp_cmd + 8; + + if (shade) + { + texture_base += 16; + zbuffer_base += 16; + } + if (texture) + { + zbuffer_base += 16; + } + + w3 = rdp_cmd[2]; + w4 = rdp_cmd[3]; + w5 = rdp_cmd[4]; + w6 = rdp_cmd[5]; + w7 = rdp_cmd[6]; + w8 = rdp_cmd[7]; + + yl = (w1 & 0x3fff); + ym = ((w2 >> 16) & 0x3fff); + yh = ((w2 >> 0) & 0x3fff); + xl = (wxInt32)(w3); + xh = (wxInt32)(w5); + xm = (wxInt32)(w7); + dxldy = (wxInt32)(w4); + dxhdy = (wxInt32)(w6); + dxmdy = (wxInt32)(w8); + + if (yl & (0x800<<2)) yl |= 0xfffff000<<2; + if (ym & (0x800<<2)) ym |= 0xfffff000<<2; + if (yh & (0x800<<2)) yh |= 0xfffff000<<2; + + yh &= ~3; + + r = 0xff; g = 0xff; b = 0xff; a = 0xff; z = 0xffff0000; s = 0; t = 0; w = 0x30000; + + if (shade) + { + r = (shade_base[0] & 0xffff0000) | ((shade_base[+4 ] >> 16) & 0x0000ffff); + g = ((shade_base[0 ] << 16) & 0xffff0000) | (shade_base[4 ] & 0x0000ffff); + b = (shade_base[1 ] & 0xffff0000) | ((shade_base[5 ] >> 16) & 0x0000ffff); + a = ((shade_base[1 ] << 16) & 0xffff0000) | (shade_base[5 ] & 0x0000ffff); + drdx = (shade_base[2 ] & 0xffff0000) | ((shade_base[6 ] >> 16) & 0x0000ffff); + dgdx = ((shade_base[2 ] << 16) & 0xffff0000) | (shade_base[6 ] & 0x0000ffff); + dbdx = (shade_base[3 ] & 0xffff0000) | ((shade_base[7 ] >> 16) & 0x0000ffff); + dadx = ((shade_base[3 ] << 16) & 0xffff0000) | (shade_base[7 ] & 0x0000ffff); + drde = (shade_base[8 ] & 0xffff0000) | ((shade_base[12] >> 16) & 0x0000ffff); + dgde = ((shade_base[8 ] << 16) & 0xffff0000) | (shade_base[12] & 0x0000ffff); + dbde = (shade_base[9 ] & 0xffff0000) | ((shade_base[13] >> 16) & 0x0000ffff); + dade = ((shade_base[9 ] << 16) & 0xffff0000) | (shade_base[13] & 0x0000ffff); + } + if (texture) + { + s = (texture_base[0 ] & 0xffff0000) | ((texture_base[4 ] >> 16) & 0x0000ffff); + t = ((texture_base[0 ] << 16) & 0xffff0000) | (texture_base[4 ] & 0x0000ffff); + w = (texture_base[1 ] & 0xffff0000) | ((texture_base[5 ] >> 16) & 0x0000ffff); + // w = abs(w); + dsdx = (texture_base[2 ] & 0xffff0000) | ((texture_base[6 ] >> 16) & 0x0000ffff); + dtdx = ((texture_base[2 ] << 16) & 0xffff0000) | (texture_base[6 ] & 0x0000ffff); + dwdx = (texture_base[3 ] & 0xffff0000) | ((texture_base[7 ] >> 16) & 0x0000ffff); + dsde = (texture_base[8 ] & 0xffff0000) | ((texture_base[12] >> 16) & 0x0000ffff); + dtde = ((texture_base[8 ] << 16) & 0xffff0000) | (texture_base[12] & 0x0000ffff); + dwde = (texture_base[9 ] & 0xffff0000) | ((texture_base[13] >> 16) & 0x0000ffff); + } + if (zbuffer) + { + z = zbuffer_base[0]; + dzdx = zbuffer_base[1]; + dzde = zbuffer_base[2]; + } + + xh <<= 2; xm <<= 2; xl <<= 2; + r <<= 2; g <<= 2; b <<= 2; a <<= 2; + dsde >>= 2; dtde >>= 2; dsdx >>= 2; dtdx >>= 2; + dzdx >>= 2; dzde >>= 2; + dwdx >>= 2; dwde >>= 2; + +#define XSCALE(x) (float(x)/(1<<18)) +#define YSCALE(y) (float(y)/(1<<2)) +#define ZSCALE(z) ((rdp.zsrc == 1)? float(rdp.prim_depth) : float(wxUint32(z))/0xffff0000) + //#define WSCALE(w) (rdp.Persp_en? (float(wxUint32(w) + 0x10000)/0xffff0000) : 1.0f) + //#define WSCALE(w) (rdp.Persp_en? 4294901760.0/(w + 65536) : 1.0f) +#define WSCALE(w) (rdp.Persp_en? 65536.0f/float((w+ 0xffff)>>16) : 1.0f) +#define CSCALE(c) (((c)>0x3ff0000? 0x3ff0000:((c)<0? 0 : (c)))>>18) +#define _PERSP(w) ( w ) +#define PERSP(s, w) ( ((int64_t)(s) << 20) / (_PERSP(w)? _PERSP(w):1) ) +#define SSCALE(s, _w) (rdp.Persp_en? float(PERSP(s, _w))/(1 << 10) : float(s)/(1<<21)) +#define TSCALE(s, w) (rdp.Persp_en? float(PERSP(s, w))/(1 << 10) : float(s)/(1<<21)) + + int nbVtxs = 0; + VERTEX vtxbuf[12]; + VERTEX * vtx = &vtxbuf[nbVtxs++]; + + xleft = xm; + xright = xh; + xleft_inc = dxmdy; + xright_inc = dxhdy; + + while (yh xright-0x10000))) { + xleft += xleft_inc; + xright += xright_inc; + s += dsde; t += dtde; w += dwde; + r += drde; g += dgde; b += dbde; a += dade; + z += dzde; + yh++; + } + + j = ym-yh; + if (j > 0) + { + int dx = (xleft-xright)>>16; + if ((!flip && xleft < xright) || + (flip/* && xleft > xright*/)) + { + if (shade) { + vtx->r = CSCALE(r+drdx*dx); + vtx->g = CSCALE(g+dgdx*dx); + vtx->b = CSCALE(b+dbdx*dx); + vtx->a = CSCALE(a+dadx*dx); + } + if (texture) { + vtx->ou = SSCALE(s+dsdx*dx, w+dwdx*dx); + vtx->ov = TSCALE(t+dtdx*dx, w+dwdx*dx); + } + vtx->x = XSCALE(xleft); + vtx->y = YSCALE(yh); + vtx->z = ZSCALE(z+dzdx*dx); + vtx->w = WSCALE(w+dwdx*dx); + vtx = &vtxbuf[nbVtxs++]; + } + if ((!flip/* && xleft < xright*/) || + (flip && xleft > xright)) + { + if (shade) { + vtx->r = CSCALE(r); + vtx->g = CSCALE(g); + vtx->b = CSCALE(b); + vtx->a = CSCALE(a); + } + if (texture) { + vtx->ou = SSCALE(s, w); + vtx->ov = TSCALE(t, w); + } + vtx->x = XSCALE(xright); + vtx->y = YSCALE(yh); + vtx->z = ZSCALE(z); + vtx->w = WSCALE(w); + vtx = &vtxbuf[nbVtxs++]; + } + xleft += xleft_inc*j; xright += xright_inc*j; + s += dsde*j; t += dtde*j; + if (w + dwde*j) w += dwde*j; + else w += dwde*(j-1); + r += drde*j; g += dgde*j; b += dbde*j; a += dade*j; + z += dzde*j; + // render ... + } + + if (xl != xh) + xleft = xl; + + //if (yl-ym > 0) + { + int dx = (xleft-xright)>>16; + if ((!flip && xleft <= xright) || + (flip/* && xleft >= xright*/)) + { + if (shade) { + vtx->r = CSCALE(r+drdx*dx); + vtx->g = CSCALE(g+dgdx*dx); + vtx->b = CSCALE(b+dbdx*dx); + vtx->a = CSCALE(a+dadx*dx); + } + if (texture) { + vtx->ou = SSCALE(s+dsdx*dx, w+dwdx*dx); + vtx->ov = TSCALE(t+dtdx*dx, w+dwdx*dx); + } + vtx->x = XSCALE(xleft); + vtx->y = YSCALE(ym); + vtx->z = ZSCALE(z+dzdx*dx); + vtx->w = WSCALE(w+dwdx*dx); + vtx = &vtxbuf[nbVtxs++]; + } + if ((!flip/* && xleft <= xright*/) || + (flip && xleft >= xright)) + { + if (shade) { + vtx->r = CSCALE(r); + vtx->g = CSCALE(g); + vtx->b = CSCALE(b); + vtx->a = CSCALE(a); + } + if (texture) { + vtx->ou = SSCALE(s, w); + vtx->ov = TSCALE(t, w); + } + vtx->x = XSCALE(xright); + vtx->y = YSCALE(ym); + vtx->z = ZSCALE(z); + vtx->w = WSCALE(w); + vtx = &vtxbuf[nbVtxs++]; + } + } + xleft_inc = dxldy; + xright_inc = dxhdy; + + j = yl-ym; + //j--; // ? + xleft += xleft_inc*j; xright += xright_inc*j; + s += dsde*j; t += dtde*j; w += dwde*j; + r += drde*j; g += dgde*j; b += dbde*j; a += dade*j; + z += dzde*j; + + while (yl>ym && + !((!flip && xleft < xright+0x10000) || + (flip && xleft > xright-0x10000))) { + xleft -= xleft_inc; xright -= xright_inc; + s -= dsde; t -= dtde; w -= dwde; + r -= drde; g -= dgde; b -= dbde; a -= dade; + z -= dzde; + j--; + yl--; + } + + // render ... + if (j >= 0) { + int dx = (xleft-xright)>>16; + if ((!flip && xleft <= xright) || + (flip/* && xleft >= xright*/)) + { + if (shade) { + vtx->r = CSCALE(r+drdx*dx); + vtx->g = CSCALE(g+dgdx*dx); + vtx->b = CSCALE(b+dbdx*dx); + vtx->a = CSCALE(a+dadx*dx); + } + if (texture) { + vtx->ou = SSCALE(s+dsdx*dx, w+dwdx*dx); + vtx->ov = TSCALE(t+dtdx*dx, w+dwdx*dx); + } + vtx->x = XSCALE(xleft); + vtx->y = YSCALE(yl); + vtx->z = ZSCALE(z+dzdx*dx); + vtx->w = WSCALE(w+dwdx*dx); + vtx = &vtxbuf[nbVtxs++]; + } + if ((!flip/* && xleft <= xright*/) || + (flip && xleft >= xright)) + { + if (shade) { + vtx->r = CSCALE(r); + vtx->g = CSCALE(g); + vtx->b = CSCALE(b); + vtx->a = CSCALE(a); + } + if (texture) { + vtx->ou = SSCALE(s, w); + vtx->ov = TSCALE(t, w); + } + vtx->x = XSCALE(xright); + vtx->y = YSCALE(yl); + vtx->z = ZSCALE(z); + vtx->w = WSCALE(w); + vtx = &vtxbuf[nbVtxs++]; + } + } + + if (fullscreen) + { + update (); + for (int k = 0; k < nbVtxs-1; k++) + { + VERTEX * v = &vtxbuf[k]; + v->x = v->x * rdp.scale_x + rdp.offset_x; + v->y = v->y * rdp.scale_y + rdp.offset_y; + // v->z = 1.0f;///v->w; + v->q = 1.0f/v->w; + v->u1 = v->u0 = v->ou; + v->v1 = v->v0 = v->ov; + if (rdp.tex >= 1 && rdp.cur_cache[0]) + { + if (rdp.tiles[rdp.cur_tile].shift_s) + { + if (rdp.tiles[rdp.cur_tile].shift_s > 10) + v->u0 *= (float)(1 << (16 - rdp.tiles[rdp.cur_tile].shift_s)); + else + v->u0 /= (float)(1 << rdp.tiles[rdp.cur_tile].shift_s); + } + if (rdp.tiles[rdp.cur_tile].shift_t) + { + if (rdp.tiles[rdp.cur_tile].shift_t > 10) + v->v0 *= (float)(1 << (16 - rdp.tiles[rdp.cur_tile].shift_t)); + else + v->v0 /= (float)(1 << rdp.tiles[rdp.cur_tile].shift_t); + } + + v->u0 -= rdp.tiles[rdp.cur_tile].f_ul_s; + v->v0 -= rdp.tiles[rdp.cur_tile].f_ul_t; + v->u0 = rdp.cur_cache[0]->c_off + rdp.cur_cache[0]->c_scl_x * v->u0; + v->v0 = rdp.cur_cache[0]->c_off + rdp.cur_cache[0]->c_scl_y * v->v0; + v->u0 /= v->w; + v->v0 /= v->w; + } + + if (rdp.tex >= 2 && rdp.cur_cache[1]) + { + if (rdp.tiles[rdp.cur_tile+1].shift_s) + { + if (rdp.tiles[rdp.cur_tile+1].shift_s > 10) + v->u1 *= (float)(1 << (16 - rdp.tiles[rdp.cur_tile+1].shift_s)); + else + v->u1 /= (float)(1 << rdp.tiles[rdp.cur_tile+1].shift_s); + } + if (rdp.tiles[rdp.cur_tile+1].shift_t) + { + if (rdp.tiles[rdp.cur_tile+1].shift_t > 10) + v->v1 *= (float)(1 << (16 - rdp.tiles[rdp.cur_tile+1].shift_t)); + else + v->v1 /= (float)(1 << rdp.tiles[rdp.cur_tile+1].shift_t); + } + + v->u1 -= rdp.tiles[rdp.cur_tile+1].f_ul_s; + v->v1 -= rdp.tiles[rdp.cur_tile+1].f_ul_t; + v->u1 = rdp.cur_cache[1]->c_off + rdp.cur_cache[1]->c_scl_x * v->u1; + v->v1 = rdp.cur_cache[1]->c_off + rdp.cur_cache[1]->c_scl_y * v->v1; + v->u1 /= v->w; + v->v1 /= v->w; + } + apply_shade_mods (v); + } + ConvertCoordsConvert (vtxbuf, nbVtxs); + grCullMode (GR_CULL_DISABLE); + grDrawVertexArrayContiguous (GR_TRIANGLE_STRIP, nbVtxs-1, vtxbuf, sizeof(VERTEX)); + if (_debugger.capture) + { + VERTEX vl[3]; + vl[0] = vtxbuf[0]; + vl[1] = vtxbuf[2]; + vl[2] = vtxbuf[1]; + add_tri (vl, 3, TRI_TRIANGLE); + rdp.tri_n++; + if (nbVtxs > 4) + { + vl[0] = vtxbuf[2]; + vl[1] = vtxbuf[3]; + vl[2] = vtxbuf[1]; + add_tri (vl, 3, TRI_TRIANGLE); + rdp.tri_n++; + } + } + } +} + +static void rdp_triangle(int shade, int texture, int zbuffer) +{ + lle_triangle(rdp.cmd0, rdp.cmd1, shade, texture, zbuffer, rdp_cmd_data + rdp_cmd_cur); +} + +static void rdp_trifill() +{ + rdp_triangle(0, 0, 0); + LRDP("trifill\n"); +} + +static void rdp_trishade() +{ + rdp_triangle(1, 0, 0); + LRDP("trishade\n"); +} + +static void rdp_tritxtr() +{ + rdp_triangle(0, 1, 0); + LRDP("tritxtr\n"); +} + +static void rdp_trishadetxtr() +{ + rdp_triangle(1, 1, 0); + LRDP("trishadetxtr\n"); +} + +static void rdp_trifillz() +{ + rdp_triangle(0, 0, 1); + LRDP("trifillz\n"); +} + +static void rdp_trishadez() +{ + rdp_triangle(1, 0, 1); + LRDP("trishadez\n"); +} + +static void rdp_tritxtrz() +{ + rdp_triangle(0, 1, 1); + LRDP("tritxtrz\n"); +} + +static void rdp_trishadetxtrz() +{ + rdp_triangle(1, 1, 1); + LRDP("trishadetxtrz\n"); +} + + +static rdp_instr rdp_command_table[64] = +{ + /* 0x00 */ + spnoop, undef, undef, undef, + undef, undef, undef, undef, + rdp_trifill, rdp_trifillz, rdp_tritxtr, rdp_tritxtrz, + rdp_trishade, rdp_trishadez, rdp_trishadetxtr, rdp_trishadetxtrz, + /* 0x10 */ + undef, undef, undef, undef, + undef, undef, undef, undef, + undef, undef, undef, undef, + undef, undef, undef, undef, + /* 0x20 */ + undef, undef, undef, undef, + rdp_texrect, rdp_texrect, rdp_loadsync, rdp_pipesync, + rdp_tilesync, rdp_fullsync, rdp_setkeygb, rdp_setkeyr, + rdp_setconvert, rdp_setscissor, rdp_setprimdepth, rdp_setothermode, + /* 0x30 */ + rdp_loadtlut, undef, rdp_settilesize, rdp_loadblock, + rdp_loadtile, rdp_settile, rdp_fillrect, rdp_setfillcolor, + rdp_setfogcolor, rdp_setblendcolor, rdp_setprimcolor, rdp_setenvcolor, + rdp_setcombine, rdp_settextureimage, rdp_setdepthimage, rdp_setcolorimage +}; + +static const wxUint32 rdp_command_length[64] = +{ + 8, // 0x00, No Op + 8, // 0x01, ??? + 8, // 0x02, ??? + 8, // 0x03, ??? + 8, // 0x04, ??? + 8, // 0x05, ??? + 8, // 0x06, ??? + 8, // 0x07, ??? + 32, // 0x08, Non-Shaded Triangle + 32+16, // 0x09, Non-Shaded, Z-Buffered Triangle + 32+64, // 0x0a, Textured Triangle + 32+64+16, // 0x0b, Textured, Z-Buffered Triangle + 32+64, // 0x0c, Shaded Triangle + 32+64+16, // 0x0d, Shaded, Z-Buffered Triangle + 32+64+64, // 0x0e, Shaded+Textured Triangle + 32+64+64+16,// 0x0f, Shaded+Textured, Z-Buffered Triangle + 8, // 0x10, ??? + 8, // 0x11, ??? + 8, // 0x12, ??? + 8, // 0x13, ??? + 8, // 0x14, ??? + 8, // 0x15, ??? + 8, // 0x16, ??? + 8, // 0x17, ??? + 8, // 0x18, ??? + 8, // 0x19, ??? + 8, // 0x1a, ??? + 8, // 0x1b, ??? + 8, // 0x1c, ??? + 8, // 0x1d, ??? + 8, // 0x1e, ??? + 8, // 0x1f, ??? + 8, // 0x20, ??? + 8, // 0x21, ??? + 8, // 0x22, ??? + 8, // 0x23, ??? + 16, // 0x24, Texture_Rectangle + 16, // 0x25, Texture_Rectangle_Flip + 8, // 0x26, Sync_Load + 8, // 0x27, Sync_Pipe + 8, // 0x28, Sync_Tile + 8, // 0x29, Sync_Full + 8, // 0x2a, Set_Key_GB + 8, // 0x2b, Set_Key_R + 8, // 0x2c, Set_Convert + 8, // 0x2d, Set_Scissor + 8, // 0x2e, Set_Prim_Depth + 8, // 0x2f, Set_Other_Modes + 8, // 0x30, Load_TLUT + 8, // 0x31, ??? + 8, // 0x32, Set_Tile_Size + 8, // 0x33, Load_Block + 8, // 0x34, Load_Tile + 8, // 0x35, Set_Tile + 8, // 0x36, Fill_Rectangle + 8, // 0x37, Set_Fill_Color + 8, // 0x38, Set_Fog_Color + 8, // 0x39, Set_Blend_Color + 8, // 0x3a, Set_Prim_Color + 8, // 0x3b, Set_Env_Color + 8, // 0x3c, Set_Combine + 8, // 0x3d, Set_Texture_Image + 8, // 0x3e, Set_Mask_Image + 8 // 0x3f, Set_Color_Image +}; + +#define rdram ((wxUint32*)gfx.RDRAM) +#define rsp_dmem ((wxUint32*)gfx.DMEM) + +#define dp_start (*(wxUint32*)gfx.DPC_START_REG) +#define dp_end (*(wxUint32*)gfx.DPC_END_REG) +#define dp_current (*(wxUint32*)gfx.DPC_CURRENT_REG) +#define dp_status (*(wxUint32*)gfx.DPC_STATUS_REG) + +inline wxUint32 READ_RDP_DATA(wxUint32 address) +{ + if (dp_status & 0x1) // XBUS_DMEM_DMA enabled + return rsp_dmem[(address & 0xfff)>>2]; + else + return rdram[address>>2]; +} + +static void rdphalf_1() +{ + wxUint32 cmd = rdp.cmd1 >> 24; + if (cmd >= 0xc8 && cmd <=0xcf) //triangle command + { + LRDP("rdphalf_1 - lle triangle\n"); + rdp_cmd_ptr = 0; + rdp_cmd_cur = 0; + wxUint32 a; + + do + { + rdp_cmd_data[rdp_cmd_ptr++] = rdp.cmd1; + // check DL counter + if (rdp.dl_count != -1) + { + rdp.dl_count --; + if (rdp.dl_count == 0) + { + rdp.dl_count = -1; + + LRDP("End of DL\n"); + rdp.pc_i --; + } + } + + // Get the address of the next command + a = rdp.pc[rdp.pc_i] & BMASK; + + // Load the next command and its input + rdp.cmd0 = ((wxUint32*)gfx.RDRAM)[a>>2]; // \ Current command, 64 bit + rdp.cmd1 = ((wxUint32*)gfx.RDRAM)[(a>>2)+1]; // / + + // Go to the next instruction + rdp.pc[rdp.pc_i] = (a+8) & BMASK; + + }while ((rdp.cmd0 >> 24) != 0xb3); + rdp_cmd_data[rdp_cmd_ptr++] = rdp.cmd1; + cmd = (rdp_cmd_data[rdp_cmd_cur] >> 24) & 0x3f; + rdp.cmd0 = rdp_cmd_data[rdp_cmd_cur+0]; + rdp.cmd1 = rdp_cmd_data[rdp_cmd_cur+1]; + /* + wxUint32 cmd3 = ((wxUint32*)gfx.RDRAM)[(a>>2)+2]; + if ((cmd3>>24) == 0xb4) + rglSingleTriangle = TRUE; + else + rglSingleTriangle = FALSE; + */ + rdp_command_table[cmd](); + } + else + { + LRDP("rdphalf_1 - IGNORED\n"); + } +} + +static void rdphalf_2() +{ + RDP_E("rdphalf_2 - IGNORED\n"); + LRDP("rdphalf_2 - IGNORED\n"); +} + +static void rdphalf_cont() +{ + RDP_E("rdphalf_cont - IGNORED\n"); + LRDP("rdphalf_cont - IGNORED\n"); +} + +/****************************************************************** +Function: ProcessRDPList +Purpose: This function is called when there is a Dlist to be +processed. (Low level GFX list) +input: none +output: none +*******************************************************************/ +#ifdef __cplusplus +extern "C" { +#endif +EXPORT void CALL ProcessRDPList(void) +{ + LOG ("ProcessRDPList ()\n"); + LRDP("ProcessRDPList ()\n"); + + SoftLocker lock(mutexProcessDList); + if (!lock.IsOk()) //mutex is busy + { + if (!fullscreen) + drawNoFullscreenMessage(); + // Set an interrupt to allow the game to continue + *gfx.MI_INTR_REG |= 0x20; + gfx.CheckInterrupts(); + return; + } + + wxUint32 i; + wxUint32 cmd, length, cmd_length; + rdp_cmd_ptr = 0; + rdp_cmd_cur = 0; + + if (dp_end <= dp_current) return; + length = dp_end - dp_current; + + // load command data + for (i=0; i < length; i += 4) + { + rdp_cmd_data[rdp_cmd_ptr++] = READ_RDP_DATA(dp_current + i); + if (rdp_cmd_ptr >= 0x1000) + { + FRDP("rdp_process_list: rdp_cmd_ptr overflow %x %x --> %x\n", length, dp_current, dp_end); + } + } + + dp_current = dp_end; + + cmd = (rdp_cmd_data[0] >> 24) & 0x3f; + cmd_length = (rdp_cmd_ptr + 1) * 4; + + // check if more data is needed + if (cmd_length < rdp_command_length[cmd]) + return; + rdp.LLE = TRUE; + while (rdp_cmd_cur < rdp_cmd_ptr) + { + cmd = (rdp_cmd_data[rdp_cmd_cur] >> 24) & 0x3f; + + if (((rdp_cmd_ptr-rdp_cmd_cur) * 4) < rdp_command_length[cmd]) + return; + + // execute the command + rdp.cmd0 = rdp_cmd_data[rdp_cmd_cur+0]; + rdp.cmd1 = rdp_cmd_data[rdp_cmd_cur+1]; + rdp.cmd2 = rdp_cmd_data[rdp_cmd_cur+2]; + rdp.cmd3 = rdp_cmd_data[rdp_cmd_cur+3]; + rdp_command_table[cmd](); + + rdp_cmd_cur += rdp_command_length[cmd] / 4; + }; + rdp.LLE = FALSE; + + dp_start = dp_end; + + dp_status &= ~0x0002; + + //} +} + +#ifdef __cplusplus +} +#endif +