/****************************************************************************** * The MIT License (MIT) * * Copyright (c) 2018 Baldur Karlsson * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. ******************************************************************************/ #include "driver/gl/gl_common.h" #include "driver/gl/wgl_dispatch_table.h" #define WINDOW_CLASS_NAME L"renderdocGLclass" class WGLPlatform : public GLPlatform { bool MakeContextCurrent(GLWindowingData data) { if(WGL.wglMakeCurrent) return WGL.wglMakeCurrent(data.DC, data.ctx) == TRUE; return false; } GLWindowingData CloneTemporaryContext(GLWindowingData share) { GLWindowingData ret = share; ret.ctx = NULL; if(!WGL.wglCreateContextAttribsARB) return ret; const int attribs[] = { WGL_CONTEXT_MAJOR_VERSION_ARB, 3, WGL_CONTEXT_MINOR_VERSION_ARB, 2, WGL_CONTEXT_FLAGS_ARB, 0, WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, 0, 0, }; ret.ctx = WGL.wglCreateContextAttribsARB(share.DC, share.ctx, attribs); return ret; } void DeleteClonedContext(GLWindowingData context) { if(context.ctx && WGL.wglDeleteContext) WGL.wglDeleteContext(context.ctx); } void DeleteReplayContext(GLWindowingData context) { if(WGL.wglMakeCurrent && WGL.wglDeleteContext) { WGL.wglMakeCurrent(NULL, NULL); WGL.wglDeleteContext(context.ctx); ::ReleaseDC(context.wnd, context.DC); ::DestroyWindow(context.wnd); } } void SwapBuffers(GLWindowingData context) { if(WGL.SwapBuffers) WGL.SwapBuffers(context.DC); } void GetOutputWindowDimensions(GLWindowingData context, int32_t &w, int32_t &h) { RECT rect = {0}; ::GetClientRect(context.wnd, &rect); w = rect.right - rect.left; h = rect.bottom - rect.top; } bool IsOutputWindowVisible(GLWindowingData context) { return (IsWindowVisible(context.wnd) == TRUE); } GLWindowingData MakeOutputWindow(WindowingData window, bool depth, GLWindowingData share_context) { GLWindowingData ret = {}; if(!WGL.wglGetPixelFormatAttribivARB || !WGL.wglCreateContextAttribsARB) return ret; RDCASSERT(window.system == WindowingSystem::Win32 || window.system == WindowingSystem::Unknown, window.system); HWND w = window.win32.window; if(w == NULL) w = CreateWindowEx(WS_EX_CLIENTEDGE, WINDOW_CLASS_NAME, L"", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, GetModuleHandle(NULL), NULL); HDC DC = GetDC(w); PIXELFORMATDESCRIPTOR pfd = {0}; int attrib = eWGL_NUMBER_PIXEL_FORMATS_ARB; int value = 1; WGL.wglGetPixelFormatAttribivARB(DC, 1, 0, 1, &attrib, &value); int pf = 0; int numpfs = value; for(int i = 1; i <= numpfs; i++) { // verify that we have the properties we want attrib = eWGL_DRAW_TO_WINDOW_ARB; WGL.wglGetPixelFormatAttribivARB(DC, i, 0, 1, &attrib, &value); if(value == 0) continue; attrib = eWGL_ACCELERATION_ARB; WGL.wglGetPixelFormatAttribivARB(DC, i, 0, 1, &attrib, &value); if(value == eWGL_NO_ACCELERATION_ARB) continue; attrib = eWGL_SUPPORT_OPENGL_ARB; WGL.wglGetPixelFormatAttribivARB(DC, i, 0, 1, &attrib, &value); if(value == 0) continue; attrib = eWGL_DOUBLE_BUFFER_ARB; WGL.wglGetPixelFormatAttribivARB(DC, i, 0, 1, &attrib, &value); if(value == 0) continue; attrib = eWGL_PIXEL_TYPE_ARB; WGL.wglGetPixelFormatAttribivARB(DC, i, 0, 1, &attrib, &value); if(value != eWGL_TYPE_RGBA_ARB) continue; // we have an opengl-capable accelerated RGBA context. // we use internal framebuffers to do almost all rendering, so we just need // RGB (color bits > 24) and SRGB buffer. attrib = eWGL_COLOR_BITS_ARB; WGL.wglGetPixelFormatAttribivARB(DC, i, 0, 1, &attrib, &value); if(value < 24) continue; attrib = WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB; WGL.wglGetPixelFormatAttribivARB(DC, i, 0, 1, &attrib, &value); if(value == 0) continue; // this one suits our needs, choose it pf = i; break; } if(pf == 0) { ReleaseDC(w, DC); RDCERR("Couldn't choose pixel format"); return ret; } BOOL res = DescribePixelFormat(DC, pf, sizeof(pfd), &pfd); if(res == FALSE) { ReleaseDC(w, DC); RDCERR("Couldn't describe pixel format"); return ret; } res = SetPixelFormat(DC, pf, &pfd); if(res == FALSE) { ReleaseDC(w, DC); RDCERR("Couldn't set pixel format"); return ret; } int attribs[64] = {0}; int i = 0; attribs[i++] = WGL_CONTEXT_MAJOR_VERSION_ARB; attribs[i++] = GLCoreVersion / 10; attribs[i++] = WGL_CONTEXT_MINOR_VERSION_ARB; attribs[i++] = GLCoreVersion % 10; attribs[i++] = WGL_CONTEXT_FLAGS_ARB; #if ENABLED(RDOC_DEVEL) attribs[i++] = WGL_CONTEXT_DEBUG_BIT_ARB; #else attribs[i++] = 0; #endif attribs[i++] = WGL_CONTEXT_PROFILE_MASK_ARB; attribs[i++] = WGL_CONTEXT_CORE_PROFILE_BIT_ARB; HGLRC rc = WGL.wglCreateContextAttribsARB(DC, share_context.ctx, attribs); if(rc == NULL) { ReleaseDC(w, DC); RDCERR("Couldn't create %d.%d context - something changed since creation", GLCoreVersion / 10, GLCoreVersion % 10); return ret; } ret.DC = DC; ret.ctx = rc; ret.wnd = w; return ret; } void *GetReplayFunction(const char *funcname) { // prefer wglGetProcAddress void *ret = WGL.wglGetProcAddress(funcname); if(ret) return ret; // but if it returns NULL, try looking up the dll directly return Process::GetFunctionAddress(Process::LoadModule("opengl32.dll"), funcname); } bool CanCreateGLESContext() { bool success = WGL.PopulateForReplay(); // if we can't populate our functions we bail now. if(!success) return false; // we need to check for the presence of EXT_create_context_es2_profile. // Unfortunately on windows this means creating a trampoline context success = RegisterClass(); if(!success) return false; HWND w = NULL; HDC dc = NULL; HGLRC rc = NULL; success = CreateTrampolineContext(w, dc, rc); if(!success) return false; const char *exts = NULL; if(WGL.wglGetExtensionsStringARB) exts = WGL.wglGetExtensionsStringARB(dc); if(!exts && WGL.wglGetExtensionsStringEXT) exts = WGL.wglGetExtensionsStringEXT(); if(!exts) RDCERR("Couldn't get WGL extension string"); bool ret = (exts && strstr(exts, "EXT_create_context_es2_profile") != NULL); WGL.wglMakeCurrent(NULL, NULL); WGL.wglDeleteContext(rc); ReleaseDC(w, dc); DestroyWindow(w); return ret; } bool PopulateForReplay() { return WGL.PopulateForReplay(); } ReplayStatus InitialiseAPI(GLWindowingData &replayContext, RDCDriver api) { RDCASSERT(api == RDCDriver::OpenGL || api == RDCDriver::OpenGLES); bool success = RegisterClass(); if(!success) return ReplayStatus::APIInitFailed; HWND w = NULL; HDC dc = NULL; HGLRC rc = NULL; success = CreateTrampolineContext(w, dc, rc); if(!success) return ReplayStatus::APIInitFailed; if(!WGL.wglCreateContextAttribsARB || !WGL.wglGetPixelFormatAttribivARB) { WGL.wglMakeCurrent(NULL, NULL); WGL.wglDeleteContext(rc); ReleaseDC(w, dc); DestroyWindow(w); RDCERR("RenderDoc requires WGL_ARB_create_context and WGL_ARB_pixel_format"); return ReplayStatus::APIHardwareUnsupported; } WGL.wglMakeCurrent(NULL, NULL); WGL.wglDeleteContext(rc); ReleaseDC(w, dc); DestroyWindow(w); // we don't use the default framebuffer (backbuffer) for anything, so we make it // tiny and with no depth/stencil bits PIXELFORMATDESCRIPTOR pfd = {0}; pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); pfd.nVersion = 1; pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; pfd.iLayerType = PFD_MAIN_PLANE; pfd.iPixelType = PFD_TYPE_RGBA; pfd.cColorBits = 24; pfd.cDepthBits = 0; pfd.cStencilBits = 0; w = CreateWindowEx(WS_EX_CLIENTEDGE, WINDOW_CLASS_NAME, L"RenderDoc replay window", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 32, 32, NULL, NULL, GetModuleHandle(NULL), NULL); dc = GetDC(w); int pf = ChoosePixelFormat(dc, &pfd); if(pf == 0) { RDCERR("Couldn't choose pixel format"); ReleaseDC(w, dc); DestroyWindow(w); return ReplayStatus::APIInitFailed; } BOOL res = SetPixelFormat(dc, pf, &pfd); if(res == FALSE) { RDCERR("Couldn't set pixel format"); ReleaseDC(w, dc); DestroyWindow(w); return ReplayStatus::APIInitFailed; } int attribs[64] = {0}; int i = 0; attribs[i++] = WGL_CONTEXT_MAJOR_VERSION_ARB; int &major = attribs[i]; attribs[i++] = 0; attribs[i++] = WGL_CONTEXT_MINOR_VERSION_ARB; int &minor = attribs[i]; attribs[i++] = 0; attribs[i++] = WGL_CONTEXT_FLAGS_ARB; #if ENABLED(RDOC_DEVEL) attribs[i++] = WGL_CONTEXT_DEBUG_BIT_ARB; #else attribs[i++] = 0; #endif attribs[i++] = WGL_CONTEXT_PROFILE_MASK_ARB; attribs[i++] = api == RDCDriver::OpenGLES ? WGL_CONTEXT_ES2_PROFILE_BIT_EXT : WGL_CONTEXT_CORE_PROFILE_BIT_ARB; rc = NULL; std::vector versions = GetReplayVersions(api); for(GLVersion v : versions) { major = v.major; minor = v.minor; rc = WGL.wglCreateContextAttribsARB(dc, NULL, attribs); if(rc) break; } if(rc == NULL) { RDCERR("Couldn't create at least 3.2 context - RenderDoc requires OpenGL 3.2 availability"); ReleaseDC(w, dc); DestroyWindow(w); return ReplayStatus::APIHardwareUnsupported; } GLCoreVersion = major * 10 + minor; res = WGL.wglMakeCurrent(dc, rc); if(res == FALSE) { RDCERR("Couldn't make 3.2 RC current"); WGL.wglMakeCurrent(NULL, NULL); WGL.wglDeleteContext(rc); ReleaseDC(w, dc); DestroyWindow(w); return ReplayStatus::APIInitFailed; } replayContext.DC = dc; replayContext.ctx = rc; replayContext.wnd = w; return ReplayStatus::Succeeded; } bool CreateTrampolineContext(HWND &w, HDC &dc, HGLRC &rc) { PIXELFORMATDESCRIPTOR pfd = {0}; pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); pfd.nVersion = 1; pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; pfd.iLayerType = PFD_MAIN_PLANE; pfd.iPixelType = PFD_TYPE_RGBA; pfd.cColorBits = 24; pfd.cDepthBits = 0; pfd.cStencilBits = 0; w = CreateWindowEx(WS_EX_CLIENTEDGE, WINDOW_CLASS_NAME, L"", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, GetModuleHandle(NULL), NULL); dc = GetDC(w); int pf = ::ChoosePixelFormat(dc, &pfd); if(pf == 0) { ReleaseDC(w, dc); DestroyWindow(w); RDCERR("Couldn't choose pixel format"); return false; } BOOL res = ::SetPixelFormat(dc, pf, &pfd); if(res == FALSE) { ReleaseDC(w, dc); DestroyWindow(w); RDCERR("Couldn't set pixel format"); return false; } rc = WGL.wglCreateContext(dc); if(rc == NULL) { ReleaseDC(w, dc); DestroyWindow(w); RDCERR("Couldn't create trampoline context"); return false; } res = WGL.wglMakeCurrent(dc, rc); if(res == FALSE) { WGL.wglMakeCurrent(NULL, NULL); WGL.wglDeleteContext(rc); ReleaseDC(w, dc); DestroyWindow(w); RDCERR("Couldn't make trampoline context current"); return false; } // now we can fetch the extension functions we need and fill out WGL if(!WGL.wglCreateContextAttribsARB) WGL.wglCreateContextAttribsARB = (PFN_wglCreateContextAttribsARB)WGL.wglGetProcAddress("wglCreateContextAttribsARB"); if(!WGL.wglGetPixelFormatAttribivARB) WGL.wglGetPixelFormatAttribivARB = (PFN_wglGetPixelFormatAttribivARB)WGL.wglGetProcAddress("wglGetPixelFormatAttribivARB"); if(!WGL.wglGetExtensionsStringEXT) WGL.wglGetExtensionsStringEXT = (PFN_wglGetExtensionsStringEXT)WGL.wglGetProcAddress("wglGetExtensionsStringEXT"); if(!WGL.wglGetExtensionsStringARB) WGL.wglGetExtensionsStringARB = (PFN_wglGetExtensionsStringARB)WGL.wglGetProcAddress("wglGetExtensionsStringARB"); return true; } bool RegisterClass() { WNDCLASSEX wc = {}; wc.style = CS_OWNDC; wc.cbSize = sizeof(WNDCLASSEX); wc.lpfnWndProc = DefWindowProc; wc.hInstance = GetModuleHandle(NULL); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.lpszClassName = WINDOW_CLASS_NAME; WNDCLASSEX dummyCheck = {}; // if the class isn't already registered, then register it. if(!GetClassInfoExW(wc.hInstance, wc.lpszClassName, &dummyCheck)) { if(!RegisterClassEx(&wc)) { RDCERR("Couldn't register GL window class"); return false; } } return true; } void DrawQuads(float width, float height, const std::vector &vertices) { ::DrawQuads(WGL, width, height, vertices); } } wglPlatform; GLPlatform &GetGLPlatform() { return wglPlatform; } WGLDispatchTable WGL = {}; bool WGLDispatchTable::PopulateForReplay() { RDCASSERT(RenderDoc::Inst().IsReplayApp()); RDCDEBUG("Initialising WGL function pointers"); bool symbols_ok = true; // library will be NULL for extension functions that we can't fetch yet. #define LOAD_FUNC(library, func) \ if(CheckConstParam(sizeof(library) > 2)) \ { \ if(!this->func) \ this->func = (CONCAT(PFN_, func))Process::GetFunctionAddress(Process::LoadModule(library), \ STRINGIZE(func)); \ \ if(!this->func) \ { \ symbols_ok = false; \ RDCWARN("Unable to load '%s'", STRINGIZE(func)); \ } \ } WGL_HOOKED_SYMBOLS(LOAD_FUNC); WGL_NONHOOKED_SYMBOLS(LOAD_FUNC); #undef LOAD_FUNC return symbols_ok; }