| // 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") |
| } |