duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

opengl_context_wgl.cpp (12477B)


      1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
      2 // SPDX-License-Identifier: (GPL-3.0 OR PolyForm-Strict-1.0.0)
      3 
      4 #include "opengl_context_wgl.h"
      5 #include "opengl_loader.h"
      6 
      7 #include "common/assert.h"
      8 #include "common/error.h"
      9 #include "common/log.h"
     10 #include "common/scoped_guard.h"
     11 
     12 Log_SetChannel(GL::OpenGLContext);
     13 
     14 #ifdef __clang__
     15 #pragma clang diagnostic ignored "-Wmicrosoft-cast"
     16 #endif
     17 
     18 static void* GetProcAddressCallback(const char* name)
     19 {
     20   void* addr = wglGetProcAddress(name);
     21   if (addr)
     22     return addr;
     23 
     24   // try opengl32.dll
     25   return ::GetProcAddress(GetModuleHandleA("opengl32.dll"), name);
     26 }
     27 
     28 static bool ReloadWGL(HDC dc)
     29 {
     30   if (!gladLoadWGL(dc, [](const char* name) { return (GLADapiproc)wglGetProcAddress(name); }))
     31   {
     32     ERROR_LOG("Loading GLAD WGL functions failed");
     33     return false;
     34   }
     35 
     36   return true;
     37 }
     38 
     39 OpenGLContextWGL::OpenGLContextWGL(const WindowInfo& wi) : OpenGLContext(wi)
     40 {
     41 }
     42 
     43 OpenGLContextWGL::~OpenGLContextWGL()
     44 {
     45   if (wglGetCurrentContext() == m_rc)
     46     wglMakeCurrent(m_dc, nullptr);
     47 
     48   if (m_rc)
     49     wglDeleteContext(m_rc);
     50 
     51   ReleaseDC();
     52 }
     53 
     54 std::unique_ptr<OpenGLContext> OpenGLContextWGL::Create(const WindowInfo& wi, std::span<const Version> versions_to_try,
     55                                                         Error* error)
     56 {
     57   std::unique_ptr<OpenGLContextWGL> context = std::make_unique<OpenGLContextWGL>(wi);
     58   if (!context->Initialize(versions_to_try, error))
     59     return nullptr;
     60 
     61   return context;
     62 }
     63 
     64 bool OpenGLContextWGL::Initialize(std::span<const Version> versions_to_try, Error* error)
     65 {
     66   if (m_wi.type == WindowInfo::Type::Win32)
     67   {
     68     if (!InitializeDC(error))
     69       return false;
     70   }
     71   else
     72   {
     73     if (!CreatePBuffer(error))
     74       return false;
     75   }
     76 
     77   // Everything including core/ES requires a dummy profile to load the WGL extensions.
     78   if (!CreateAnyContext(nullptr, true, error))
     79     return false;
     80 
     81   for (const Version& cv : versions_to_try)
     82   {
     83     if (cv.profile == Profile::NoProfile)
     84     {
     85       // we already have the dummy context, so just use that
     86       m_version = cv;
     87       return true;
     88     }
     89     else if (CreateVersionContext(cv, nullptr, true, error))
     90     {
     91       m_version = cv;
     92       return true;
     93     }
     94   }
     95 
     96   Error::SetStringView(error, "Failed to create any contexts.");
     97   return false;
     98 }
     99 
    100 void* OpenGLContextWGL::GetProcAddress(const char* name)
    101 {
    102   return GetProcAddressCallback(name);
    103 }
    104 
    105 bool OpenGLContextWGL::ChangeSurface(const WindowInfo& new_wi)
    106 {
    107   const bool was_current = (wglGetCurrentContext() == m_rc);
    108   Error error;
    109 
    110   ReleaseDC();
    111 
    112   m_wi = new_wi;
    113   if (!InitializeDC(&error))
    114   {
    115     ERROR_LOG("Failed to change surface: {}", error.GetDescription());
    116     return false;
    117   }
    118 
    119   if (was_current && !wglMakeCurrent(m_dc, m_rc))
    120   {
    121     error.SetWin32(GetLastError());
    122     ERROR_LOG("Failed to make context current again after surface change: {}", error.GetDescription());
    123     return false;
    124   }
    125 
    126   return true;
    127 }
    128 
    129 void OpenGLContextWGL::ResizeSurface(u32 new_surface_width /*= 0*/, u32 new_surface_height /*= 0*/)
    130 {
    131   RECT client_rc = {};
    132   GetClientRect(GetHWND(), &client_rc);
    133   m_wi.surface_width = static_cast<u32>(client_rc.right - client_rc.left);
    134   m_wi.surface_height = static_cast<u32>(client_rc.bottom - client_rc.top);
    135 }
    136 
    137 bool OpenGLContextWGL::SwapBuffers()
    138 {
    139   return ::SwapBuffers(m_dc);
    140 }
    141 
    142 bool OpenGLContextWGL::IsCurrent() const
    143 {
    144   return (m_rc && wglGetCurrentContext() == m_rc);
    145 }
    146 
    147 bool OpenGLContextWGL::MakeCurrent()
    148 {
    149   if (!wglMakeCurrent(m_dc, m_rc))
    150   {
    151     ERROR_LOG("wglMakeCurrent() failed: {}", GetLastError());
    152     return false;
    153   }
    154 
    155   return true;
    156 }
    157 
    158 bool OpenGLContextWGL::DoneCurrent()
    159 {
    160   return wglMakeCurrent(m_dc, nullptr);
    161 }
    162 
    163 bool OpenGLContextWGL::SupportsNegativeSwapInterval() const
    164 {
    165   return GLAD_WGL_EXT_swap_control && GLAD_WGL_EXT_swap_control_tear;
    166 }
    167 
    168 bool OpenGLContextWGL::SetSwapInterval(s32 interval)
    169 {
    170   if (!GLAD_WGL_EXT_swap_control)
    171     return false;
    172 
    173   return wglSwapIntervalEXT(interval);
    174 }
    175 
    176 std::unique_ptr<OpenGLContext> OpenGLContextWGL::CreateSharedContext(const WindowInfo& wi, Error* error)
    177 {
    178   std::unique_ptr<OpenGLContextWGL> context = std::make_unique<OpenGLContextWGL>(wi);
    179   if (wi.type == WindowInfo::Type::Win32)
    180   {
    181     if (!context->InitializeDC(error))
    182       return nullptr;
    183   }
    184   else
    185   {
    186     if (!context->CreatePBuffer(error))
    187       return nullptr;
    188   }
    189 
    190   if (m_version.profile == Profile::NoProfile)
    191   {
    192     if (!context->CreateAnyContext(m_rc, false, error))
    193       return nullptr;
    194   }
    195   else
    196   {
    197     if (!context->CreateVersionContext(m_version, m_rc, false, error))
    198       return nullptr;
    199   }
    200 
    201   context->m_version = m_version;
    202   return context;
    203 }
    204 
    205 HDC OpenGLContextWGL::GetDCAndSetPixelFormat(HWND hwnd, Error* error)
    206 {
    207   PIXELFORMATDESCRIPTOR pfd = {};
    208   pfd.nSize = sizeof(pfd);
    209   pfd.nVersion = 1;
    210   pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
    211   pfd.iPixelType = PFD_TYPE_RGBA;
    212   pfd.dwLayerMask = PFD_MAIN_PLANE;
    213   pfd.cRedBits = 8;
    214   pfd.cGreenBits = 8;
    215   pfd.cBlueBits = 8;
    216   pfd.cColorBits = 24;
    217 
    218   HDC hDC = ::GetDC(hwnd);
    219   if (!hDC)
    220   {
    221     Error::SetWin32(error, "GetDC() failed: ", GetLastError());
    222     return {};
    223   }
    224 
    225   if (!m_pixel_format.has_value())
    226   {
    227     const int pf = ChoosePixelFormat(hDC, &pfd);
    228     if (pf == 0)
    229     {
    230       Error::SetWin32(error, "ChoosePixelFormat() failed: ", GetLastError());
    231       ::ReleaseDC(hwnd, hDC);
    232       return {};
    233     }
    234 
    235     m_pixel_format = pf;
    236   }
    237 
    238   if (!SetPixelFormat(hDC, m_pixel_format.value(), &pfd))
    239   {
    240     Error::SetWin32(error, "SetPixelFormat() failed: ", GetLastError());
    241     ::ReleaseDC(hwnd, hDC);
    242     return {};
    243   }
    244 
    245   m_wi.surface_format = GPUTexture::Format::RGBA8;
    246   return hDC;
    247 }
    248 
    249 bool OpenGLContextWGL::InitializeDC(Error* error)
    250 {
    251   if (m_wi.type == WindowInfo::Type::Win32)
    252   {
    253     m_dc = GetDCAndSetPixelFormat(GetHWND(), error);
    254     if (!m_dc)
    255       return false;
    256 
    257     return true;
    258   }
    259   else if (m_wi.type == WindowInfo::Type::Surfaceless)
    260   {
    261     return CreatePBuffer(error);
    262   }
    263   else
    264   {
    265     Error::SetStringFmt(error, "Unknown window info type {}", static_cast<unsigned>(m_wi.type));
    266     return false;
    267   }
    268 }
    269 
    270 void OpenGLContextWGL::ReleaseDC()
    271 {
    272   if (m_pbuffer)
    273   {
    274     wglReleasePbufferDCARB(m_pbuffer, m_dc);
    275     m_dc = {};
    276 
    277     wglDestroyPbufferARB(m_pbuffer);
    278     m_pbuffer = {};
    279 
    280     ::ReleaseDC(m_dummy_window, m_dummy_dc);
    281     m_dummy_dc = {};
    282 
    283     DestroyWindow(m_dummy_window);
    284     m_dummy_window = {};
    285   }
    286   else if (m_dc)
    287   {
    288     ::ReleaseDC(GetHWND(), m_dc);
    289     m_dc = {};
    290   }
    291 }
    292 
    293 bool OpenGLContextWGL::CreatePBuffer(Error* error)
    294 {
    295   static bool window_class_registered = false;
    296   static const wchar_t* window_class_name = L"ContextWGLPBuffer";
    297 
    298   if (!window_class_registered)
    299   {
    300     WNDCLASSEXW wc = {};
    301     wc.cbSize = sizeof(WNDCLASSEXW);
    302     wc.style = 0;
    303     wc.lpfnWndProc = DefWindowProcW;
    304     wc.cbClsExtra = 0;
    305     wc.cbWndExtra = 0;
    306     wc.hInstance = GetModuleHandle(nullptr);
    307     wc.hIcon = NULL;
    308     wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    309     wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    310     wc.lpszMenuName = NULL;
    311     wc.lpszClassName = window_class_name;
    312     wc.hIconSm = NULL;
    313 
    314     if (!RegisterClassExW(&wc))
    315     {
    316       Error::SetStringView(error, "(ContextWGL::CreatePBuffer) RegisterClassExW() failed");
    317       return false;
    318     }
    319 
    320     window_class_registered = true;
    321   }
    322 
    323   HWND hwnd = CreateWindowExW(0, window_class_name, window_class_name, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL);
    324   if (!hwnd)
    325   {
    326     Error::SetStringView(error, "(ContextWGL::CreatePBuffer) CreateWindowEx() failed");
    327     return false;
    328   }
    329 
    330   ScopedGuard hwnd_guard([hwnd]() { DestroyWindow(hwnd); });
    331 
    332   HDC hdc = GetDCAndSetPixelFormat(hwnd, error);
    333   if (!hdc)
    334     return false;
    335 
    336   ScopedGuard hdc_guard([hdc, hwnd]() { ::ReleaseDC(hwnd, hdc); });
    337 
    338   static constexpr const int pb_attribs[] = {0, 0};
    339 
    340   HGLRC temp_rc = nullptr;
    341   ScopedGuard temp_rc_guard([&temp_rc, hdc]() {
    342     if (temp_rc)
    343     {
    344       wglMakeCurrent(hdc, nullptr);
    345       wglDeleteContext(temp_rc);
    346     }
    347   });
    348 
    349   if (!GLAD_WGL_ARB_pbuffer)
    350   {
    351     // we're probably running completely surfaceless... need a temporary context.
    352     temp_rc = wglCreateContext(hdc);
    353     if (!temp_rc || !wglMakeCurrent(hdc, temp_rc))
    354     {
    355       Error::SetStringView(error, "Failed to create temporary context to load WGL for pbuffer.");
    356       return false;
    357     }
    358 
    359     if (!ReloadWGL(hdc) || !GLAD_WGL_ARB_pbuffer)
    360     {
    361       Error::SetStringView(error, "Missing WGL_ARB_pbuffer");
    362       return false;
    363     }
    364   }
    365 
    366   AssertMsg(m_pixel_format.has_value(), "Has pixel format for pbuffer");
    367   HPBUFFERARB pbuffer = wglCreatePbufferARB(hdc, m_pixel_format.value(), 1, 1, pb_attribs);
    368   if (!pbuffer)
    369   {
    370     Error::SetStringView(error, "(ContextWGL::CreatePBuffer) wglCreatePbufferARB() failed");
    371     return false;
    372   }
    373 
    374   ScopedGuard pbuffer_guard([pbuffer]() { wglDestroyPbufferARB(pbuffer); });
    375 
    376   m_dc = wglGetPbufferDCARB(pbuffer);
    377   if (!m_dc)
    378   {
    379     Error::SetStringView(error, "(ContextWGL::CreatePbuffer) wglGetPbufferDCARB() failed");
    380     return false;
    381   }
    382 
    383   m_dummy_window = hwnd;
    384   m_dummy_dc = hdc;
    385   m_pbuffer = pbuffer;
    386 
    387   temp_rc_guard.Run();
    388   pbuffer_guard.Cancel();
    389   hdc_guard.Cancel();
    390   hwnd_guard.Cancel();
    391   return true;
    392 }
    393 
    394 bool OpenGLContextWGL::CreateAnyContext(HGLRC share_context, bool make_current, Error* error)
    395 {
    396   m_rc = wglCreateContext(m_dc);
    397   if (!m_rc)
    398   {
    399     Error::SetWin32(error, "wglCreateContext() failed: ", GetLastError());
    400     return false;
    401   }
    402 
    403   if (make_current)
    404   {
    405     if (!wglMakeCurrent(m_dc, m_rc))
    406     {
    407       Error::SetWin32(error, "wglMakeCurrent() failed: ", GetLastError());
    408       return false;
    409     }
    410 
    411     // re-init glad-wgl
    412     if (!gladLoadWGL(m_dc, [](const char* name) { return (GLADapiproc)wglGetProcAddress(name); }))
    413     {
    414       Error::SetStringView(error, "Loading GLAD WGL functions failed");
    415       return false;
    416     }
    417   }
    418 
    419   if (share_context && !wglShareLists(share_context, m_rc))
    420   {
    421     Error::SetWin32(error, "wglShareLists() failed: ", GetLastError());
    422     return false;
    423   }
    424 
    425   return true;
    426 }
    427 
    428 bool OpenGLContextWGL::CreateVersionContext(const Version& version, HGLRC share_context, bool make_current,
    429                                             Error* error)
    430 {
    431   // we need create context attribs
    432   if (!GLAD_WGL_ARB_create_context)
    433   {
    434     Error::SetStringView(error, "Missing GLAD_WGL_ARB_create_context.");
    435     return false;
    436   }
    437 
    438   HGLRC new_rc;
    439   if (version.profile == Profile::Core)
    440   {
    441     const int attribs[] = {WGL_CONTEXT_PROFILE_MASK_ARB,
    442                            WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
    443                            WGL_CONTEXT_MAJOR_VERSION_ARB,
    444                            version.major_version,
    445                            WGL_CONTEXT_MINOR_VERSION_ARB,
    446                            version.minor_version,
    447 #ifdef _DEBUG
    448                            WGL_CONTEXT_FLAGS_ARB,
    449                            WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB | WGL_CONTEXT_DEBUG_BIT_ARB,
    450 #else
    451                            WGL_CONTEXT_FLAGS_ARB,
    452                            WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
    453 #endif
    454                            0,
    455                            0};
    456 
    457     new_rc = wglCreateContextAttribsARB(m_dc, share_context, attribs);
    458   }
    459   else if (version.profile == Profile::ES)
    460   {
    461     if ((version.major_version >= 2 && !GLAD_WGL_EXT_create_context_es2_profile) ||
    462         (version.major_version < 2 && !GLAD_WGL_EXT_create_context_es_profile))
    463     {
    464       Error::SetStringView(error, "WGL_EXT_create_context_es_profile not supported");
    465       return false;
    466     }
    467 
    468     const int attribs[] = {
    469       WGL_CONTEXT_PROFILE_MASK_ARB,
    470       ((version.major_version >= 2) ? WGL_CONTEXT_ES2_PROFILE_BIT_EXT : WGL_CONTEXT_ES_PROFILE_BIT_EXT),
    471       WGL_CONTEXT_MAJOR_VERSION_ARB,
    472       version.major_version,
    473       WGL_CONTEXT_MINOR_VERSION_ARB,
    474       version.minor_version,
    475       0,
    476       0};
    477 
    478     new_rc = wglCreateContextAttribsARB(m_dc, share_context, attribs);
    479   }
    480   else
    481   {
    482     Error::SetStringView(error, "Unknown profile");
    483     return false;
    484   }
    485 
    486   if (!new_rc)
    487     return false;
    488 
    489   // destroy and swap contexts
    490   if (m_rc)
    491   {
    492     if (!wglMakeCurrent(m_dc, make_current ? new_rc : nullptr))
    493     {
    494       Error::SetWin32(error, "wglMakeCurrent() failed: ", GetLastError());
    495       wglDeleteContext(new_rc);
    496       return false;
    497     }
    498 
    499     // re-init glad-wgl
    500     if (make_current && !ReloadWGL(m_dc))
    501       return false;
    502 
    503     wglDeleteContext(m_rc);
    504   }
    505 
    506   m_rc = new_rc;
    507   return true;
    508 }