/* * Copyright (C) 2007-2009 Gabest * http://www.gabest.org * * 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, or (at your option) * 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 GNU Make; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA USA. * http://www.gnu.org/copyleft/gpl.html * */ #include "stdafx.h" #include "GSRenderer.h" #if defined(__unix__) #include #endif const unsigned int s_interlace_nb = 8; const unsigned int s_post_shader_nb = 5; const unsigned int s_aspect_ratio_nb = 3; const unsigned int s_mipmap_nb = 3; GSRenderer::GSRenderer() : m_shader(0) , m_shift_key(false) , m_control_key(false) , m_framelimit(false) , m_texture_shuffle(false) , m_real_size(0,0) , m_wnd() , m_dev(NULL) { m_GStitleInfoBuffer[0] = 0; m_interlace = theApp.GetConfigI("interlace") % s_interlace_nb; m_aspectratio = theApp.GetConfigI("AspectRatio") % s_aspect_ratio_nb; m_shader = theApp.GetConfigI("TVShader") % s_post_shader_nb; m_vsync = theApp.GetConfigB("vsync"); m_aa1 = theApp.GetConfigB("aa1"); m_fxaa = theApp.GetConfigB("fxaa"); m_shaderfx = theApp.GetConfigB("shaderfx"); m_shadeboost = theApp.GetConfigB("ShadeBoost"); } GSRenderer::~GSRenderer() { /*if(m_dev) { m_dev->Reset(1, 1, GSDevice::Windowed); }*/ delete m_dev; } bool GSRenderer::CreateDevice(GSDevice* dev) { ASSERT(dev); ASSERT(!m_dev); if(!dev->Create(m_wnd)) { return false; } m_dev = dev; m_dev->SetVSync(m_vsync && m_framelimit); return true; } void GSRenderer::ResetDevice() { if(m_dev) m_dev->Reset(1, 1); } bool GSRenderer::Merge(int field) { bool en[2]; GSVector4i fr[2]; GSVector4i dr[2]; GSVector2i display_baseline = { INT_MAX, INT_MAX }; GSVector2i frame_baseline = { INT_MAX, INT_MAX }; for(int i = 0; i < 2; i++) { en[i] = IsEnabled(i); if(en[i]) { fr[i] = GetFrameRect(i); dr[i] = GetDisplayRect(i); display_baseline.x = min(dr[i].left, display_baseline.x); display_baseline.y = min(dr[i].top, display_baseline.y); frame_baseline.x = min(fr[i].left, frame_baseline.x); frame_baseline.y = min(fr[i].top, frame_baseline.y); //printf("[%d]: %d %d %d %d, %d %d %d %d\n", i, fr[i].x,fr[i].y,fr[i].z,fr[i].w , dr[i].x,dr[i].y,dr[i].z,dr[i].w); } } if(!en[0] && !en[1]) { return false; } GL_PUSH("Renderer Merge %d (0: enabled %d 0x%x, 1: enabled %d 0x%x)", s_n, en[0], m_regs->DISP[0].DISPFB.Block(), en[1], m_regs->DISP[1].DISPFB.Block()); // try to avoid fullscreen blur, could be nice on tv but on a monitor it's like double vision, hurts my eyes (persona 4, guitar hero) // // NOTE: probably the technique explained in graphtip.pdf (Antialiasing by Supersampling / 4. Reading Odd/Even Scan Lines Separately with the PCRTC then Blending) bool samesrc = en[0] && en[1] && m_regs->DISP[0].DISPFB.FBP == m_regs->DISP[1].DISPFB.FBP && m_regs->DISP[0].DISPFB.FBW == m_regs->DISP[1].DISPFB.FBW && m_regs->DISP[0].DISPFB.PSM == m_regs->DISP[1].DISPFB.PSM; if(samesrc /*&& m_regs->PMODE.SLBG == 0 && m_regs->PMODE.MMOD == 1 && m_regs->PMODE.ALP == 0x80*/) { // persona 4: // // fr[0] = 0 0 640 448 // fr[1] = 0 1 640 448 // dr[0] = 159 50 779 498 // dr[1] = 159 50 779 497 // // second image shifted up by 1 pixel and blended over itself // // god of war: // // fr[0] = 0 1 512 448 // fr[1] = 0 0 512 448 // dr[0] = 127 50 639 497 // dr[1] = 127 50 639 498 // // same just the first image shifted // // These kinds of cases are now fixed by the more generic frame_diff code below, as the code here was too specific and has become obsolete. // NOTE: Persona 4 and God Of War are not rare exceptions, many games have the same(or very similar) offsets. int topDiff = fr[0].top - fr[1].top; if (dr[0].eq(dr[1]) && (fr[0].eq(fr[1] + GSVector4i(0, topDiff, 0, topDiff)) || fr[1].eq(fr[0] + GSVector4i(0, topDiff, 0, topDiff)))) { // dq5: // // fr[0] = 0 1 512 445 // fr[1] = 0 0 512 444 // dr[0] = 127 50 639 494 // dr[1] = 127 50 639 494 int top = min(fr[0].top, fr[1].top); int bottom = min(fr[0].bottom, fr[1].bottom); fr[0].top = fr[1].top = top; fr[0].bottom = fr[1].bottom = bottom; } } GSVector2i fs(0, 0); GSVector2i ds(0, 0); GSTexture* tex[3] = {NULL, NULL, NULL}; int y_offset[3] = {0, 0, 0}; s_n++; bool feedback_merge = m_regs->EXTWRITE.WRITE == 1; if(samesrc && fr[0].bottom == fr[1].bottom && !feedback_merge) { tex[0] = GetOutput(0, y_offset[0]); tex[1] = tex[0]; // saves one texture fetch y_offset[1] = y_offset[0]; } else { if(en[0]) tex[0] = GetOutput(0, y_offset[0]); if(en[1]) tex[1] = GetOutput(1, y_offset[1]); if(feedback_merge) tex[2] = GetFeedbackOutput(); } GSVector4 src[2]; GSVector4 src_hw[2]; GSVector4 dst[2]; for(int i = 0; i < 2; i++) { if(!en[i] || !tex[i]) continue; GSVector4i r = fr[i]; GSVector4 scale = GSVector4(tex[i]->GetScale()).xyxy(); src[i] = GSVector4(r) * scale / GSVector4(tex[i]->GetSize()).xyxy(); src_hw[i] = (GSVector4(r) + GSVector4 (0, y_offset[i], 0, y_offset[i])) * scale / GSVector4(tex[i]->GetSize()).xyxy(); GSVector2 off(0); GSVector2i display_diff(dr[i].left - display_baseline.x, dr[i].top - display_baseline.y); GSVector2i frame_diff(fr[i].left - frame_baseline.x, fr[i].top - frame_baseline.y); // Time Crisis 2/3 uses two side by side images when in split screen mode. // Though ignore cases where baseline and display rectangle offsets only differ by 1 pixel, causes blurring and wrong resolution output on FFXII if(display_diff.x > 2) { off.x = tex[i]->GetScale().x * display_diff.x; } // If the DX offset is too small then consider the status of frame memory offsets, prevents blurring on Tenchu: Fatal Shadows, Worms 3D else if(display_diff.x != frame_diff.x) { off.x = tex[i]->GetScale().x * frame_diff.x; } if(display_diff.y >= 4) // Shouldn't this be >= 2? { off.y = tex[i]->GetScale().y * display_diff.y; if(m_regs->SMODE2.INT && m_regs->SMODE2.FFMD) { off.y /= 2; } } else if(display_diff.y != frame_diff.y) { off.y = tex[i]->GetScale().y * frame_diff.y; } dst[i] = GSVector4(off).xyxy() + scale * GSVector4(r.rsize()); fs.x = max(fs.x, (int)(dst[i].z + 0.5f)); fs.y = max(fs.y, (int)(dst[i].w + 0.5f)); } ds = fs; if(m_regs->SMODE2.INT && m_regs->SMODE2.FFMD) { ds.y *= 2; } m_real_size = ds; bool slbg = m_regs->PMODE.SLBG; if(tex[0] || tex[1]) { if(tex[0] == tex[1] && !slbg && (src[0] == src[1] & dst[0] == dst[1]).alltrue()) { // the two outputs are identical, skip drawing one of them (the one that is alpha blended) tex[0] = NULL; } GSVector4 c = GSVector4((int)m_regs->BGCOLOR.R, (int)m_regs->BGCOLOR.G, (int)m_regs->BGCOLOR.B, (int)m_regs->PMODE.ALP) / 255; m_dev->Merge(tex, src_hw, dst, fs, m_regs->PMODE, m_regs->EXTBUF, c); if(m_regs->SMODE2.INT && m_interlace > 0) { if(m_interlace == 7 && m_regs->SMODE2.FFMD) // Auto interlace enabled / Odd frame interlace setting { int field2 = 0; int mode = 2; m_dev->Interlace(ds, field ^ field2, mode, tex[1] ? tex[1]->GetScale().y : tex[0]->GetScale().y); } else { int field2 = 1 - ((m_interlace - 1) & 1); int mode = (m_interlace - 1) >> 1; m_dev->Interlace(ds, field ^ field2, mode, tex[1] ? tex[1]->GetScale().y : tex[0]->GetScale().y); } } if(m_shadeboost) { m_dev->ShadeBoost(); } if(m_shaderfx) { m_dev->ExternalFX(); } if(m_fxaa) { m_dev->FXAA(); } } return true; } GSVector2i GSRenderer::GetInternalResolution() { return m_real_size; } void GSRenderer::SetFrameLimit(bool limit) { m_framelimit = limit; if(m_dev) m_dev->SetVSync(m_vsync && m_framelimit); } void GSRenderer::SetVSync(bool enabled) { m_vsync = enabled; if(m_dev) m_dev->SetVSync(m_vsync); } void GSRenderer::VSync(int field) { GSPerfMonAutoTimer pmat(&m_perfmon); m_perfmon.Put(GSPerfMon::Frame); Flush(); if(s_dump && s_n >= s_saven) { m_regs->Dump(root_sw + format("%05d_f%lld_gs_reg.txt", s_n, m_perfmon.GetFrame())); } if(!m_dev->IsLost(true)) { if(!Merge(field ? 1 : 0)) { return; } } else { ResetDevice(); } m_dev->AgePool(); // osd if((m_perfmon.GetFrame() & 0x1f) == 0) { m_perfmon.Update(); double fps = 1000.0f / m_perfmon.Get(GSPerfMon::Frame); string s; #ifdef GSTITLEINFO_API_FORCE_VERBOSE if(1)//force verbose reply #else if(m_wnd->IsManaged()) #endif { //GSdx owns the window's title, be verbose. string s2 = m_regs->SMODE2.INT ? (string("Interlaced ") + (m_regs->SMODE2.FFMD ? "(frame)" : "(field)")) : "Progressive"; s = format( "%lld | %d x %d | %.2f fps (%d%%) | %s - %s | %s | %d S/%d P/%d D | %d%% CPU | %.2f | %.2f", m_perfmon.GetFrame(), GetInternalResolution().x, GetInternalResolution().y, fps, (int)(100.0 * fps / GetTvRefreshRate()), s2.c_str(), theApp.m_gs_interlace[m_interlace].name.c_str(), theApp.m_gs_aspectratio[m_aspectratio].name.c_str(), (int)m_perfmon.Get(GSPerfMon::SyncPoint), (int)m_perfmon.Get(GSPerfMon::Prim), (int)m_perfmon.Get(GSPerfMon::Draw), m_perfmon.CPU(), m_perfmon.Get(GSPerfMon::Swizzle) / 1024, m_perfmon.Get(GSPerfMon::Unswizzle) / 1024 ); double fillrate = m_perfmon.Get(GSPerfMon::Fillrate); if(fillrate > 0) { s += format(" | %.2f mpps", fps * fillrate / (1024 * 1024)); int sum = 0; for(int i = 0; i < 16; i++) { sum += m_perfmon.CPU(GSPerfMon::WorkerDraw0 + i); } s += format(" | %d%% CPU", sum); } } else { // Satisfy PCSX2's request for title info: minimal verbosity due to more external title text s = format("%dx%d | %s", GetInternalResolution().x, GetInternalResolution().y, theApp.m_gs_interlace[m_interlace].name.c_str()); } if(m_capture.IsCapturing()) { s += " | Recording..."; } if(m_wnd->IsManaged()) { m_wnd->SetWindowText(s.c_str()); } else { // note: do not use TryEnterCriticalSection. It is unnecessary code complication in // an area that absolutely does not matter (even if it were 100 times slower, it wouldn't // be noticeable). Besides, these locks are extremely short -- overhead of conditional // is way more expensive than just waiting for the CriticalSection in 1 of 10,000,000 tries. --air std::lock_guard lock(m_pGSsetTitle_Crit); strncpy(m_GStitleInfoBuffer, s.c_str(), countof(m_GStitleInfoBuffer) - 1); m_GStitleInfoBuffer[sizeof(m_GStitleInfoBuffer) - 1] = 0; // make sure null terminated even if text overflows } } else { // [TODO] // We don't have window title rights, or the window has no title, // so let's use actual OSD! } if(m_frameskip) { return; } // present #if 0 // This will scale the OSD to the PS2's output resolution. // Will be affected by 2x, 4x, etc scaling. m_dev->m_osd.m_real_size = m_real_size #elif 0 // This will scale the OSD to the window's size. // Will maintiain the font size no matter what size the window is. GSVector4i window_size = m_wnd->GetClientRect(); m_dev->m_osd.m_real_size.x = window_size.v[2]; m_dev->m_osd.m_real_size.y = window_size.v[3]; #else // This will scale the OSD to the native resolution. // Will size font relative to the window's size. // TODO this should probably be done with native calls m_dev->m_osd.m_real_size.x = 1024; m_dev->m_osd.m_real_size.y = 768; #endif m_dev->Present(m_wnd->GetClientRect().fit(m_aspectratio), m_shader); // snapshot if(!m_snapshot.empty()) { bool shift = false; #ifdef _WIN32 shift = !!(::GetAsyncKeyState(VK_SHIFT) & 0x8000); #else shift = m_shift_key; #endif if(!m_dump && shift) { GSFreezeData fd; fd.size = 0; fd.data = NULL; Freeze(&fd, true); fd.data = new uint8[fd.size]; Freeze(&fd, false); m_dump.Open(m_snapshot, m_crc, fd, m_regs); delete [] fd.data; } if(GSTexture* t = m_dev->GetCurrent()) { t->Save(m_snapshot + ".bmp"); } m_snapshot.clear(); } else { if(m_dump) { bool control = false; #ifdef _WIN32 control = !!(::GetAsyncKeyState(VK_CONTROL) & 0x8000); #else control = m_control_key; #endif m_dump.VSync(field, !control, m_regs); } } // capture if(m_capture.IsCapturing()) { if(GSTexture* current = m_dev->GetCurrent()) { GSVector2i size = m_capture.GetSize(); if(GSTexture* offscreen = m_dev->CopyOffscreen(current, GSVector4(0, 0, 1, 1), size.x, size.y)) { GSTexture::GSMap m; if(offscreen->Map(m)) { m_capture.DeliverFrame(m.bits, m.pitch, !m_dev->IsRBSwapped()); offscreen->Unmap(); } m_dev->Recycle(offscreen); } } } } bool GSRenderer::MakeSnapshot(const string& path) { if(m_snapshot.empty()) { time_t cur_time = time(nullptr); static time_t prev_snap; // The variable 'n' is used for labelling the screenshots when multiple screenshots are taken in // a single second, we'll start using this variable for naming when a second screenshot request is detected // at the same time as the first one. Hence, we're initially setting this counter to 2 to imply that // the captured image is the 2nd image captured at this specific time. static int n = 2; char local_time[16]; if (strftime(local_time, sizeof(local_time), "%Y%m%d%H%M%S", localtime(&cur_time))) { if (cur_time == prev_snap) { m_snapshot = format("%s_%s_(%d)", path.c_str(), local_time, n++); } else { n = 2; m_snapshot = format("%s_%s", path.c_str(), local_time); } prev_snap = cur_time; } } return true; } bool GSRenderer::BeginCapture() { GSVector4i disp = m_wnd->GetClientRect().fit(m_aspectratio); float aspect = (float)disp.width() / max(1, disp.height()); return m_capture.BeginCapture(GetTvRefreshRate(), GetInternalResolution(), aspect); } void GSRenderer::EndCapture() { m_capture.EndCapture(); } void GSRenderer::KeyEvent(GSKeyEventData* e) { if(e->type == KEYPRESS) { #ifdef _WIN32 int step = (::GetAsyncKeyState(VK_SHIFT) & 0x8000) ? -1 : 1; #elif defined(__unix__) int step = m_shift_key ? -1 : 1; #define VK_F5 XK_F5 #define VK_F6 XK_F6 #define VK_F7 XK_F7 #define VK_DELETE XK_Delete #define VK_INSERT XK_Insert #define VK_PRIOR XK_Prior #define VK_HOME XK_Home #endif switch(e->key) { case VK_F5: m_interlace = (m_interlace + s_interlace_nb + step) % s_interlace_nb; printf("GSdx: Set deinterlace mode to %d (%s).\n", (int)m_interlace, theApp.m_gs_interlace.at(m_interlace).name.c_str()); return; case VK_F6: if( m_wnd->IsManaged() ) m_aspectratio = (m_aspectratio + s_aspect_ratio_nb + step) % s_aspect_ratio_nb; return; case VK_F7: m_shader = (m_shader + s_post_shader_nb + step) % s_post_shader_nb; printf("GSdx: Set shader to: %d.\n", (int)m_shader); return; case VK_DELETE: m_aa1 = !m_aa1; printf("GSdx: (Software) Edge anti-aliasing is now %s.\n", m_aa1 ? "enabled" : "disabled"); return; case VK_INSERT: m_mipmap = (m_mipmap + s_mipmap_nb + step) % s_mipmap_nb; printf("GSdx: Mipmapping is now %s.\n", theApp.m_gs_hack.at(m_mipmap).name.c_str()); return; case VK_PRIOR: m_fxaa = !m_fxaa; printf("GSdx: FXAA anti-aliasing is now %s.\n", m_fxaa ? "enabled" : "disabled"); return; case VK_HOME: m_shaderfx = !m_shaderfx; printf("GSdx: External post-processing is now %s.\n", m_shaderfx ? "enabled" : "disabled"); return; } } #if defined(__unix__) switch(e->key) { case XK_Shift_L: case XK_Shift_R: m_shift_key = (e->type == KEYPRESS); return; case XK_Control_L: case XK_Control_R: m_control_key = (e->type == KEYPRESS); return; } #endif } void GSRenderer::PurgePool() { m_dev->PurgePool(); }