Files
pcsx2/plugins/GSdx/GSRenderer.cpp
T
Avi Halachmi (:avih) 54afd377ee GSdx: recording: Progressive: make the recorded clip play at full speed
Before this patch, when recording Progressive (frame) mode, it recorded all
the frames correctly but set the clip's fps property to 25/29.97, so when
played back it played at half the speed (but was fine when played at double speed).

This patch does not affect the number of frames recorded per second, but rather
only sets the resulting clip fps property to the correct one (double than before).

Also fixes a bug that in a non-managed window in progressive mode, the title
displayed "200%" speed when it should have displayed 100% speed.

Fixes #832
2015-09-19 14:44:48 +03:00

652 lines
14 KiB
C++

/*
* 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"
#ifdef __linux__
#include <X11/keysym.h>
#endif
GSRenderer::GSRenderer()
: m_shader(0)
, m_shift_key(false)
, m_control_key(false)
, m_wnd(NULL)
, m_dev(NULL)
{
m_GStitleInfoBuffer[0] = 0;
m_interlace = theApp.GetConfig("interlace", 7);
m_aspectratio = theApp.GetConfig("aspectratio", 1);
m_shader = theApp.GetConfig("TVShader", 0);
m_filter = theApp.GetConfig("filter", 1);
m_vsync = !!theApp.GetConfig("vsync", 0);
m_aa1 = !!theApp.GetConfig("aa1", 0);
m_fxaa = !!theApp.GetConfig("fxaa", 0);
m_shaderfx = !!theApp.GetConfig("shaderfx", 0);
m_shadeboost = !!theApp.GetConfig("ShadeBoost", 0);
}
GSRenderer::~GSRenderer()
{
/*if(m_dev)
{
m_dev->Reset(1, 1, GSDevice::Windowed);
}*/
delete m_dev;
if (m_wnd)
{
delete m_wnd;
}
}
bool GSRenderer::CreateWnd(const string& title, int w, int h)
{
return m_wnd->Create(title.c_str(), w, h);
}
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];
int baseline = INT_MAX;
for(int i = 0; i < 2; i++)
{
en[i] = IsEnabled(i);
if(en[i])
{
fr[i] = GetFrameRect(i);
dr[i] = GetDisplayRect(i);
baseline = min(dr[i].top, baseline);
//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");
// 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;
// bool blurdetected = false;
if(samesrc /*&& m_regs->PMODE.SLBG == 0 && m_regs->PMODE.MMOD == 1 && m_regs->PMODE.ALP == 0x80*/)
{
if(fr[0].eq(fr[1] + GSVector4i(0, -1, 0, 0)) && dr[0].eq(dr[1] + GSVector4i(0, 0, 0, 1))
|| fr[1].eq(fr[0] + GSVector4i(0, -1, 0, 0)) && dr[1].eq(dr[0] + GSVector4i(0, 0, 0, 1)))
{
// 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
int top = min(fr[0].top, fr[1].top);
int bottom = max(dr[0].bottom, dr[1].bottom);
fr[0].top = top;
fr[1].top = top;
dr[0].bottom = bottom;
dr[1].bottom = bottom;
// blurdetected = true;
}
else if(dr[0].eq(dr[1]) && (fr[0].eq(fr[1] + GSVector4i(0, 1, 0, 1)) || fr[1].eq(fr[0] + GSVector4i(0, 1, 0, 1))))
{
// 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;
// blurdetected = true;
}
//printf("samesrc = %d blurdetected = %d\n",samesrc,blurdetected);
}
GSVector2i fs(0, 0);
GSVector2i ds(0, 0);
GSTexture* tex[2] = {NULL, NULL};
if(samesrc && fr[0].bottom == fr[1].bottom)
{
tex[0] = GetOutput(0);
tex[1] = tex[0]; // saves one texture fetch
}
else
{
if(en[0]) tex[0] = GetOutput(0);
if(en[1]) tex[1] = GetOutput(1);
}
GSVector4 src[2];
GSVector4 dst[2];
for(int i = 0; i < 2; i++)
{
if(!en[i] || !tex[i]) continue;
GSVector4i r = fr[i];
// overscan hack
if(dr[i].height() > 512) // hmm
{
int y = GetDeviceSize(i).y;
if(m_regs->SMODE2.INT && m_regs->SMODE2.FFMD) y /= 2;
r.bottom = r.top + y;
}
GSVector4 scale = GSVector4(tex[i]->GetScale()).xyxy();
src[i] = GSVector4(r) * scale / GSVector4(tex[i]->GetSize()).xyxy();
GSVector2 off(0, 0);
if(dr[i].top - baseline >= 4) // 2?
{
off.y = tex[i]->GetScale().y * (dr[i].top - baseline);
if(m_regs->SMODE2.INT && m_regs->SMODE2.FFMD)
{
off.y /= 2;
}
}
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;
}
bool slbg = m_regs->PMODE.SLBG;
bool mmod = m_regs->PMODE.MMOD;
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, dst, fs, slbg, mmod, c);
if(m_regs->SMODE2.INT && m_interlace > 0)
{
if (m_interlace == 7 && m_regs->SMODE2.FFMD == 1) // 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();
}
}
GL_POP();
return true;
}
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(!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);
GSVector4i r = GetDisplayRect();
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(), r.width(), r.height(), 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", r.width(), r.height(), 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<std::mutex> 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
m_dev->Present(m_wnd->GetClientRect().fit(m_aspectratio), m_shader);
// snapshot
if(!m_snapshot.empty())
{
bool shift = false;
#ifdef _WINDOWS
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 _WINDOWS
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 t = time(NULL);
char buff[16];
if(strftime(buff, sizeof(buff), "%Y%m%d%H%M%S", localtime(&t)))
{
m_snapshot = format("%s_%s", path.c_str(), buff);
}
}
return true;
}
bool GSRenderer::BeginCapture()
{
return m_capture.BeginCapture(GetTvRefreshRate());
}
void GSRenderer::EndCapture()
{
m_capture.EndCapture();
}
void GSRenderer::KeyEvent(GSKeyEventData* e)
{
const unsigned int interlace_nb = 8;
const unsigned int post_shader_nb = 5;
const unsigned int aspect_ratio_nb = 3;
#ifdef _WINDOWS
if(e->type == KEYPRESS)
{
int step = (::GetAsyncKeyState(VK_SHIFT) & 0x8000) ? -1 : 1;
switch(e->key)
{
case VK_F5:
m_interlace = (m_interlace + interlace_nb + step) % 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 + aspect_ratio_nb + step) % aspect_ratio_nb;
return;
case VK_F7:
m_shader = (m_shader + post_shader_nb + step) % post_shader_nb;
printf("GSdx: Set shader to: %d.\n", (int)m_shader);
theApp.SetConfig("TVShader", (int)m_shader);
return;
case VK_DELETE:
m_aa1 = !m_aa1;
printf("GSdx: (Software) aa1 is now %s.\n", m_aa1 ? "enabled" : "disabled");
return;
case VK_INSERT:
m_mipmap = !m_mipmap;
printf("GSdx: (Software) mipmapping is now %s.\n", m_mipmap ? "enabled" : "disabled");
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;
}
}
#elif defined(__linux__)
if(e->type == KEYPRESS)
{
int step = m_shift_key ? -1 : 1;
switch(e->key)
{
case XK_F5:
m_interlace = (m_interlace + interlace_nb + step) % interlace_nb;
fprintf(stderr, "GSdx: Set deinterlace mode to %d (%s).\n", (int)m_interlace, theApp.m_gs_interlace.at(m_interlace).name.c_str());
return;
case XK_F6:
if( m_wnd->IsManaged() )
m_aspectratio = (m_aspectratio + aspect_ratio_nb + step) % aspect_ratio_nb;
return;
case XK_F7:
m_shader = (m_shader + post_shader_nb + step) % post_shader_nb;
theApp.SetConfig("TVShader", (int)m_shader);
fprintf(stderr,"GSdx: Set shader %d.\n", (int)m_shader);
return;
case XK_Delete:
m_aa1 = !m_aa1;
fprintf(stderr,"GSdx: (Software) aa1 is now %s.\n", m_aa1 ? "enabled" : "disabled");
return;
case XK_Insert:
m_mipmap = !m_mipmap;
fprintf(stderr,"GSdx: (Software) mipmapping is now %s.\n", m_mipmap ? "enabled" : "disabled");
return;
case XK_Prior:
m_fxaa = !m_fxaa;
fprintf(stderr,"GSdx: fxaa is now %s.\n", m_fxaa ? "enabled" : "disabled");
return;
case XK_Home:
m_shaderfx = !m_shaderfx;
printf("GSdx: External post-processing is now %s.\n", m_shaderfx ? "enabled" : "disabled");
return;
case XK_Shift_L:
case XK_Shift_R:
m_shift_key = true;
return;
case XK_Control_L:
case XK_Control_R:
m_control_key = true;
return;
}
}
else if(e->type == KEYRELEASE)
{
switch(e->key)
{
case XK_Shift_L:
case XK_Shift_R:
m_shift_key = false;
return;
case XK_Control_L:
case XK_Control_R:
m_control_key = false;
return;
}
}
#endif
}