// 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; }`