/* * OpenClonk, http://www.openclonk.org * * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ * Copyright (c) 2009-2013, The OpenClonk Team and contributors * * Distributed under the terms of the ISC license; see accompanying file * "COPYING" for details. * * "Clonk" is a registered trademark of Matthes Bender, used with permission. * See accompanying file "TRADEMARK" for details. * * To redistribute this file separately, substitute the full license texts * for the above references. */ /* OpenGL implementation of NewGfx, the context */ #include "C4Include.h" #include #include #include #ifndef USE_CONSOLE static const int REQUESTED_GL_CTX_MAJOR = 3; static const int REQUESTED_GL_CTX_MINOR = 2; std::list CStdGLCtx::contexts; void CStdGLCtx::SelectCommon() { pGL->pCurrCtx = this; // set some default states glDisable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); glDisable(GL_CULL_FACE); glEnable(GL_BLEND); // Delete pending VAOs std::vector toBeDeleted; if (!VAOsToBeDeleted.empty()) { for (unsigned int i = 0; i < VAOsToBeDeleted.size(); ++i) { if (VAOsToBeDeleted[i] < hVAOs.size() && hVAOs[VAOsToBeDeleted[i]] != 0) { toBeDeleted.push_back(hVAOs[VAOsToBeDeleted[i]]); hVAOs[VAOsToBeDeleted[i]] = 0; } } glDeleteVertexArrays(toBeDeleted.size(), &toBeDeleted[0]); VAOsToBeDeleted.clear(); } } #ifdef USE_WGL #include static PIXELFORMATDESCRIPTOR pfd; // desired pixel format static HGLRC hrc = 0; // Enumerate available pixel formats. Choose the best pixel format in // terms of color and depth buffer bits and then return all formats with // different multisampling settings. If there are more then one, then choose // the one with highest depth buffer size and lowest stencil and auxiliary // buffer sizes since we don't use them in Clonk. static std::vector EnumeratePixelFormats(HDC hdc) { std::vector result; if(!wglGetPixelFormatAttribivARB) return result; int n_formats; int attributes = WGL_NUMBER_PIXEL_FORMATS_ARB; if(!wglGetPixelFormatAttribivARB(hdc, 0, 0, 1, &attributes, &n_formats)) return result; for(int i = 1; i < n_formats+1; ++i) { int new_attributes[] = { WGL_DRAW_TO_WINDOW_ARB, WGL_SUPPORT_OPENGL_ARB, WGL_DOUBLE_BUFFER_ARB, WGL_COLOR_BITS_ARB, WGL_DEPTH_BITS_ARB, WGL_STENCIL_BITS_ARB, WGL_AUX_BUFFERS_ARB, WGL_SAMPLE_BUFFERS_ARB, WGL_SAMPLES_ARB }; const unsigned int nnew_attributes = sizeof(new_attributes)/sizeof(int); int new_results[nnew_attributes]; if(!wglGetPixelFormatAttribivARB(hdc, i, 0, nnew_attributes, new_attributes, new_results)) continue; if(!new_results[0] || !new_results[1] || !new_results[2]) continue; if(new_results[3] < 16 || new_results[4] < 16) continue; // require at least 16 bits per pixel in color and depth // For no MS we do find a pixel format with depth 32 on my (ck's) computer, // however, when choosing it then texturing does not work anymore. I am not // exactly sure what the cause of that is, so let's not choose that one for now. if(new_results[4] > 24) continue; // Multisampling with just one sample is equivalent to no-multisampling, // so don't include that in the result. if(new_results[7] == 1 && new_results[8] == 1) continue; if(result.empty()) { result.push_back(i); } else { int old_attributes[] = { WGL_COLOR_BITS_ARB }; const unsigned int nold_attributes = sizeof(old_attributes)/sizeof(int); int old_results[nold_attributes]; if(!wglGetPixelFormatAttribivARB(hdc, result[0], 0, nold_attributes, old_attributes, old_results)) continue; if(new_results[3] > old_results[0]) { result.clear(); result.push_back(i); } else if(new_results[3] == old_results[0]) { unsigned int j; for(j = 0; j < result.size(); ++j) { int equiv_attributes[] = { WGL_DEPTH_BITS_ARB, WGL_STENCIL_BITS_ARB, WGL_AUX_BUFFERS_ARB, WGL_SAMPLE_BUFFERS_ARB, WGL_SAMPLES_ARB }; const unsigned int nequiv_attributes = sizeof(equiv_attributes)/sizeof(int); int equiv_results[nequiv_attributes]; if(!wglGetPixelFormatAttribivARB(hdc, result[j], 0, nequiv_attributes, equiv_attributes, equiv_results)) continue; if(new_results[7] == equiv_results[3] && new_results[8] == equiv_results[4]) { if(new_results[4] > equiv_results[0] || (new_results[4] == equiv_results[0] && (new_results[5] < equiv_results[1] || (new_results[5] == equiv_results[1] && new_results[6] < equiv_results[2])))) result[j] = i; break; } } if(j == result.size()) result.push_back(i); } } } return result; } static int GetPixelFormatForMS(HDC hDC, int samples) { std::vector vec = EnumeratePixelFormats(hDC); for(unsigned int i = 0; i < vec.size(); ++i) { int attributes[] = { WGL_SAMPLE_BUFFERS_ARB, WGL_SAMPLES_ARB }; const unsigned int n_attributes = 2; int results[2]; if(!wglGetPixelFormatAttribivARB(hDC, vec[i], 0, n_attributes, attributes, results)) continue; if( (samples == 0 && results[0] == 0) || (samples > 0 && results[0] == 1 && results[1] == samples)) { return vec[i]; } } return 0; } class WinAPIError : public std::runtime_error { public: typedef DWORD error_code; WinAPIError() : WinAPIError(GetLastError()) {} WinAPIError(error_code err) : std::runtime_error(format_error(err)) {} private: static std::string format_error(error_code err) { LPWSTR buffer = 0; FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM, 0, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast(&buffer), 0, 0); StdStrBuf str(buffer); LocalFree(buffer); return std::string(str.getData(), str.getLength()); } }; class GLTempContext { HWND wnd; HDC dc; HGLRC glrc; public: GLTempContext() { wnd = CreateWindowExW(0, L"STATIC", 0, WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, 0, 0, GetModuleHandle(0), 0); if (!wnd) throw WinAPIError(); dc = GetDC(wnd); auto pfd = PIXELFORMATDESCRIPTOR(); pfd.nSize = sizeof(pfd); pfd.nVersion = 1; pfd.dwFlags = PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW; pfd.iPixelType = PFD_TYPE_RGBA; pfd.iLayerType = PFD_MAIN_PLANE; int format = ChoosePixelFormat(dc, &pfd); if (!format || !SetPixelFormat(dc, format, &pfd) || (glrc = wglCreateContext(dc)) == 0) { DWORD err = GetLastError(); ReleaseDC(wnd, dc); DestroyWindow(wnd); throw WinAPIError(err); } if (!wglMakeCurrent(dc, glrc)) { DWORD err = GetLastError(); wglDeleteContext(glrc); ReleaseDC(wnd, dc); DestroyWindow(wnd); throw WinAPIError(err); } } ~GLTempContext() { if (glrc == wglGetCurrentContext()) wglMakeCurrent(dc, 0); wglDeleteContext(glrc); ReleaseDC(wnd, dc); DestroyWindow(wnd); } }; CStdGLCtx::CStdGLCtx(): pWindow(0), hDC(0), this_context(contexts.end()) { } void CStdGLCtx::Clear(bool multisample_change) { Deselect(); if (hDC && pWindow) ReleaseDC(pWindow->renderwnd, hDC); hDC = 0; pWindow = 0; if (this_context != contexts.end()) { contexts.erase(this_context); this_context = contexts.end(); } if (multisample_change) { assert(!pGL->pCurrCtx); if (hrc) wglDeleteContext(hrc); hrc = 0; } } bool CStdGLCtx::Init(C4Window * pWindow, C4AbstractApp *pApp) { // safety if (!pGL || !pWindow) return false; std::unique_ptr tempContext; if (hrc == 0) { // Create a temporary context to be able to fetch GL extension pointers try { tempContext.reset(new GLTempContext); glewExperimental = GL_TRUE; GLenum err = glewInit(); if(err != GLEW_OK) { // Problem: glewInit failed, something is seriously wrong. pGL->Error(reinterpret_cast(glewGetErrorString(err))); return false; } } catch (const WinAPIError &e) { pGL->Error((std::string(" gl: Unable to create temporary context: ") + e.what()).c_str()); return false; } } // store window this->pWindow = pWindow; // get DC hDC = GetDC(pWindow->renderwnd); if(!hDC) { pGL->Error(" gl: Error getting DC"); return false; } if (hrc) { SetPixelFormat(hDC, pGL->iPixelFormat, &pfd); } else { // Choose a good pixel format. int pixel_format; if((pixel_format = GetPixelFormatForMS(hDC, Config.Graphics.MultiSampling)) == 0) if((pixel_format = GetPixelFormatForMS(hDC, 0)) != 0) Config.Graphics.MultiSampling = 0; if (!pixel_format) { pGL->Error(" gl: Error choosing pixel format"); } else { ZeroMemory(&pfd, sizeof(pfd)); pfd.nSize = sizeof(pfd); if(!DescribePixelFormat(hDC, pixel_format, sizeof(pfd), &pfd)) { pGL->Error(" gl: Error describing chosen pixel format"); } else if(!SetPixelFormat(hDC, pixel_format, &pfd)) { pGL->Error(" gl: Error setting chosen pixel format"); } else { // create context if (wglCreateContextAttribsARB) { const int attribs[] = { WGL_CONTEXT_FLAGS_ARB, Config.Graphics.DebugOpenGL ? WGL_CONTEXT_DEBUG_BIT_ARB : 0, WGL_CONTEXT_MAJOR_VERSION_ARB, REQUESTED_GL_CTX_MAJOR, WGL_CONTEXT_MINOR_VERSION_ARB, REQUESTED_GL_CTX_MINOR, WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, 0 }; hrc = wglCreateContextAttribsARB(hDC, 0, attribs); } else { DebugLog(" gl: wglCreateContextAttribsARB not available; creating default context."); hrc = wglCreateContext(hDC); } if(!hrc) { pGL->Error(" gl: Error creating gl context"); } pGL->iPixelFormat = pixel_format; } } } if (hrc) { Select(); // After selecting the new context, we have to reinitialize GLEW to // update its function pointers - the driver may elect to expose // different extensions depending on the context attributes glewExperimental = GL_TRUE; GLenum err = glewInit(); if (err != GLEW_OK) { // Uh. This is a problem. pGL->Error(reinterpret_cast(glewGetErrorString(err))); return false; } this_context = contexts.insert(contexts.end(), this); return true; } ReleaseDC(pWindow->renderwnd, hDC); hDC = NULL; return false; } std::vector CStdGLCtx::EnumerateMultiSamples() const { std::vector result; std::vector vec = EnumeratePixelFormats(hDC); for(unsigned int i = 0; i < vec.size(); ++i) { int attributes[] = { WGL_SAMPLE_BUFFERS_ARB, WGL_SAMPLES_ARB }; const unsigned int n_attributes = 2; int results[2]; if(!wglGetPixelFormatAttribivARB(hDC, vec[i], 0, n_attributes, attributes, results)) continue; if(results[0] == 1) result.push_back(results[1]); } return result; } bool CStdGLCtx::Select(bool verbose) { // safety if (!pGL || !hrc) return false; // make context current if (!wglMakeCurrent (hDC, hrc)) { pGL->Error("Unable to select context."); return false; } SelectCommon(); // update clipper - might have been done by UpdateSize // however, the wrong size might have been assumed if (!pGL->UpdateClipper()) return false; // success return true; } void CStdGLCtx::Deselect() { if (pGL && pGL->pCurrCtx == this) { wglMakeCurrent(NULL, NULL); pGL->pCurrCtx=NULL; pGL->RenderTarget=NULL; } } bool CStdGLCtx::PageFlip() { // flush GL buffer glFlush(); SwapBuffers(hDC); return true; } #elif defined(USE_GTK) #include #include #include #include namespace { void InitGLXPointers() { glXGetVisualFromFBConfig = (PFNGLXGETVISUALFROMFBCONFIGPROC)(glXGetProcAddress((const GLubyte*)"glXGetVisualFromFBConfig")); glXChooseFBConfig = (PFNGLXCHOOSEFBCONFIGPROC)(glXGetProcAddress((const GLubyte*)"glXChooseFBConfig")); glXCreateNewContext = (PFNGLXCREATENEWCONTEXTPROC)(glXGetProcAddress((const GLubyte*)"glXCreateNewContext")); } } CStdGLCtx::CStdGLCtx(): pWindow(0), ctx(0), this_context(contexts.end()) { } void CStdGLCtx::Clear(bool multisample_change) { Deselect(); if (ctx) { Display * const dpy = gdk_x11_display_get_xdisplay(gdk_display_get_default()); glXDestroyContext(dpy, (GLXContext)ctx); ctx = 0; } pWindow = 0; if (this_context != contexts.end()) { contexts.erase(this_context); this_context = contexts.end(); } } static int GLXErrorHandler(Display * dpy, XErrorEvent * ev) { return 0; } bool CStdGLCtx::Init(C4Window * pWindow, C4AbstractApp *) { // safety if (!pGL) return false; // store window this->pWindow = pWindow; Display * const dpy = gdk_x11_display_get_xdisplay(gdk_display_get_default()); InitGLXPointers(); if (!glXGetVisualFromFBConfig || !glXChooseFBConfig || !glXCreateNewContext) { return pGL->Error(" gl: Unable to retrieve GLX 1.4 entry points"); } XVisualInfo *vis_info = glXGetVisualFromFBConfig(dpy, pWindow->Info); // Create base context so we can initialize GLEW GLXContext dummy_ctx = glXCreateContext(dpy, vis_info, 0, True); XFree(vis_info); glXMakeCurrent(dpy, pWindow->renderwnd, dummy_ctx); glewExperimental = GL_TRUE; GLenum err = glewInit(); if (err != GLEW_OK) { return pGL->Error((const char*)glewGetErrorString(err)); } // Create Context with sharing (if this is the main context, our ctx will be 0, so no sharing) const int attribs[] = { GLX_CONTEXT_MAJOR_VERSION_ARB, REQUESTED_GL_CTX_MAJOR, GLX_CONTEXT_MINOR_VERSION_ARB, REQUESTED_GL_CTX_MINOR, GLX_CONTEXT_FLAGS_ARB, (Config.Graphics.DebugOpenGL ? GLX_CONTEXT_DEBUG_BIT_ARB : 0), GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, None }; GLXContext share_context = (pGL->pMainCtx != this) ? static_cast(pGL->pMainCtx->ctx) : 0; if (glXCreateContextAttribsARB) { int (*oldErrorHandler) (Display *, XErrorEvent *) = XSetErrorHandler(GLXErrorHandler); ctx = glXCreateContextAttribsARB(dpy, pWindow->Info, share_context, True, attribs); XSync(dpy, False); XSetErrorHandler(oldErrorHandler); } if(!ctx) { Log(" gl: falling back to attribute-less context creation."); ctx = glXCreateNewContext(dpy, pWindow->Info, GLX_RGBA_TYPE, share_context, True); } glXMakeCurrent(dpy, None, NULL); glXDestroyContext(dpy, dummy_ctx); // No luck? if (!ctx) return pGL->Error(" gl: Unable to create context"); if (!Select(true)) return pGL->Error(" gl: Unable to select context"); // init extensions glewExperimental = GL_TRUE; err = glewInit(); if (GLEW_OK != err) { // Problem: glewInit failed, something is seriously wrong. return pGL->Error(reinterpret_cast(glewGetErrorString(err))); } this_context = contexts.insert(contexts.end(), this); return true; } bool CStdGLCtx::Select(bool verbose) { // safety if (!pGL || !ctx) { if (verbose) pGL->Error(" gl: pGL is zero"); return false; } Display * const dpy = gdk_x11_display_get_xdisplay(gdk_display_get_default()); // make context current if (!pWindow->renderwnd || !glXMakeCurrent(dpy, pWindow->renderwnd, (GLXContext)ctx)) { if (verbose) pGL->Error(" gl: glXMakeCurrent failed"); return false; } SelectCommon(); // update clipper - might have been done by UpdateSize // however, the wrong size might have been assumed if (!pGL->UpdateClipper()) { if (verbose) pGL->Error(" gl: UpdateClipper failed"); return false; } // success return true; } void CStdGLCtx::Deselect() { if (pGL && pGL->pCurrCtx == this) { Display * const dpy = gdk_x11_display_get_xdisplay(gdk_display_get_default()); glXMakeCurrent(dpy, None, NULL); pGL->pCurrCtx = 0; pGL->RenderTarget = 0; } } bool CStdGLCtx::PageFlip() { // flush GL buffer glFlush(); if (!pWindow || !pWindow->renderwnd) return false; Display * const dpy = gdk_x11_display_get_xdisplay(gdk_display_get_default()); glXSwapBuffers(dpy, pWindow->renderwnd); return true; } #elif defined(USE_SDL_MAINLOOP) CStdGLCtx::CStdGLCtx(): pWindow(0), this_context(contexts.end()) { } void CStdGLCtx::Clear(bool multisample_change) { SDL_GL_DeleteContext(ctx); ctx = 0; pWindow = 0; if (this_context != contexts.end()) { contexts.erase(this_context); this_context = contexts.end(); } } bool CStdGLCtx::Init(C4Window * pWindow, C4AbstractApp *) { // safety if (!pGL) return false; // store window this->pWindow = pWindow; ctx = SDL_GL_CreateContext(pWindow->window); // No luck at all? if (!Select(true)) return pGL->Error(" gl: Unable to select context"); // init extensions glewExperimental = GL_TRUE; GLenum err = glewInit(); if (GLEW_OK != err) { // Problem: glewInit failed, something is seriously wrong. return pGL->Error(reinterpret_cast(glewGetErrorString(err))); } this_context = contexts.insert(contexts.end(), this); return true; } bool CStdGLCtx::Select(bool verbose) { SDL_GL_MakeCurrent(pWindow->window, ctx); SelectCommon(); // update clipper - might have been done by UpdateSize // however, the wrong size might have been assumed if (!pGL->UpdateClipper()) { if (verbose) pGL->Error(" gl: UpdateClipper failed"); return false; } // success return true; } void CStdGLCtx::Deselect() { if (pGL && pGL->pCurrCtx == this) { pGL->pCurrCtx = 0; pGL->RenderTarget = 0; } } bool CStdGLCtx::PageFlip() { // flush GL buffer glFlush(); if (!pWindow) return false; SDL_GL_SwapWindow(pWindow->window); return true; } #endif //USE_GTK/USE_SDL_MAINLOOP #endif // USE_CONSOLE