2009-07-28 21:32:10 +00:00
// Copyright (C) 2003 Dolphin Project.
2008-12-08 05:25:12 +00:00
// 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, version 2.0.
// 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 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
# include "Globals.h"
2009-09-13 09:23:30 +00:00
# include "VideoConfig.h"
2008-12-08 05:25:12 +00:00
# include "IniFile.h"
# include "svnrev.h"
2009-02-22 21:16:12 +00:00
# include "Setup.h"
2008-12-08 05:25:12 +00:00
# include "Render.h"
# if defined(_WIN32)
# include "OS/Win32.h"
# else
struct RECT
{
2010-02-20 04:18:19 +00:00
int left , top ;
int right , bottom ;
2008-12-08 05:25:12 +00:00
} ;
# endif
# include "GLUtil.h"
// Handles OpenGL and the window
2009-02-28 16:33:59 +00:00
// Window dimensions.
static int s_backbuffer_width ;
static int s_backbuffer_height ;
2008-12-08 05:25:12 +00:00
# ifndef _WIN32
GLWindow GLWin ;
# endif
# if defined(_WIN32)
2009-02-28 16:33:59 +00:00
static HDC hDC = NULL ; // Private GDI Device Context
static HGLRC hRC = NULL ; // Permanent Rendering Context
2008-12-08 05:25:12 +00:00
extern HINSTANCE g_hInstance ;
# endif
void OpenGL_SwapBuffers ( )
{
2009-09-09 20:47:11 +00:00
# if defined(USE_WX) && USE_WX
2010-02-20 04:18:19 +00:00
GLWin . glCanvas - > SwapBuffers ( ) ;
2008-12-10 23:23:05 +00:00
# elif defined(HAVE_COCOA) && HAVE_COCOA
2010-02-20 04:18:19 +00:00
cocoaGLSwap ( GLWin . cocoaCtx , GLWin . cocoaWin ) ;
2008-12-08 05:25:12 +00:00
# elif defined(_WIN32)
2010-02-20 04:18:19 +00:00
SwapBuffers ( hDC ) ;
2008-12-10 23:23:05 +00:00
# elif defined(HAVE_X11) && HAVE_X11
2010-02-20 04:18:19 +00:00
glXSwapBuffers ( GLWin . dpy , GLWin . win ) ;
2008-12-08 05:25:12 +00:00
# endif
}
2009-04-03 14:35:49 +00:00
u32 OpenGL_GetBackbufferWidth ( )
{
2010-02-20 04:18:19 +00:00
return s_backbuffer_width ;
2008-12-21 21:02:43 +00:00
}
2009-04-03 14:35:49 +00:00
u32 OpenGL_GetBackbufferHeight ( )
{
2010-02-20 04:18:19 +00:00
return s_backbuffer_height ;
2008-12-21 21:02:43 +00:00
}
2009-01-15 06:48:15 +00:00
void OpenGL_SetWindowText ( const char * text )
2008-12-08 05:25:12 +00:00
{
2009-09-09 20:47:11 +00:00
# if defined(USE_WX) && USE_WX
2010-02-20 04:18:19 +00:00
GLWin . frame - > SetTitle ( wxString : : FromAscii ( text ) ) ;
2008-12-10 23:23:05 +00:00
# elif defined(HAVE_COCOA) && HAVE_COCOA
2010-02-20 04:18:19 +00:00
cocoaGLSetTitle ( GLWin . cocoaWin , text ) ;
2008-12-08 05:25:12 +00:00
# elif defined(_WIN32)
2009-07-30 07:08:31 +00:00
// TODO convert text to unicode and change SetWindowTextA to SetWindowText
2010-02-20 04:18:19 +00:00
SetWindowTextA ( EmuWindow : : GetWnd ( ) , text ) ;
2008-12-08 05:25:12 +00:00
# elif defined(HAVE_X11) && HAVE_X11 // GLX
2010-02-20 04:18:19 +00:00
// Tell X to ask the window manager to set the window title. (X
// itself doesn't provide window title functionality.)
XStoreName ( GLWin . dpy , GLWin . win , text ) ;
2008-12-08 05:25:12 +00:00
# endif
}
2009-02-19 06:52:01 +00:00
// Draw messages on top of the screen
2008-12-10 23:23:05 +00:00
unsigned int Callback_PeekMessages ( )
2008-12-08 05:25:12 +00:00
{
# ifdef _WIN32
2010-02-20 04:18:19 +00:00
// TODO: peekmessage
MSG msg ;
while ( PeekMessage ( & msg , 0 , 0 , 0 , PM_REMOVE ) )
{
if ( msg . message = = WM_QUIT )
return FALSE ;
TranslateMessage ( & msg ) ;
DispatchMessage ( & msg ) ;
}
return TRUE ;
2009-01-15 06:48:15 +00:00
# else
2010-02-20 04:18:19 +00:00
return FALSE ;
2008-12-08 05:25:12 +00:00
# endif
}
2009-02-28 16:33:59 +00:00
2009-02-19 06:52:01 +00:00
// Show the current FPS
2008-12-08 05:25:12 +00:00
void UpdateFPSDisplay ( const char * text )
{
2010-02-20 04:18:19 +00:00
char temp [ 512 ] ;
sprintf ( temp , " SVN R%s: GL: %s " , SVN_REV_STR , text ) ;
OpenGL_SetWindowText ( temp ) ;
2008-12-08 05:25:12 +00:00
}
2010-02-16 04:59:45 +00:00
# if defined(HAVE_X11) && HAVE_X11
2010-03-15 23:25:11 +00:00
THREAD_RETURN XEventThread ( void * pArg ) ;
void X11_EWMH_Fullscreen ( int action )
{
2010-03-16 01:16:55 +00:00
_assert_ ( action = = _NET_WM_STATE_REMOVE | | action = = _NET_WM_STATE_ADD
2010-03-15 23:25:11 +00:00
| | action = = _NET_WM_STATE_TOGGLE ) ;
// Init X event structure for _NET_WM_STATE_FULLSCREEN client message
XEvent event ;
event . xclient . type = ClientMessage ;
event . xclient . message_type = XInternAtom ( GLWin . dpy , " _NET_WM_STATE " , False ) ;
event . xclient . window = GLWin . win ;
event . xclient . format = 32 ;
event . xclient . data . l [ 0 ] = action ;
event . xclient . data . l [ 1 ] = XInternAtom ( GLWin . dpy , " _NET_WM_STATE_FULLSCREEN " , False ) ;
// Send the event
if ( ! XSendEvent ( GLWin . dpy , DefaultRootWindow ( GLWin . dpy ) , False ,
SubstructureRedirectMask | SubstructureNotifyMask , & event ) )
ERROR_LOG ( VIDEO , " Failed to switch fullscreen/windowed mode. \n " ) ;
}
2010-02-16 04:59:45 +00:00
void CreateXWindow ( void )
{
2010-03-15 23:25:11 +00:00
Atom wmProtocols [ 3 ] ;
Window parent ;
2010-02-16 04:59:45 +00:00
2010-03-08 23:29:16 +00:00
# if defined(HAVE_GTK2) && HAVE_GTK2 && defined(wxGTK)
wxMutexGuiEnter ( ) ;
# endif
2010-02-16 04:59:45 +00:00
# if defined(HAVE_XRANDR) && HAVE_XRANDR
2010-03-16 01:16:55 +00:00
if ( GLWin . fs & & ! GLWin . renderToMain )
2010-03-15 23:25:11 +00:00
XRRSetScreenConfig ( GLWin . dpy , GLWin . screenConfig , RootWindow ( GLWin . dpy , GLWin . screen ) ,
GLWin . fullSize , GLWin . screenRotation , CurrentTime ) ;
# endif
# if defined(HAVE_GTK2) && HAVE_GTK2 && defined(wxGTK)
2010-03-16 01:16:55 +00:00
if ( GLWin . renderToMain )
2010-03-15 23:25:11 +00:00
{
GLWin . panel - > GetSize ( ( int * ) & GLWin . width , ( int * ) & GLWin . height ) ;
GLWin . panel - > GetPosition ( & GLWin . x , & GLWin . y ) ;
parent = GDK_WINDOW_XID ( GTK_WIDGET ( GLWin . panel - > GetHandle ( ) ) - > window ) ;
GLWin . panel - > SetFocus ( ) ;
2010-02-20 04:18:19 +00:00
}
else
2010-03-08 23:29:16 +00:00
# endif
2010-03-15 23:25:11 +00:00
{
GLWin . x = 0 ;
GLWin . y = 0 ;
GLWin . width = GLWin . winWidth ;
GLWin . height = GLWin . winHeight ;
parent = RootWindow ( GLWin . dpy , GLWin . vi - > screen ) ;
2010-02-20 04:18:19 +00:00
}
// Control window size and picture scaling
2010-03-08 23:29:16 +00:00
s_backbuffer_width = GLWin . width ;
s_backbuffer_height = GLWin . height ;
2010-02-20 04:18:19 +00:00
// create the window
2010-03-08 23:29:16 +00:00
GLWin . win = XCreateWindow ( GLWin . dpy , parent ,
GLWin . x , GLWin . y , GLWin . width , GLWin . height , 0 , GLWin . vi - > depth , InputOutput , GLWin . vi - > visual ,
2010-03-15 23:25:11 +00:00
CWBorderPixel | CWBackPixel | CWColormap | CWEventMask , & GLWin . attr ) ;
2010-02-20 04:18:19 +00:00
wmProtocols [ 0 ] = XInternAtom ( GLWin . dpy , " WM_DELETE_WINDOW " , True ) ;
2010-03-15 23:25:11 +00:00
wmProtocols [ 1 ] = XInternAtom ( GLWin . dpy , " _NET_WM_STATE " , False ) ;
wmProtocols [ 2 ] = XInternAtom ( GLWin . dpy , " _NET_WM_STATE_FULLSCREEN " , False ) ;
XSetWMProtocols ( GLWin . dpy , GLWin . win , wmProtocols , 3 ) ;
2010-02-20 04:18:19 +00:00
XSetStandardProperties ( GLWin . dpy , GLWin . win , " GPU " , " GPU " , None , NULL , 0 , NULL ) ;
XMapRaised ( GLWin . dpy , GLWin . win ) ;
XSync ( GLWin . dpy , True ) ;
2010-03-08 23:29:16 +00:00
# if defined(HAVE_GTK2) && HAVE_GTK2 && defined(wxGTK)
wxMutexGuiLeave ( ) ;
# endif
2010-03-16 03:34:27 +00:00
if ( g_Config . bHideCursor )
{
// make a blank cursor
Pixmap Blank ;
XColor DummyColor ;
char ZeroData [ 1 ] = { 0 } ;
Blank = XCreateBitmapFromData ( GLWin . dpy , GLWin . win , ZeroData , 1 , 1 ) ;
GLWin . blankCursor = XCreatePixmapCursor ( GLWin . dpy , Blank , Blank , & DummyColor , & DummyColor , 0 , 0 ) ;
XFreePixmap ( GLWin . dpy , Blank ) ;
}
2010-03-15 23:25:11 +00:00
GLWin . xEventThread = new Common : : Thread ( XEventThread , NULL ) ;
2010-02-16 04:59:45 +00:00
}
void DestroyXWindow ( void )
{
2010-02-20 04:18:19 +00:00
if ( GLWin . ctx )
{
if ( ! glXMakeCurrent ( GLWin . dpy , None , NULL ) )
{
printf ( " Could not release drawing context. \n " ) ;
}
}
/* switch back to original desktop resolution if we were in fullscreen */
if ( GLWin . fs )
{
2010-02-16 04:59:45 +00:00
# if defined(HAVE_XRANDR) && HAVE_XRANDR
2010-03-15 23:25:11 +00:00
XRRSetScreenConfig ( GLWin . dpy , GLWin . screenConfig , RootWindow ( GLWin . dpy , GLWin . screen ) ,
GLWin . deskSize , GLWin . screenRotation , CurrentTime ) ;
# if defined(HAVE_GTK2) && HAVE_GTK2 && defined(wxGTK)
2010-03-16 01:16:55 +00:00
if ( ! GLWin . renderToMain )
2010-03-15 23:25:11 +00:00
# endif
X11_EWMH_Fullscreen ( _NET_WM_STATE_REMOVE ) ;
2010-02-16 04:59:45 +00:00
# endif
2010-02-20 04:18:19 +00:00
}
XUndefineCursor ( GLWin . dpy , GLWin . win ) ;
XUnmapWindow ( GLWin . dpy , GLWin . win ) ;
2010-03-15 23:25:11 +00:00
GLWin . win = 0 ;
2010-02-16 04:59:45 +00:00
}
void ToggleFullscreenMode ( void )
{
2010-02-20 04:18:19 +00:00
GLWin . fs = ! GLWin . fs ;
2010-03-15 23:25:11 +00:00
# if defined(HAVE_XRANDR) && HAVE_XRANDR
if ( GLWin . fs )
XRRSetScreenConfig ( GLWin . dpy , GLWin . screenConfig , RootWindow ( GLWin . dpy , GLWin . screen ) ,
GLWin . fullSize , GLWin . screenRotation , CurrentTime ) ;
else
XRRSetScreenConfig ( GLWin . dpy , GLWin . screenConfig , RootWindow ( GLWin . dpy , GLWin . screen ) ,
GLWin . deskSize , GLWin . screenRotation , CurrentTime ) ;
# endif
# if defined(HAVE_GTK2) && HAVE_GTK2 && defined(wxGTK)
2010-03-16 01:16:55 +00:00
if ( ! GLWin . renderToMain )
2010-03-15 23:25:11 +00:00
# endif
{
X11_EWMH_Fullscreen ( _NET_WM_STATE_TOGGLE ) ;
XRaiseWindow ( GLWin . dpy , GLWin . win ) ;
XSetInputFocus ( GLWin . dpy , GLWin . win , RevertToPointerRoot , CurrentTime ) ;
}
XSync ( GLWin . dpy , False ) ;
}
THREAD_RETURN XEventThread ( void * pArg )
{
bool bPaused = False ;
while ( GLWin . win )
{
XEvent event ;
KeySym key ;
2010-03-16 13:18:52 +00:00
for ( int num_events = XPending ( GLWin . dpy ) ; num_events > 0 ; num_events - - ) {
2010-03-15 23:25:11 +00:00
XNextEvent ( GLWin . dpy , & event ) ;
switch ( event . type ) {
case KeyPress :
key = XLookupKeysym ( ( XKeyEvent * ) & event , 0 ) ;
switch ( key )
{
case XK_F1 : case XK_F2 : case XK_F3 : case XK_F4 : case XK_F5 : case XK_F6 :
case XK_F7 : case XK_F8 : case XK_F9 : case XK_F11 : case XK_F12 :
g_VideoInitialize . pKeyPress ( key - 0xff4e ,
event . xkey . state & ShiftMask ,
event . xkey . state & ControlMask ) ;
break ;
case XK_Escape :
if ( GLWin . fs & & ! bPaused )
{
printf ( " toggling fullscreen \n " ) ;
ToggleFullscreenMode ( ) ;
}
g_VideoInitialize . pKeyPress ( 0x1c , False , False ) ;
break ;
case XK_Return :
if ( event . xkey . state & Mod1Mask )
ToggleFullscreenMode ( ) ;
break ;
case XK_3 :
OSDChoice = 1 ;
// Toggle native resolution
if ( ! ( g_Config . bNativeResolution | | g_Config . b2xResolution ) )
g_Config . bNativeResolution = true ;
else if ( g_Config . bNativeResolution & & Renderer : : AllowCustom ( ) )
{ g_Config . bNativeResolution = false ; if ( Renderer : : Allow2x ( ) ) { g_Config . b2xResolution = true ; } }
else if ( Renderer : : AllowCustom ( ) )
g_Config . b2xResolution = false ;
break ;
case XK_4 :
OSDChoice = 2 ;
// Toggle aspect ratio
g_Config . iAspectRatio = ( g_Config . iAspectRatio + 1 ) & 3 ;
break ;
case XK_5 :
OSDChoice = 3 ;
// Toggle EFB copy
if ( g_Config . bEFBCopyDisable | | g_Config . bCopyEFBToTexture )
{
g_Config . bEFBCopyDisable = ! g_Config . bEFBCopyDisable ;
g_Config . bCopyEFBToTexture = false ;
}
else
{
g_Config . bCopyEFBToTexture = ! g_Config . bCopyEFBToTexture ;
}
break ;
case XK_6 :
OSDChoice = 4 ;
g_Config . bDisableFog = ! g_Config . bDisableFog ;
break ;
case XK_7 :
OSDChoice = 5 ;
g_Config . bDisableLighting = ! g_Config . bDisableLighting ;
break ;
default :
break ;
}
break ;
case FocusIn :
2010-03-16 01:16:55 +00:00
if ( g_Config . bHideCursor & & ! bPaused & & ! GLWin . renderToMain )
2010-03-15 23:25:11 +00:00
XDefineCursor ( GLWin . dpy , GLWin . win , GLWin . blankCursor ) ;
break ;
case FocusOut :
2010-03-16 01:16:55 +00:00
if ( g_Config . bHideCursor & & ! bPaused & & ! GLWin . renderToMain )
2010-03-15 23:25:11 +00:00
XUndefineCursor ( GLWin . dpy , GLWin . win ) ;
break ;
case ConfigureNotify :
Window winDummy ;
unsigned int borderDummy ;
XGetGeometry ( GLWin . dpy , GLWin . win , & winDummy , & GLWin . x , & GLWin . y ,
& GLWin . width , & GLWin . height , & borderDummy , & GLWin . depth ) ;
s_backbuffer_width = GLWin . width ;
s_backbuffer_height = GLWin . height ;
// Save windowed mode size for return from fullscreen
if ( ! GLWin . fs )
{
GLWin . winWidth = GLWin . width ;
GLWin . winHeight = GLWin . height ;
}
break ;
case ClientMessage :
if ( ( ulong ) event . xclient . data . l [ 0 ] = = XInternAtom ( GLWin . dpy , " WM_DELETE_WINDOW " , False ) )
g_VideoInitialize . pKeyPress ( 0x1b , False , False ) ;
if ( ( ulong ) event . xclient . data . l [ 0 ] = = XInternAtom ( GLWin . dpy , " TOGGLE_FULLSCREEN " , False ) )
ToggleFullscreenMode ( ) ;
if ( g_Config . bHideCursor & &
( ulong ) event . xclient . data . l [ 0 ] = = XInternAtom ( GLWin . dpy , " PAUSE " , False ) )
{
bPaused = True ;
XUndefineCursor ( GLWin . dpy , GLWin . win ) ;
}
if ( g_Config . bHideCursor & &
( ulong ) event . xclient . data . l [ 0 ] = = XInternAtom ( GLWin . dpy , " RESUME " , False ) )
{
bPaused = False ;
XDefineCursor ( GLWin . dpy , GLWin . win , GLWin . blankCursor ) ;
}
# if defined(HAVE_GTK2) && HAVE_GTK2 && defined(wxGTK)
2010-03-16 01:16:55 +00:00
if ( GLWin . renderToMain & &
2010-03-15 23:25:11 +00:00
( ulong ) event . xclient . data . l [ 0 ] = = XInternAtom ( GLWin . dpy , " RESIZE " , False ) )
{
GLWin . panel - > GetSize ( ( int * ) & GLWin . width , ( int * ) & GLWin . height ) ;
GLWin . panel - > GetPosition ( & GLWin . x , & GLWin . y ) ;
XMoveResizeWindow ( GLWin . dpy , GLWin . win , GLWin . x , GLWin . y , GLWin . width , GLWin . height ) ;
}
2010-03-16 01:16:55 +00:00
if ( GLWin . renderToMain & &
2010-03-15 23:25:11 +00:00
( ulong ) event . xclient . data . l [ 0 ] = = XInternAtom ( GLWin . dpy , " FOCUSIN " , False ) )
{
GLWin . panel - > SetFocus ( ) ;
if ( g_Config . bHideCursor )
XDefineCursor ( GLWin . dpy , GLWin . win , GLWin . blankCursor ) ;
}
2010-03-16 01:16:55 +00:00
if ( GLWin . renderToMain & & g_Config . bHideCursor & &
2010-03-15 23:25:11 +00:00
( ulong ) event . xclient . data . l [ 0 ] = = XInternAtom ( GLWin . dpy , " FOCUSOUT " , False ) )
XUndefineCursor ( GLWin . dpy , GLWin . win ) ;
# endif
break ;
default :
break ;
}
}
Common : : SleepCurrentThread ( 20 ) ;
}
return 0 ;
2010-02-16 04:59:45 +00:00
}
# endif
2009-01-04 21:53:41 +00:00
// Create rendering window.
// Call browser: Core.cpp:EmuThread() > main.cpp:Video_Initialize()
2009-01-15 06:48:15 +00:00
bool OpenGL_Create ( SVideoInitialize & _VideoInitialize , int _iwidth , int _iheight )
2008-12-08 05:25:12 +00:00
{
2010-02-16 04:59:45 +00:00
# if !defined(HAVE_X11) || !HAVE_X11
2009-01-04 21:53:41 +00:00
// Check for fullscreen mode
2010-02-20 04:18:19 +00:00
int _twidth , _theight ;
if ( g_Config . bFullscreen )
{
if ( strlen ( g_Config . cFSResolution ) > 1 )
{
sscanf ( g_Config . cFSResolution , " %dx%d " , & _twidth , & _theight ) ;
}
else // No full screen reso set, fall back to default reso
{
_twidth = _iwidth ;
_theight = _iheight ;
}
}
else // Going Windowed
{
if ( strlen ( g_Config . cInternalRes ) > 1 )
{
sscanf ( g_Config . cInternalRes , " %dx%d " , & _twidth , & _theight ) ;
}
else // No Window resolution set, fall back to default
{
_twidth = _iwidth ;
_theight = _iheight ;
}
}
2009-01-04 21:53:41 +00:00
2008-12-08 05:25:12 +00:00
// Control window size and picture scaling
2010-02-20 04:18:19 +00:00
s_backbuffer_width = _twidth ;
s_backbuffer_height = _theight ;
2010-02-16 04:59:45 +00:00
# endif
2008-12-08 05:25:12 +00:00
2010-02-20 04:18:19 +00:00
g_VideoInitialize . pPeekMessages = & Callback_PeekMessages ;
g_VideoInitialize . pUpdateFPSDisplay = & UpdateFPSDisplay ;
2008-12-08 05:25:12 +00:00
2009-09-09 20:47:11 +00:00
# if defined(USE_WX) && USE_WX
2010-02-20 04:18:19 +00:00
int args [ ] = { WX_GL_RGBA , WX_GL_DOUBLEBUFFER , WX_GL_DEPTH_SIZE , 16 , 0 } ;
wxSize size ( _iwidth , _iheight ) ;
if ( ! g_Config . RenderToMainframe | |
g_VideoInitialize . pWindowHandle = = NULL ) {
GLWin . frame = new wxFrame ( ( wxWindow * ) NULL ,
- 1 , _ ( " Dolphin " ) , wxPoint ( 50 , 50 ) , size ) ;
} else {
GLWin . frame = new wxFrame ( ( wxWindow * ) g_VideoInitialize . pWindowHandle ,
- 1 , _ ( " Dolphin " ) , wxPoint ( 50 , 50 ) , size ) ;
}
GLWin . glCanvas = new wxGLCanvas ( GLWin . frame , wxID_ANY , args ,
wxPoint ( 0 , 0 ) , size , wxSUNKEN_BORDER ) ;
GLWin . glCtxt = new wxGLContext ( GLWin . glCanvas ) ;
GLWin . frame - > Show ( TRUE ) ;
GLWin . glCanvas - > Show ( TRUE ) ;
GLWin . glCanvas - > SetCurrent ( * GLWin . glCtxt ) ;
2009-01-04 21:53:41 +00:00
2009-09-09 20:47:11 +00:00
# elif defined(HAVE_COCOA) && HAVE_COCOA
2010-02-20 04:18:19 +00:00
GLWin . width = s_backbuffer_width ;
GLWin . height = s_backbuffer_height ;
GLWin . cocoaWin = cocoaGLCreateWindow ( GLWin . width , GLWin . height ) ;
GLWin . cocoaCtx = cocoaGLInit ( g_Config . iMultisampleMode ) ;
2009-01-04 21:53:41 +00:00
2008-12-08 05:25:12 +00:00
# elif defined(_WIN32)
2010-01-20 19:51:13 +00:00
g_VideoInitialize . pWindowHandle = ( void * ) EmuWindow : : Create ( ( HWND ) g_VideoInitialize . pWindowHandle , g_hInstance , _T ( " Please wait... " ) ) ;
2008-12-08 05:25:12 +00:00
if ( g_VideoInitialize . pWindowHandle = = NULL )
{
g_VideoInitialize . pSysMessage ( " failed to create window " ) ;
return false ;
}
2010-02-20 04:18:19 +00:00
if ( g_Config . bFullscreen )
2010-01-20 19:51:13 +00:00
{
2010-02-20 04:18:19 +00:00
DEVMODE dmScreenSettings ;
memset ( & dmScreenSettings , 0 , sizeof ( dmScreenSettings ) ) ;
dmScreenSettings . dmSize = sizeof ( dmScreenSettings ) ;
dmScreenSettings . dmPelsWidth = s_backbuffer_width ;
dmScreenSettings . dmPelsHeight = s_backbuffer_height ;
dmScreenSettings . dmBitsPerPel = 32 ;
dmScreenSettings . dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT ;
// Try To Set Selected Mode And Get Results. NOTE: CDS_FULLSCREEN Gets Rid Of Start Bar.
if ( ChangeDisplaySettings ( & dmScreenSettings , CDS_FULLSCREEN ) ! = DISP_CHANGE_SUCCESSFUL )
{
if ( MessageBox ( NULL , _T ( " The Requested Fullscreen Mode Is Not Supported By \n Your Video Card. Use Windowed Mode Instead? " ) , _T ( " NeHe GL " ) , MB_YESNO | MB_ICONEXCLAMATION ) = = IDYES )
2010-01-20 19:51:13 +00:00
EmuWindow : : ToggleFullscreen ( EmuWindow : : GetWnd ( ) ) ;
2010-02-20 04:18:19 +00:00
else
return false ;
}
2010-01-20 19:51:13 +00:00
else
{
// SetWindowPos to the upper-left corner of the screen
SetWindowPos ( EmuWindow : : GetWnd ( ) , HWND_TOP , 0 , 0 , _twidth , _theight , SWP_NOREPOSITION | SWP_NOZORDER ) ;
}
2010-02-20 04:18:19 +00:00
}
else
2008-12-08 05:25:12 +00:00
{
2010-02-20 04:18:19 +00:00
// Change to default resolution
ChangeDisplaySettings ( NULL , 0 ) ;
}
2008-12-08 05:25:12 +00:00
2010-01-20 19:51:13 +00:00
// Show the window
EmuWindow : : Show ( ) ;
2008-12-08 05:25:12 +00:00
2010-02-20 04:18:19 +00:00
PIXELFORMATDESCRIPTOR pfd = // pfd Tells Windows How We Want Things To Be
{
sizeof ( PIXELFORMATDESCRIPTOR ) , // Size Of This Pixel Format Descriptor
1 , // Version Number
PFD_DRAW_TO_WINDOW | // Format Must Support Window
PFD_SUPPORT_OPENGL | // Format Must Support OpenGL
PFD_DOUBLEBUFFER , // Must Support Double Buffering
PFD_TYPE_RGBA , // Request An RGBA Format
32 , // Select Our Color Depth
0 , 0 , 0 , 0 , 0 , 0 , // Color Bits Ignored
0 , // 8bit Alpha Buffer
0 , // Shift Bit Ignored
0 , // No Accumulation Buffer
0 , 0 , 0 , 0 , // Accumulation Bits Ignored
24 , // 24Bit Z-Buffer (Depth Buffer)
8 , // 8bit Stencil Buffer
0 , // No Auxiliary Buffer
PFD_MAIN_PLANE , // Main Drawing Layer
0 , // Reserved
0 , 0 , 0 // Layer Masks Ignored
} ;
2010-01-20 19:51:13 +00:00
GLuint PixelFormat ; // Holds The Results After Searching For A Match
2010-02-20 04:18:19 +00:00
if ( ! ( hDC = GetDC ( EmuWindow : : GetWnd ( ) ) ) ) {
2009-03-22 11:21:44 +00:00
PanicAlert ( " (1) Can't create an OpenGL Device context. Fail. " ) ;
2010-02-20 04:18:19 +00:00
return false ;
}
if ( ! ( PixelFormat = ChoosePixelFormat ( hDC , & pfd ) ) ) {
PanicAlert ( " (2) Can't find a suitable PixelFormat. " ) ;
return false ;
}
if ( ! SetPixelFormat ( hDC , PixelFormat , & pfd ) ) {
2009-03-22 11:21:44 +00:00
PanicAlert ( " (3) Can't set the PixelFormat. " ) ;
2010-02-20 04:18:19 +00:00
return false ;
}
if ( ! ( hRC = wglCreateContext ( hDC ) ) ) {
2009-03-22 11:21:44 +00:00
PanicAlert ( " (4) Can't create an OpenGL rendering context. " ) ;
2010-02-20 04:18:19 +00:00
return false ;
}
2009-01-04 21:53:41 +00:00
// --------------------------------------
2008-12-08 05:25:12 +00:00
# elif defined(HAVE_X11) && HAVE_X11
2010-02-20 04:18:19 +00:00
int glxMajorVersion , glxMinorVersion ;
int vidModeMajorVersion , vidModeMinorVersion ;
// attributes for a single buffered visual in RGBA format with at least
// 8 bits per color and a 24 bit depth buffer
int attrListSgl [ ] = { GLX_RGBA , GLX_RED_SIZE , 8 ,
GLX_GREEN_SIZE , 8 ,
GLX_BLUE_SIZE , 8 ,
GLX_DEPTH_SIZE , 24 ,
None } ;
// attributes for a double buffered visual in RGBA format with at least
// 8 bits per color and a 24 bit depth buffer
int attrListDbl [ ] = { GLX_RGBA , GLX_DOUBLEBUFFER ,
GLX_RED_SIZE , 8 ,
GLX_GREEN_SIZE , 8 ,
GLX_BLUE_SIZE , 8 ,
GLX_DEPTH_SIZE , 24 ,
GLX_SAMPLE_BUFFERS_ARB , g_Config . iMultisampleMode , GLX_SAMPLES_ARB , 1 , None } ;
GLWin . dpy = XOpenDisplay ( 0 ) ;
2010-03-08 23:29:16 +00:00
# if defined(HAVE_GTK2) && HAVE_GTK2 && defined(wxGTK)
2010-03-15 23:25:11 +00:00
GLWin . panel = ( wxPanel * ) g_VideoInitialize . pPanel ;
2010-03-08 23:29:16 +00:00
# endif
g_VideoInitialize . pWindowHandle = ( Display * ) GLWin . dpy ;
2010-02-20 04:18:19 +00:00
GLWin . screen = DefaultScreen ( GLWin . dpy ) ;
// Fullscreen option.
GLWin . fs = g_Config . bFullscreen ; //Set to setting in Options
2010-03-16 01:16:55 +00:00
// Render to main option.
# if defined(HAVE_GTK2) && HAVE_GTK2 && defined(wxGTK)
GLWin . renderToMain = g_Config . RenderToMainframe ;
# else
GLWin . renderToMain = False ;
# endif
2010-02-20 04:18:19 +00:00
/* get an appropriate visual */
GLWin . vi = glXChooseVisual ( GLWin . dpy , GLWin . screen , attrListDbl ) ;
if ( GLWin . vi = = NULL ) {
GLWin . vi = glXChooseVisual ( GLWin . dpy , GLWin . screen , attrListSgl ) ;
GLWin . doubleBuffered = False ;
ERROR_LOG ( VIDEO , " Only Singlebuffered Visual! " ) ;
}
else {
GLWin . doubleBuffered = True ;
NOTICE_LOG ( VIDEO , " Got Doublebuffered Visual! " ) ;
}
2008-12-08 05:25:12 +00:00
2009-03-22 11:21:44 +00:00
glXQueryVersion ( GLWin . dpy , & glxMajorVersion , & glxMinorVersion ) ;
NOTICE_LOG ( VIDEO , " glX-Version %d.%d " , glxMajorVersion , glxMinorVersion ) ;
// Create a GLX context.
2010-02-16 04:59:45 +00:00
GLWin . ctx = glXCreateContext ( GLWin . dpy , GLWin . vi , 0 , GL_TRUE ) ;
2009-03-22 11:21:44 +00:00
if ( ! GLWin . ctx )
{
PanicAlert ( " Couldn't Create GLX context.Quit " ) ;
exit ( 0 ) ; // TODO: Don't bring down entire Emu
}
2010-03-12 04:10:48 +00:00
// Create a color map and set the event masks
2010-03-15 23:25:11 +00:00
GLWin . attr . colormap = XCreateColormap ( GLWin . dpy ,
RootWindow ( GLWin . dpy , GLWin . vi - > screen ) , GLWin . vi - > visual , AllocNone ) ;
2010-03-12 04:10:48 +00:00
GLWin . attr . event_mask = ExposureMask | KeyPressMask | KeyReleaseMask |
StructureNotifyMask | ResizeRedirectMask ;
2010-03-15 23:25:11 +00:00
GLWin . attr . background_pixel = BlackPixel ( GLWin . dpy , GLWin . screen ) ;
2009-03-22 11:21:44 +00:00
GLWin . attr . border_pixel = 0 ;
XkbSetDetectableAutoRepeat ( GLWin . dpy , True , NULL ) ;
2008-12-08 05:25:12 +00:00
2010-02-20 04:18:19 +00:00
// Get the resolution setings for both fullscreen and windowed modes
if ( strlen ( g_Config . cFSResolution ) > 1 )
sscanf ( g_Config . cFSResolution , " %dx%d " , & GLWin . fullWidth , & GLWin . fullHeight ) ;
else // No full screen reso set, fall back to desktop resolution
{
GLWin . fullWidth = DisplayWidth ( GLWin . dpy , GLWin . screen ) ;
GLWin . fullHeight = DisplayHeight ( GLWin . dpy , GLWin . screen ) ;
}
if ( strlen ( g_Config . cInternalRes ) > 1 )
sscanf ( g_Config . cInternalRes , " %dx%d " , & GLWin . winWidth , & GLWin . winHeight ) ;
else // No Window resolution set, fall back to default
{
GLWin . winWidth = _iwidth ;
GLWin . winHeight = _iheight ;
}
2010-02-16 04:59:45 +00:00
# if defined(HAVE_XRANDR) && HAVE_XRANDR
2010-02-20 04:18:19 +00:00
XRRQueryVersion ( GLWin . dpy , & vidModeMajorVersion , & vidModeMinorVersion ) ;
XRRScreenSize * sizes ;
int numSizes ;
NOTICE_LOG ( VIDEO , " XRRExtension-Version %d.%d " , vidModeMajorVersion , vidModeMinorVersion ) ;
GLWin . screenConfig = XRRGetScreenInfo ( GLWin . dpy , RootWindow ( GLWin . dpy , GLWin . screen ) ) ;
/* save desktop resolution */
GLWin . deskSize = XRRConfigCurrentConfiguration ( GLWin . screenConfig , & GLWin . screenRotation ) ;
/* Set the desktop resolution as the default */
GLWin . fullSize = - 1 ;
/* Find the index of the fullscreen resolution from config */
sizes = XRRConfigSizes ( GLWin . screenConfig , & numSizes ) ;
if ( numSizes > 0 & & sizes ! = NULL ) {
for ( int i = 0 ; i < numSizes ; i + + ) {
if ( ( sizes [ i ] . width = = GLWin . fullWidth ) & & ( sizes [ i ] . height = = GLWin . fullHeight ) ) {
GLWin . fullSize = i ;
}
}
NOTICE_LOG ( VIDEO , " Fullscreen Resolution %dx%d " , sizes [ GLWin . fullSize ] . width , sizes [ GLWin . fullSize ] . height ) ;
}
else {
ERROR_LOG ( VIDEO , " Failed to obtain fullscreen sizes. \n "
" Using current desktop resolution for fullscreen. \n " ) ;
GLWin . fullWidth = DisplayWidth ( GLWin . dpy , GLWin . screen ) ;
GLWin . fullHeight = DisplayHeight ( GLWin . dpy , GLWin . screen ) ;
}
2010-02-16 04:59:45 +00:00
# else
2010-02-20 04:18:19 +00:00
GLWin . fullWidth = DisplayWidth ( GLWin . dpy , GLWin . screen ) ;
GLWin . fullHeight = DisplayHeight ( GLWin . dpy , GLWin . screen ) ;
2010-02-16 04:59:45 +00:00
# endif
2008-12-08 05:25:12 +00:00
2010-03-15 23:25:11 +00:00
# if defined(HAVE_GTK2) && HAVE_GTK2 && defined(wxGTK)
2010-03-16 01:16:55 +00:00
if ( GLWin . renderToMain )
2010-03-15 23:25:11 +00:00
g_VideoInitialize . pKeyPress ( 0 , False , False ) ;
# endif
2010-02-20 04:18:19 +00:00
CreateXWindow ( ) ;
g_VideoInitialize . pXWindow = ( Window * ) & GLWin . win ;
2008-12-08 05:25:12 +00:00
# endif
return true ;
}
bool OpenGL_MakeCurrent ( )
{
2009-09-09 20:47:11 +00:00
# if defined(USE_WX) && USE_WX
2010-02-20 04:18:19 +00:00
GLWin . glCanvas - > SetCurrent ( * GLWin . glCtxt ) ;
2008-12-10 23:23:05 +00:00
# elif defined(HAVE_COCOA) && HAVE_COCOA
2010-02-20 04:18:19 +00:00
cocoaGLMakeCurrent ( GLWin . cocoaCtx , GLWin . cocoaWin ) ;
2008-12-08 05:25:12 +00:00
# elif defined(_WIN32)
2010-02-20 04:18:19 +00:00
if ( ! wglMakeCurrent ( hDC , hRC ) ) {
PanicAlert ( " (5) Can't Activate The GL Rendering Context. " ) ;
return false ;
}
2008-12-10 23:23:05 +00:00
# elif defined(HAVE_X11) && HAVE_X11
2010-02-20 04:18:19 +00:00
Window winDummy ;
unsigned int borderDummy ;
// connect the glx-context to the window
glXMakeCurrent ( GLWin . dpy , GLWin . win , GLWin . ctx ) ;
XGetGeometry ( GLWin . dpy , GLWin . win , & winDummy , & GLWin . x , & GLWin . y ,
& GLWin . width , & GLWin . height , & borderDummy , & GLWin . depth ) ;
NOTICE_LOG ( VIDEO , " GLWin Depth %d " , GLWin . depth )
if ( glXIsDirect ( GLWin . dpy , GLWin . ctx ) ) {
NOTICE_LOG ( VIDEO , " detected direct rendering " ) ;
} else {
ERROR_LOG ( VIDEO , " no Direct Rendering possible! " ) ;
}
2010-03-15 23:25:11 +00:00
if ( GLWin . fs )
{
# if defined(HAVE_GTK2) && HAVE_GTK2 && defined(wxGTK)
2010-03-16 01:16:55 +00:00
if ( GLWin . renderToMain )
2010-03-15 23:25:11 +00:00
{
GLWin . fs = False ;
g_VideoInitialize . pKeyPress ( 0x1d , False , False ) ;
}
else
# endif
X11_EWMH_Fullscreen ( _NET_WM_STATE_ADD ) ;
}
2010-02-20 04:18:19 +00:00
// Hide the cursor now
if ( g_Config . bHideCursor )
XDefineCursor ( GLWin . dpy , GLWin . win , GLWin . blankCursor ) ;
// better for pad plugin key input (thc)
XSelectInput ( GLWin . dpy , GLWin . win , ExposureMask | KeyPressMask | KeyReleaseMask |
StructureNotifyMask | EnterWindowMask | LeaveWindowMask | FocusChangeMask ) ;
2008-12-08 05:25:12 +00:00
# endif
return true ;
}
// Update window width, size and etc. Called from Render.cpp
void OpenGL_Update ( )
{
2009-09-09 20:47:11 +00:00
# if defined(USE_WX) && USE_WX
2010-02-20 04:18:19 +00:00
RECT rcWindow = { 0 } ;
rcWindow . right = GLWin . width ;
rcWindow . bottom = GLWin . height ;
// TODO fill in
2008-12-13 23:19:56 +00:00
2008-12-10 23:23:05 +00:00
# elif defined(HAVE_COCOA) && HAVE_COCOA
2009-02-28 16:33:59 +00:00
RECT rcWindow = { 0 } ;
2010-02-20 04:18:19 +00:00
rcWindow . right = GLWin . width ;
rcWindow . bottom = GLWin . height ;
2008-12-08 05:25:12 +00:00
# elif defined(_WIN32)
RECT rcWindow ;
2009-02-19 06:52:01 +00:00
if ( ! EmuWindow : : GetParentWnd ( ) )
{
2009-02-28 16:33:59 +00:00
// We are not rendering to a child window - use client size.
GetClientRect ( EmuWindow : : GetWnd ( ) , & rcWindow ) ;
2008-12-08 05:25:12 +00:00
}
else
{
2009-02-28 16:33:59 +00:00
// We are rendering to a child window - use parent size.
2008-12-08 05:25:12 +00:00
GetWindowRect ( EmuWindow : : GetParentWnd ( ) , & rcWindow ) ;
}
// ---------------------------------------------------------------------------------------
// Get the new window width and height
// ------------------
// See below for documentation
// ------------------
2010-02-20 04:18:19 +00:00
int width = rcWindow . right - rcWindow . left ;
int height = rcWindow . bottom - rcWindow . top ;
2008-12-08 05:25:12 +00:00
2009-02-19 06:52:01 +00:00
// If we are rendering to a child window
2008-12-08 05:25:12 +00:00
if ( EmuWindow : : GetParentWnd ( ) ! = 0 )
2008-12-25 15:56:36 +00:00
: : MoveWindow ( EmuWindow : : GetWnd ( ) , 0 , 0 , width , height , FALSE ) ;
2008-12-08 05:25:12 +00:00
2010-02-20 04:18:19 +00:00
s_backbuffer_width = width ;
s_backbuffer_height = height ;
2008-12-08 05:25:12 +00:00
2008-12-10 23:23:05 +00:00
# elif defined(HAVE_X11) && HAVE_X11
2008-12-08 05:25:12 +00:00
# endif
}
// Close plugin
void OpenGL_Shutdown ( )
{
2009-09-09 20:47:11 +00:00
# if defined(USE_WX) && USE_WX
2009-03-22 11:21:44 +00:00
delete GLWin . glCanvas ;
delete GLWin . frame ;
2009-09-09 20:47:11 +00:00
# elif defined(HAVE_COCOA) && HAVE_COCOA
2010-02-12 12:41:53 +00:00
cocoaGLDeleteWindow ( GLWin . cocoaWin ) ;
2009-09-09 20:47:11 +00:00
cocoaGLDelete ( GLWin . cocoaCtx ) ;
2008-12-08 05:25:12 +00:00
# elif defined(_WIN32)
2009-03-22 11:21:44 +00:00
if ( hRC ) // Do We Have A Rendering Context?
{
if ( ! wglMakeCurrent ( NULL , NULL ) ) // Are We Able To Release The DC And RC Contexts?
{
2008-12-08 05:25:12 +00:00
// [F|RES]: if this fails i dont see the message box and
// cant get out of the modal state so i disable it.
// This function fails only if i render to main window
2009-03-22 11:21:44 +00:00
// MessageBox(NULL,"Release Of DC And RC Failed.", "SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
}
2008-12-08 05:25:12 +00:00
2009-03-22 11:21:44 +00:00
if ( ! wglDeleteContext ( hRC ) ) // Are We Able To Delete The RC?
{
ERROR_LOG ( VIDEO , " Release Rendering Context Failed. " ) ;
}
hRC = NULL ; // Set RC To NULL
}
2008-12-08 05:25:12 +00:00
2009-03-22 11:21:44 +00:00
if ( hDC & & ! ReleaseDC ( EmuWindow : : GetWnd ( ) , hDC ) ) // Are We Able To Release The DC
{
ERROR_LOG ( VIDEO , " Release Device Context Failed. " ) ;
hDC = NULL ; // Set DC To NULL
}
2008-12-08 05:25:12 +00:00
# elif defined(HAVE_X11) && HAVE_X11
2010-02-20 04:18:19 +00:00
DestroyXWindow ( ) ;
2010-03-15 23:25:11 +00:00
if ( GLWin . xEventThread )
GLWin . xEventThread - > WaitForDeath ( ) ;
GLWin . xEventThread = NULL ;
2010-02-16 04:59:45 +00:00
# if defined(HAVE_XRANDR) && HAVE_XRANDR
2010-02-20 04:18:19 +00:00
if ( GLWin . fullSize > = 0 )
XRRFreeScreenConfigInfo ( GLWin . screenConfig ) ;
2010-02-07 02:41:02 +00:00
# endif
2010-02-16 04:59:45 +00:00
if ( g_Config . bHideCursor )
2010-02-20 04:18:19 +00:00
XFreeCursor ( GLWin . dpy , GLWin . blankCursor ) ;
2009-03-22 11:21:44 +00:00
if ( GLWin . ctx )
{
glXDestroyContext ( GLWin . dpy , GLWin . ctx ) ;
2010-03-12 04:10:48 +00:00
XFreeColormap ( GLWin . dpy , GLWin . attr . colormap ) ;
2009-03-22 11:21:44 +00:00
XCloseDisplay ( GLWin . dpy ) ;
GLWin . ctx = NULL ;
}
2008-12-08 09:58:02 +00:00
# endif
2008-12-08 05:25:12 +00:00
}
2009-02-21 13:11:49 +00:00
2009-03-22 11:21:44 +00:00
GLuint OpenGL_ReportGLError ( const char * function , const char * file , int line )
{
GLint err = glGetError ( ) ;
if ( err ! = GL_NO_ERROR )
{
ERROR_LOG ( VIDEO , " %s:%d: (%s) OpenGL error 0x%x - %s \n " , file , line , function , err , gluErrorString ( err ) ) ;
}
return err ;
}
void OpenGL_ReportARBProgramError ( )
2009-02-21 13:11:49 +00:00
{
const GLubyte * pstr = glGetString ( GL_PROGRAM_ERROR_STRING_ARB ) ;
if ( pstr ! = NULL & & pstr [ 0 ] ! = 0 )
{
GLint loc = 0 ;
glGetIntegerv ( GL_PROGRAM_ERROR_POSITION_ARB , & loc ) ;
2009-02-28 01:26:56 +00:00
ERROR_LOG ( VIDEO , " program error at %d: " , loc ) ;
ERROR_LOG ( VIDEO , ( char * ) pstr ) ;
2009-03-21 20:07:56 +00:00
ERROR_LOG ( VIDEO , " " ) ;
2009-02-21 13:11:49 +00:00
}
2009-03-22 11:21:44 +00:00
}
2009-02-21 13:11:49 +00:00
2009-03-22 11:21:44 +00:00
bool OpenGL_ReportFBOError ( const char * function , const char * file , int line )
{
unsigned int fbo_status = glCheckFramebufferStatusEXT ( GL_FRAMEBUFFER_EXT ) ;
if ( fbo_status ! = GL_FRAMEBUFFER_COMPLETE_EXT )
2009-02-21 13:11:49 +00:00
{
2009-03-22 11:21:44 +00:00
const char * error = " - " ;
switch ( fbo_status )
{
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT : error = " INCOMPLETE_ATTACHMENT_EXT " ; break ;
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT : error = " INCOMPLETE_MISSING_ATTACHMENT_EXT " ; break ;
case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT : error = " INCOMPLETE_DIMENSIONS_EXT " ; break ;
case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT : error = " INCOMPLETE_FORMATS_EXT " ; break ;
case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT : error = " INCOMPLETE_DRAW_BUFFER_EXT " ; break ;
case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT : error = " INCOMPLETE_READ_BUFFER_EXT " ; break ;
case GL_FRAMEBUFFER_UNSUPPORTED_EXT : error = " UNSUPPORTED_EXT " ; break ;
}
ERROR_LOG ( VIDEO , " %s:%d: (%s) OpenGL FBO error - %s \n " , file , line , function , error ) ;
return false ;
2009-02-21 13:11:49 +00:00
}
2009-03-22 11:21:44 +00:00
return true ;
2009-02-21 13:11:49 +00:00
}
2009-09-09 19:52:45 +00:00