snes9x/win32/COpenGL.cpp
OV2 d0b9becaab Import latest win32-dev changes
OpengGL support
full unicode support
x64 build support
shader support (D3D+OGL)
DDraw: vsync, dynamic buffer allocation
restored SPC save option
better window position saving
fixed crash during fullscreen switch
2010-09-25 19:35:19 +02:00

558 lines
15 KiB
C++

#include "COpenGL.h"
#include "win32_display.h"
#include "../snes9x.h"
#include "../gfx.h"
#include "../display.h"
#include "wsnes9x.h"
#include "../filter/hq2x.h"
#include "../filter/2xsai.h"
COpenGL::COpenGL(void)
{
hDC = NULL;
hRC = NULL;
hWnd = NULL;
drawTexture = 0;
initDone = false;
quadTextureSize = 0;
filterScale = 0;
afterRenderWidth = 0;
afterRenderHeight = 0;
fullscreen = false;
shaderFunctionsLoaded = false;
shaderCompiled = false;
pboFunctionsLoaded = false;
shaderProgram = 0;
vertexShader = 0;
fragmentShader = 0;
}
COpenGL::~COpenGL(void)
{
DeInitialize();
}
bool COpenGL::Initialize(HWND hWnd)
{
int pfdIndex;
RECT windowRect;
this->hWnd = hWnd;
this->hDC = GetDC(hWnd);
PIXELFORMATDESCRIPTOR pfd=
{
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
16, // Select Our Color Depth
0, 0, 0, 0, 0, 0, // Color Bits Ignored
0, // No Alpha Buffer
0, // Shift Bit Ignored
0, // No Accumulation Buffer
0, 0, 0, 0, // Accumulation Bits Ignored
16, // 16Bit Z-Buffer (Depth Buffer)
0, // No Stencil Buffer
0, // No Auxiliary Buffer
PFD_MAIN_PLANE, // Main Drawing Layer
0, // Reserved
0, 0, 0 // Layer Masks Ignored
};
PIXELFORMATDESCRIPTOR pfdSel;
if(!(pfdIndex=ChoosePixelFormat(hDC,&pfd))) {
DeInitialize();
return false;
}
if(!SetPixelFormat(hDC,pfdIndex,&pfd)) {
DeInitialize();
return false;
}
if(!(hRC=wglCreateContext(hDC))) {
DeInitialize();
return false;
}
if(!wglMakeCurrent(hDC,hRC)) {
DeInitialize();
return false;
}
LoadPBOFunctions();
wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress( "wglSwapIntervalEXT" );
glEnable(GL_VERTEX_ARRAY);
glEnable(GL_TEXTURE_COORD_ARRAY);
glEnable(GL_BLEND);
glEnable(GL_PIXEL_UNPACK_BUFFER);
glEnable(GL_TEXTURE_2D);
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
glOrtho (0.0, 1.0, 0.0, 1.0, -1, 1);
glVertexPointer(2, GL_FLOAT, 0, vertices);
glTexCoordPointer(2, GL_FLOAT, 0, texcoords);
GetClientRect(hWnd,&windowRect);
ChangeRenderSize(windowRect.right,windowRect.bottom);
glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
glClear(GL_COLOR_BUFFER_BIT);
SwapBuffers(hDC);
initDone = true;
return true;
}
void COpenGL::DeInitialize()
{
initDone = false;
if(shaderCompiled)
SetShaders(NULL,NULL);
DestroyDrawSurface();
wglMakeCurrent(NULL,NULL);
if(hRC) {
wglDeleteContext(hRC);
hRC = NULL;
}
if(hDC) {
ReleaseDC(hWnd,hDC);
hDC = NULL;
}
hWnd = NULL;
initDone = false;
quadTextureSize = 0;
filterScale = 0;
afterRenderWidth = 0;
afterRenderHeight = 0;
shaderFunctionsLoaded = false;
shaderCompiled = false;
}
void COpenGL::CreateDrawSurface()
{
unsigned int neededSize;
HRESULT hr;
//we need at least 512 pixels (SNES_WIDTH * 2) so we can start with that value
quadTextureSize = 512;
neededSize = SNES_WIDTH * filterScale;
while(quadTextureSize < neededSize)
quadTextureSize *=2;
if(!drawTexture) {
glGenTextures(1,&drawTexture);
glBindTexture(GL_TEXTURE_2D,drawTexture);
glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,quadTextureSize,quadTextureSize,0,GL_RGB,GL_UNSIGNED_SHORT_5_6_5,NULL);
if(pboFunctionsLoaded) {
glGenBuffers(1,&drawBuffer);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER,drawBuffer);
glBufferData(GL_PIXEL_UNPACK_BUFFER,quadTextureSize*quadTextureSize*2,NULL,GL_STREAM_DRAW);
} else {
noPboBuffer = new BYTE[quadTextureSize*quadTextureSize*2];
}
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
}
ApplyDisplayChanges();
}
void COpenGL::DestroyDrawSurface()
{
if(drawTexture) {
glDeleteTextures(1,&drawTexture);
drawTexture = NULL;
}
if(drawBuffer) {
glDeleteBuffers(1,&drawBuffer);
drawBuffer = NULL;
}
if(noPboBuffer) {
delete [] noPboBuffer;
noPboBuffer = NULL;
}
}
bool COpenGL::ChangeDrawSurfaceSize(unsigned int scale)
{
filterScale = scale;
DestroyDrawSurface();
CreateDrawSurface();
SetupVertices();
return true;
}
void COpenGL::SetupVertices()
{
vertices[0] = 0.0f;
vertices[1] = 0.0f;
vertices[2] = 1.0f;
vertices[3] = 0.0f;
vertices[4] = 1.0f;
vertices[5] = 1.0f;
vertices[6] = 0.0f;
vertices[7] = 1.0f;
float tX = (float)afterRenderWidth / (float)quadTextureSize;
float tY = (float)afterRenderHeight / (float)quadTextureSize;
texcoords[0] = 0.0f;
texcoords[1] = tY;
texcoords[2] = tX;
texcoords[3] = tY;
texcoords[4] = tX;
texcoords[5] = 0.0f;
texcoords[6] = 0.0f;
texcoords[7] = 0.0f;
}
void COpenGL::Render(SSurface Src)
{
SSurface Dst;
RECT dstRect;
unsigned int newFilterScale;
GLenum error;
if(!initDone) return;
//create a new draw surface if the filter scale changes
//at least factor 2 so we can display unscaled hi-res images
newFilterScale = max(2,max(GetFilterScale(GUI.ScaleHiRes),GetFilterScale(GUI.Scale)));
if(newFilterScale!=filterScale) {
ChangeDrawSurfaceSize(newFilterScale);
}
if(pboFunctionsLoaded) {
Dst.Surface = (unsigned char *)glMapBuffer(GL_PIXEL_UNPACK_BUFFER,GL_WRITE_ONLY);
} else {
Dst.Surface = noPboBuffer;
}
Dst.Height = quadTextureSize;
Dst.Width = quadTextureSize;
Dst.Pitch = quadTextureSize * 2;
RenderMethod (Src, Dst, &dstRect);
if(!Settings.AutoDisplayMessages) {
WinSetCustomDisplaySurface((void *)Dst.Surface, Dst.Pitch/2, dstRect.right-dstRect.left, dstRect.bottom-dstRect.top, GetFilterScale(CurrentScale));
S9xDisplayMessages ((uint16*)Dst.Surface, Dst.Pitch/2, dstRect.right-dstRect.left, dstRect.bottom-dstRect.top, GetFilterScale(CurrentScale));
}
if(pboFunctionsLoaded)
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
if(afterRenderHeight != dstRect.bottom || afterRenderWidth != dstRect.right) {
afterRenderHeight = dstRect.bottom;
afterRenderWidth = dstRect.right;
ApplyDisplayChanges();
}
if (shaderCompiled) {
GLint location;
float inputSize[2] = { afterRenderWidth, afterRenderHeight };
location = glGetUniformLocation (shaderProgram, "rubyInputSize");
glUniform2fv (location, 1, inputSize);
RECT windowSize;
GetClientRect(hWnd,&windowSize);
float outputSize[2] = {GUI.Stretch?windowSize.right:afterRenderWidth,
GUI.Stretch?windowSize.bottom:afterRenderHeight };
location = glGetUniformLocation (shaderProgram, "rubyOutputSize");
glUniform2fv (location, 1, outputSize);
float textureSize[2] = { quadTextureSize, quadTextureSize };
location = glGetUniformLocation (shaderProgram, "rubyTextureSize");
glUniform2fv (location, 1, textureSize);
}
glPixelStorei(GL_UNPACK_ROW_LENGTH, quadTextureSize);
glTexSubImage2D (GL_TEXTURE_2D,0,0,0,dstRect.right-dstRect.left,dstRect.bottom-dstRect.top,GL_RGB,GL_UNSIGNED_SHORT_5_6_5,pboFunctionsLoaded?0:noPboBuffer);
glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays (GL_QUADS, 0, 4);
glFlush();
SwapBuffers(hDC);
}
bool COpenGL::ChangeRenderSize(unsigned int newWidth, unsigned int newHeight)
{
RECT displayRect=CalculateDisplayRect(afterRenderWidth,afterRenderHeight,newWidth,newHeight);
glViewport(displayRect.left,newHeight-displayRect.bottom,displayRect.right-displayRect.left,displayRect.bottom-displayRect.top);
SetupVertices();
return true;
}
bool COpenGL::ApplyDisplayChanges(void)
{
if(GUI.BilinearFilter) {
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
} else {
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
}
if(wglSwapIntervalEXT) {
wglSwapIntervalEXT(GUI.Vsync?1:0);
}
if(GUI.shaderEnabled && GUI.GLSLvertexShaderFileName && GUI.GLSLfragmentShaderFileName)
SetShaders(GUI.GLSLfragmentShaderFileName,GUI.GLSLvertexShaderFileName);
else
SetShaders(NULL,NULL);
RECT windowSize;
GetClientRect(hWnd,&windowSize);
ChangeRenderSize(windowSize.right,windowSize.bottom);
SetupVertices();
return true;
}
bool COpenGL::SetFullscreen(bool fullscreen)
{
if(!initDone)
return false;
if(this->fullscreen==fullscreen)
return true;
this->fullscreen = fullscreen;
if(fullscreen) {
DEVMODE dmScreenSettings={0};
dmScreenSettings.dmSize=sizeof(dmScreenSettings);
dmScreenSettings.dmPelsWidth = GUI.FullscreenMode.width;
dmScreenSettings.dmPelsHeight = GUI.FullscreenMode.height;
dmScreenSettings.dmBitsPerPel = GUI.FullscreenMode.depth;
dmScreenSettings.dmDisplayFrequency = GUI.FullscreenMode.rate;
dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT|DM_DISPLAYFREQUENCY;
if(ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL) {
this->fullscreen = false;
return false;
}
ChangeRenderSize(GUI.FullscreenMode.width,GUI.FullscreenMode.height);
} else {
ChangeDisplaySettings(NULL,0);
}
return true;
}
void COpenGL::SetSnes9xColorFormat()
{
GUI.ScreenDepth = 16;
GUI.BlueShift = 0;
GUI.GreenShift = 6;
GUI.RedShift = 11;
S9xSetRenderPixelFormat (RGB565);
S9xBlit2xSaIFilterInit();
S9xBlitHQ2xFilterInit();
GUI.NeedDepthConvert = FALSE;
GUI.DepthConverted = TRUE;
return;
}
void COpenGL::EnumModes(std::vector<dMode> *modeVector)
{
DISPLAY_DEVICE dd;
dd.cb = sizeof(dd);
DWORD dev = 0;
int iMode = 0;
dMode mode;
while (EnumDisplayDevices(0, dev, &dd, 0))
{
if (!(dd.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) && (dd.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE))
{
DEVMODE dm;
ZeroMemory(&dm, sizeof(dm));
dm.dmSize = sizeof(dm);
iMode = 0;
while(EnumDisplaySettings(dd.DeviceName,iMode,&dm)) {
if(dm.dmBitsPerPel>=16) {
mode.width = dm.dmPelsWidth;
mode.height = dm.dmPelsHeight;
mode.rate = dm.dmDisplayFrequency;
mode.depth = dm.dmBitsPerPel;
modeVector->push_back(mode);
}
iMode++;
}
}
dev++;
}
}
bool COpenGL::LoadPBOFunctions()
{
if(pboFunctionsLoaded)
return true;
const char *extensions = (const char *) glGetString(GL_EXTENSIONS);
if(extensions && strstr(extensions, "pixel_buffer_object")) {
glGenBuffers = (PFNGLGENBUFFERSPROC)wglGetProcAddress("glGenBuffers");
glBindBuffer = (PFNGLBINDBUFFERPROC)wglGetProcAddress("glBindBuffer");
glBufferData = (PFNGLBUFFERDATAPROC)wglGetProcAddress("glBufferData");
glDeleteBuffers = (PFNGLDELETEBUFFERSPROC)wglGetProcAddress("glDeleteBuffers");
glMapBuffer = (PFNGLMAPBUFFERPROC)wglGetProcAddress("glMapBuffer");
glUnmapBuffer = (PFNGLUNMAPBUFFERPROC)wglGetProcAddress("glUnmapBuffer");
if(glGenBuffers && glBindBuffer && glBufferData && glDeleteBuffers && glMapBuffer) {
pboFunctionsLoaded = true;
}
}
return pboFunctionsLoaded;
}
bool COpenGL::LoadShaderFunctions()
{
if(shaderFunctionsLoaded)
return true;
const char *extensions = (const char *) glGetString(GL_EXTENSIONS);
if(extensions && strstr(extensions, "fragment_program")) {
glCreateProgram = (PFNGLCREATEPROGRAMPROC) wglGetProcAddress ("glCreateProgram");
glCreateShader = (PFNGLCREATESHADERPROC) wglGetProcAddress ("glCreateShader");
glCompileShader = (PFNGLCOMPILESHADERPROC) wglGetProcAddress ("glCompileShader");
glDeleteShader = (PFNGLDELETESHADERPROC) wglGetProcAddress ("glDeleteShader");
glDeleteProgram = (PFNGLDELETEPROGRAMPROC) wglGetProcAddress ("glDeleteProgram");
glAttachShader = (PFNGLATTACHSHADERPROC) wglGetProcAddress ("glAttachShader");
glDetachShader = (PFNGLDETACHSHADERPROC) wglGetProcAddress ("glDetachShader");
glLinkProgram = (PFNGLLINKPROGRAMPROC) wglGetProcAddress ("glLinkProgram");
glUseProgram = (PFNGLUSEPROGRAMPROC) wglGetProcAddress ("glUseProgram");
glShaderSource = (PFNGLSHADERSOURCEPROC) wglGetProcAddress ("glShaderSource");
glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC) wglGetProcAddress ("glGetUniformLocation");
glUniform2fv = (PFNGLUNIFORM2FVPROC) wglGetProcAddress ("glUniform2fv");
if(glCreateProgram &&
glCreateShader &&
glCompileShader &&
glDeleteShader &&
glDeleteProgram &&
glAttachShader &&
glDetachShader &&
glLinkProgram &&
glUseProgram &&
glShaderSource &&
glGetUniformLocation &&
glUniform2fv) {
shaderFunctionsLoaded = true;
}
}
return shaderFunctionsLoaded;
}
char *ReadFileContents(const TCHAR *filename)
{
HANDLE hFile;
DWORD size;
DWORD bytesRead;
char *contents;
hFile = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN , 0);
if(hFile == INVALID_HANDLE_VALUE){
return NULL;
}
size = GetFileSize(hFile,NULL);
contents = new char[size+1];
if(!ReadFile(hFile,contents,size,&bytesRead,NULL)) {
CloseHandle(hFile);
delete[] contents;
return NULL;
}
CloseHandle(hFile);
contents[size] = '\0';
return contents;
}
bool COpenGL::SetShaders(const TCHAR *fragmentFileName,const TCHAR *vertexFileName)
{
char *fragment=NULL, *vertex=NULL;
shaderCompiled = false;
if(fragmentShader) {
glDetachShader(shaderProgram,fragmentShader);
glDeleteShader(fragmentShader);
fragmentShader = 0;
}
if(vertexShader) {
glDetachShader(shaderProgram,vertexShader);
glDeleteShader(vertexShader);
vertexShader = 0;
}
if(shaderProgram) {
glUseProgram(0);
glDeleteProgram(shaderProgram);
shaderProgram = 0;
}
if(fragmentFileName==NULL||vertexFileName==NULL)
return true;
if(!LoadShaderFunctions()) {
MessageBox(NULL, TEXT("Unable to load OpenGL shader functions"), TEXT("Shader Loading Error"),
MB_OK|MB_ICONEXCLAMATION);
return false;
}
if(*fragmentFileName!=TEXT('\0')) {
fragment = ReadFileContents(fragmentFileName);
if (!fragment) {
TCHAR errorMsg[MAX_PATH + 50];
_stprintf(errorMsg,TEXT("Error loading GLSL fragment shader file:\n%s"),fragmentFileName);
MessageBox(NULL, errorMsg, TEXT("Shader Loading Error"),
MB_OK|MB_ICONEXCLAMATION);
}
}
if(*vertexFileName!=TEXT('\0')) {
vertex = ReadFileContents (vertexFileName);
if (!vertex) {
TCHAR errorMsg[MAX_PATH + 50];
_stprintf(errorMsg,TEXT("Error loading GLSL vertex shader file:\n%s"),vertexFileName);
MessageBox(NULL, errorMsg, TEXT("Shader Loading Error"),
MB_OK|MB_ICONEXCLAMATION);
}
}
shaderProgram = glCreateProgram ();
if(vertex) {
vertexShader = glCreateShader (GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, (const GLchar **)&vertex, NULL);
glCompileShader(vertexShader);
glAttachShader(shaderProgram, vertexShader);
delete[] vertex;
}
if(fragment) {
fragmentShader = glCreateShader (GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, (const GLchar **)&fragment, NULL);
glCompileShader(fragmentShader);
glAttachShader(shaderProgram, fragmentShader);
delete[] fragment;
}
glLinkProgram(shaderProgram);
glUseProgram(shaderProgram);
shaderCompiled = true;
return true;
}