ebiten/internal/goglfw/wglcontext_windows.go
TotallyGamerJet b359985e97
internal/goglfw: separate windows specific struct from common structs (#2567)
Many of the structs found in internal.h (internal_windows.go) contain defines such as GLFW_WIN32_WINDOW_STATE
which gets replaced with a struct defined in win32platform_windows.go (win32_platform.h).

Originally, these structs where directly placed inside of internal_windows.go. However, to make it easier to add macOS
and Linux these cannot be in this file.

This commit separates the windows specific structs into the respective windows file and updates the field to be named
state.

Updates #2546
2023-02-08 03:05:46 +09:00

573 lines
16 KiB
Go

// SPDX-License-Identifier: Zlib
// SPDX-FileCopyrightText: 2002-2006 Marcus Geelnard
// SPDX-FileCopyrightText: 2006-2019 Camilla Löwy
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
package goglfw
import (
"errors"
"fmt"
"strings"
"unsafe"
"golang.org/x/sys/windows"
"github.com/hajimehoshi/ebiten/v2/internal/microsoftgdk"
)
func findPixelFormatAttribValue(attribs []int32, values []int32, attrib int32) int32 {
for i := range attribs {
if attribs[i] == attrib {
return values[i]
}
}
return 0
}
func (w *Window) choosePixelFormat(ctxconfig *ctxconfig, fbconfig_ *fbconfig) (int, error) {
var nativeCount int32
var attribs []int32
if _glfw.platformContext.ARB_pixel_format {
var attrib int32 = _WGL_NUMBER_PIXEL_FORMATS_ARB
if err := wglGetPixelFormatAttribivARB(w.context.platform.dc, 1, 0, 1, &attrib, &nativeCount); err != nil {
return 0, err
}
attribs = append(attribs,
_WGL_SUPPORT_OPENGL_ARB,
_WGL_DRAW_TO_WINDOW_ARB,
_WGL_PIXEL_TYPE_ARB,
_WGL_ACCELERATION_ARB,
_WGL_RED_BITS_ARB,
_WGL_RED_SHIFT_ARB,
_WGL_GREEN_BITS_ARB,
_WGL_GREEN_SHIFT_ARB,
_WGL_BLUE_BITS_ARB,
_WGL_BLUE_SHIFT_ARB,
_WGL_ALPHA_BITS_ARB,
_WGL_ALPHA_SHIFT_ARB,
_WGL_DEPTH_BITS_ARB,
_WGL_STENCIL_BITS_ARB,
_WGL_ACCUM_BITS_ARB,
_WGL_ACCUM_RED_BITS_ARB,
_WGL_ACCUM_GREEN_BITS_ARB,
_WGL_ACCUM_BLUE_BITS_ARB,
_WGL_ACCUM_ALPHA_BITS_ARB,
_WGL_AUX_BUFFERS_ARB,
_WGL_STEREO_ARB,
_WGL_DOUBLE_BUFFER_ARB)
if _glfw.platformContext.ARB_multisample {
attribs = append(attribs, _WGL_SAMPLES_ARB)
}
if ctxconfig.client == OpenGLAPI {
if _glfw.platformContext.ARB_framebuffer_sRGB || _glfw.platformContext.EXT_framebuffer_sRGB {
attribs = append(attribs, _WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB)
}
} else {
if _glfw.platformContext.EXT_colorspace {
attribs = append(attribs, _WGL_COLORSPACE_EXT)
}
}
} else {
c, err := _DescribePixelFormat(w.context.platform.dc, 1, uint32(unsafe.Sizeof(_PIXELFORMATDESCRIPTOR{})), nil)
if err != nil {
return 0, err
}
nativeCount = c
}
usableConfigs := make([]*fbconfig, 0, nativeCount)
for i := int32(0); i < nativeCount; i++ {
var u fbconfig
pixelFormat := uintptr(i) + 1
if _glfw.platformContext.ARB_pixel_format {
// Get pixel format attributes through "modern" extension
values := make([]int32, len(attribs))
if err := wglGetPixelFormatAttribivARB(w.context.platform.dc, int32(pixelFormat), 0, uint32(len(attribs)), &attribs[0], &values[0]); err != nil {
return 0, err
}
findAttribValue := func(attrib int32) int32 {
return findPixelFormatAttribValue(attribs, values, attrib)
}
if findAttribValue(_WGL_SUPPORT_OPENGL_ARB) == 0 || findAttribValue(_WGL_DRAW_TO_WINDOW_ARB) == 0 {
continue
}
if findAttribValue(_WGL_PIXEL_TYPE_ARB) != _WGL_TYPE_RGBA_ARB {
continue
}
if findAttribValue(_WGL_ACCELERATION_ARB) == _WGL_NO_ACCELERATION_ARB {
continue
}
if (findAttribValue(_WGL_DOUBLE_BUFFER_ARB) != 0) != fbconfig_.doublebuffer {
continue
}
u.redBits = int(findAttribValue(_WGL_RED_BITS_ARB))
u.greenBits = int(findAttribValue(_WGL_GREEN_BITS_ARB))
u.blueBits = int(findAttribValue(_WGL_BLUE_BITS_ARB))
u.alphaBits = int(findAttribValue(_WGL_ALPHA_BITS_ARB))
u.depthBits = int(findAttribValue(_WGL_DEPTH_BITS_ARB))
u.stencilBits = int(findAttribValue(_WGL_STENCIL_BITS_ARB))
u.accumRedBits = int(findAttribValue(_WGL_ACCUM_RED_BITS_ARB))
u.accumGreenBits = int(findAttribValue(_WGL_ACCUM_GREEN_BITS_ARB))
u.accumBlueBits = int(findAttribValue(_WGL_ACCUM_BLUE_BITS_ARB))
u.accumAlphaBits = int(findAttribValue(_WGL_ACCUM_ALPHA_BITS_ARB))
u.auxBuffers = int(findAttribValue(_WGL_AUX_BUFFERS_ARB))
if findAttribValue(_WGL_STEREO_ARB) != 0 {
u.stereo = true
}
if _glfw.platformContext.ARB_multisample {
u.samples = int(findAttribValue(_WGL_SAMPLES_ARB))
}
if ctxconfig.client == OpenGLAPI {
if _glfw.platformContext.ARB_framebuffer_sRGB || _glfw.platformContext.EXT_framebuffer_sRGB {
if findAttribValue(_WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB) != 0 {
u.sRGB = true
}
}
} else {
if _glfw.platformContext.EXT_colorspace {
if findAttribValue(_WGL_COLORSPACE_EXT) == _WGL_COLORSPACE_SRGB_EXT {
u.sRGB = true
}
}
}
} else {
// Get pixel format attributes through legacy PFDs
var pfd _PIXELFORMATDESCRIPTOR
if _, err := _DescribePixelFormat(w.context.platform.dc, int32(pixelFormat), uint32(unsafe.Sizeof(pfd)), &pfd); err != nil {
return 0, err
}
if pfd.dwFlags&_PFD_DRAW_TO_WINDOW == 0 || pfd.dwFlags&_PFD_SUPPORT_OPENGL == 0 {
continue
}
if pfd.dwFlags&_PFD_GENERIC_ACCELERATED == 0 && pfd.dwFlags&_PFD_GENERIC_FORMAT != 0 {
continue
}
if pfd.iPixelType != _PFD_TYPE_RGBA {
continue
}
if (pfd.dwFlags&_PFD_DOUBLEBUFFER != 0) != fbconfig_.doublebuffer {
continue
}
u.redBits = int(pfd.cRedBits)
u.greenBits = int(pfd.cGreenBits)
u.blueBits = int(pfd.cBlueBits)
u.alphaBits = int(pfd.cAlphaBits)
u.depthBits = int(pfd.cDepthBits)
u.stencilBits = int(pfd.cStencilBits)
u.accumRedBits = int(pfd.cAccumRedBits)
u.accumGreenBits = int(pfd.cAccumGreenBits)
u.accumBlueBits = int(pfd.cAccumBlueBits)
u.accumAlphaBits = int(pfd.cAccumAlphaBits)
u.auxBuffers = int(pfd.cAuxBuffers)
if pfd.dwFlags&_PFD_STEREO != 0 {
u.stereo = true
}
}
u.handle = pixelFormat
usableConfigs = append(usableConfigs, &u)
}
if len(usableConfigs) == 0 {
return 0, fmt.Errorf("goglfw: the driver does not appear to support OpenGL")
}
closest := chooseFBConfig(fbconfig_, usableConfigs)
if closest == nil {
return 0, fmt.Errorf("goglfw: failed to find a suitable pixel format")
}
return int(closest.handle), nil
}
func makeContextCurrentWGL(window *Window) error {
if window != nil {
if err := wglMakeCurrent(window.context.platform.dc, window.context.platform.handle); err != nil {
_ = _glfw.contextSlot.set(0)
return err
}
if err := _glfw.contextSlot.set(uintptr(unsafe.Pointer(window))); err != nil {
return err
}
} else {
if err := wglMakeCurrent(0, 0); err != nil {
_ = _glfw.contextSlot.set(0)
return err
}
if err := _glfw.contextSlot.set(0); err != nil {
return err
}
}
return nil
}
func swapBuffersWGL(window *Window) error {
if window.monitor == nil && _IsWindowsVistaOrGreater() {
// DWM Composition is always enabled on Win8+
enabled := _IsWindows8OrGreater()
if !enabled {
var err error
enabled, err = _DwmIsCompositionEnabled()
if err != nil {
return err
}
}
// HACK: Use DwmFlush when desktop composition is enabled
if enabled {
for i := 0; i < window.context.platform.interval; i++ {
// Ignore an error from DWM functions as they might not be implemented e.g. on Proton (#2113).
_ = _DwmFlush()
}
}
}
if err := _SwapBuffers(window.context.platform.dc); err != nil {
return err
}
return nil
}
func swapIntervalWGL(interval int) error {
ptr, err := _glfw.contextSlot.get()
if err != nil {
return err
}
window := (*Window)(unsafe.Pointer(ptr))
window.context.platform.interval = interval
if window.monitor == nil && _IsWindowsVistaOrGreater() {
// DWM Composition is always enabled on Win8+
enabled := _IsWindows8OrGreater()
if !enabled {
e, err := _DwmIsCompositionEnabled()
// Ignore an error from DWM functions as they might not be implemented e.g. on Proton (#2113).
if err == nil {
enabled = e
}
}
// HACK: Disable WGL swap interval when desktop composition is enabled to
// avoid interfering with DWM vsync
if enabled {
interval = 0
}
}
if _glfw.platformContext.EXT_swap_control {
if err := wglSwapIntervalEXT(int32(interval)); err != nil {
return err
}
}
return nil
}
func extensionSupportedWGL(extension string) bool {
var extensions string
if wglGetExtensionsStringARB_Available() {
extensions = wglGetExtensionsStringARB(wglGetCurrentDC())
} else if wglGetExtensionsStringEXT_Available() {
extensions = wglGetExtensionsStringEXT()
}
if len(extensions) == 0 {
return false
}
for _, str := range strings.Split(extensions, " ") {
if extension == str {
return true
}
}
return false
}
func getProcAddressWGL(procname string) uintptr {
proc := wglGetProcAddress(procname)
if proc != 0 {
return proc
}
return opengl32.NewProc(procname).Addr()
}
func destroyContextWGL(window *Window) error {
if window.context.platform.handle != 0 {
// Ignore ERROR_BUSY. This happens when the thread is different from the context thread (#2518).
// This is a known issue of GLFW (glfw/glfw#2239).
// TODO: Delete the context on an appropriate thread.
if err := wglDeleteContext(window.context.platform.handle); err != nil && !errors.Is(err, windows.ERROR_BUSY) {
return err
}
window.context.platform.handle = 0
}
return nil
}
func initWGL() error {
if microsoftgdk.IsXbox() {
return fmt.Errorf("goglfw: WGL is not available in Xbox")
}
if _glfw.platformContext.inited {
return nil
}
// opengl32.dll must be loaded first. The loading state might affect Windows APIs.
// This is needed at least before SetPixelFormat.
if err := opengl32.Load(); err != nil {
return err
}
// NOTE: A dummy context has to be created for opengl32.dll to load the
// OpenGL ICD, from which we can then query WGL extensions
// NOTE: This code will accept the Microsoft GDI ICD; accelerated context
// creation failure occurs during manual pixel format enumeration
dc, err := _GetDC(_glfw.platformWindow.helperWindowHandle)
if err != nil {
return err
}
pfd := _PIXELFORMATDESCRIPTOR{
nVersion: 1,
dwFlags: _PFD_DRAW_TO_WINDOW | _PFD_SUPPORT_OPENGL | _PFD_DOUBLEBUFFER,
iPixelType: _PFD_TYPE_RGBA,
cColorBits: 24,
}
pfd.nSize = uint16(unsafe.Sizeof(pfd))
format, err := _ChoosePixelFormat(dc, &pfd)
if err != nil {
return err
}
if err := _SetPixelFormat(dc, format, &pfd); err != nil {
return err
}
rc, err := wglCreateContext(dc)
if err != nil {
return err
}
pdc := wglGetCurrentDC()
prc := wglGetCurrentContext()
if err := wglMakeCurrent(dc, rc); err != nil {
_ = wglMakeCurrent(pdc, prc)
_ = wglDeleteContext(rc)
return err
}
// NOTE: Functions must be loaded first as they're needed to retrieve the
// extension string that tells us whether the functions are supported
//
// Interestingly, wglGetProcAddress might return 0 after extensionSupportedWGL is called.
initWGLExtensionFunctions()
// NOTE: WGL_ARB_extensions_string and WGL_EXT_extensions_string are not
// checked below as we are already using them
_glfw.platformContext.ARB_multisample = extensionSupportedWGL("WGL_ARB_multisample")
_glfw.platformContext.ARB_framebuffer_sRGB = extensionSupportedWGL("WGL_ARB_framebuffer_sRGB")
_glfw.platformContext.EXT_framebuffer_sRGB = extensionSupportedWGL("WGL_EXT_framebuffer_sRGB")
_glfw.platformContext.ARB_create_context = extensionSupportedWGL("WGL_ARB_create_context")
_glfw.platformContext.ARB_create_context_profile = extensionSupportedWGL("WGL_ARB_create_context_profile")
_glfw.platformContext.EXT_create_context_es2_profile = extensionSupportedWGL("WGL_EXT_create_context_es2_profile")
_glfw.platformContext.ARB_create_context_robustness = extensionSupportedWGL("WGL_ARB_create_context_robustness")
_glfw.platformContext.ARB_create_context_no_error = extensionSupportedWGL("WGL_ARB_create_context_no_error")
_glfw.platformContext.EXT_swap_control = extensionSupportedWGL("WGL_EXT_swap_control")
_glfw.platformContext.EXT_colorspace = extensionSupportedWGL("WGL_EXT_colorspace")
_glfw.platformContext.ARB_pixel_format = extensionSupportedWGL("WGL_ARB_pixel_format")
_glfw.platformContext.ARB_context_flush_control = extensionSupportedWGL("WGL_ARB_context_flush_control")
if err := wglMakeCurrent(pdc, prc); err != nil {
return err
}
if err := wglDeleteContext(rc); err != nil {
return err
}
_glfw.platformContext.inited = true
return nil
}
func terminateWGL() {
}
func (w *Window) createContextWGL(ctxconfig *ctxconfig, fbconfig *fbconfig) error {
var share _HGLRC
if ctxconfig.share != nil {
share = ctxconfig.share.context.platform.handle
}
dc, err := _GetDC(w.platform.handle)
if err != nil {
return err
}
w.context.platform.dc = dc
pixelFormat, err := w.choosePixelFormat(ctxconfig, fbconfig)
if err != nil {
return err
}
var pfd _PIXELFORMATDESCRIPTOR
if _, err := _DescribePixelFormat(w.context.platform.dc, int32(pixelFormat), uint32(unsafe.Sizeof(pfd)), &pfd); err != nil {
return err
}
if err := _SetPixelFormat(w.context.platform.dc, int32(pixelFormat), &pfd); err != nil {
return err
}
if ctxconfig.client == OpenGLAPI {
if ctxconfig.forward && !_glfw.platformContext.ARB_create_context {
return fmt.Errorf("goglfw: a forward compatible OpenGL context requested but WGL_ARB_create_context is unavailable: %w", VersionUnavailable)
}
if ctxconfig.profile != 0 && !_glfw.platformContext.ARB_create_context_profile {
return fmt.Errorf("goglfw: OpenGL profile requested but WGL_ARB_create_context_profile is unavailable: %w", VersionUnavailable)
}
} else {
if !_glfw.platformContext.ARB_create_context || !_glfw.platformContext.ARB_create_context_profile || !_glfw.platformContext.EXT_create_context_es2_profile {
return fmt.Errorf("goglfw: OpenGL ES requested but WGL_ARB_create_context_es2_profile is unavailable: %w", ApiUnavailable)
}
}
if _glfw.platformContext.ARB_create_context {
var flags int32
var mask int32
if ctxconfig.client == OpenGLAPI {
if ctxconfig.forward {
flags |= _WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB
}
if ctxconfig.profile == OpenGLCoreProfile {
mask |= _WGL_CONTEXT_CORE_PROFILE_BIT_ARB
} else if ctxconfig.profile == OpenGLCompatProfile {
mask |= _WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB
}
} else {
mask |= _WGL_CONTEXT_ES2_PROFILE_BIT_EXT
}
if ctxconfig.debug {
flags |= _WGL_CONTEXT_DEBUG_BIT_ARB
}
var attribs []int32
if ctxconfig.robustness != 0 {
if _glfw.platformContext.ARB_create_context_robustness {
if ctxconfig.robustness == NoResetNotification {
attribs = append(attribs, _WGL_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB, _WGL_NO_RESET_NOTIFICATION_ARB)
} else if ctxconfig.robustness == LoseContextOnReset {
attribs = append(attribs, _WGL_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB, _WGL_LOSE_CONTEXT_ON_RESET_ARB)
}
flags |= _WGL_CONTEXT_ROBUST_ACCESS_BIT_ARB
}
}
if ctxconfig.release != 0 {
if _glfw.platformContext.ARB_context_flush_control {
if ctxconfig.release == ReleaseBehaviorNone {
attribs = append(attribs, _WGL_CONTEXT_RELEASE_BEHAVIOR_ARB, _WGL_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB)
} else if ctxconfig.release == ReleaseBehaviorFlush {
attribs = append(attribs, _WGL_CONTEXT_RELEASE_BEHAVIOR_ARB, _WGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB)
}
}
}
if ctxconfig.noerror {
if _glfw.platformContext.ARB_create_context_no_error {
attribs = append(attribs, _WGL_CONTEXT_OPENGL_NO_ERROR_ARB, 1)
}
}
// NOTE: Only request an explicitly versioned context when necessary, as
// explicitly requesting version 1.0 does not always return the
// highest version supported by the driver
if ctxconfig.major != 1 || ctxconfig.minor != 0 {
attribs = append(attribs, _WGL_CONTEXT_MAJOR_VERSION_ARB, int32(ctxconfig.major))
attribs = append(attribs, _WGL_CONTEXT_MINOR_VERSION_ARB, int32(ctxconfig.minor))
}
if flags != 0 {
attribs = append(attribs, _WGL_CONTEXT_FLAGS_ARB, flags)
}
if mask != 0 {
attribs = append(attribs, _WGL_CONTEXT_PROFILE_MASK_ARB, mask)
}
attribs = append(attribs, 0, 0)
var err error
w.context.platform.handle, err = wglCreateContextAttribsARB(w.context.platform.dc, share, &attribs[0])
if err != nil {
return err
}
} else {
var err error
w.context.platform.handle, err = wglCreateContext(w.context.platform.dc)
if err != nil {
return err
}
if share != 0 {
if err := wglShareLists(share, w.context.platform.handle); err != nil {
return err
}
}
}
w.context.makeCurrent = makeContextCurrentWGL
w.context.swapBuffers = swapBuffersWGL
w.context.swapInterval = swapIntervalWGL
w.context.extensionSupported = extensionSupportedWGL
w.context.getProcAddress = getProcAddressWGL
w.context.destroy = destroyContextWGL
return nil
}
func getWGLContext(handle *Window) _HGLRC {
window := handle
if !_glfw.initialized {
panic(NotInitialized)
}
if window.context.source != NativeContextAPI {
// TODO: Should this return an error?
return 0
}
return window.context.platform.handle
}