/* * OpenClonk, http://www.openclonk.org * * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ * Copyright (c) 2009-2016, 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 "C4ForbidLibraryCompilation.h" #include "graphics/C4DrawGL.h" #include "platform/C4App.h" #include "platform/C4Window.h" #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 : VAOsToBeDeleted) { if (i < hVAOs.size() && hVAOs[i] != 0) { toBeDeleted.push_back(hVAOs[i]); hVAOs[i] = 0; } } glDeleteVertexArrays(toBeDeleted.size(), &toBeDeleted[0]); VAOsToBeDeleted.clear(); } } #ifdef USE_WGL #include static PIXELFORMATDESCRIPTOR pfd; // desired pixel format static HGLRC hrc = nullptr; // 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(int i : vec) { int attributes[] = { WGL_SAMPLE_BUFFERS_ARB, WGL_SAMPLES_ARB }; const unsigned int n_attributes = 2; int results[2]; if(!wglGetPixelFormatAttribivARB(hDC, i, 0, n_attributes, attributes, results)) continue; if( (samples == 0 && results[0] == 0) || (samples > 0 && results[0] == 1 && results[1] == samples)) { return 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 = nullptr; FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM, nullptr, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast(&buffer), 0, nullptr); 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", nullptr, WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, nullptr, nullptr, GetModuleHandle(nullptr), nullptr); 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)) == nullptr) { 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, nullptr); wglDeleteContext(glrc); ReleaseDC(wnd, dc); DestroyWindow(wnd); } }; CStdGLCtx::CStdGLCtx(): this_context(contexts.end()) { } void CStdGLCtx::Clear(bool multisample_change) { Deselect(); if (hDC && pWindow) ReleaseDC(pWindow->renderwnd, hDC); hDC = nullptr; pWindow = nullptr; if (this_context != contexts.end()) { contexts.erase(this_context); this_context = contexts.end(); } if (multisample_change) { assert(!pGL->pCurrCtx); if (hrc) wglDeleteContext(hrc); hrc = nullptr; } } bool CStdGLCtx::Init(C4Window * pWindow, C4AbstractApp *pApp) { // safety if (!pGL || !pWindow) return false; std::unique_ptr tempContext; if (hrc == nullptr) { // Create a temporary context to be able to fetch GL extension pointers try { tempContext = std::make_unique(); 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, nullptr, attribs); } if (!hrc) { LogSilentF(" gl: OpenGL %d.%d not available; falling back to 3.1 emergency context.", REQUESTED_GL_CTX_MAJOR, REQUESTED_GL_CTX_MINOR); // Some older Intel drivers don't support OpenGL 3.2; we don't use (much?) of // that so we'll request a 3.1 context as a fallback. const int attribs[] = { WGL_CONTEXT_FLAGS_ARB, Config.Graphics.DebugOpenGL ? WGL_CONTEXT_DEBUG_BIT_ARB : 0, WGL_CONTEXT_MAJOR_VERSION_ARB, 3, WGL_CONTEXT_MINOR_VERSION_ARB, 1, 0 }; pGL->Workarounds.ForceSoftwareTransform = true; hrc = wglCreateContextAttribsARB(hDC, nullptr, 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 = nullptr; return false; } std::vector CStdGLCtx::EnumerateMultiSamples() const { std::vector result; std::vector vec = EnumeratePixelFormats(hDC); for(int i : vec) { int attributes[] = { WGL_SAMPLE_BUFFERS_ARB, WGL_SAMPLES_ARB }; const unsigned int n_attributes = 2; int results[2]; if(!wglGetPixelFormatAttribivARB(hDC, 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(nullptr, nullptr); pGL->pCurrCtx=nullptr; pGL->RenderTarget=nullptr; } } bool CStdGLCtx::PageFlip() { // flush GL buffer glFlush(); SwapBuffers(hDC); return true; } #elif defined(USE_SDL_MAINLOOP) CStdGLCtx::CStdGLCtx(): pWindow(0), this_context(contexts.end()) { ctx = nullptr; } void CStdGLCtx::Clear(bool multisample_change) { Deselect(); if (ctx) 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); if (!ctx) { LogSilentF(" gl: OpenGL %d.%d not available; falling back to 3.1 emergency context.", REQUESTED_GL_CTX_MAJOR, REQUESTED_GL_CTX_MINOR); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); pGL->Workarounds.ForceSoftwareTransform = true; ctx = SDL_GL_CreateContext(pWindow->window); } if (!ctx) { return pGL->Error(FormatString("SDL_GL_CreateContext: %s", SDL_GetError()).getData()); } // No luck at all? if (!Select(true)) return false; // 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) { if (SDL_GL_MakeCurrent(pWindow->window, ctx) != 0) return pGL->Error(FormatString("SDL_GL_MakeCurrent: %s", SDL_GetError()).getData()); 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_* #ifdef WITH_QT_EDITOR #undef new #undef delete #include #include #include CStdGLCtxQt::CStdGLCtxQt() { context = nullptr; surface = nullptr; } void CStdGLCtxQt::Clear(bool multisample_change) { Deselect(); if (context) { if (!pWindow->glwidget) delete context; delete surface; } pWindow = nullptr; } bool CStdGLCtxQt::Init(C4Window *window, C4AbstractApp *app) { if (!pGL) return false; pWindow = window; if (!pWindow->glwidget) { surface = new QOffscreenSurface(); surface->create(); context = new QOpenGLContext(); QOpenGLContext* share_context = QOpenGLContext::globalShareContext(); if (share_context) context->setShareContext(share_context); if (!context->create()) return false; if (!Select(true)) return false; // 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))); } } else { // The Qt GL widget has its own context context = pWindow->glwidget->context(); } this_context = contexts.insert(contexts.end(), this); return true; } bool CStdGLCtxQt::Select(bool verbose) { if (!pWindow->glwidget) { if (!context->makeCurrent(surface)) return false; } else { // done automatically /* pWindow->glwidget->makeCurrent(); */ } 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 CStdGLCtxQt::Deselect() { if (!pWindow->glwidget) context->doneCurrent(); else { // done automatically /* pWindow->glwidget->doneCurrent(); */ } if (pGL && pGL->pCurrCtx == this) { pGL->pCurrCtx = nullptr; pGL->RenderTarget = nullptr; } } bool CStdGLCtxQt::PageFlip() { // flush GL buffer glFlush(); if (!pWindow) return false; if (!pWindow->glwidget) return false; return true; } #endif #endif // USE_CONSOLE