diff --git a/src/win32/OpenGL.cpp b/src/win32/OpenGL.cpp new file mode 100644 index 00000000..3f1e337b --- /dev/null +++ b/src/win32/OpenGL.cpp @@ -0,0 +1,764 @@ +// VisualBoyAdvance - Nintendo Gameboy/GameboyAdvance (TM) emulator. +// Copyright (C) 1999-2003 Forgotten +// Copyright (C) 2004 Forgotten and the VBA development team +// Copyright (C) 2005-2006 VBA development team +// Copyright (C) 2007-2008 VBA-M development team +// 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 this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef NO_OGL + +//OpenGL library +#pragma comment( lib, "opengl32.lib" ) + +// MFC +#include "stdafx.h" + +//GUI +#include "MainWnd.h" +#include "FullscreenSettings.h" + +// Internals +#include "../System.h" +#include "../agb/GBA.h" +#include "../Globals.h" +#include "../Util.h" +#include "../dmg/gbGlobals.h" +#include "..\memgzio.h" + +//Math +#include +#include + +// OpenGL +#include // main include file +#include +#include "glFont.h" +#include +typedef BOOL (APIENTRY *PFNWGLSWAPINTERVALFARPROC)( int ); +extern int Init_2xSaI(u32); +extern void winlog(const char *,...); +extern int systemSpeed; + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +#ifdef MMX +extern "C" bool cpu_mmx; +extern bool detectMMX(); +#endif + + +class OpenGLDisplay : public IDisplay { +private: + HDC hDC; + HGLRC hRC; + GLuint texture; + int width,height; + float size; + u8 *filterData; + RECT destRect; + bool failed; + GLFONT font; + int pitch; + GLuint displaylist; + u8 *data; + GLhandleARB v,f,p,t; + DWORD currentAdapter; + + void initializeMatrices( int w, int h ); + bool initializeTexture( int w, int h ); + void updateFiltering( int value ); + void setVSync( int interval = 1 ); + void calculateDestRect( int w, int h ); + void initializeFont(); + void renderlist(); + +public: + OpenGLDisplay(); + virtual ~OpenGLDisplay(); + virtual DISPLAY_TYPE getType() { return OPENGL; }; + + virtual void EnableOpenGL(); + virtual void DisableOpenGL(); + virtual bool initialize(); + virtual void cleanup(); + virtual void clear(); + virtual void render(); + + virtual bool changeRenderSize( int w, int h ); + virtual void resize( int w, int h ); + virtual void setOption( const char *, int ); + virtual bool selectFullScreenMode( VIDEO_MODE &mode ); +}; + +#include "gzglfont.h" +//Load GL font +void OpenGLDisplay::initializeFont() +{ + int ret; + z_stream strm; + char *buf = (char *)malloc(GZGLFONT_SIZE); + + /* allocate inflate state */ + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = 0; + strm.next_in = Z_NULL; + ret = inflateInit2(&strm, 16+MAX_WBITS); + if (ret != Z_OK) + return; + + strm.avail_in = sizeof(gzglfont); + strm.next_in = gzglfont; + strm.avail_out = GZGLFONT_SIZE; + strm.next_out = (Bytef *)buf; + ret = inflate(&strm, Z_NO_FLUSH); + if (ret==Z_STREAM_END) + { + glGenTextures( 1, &texture ); + glFontCreate(&font, (char *)buf, texture); + texture=0; + } + free(buf); + (void)inflateEnd(&strm); +} + +//OpenGL class constructor +OpenGLDisplay::OpenGLDisplay() +{ + hDC = NULL; + hRC = NULL; + texture = 0; + width = 0; + height = 0; + size = 0.0f; + failed = false; + filterData = NULL; + currentAdapter = 0; +} + +//OpenGL class destroyer +OpenGLDisplay::~OpenGLDisplay() +{ + cleanup(); +} + +//Set OpenGL PFD and contexts +void OpenGLDisplay::EnableOpenGL() +{ + PIXELFORMATDESCRIPTOR pfd; + // get the device context (DC) + hDC = GetDC( theApp.m_pMainWnd->GetSafeHwnd() ); + // set the pixel format for the DC + ZeroMemory( &pfd, sizeof( pfd ) ); + pfd.nSize = sizeof( pfd ); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cColorBits = 24; + pfd.cDepthBits = 16; + pfd.iLayerType = PFD_MAIN_PLANE; + SetPixelFormat (GetDC (theApp.m_pMainWnd->GetSafeHwnd()), ChoosePixelFormat ( GetDC (theApp.m_pMainWnd->GetSafeHwnd()), &pfd), &pfd); + wglMakeCurrent (GetDC (theApp.m_pMainWnd->GetSafeHwnd()), wglCreateContext(GetDC (theApp.m_pMainWnd->GetSafeHwnd()) ) ); +} +//Remove contexts +void OpenGLDisplay::DisableOpenGL() +{ + wglMakeCurrent( NULL, NULL ); + wglDeleteContext( hRC ); + ReleaseDC( theApp.m_pMainWnd->GetSafeHwnd(), hDC ); +} +//Remove resources used +void OpenGLDisplay::cleanup() +{ + if(texture != 0) { + glDeleteTextures(1, &texture); + texture = 0; + } + + if (displaylist) + { + glDeleteLists(displaylist, 1); + displaylist = 0; + } + + DisableOpenGL(); + if(filterData) { + free(filterData); + filterData = NULL; + } + width = 0; + height = 0; + size = 0.0f; + + DISPLAY_DEVICE dev; + ZeroMemory( &dev, sizeof(dev) ); + dev.cb = sizeof(dev); + EnumDisplayDevices( NULL, currentAdapter, &dev, 0 ); + // restore default video mode + ChangeDisplaySettingsEx( dev.DeviceName, NULL, NULL, 0, NULL ); +} + +//init renderer +bool OpenGLDisplay::initialize() +{ + switch( theApp.cartridgeType ) + { + case IMAGE_GBA: + theApp.sizeX = 240; + theApp.sizeY = 160; + break; + case IMAGE_GB: + if ( gbBorderOn ) + { + theApp.sizeX = 256; + theApp.sizeY = 224; + } + else + { + theApp.sizeX = 160; + theApp.sizeY = 144; + } + break; + } + + + switch(theApp.videoOption) + { + case VIDEO_1X: + theApp.surfaceSizeX = theApp.sizeX; + theApp.surfaceSizeY = theApp.sizeY; + break; + case VIDEO_2X: + theApp.surfaceSizeX = theApp.sizeX * 2; + theApp.surfaceSizeY = theApp.sizeY * 2; + break; + case VIDEO_3X: + theApp.surfaceSizeX = theApp.sizeX * 3; + theApp.surfaceSizeY = theApp.sizeY * 3; + break; + case VIDEO_4X: + theApp.surfaceSizeX = theApp.sizeX * 4; + theApp.surfaceSizeY = theApp.sizeY * 4; + break; + case VIDEO_320x240: + case VIDEO_640x480: + case VIDEO_800x600: + case VIDEO_1024x768: + case VIDEO_1280x960: + case VIDEO_OTHER: + { + if( theApp.fullScreenStretch ) { + theApp.surfaceSizeX = theApp.fsWidth; + theApp.surfaceSizeY = theApp.fsHeight; + } else { + float scaleX = (float)theApp.fsWidth / (float)theApp.sizeX; + float scaleY = (float)theApp.fsHeight / (float)theApp.sizeY; + float min = ( scaleX < scaleY ) ? scaleX : scaleY; + if( theApp.fsMaxScale ) + min = ( min > (float)theApp.fsMaxScale ) ? (float)theApp.fsMaxScale : min; + theApp.surfaceSizeX = (int)((float)theApp.sizeX * min); + theApp.surfaceSizeY = (int)((float)theApp.sizeY * min); + } + } + break; + } + + theApp.rect.left = 0; + theApp.rect.top = 0; + theApp.rect.right = theApp.sizeX; + theApp.rect.bottom = theApp.sizeY; + + theApp.dest.left = 0; + theApp.dest.top = 0; + theApp.dest.right = theApp.surfaceSizeX; + theApp.dest.bottom = theApp.surfaceSizeY; + + DWORD style = WS_POPUP | WS_VISIBLE; + DWORD styleEx = 0; + + if( theApp.videoOption <= VIDEO_4X ) + style |= WS_OVERLAPPEDWINDOW; + else + styleEx = 0; + + if( theApp.videoOption <= VIDEO_4X ) + AdjustWindowRectEx( &theApp.dest, style, TRUE, styleEx ); + else + AdjustWindowRectEx( &theApp.dest, style, FALSE, styleEx ); + + int winSizeX = theApp.dest.right - theApp.dest.left; + int winSizeY = theApp.dest.bottom - theApp.dest.top; + int x = 0, y = 0; + + if( theApp.videoOption <= VIDEO_4X ) { + x = theApp.windowPositionX; + y = theApp.windowPositionY; + } else { + winSizeX = theApp.fsWidth; + winSizeY = theApp.fsHeight; + } + + // Create a window + MainWnd *pWnd = new MainWnd; + theApp.m_pMainWnd = pWnd; + + pWnd->CreateEx( + styleEx, + theApp.wndClass, + "VisualBoyAdvance", + style, + x,y,winSizeX,winSizeY, + NULL, + 0 ); + + if (!(HWND)*pWnd) { + winlog("Error creating Window %08x\n", GetLastError()); + return FALSE; + } + + theApp.updateMenuBar(); + + theApp.adjustDestRect(); + theApp.mode320Available = FALSE; + theApp.mode640Available = FALSE; + theApp.mode800Available = FALSE; + theApp.mode1024Available = FALSE; + theApp.mode1280Available = FALSE; + + + currentAdapter = theApp.fsAdapter; + DISPLAY_DEVICE dev; + ZeroMemory( &dev, sizeof(dev) ); + dev.cb = sizeof(dev); + EnumDisplayDevices( NULL, currentAdapter, &dev, 0 ); + if( theApp.videoOption >= VIDEO_320x240 ) { + // enter full screen mode + DEVMODE mode; + ZeroMemory( &mode, sizeof(mode) ); + mode.dmSize = sizeof(mode); + mode.dmBitsPerPel = theApp.fsColorDepth; + mode.dmPelsWidth = theApp.fsWidth; + mode.dmPelsHeight = theApp.fsHeight; + mode.dmDisplayFrequency = theApp.fsFrequency; + mode.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY; + LONG ret = ChangeDisplaySettingsEx( dev.DeviceName, &mode, NULL, CDS_FULLSCREEN, NULL ); + if( ret != DISP_CHANGE_SUCCESSFUL ) { + systemMessage( 0, "Can not change display mode!" ); + failed = true; + } + } else { + // restore default mode + ChangeDisplaySettingsEx( dev.DeviceName, NULL, NULL, 0, NULL ); + } + + EnableOpenGL(); + initializeFont(); + glPushAttrib( GL_ENABLE_BIT ); + glDisable( GL_DEPTH_TEST ); + glDisable( GL_CULL_FACE ); + glEnable( GL_TEXTURE_2D ); + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + initializeMatrices( theApp.surfaceSizeX, theApp.surfaceSizeY ); + + setVSync( theApp.vsync ); + +#ifdef MMX + if(!theApp.disableMMX) + cpu_mmx = theApp.detectMMX(); + else + cpu_mmx = 0; +#endif + + systemRedShift = 3; + systemGreenShift = 11; + systemBlueShift = 19; + systemColorDepth = 32; + theApp.fsColorDepth = 32; + + Init_2xSaI(32); + + utilUpdateSystemColorMaps(); + theApp.updateFilter(); + theApp.updateIFB(); + pitch = theApp.filterWidth * (systemColorDepth>>3) + 4; + data = pix + ( theApp.sizeX + 1 ) * 4; + renderlist(); + + if(failed) + return false; + + return true; +} + +//clear colour buffer +void OpenGLDisplay::clear() +{ + glClearColor(0.0,0.0,0.0,1.0); + glClear( GL_COLOR_BUFFER_BIT ); +} + +//dlist +void OpenGLDisplay::renderlist() +{ + displaylist = glGenLists(1); //set the cube list to Generate a List + glNewList(displaylist,GL_COMPILE); //compile the new list + glBegin( GL_QUADS ); + + glTexCoord2f( 0.0f, 0.0f ); + glVertex3i( 0, 0, 0 ); + + glTexCoord2f( (float)(width) / size, 0.0f ); + glVertex3i( theApp.surfaceSizeX, 0, 0 ); + + glTexCoord2f( (float)(width) / size, (float)(height) / size ); + glVertex3i( theApp.surfaceSizeX, theApp.surfaceSizeY, 0 ); + + glTexCoord2f( 0.0f, (float)(height) / size ); + glVertex3i( 0, theApp.surfaceSizeY, 0 ); + glEnd(); + glEndList(); +} + +//main render func +void OpenGLDisplay::render() +{ + clear(); + + pitch = theApp.filterWidth * (systemColorDepth>>3) + 4; + data = pix + ( theApp.sizeX + 1 ) * 4; + + // apply pixel filter + if(theApp.filterFunction) { + data = filterData; + theApp.filterFunction( + pix + pitch, + pitch, + (u8*)theApp.delta, + (u8*)filterData, + width * 4 , + theApp.filterWidth, + theApp.filterHeight); + } + + // Texturemap complete texture to surface + // so we have free scaling and antialiasing + + if( theApp.filterFunction ) { + glPixelStorei( GL_UNPACK_ROW_LENGTH, width); + } else { + glPixelStorei( GL_UNPACK_ROW_LENGTH, theApp.sizeX + 1 ); + } + glTexSubImage2D(GL_TEXTURE_2D,0,0,0,width,height,GL_RGBA,GL_UNSIGNED_BYTE,data ); + + + glCallList(displaylist); + + + if( theApp.showSpeed ) { // && ( theApp.videoOption > VIDEO_4X ) ) { + char buffer[30]; + if( theApp.showSpeed == 1 ) { + sprintf( buffer, "%3d%%", systemSpeed ); + } else { + sprintf( buffer, "%3d%%(%d, %d fps)", systemSpeed, systemFrameSkip, theApp.showRenderedFrames ); + } + glFontBegin(&font); + glPushMatrix(); + float fontscale = (float)theApp.surfaceSizeX / 100.0f; + glScalef(fontscale, fontscale, fontscale); + glColor4f(1.0f, 0.25f, 0.25f, 1.0f); + glFontTextOut(buffer, (theApp.surfaceSizeX-(strlen(buffer)*11))/(fontscale*2), (theApp.surfaceSizeY-20)/fontscale, 0); + glPopMatrix(); + glFontEnd(); + glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + glBindTexture( GL_TEXTURE_2D, texture ); + } + if( theApp.screenMessage ) { + if( ( ( GetTickCount() - theApp.screenMessageTime ) < 3000 ) && !theApp.disableStatusMessage ) { + glFontBegin(&font); + glPushMatrix(); + + float fontscale = (float)theApp.surfaceSizeX / 100.0f; + glScalef(fontscale, fontscale, fontscale); + glColor4f(1.0f, 0.25f, 0.25f, 1.0f); + glFontTextOut((char *)((const char *)theApp.screenMessageBuffer), (theApp.surfaceSizeX-(theApp.screenMessageBuffer.GetLength()*11))/(fontscale*2), (theApp.surfaceSizeY-40)/fontscale, 0); + glPopMatrix(); + glFontEnd(); + glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + glBindTexture( GL_TEXTURE_2D, texture ); + } else { + theApp.screenMessage = false; + } + } + + glFlush(); + SwapBuffers( hDC ); + // since OpenGL draws on the back buffer, + // we have to swap it to the front buffer to see the content + +} + +//resize screen +void OpenGLDisplay::resize( int w, int h ) +{ + initializeMatrices( w, h ); + /* Display lists are not mutable, so we have to do this*/ + if (displaylist) + { + glDeleteLists(displaylist, 1); + displaylist = 0; + renderlist(); + } +} + +//update filtering methods +void OpenGLDisplay::updateFiltering( int value ) +{ + switch( value ) + { + case 0: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + break; + case 1: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + break; + } + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP ); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP ); +} + +//init projection matrixes and viewports +void OpenGLDisplay::initializeMatrices( int w, int h ) +{ + if( theApp.fullScreenStretch ) { + glViewport( 0, 0, w, h ); + } else { + calculateDestRect( w, h ); + glViewport( + destRect.left, + destRect.top, + destRect.right - destRect.left, + destRect.bottom - destRect.top ); + } + + glMatrixMode( GL_PROJECTION ); + glLoadIdentity(); + glOrtho( + /* left */ 1.0f, + /* right */ (GLdouble)(w - 1), + /* bottom */ (GLdouble)(h - 1), + /* top */ 1.0f, + 0.0f, + 1.0f ); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + if (displaylist) + { + glDeleteLists(displaylist, 1); + displaylist = 0; + renderlist(); + } +} + +//init font texture +bool OpenGLDisplay::initializeTexture( int w, int h ) +{ + // size = 2^n + // w = 24 > size = 256 = 2^8 + // w = 255 > size = 256 = 2^8 + // w = 256 > size = 512 = 2^9 + // w = 300 > size = 512 = 2^9 + // OpenGL textures have to be square and a power of 2 + // We could use methods that allow tex's to not be powers of two + // but that requires extra OGL extensions + + float n1 = log10( (float)w ) / log10( 2.0f ); + float n2 = log10( (float)h ) / log10( 2.0f ); + float n = ( n1 > n2 ) ? n1 : n2; + + if( ((float)((int)n)) != n ) { + // round up + n = ((float)((int)n)) + 1.0f; + } + + size = pow( 2.0f, n ); + + glGenTextures( 1, &texture ); + glBindTexture( GL_TEXTURE_2D, texture ); + updateFiltering( theApp.glFilter ); + + glTexImage2D( + GL_TEXTURE_2D, + 0, + GL_RGBA, + (GLsizei)size, + (GLsizei)size, + 0, + GL_RGBA, + GL_UNSIGNED_BYTE, + NULL ); + + width = w; + height = h; + + //return ( glGetError() == GL_NO_ERROR) ? true : false; + // Workaround: We usually get GL_INVALID_VALUE, but somehow it works nevertheless + // In consequence, we must not treat it as an error or else the app behaves as if an error occured. + // This in the end results in theApp->input not being created = no input when switching from D3D to OGL + return true; +} + +//turn vsync on or off +void OpenGLDisplay::setVSync( int interval ) +{ + const char *extensions = (const char *)glGetString( GL_EXTENSIONS ); + + if( strstr( extensions, "WGL_EXT_swap_control" ) == 0 ) { + winlog( "Error: WGL_EXT_swap_control extension not supported on your computer.\n" ); + return; + } else { + PFNWGLSWAPINTERVALFARPROC wglSwapIntervalEXT = NULL; + wglSwapIntervalEXT = (PFNWGLSWAPINTERVALFARPROC)wglGetProcAddress( "wglSwapIntervalEXT" ); + if( wglSwapIntervalEXT ) { + wglSwapIntervalEXT( interval ); + } + } +} + +//change render size for fonts and filter data +bool OpenGLDisplay::changeRenderSize( int w, int h ) +{ + if( (width != w) || (height != h) ) { + if( texture != 0 ) { + glDeleteTextures( 1, &texture ); + texture = 0; + } + + if( !initializeTexture( w, h ) ) { + failed = true; + return false; + } + if (filterData) + free(filterData); + filterData = (u8 *)malloc(4*w*h); + } + if (displaylist) + { + glDeleteLists(displaylist, 1); + displaylist = 0; + renderlist(); + } + return true; +} + +//calculate RECTs +void OpenGLDisplay::calculateDestRect( int w, int h ) +{ + float scaleX = (float)w / (float)width; + float scaleY = (float)h / (float)height; + float min = (scaleX < scaleY) ? scaleX : scaleY; + if( theApp.fsMaxScale && (min > theApp.fsMaxScale) ) { + min = (float)theApp.fsMaxScale; + } + destRect.left = 0; + destRect.top = 0; + destRect.right = (LONG)(width * min); + destRect.bottom = (LONG)(height * min); + if( destRect.right != w ) { + LONG diff = (w - destRect.right) / 2; + destRect.left += diff; + destRect.right += diff; + } + if( destRect.bottom != h ) { + LONG diff = (h - destRect.bottom) / 2; + destRect.top += diff; + destRect.bottom += diff; + } + if (displaylist) + { + glDeleteLists(displaylist, 1); + displaylist = 0; + renderlist(); + } +} + +//config options +void OpenGLDisplay::setOption( const char *option, int value ) +{ + if( !_tcscmp( option, _T("vsync") ) ) { + setVSync( value ); + } + + if( !_tcscmp( option, _T("glFilter") ) ) { + updateFiltering( value ); + } + + if( !_tcscmp( option, _T("maxScale") ) ) { + initializeMatrices( theApp.dest.right, theApp.dest.bottom ); + } + + if( !_tcscmp( option, _T("fullScreenStretch") ) ) { + initializeMatrices( theApp.dest.right, theApp.dest.bottom ); + } +} + +//set fullscreen mode +bool OpenGLDisplay::selectFullScreenMode( VIDEO_MODE &mode ) +{ + FullscreenSettings dlg; + dlg.setAPI( this->getType() ); + INT_PTR ret = dlg.DoModal(); + if( ret == IDOK ) { + mode.adapter = dlg.m_device; + switch( dlg.m_colorDepth ) + { + case 30: + // TODO: support + return false; + break; + case 24: + mode.bitDepth = 32; + break; + case 16: + case 15: + mode.bitDepth = 16; + break; + } + mode.width = dlg.m_width; + mode.height = dlg.m_height; + mode.frequency = dlg.m_refreshRate; + return true; + } else { + return false; + } +} + + +IDisplay *newOpenGLDisplay() +{ + return new OpenGLDisplay(); +} + +#endif // #ifndef NO_OGL