| // Copyright 2016 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 !android |
| // +build !android |
| |
| // Package glwidget provides a widget containing a GL ES framebuffer. |
| package glwidget |
| |
| import ( |
| "fmt" |
| "image" |
| "image/draw" |
| |
| "golang.org/x/exp/shiny/driver/gldriver" |
| "golang.org/x/exp/shiny/widget/node" |
| "golang.org/x/mobile/gl" |
| ) |
| |
| // GL is a widget that maintains an OpenGL ES context. |
| // |
| // The Draw function is responsible for configuring the GL viewport |
| // and for publishing the result to the widget by calling the Publish |
| // method when the frame is complete. A typical draw function: |
| // |
| // func(w *glwidget.GL) { |
| // w.Ctx.Viewport(0, 0, w.Rect.Dx(), w.Rect.Dy()) |
| // w.Ctx.ClearColor(0, 0, 0, 1) |
| // w.Ctx.Clear(gl.COLOR_BUFFER_BIT) |
| // // ... draw the frame |
| // w.Publish() |
| // } |
| // |
| // The GL context is separate from the one used by the gldriver to |
| // render the window, and is only used by the glwidget package during |
| // initialization and for the duration of the Publish call. This means |
| // a glwidget user is free to use Ctx as a background GL context |
| // concurrently with the primary UI drawing done by the gldriver. |
| type GL struct { |
| node.LeafEmbed |
| |
| Ctx gl.Context |
| |
| draw func(*GL) |
| framebuffer gl.Framebuffer |
| tex gl.Texture |
| dst *image.RGBA |
| origin image.Point |
| } |
| |
| // NewGL creates a GL widget with a Draw function called when painted. |
| func NewGL(drawFunc func(*GL)) *GL { |
| // TODO: use the size of the monitor as a bound for texture size. |
| const maxWidth, maxHeight = 4096, 3072 |
| |
| glctx, err := gldriver.NewContext() |
| if err != nil { |
| panic(fmt.Sprintf("glwidget: %v", err)) // TODO: return error? |
| } |
| w := &GL{ |
| Ctx: glctx, |
| draw: drawFunc, |
| } |
| w.tex = w.Ctx.CreateTexture() |
| w.Ctx.BindTexture(gl.TEXTURE_2D, w.tex) |
| |
| w.Ctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, maxWidth, maxHeight, gl.RGBA, gl.UNSIGNED_BYTE, nil) |
| w.Ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) |
| w.Ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) |
| w.Ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) |
| w.Ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) |
| |
| w.framebuffer = w.Ctx.CreateFramebuffer() |
| w.Ctx.BindFramebuffer(gl.FRAMEBUFFER, w.framebuffer) |
| w.Ctx.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, w.tex, 0) |
| |
| // TODO: delete the framebuffer, texture, and gl.Context. |
| // TODO: explicit or finalizer cleanup? |
| |
| w.Wrapper = w |
| |
| return w |
| } |
| |
| func (w *GL) PaintBase(ctx *node.PaintBaseContext, origin image.Point) error { |
| w.Marks.UnmarkNeedsPaintBase() |
| if w.Rect.Empty() { |
| return nil |
| } |
| w.dst = ctx.Dst |
| w.origin = origin |
| w.draw(w) |
| w.dst = nil |
| return nil |
| } |
| |
| // Publish renders the default framebuffer of Ctx onto the area of the |
| // window occupied by the widget. |
| func (w *GL) Publish() { |
| if w.dst == nil { |
| panic("glwidget: no destination, Publish called outside of Draw") |
| } |
| // TODO: draw the widget texture directly into the window framebuffer. |
| m := image.NewRGBA(image.Rect(0, 0, w.Rect.Dx(), w.Rect.Dy())) |
| w.Ctx.PixelStorei(gl.PACK_ALIGNMENT, 1) |
| w.Ctx.ReadPixels(m.Pix, 0, 0, w.Rect.Dx(), w.Rect.Dy(), gl.RGBA, gl.UNSIGNED_BYTE) |
| draw.Draw(w.dst, w.Rect.Add(w.origin), m, image.Point{}, draw.Over) |
| } |