adam-gui/vendor/fyne.io/fyne/v2/internal/painter/gl/painter.go
2024-04-29 19:13:50 +02:00

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)
}