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 }