| // 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. |
| |
| // +build example |
| // |
| // This build tag means that "go install golang.org/x/exp/shiny/..." doesn't |
| // install this example program. Use "go run main.go" to run it or "go install |
| // -tags=example" to install it. |
| |
| // Basicgl demonstrates the use of Shiny's glwidget. |
| package main |
| |
| import ( |
| "encoding/binary" |
| "fmt" |
| "image" |
| "image/color" |
| "log" |
| "math" |
| |
| "golang.org/x/exp/shiny/driver/gldriver" |
| "golang.org/x/exp/shiny/screen" |
| "golang.org/x/exp/shiny/unit" |
| "golang.org/x/exp/shiny/widget" |
| "golang.org/x/exp/shiny/widget/flex" |
| "golang.org/x/exp/shiny/widget/glwidget" |
| "golang.org/x/exp/shiny/widget/theme" |
| "golang.org/x/image/colornames" |
| "golang.org/x/mobile/gl" |
| ) |
| |
| func colorPatch(c color.Color, w, h unit.Value) *widget.Sizer { |
| return widget.NewSizer(w, h, widget.NewUniform(theme.StaticColor(c), nil)) |
| } |
| |
| func main() { |
| gldriver.Main(func(s screen.Screen) { |
| t1, t2 := newTriangleGL(), newTriangleGL() |
| defer t1.cleanup() |
| defer t2.cleanup() |
| |
| body := widget.NewSheet(flex.NewFlex( |
| colorPatch(colornames.Green, unit.Pixels(50), unit.Pixels(50)), |
| widget.WithLayoutData(t1.w, flex.LayoutData{Grow: 1, Align: flex.AlignItemStretch}), |
| colorPatch(colornames.Blue, unit.Pixels(50), unit.Pixels(50)), |
| widget.WithLayoutData(t2.w, flex.LayoutData{MinSize: image.Point{80, 80}}), |
| colorPatch(colornames.Green, unit.Pixels(50), unit.Pixels(50)), |
| )) |
| |
| if err := widget.RunWindow(s, body, &widget.RunWindowOptions{ |
| NewWindowOptions: screen.NewWindowOptions{ |
| Title: "BasicGL Shiny Example", |
| }, |
| }); err != nil { |
| log.Fatal(err) |
| } |
| }) |
| } |
| |
| func newTriangleGL() *triangleGL { |
| t := new(triangleGL) |
| t.w = glwidget.NewGL(t.draw) |
| t.init() |
| return t |
| } |
| |
| type triangleGL struct { |
| w *glwidget.GL |
| |
| program gl.Program |
| position gl.Attrib |
| offset gl.Uniform |
| color gl.Uniform |
| buf gl.Buffer |
| |
| green float32 |
| } |
| |
| func (t *triangleGL) init() { |
| glctx := t.w.Ctx |
| var err error |
| t.program, err = createProgram(glctx, vertexShader, fragmentShader) |
| if err != nil { |
| log.Fatalf("error creating GL program: %v", err) |
| } |
| |
| t.buf = glctx.CreateBuffer() |
| glctx.BindBuffer(gl.ARRAY_BUFFER, t.buf) |
| glctx.BufferData(gl.ARRAY_BUFFER, triangleData, gl.STATIC_DRAW) |
| |
| t.position = glctx.GetAttribLocation(t.program, "position") |
| t.color = glctx.GetUniformLocation(t.program, "color") |
| t.offset = glctx.GetUniformLocation(t.program, "offset") |
| |
| glctx.UseProgram(t.program) |
| glctx.ClearColor(1, 0, 0, 1) |
| } |
| |
| func (t *triangleGL) cleanup() { |
| glctx := t.w.Ctx |
| glctx.DeleteProgram(t.program) |
| glctx.DeleteBuffer(t.buf) |
| } |
| |
| func (t *triangleGL) draw(w *glwidget.GL) { |
| glctx := t.w.Ctx |
| |
| glctx.Viewport(0, 0, w.Rect.Dx(), w.Rect.Dy()) |
| glctx.Clear(gl.COLOR_BUFFER_BIT) |
| |
| t.green += 0.01 |
| if t.green > 1 { |
| t.green = 0 |
| } |
| glctx.Uniform4f(t.color, 0, t.green, 0, 1) |
| glctx.Uniform2f(t.offset, 0.2, 0.9) |
| |
| glctx.BindBuffer(gl.ARRAY_BUFFER, t.buf) |
| glctx.EnableVertexAttribArray(t.position) |
| glctx.VertexAttribPointer(t.position, coordsPerVertex, gl.FLOAT, false, 0, 0) |
| glctx.DrawArrays(gl.TRIANGLES, 0, vertexCount) |
| glctx.DisableVertexAttribArray(t.position) |
| w.Publish() |
| } |
| |
| // asBytes returns the byte representation of float32 values in the given byte |
| // order. byteOrder must be either binary.BigEndian or binary.LittleEndian. |
| func asBytes(byteOrder binary.ByteOrder, values ...float32) []byte { |
| le := false |
| switch byteOrder { |
| case binary.BigEndian: |
| case binary.LittleEndian: |
| le = true |
| default: |
| panic(fmt.Sprintf("invalid byte order %v", byteOrder)) |
| } |
| |
| b := make([]byte, 4*len(values)) |
| for i, v := range values { |
| u := math.Float32bits(v) |
| if le { |
| b[4*i+0] = byte(u >> 0) |
| b[4*i+1] = byte(u >> 8) |
| b[4*i+2] = byte(u >> 16) |
| b[4*i+3] = byte(u >> 24) |
| } else { |
| b[4*i+0] = byte(u >> 24) |
| b[4*i+1] = byte(u >> 16) |
| b[4*i+2] = byte(u >> 8) |
| b[4*i+3] = byte(u >> 0) |
| } |
| } |
| return b |
| } |
| |
| // createProgram creates, compiles, and links a gl.Program. |
| func createProgram(glctx gl.Context, vertexSrc, fragmentSrc string) (gl.Program, error) { |
| program := glctx.CreateProgram() |
| if program.Value == 0 { |
| return gl.Program{}, fmt.Errorf("basicgl: no programs available") |
| } |
| |
| vertexShader, err := loadShader(glctx, gl.VERTEX_SHADER, vertexSrc) |
| if err != nil { |
| return gl.Program{}, err |
| } |
| fragmentShader, err := loadShader(glctx, gl.FRAGMENT_SHADER, fragmentSrc) |
| if err != nil { |
| glctx.DeleteShader(vertexShader) |
| return gl.Program{}, err |
| } |
| |
| glctx.AttachShader(program, vertexShader) |
| glctx.AttachShader(program, fragmentShader) |
| glctx.LinkProgram(program) |
| |
| // Flag shaders for deletion when program is unlinked. |
| glctx.DeleteShader(vertexShader) |
| glctx.DeleteShader(fragmentShader) |
| |
| if glctx.GetProgrami(program, gl.LINK_STATUS) == 0 { |
| defer glctx.DeleteProgram(program) |
| return gl.Program{}, fmt.Errorf("basicgl: %s", glctx.GetProgramInfoLog(program)) |
| } |
| return program, nil |
| } |
| |
| func loadShader(glctx gl.Context, shaderType gl.Enum, src string) (gl.Shader, error) { |
| shader := glctx.CreateShader(shaderType) |
| if shader.Value == 0 { |
| return gl.Shader{}, fmt.Errorf("basicgl: could not create shader (type %v)", shaderType) |
| } |
| glctx.ShaderSource(shader, src) |
| glctx.CompileShader(shader) |
| if glctx.GetShaderi(shader, gl.COMPILE_STATUS) == 0 { |
| defer glctx.DeleteShader(shader) |
| return gl.Shader{}, fmt.Errorf("basicgl: shader compile: %s", glctx.GetShaderInfoLog(shader)) |
| } |
| return shader, nil |
| } |
| |
| var triangleData = asBytes(binary.LittleEndian, |
| 0.0, 0.4, 0.0, // top left |
| 0.0, 0.0, 0.0, // bottom left |
| 0.4, 0.0, 0.0, // bottom right |
| ) |
| |
| const ( |
| coordsPerVertex = 3 |
| vertexCount = 3 |
| ) |
| |
| const vertexShader = `#version 100 |
| uniform vec2 offset; |
| |
| attribute vec4 position; |
| void main() { |
| // offset comes in with x/y values between 0 and 1. |
| // position bounds are -1 to 1. |
| vec4 offset4 = vec4(2.0*offset.x-1.0, 1.0-2.0*offset.y, 0, 0); |
| gl_Position = position + offset4; |
| }` |
| |
| const fragmentShader = `#version 100 |
| precision mediump float; |
| uniform vec4 color; |
| void main() { |
| gl_FragColor = color; |
| }` |