mirror of https://github.com/PCSX2/pcsx2.git
1522 lines
28 KiB
C++
1522 lines
28 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 "GSdx.h"
|
|
#include "GSUtil.h"
|
|
#include "Renderers/SW/GSRendererSW.h"
|
|
#include "Renderers/Null/GSRendererNull.h"
|
|
#include "Renderers/Null/GSDeviceNull.h"
|
|
#include "Renderers/OpenGL/GSDeviceOGL.h"
|
|
#include "Renderers/OpenGL/GSRendererOGL.h"
|
|
#include "GSLzma.h"
|
|
|
|
#ifdef _WIN32
|
|
|
|
#include "Renderers/DX11/GSRendererDX11.h"
|
|
#include "Renderers/DX11/GSDevice11.h"
|
|
#include "Window/GSWndDX.h"
|
|
#include "Window/GSWndWGL.h"
|
|
#include "Window/GSSettingsDlg.h"
|
|
|
|
static HRESULT s_hr = E_FAIL;
|
|
|
|
#else
|
|
|
|
#include "Window/GSWndEGL.h"
|
|
|
|
extern bool RunLinuxDialog();
|
|
|
|
#endif
|
|
|
|
#define PS2E_LT_GS 0x01
|
|
#define PS2E_GS_VERSION 0x0006
|
|
#define PS2E_X86 0x01 // 32 bit
|
|
#define PS2E_X86_64 0x02 // 64 bit
|
|
|
|
static GSRenderer* s_gs = NULL;
|
|
static void (*s_irq)() = NULL;
|
|
static uint8* s_basemem = NULL;
|
|
static int s_vsync = 0;
|
|
static bool s_exclusive = true;
|
|
static std::string s_renderer_name;
|
|
bool gsopen_done = false; // crash guard for GSgetTitleInfo2 and GSKeyEvent (replace with lock?)
|
|
|
|
EXPORT_C_(uint32) PS2EgetLibType()
|
|
{
|
|
return PS2E_LT_GS;
|
|
}
|
|
|
|
EXPORT_C_(const char*) PS2EgetLibName()
|
|
{
|
|
return GSUtil::GetLibName();
|
|
}
|
|
|
|
EXPORT_C_(uint32) PS2EgetLibVersion2(uint32 type)
|
|
{
|
|
const uint32 revision = 1;
|
|
const uint32 build = 2;
|
|
|
|
return (build << 0) | (revision << 8) | (PS2E_GS_VERSION << 16) | (PLUGIN_VERSION << 24);
|
|
}
|
|
|
|
EXPORT_C_(uint32) PS2EgetCpuPlatform()
|
|
{
|
|
#ifdef _M_AMD64
|
|
|
|
return PS2E_X86_64;
|
|
|
|
#else
|
|
|
|
return PS2E_X86;
|
|
|
|
#endif
|
|
}
|
|
|
|
EXPORT_C GSsetBaseMem(uint8* mem)
|
|
{
|
|
s_basemem = mem;
|
|
|
|
if(s_gs)
|
|
{
|
|
s_gs->SetRegsMem(s_basemem);
|
|
}
|
|
}
|
|
|
|
EXPORT_C GSsetSettingsDir(const char* dir)
|
|
{
|
|
theApp.SetConfigDir(dir);
|
|
}
|
|
|
|
EXPORT_C_(int) GSinit()
|
|
{
|
|
if(!GSUtil::CheckSSE())
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
// Vector instructions must be avoided when initialising GSdx since PCSX2
|
|
// can crash if the CPU does not support the instruction set.
|
|
// Initialise it here instead - it's not ideal since we have to strip the
|
|
// const type qualifier from all the affected variables.
|
|
theApp.Init();
|
|
|
|
GSUtil::Init();
|
|
GSBlock::InitVectors();
|
|
GSClut::InitVectors();
|
|
GSRendererSW::InitVectors();
|
|
GSVector4i::InitVectors();
|
|
GSVector4::InitVectors();
|
|
#if _M_SSE >= 0x500
|
|
GSVector8::InitVectors();
|
|
#endif
|
|
#if _M_SSE >= 0x501
|
|
GSVector8i::InitVectors();
|
|
#endif
|
|
GSVertexTrace::InitVectors();
|
|
|
|
if (g_const == nullptr)
|
|
return -1;
|
|
else
|
|
g_const->Init();
|
|
|
|
#ifdef _WIN32
|
|
s_hr = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
EXPORT_C GSshutdown()
|
|
{
|
|
gsopen_done = false;
|
|
|
|
delete s_gs;
|
|
s_gs = nullptr;
|
|
|
|
theApp.SetCurrentRendererType(GSRendererType::Undefined);
|
|
|
|
#ifdef _WIN32
|
|
if(SUCCEEDED(s_hr))
|
|
{
|
|
::CoUninitialize();
|
|
|
|
s_hr = E_FAIL;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
EXPORT_C GSclose()
|
|
{
|
|
gsopen_done = false;
|
|
|
|
if(s_gs == NULL) return;
|
|
|
|
s_gs->ResetDevice();
|
|
|
|
// Opengl requirement: It must be done before the Detach() of
|
|
// the context
|
|
delete s_gs->m_dev;
|
|
|
|
s_gs->m_dev = NULL;
|
|
|
|
if (s_gs->m_wnd)
|
|
{
|
|
s_gs->m_wnd->Detach();
|
|
}
|
|
}
|
|
|
|
static int _GSopen(void** dsp, const char* title, GSRendererType renderer, int threads = -1)
|
|
{
|
|
GSDevice* dev = NULL;
|
|
bool old_api = *dsp == NULL;
|
|
|
|
// Fresh start up or config file changed
|
|
if(renderer == GSRendererType::Undefined)
|
|
{
|
|
renderer = static_cast<GSRendererType>(theApp.GetConfigI("Renderer"));
|
|
#ifdef _WIN32
|
|
if (renderer == GSRendererType::Default)
|
|
renderer = GSUtil::GetBestRenderer();
|
|
#endif
|
|
}
|
|
|
|
if(threads == -1)
|
|
{
|
|
threads = theApp.GetConfigI("extrathreads");
|
|
}
|
|
|
|
try
|
|
{
|
|
if (theApp.GetCurrentRendererType() != renderer)
|
|
{
|
|
// Emulator has made a render change request, which requires a completely
|
|
// new s_gs -- if the emu doesn't save/restore the GS state across this
|
|
// GSopen call then they'll get corrupted graphics, but that's not my problem.
|
|
|
|
delete s_gs;
|
|
|
|
s_gs = NULL;
|
|
|
|
theApp.SetCurrentRendererType(renderer);
|
|
}
|
|
|
|
std::shared_ptr<GSWnd> window;
|
|
{
|
|
// Select the window first to detect the GL requirement
|
|
std::vector<std::shared_ptr<GSWnd>> wnds;
|
|
switch (renderer)
|
|
{
|
|
case GSRendererType::OGL_HW:
|
|
case GSRendererType::OGL_SW:
|
|
#if defined(__unix__)
|
|
// Note: EGL code use GLX otherwise maybe it could be also compatible with Windows
|
|
// Yes OpenGL code isn't complicated enough !
|
|
switch (GSWndEGL::SelectPlatform()) {
|
|
#if GS_EGL_X11
|
|
case EGL_PLATFORM_X11_KHR:
|
|
wnds.push_back(std::make_shared<GSWndEGL_X11>());
|
|
break;
|
|
#endif
|
|
#if GS_EGL_WL
|
|
case EGL_PLATFORM_WAYLAND_KHR:
|
|
wnds.push_back(std::make_shared<GSWndEGL_WL>());
|
|
break;
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
#else
|
|
wnds.push_back(std::make_shared<GSWndWGL>());
|
|
#endif
|
|
break;
|
|
default:
|
|
#ifdef _WIN32
|
|
wnds.push_back(std::make_shared<GSWndDX>());
|
|
#else
|
|
wnds.push_back(std::make_shared<GSWndEGL_X11>());
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
int w = theApp.GetConfigI("ModeWidth");
|
|
int h = theApp.GetConfigI("ModeHeight");
|
|
#if defined(__unix__)
|
|
void *win_handle = (void*)((uptr*)(dsp)+1);
|
|
#else
|
|
void *win_handle = *dsp;
|
|
#endif
|
|
|
|
for(auto& wnd : wnds)
|
|
{
|
|
try
|
|
{
|
|
if (old_api)
|
|
{
|
|
// old-style API expects us to create and manage our own window:
|
|
wnd->Create(title, w, h);
|
|
|
|
wnd->Show();
|
|
|
|
*dsp = wnd->GetDisplay();
|
|
}
|
|
else
|
|
{
|
|
wnd->Attach(win_handle, false);
|
|
}
|
|
|
|
window = wnd; // Previous code will throw if window isn't supported
|
|
|
|
break;
|
|
}
|
|
catch (GSDXRecoverableError)
|
|
{
|
|
wnd->Detach();
|
|
}
|
|
}
|
|
|
|
if(!window)
|
|
{
|
|
GSclose();
|
|
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
std::string renderer_name;
|
|
|
|
switch (renderer)
|
|
{
|
|
default:
|
|
#ifdef _WIN32
|
|
case GSRendererType::DX1011_HW:
|
|
dev = new GSDevice11();
|
|
s_renderer_name = "D3D11";
|
|
renderer_name = "Direct3D 11";
|
|
break;
|
|
#endif
|
|
case GSRendererType::OGL_HW:
|
|
dev = new GSDeviceOGL();
|
|
s_renderer_name = "OGL";
|
|
renderer_name = "OpenGL";
|
|
break;
|
|
case GSRendererType::OGL_SW:
|
|
dev = new GSDeviceOGL();
|
|
s_renderer_name = "SW";
|
|
renderer_name = "Software";
|
|
break;
|
|
case GSRendererType::Null:
|
|
dev = new GSDeviceNull();
|
|
s_renderer_name = "NULL";
|
|
renderer_name = "Null";
|
|
break;
|
|
}
|
|
|
|
printf("Current Renderer: %s\n", renderer_name.c_str());
|
|
|
|
if (dev == NULL)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if (s_gs == NULL)
|
|
{
|
|
switch (renderer)
|
|
{
|
|
default:
|
|
#ifdef _WIN32
|
|
case GSRendererType::DX1011_HW:
|
|
s_gs = (GSRenderer*)new GSRendererDX11();
|
|
break;
|
|
#endif
|
|
case GSRendererType::OGL_HW:
|
|
s_gs = (GSRenderer*)new GSRendererOGL();
|
|
break;
|
|
case GSRendererType::OGL_SW:
|
|
s_gs = new GSRendererSW(threads);
|
|
break;
|
|
case GSRendererType::Null:
|
|
s_gs = new GSRendererNull();
|
|
break;
|
|
}
|
|
if (s_gs == NULL)
|
|
return -1;
|
|
}
|
|
|
|
s_gs->m_wnd = window;
|
|
}
|
|
catch (std::exception& ex)
|
|
{
|
|
// Allowing std exceptions to escape the scope of the plugin callstack could
|
|
// be problematic, because of differing typeids between DLL and EXE compilations.
|
|
// ('new' could throw std::alloc)
|
|
|
|
printf("GSdx error: Exception caught in GSopen: %s", ex.what());
|
|
|
|
return -1;
|
|
}
|
|
|
|
s_gs->SetRegsMem(s_basemem);
|
|
s_gs->SetIrqCallback(s_irq);
|
|
s_gs->SetVSync(s_vsync);
|
|
|
|
if(!old_api)
|
|
s_gs->SetMultithreaded(true);
|
|
|
|
if(!s_gs->CreateDevice(dev))
|
|
{
|
|
// This probably means the user has DX11 configured with a video card that is only DX9
|
|
// compliant. Cound mean drivr issues of some sort also, but to be sure, that's the most
|
|
// common cause of device creation errors. :) --air
|
|
|
|
GSclose();
|
|
|
|
return -1;
|
|
}
|
|
|
|
if (renderer == GSRendererType::OGL_HW && theApp.GetConfigI("debug_glsl_shader") == 2) {
|
|
printf("GSdx: test OpenGL shader. Please wait...\n\n");
|
|
static_cast<GSDeviceOGL*>(s_gs->m_dev)->SelfShaderTest();
|
|
printf("\nGSdx: test OpenGL shader done. It will now exit\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
EXPORT_C_(void) GSosdLog(const char *utf8, uint32 color)
|
|
{
|
|
if(s_gs && s_gs->m_dev) s_gs->m_dev->m_osd.Log(utf8);
|
|
}
|
|
|
|
EXPORT_C_(void) GSosdMonitor(const char *key, const char *value, uint32 color)
|
|
{
|
|
if(s_gs && s_gs->m_dev) s_gs->m_dev->m_osd.Monitor(key, value);
|
|
}
|
|
|
|
EXPORT_C_(int) GSopen2(void** dsp, uint32 flags)
|
|
{
|
|
static bool stored_toggle_state = false;
|
|
const bool toggle_state = !!(flags & 4);
|
|
|
|
auto current_renderer = theApp.GetCurrentRendererType();
|
|
|
|
if (current_renderer != GSRendererType::Undefined && stored_toggle_state != toggle_state)
|
|
{
|
|
// SW -> HW and HW -> SW (F9 Switch)
|
|
switch (current_renderer)
|
|
{
|
|
#ifdef _WIN32
|
|
case GSRendererType::DX1011_HW:
|
|
current_renderer = GSRendererType::OGL_SW;
|
|
break;
|
|
#endif
|
|
case GSRendererType::OGL_SW:
|
|
#ifdef _WIN32
|
|
{
|
|
const auto config_renderer = static_cast<GSRendererType>(
|
|
theApp.GetConfigI("Renderer")
|
|
);
|
|
|
|
if (current_renderer == config_renderer)
|
|
current_renderer = GSUtil::GetBestRenderer();
|
|
else
|
|
current_renderer = config_renderer;
|
|
}
|
|
#else
|
|
current_renderer = GSRendererType::OGL_HW;
|
|
#endif
|
|
break;
|
|
case GSRendererType::OGL_HW:
|
|
current_renderer = GSRendererType::OGL_SW;
|
|
break;
|
|
default:
|
|
current_renderer = GSRendererType::OGL_SW;
|
|
break;
|
|
}
|
|
}
|
|
stored_toggle_state = toggle_state;
|
|
|
|
int retval = _GSopen(dsp, "", current_renderer);
|
|
|
|
if (s_gs != NULL)
|
|
s_gs->SetAspectRatio(0); // PCSX2 manages the aspect ratios
|
|
|
|
gsopen_done = true;
|
|
|
|
return retval;
|
|
}
|
|
|
|
EXPORT_C_(int) GSopen(void** dsp, const char* title, int mt)
|
|
{
|
|
GSRendererType renderer = GSRendererType::Default;
|
|
|
|
// Legacy GUI expects to acquire vsync from the configuration files.
|
|
|
|
s_vsync = theApp.GetConfigI("vsync");
|
|
|
|
if(mt == 2)
|
|
{
|
|
// pcsx2 sent a switch renderer request
|
|
mt = 1;
|
|
}
|
|
else
|
|
{
|
|
// normal init
|
|
|
|
renderer = static_cast<GSRendererType>(theApp.GetConfigI("Renderer"));
|
|
}
|
|
|
|
*dsp = NULL;
|
|
|
|
int retval = _GSopen(dsp, title, renderer);
|
|
|
|
if(retval == 0 && s_gs)
|
|
{
|
|
s_gs->SetMultithreaded(!!mt);
|
|
}
|
|
|
|
gsopen_done = true;
|
|
|
|
return retval;
|
|
}
|
|
|
|
EXPORT_C GSreset()
|
|
{
|
|
try
|
|
{
|
|
s_gs->Reset();
|
|
}
|
|
catch (GSDXRecoverableError)
|
|
{
|
|
}
|
|
}
|
|
|
|
EXPORT_C GSgifSoftReset(uint32 mask)
|
|
{
|
|
try
|
|
{
|
|
s_gs->SoftReset(mask);
|
|
}
|
|
catch (GSDXRecoverableError)
|
|
{
|
|
}
|
|
}
|
|
|
|
EXPORT_C GSwriteCSR(uint32 csr)
|
|
{
|
|
try
|
|
{
|
|
s_gs->WriteCSR(csr);
|
|
}
|
|
catch (GSDXRecoverableError)
|
|
{
|
|
}
|
|
}
|
|
|
|
EXPORT_C GSinitReadFIFO(uint8* mem)
|
|
{
|
|
GL_PERF("Init Read FIFO1");
|
|
try
|
|
{
|
|
s_gs->InitReadFIFO(mem, 1);
|
|
}
|
|
catch (GSDXRecoverableError)
|
|
{
|
|
}
|
|
catch (const std::bad_alloc&)
|
|
{
|
|
fprintf(stderr, "GSdx: Memory allocation error\n");
|
|
}
|
|
}
|
|
|
|
EXPORT_C GSreadFIFO(uint8* mem)
|
|
{
|
|
try
|
|
{
|
|
s_gs->ReadFIFO(mem, 1);
|
|
}
|
|
catch (GSDXRecoverableError)
|
|
{
|
|
}
|
|
catch (const std::bad_alloc&)
|
|
{
|
|
fprintf(stderr, "GSdx: Memory allocation error\n");
|
|
}
|
|
}
|
|
|
|
EXPORT_C GSinitReadFIFO2(uint8* mem, uint32 size)
|
|
{
|
|
GL_PERF("Init Read FIFO2");
|
|
try
|
|
{
|
|
s_gs->InitReadFIFO(mem, size);
|
|
}
|
|
catch (GSDXRecoverableError)
|
|
{
|
|
}
|
|
catch (const std::bad_alloc&)
|
|
{
|
|
fprintf(stderr, "GSdx: Memory allocation error\n");
|
|
}
|
|
}
|
|
|
|
EXPORT_C GSreadFIFO2(uint8* mem, uint32 size)
|
|
{
|
|
try
|
|
{
|
|
s_gs->ReadFIFO(mem, size);
|
|
}
|
|
catch (GSDXRecoverableError)
|
|
{
|
|
}
|
|
catch (const std::bad_alloc&)
|
|
{
|
|
fprintf(stderr, "GSdx: Memory allocation error\n");
|
|
}
|
|
}
|
|
|
|
EXPORT_C GSgifTransfer(const uint8* mem, uint32 size)
|
|
{
|
|
try
|
|
{
|
|
s_gs->Transfer<3>(mem, size);
|
|
}
|
|
catch (GSDXRecoverableError)
|
|
{
|
|
}
|
|
}
|
|
|
|
EXPORT_C GSgifTransfer1(uint8* mem, uint32 addr)
|
|
{
|
|
try
|
|
{
|
|
s_gs->Transfer<0>(const_cast<uint8*>(mem) + addr, (0x4000 - addr) / 16);
|
|
}
|
|
catch (GSDXRecoverableError)
|
|
{
|
|
}
|
|
}
|
|
|
|
EXPORT_C GSgifTransfer2(uint8* mem, uint32 size)
|
|
{
|
|
try
|
|
{
|
|
s_gs->Transfer<1>(const_cast<uint8*>(mem), size);
|
|
}
|
|
catch (GSDXRecoverableError)
|
|
{
|
|
}
|
|
}
|
|
|
|
EXPORT_C GSgifTransfer3(uint8* mem, uint32 size)
|
|
{
|
|
try
|
|
{
|
|
s_gs->Transfer<2>(const_cast<uint8*>(mem), size);
|
|
}
|
|
catch (GSDXRecoverableError)
|
|
{
|
|
}
|
|
}
|
|
|
|
EXPORT_C GSvsync(int field)
|
|
{
|
|
try
|
|
{
|
|
#ifdef _WIN32
|
|
|
|
if(s_gs->m_wnd->IsManaged())
|
|
{
|
|
MSG msg;
|
|
|
|
memset(&msg, 0, sizeof(msg));
|
|
|
|
while(msg.message != WM_QUIT && PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
|
|
{
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
s_gs->VSync(field);
|
|
}
|
|
catch (GSDXRecoverableError)
|
|
{
|
|
}
|
|
catch (const std::bad_alloc&)
|
|
{
|
|
fprintf(stderr, "GSdx: Memory allocation error\n");
|
|
}
|
|
}
|
|
|
|
EXPORT_C_(uint32) GSmakeSnapshot(char* path)
|
|
{
|
|
try
|
|
{
|
|
std::string s{path};
|
|
|
|
if(!s.empty() && s[s.length() - 1] != DIRECTORY_SEPARATOR)
|
|
{
|
|
s = s + DIRECTORY_SEPARATOR;
|
|
}
|
|
|
|
return s_gs->MakeSnapshot(s + "gsdx");
|
|
}
|
|
catch (GSDXRecoverableError)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
EXPORT_C GSkeyEvent(GSKeyEventData* e)
|
|
{
|
|
try
|
|
{
|
|
if(gsopen_done)
|
|
{
|
|
s_gs->KeyEvent(e);
|
|
}
|
|
}
|
|
catch (GSDXRecoverableError)
|
|
{
|
|
}
|
|
}
|
|
|
|
EXPORT_C_(int) GSfreeze(int mode, GSFreezeData* data)
|
|
{
|
|
try
|
|
{
|
|
if(mode == FREEZE_SAVE)
|
|
{
|
|
return s_gs->Freeze(data, false);
|
|
}
|
|
else if(mode == FREEZE_SIZE)
|
|
{
|
|
return s_gs->Freeze(data, true);
|
|
}
|
|
else if(mode == FREEZE_LOAD)
|
|
{
|
|
return s_gs->Defrost(data);
|
|
}
|
|
}
|
|
catch (GSDXRecoverableError)
|
|
{
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
EXPORT_C GSconfigure()
|
|
{
|
|
try
|
|
{
|
|
if(!GSUtil::CheckSSE()) return;
|
|
|
|
theApp.Init();
|
|
|
|
#ifdef _WIN32
|
|
GSDialog::InitCommonControls();
|
|
if(GSSettingsDlg().DoModal() == IDOK)
|
|
{
|
|
// Force a reload of the gs state
|
|
theApp.SetCurrentRendererType(GSRendererType::Undefined);
|
|
}
|
|
|
|
#else
|
|
|
|
if (RunLinuxDialog()) {
|
|
theApp.ReloadConfig();
|
|
// Force a reload of the gs state
|
|
theApp.SetCurrentRendererType(GSRendererType::Undefined);
|
|
}
|
|
|
|
#endif
|
|
|
|
} catch (GSDXRecoverableError)
|
|
{
|
|
}
|
|
}
|
|
|
|
EXPORT_C_(int) GStest()
|
|
{
|
|
if(!GSUtil::CheckSSE())
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
EXPORT_C GSabout()
|
|
{
|
|
}
|
|
|
|
EXPORT_C GSirqCallback(void (*irq)())
|
|
{
|
|
s_irq = irq;
|
|
|
|
if(s_gs)
|
|
{
|
|
s_gs->SetIrqCallback(s_irq);
|
|
}
|
|
}
|
|
|
|
void pt(const char* str){
|
|
struct tm *current;
|
|
time_t now;
|
|
|
|
time(&now);
|
|
current = localtime(&now);
|
|
|
|
printf("%02i:%02i:%02i%s", current->tm_hour, current->tm_min, current->tm_sec, str);
|
|
}
|
|
|
|
EXPORT_C_(std::wstring*) GSsetupRecording(int start)
|
|
{
|
|
if (s_gs == NULL) {
|
|
printf("GSdx: no s_gs for recording\n");
|
|
return nullptr;
|
|
}
|
|
#if defined(__unix__)
|
|
if (!theApp.GetConfigB("capture_enabled")) {
|
|
printf("GSdx: Recording is disabled\n");
|
|
return nullptr;
|
|
}
|
|
#endif
|
|
std::wstring* filename = nullptr;
|
|
if(start & 1)
|
|
{
|
|
printf("GSdx: Recording start command\n");
|
|
filename = s_gs->BeginCapture();
|
|
if (filename)
|
|
{
|
|
pt(" - Capture started\n");
|
|
}
|
|
else
|
|
{
|
|
pt(" - Capture cancelled\n");
|
|
return nullptr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
printf("GSdx: Recording end command\n");
|
|
s_gs->EndCapture();
|
|
pt(" - Capture ended\n");
|
|
}
|
|
|
|
return filename;
|
|
}
|
|
|
|
EXPORT_C GSsetGameCRC(uint32 crc, int options)
|
|
{
|
|
s_gs->SetGameCRC(crc, options);
|
|
}
|
|
|
|
EXPORT_C GSgetLastTag(uint32* tag)
|
|
{
|
|
s_gs->GetLastTag(tag);
|
|
}
|
|
|
|
EXPORT_C GSgetTitleInfo2(char* dest, size_t length)
|
|
{
|
|
std::string s;
|
|
s.append(s_renderer_name);
|
|
// TODO: this gets called from a different thread concurrently with GSOpen (on linux)
|
|
if (gsopen_done && s_gs != NULL && s_gs->m_GStitleInfoBuffer[0])
|
|
{
|
|
std::lock_guard<std::mutex> lock(s_gs->m_pGSsetTitle_Crit);
|
|
|
|
s.append(" | ").append(s_gs->m_GStitleInfoBuffer);
|
|
|
|
if(s.size() > length - 1)
|
|
{
|
|
s = s.substr(0, length - 1);
|
|
}
|
|
}
|
|
|
|
strcpy(dest, s.c_str());
|
|
}
|
|
|
|
EXPORT_C GSsetFrameSkip(int frameskip)
|
|
{
|
|
s_gs->SetFrameSkip(frameskip);
|
|
}
|
|
|
|
EXPORT_C GSsetVsync(int vsync)
|
|
{
|
|
s_vsync = vsync;
|
|
|
|
if(s_gs)
|
|
{
|
|
s_gs->SetVSync(s_vsync);
|
|
}
|
|
}
|
|
|
|
EXPORT_C GSsetExclusive(int enabled)
|
|
{
|
|
s_exclusive = !!enabled;
|
|
|
|
if(s_gs)
|
|
{
|
|
s_gs->SetVSync(s_vsync);
|
|
}
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
|
|
#include <io.h>
|
|
#include <fcntl.h>
|
|
|
|
class Console
|
|
{
|
|
HANDLE m_console;
|
|
std::string m_title;
|
|
|
|
public:
|
|
Console::Console(LPCSTR title, bool open)
|
|
: m_console(NULL)
|
|
, m_title(title)
|
|
{
|
|
if(open) Open();
|
|
}
|
|
|
|
Console::~Console()
|
|
{
|
|
Close();
|
|
}
|
|
|
|
void Console::Open()
|
|
{
|
|
if(m_console == NULL)
|
|
{
|
|
CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
|
|
|
|
AllocConsole();
|
|
|
|
SetConsoleTitle(m_title.c_str());
|
|
|
|
m_console = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
|
|
COORD size;
|
|
|
|
size.X = 100;
|
|
size.Y = 300;
|
|
|
|
SetConsoleScreenBufferSize(m_console, size);
|
|
|
|
GetConsoleScreenBufferInfo(m_console, &csbiInfo);
|
|
|
|
SMALL_RECT rect;
|
|
|
|
rect = csbiInfo.srWindow;
|
|
rect.Right = rect.Left + 99;
|
|
rect.Bottom = rect.Top + 64;
|
|
|
|
SetConsoleWindowInfo(m_console, TRUE, &rect);
|
|
|
|
freopen("CONOUT$", "w", stdout);
|
|
freopen("CONOUT$", "w", stderr);
|
|
|
|
setvbuf(stdout, nullptr, _IONBF, 0);
|
|
setvbuf(stderr, nullptr, _IONBF, 0);
|
|
}
|
|
}
|
|
|
|
void Console::Close()
|
|
{
|
|
if(m_console != NULL)
|
|
{
|
|
FreeConsole();
|
|
|
|
m_console = NULL;
|
|
}
|
|
}
|
|
};
|
|
|
|
// lpszCmdLine:
|
|
// First parameter is the renderer.
|
|
// Second parameter is the gs file to load and run.
|
|
|
|
EXPORT_C GSReplay(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow)
|
|
{
|
|
GSRendererType renderer = GSRendererType::Undefined;
|
|
|
|
{
|
|
char* start = lpszCmdLine;
|
|
char* end = NULL;
|
|
long n = strtol(lpszCmdLine, &end, 10);
|
|
if(end > start) {renderer = static_cast<GSRendererType>(n); lpszCmdLine = end;}
|
|
}
|
|
|
|
while(*lpszCmdLine == ' ') lpszCmdLine++;
|
|
|
|
::SetPriorityClass(::GetCurrentProcess(), HIGH_PRIORITY_CLASS);
|
|
|
|
Console console{"GSdx", true};
|
|
|
|
const std::string f{lpszCmdLine};
|
|
const bool is_xz = f.size() >= 4 && f.compare(f.size() - 3, 3, ".xz") == 0;
|
|
|
|
auto file = is_xz
|
|
? std::unique_ptr<GSDumpFile>{std::make_unique<GSDumpLzma>(lpszCmdLine, nullptr)}
|
|
: std::unique_ptr<GSDumpFile>{std::make_unique<GSDumpRaw>(lpszCmdLine, nullptr)};
|
|
|
|
GSinit();
|
|
|
|
std::array<uint8, 0x2000> regs;
|
|
GSsetBaseMem(regs.data());
|
|
|
|
s_vsync = theApp.GetConfigI("vsync");
|
|
|
|
HWND hWnd = nullptr;
|
|
|
|
_GSopen((void**)&hWnd, "", renderer);
|
|
|
|
uint32 crc;
|
|
file->Read(&crc, 4);
|
|
GSsetGameCRC(crc, 0);
|
|
|
|
{
|
|
GSFreezeData fd;
|
|
file->Read(&fd.size, 4);
|
|
std::vector<uint8> freeze_data(fd.size);
|
|
fd.data = freeze_data.data();
|
|
file->Read(fd.data, fd.size);
|
|
GSfreeze(FREEZE_LOAD, &fd);
|
|
}
|
|
|
|
file->Read(regs.data(), 0x2000);
|
|
|
|
GSvsync(1);
|
|
|
|
struct Packet {uint8 type, param; uint32 size, addr; std::vector<uint8> buff;};
|
|
|
|
auto read_packet = [&file](uint8 type) {
|
|
Packet p;
|
|
p.type = type;
|
|
|
|
switch(p.type) {
|
|
case 0:
|
|
file->Read(&p.param, 1);
|
|
file->Read(&p.size, 4);
|
|
switch(p.param) {
|
|
case 0:
|
|
p.buff.resize(0x4000);
|
|
p.addr = 0x4000 - p.size;
|
|
file->Read(&p.buff[p.addr], p.size);
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
p.buff.resize(p.size);
|
|
file->Read(p.buff.data(), p.size);
|
|
break;
|
|
}
|
|
break;
|
|
case 1:
|
|
file->Read(&p.param, 1);
|
|
break;
|
|
case 2:
|
|
file->Read(&p.size, 4);
|
|
break;
|
|
case 3:
|
|
p.buff.resize(0x2000);
|
|
file->Read(p.buff.data(), 0x2000);
|
|
break;
|
|
}
|
|
|
|
return p;
|
|
};
|
|
|
|
std::list<Packet> packets;
|
|
uint8 type;
|
|
while(file->Read(&type, 1))
|
|
packets.push_back(read_packet(type));
|
|
|
|
Sleep(100);
|
|
|
|
std::vector<uint8> buff;
|
|
while(IsWindowVisible(hWnd))
|
|
{
|
|
for(auto &p : packets)
|
|
{
|
|
switch(p.type)
|
|
{
|
|
case 0:
|
|
switch(p.param)
|
|
{
|
|
case 0: GSgifTransfer1(p.buff.data(), p.addr); break;
|
|
case 1: GSgifTransfer2(p.buff.data(), p.size / 16); break;
|
|
case 2: GSgifTransfer3(p.buff.data(), p.size / 16); break;
|
|
case 3: GSgifTransfer(p.buff.data(), p.size / 16); break;
|
|
}
|
|
break;
|
|
case 1:
|
|
GSvsync(p.param);
|
|
break;
|
|
case 2:
|
|
if(buff.size() < p.size) buff.resize(p.size);
|
|
GSreadFIFO2(p.buff.data(), p.size / 16);
|
|
break;
|
|
case 3:
|
|
memcpy(regs.data(), p.buff.data(), 0x2000);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Sleep(100);
|
|
|
|
GSclose();
|
|
GSshutdown();
|
|
}
|
|
|
|
EXPORT_C GSBenchmark(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow)
|
|
{
|
|
::SetPriorityClass(::GetCurrentProcess(), HIGH_PRIORITY_CLASS);
|
|
|
|
Console console("GSdx", true);
|
|
|
|
if(1)
|
|
{
|
|
GSLocalMemory* mem = new GSLocalMemory();
|
|
|
|
static struct {int psm; const char* name;} s_format[] =
|
|
{
|
|
{PSM_PSMCT32, "32"},
|
|
{PSM_PSMCT24, "24"},
|
|
{PSM_PSMCT16, "16"},
|
|
{PSM_PSMCT16S, "16S"},
|
|
{PSM_PSMT8, "8"},
|
|
{PSM_PSMT4, "4"},
|
|
{PSM_PSMT8H, "8H"},
|
|
{PSM_PSMT4HL, "4HL"},
|
|
{PSM_PSMT4HH, "4HH"},
|
|
{PSM_PSMZ32, "32Z"},
|
|
{PSM_PSMZ24, "24Z"},
|
|
{PSM_PSMZ16, "16Z"},
|
|
{PSM_PSMZ16S, "16ZS"},
|
|
};
|
|
|
|
uint8* ptr = (uint8*)_aligned_malloc(1024 * 1024 * 4, 32);
|
|
|
|
for(int i = 0; i < 1024 * 1024 * 4; i++) ptr[i] = (uint8)i;
|
|
|
|
//
|
|
|
|
for(int tbw = 5; tbw <= 10; tbw++)
|
|
{
|
|
int n = 256 << ((10 - tbw) * 2);
|
|
|
|
int w = 1 << tbw;
|
|
int h = 1 << tbw;
|
|
|
|
printf("%d x %d\n\n", w, h);
|
|
|
|
for(size_t i = 0; i < countof(s_format); i++)
|
|
{
|
|
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[s_format[i].psm];
|
|
|
|
GSLocalMemory::writeImage wi = psm.wi;
|
|
GSLocalMemory::readImage ri = psm.ri;
|
|
GSLocalMemory::readTexture rtx = psm.rtx;
|
|
GSLocalMemory::readTexture rtxP = psm.rtxP;
|
|
|
|
GIFRegBITBLTBUF BITBLTBUF;
|
|
|
|
BITBLTBUF.SBP = 0;
|
|
BITBLTBUF.SBW = w / 64;
|
|
BITBLTBUF.SPSM = s_format[i].psm;
|
|
BITBLTBUF.DBP = 0;
|
|
BITBLTBUF.DBW = w / 64;
|
|
BITBLTBUF.DPSM = s_format[i].psm;
|
|
|
|
GIFRegTRXPOS TRXPOS;
|
|
|
|
TRXPOS.SSAX = 0;
|
|
TRXPOS.SSAY = 0;
|
|
TRXPOS.DSAX = 0;
|
|
TRXPOS.DSAY = 0;
|
|
|
|
GIFRegTRXREG TRXREG;
|
|
|
|
TRXREG.RRW = w;
|
|
TRXREG.RRH = h;
|
|
|
|
GSVector4i r(0, 0, w, h);
|
|
|
|
GIFRegTEX0 TEX0;
|
|
|
|
TEX0.TBP0 = 0;
|
|
TEX0.TBW = w / 64;
|
|
|
|
GIFRegTEXA TEXA;
|
|
|
|
TEXA.TA0 = 0;
|
|
TEXA.TA1 = 0x80;
|
|
TEXA.AEM = 0;
|
|
|
|
int trlen = w * h * psm.trbpp / 8;
|
|
int len = w * h * psm.bpp / 8;
|
|
|
|
clock_t start, end;
|
|
|
|
printf("[%4s] ", s_format[i].name);
|
|
|
|
start = clock();
|
|
|
|
for(int j = 0; j < n; j++)
|
|
{
|
|
int x = 0;
|
|
int y = 0;
|
|
|
|
(mem->*wi)(x, y, ptr, trlen, BITBLTBUF, TRXPOS, TRXREG);
|
|
}
|
|
|
|
end = clock();
|
|
|
|
printf("%6d %6d | ", (int)((float)trlen * n / (end - start) / 1000), (int)((float)(w * h) * n / (end - start) / 1000));
|
|
|
|
start = clock();
|
|
|
|
for(int j = 0; j < n; j++)
|
|
{
|
|
int x = 0;
|
|
int y = 0;
|
|
|
|
(mem->*ri)(x, y, ptr, trlen, BITBLTBUF, TRXPOS, TRXREG);
|
|
}
|
|
|
|
end = clock();
|
|
|
|
printf("%6d %6d | ", (int)((float)trlen * n / (end - start) / 1000), (int)((float)(w * h) * n / (end - start) / 1000));
|
|
|
|
const GSOffset* off = mem->GetOffset(TEX0.TBP0, TEX0.TBW, TEX0.PSM);
|
|
|
|
start = clock();
|
|
|
|
for(int j = 0; j < n; j++)
|
|
{
|
|
(mem->*rtx)(off, r, ptr, w * 4, TEXA);
|
|
}
|
|
|
|
end = clock();
|
|
|
|
printf("%6d %6d ", (int)((float)len * n / (end - start) / 1000), (int)((float)(w * h) * n / (end - start) / 1000));
|
|
|
|
if(psm.pal > 0)
|
|
{
|
|
start = clock();
|
|
|
|
for(int j = 0; j < n; j++)
|
|
{
|
|
(mem->*rtxP)(off, r, ptr, w, TEXA);
|
|
}
|
|
|
|
end = clock();
|
|
|
|
printf("| %6d %6d ", (int)((float)len * n / (end - start) / 1000), (int)((float)(w * h) * n / (end - start) / 1000));
|
|
}
|
|
|
|
printf("\n");
|
|
}
|
|
|
|
printf("\n");
|
|
}
|
|
|
|
_aligned_free(ptr);
|
|
|
|
delete mem;
|
|
}
|
|
|
|
//
|
|
|
|
if(0)
|
|
{
|
|
GSLocalMemory* mem = new GSLocalMemory();
|
|
|
|
uint8* ptr = (uint8*)_aligned_malloc(1024 * 1024 * 4, 32);
|
|
|
|
for(int i = 0; i < 1024 * 1024 * 4; i++) ptr[i] = (uint8)i;
|
|
|
|
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[PSM_PSMCT32];
|
|
|
|
GSLocalMemory::writeImage wi = psm.wi;
|
|
|
|
GIFRegBITBLTBUF BITBLTBUF;
|
|
|
|
BITBLTBUF.DBP = 0;
|
|
BITBLTBUF.DBW = 32;
|
|
BITBLTBUF.DPSM = PSM_PSMCT32;
|
|
|
|
GIFRegTRXPOS TRXPOS;
|
|
|
|
TRXPOS.DSAX = 0;
|
|
TRXPOS.DSAY = 1;
|
|
|
|
GIFRegTRXREG TRXREG;
|
|
|
|
TRXREG.RRW = 256;
|
|
TRXREG.RRH = 256;
|
|
|
|
int trlen = 256 * 256 * psm.trbpp / 8;
|
|
|
|
int x = 0;
|
|
int y = 0;
|
|
|
|
(mem->*wi)(x, y, ptr, trlen, BITBLTBUF, TRXPOS, TRXREG);
|
|
|
|
delete mem;
|
|
}
|
|
|
|
//
|
|
|
|
PostQuitMessage(0);
|
|
}
|
|
|
|
#endif
|
|
|
|
#if defined(__unix__)
|
|
|
|
inline unsigned long timeGetTime()
|
|
{
|
|
struct timespec t;
|
|
clock_gettime(CLOCK_REALTIME, &t);
|
|
return (unsigned long)(t.tv_sec*1000 + t.tv_nsec/1000000);
|
|
}
|
|
|
|
// Note
|
|
EXPORT_C GSReplay(char* lpszCmdLine, int renderer)
|
|
{
|
|
GLLoader::in_replayer = true;
|
|
// Required by multithread driver
|
|
XInitThreads();
|
|
|
|
GSinit();
|
|
|
|
GSRendererType m_renderer;
|
|
// Allow to easyly switch between SW/HW renderer -> this effectively removes the ability to select the renderer by function args
|
|
m_renderer = static_cast<GSRendererType>(theApp.GetConfigI("Renderer"));
|
|
|
|
if (m_renderer != GSRendererType::OGL_HW && m_renderer != GSRendererType::OGL_SW)
|
|
{
|
|
fprintf(stderr, "wrong renderer selected %d\n", static_cast<int>(m_renderer));
|
|
return;
|
|
}
|
|
|
|
struct Packet {uint8 type, param; uint32 size, addr; std::vector<uint8> buff;};
|
|
|
|
std::list<Packet*> packets;
|
|
std::vector<uint8> buff;
|
|
uint8 regs[0x2000];
|
|
|
|
GSsetBaseMem(regs);
|
|
|
|
s_vsync = theApp.GetConfigI("vsync");
|
|
int finished = theApp.GetConfigI("linux_replay");
|
|
bool repack_dump = (finished < 0);
|
|
|
|
if (theApp.GetConfigI("dump")) {
|
|
fprintf(stderr, "Dump is enabled. Replay will be disabled\n");
|
|
finished = 1;
|
|
}
|
|
|
|
long frame_number = 0;
|
|
|
|
void* hWnd = NULL;
|
|
int err = _GSopen((void**)&hWnd, "", m_renderer);
|
|
if (err != 0) {
|
|
fprintf(stderr, "Error failed to GSopen\n");
|
|
return;
|
|
}
|
|
if (s_gs->m_wnd == NULL) return;
|
|
|
|
{ // Read .gs content
|
|
std::string f(lpszCmdLine);
|
|
bool is_xz = (f.size() >= 4) && (f.compare(f.size()-3, 3, ".xz") == 0);
|
|
if (is_xz)
|
|
f.replace(f.end()-6, f.end(), "_repack.gs");
|
|
else
|
|
f.replace(f.end()-3, f.end(), "_repack.gs");
|
|
|
|
GSDumpFile* file = is_xz
|
|
? (GSDumpFile*) new GSDumpLzma(lpszCmdLine, repack_dump ? f.c_str() : nullptr)
|
|
: (GSDumpFile*) new GSDumpRaw(lpszCmdLine, repack_dump ? f.c_str() : nullptr);
|
|
|
|
uint32 crc;
|
|
file->Read(&crc, 4);
|
|
GSsetGameCRC(crc, 0);
|
|
|
|
GSFreezeData fd;
|
|
file->Read(&fd.size, 4);
|
|
fd.data = new uint8[fd.size];
|
|
file->Read(fd.data, fd.size);
|
|
|
|
GSfreeze(FREEZE_LOAD, &fd);
|
|
delete [] fd.data;
|
|
|
|
file->Read(regs, 0x2000);
|
|
|
|
uint8 type;
|
|
while(file->Read(&type, 1))
|
|
{
|
|
Packet* p = new Packet();
|
|
|
|
p->type = type;
|
|
|
|
switch(type)
|
|
{
|
|
case 0:
|
|
file->Read(&p->param, 1);
|
|
file->Read(&p->size, 4);
|
|
|
|
switch(p->param)
|
|
{
|
|
case 0:
|
|
p->buff.resize(0x4000);
|
|
p->addr = 0x4000 - p->size;
|
|
file->Read(&p->buff[p->addr], p->size);
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
p->buff.resize(p->size);
|
|
file->Read(&p->buff[0], p->size);
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
case 1:
|
|
file->Read(&p->param, 1);
|
|
frame_number++;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
file->Read(&p->size, 4);
|
|
|
|
break;
|
|
|
|
case 3:
|
|
p->buff.resize(0x2000);
|
|
|
|
file->Read(&p->buff[0], 0x2000);
|
|
|
|
break;
|
|
}
|
|
|
|
packets.push_back(p);
|
|
|
|
if (repack_dump && frame_number > -finished)
|
|
break;
|
|
}
|
|
|
|
delete file;
|
|
}
|
|
|
|
sleep(2);
|
|
|
|
|
|
frame_number = 0;
|
|
|
|
// Init vsync stuff
|
|
GSvsync(1);
|
|
|
|
while(finished > 0)
|
|
{
|
|
for(auto i = packets.begin(); i != packets.end(); i++)
|
|
{
|
|
Packet* p = *i;
|
|
|
|
switch(p->type)
|
|
{
|
|
case 0:
|
|
|
|
switch(p->param)
|
|
{
|
|
case 0: GSgifTransfer1(&p->buff[0], p->addr); break;
|
|
case 1: GSgifTransfer2(&p->buff[0], p->size / 16); break;
|
|
case 2: GSgifTransfer3(&p->buff[0], p->size / 16); break;
|
|
case 3: GSgifTransfer(&p->buff[0], p->size / 16); break;
|
|
}
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
GSvsync(p->param);
|
|
frame_number++;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
if(buff.size() < p->size) buff.resize(p->size);
|
|
|
|
GSreadFIFO2(&buff[0], p->size / 16);
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
memcpy(regs, &p->buff[0], 0x2000);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (finished >= 200) {
|
|
; // Nop for Nvidia Profiler
|
|
} else if (finished > 90) {
|
|
sleep(1);
|
|
} else {
|
|
finished--;
|
|
}
|
|
}
|
|
|
|
static_cast<GSDeviceOGL*>(s_gs->m_dev)->GenerateProfilerData();
|
|
|
|
#ifdef ENABLE_OGL_DEBUG_MEM_BW
|
|
unsigned long total_frame_nb = std::max(1l, frame_number) << 10;
|
|
fprintf(stderr, "memory bandwith. T: %f KB/f. V: %f KB/f. U: %f KB/f\n",
|
|
(float)g_real_texture_upload_byte/(float)total_frame_nb,
|
|
(float)g_vertex_upload_byte/(float)total_frame_nb,
|
|
(float)g_uniform_upload_byte/(float)total_frame_nb
|
|
);
|
|
#endif
|
|
|
|
for(auto i = packets.begin(); i != packets.end(); i++)
|
|
{
|
|
delete *i;
|
|
}
|
|
|
|
packets.clear();
|
|
|
|
sleep(2);
|
|
|
|
GSclose();
|
|
GSshutdown();
|
|
}
|
|
#endif
|