From 9e69532222f82a21ba1e545c5de46e0a59f97542 Mon Sep 17 00:00:00 2001 From: Ethan Reesor Date: Wed, 7 Jul 2021 16:34:12 -0500 Subject: [PATCH 1/3] Add OpenGL widget --- AUTHORS | 1 + declarative/opengl.go | 71 +++++++++ opengl.go | 360 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 432 insertions(+) create mode 100644 declarative/opengl.go create mode 100644 opengl.go diff --git a/AUTHORS b/AUTHORS index 5010e98b..1cca8c3e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -17,6 +17,7 @@ Audrius Karabanovas Benny Siegert Cary Cherng Dmitry Bagdanov +Ethan Reesor Ham Yeongtaek Hill iquanxin diff --git a/declarative/opengl.go b/declarative/opengl.go new file mode 100644 index 00000000..d6837f2e --- /dev/null +++ b/declarative/opengl.go @@ -0,0 +1,71 @@ +// Copyright 2021 The Walk Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package declarative + +import ( + "github.com/lxn/walk" +) + +type OpenGL struct { + // Window + + Accessibility Accessibility + Background Brush + ContextMenuItems []MenuItem + DoubleBuffering bool + Enabled Property + Font Font + MaxSize Size + MinSize Size + Name string + OnBoundsChanged walk.EventHandler + OnKeyDown walk.KeyEventHandler + OnKeyPress walk.KeyEventHandler + OnKeyUp walk.KeyEventHandler + OnMouseDown walk.MouseEventHandler + OnMouseMove walk.MouseEventHandler + OnMouseUp walk.MouseEventHandler + OnSizeChanged walk.EventHandler + Persistent bool + RightToLeftReading bool + ToolTipText Property + Visible Property + + // Widget + + Alignment Alignment2D + AlwaysConsumeSpace bool + Column int + ColumnSpan int + GraphicsEffects []walk.WidgetGraphicsEffect + Row int + RowSpan int + StretchFactor int + + // OpenGL + + AssignTo **walk.OpenGL + Setup walk.GLFunc + Paint walk.GLFunc + Teardown walk.GLFunc + Style int + PixelFormat []int32 // for wglCreateContextAttribsARB + Context []int32 // for wglCreateContextAttribsARB +} + +func (gl OpenGL) Create(builder *Builder) error { + w, err := walk.NewOpenGL(builder.Parent(), uint32(gl.Style), gl.Setup, gl.Paint, gl.Teardown, gl.PixelFormat, gl.Context) + if err != nil { + return err + } + + if gl.AssignTo != nil { + *gl.AssignTo = w + } + + return builder.InitWidget(gl, w, func() error { return nil }) +} diff --git a/opengl.go b/opengl.go new file mode 100644 index 00000000..ceb6481f --- /dev/null +++ b/opengl.go @@ -0,0 +1,360 @@ +// Copyright 2021 The Walk Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package walk + +import ( + "fmt" + "sync" + "time" + "unsafe" + + "github.com/lxn/win" +) + +const openGLWindowClass = `\o/ Walk_OpenGL_Class \o/` + +func init() { + AppendToWalkInit(func() { + MustRegisterWindowClass(openGLWindowClass) + }) +} + +// OpenGLContext is passed to GL lifecycle callbacks +type OpenGLContext struct { + widget *OpenGL + hDc win.HDC +} + +// DC returns the device context handle +func (c *OpenGLContext) DC() win.HDC { return c.hDc } + +// Handle returns the OpenGL rendering context handle +func (c *OpenGLContext) Handle() win.HGLRC { return c.widget.hGlrc } + +// Widget returns the OpenGL widget +func (c *OpenGLContext) Widget() *OpenGL { return c.widget } + +// GLFunc sets up, paints, or tears down OpenGL content +type GLFunc func(*OpenGLContext) error + +// GLTickFunc returns whether a window repaint is required +type GLTickFunc func(*OpenGLContext) bool + +type OpenGL struct { + WidgetBase + hGlrc win.HGLRC + + setup, paint, teardown GLFunc + + pixFmt int32 + ppfd *win.PIXELFORMATDESCRIPTOR + contextAttrs []int32 + + tickMu sync.Mutex + tickFn GLTickFunc + tickTk *time.Ticker + tickCh chan struct{} +} + +// NewOpenGL creates and initializes an OpenGL widget. +// +// pixFmtAttrs (can be null) is a list of attributes passed to +// wglCreateContextAttribsARB. contextAttrs (can be null) is a list of +// attributes passed to wglCreateContextAttribsARB. +// +// If wglCreateContextAttribsARB is unavailable, an equivalent pixel format +// descriptor will be used (attributes without a corresponding property will be +// ignored). If wglCreateContextAttribsARB is unavailable, wglCreateContext will +// be used. +func NewOpenGL(parent Container, style uint32, setup, paint, teardown GLFunc, pixFmtAttrs, contextAttrs []int32) (*OpenGL, error) { + gl := &OpenGL{setup: setup, paint: paint, teardown: teardown} + err := gl.init(parent, style) + if err != nil { + return nil, err + } + + if pixFmtAttrs == nil { + dc := win.GetDC(gl.hWnd) + defer win.ReleaseDC(gl.hWnd, dc) + + pixFmtAttrs = []int32{ + win.WGL_SUPPORT_OPENGL_ARB, 1, + win.WGL_DRAW_TO_WINDOW_ARB, 1, + win.WGL_PIXEL_TYPE_ARB, win.WGL_TYPE_RGBA_ARB, + win.WGL_COLOR_BITS_ARB, win.GetDeviceCaps(dc, win.BITSPIXEL), + 0, + } + } else { + pixFmtAttrs = openGLValidateAttribs(pixFmtAttrs) + } + + if contextAttrs == nil { + gl.contextAttrs = []int32{ + win.WGL_CONTEXT_PROFILE_MASK_ARB, win.WGL_CONTEXT_CORE_PROFILE_BIT_ARB, + 0, + } + } else { + gl.contextAttrs = openGLValidateAttribs(contextAttrs) + } + + gl.pixFmt, gl.ppfd, err = openGLChoosePixelFormat(pixFmtAttrs) + if err != nil { + return nil, err + } + + return gl, nil +} + +func (gl *OpenGL) init(parent Container, style uint32) error { + if err := InitWidget( + gl, + parent, + customWidgetWindowClass, + win.WS_VISIBLE|uint32(style), + 0); err != nil { + return err + } + + return nil +} + +func (*OpenGL) CreateLayoutItem(ctx *LayoutContext) LayoutItem { + return NewGreedyLayoutItem() +} + +func (gl *OpenGL) context(dc win.HDC) *OpenGLContext { + return &OpenGLContext{ + widget: gl, + hDc: dc, + } +} + +func (gl *OpenGL) createContext() bool { + dc := win.GetDC(gl.hWnd) + defer win.ReleaseDC(gl.hWnd, dc) + + if !win.SetPixelFormat(dc, gl.pixFmt, gl.ppfd) { + processError(lastError("SetPixelFormat")) + return false + } + + if win.HasWglCreateContextAttribsARB() { + gl.hGlrc = win.WglCreateContextAttribsARB(dc, 0, &gl.contextAttrs[0]) + if gl.hGlrc == 0 { + processError(lastError("WglCreateContextAttribsARB")) + return false + } + } else { + gl.hGlrc = win.WglCreateContext(dc) + if gl.hGlrc == 0 { + processError(lastError("WglCreateContext")) + return false + } + } + + win.WglMakeCurrent(dc, gl.hGlrc) + defer win.WglMakeCurrent(0, 0) + + if gl.setup != nil { + err := gl.setup(gl.context(dc)) + if err != nil { + newError(fmt.Sprintf("setup func failed: %v", err)) + return false + } + } + + return true +} + +func (gl *OpenGL) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { + switch msg { + case win.WM_PAINT: + if gl.hGlrc == 0 { + if !gl.createContext() { + break + } + } + + if gl.paint == nil { + newError("paint func is nil") + break + } + + var ps win.PAINTSTRUCT + var dc win.HDC + if wParam == 0 { + dc = win.BeginPaint(gl.hWnd, &ps) + if dc == 0 { + processError(lastError("BeginPaint")) + break + } + defer win.EndPaint(gl.hWnd, &ps) + } else { + dc = win.HDC(wParam) + } + + win.WglMakeCurrent(dc, gl.hGlrc) + defer win.WglMakeCurrent(0, 0) + + err := gl.paint(gl.context(dc)) + if err != nil { + newError(fmt.Sprintf("paint func failed: %v", err)) + } + + return 0 + + case win.WM_DESTROY: + if gl.teardown != nil { + dc := win.GetDC(gl.hWnd) + win.WglMakeCurrent(dc, gl.hGlrc) + + err := gl.teardown(gl.context(dc)) + if err != nil { + newError(fmt.Sprintf("teardown func failed: %v", err)) + } + + win.WglMakeCurrent(0, 0) + win.ReleaseDC(gl.hWnd, dc) + } + + win.WglDeleteContext(gl.hGlrc) + gl.hGlrc = 0 + break // continue with base teardown + } + + return gl.WidgetBase.WndProc(hwnd, msg, wParam, lParam) +} + +func openGLValidateAttribs(attribs []int32) []int32 { + if len(attribs) == 0 { + // length zero, no attributes + return []int32{0} + } + + if len(attribs)%2 == 0 { + // length divisible by 2, add null terminator + return append(attribs, 0) + } + + if attribs[len(attribs)-1] != 0 { + // length is not divisible by 2, but last element is not null + panic("WGL attributes list must be null terminated or contain an even number of elements") + } + + return attribs +} + +func openGLChoosePixelFormat(attribs []int32) (int32, *win.PIXELFORMATDESCRIPTOR, error) { + // create dummy window, dispose after + w, err := NewMainWindow() + if err != nil { + return 0, nil, err + } + defer w.Dispose() + + // get the device context, release after + hDC := win.GetDC(w.hWnd) + if hDC == 0 { + return 0, nil, lastError("GetDC") + } + defer win.ReleaseDC(w.hWnd, hDC) + + // legacy pixel format + pfd := win.PIXELFORMATDESCRIPTOR{ + NSize: uint16(unsafe.Sizeof(win.PIXELFORMATDESCRIPTOR{})), + NVersion: 1, + DwLayerMask: win.PFD_MAIN_PLANE, + } + wglConvertAttributes(&pfd, attribs) + + pixelFormat := win.ChoosePixelFormat(hDC, &pfd) + if pixelFormat == 0 { + return 0, nil, lastError("ChoosePixelFormat") + } + + if !win.SetPixelFormat(hDC, pixelFormat, &pfd) { + return 0, nil, lastError("SetPixelFormat") + } + + // create dummy context, dispose after + hRC := win.WglCreateContext(hDC) + if hRC == 0 { + return 0, nil, lastError("WglCreateContext") + } + defer win.WglDeleteContext(hRC) + + if !win.WglMakeCurrent(hDC, hRC) { + return 0, nil, lastError("WglMakeCurrent") + } + defer win.WglMakeCurrent(0, 0) + + // get WGL extension functions + win.InitWglExt() + + // use the legacy format if the extension is not available + if !win.HasWglChoosePixelFormatARB() { + return pixelFormat, &pfd, nil + } + + var formatARB int32 + var numFormats uint32 + if !win.WglChoosePixelFormatARB(hDC, &attribs[0], nil, 1, &formatARB, &numFormats) { + return 0, nil, lastError("WglChoosePixelFormatARB") + } + + // use the legacy format if no acceptable formats are found + if numFormats == 0 { + return pixelFormat, &pfd, nil + } + + // update the PFD + if !win.DescribePixelFormat(hDC, formatARB, uint32(unsafe.Sizeof(pfd)), &pfd) { + return 0, nil, lastError("DescribePixelFormat") + } + + return formatARB, &pfd, nil +} + +func wglConvertAttributes(ppfd *win.PIXELFORMATDESCRIPTOR, attribs []int32) { + amap := map[int32]int32{} + for i, n := 0, len(attribs)/2; i < n; i++ { + amap[attribs[2*i]] = attribs[2*i+1] + } + + ppfd.CColorBits = byte(amap[win.WGL_COLOR_BITS_ARB]) + ppfd.CRedBits = byte(amap[win.WGL_RED_BITS_ARB]) + ppfd.CRedShift = byte(amap[win.WGL_RED_SHIFT_ARB]) + ppfd.CGreenBits = byte(amap[win.WGL_GREEN_BITS_ARB]) + ppfd.CGreenShift = byte(amap[win.WGL_GREEN_SHIFT_ARB]) + ppfd.CBlueBits = byte(amap[win.WGL_BLUE_BITS_ARB]) + ppfd.CBlueShift = byte(amap[win.WGL_BLUE_SHIFT_ARB]) + ppfd.CAlphaBits = byte(amap[win.WGL_ALPHA_BITS_ARB]) + ppfd.CAlphaShift = byte(amap[win.WGL_ALPHA_SHIFT_ARB]) + ppfd.CAccumBits = byte(amap[win.WGL_ACCUM_BITS_ARB]) + ppfd.CAccumRedBits = byte(amap[win.WGL_ACCUM_RED_BITS_ARB]) + ppfd.CAccumGreenBits = byte(amap[win.WGL_ACCUM_GREEN_BITS_ARB]) + ppfd.CAccumBlueBits = byte(amap[win.WGL_ACCUM_BLUE_BITS_ARB]) + ppfd.CAccumAlphaBits = byte(amap[win.WGL_ACCUM_ALPHA_BITS_ARB]) + ppfd.CDepthBits = byte(amap[win.WGL_DEPTH_BITS_ARB]) + ppfd.CStencilBits = byte(amap[win.WGL_STENCIL_BITS_ARB]) + ppfd.CAuxBuffers = byte(amap[win.WGL_AUX_BUFFERS_ARB]) + + if amap[win.WGL_SUPPORT_OPENGL_ARB] == 1 { + ppfd.DwFlags |= win.PFD_SUPPORT_OPENGL + } + + if amap[win.WGL_DRAW_TO_WINDOW_ARB] == 1 { + ppfd.DwFlags |= win.PFD_DRAW_TO_WINDOW + } + + switch amap[win.WGL_PIXEL_TYPE_ARB] { + case win.WGL_TYPE_RGBA_ARB, 0: + ppfd.IPixelType = win.PFD_TYPE_RGBA + case win.WGL_TYPE_COLORINDEX_ARB: + ppfd.IPixelType = win.PFD_TYPE_COLORINDEX + } +} From 455227e2088bb0e470656ccd589f1e4eb3c2f78d Mon Sep 17 00:00:00 2001 From: Ethan Reesor Date: Wed, 7 Jul 2021 16:35:32 -0500 Subject: [PATCH 2/3] Simple OpenGL example --- .../opengl/triangle/rsrc_windows_amd64.syso | Bin 0 -> 1012 bytes .../opengl/triangle/triangle.exe.manifest | 15 +++++++ examples/opengl/triangle/triangle.go | 41 ++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 examples/opengl/triangle/rsrc_windows_amd64.syso create mode 100644 examples/opengl/triangle/triangle.exe.manifest create mode 100644 examples/opengl/triangle/triangle.go diff --git a/examples/opengl/triangle/rsrc_windows_amd64.syso b/examples/opengl/triangle/rsrc_windows_amd64.syso new file mode 100644 index 0000000000000000000000000000000000000000..2d6f6311412826ebced74e961e8d6c0c77702940 GIT binary patch literal 1012 zcmb7D&5qMB5Kh&-6p8yDBOeh>(r#NpmF`llwAvLaLTXvKy3VwskKa6cfvpptLE*xSQs2-SPUx=BDq3D#mmm^wX4#^-ygL1IA~z@4?( zGz{ww2p&5izzztPi<@v54JKioss@jozXiQel3S&3htK_1Lk + + + + + + + + + + PerMonitorV2, PerMonitor + True + + + diff --git a/examples/opengl/triangle/triangle.go b/examples/opengl/triangle/triangle.go new file mode 100644 index 00000000..a04379eb --- /dev/null +++ b/examples/opengl/triangle/triangle.go @@ -0,0 +1,41 @@ +// Copyright 2021 The Walk Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "github.com/go-gl/gl/all-core/gl" + "github.com/lxn/walk" + . "github.com/lxn/walk/declarative" +) + +func main() { + MainWindow{ + Title: "Walk OpenGL Example", + MinSize: Size{320, 240}, + Layout: HBox{}, + Children: []Widget{ + OpenGL{ + Setup: func(*walk.OpenGLContext) error { + return gl.Init() + }, + Paint: func(glc *walk.OpenGLContext) error { + sz := glc.Widget().Size() + gl.Viewport(0, 0, int32(sz.Width), int32(sz.Height)) + gl.Clear(gl.COLOR_BUFFER_BIT) + gl.Begin(gl.TRIANGLES) + gl.Color3f(1.0, 0.0, 0.0) + gl.Vertex2i(0, 1) + gl.Color3f(0.0, 1.0, 0.0) + gl.Vertex2i(-1, -1) + gl.Color3f(0.0, 0.0, 1.0) + gl.Vertex2i(1, -1) + gl.End() + gl.Flush() + return nil + }, + }, + }, + }.Run() +} From b6cf53106f714ba9b4aac686f610c6729644dd28 Mon Sep 17 00:00:00 2001 From: Ethan Reesor Date: Wed, 7 Jul 2021 16:35:46 -0500 Subject: [PATCH 3/3] Add window timers --- window.go | 5 +++++ window_timer.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 window_timer.go diff --git a/window.go b/window.go index f580f663..f304119a 100644 --- a/window.go +++ b/window.go @@ -441,6 +441,8 @@ type WindowBase struct { visible bool enabled bool acc *Accessibility + timerNextID TimerID + timerFuncs map[TimerID]TimerFunc } var ( @@ -2460,6 +2462,9 @@ func (wb *WindowBase) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) case win.WM_KEYUP: wb.handleKeyUp(wParam, lParam) + case win.WM_TIMER: + wb.handleTimer(wParam, lParam) + case win.WM_DROPFILES: wb.dropFilesPublisher.Publish(win.HDROP(wParam)) diff --git a/window_timer.go b/window_timer.go new file mode 100644 index 00000000..d5bcd9ab --- /dev/null +++ b/window_timer.go @@ -0,0 +1,45 @@ +// Copyright 2021 The Walk Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package walk + +import ( + "time" + + "github.com/lxn/win" +) + +type TimerID int + +type TimerFunc func(wb *WindowBase) + +func (wb *WindowBase) AddTimer(d time.Duration, fn TimerFunc) (TimerID, error) { + wb.timerNextID++ + id := wb.timerNextID + + if wb.timerFuncs == nil { + wb.timerFuncs = map[TimerID]TimerFunc{} + } + wb.timerFuncs[id] = fn + + if win.SetTimer(wb.hWnd, uintptr(id), uint32(d.Milliseconds()), 0) == 0 { + return 0, lastError("SetTimer") + } + + return id, nil +} + +func (wb *WindowBase) ClearTimer(id TimerID) { + win.KillTimer(wb.hWnd, uintptr(id)) + delete(wb.timerFuncs, id) +} + +func (wb *WindowBase) handleTimer(wParam, lParam uintptr) { + fn := wb.timerFuncs[TimerID(wParam)] + if fn != nil { + fn(wb) + } +}