// Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build windows // +build windows package gldriver import ( "errors" "fmt" "runtime" "syscall" "unsafe" "golang.org/x/exp/shiny/driver/internal/win32" "golang.org/x/exp/shiny/screen" "golang.org/x/mobile/event/key" "golang.org/x/mobile/event/lifecycle" "golang.org/x/mobile/event/mouse" "golang.org/x/mobile/event/paint" "golang.org/x/mobile/event/size" "golang.org/x/mobile/gl" ) // TODO: change this to true, after manual testing on Win32. const useLifecycler = false // TODO: change this to true, after manual testing on Win32. const handleSizeEventsAtChannelReceive = false func main(f func(screen.Screen)) error { return win32.Main(func() { f(theScreen) }) } var ( eglGetPlatformDisplayEXT = gl.LibEGL.NewProc("eglGetPlatformDisplayEXT") eglInitialize = gl.LibEGL.NewProc("eglInitialize") eglChooseConfig = gl.LibEGL.NewProc("eglChooseConfig") eglGetError = gl.LibEGL.NewProc("eglGetError") eglBindAPI = gl.LibEGL.NewProc("eglBindAPI") eglCreateWindowSurface = gl.LibEGL.NewProc("eglCreateWindowSurface") eglCreateContext = gl.LibEGL.NewProc("eglCreateContext") eglMakeCurrent = gl.LibEGL.NewProc("eglMakeCurrent") eglSwapInterval = gl.LibEGL.NewProc("eglSwapInterval") eglDestroySurface = gl.LibEGL.NewProc("eglDestroySurface") eglSwapBuffers = gl.LibEGL.NewProc("eglSwapBuffers") ) type eglConfig uintptr // void* type eglInt int32 var rgb888 = [...]eglInt{ _EGL_RENDERABLE_TYPE, _EGL_OPENGL_ES2_BIT, _EGL_SURFACE_TYPE, _EGL_WINDOW_BIT, _EGL_BLUE_SIZE, 8, _EGL_GREEN_SIZE, 8, _EGL_RED_SIZE, 8, _EGL_DEPTH_SIZE, 16, _EGL_STENCIL_SIZE, 8, _EGL_NONE, } type ctxWin32 struct { ctx uintptr display uintptr // EGLDisplay surface uintptr // EGLSurface } func newWindow(opts *screen.NewWindowOptions) (uintptr, error) { w, err := win32.NewWindow(opts) if err != nil { return 0, err } return uintptr(w), nil } func initWindow(w *windowImpl) { w.glctx, w.worker = gl.NewContext() } func showWindow(w *windowImpl) { // Show makes an initial call to sizeEvent (via win32.SizeEvent), where // we setup the EGL surface and GL context. win32.Show(syscall.Handle(w.id)) } func closeWindow(id uintptr) {} // TODO func drawLoop(w *windowImpl) { runtime.LockOSThread() display := w.ctx.(ctxWin32).display surface := w.ctx.(ctxWin32).surface ctx := w.ctx.(ctxWin32).ctx if ret, _, _ := eglMakeCurrent.Call(display, surface, surface, ctx); ret == 0 { panic(fmt.Sprintf("eglMakeCurrent failed: %v", eglErr())) } // TODO(crawshaw): exit this goroutine on Release. workAvailable := w.worker.WorkAvailable() for { select { case <-workAvailable: w.worker.DoWork() case <-w.publish: loop: for { select { case <-workAvailable: w.worker.DoWork() default: break loop } } if ret, _, _ := eglSwapBuffers.Call(display, surface); ret == 0 { panic(fmt.Sprintf("eglSwapBuffers failed: %v", eglErr())) } w.publishDone <- screen.PublishResult{} } } } func init() { win32.SizeEvent = sizeEvent win32.PaintEvent = paintEvent win32.MouseEvent = mouseEvent win32.KeyEvent = keyEvent win32.LifecycleEvent = lifecycleEvent } func lifecycleEvent(hwnd syscall.Handle, to lifecycle.Stage) { theScreen.mu.Lock() w := theScreen.windows[uintptr(hwnd)] theScreen.mu.Unlock() if w.lifecycleStage == to { return } w.Send(lifecycle.Event{ From: w.lifecycleStage, To: to, DrawContext: w.glctx, }) w.lifecycleStage = to } func mouseEvent(hwnd syscall.Handle, e mouse.Event) { theScreen.mu.Lock() w := theScreen.windows[uintptr(hwnd)] theScreen.mu.Unlock() w.Send(e) } func keyEvent(hwnd syscall.Handle, e key.Event) { theScreen.mu.Lock() w := theScreen.windows[uintptr(hwnd)] theScreen.mu.Unlock() w.Send(e) } func paintEvent(hwnd syscall.Handle, e paint.Event) { theScreen.mu.Lock() w := theScreen.windows[uintptr(hwnd)] theScreen.mu.Unlock() if w.ctx == nil { // Sometimes a paint event comes in before initial // window size is set. Ignore it. return } // TODO: the paint.Event should have External: true. w.Send(paint.Event{}) } func sizeEvent(hwnd syscall.Handle, e size.Event) { theScreen.mu.Lock() w := theScreen.windows[uintptr(hwnd)] theScreen.mu.Unlock() if w.ctx == nil { // This is the initial size event on window creation. // Create an EGL surface and spin up a GL context. if err := createEGLSurface(hwnd, w); err != nil { panic(err) } go drawLoop(w) } if !handleSizeEventsAtChannelReceive { w.szMu.Lock() w.sz = e w.szMu.Unlock() } w.Send(e) if handleSizeEventsAtChannelReceive { return } // Screen is dirty, generate a paint event. // // The sizeEvent function is called on the goroutine responsible for // calling the GL worker.DoWork. When compiling with -tags gldebug, // these GL calls are blocking (so we can read the error message), so // to make progress they need to happen on another goroutine. go func() { // TODO: this call to Viewport is not right, but is very hard to // do correctly with our async events channel model. We want // the call to Viewport to be made the instant before the // paint.Event is received. w.glctxMu.Lock() w.glctx.Viewport(0, 0, e.WidthPx, e.HeightPx) w.glctx.ClearColor(0, 0, 0, 1) w.glctx.Clear(gl.COLOR_BUFFER_BIT) w.glctxMu.Unlock() w.Send(paint.Event{}) }() } func eglErr() error { if ret, _, _ := eglGetError.Call(); ret != _EGL_SUCCESS { return errors.New(eglErrString(ret)) } return nil } func createEGLSurface(hwnd syscall.Handle, w *windowImpl) error { var displayAttribPlatforms = [][]eglInt{ // Default []eglInt{ _EGL_PLATFORM_ANGLE_TYPE_ANGLE, _EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE, _EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, _EGL_DONT_CARE, _EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, _EGL_DONT_CARE, _EGL_NONE, }, // Direct3D 11 []eglInt{ _EGL_PLATFORM_ANGLE_TYPE_ANGLE, _EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, _EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, _EGL_DONT_CARE, _EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, _EGL_DONT_CARE, _EGL_NONE, }, // Direct3D 9 []eglInt{ _EGL_PLATFORM_ANGLE_TYPE_ANGLE, _EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE, _EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, _EGL_DONT_CARE, _EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, _EGL_DONT_CARE, _EGL_NONE, }, // Direct3D 11 with WARP // https://msdn.microsoft.com/en-us/library/windows/desktop/gg615082.aspx []eglInt{ _EGL_PLATFORM_ANGLE_TYPE_ANGLE, _EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, _EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE, _EGL_PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE, _EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, _EGL_DONT_CARE, _EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, _EGL_DONT_CARE, _EGL_NONE, }, } dc, err := win32.GetDC(hwnd) if err != nil { return fmt.Errorf("win32.GetDC failed: %v", err) } var display uintptr = _EGL_NO_DISPLAY for i, displayAttrib := range displayAttribPlatforms { lastTry := i == len(displayAttribPlatforms)-1 display, _, _ = eglGetPlatformDisplayEXT.Call( _EGL_PLATFORM_ANGLE_ANGLE, uintptr(dc), uintptr(unsafe.Pointer(&displayAttrib[0])), ) if display == _EGL_NO_DISPLAY { if !lastTry { continue } return fmt.Errorf("eglGetPlatformDisplayEXT failed: %v", eglErr()) } if ret, _, _ := eglInitialize.Call(display, 0, 0); ret == 0 { if !lastTry { continue } return fmt.Errorf("eglInitialize failed: %v", eglErr()) } } eglBindAPI.Call(_EGL_OPENGL_ES_API) if err := eglErr(); err != nil { return err } var numConfigs eglInt var config eglConfig ret, _, _ := eglChooseConfig.Call( display, uintptr(unsafe.Pointer(&rgb888[0])), uintptr(unsafe.Pointer(&config)), 1, uintptr(unsafe.Pointer(&numConfigs)), ) if ret == 0 { return fmt.Errorf("eglChooseConfig failed: %v", eglErr()) } if numConfigs <= 0 { return errors.New("eglChooseConfig found no valid config") } surface, _, _ := eglCreateWindowSurface.Call(display, uintptr(config), uintptr(hwnd), 0, 0) if surface == _EGL_NO_SURFACE { return fmt.Errorf("eglCreateWindowSurface failed: %v", eglErr()) } contextAttribs := [...]eglInt{ _EGL_CONTEXT_CLIENT_VERSION, 2, _EGL_NONE, } context, _, _ := eglCreateContext.Call( display, uintptr(config), _EGL_NO_CONTEXT, uintptr(unsafe.Pointer(&contextAttribs[0])), ) if context == _EGL_NO_CONTEXT { return fmt.Errorf("eglCreateContext failed: %v", eglErr()) } eglSwapInterval.Call(display, 1) w.ctx = ctxWin32{ ctx: context, display: display, surface: surface, } return nil } func surfaceCreate() error { return errors.New("gldriver: surface creation not implemented on windows") }