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