1# This file is part of Xpra. 2# Copyright (C) 2017-2021 Antoine Martin <antoine@xpra.org> 3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any 4# later version. See the file COPYING for details. 5 6from ctypes import sizeof, byref, cast, c_void_p, FormatError 7from ctypes.wintypes import LPCWSTR 8 9from xpra.client.gl.gl_check import check_PyOpenGL_support 10from xpra.platform.win32.gui import get_window_handle 11from xpra.platform.win32.constants import ( 12 CS_OWNDC, CS_HREDRAW, CS_VREDRAW, COLOR_WINDOW, 13 WS_OVERLAPPED, WS_SYSMENU, CW_USEDEFAULT, 14 ) 15from xpra.platform.win32.common import ( 16 GetDC, SwapBuffers, ChoosePixelFormat, DescribePixelFormat, SetPixelFormat, 17 BeginPaint, EndPaint, DestroyWindow, UnregisterClassW, 18 GetModuleHandleA, RegisterClassExA, CreateWindowExA, DefWindowProcA, WNDPROC, WNDCLASSEX 19 ) 20from xpra.platform.win32.glwin32 import ( 21 wglCreateContext, wglMakeCurrent, wglDeleteContext, 22 PIXELFORMATDESCRIPTOR, PFD_TYPE_RGBA, PFD_DRAW_TO_WINDOW, PFD_SUPPORT_OPENGL, 23 PFD_DOUBLEBUFFER, PFD_DEPTH_DONTCARE, PFD_SUPPORT_COMPOSITION, PFD_MAIN_PLANE, 24 PAINTSTRUCT, 25 ) 26from xpra.log import Logger 27 28log = Logger("opengl") 29 30DOUBLE_BUFFERED = True 31 32 33def DefWndProc(hwnd, msg, wParam, lParam): 34 return DefWindowProcA(hwnd, msg, wParam, lParam) 35 36class WGLWindowContext: 37 38 def __init__(self, hwnd, hdc, context): 39 self.hwnd = hwnd 40 self.hdc = hdc 41 self.context = context 42 self.ps = None 43 self.paint_hdc = None 44 45 def __enter__(self): 46 log("wglMakeCurrent(%#x, %#x)", self.hdc, self.context) 47 r = wglMakeCurrent(self.hdc, self.context) 48 if not r: 49 raise Exception("wglMakeCurrent failed") 50 self.ps = PAINTSTRUCT() 51 self.paint_hdc = BeginPaint(self.hwnd, byref(self.ps)) 52 assert self.paint_hdc, "BeginPaint: no display device context" 53 log("BeginPaint hdc=%#x", self.paint_hdc) 54 return self 55 56 def __exit__(self, *_args): 57 assert self.context 58 log("EndPaint") 59 EndPaint(self.hwnd, byref(self.ps)) 60 wglMakeCurrent(0, 0) 61 self.paint_hdc = None 62 self.ps = None 63 64 def update_geometry(self): 65 """ not needed on MS Windows """ 66 67 def swap_buffers(self): 68 assert self.paint_hdc 69 log("swap_buffers: calling SwapBuffers(%#x)", self.paint_hdc) 70 SwapBuffers(self.paint_hdc) 71 72 def __repr__(self): 73 return "WGLWindowContext(%#x)" % self.hwnd 74 75 76class WGLContext: 77 78 def __init__(self, alpha=True): 79 self.alpha = alpha 80 self.hwnd = 0 81 self.hdc = 0 82 self.context = 0 83 self.pixel_format_props = {} 84 85 def check_support(self, force_enable=False): 86 #create a temporary window to query opengl attributes: 87 hInst = GetModuleHandleA(0) 88 log("check_support() GetModuleHandleW()=%#x", hInst or 0) 89 classname = "Xpra Temporary Window for OpenGL" 90 wndc = WNDCLASSEX() 91 wndc.cbSize = sizeof(WNDCLASSEX) 92 wndc.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW 93 wndc.hInstance = hInst 94 wndc.hBrush = COLOR_WINDOW 95 wndc.lpszClassName = classname 96 wndc.lpfnWndProc = WNDPROC(DefWndProc) 97 reg_atom = RegisterClassExA(byref(wndc)) 98 log("check_support() RegisterClassExW()=%#x", reg_atom or 0) 99 if not reg_atom: 100 return {"info" : "disabled: failed to register window class, %s" % FormatError()} 101 style = WS_OVERLAPPED | WS_SYSMENU 102 window_name = "Xpra OpenGL Test" 103 self.hwnd = CreateWindowExA(0, reg_atom, window_name, style, 104 CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, 105 0, 0, hInst, None) 106 log("check_support() CreateWindowExW()=%#x", self.hwnd or 0) 107 if not self.hwnd: 108 return {"info" : "disabled: failed to create temporary window, %s" % FormatError()} 109 try: 110 self.context = self.create_wgl_context(self.hwnd) 111 with WGLWindowContext(self.hwnd, self.hdc, self.context): 112 props = check_PyOpenGL_support(force_enable) 113 props["display_mode"] = [["SINGLE","DOUBLE"][int(DOUBLE_BUFFERED)], ] #, "ALPHA"] 114 return props 115 finally: 116 hwnd = self.hwnd 117 self.destroy() 118 if hwnd and not DestroyWindow(hwnd): 119 log.warn("Warning: failed to destroy temporary OpenGL test window") 120 latom = c_void_p(reg_atom) 121 if not UnregisterClassW(cast(latom, LPCWSTR), hInst): 122 log.warn("Warning: failed to unregister class for OpenGL test window") 123 log.warn(" for class %r and module handle %#x:", classname, hInst or 0) 124 log.warn(" '%s'", FormatError()) 125 126 def get_bit_depth(self): 127 return 0 128 129 def is_double_buffered(self): 130 return DOUBLE_BUFFERED #self.pixel_format_props.get("double-buffered", False) 131 132 def get_paint_context(self, gdk_window): 133 hwnd = get_window_handle(gdk_window) 134 if self.hwnd!=hwnd: 135 #(this shouldn't happen) 136 #just make sure we don't keep using a context for a different handle: 137 self.destroy() 138 if not self.context: 139 self.context = self.create_wgl_context(hwnd) 140 return WGLWindowContext(hwnd, self.hdc, self.context) 141 142 def create_wgl_context(self, hwnd): 143 bpc = 8 144 self.hwnd = hwnd 145 self.pixel_format_props = {} 146 self.hdc = GetDC(hwnd) 147 log("create_wgl_context(%#x) hdc=%#x", hwnd, self.hdc) 148 flags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DEPTH_DONTCARE 149 if self.alpha: 150 flags |= PFD_SUPPORT_COMPOSITION 151 if DOUBLE_BUFFERED: 152 flags |= PFD_DOUBLEBUFFER 153 pfd = PIXELFORMATDESCRIPTOR() 154 pfd.nsize = sizeof(PIXELFORMATDESCRIPTOR) 155 pfd.nVersion = 1 156 pfd.dwFlags = flags 157 pfd.iPixelType = PFD_TYPE_RGBA 158 pfd.cColorBits = bpc*(3+int(self.alpha)) 159 pfd.cRedBits = bpc 160 pfd.cRedShift = 0 161 pfd.cGreenBits = bpc 162 pfd.cGreenShift = 0 163 pfd.cBlueBits = bpc 164 pfd.cBlueShift = 0 165 pfd.cAlphaBits = int(self.alpha)*8 166 pfd.cAlphaShift = 0 167 pfd.cAccumBits = 0 168 pfd.cAccumRedBits = 0 169 pfd.cAccumGreenBits = 0 170 pfd.cAccumBlueBits = 0 171 pfd.cAccumAlphaBits = 0 172 pfd.cDepthBits = 24 173 pfd.cStencilBits = 2 174 pfd.cAuxBuffers = 0 175 pfd.iLayerType = PFD_MAIN_PLANE #ignored 176 pfd.bReserved = 0 177 pfd.dwLayerMask = 0 178 pfd.dwVisibleMask = 0 179 pfd.dwDamageMask = 0 180 pf = ChoosePixelFormat(self.hdc, byref(pfd)) 181 log("ChoosePixelFormat for window %#x and %i bpc: %#x", hwnd, bpc, pf) 182 if not SetPixelFormat(self.hdc, pf, byref(pfd)): 183 raise Exception("SetPixelFormat failed") 184 if not DescribePixelFormat(self.hdc, pf, sizeof(PIXELFORMATDESCRIPTOR), byref(pfd)): 185 raise Exception("DescribePixelFormat failed") 186 self.pixel_format_props.update({ 187 "rgba" : pfd.iPixelType==PFD_TYPE_RGBA, 188 "depth" : pfd.cColorBits, 189 "red-size" : pfd.cRedBits, 190 "green-size" : pfd.cGreenBits, 191 "blue-size" : pfd.cBlueBits, 192 "alpha-size" : pfd.cAlphaBits, 193 "red-shift" : pfd.cRedShift, 194 "green-shift" : pfd.cGreenShift, 195 "blue-shift" : pfd.cBlueShift, 196 "alpha-shift" : pfd.cAlphaShift, 197 "accum-red-size" : pfd.cAccumRedBits, 198 "accum-green-size" : pfd.cAccumGreenBits, 199 "accum-blue-size" : pfd.cAccumBlueBits, 200 "accum-size" : pfd.cAccumBits, 201 "depth-size" : pfd.cDepthBits, 202 "stencil-size" : pfd.cStencilBits, 203 "aux-buffers" : pfd.cAuxBuffers, 204 "visible-mask" : int(pfd.dwVisibleMask), 205 "double-buffered" : bool(pfd.dwFlags & PFD_DOUBLEBUFFER) 206 }) 207 log("DescribePixelFormat: %s", self.pixel_format_props) 208 context = wglCreateContext(self.hdc) 209 assert context, "wglCreateContext failed" 210 log("wglCreateContext(%#x)=%#x", self.hdc, context) 211 return context 212 213 def destroy(self): 214 c = self.context 215 if c: 216 self.context = 0 217 if not wglDeleteContext(c): 218 raise Exception("wglDeleteContext failed for context %#x" % c) 219 self.hwnd = 0 220 221 def __repr__(self): 222 return "WGLContext(%#x)" % self.context 223 224GLContext = WGLContext 225