188 lines
5.8 KiB
Go
188 lines
5.8 KiB
Go
|
// Package gl provides a full Fyne render implementation using system OpenGL libraries.
|
||
|
package gl
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"image"
|
||
|
|
||
|
"fyne.io/fyne/v2"
|
||
|
"fyne.io/fyne/v2/internal/driver"
|
||
|
"fyne.io/fyne/v2/theme"
|
||
|
)
|
||
|
|
||
|
func shaderSourceNamed(name string) ([]byte, []byte) {
|
||
|
switch name {
|
||
|
case "line":
|
||
|
return shaderLineVert.StaticContent, shaderLineFrag.StaticContent
|
||
|
case "line_es":
|
||
|
return shaderLineesVert.StaticContent, shaderLineesFrag.StaticContent
|
||
|
case "simple":
|
||
|
return shaderSimpleVert.StaticContent, shaderSimpleFrag.StaticContent
|
||
|
case "simple_es":
|
||
|
return shaderSimpleesVert.StaticContent, shaderSimpleesFrag.StaticContent
|
||
|
case "rectangle":
|
||
|
return shaderRectangleVert.StaticContent, shaderRectangleFrag.StaticContent
|
||
|
case "round_rectangle":
|
||
|
return shaderRectangleVert.StaticContent, shaderRoundrectangleFrag.StaticContent
|
||
|
case "rectangle_es":
|
||
|
return shaderRectangleesVert.StaticContent, shaderRectangleesFrag.StaticContent
|
||
|
case "round_rectangle_es":
|
||
|
return shaderRectangleesVert.StaticContent, shaderRoundrectangleesFrag.StaticContent
|
||
|
}
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
// Painter defines the functionality of our OpenGL based renderer
|
||
|
type Painter interface {
|
||
|
// Init tell a new painter to initialise, usually called after a context is available
|
||
|
Init()
|
||
|
// Capture requests that the specified canvas be drawn to an in-memory image
|
||
|
Capture(fyne.Canvas) image.Image
|
||
|
// Clear tells our painter to prepare a fresh paint
|
||
|
Clear()
|
||
|
// Free is used to indicate that a certain canvas object is no longer needed
|
||
|
Free(fyne.CanvasObject)
|
||
|
// Paint a single fyne.CanvasObject but not its children.
|
||
|
Paint(fyne.CanvasObject, fyne.Position, fyne.Size)
|
||
|
// SetFrameBufferScale tells us when we have more than 1 framebuffer pixel for each output pixel
|
||
|
SetFrameBufferScale(float32)
|
||
|
// SetOutputSize is used to change the resolution of our output viewport
|
||
|
SetOutputSize(int, int)
|
||
|
// StartClipping tells us that the following paint actions should be clipped to the specified area.
|
||
|
StartClipping(fyne.Position, fyne.Size)
|
||
|
// StopClipping stops clipping paint actions.
|
||
|
StopClipping()
|
||
|
}
|
||
|
|
||
|
// NewPainter creates a new GL based renderer for the provided canvas.
|
||
|
// If it is a master painter it will also initialise OpenGL
|
||
|
func NewPainter(c fyne.Canvas, ctx driver.WithContext) Painter {
|
||
|
p := &painter{canvas: c, contextProvider: ctx}
|
||
|
p.SetFrameBufferScale(1.0)
|
||
|
return p
|
||
|
}
|
||
|
|
||
|
type painter struct {
|
||
|
canvas fyne.Canvas
|
||
|
ctx context
|
||
|
contextProvider driver.WithContext
|
||
|
program Program
|
||
|
lineProgram Program
|
||
|
rectangleProgram Program
|
||
|
roundRectangleProgram Program
|
||
|
texScale float32
|
||
|
pixScale float32 // pre-calculate scale*texScale for each draw
|
||
|
}
|
||
|
|
||
|
// Declare conformity to Painter interface
|
||
|
var _ Painter = (*painter)(nil)
|
||
|
|
||
|
func (p *painter) Clear() {
|
||
|
r, g, b, a := theme.BackgroundColor().RGBA()
|
||
|
p.ctx.ClearColor(float32(r)/max16bit, float32(g)/max16bit, float32(b)/max16bit, float32(a)/max16bit)
|
||
|
p.ctx.Clear(bitColorBuffer | bitDepthBuffer)
|
||
|
p.logError()
|
||
|
}
|
||
|
|
||
|
func (p *painter) Free(obj fyne.CanvasObject) {
|
||
|
p.freeTexture(obj)
|
||
|
}
|
||
|
|
||
|
func (p *painter) Paint(obj fyne.CanvasObject, pos fyne.Position, frame fyne.Size) {
|
||
|
if obj.Visible() {
|
||
|
p.drawObject(obj, pos, frame)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (p *painter) SetFrameBufferScale(scale float32) {
|
||
|
p.texScale = scale
|
||
|
p.pixScale = p.canvas.Scale() * p.texScale
|
||
|
}
|
||
|
|
||
|
func (p *painter) SetOutputSize(width, height int) {
|
||
|
p.ctx.Viewport(0, 0, width, height)
|
||
|
p.logError()
|
||
|
}
|
||
|
|
||
|
func (p *painter) StartClipping(pos fyne.Position, size fyne.Size) {
|
||
|
x := p.textureScale(pos.X)
|
||
|
y := p.textureScale(p.canvas.Size().Height - pos.Y - size.Height)
|
||
|
w := p.textureScale(size.Width)
|
||
|
h := p.textureScale(size.Height)
|
||
|
p.ctx.Scissor(int32(x), int32(y), int32(w), int32(h))
|
||
|
p.ctx.Enable(scissorTest)
|
||
|
p.logError()
|
||
|
}
|
||
|
|
||
|
func (p *painter) StopClipping() {
|
||
|
p.ctx.Disable(scissorTest)
|
||
|
p.logError()
|
||
|
}
|
||
|
|
||
|
func (p *painter) compileShader(source string, shaderType uint32) (Shader, error) {
|
||
|
shader := p.ctx.CreateShader(shaderType)
|
||
|
|
||
|
p.ctx.ShaderSource(shader, source)
|
||
|
p.logError()
|
||
|
p.ctx.CompileShader(shader)
|
||
|
p.logError()
|
||
|
|
||
|
info := p.ctx.GetShaderInfoLog(shader)
|
||
|
if p.ctx.GetShaderi(shader, compileStatus) == glFalse {
|
||
|
return noShader, fmt.Errorf("failed to compile OpenGL shader:\n%s\n>>> SHADER SOURCE\n%s\n<<< SHADER SOURCE", info, source)
|
||
|
}
|
||
|
|
||
|
// The info is probably a null terminated string.
|
||
|
// An empty info has been seen as "\x00" or "\x00\x00".
|
||
|
if len(info) > 0 && info != "\x00" && info != "\x00\x00" {
|
||
|
fmt.Printf("OpenGL shader compilation output:\n%s\n>>> SHADER SOURCE\n%s\n<<< SHADER SOURCE\n", info, source)
|
||
|
}
|
||
|
|
||
|
return shader, nil
|
||
|
}
|
||
|
|
||
|
func (p *painter) createProgram(shaderFilename string) Program {
|
||
|
// Why a switch over a filename?
|
||
|
// Because this allows for a minimal change, once we reach Go 1.16 and use go:embed instead of
|
||
|
// fyne bundle.
|
||
|
vertexSrc, fragmentSrc := shaderSourceNamed(shaderFilename)
|
||
|
if vertexSrc == nil {
|
||
|
panic("shader not found: " + shaderFilename)
|
||
|
}
|
||
|
|
||
|
vertShader, err := p.compileShader(string(vertexSrc), vertexShader)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
fragShader, err := p.compileShader(string(fragmentSrc), fragmentShader)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
|
||
|
prog := p.ctx.CreateProgram()
|
||
|
p.ctx.AttachShader(prog, vertShader)
|
||
|
p.ctx.AttachShader(prog, fragShader)
|
||
|
p.ctx.LinkProgram(prog)
|
||
|
|
||
|
info := p.ctx.GetProgramInfoLog(prog)
|
||
|
if p.ctx.GetProgrami(prog, linkStatus) == glFalse {
|
||
|
panic(fmt.Errorf("failed to link OpenGL program:\n%s", info))
|
||
|
}
|
||
|
|
||
|
// The info is probably a null terminated string.
|
||
|
// An empty info has been seen as "\x00" or "\x00\x00".
|
||
|
if len(info) > 0 && info != "\x00" && info != "\x00\x00" {
|
||
|
fmt.Printf("OpenGL program linking output:\n%s\n", info)
|
||
|
}
|
||
|
|
||
|
if glErr := p.ctx.GetError(); glErr != 0 {
|
||
|
panic(fmt.Sprintf("failed to link OpenGL program; error code: %x", glErr))
|
||
|
}
|
||
|
|
||
|
return prog
|
||
|
}
|
||
|
|
||
|
func (p *painter) logError() {
|
||
|
logGLError(p.ctx.GetError)
|
||
|
}
|