painter.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. // Package gl provides a full Fyne render implementation using system OpenGL libraries.
  2. package gl
  3. import (
  4. "fmt"
  5. "image"
  6. "fyne.io/fyne/v2"
  7. "fyne.io/fyne/v2/internal/driver"
  8. "fyne.io/fyne/v2/theme"
  9. )
  10. func shaderSourceNamed(name string) ([]byte, []byte) {
  11. switch name {
  12. case "line":
  13. return shaderLineVert.StaticContent, shaderLineFrag.StaticContent
  14. case "line_es":
  15. return shaderLineesVert.StaticContent, shaderLineesFrag.StaticContent
  16. case "simple":
  17. return shaderSimpleVert.StaticContent, shaderSimpleFrag.StaticContent
  18. case "simple_es":
  19. return shaderSimpleesVert.StaticContent, shaderSimpleesFrag.StaticContent
  20. case "single_channel":
  21. return shaderSimpleVert.StaticContent, shaderSinglechannelFrag.StaticContent
  22. case "single_channel_es":
  23. return shaderSimpleesVert.StaticContent, shaderSinglechannelesFrag.StaticContent
  24. }
  25. return nil, nil
  26. }
  27. // Painter defines the functionality of our OpenGL based renderer
  28. type Painter interface {
  29. // Init tell a new painter to initialise, usually called after a context is available
  30. Init()
  31. // Capture requests that the specified canvas be drawn to an in-memory image
  32. Capture(fyne.Canvas) image.Image
  33. // Clear tells our painter to prepare a fresh paint
  34. Clear()
  35. // Free is used to indicate that a certain canvas object is no longer needed
  36. Free(fyne.CanvasObject)
  37. // Paint a single fyne.CanvasObject but not its children.
  38. Paint(fyne.CanvasObject, fyne.Position, fyne.Size)
  39. // SetFrameBufferScale tells us when we have more than 1 framebuffer pixel for each output pixel
  40. SetFrameBufferScale(float32)
  41. // SetOutputSize is used to change the resolution of our output viewport
  42. SetOutputSize(int, int)
  43. // StartClipping tells us that the following paint actions should be clipped to the specified area.
  44. StartClipping(fyne.Position, fyne.Size)
  45. // StopClipping stops clipping paint actions.
  46. StopClipping()
  47. }
  48. // NewPainter creates a new GL based renderer for the provided canvas.
  49. // If it is a master painter it will also initialise OpenGL
  50. func NewPainter(c fyne.Canvas, ctx driver.WithContext) Painter {
  51. p := &painter{canvas: c, contextProvider: ctx}
  52. p.SetFrameBufferScale(1.0)
  53. return p
  54. }
  55. type painter struct {
  56. canvas fyne.Canvas
  57. ctx context
  58. contextProvider driver.WithContext
  59. program Program
  60. singleChannelProgram Program
  61. lineProgram Program
  62. texScale float32
  63. pixScale float32 // pre-calculate scale*texScale for each draw
  64. }
  65. // Declare conformity to Painter interface
  66. var _ Painter = (*painter)(nil)
  67. func (p *painter) Clear() {
  68. r, g, b, a := theme.BackgroundColor().RGBA()
  69. p.ctx.ClearColor(float32(r)/max16bit, float32(g)/max16bit, float32(b)/max16bit, float32(a)/max16bit)
  70. p.ctx.Clear(bitColorBuffer | bitDepthBuffer)
  71. p.logError()
  72. }
  73. func (p *painter) Free(obj fyne.CanvasObject) {
  74. p.freeTexture(obj)
  75. }
  76. func (p *painter) Paint(obj fyne.CanvasObject, pos fyne.Position, frame fyne.Size) {
  77. if obj.Visible() {
  78. p.drawObject(obj, pos, frame)
  79. }
  80. }
  81. func (p *painter) SetFrameBufferScale(scale float32) {
  82. p.texScale = scale
  83. p.pixScale = p.canvas.Scale() * p.texScale
  84. }
  85. func (p *painter) SetOutputSize(width, height int) {
  86. p.ctx.Viewport(0, 0, width, height)
  87. p.logError()
  88. }
  89. func (p *painter) StartClipping(pos fyne.Position, size fyne.Size) {
  90. x := p.textureScale(pos.X)
  91. y := p.textureScale(p.canvas.Size().Height - pos.Y - size.Height)
  92. w := p.textureScale(size.Width)
  93. h := p.textureScale(size.Height)
  94. p.ctx.Scissor(int32(x), int32(y), int32(w), int32(h))
  95. p.ctx.Enable(scissorTest)
  96. p.logError()
  97. }
  98. func (p *painter) StopClipping() {
  99. p.ctx.Disable(scissorTest)
  100. p.logError()
  101. }
  102. func (p *painter) compileShader(source string, shaderType uint32) (Shader, error) {
  103. shader := p.ctx.CreateShader(shaderType)
  104. p.ctx.ShaderSource(shader, source)
  105. p.logError()
  106. p.ctx.CompileShader(shader)
  107. p.logError()
  108. info := p.ctx.GetShaderInfoLog(shader)
  109. if p.ctx.GetShaderi(shader, compileStatus) == glFalse {
  110. return noShader, fmt.Errorf("failed to compile OpenGL shader:\n%s\n>>> SHADER SOURCE\n%s\n<<< SHADER SOURCE", info, source)
  111. }
  112. // The info is probably a null terminated string.
  113. // An empty info has been seen as "\x00" or "\x00\x00".
  114. if len(info) > 0 && info != "\x00" && info != "\x00\x00" {
  115. fmt.Printf("OpenGL shader compilation output:\n%s\n>>> SHADER SOURCE\n%s\n<<< SHADER SOURCE\n", info, source)
  116. }
  117. return shader, nil
  118. }
  119. func (p *painter) createProgram(shaderFilename string) Program {
  120. // Why a switch over a filename?
  121. // Because this allows for a minimal change, once we reach Go 1.16 and use go:embed instead of
  122. // fyne bundle.
  123. vertexSrc, fragmentSrc := shaderSourceNamed(shaderFilename)
  124. if vertexSrc == nil {
  125. panic("shader not found: " + shaderFilename)
  126. }
  127. vertShader, err := p.compileShader(string(vertexSrc), vertexShader)
  128. if err != nil {
  129. panic(err)
  130. }
  131. fragShader, err := p.compileShader(string(fragmentSrc), fragmentShader)
  132. if err != nil {
  133. panic(err)
  134. }
  135. prog := p.ctx.CreateProgram()
  136. p.ctx.AttachShader(prog, vertShader)
  137. p.ctx.AttachShader(prog, fragShader)
  138. p.ctx.LinkProgram(prog)
  139. info := p.ctx.GetProgramInfoLog(prog)
  140. if p.ctx.GetProgrami(prog, linkStatus) == glFalse {
  141. panic(fmt.Errorf("failed to link OpenGL program:\n%s", info))
  142. }
  143. // The info is probably a null terminated string.
  144. // An empty info has been seen as "\x00" or "\x00\x00".
  145. if len(info) > 0 && info != "\x00" && info != "\x00\x00" {
  146. fmt.Printf("OpenGL program linking output:\n%s\n", info)
  147. }
  148. if glErr := p.ctx.GetError(); glErr != 0 {
  149. panic(fmt.Sprintf("failed to link OpenGL program; error code: %x", glErr))
  150. }
  151. return prog
  152. }
  153. func (p *painter) logError() {
  154. logGLError(p.ctx.GetError())
  155. }