From a93f00e8c55c9af6a5d0ba3a58ad8b76a7e15487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilo=20Pr=C3=BCtz?= Date: Tue, 30 Jun 2026 10:06:43 +0200 Subject: [PATCH 1/5] [internal/painter/gl] introduce painter#blurSnap with compound type --- internal/painter/gl/draw.go | 22 +++++++++++----------- internal/painter/gl/gl_gomobile.go | 2 +- internal/painter/gl/painter.go | 21 +++++++++++++-------- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/internal/painter/gl/draw.go b/internal/painter/gl/draw.go index 2e5c0245d8..3c97362464 100644 --- a/internal/painter/gl/draw.go +++ b/internal/painter/gl/draw.go @@ -38,18 +38,18 @@ func (p *painter) drawBlur(b *canvas.Blur, pos fyne.Position, frame fyne.Size) { return } - // Ensure blurSnapTex exists at the correct size; reallocate only when dimensions change. - if !p.blurSnapTexValid || p.blurSnapW != bw || p.blurSnapH != bh { - if p.blurSnapTexValid { - p.ctx.DeleteTexture(p.blurSnapTex) + // Ensure blurSnap.tex exists at the correct size; reallocate only when dimensions change. + if !p.blurSnap.texValid || p.blurSnap.width != bw || p.blurSnap.height != bh { + if p.blurSnap.texValid { + p.ctx.DeleteTexture(p.blurSnap.tex) } // Use ImageScaleSmooth to enable bilinear filtering. // It ensures smooth interpolation between samples even when the blur radius is massive. - p.blurSnapTex = p.newTexture(canvas.ImageScaleSmooth) + p.blurSnap.tex = p.newTexture(canvas.ImageScaleSmooth) p.ctx.TexImage2D(texture2D, 0, bw, bh, colorFormatRGBA, unsignedByte, nil) - p.blurSnapTexValid = true - p.blurSnapW = bw - p.blurSnapH = bh + p.blurSnap.texValid = true + p.blurSnap.width = bw + p.blurSnap.height = bh } // Cap the kernel samples at 101 (maxKernelRadius = 50.0) per pass to ensure high performance. @@ -90,7 +90,7 @@ func (p *painter) drawBlur(b *canvas.Blur, pos fyne.Position, frame fyne.Size) { // glCopyTexSubImage2D uses GL coordinates (y=0 at bottom), so convert the canvas-top y. fbY := p.fbHeight - int(y) - bh p.ctx.ActiveTexture(texture0) - p.ctx.BindTexture(texture2D, p.blurSnapTex) + p.ctx.BindTexture(texture2D, p.blurSnap.tex) p.ctx.CopyTexSubImage2D(texture2D, 0, 0, 0, int(x), fbY, bw, bh) p.logError() @@ -121,7 +121,7 @@ func (p *painter) drawBlur(b *canvas.Blur, pos fyne.Position, frame fyne.Size) { // Bind source texture to unit 0. p.ctx.ActiveTexture(texture0) - p.ctx.BindTexture(texture2D, p.blurSnapTex) + p.ctx.BindTexture(texture2D, p.blurSnap.tex) // Set sampler uniforms. p.SetUniform1i(p.blurProgram, "tex", 0) @@ -134,7 +134,7 @@ func (p *painter) drawBlur(b *canvas.Blur, pos fyne.Position, frame fyne.Size) { p.ctx.DrawArrays(triangleStrip, 0, 4) - // Capture the horizontally-blurred result back into blurSnapTex + // Capture the horizontally-blurred result back into blurSnap.tex p.ctx.CopyTexSubImage2D(texture2D, 0, 0, 0, int(x), fbY, bw, bh) // Vertical Blur diff --git a/internal/painter/gl/gl_gomobile.go b/internal/painter/gl/gl_gomobile.go index 70232b41ef..3e157bd995 100644 --- a/internal/painter/gl/gl_gomobile.go +++ b/internal/painter/gl/gl_gomobile.go @@ -72,7 +72,7 @@ func (p *painter) glctx() gl.Context { func (p *painter) Init() { p.ctx = &mobileContext{glContext: p.contextProvider.Context().(gl.Context)} p.maxTextureSize = p.ctx.GetInteger(maxTextureSizeParam) - p.blurSnapTexValid = false // reset on context recreation; old texture IDs are no longer valid + p.blurSnap.texValid = false // reset on context recreation; old texture IDs are no longer valid p.blurKernelTexValid = false // kernel texture must also be re-created p.glctx().Disable(gl.DepthTest) p.glctx().Enable(gl.Blend) diff --git a/internal/painter/gl/painter.go b/internal/painter/gl/painter.go index 75967a8b4d..efc197a9c3 100644 --- a/internal/painter/gl/painter.go +++ b/internal/painter/gl/painter.go @@ -58,14 +58,12 @@ type painter struct { ellipseProgram programState shaderPrograms map[string]*shaderState // lazily compiled programs for user shaders, keyed by Shader.Name texScale float32 - pixScale float32 // pre-calculate scale*texScale for each draw - blurSnapTex Texture // cached texture for GPU-side blur snapshot - blurSnapTexValid bool // whether blurSnapTex has been allocated - blurSnapW, blurSnapH int // size of blurSnapTex in pixels - blurKernelTex Texture // cached 1D kernel texture on GPU - blurKernelTexValid bool // whether blurKernelTex has been allocated - blurKernelRadius float32 // radius the current kernel texture was built for - fbHeight int // current framebuffer height in pixels + pixScale float32 // pre-calculate scale*texScale for each draw + blurSnap blurSnap // cached texture for GPU-side blur snapshot + blurKernelTex Texture // cached 1D kernel texture on GPU + blurKernelTexValid bool // whether blurKernelTex has been allocated + blurKernelRadius float32 // radius the current kernel texture was built for + fbHeight int // current framebuffer height in pixels maxTextureSize int clippedTextTextures map[*canvas.Text]clippedTextTexture } @@ -314,6 +312,13 @@ func (p *painter) logError() { logGLError(p.ctx.GetError) } +type blurSnap struct { + height int + tex Texture + texValid bool // whether tex has been allocated + width int +} + type programState struct { ref Program buff Buffer From ba51780aae6ea56c0d3fd6ce1cecbee72ec9c670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilo=20Pr=C3=BCtz?= Date: Tue, 30 Jun 2026 10:09:34 +0200 Subject: [PATCH 2/5] [internal/painter/gl] introduce painter#blurKernel with compound type --- internal/painter/gl/draw.go | 14 +++++++------- internal/painter/gl/gl_gomobile.go | 4 ++-- internal/painter/gl/painter.go | 16 ++++++++++------ 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/internal/painter/gl/draw.go b/internal/painter/gl/draw.go index 3c97362464..3a6a1fa848 100644 --- a/internal/painter/gl/draw.go +++ b/internal/painter/gl/draw.go @@ -71,19 +71,19 @@ func (p *painter) drawBlur(b *canvas.Blur, pos fyne.Position, frame fyne.Size) { } // Upload kernel as a 1D texture if radius changed. - if !p.blurKernelTexValid || p.blurKernelRadius != kernelRadius { - if !p.blurKernelTexValid { - p.blurKernelTex = p.ctx.CreateTexture() + if !p.blurKernel.texValid || p.blurKernel.radius != kernelRadius { + if !p.blurKernel.texValid { + p.blurKernel.tex = p.ctx.CreateTexture() } p.ctx.ActiveTexture(texture1) - p.ctx.BindTexture(texture2D, p.blurKernelTex) + p.ctx.BindTexture(texture2D, p.blurKernel.tex) p.ctx.TexParameteri(texture2D, textureMinFilter, textureNearest) p.ctx.TexParameteri(texture2D, textureMagFilter, textureNearest) p.ctx.TexParameteri(texture2D, textureWrapS, clampToEdge) p.ctx.TexParameteri(texture2D, textureWrapT, clampToEdge) p.ctx.TexImage2D(texture2D, 0, len(values), 1, colorFormatRGBA, unsignedByte, kernelToRGBA(values)) - p.blurKernelTexValid = true - p.blurKernelRadius = kernelRadius + p.blurKernel.texValid = true + p.blurKernel.radius = kernelRadius } // Copy the blur region from the framebuffer directly to the texture on the GPU. @@ -117,7 +117,7 @@ func (p *painter) drawBlur(b *canvas.Blur, pos fyne.Position, frame fyne.Size) { // Bind kernel texture to unit 1. p.ctx.ActiveTexture(texture1) - p.ctx.BindTexture(texture2D, p.blurKernelTex) + p.ctx.BindTexture(texture2D, p.blurKernel.tex) // Bind source texture to unit 0. p.ctx.ActiveTexture(texture0) diff --git a/internal/painter/gl/gl_gomobile.go b/internal/painter/gl/gl_gomobile.go index 3e157bd995..0c3d0238aa 100644 --- a/internal/painter/gl/gl_gomobile.go +++ b/internal/painter/gl/gl_gomobile.go @@ -72,8 +72,8 @@ func (p *painter) glctx() gl.Context { func (p *painter) Init() { p.ctx = &mobileContext{glContext: p.contextProvider.Context().(gl.Context)} p.maxTextureSize = p.ctx.GetInteger(maxTextureSizeParam) - p.blurSnap.texValid = false // reset on context recreation; old texture IDs are no longer valid - p.blurKernelTexValid = false // kernel texture must also be re-created + p.blurSnap.texValid = false // reset on context recreation; old texture IDs are no longer valid + p.blurKernel.texValid = false // kernel texture must also be re-created p.glctx().Disable(gl.DepthTest) p.glctx().Enable(gl.Blend) if compiled == nil { diff --git a/internal/painter/gl/painter.go b/internal/painter/gl/painter.go index efc197a9c3..8ef6695a9f 100644 --- a/internal/painter/gl/painter.go +++ b/internal/painter/gl/painter.go @@ -58,12 +58,10 @@ type painter struct { ellipseProgram programState shaderPrograms map[string]*shaderState // lazily compiled programs for user shaders, keyed by Shader.Name texScale float32 - pixScale float32 // pre-calculate scale*texScale for each draw - blurSnap blurSnap // cached texture for GPU-side blur snapshot - blurKernelTex Texture // cached 1D kernel texture on GPU - blurKernelTexValid bool // whether blurKernelTex has been allocated - blurKernelRadius float32 // radius the current kernel texture was built for - fbHeight int // current framebuffer height in pixels + pixScale float32 // pre-calculate scale*texScale for each draw + blurSnap blurSnap // cached texture for GPU-side blur snapshot + blurKernel blurKernel // cached 1D kernel texture on GPU + fbHeight int // current framebuffer height in pixels maxTextureSize int clippedTextTextures map[*canvas.Text]clippedTextTexture } @@ -312,6 +310,12 @@ func (p *painter) logError() { logGLError(p.ctx.GetError) } +type blurKernel struct { + radius float32 + tex Texture + texValid bool // whether tex has been allocated +} + type blurSnap struct { height int tex Texture From f4f3996008b0c23f3a530c64d309c8cf9a60de94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilo=20Pr=C3=BCtz?= Date: Tue, 30 Jun 2026 10:24:58 +0200 Subject: [PATCH 3/5] =?UTF-8?q?[internal/painter/gl]=20move=20painter?= =?UTF-8?q?=E2=80=99s=20programs=20into=20compound=20structure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/painter/gl/draw.go | 86 +++++++++++++++--------------- internal/painter/gl/gl_core.go | 20 +++---- internal/painter/gl/gl_es.go | 20 +++---- internal/painter/gl/gl_gomobile.go | 60 ++++++++++----------- internal/painter/gl/gl_wasm.go | 20 +++---- internal/painter/gl/painter.go | 46 ++++++++-------- 6 files changed, 128 insertions(+), 124 deletions(-) diff --git a/internal/painter/gl/draw.go b/internal/painter/gl/draw.go index 3a6a1fa848..55ee8c0e3d 100644 --- a/internal/painter/gl/draw.go +++ b/internal/painter/gl/draw.go @@ -100,20 +100,20 @@ func (p *painter) drawBlur(b *canvas.Blur, pos fyne.Position, frame fyne.Size) { points[4], points[9] = points[9], points[4] points[14], points[19] = points[19], points[14] - p.ctx.UseProgram(p.blurProgram.ref) - p.updateBuffer(p.blurProgram.buff, points) - p.UpdateVertexArray(p.blurProgram, "vert", 3, 5, 0) - p.UpdateVertexArray(p.blurProgram, "vertTexCoord", 2, 5, 3) + p.ctx.UseProgram(p.programs.blur.ref) + p.updateBuffer(p.programs.blur.buff, points) + p.UpdateVertexArray(p.programs.blur, "vert", 3, 5, 0) + p.UpdateVertexArray(p.programs.blur, "vertTexCoord", 2, 5, 3) p.ctx.BlendFunc(one, oneMinusSrcAlpha) p.logError() cornerRadius := fyne.Min(paint.GetMaximumRadius(b.Size()), b.CornerRadius) - p.SetUniform1f(p.blurProgram, "cornerRadius", roundToPixel(cornerRadius*p.pixScale, 1.0)) - p.SetUniform2f(p.blurProgram, "size", float32(bw), float32(bh)) + p.SetUniform1f(p.programs.blur, "cornerRadius", roundToPixel(cornerRadius*p.pixScale, 1.0)) + p.SetUniform2f(p.programs.blur, "size", float32(bw), float32(bh)) - p.SetUniform1f(p.blurProgram, "radius", kernelRadius) - p.SetUniform1f(p.blurProgram, "sampleScale", sampleScale) + p.SetUniform1f(p.programs.blur, "radius", kernelRadius) + p.SetUniform1f(p.programs.blur, "sampleScale", sampleScale) // Bind kernel texture to unit 1. p.ctx.ActiveTexture(texture1) @@ -124,13 +124,13 @@ func (p *painter) drawBlur(b *canvas.Blur, pos fyne.Position, frame fyne.Size) { p.ctx.BindTexture(texture2D, p.blurSnap.tex) // Set sampler uniforms. - p.SetUniform1i(p.blurProgram, "tex", 0) - p.SetUniform1i(p.blurProgram, "kernelTex", 1) + p.SetUniform1i(p.programs.blur, "tex", 0) + p.SetUniform1i(p.programs.blur, "kernelTex", 1) // Horizontal Blur // Draw horizontal blur over the background. Use gl: one, gl: zero to replace the screen content. p.ctx.BlendFunc(one, zero) - p.SetUniform2f(p.blurProgram, "direction", 1.0/float32(bw), 0.0) + p.SetUniform2f(p.programs.blur, "direction", 1.0/float32(bw), 0.0) p.ctx.DrawArrays(triangleStrip, 0, 4) @@ -141,7 +141,7 @@ func (p *painter) drawBlur(b *canvas.Blur, pos fyne.Position, frame fyne.Size) { // Draw vertical blur using the horizontally-blurred texture. // Use one, zero since it replaces the exact same rect we just copied from. p.ctx.BlendFunc(one, zero) - p.SetUniform2f(p.blurProgram, "direction", 0.0, 1.0/float32(bh)) + p.SetUniform2f(p.programs.blur, "direction", 0.0, 1.0/float32(bh)) p.ctx.DrawArrays(triangleStrip, 0, 4) p.logError() @@ -149,7 +149,7 @@ func (p *painter) drawBlur(b *canvas.Blur, pos fyne.Position, frame fyne.Size) { func (p *painter) drawCircle(circle *canvas.Circle, pos fyne.Position, frame fyne.Size) { radius := paint.GetMaximumRadius(circle.Size()) - program := p.roundRectangleProgram + program := p.programs.roundRectangle // Vertex: BEG bounds, points := p.vecSquareCoords(pos, circle, frame, circle.Shadow) @@ -224,20 +224,20 @@ func (p *painter) drawLine(line *canvas.Line, pos fyne.Position, frame fyne.Size return } points, halfWidth, feather := p.lineCoords(pos, line.Position1, line.Position2, line.StrokeWidth, 0.5, frame) - p.ctx.UseProgram(p.lineProgram.ref) - p.updateBuffer(p.lineProgram.buff, points) - p.UpdateVertexArray(p.lineProgram, "vert", 2, 4, 0) - p.UpdateVertexArray(p.lineProgram, "normal", 2, 4, 2) + p.ctx.UseProgram(p.programs.line.ref) + p.updateBuffer(p.programs.line.buff, points) + p.UpdateVertexArray(p.programs.line, "vert", 2, 4, 0) + p.UpdateVertexArray(p.programs.line, "normal", 2, 4, 2) p.ctx.BlendFunc(srcAlpha, oneMinusSrcAlpha) p.logError() r, g, b, a := getFragmentColor(line.StrokeColor) - p.SetUniform4f(p.lineProgram, "color", r, g, b, a) + p.SetUniform4f(p.programs.line, "color", r, g, b, a) - p.SetUniform1f(p.lineProgram, "lineWidth", halfWidth) + p.SetUniform1f(p.programs.line, "lineWidth", halfWidth) - p.SetUniform1f(p.lineProgram, "feather", feather) + p.SetUniform1f(p.programs.line, "feather", feather) p.ctx.DrawArrays(triangles, 0, 6) p.logError() @@ -250,7 +250,7 @@ func (p *painter) drawBezierCurve(bezierCurve *canvas.BezierCurve, pos fyne.Posi // Vertex: BEG bounds, points := p.vecRectCoords(pos, bezierCurve, frame, 0.0, canvas.Shadow{}) - program := p.bezierCurveProgram + program := p.programs.bezierCurve p.ctx.UseProgram(program.ref) p.updateBuffer(program.buff, points) p.UpdateVertexArray(program, "vert", 2, 4, 0) @@ -315,7 +315,7 @@ func (p *painter) drawArbitraryPolygon(polygon *canvas.ArbitraryPolygon, pos fyn // Vertex: BEG bounds, points := p.vecRectCoords(pos, polygon, frame, 0.0, canvas.Shadow{}) - program := p.arbitraryPolygonProgram + program := p.programs.arbitraryPolygon p.ctx.UseProgram(program.ref) p.updateBuffer(program.buff, points) p.UpdateVertexArray(program, "vert", 2, 4, 0) @@ -550,9 +550,9 @@ func (p *painter) drawOblong(obj fyne.CanvasObject, fill, stroke color.Color, st roundedCorners := topRightRadius != 0 || topLeftRadius != 0 || bottomRightRadius != 0 || bottomLeftRadius != 0 var program programState if roundedCorners { - program = p.roundRectangleProgram + program = p.programs.roundRectangle } else { - program = p.rectangleProgram + program = p.programs.rectangle } // Vertex: BEG @@ -644,7 +644,7 @@ func (p *painter) drawPolygon(polygon *canvas.RegularPolygon, pos fyne.Position, // Vertex: BEG bounds, points := p.vecRectCoords(pos, polygon, frame, 0.0, canvas.Shadow{}) - program := p.polygonProgram + program := p.programs.polygon p.ctx.UseProgram(program.ref) p.updateBuffer(program.buff, points) p.UpdateVertexArray(program, "vert", 2, 4, 0) @@ -702,7 +702,7 @@ func (p *painter) drawArc(arc *canvas.Arc, pos fyne.Position, frame fyne.Size) { // Vertex: BEG bounds, points := p.vecRectCoords(pos, arc, frame, 0.0, canvas.Shadow{}) - program := p.arcProgram + program := p.programs.arc p.ctx.UseProgram(program.ref) p.updateBuffer(program.buff, points) p.UpdateVertexArray(program, "vert", 2, 4, 0) @@ -762,7 +762,7 @@ func (p *painter) drawEllipse(ellipse *canvas.Ellipse, pos fyne.Position, frame size := ellipse.Size() radiusX := size.Width / 2 radiusY := size.Height / 2 - program := p.ellipseProgram + program := p.programs.ellipse // when rotated, the ellipse needs more space // add half the difference between width and height as padding @@ -914,15 +914,15 @@ func (p *painter) drawTextureRegion(texture Texture, pos fyne.Position, size, fr points, insets := p.rectCoords(size, pos, frame, canvas.ImageFillStretch, 1, 0) inner, _ := rectInnerCoords(size, pos, canvas.ImageFillStretch, 1) - p.ctx.UseProgram(p.program.ref) - p.updateBuffer(p.program.buff, points) - p.UpdateVertexArray(p.program, "vert", 3, 5, 0) - p.UpdateVertexArray(p.program, "vertTexCoord", 2, 5, 3) + p.ctx.UseProgram(p.programs.simple.ref) + p.updateBuffer(p.programs.simple.buff, points) + p.UpdateVertexArray(p.programs.simple, "vert", 3, 5, 0) + p.UpdateVertexArray(p.programs.simple, "vertTexCoord", 2, 5, 3) - p.SetUniform1f(p.program, "cornerRadius", 0) - p.SetUniform2f(p.program, "size", inner.Width*p.pixScale, inner.Height*p.pixScale) - p.SetUniform4f(p.program, "inset", insets[0], insets[1], insets[2], insets[3]) - p.SetUniform1f(p.program, "alpha", 1.0) + p.SetUniform1f(p.programs.simple, "cornerRadius", 0) + p.SetUniform2f(p.programs.simple, "size", inner.Width*p.pixScale, inner.Height*p.pixScale) + p.SetUniform4f(p.programs.simple, "inset", insets[0], insets[1], insets[2], insets[3]) + p.SetUniform1f(p.programs.simple, "alpha", 1.0) p.ctx.BlendFunc(one, oneMinusSrcAlpha) p.logError() @@ -957,18 +957,18 @@ func (p *painter) drawTextureWithDetails(o fyne.CanvasObject, creator func(canva points, insets := p.rectCoords(size, pos, frame, fill, aspect, pad) inner, _ := rectInnerCoords(size, pos, fill, aspect) - p.ctx.UseProgram(p.program.ref) - p.updateBuffer(p.program.buff, points) - p.UpdateVertexArray(p.program, "vert", 3, 5, 0) - p.UpdateVertexArray(p.program, "vertTexCoord", 2, 5, 3) + p.ctx.UseProgram(p.programs.simple.ref) + p.updateBuffer(p.programs.simple.buff, points) + p.UpdateVertexArray(p.programs.simple, "vert", 3, 5, 0) + p.UpdateVertexArray(p.programs.simple, "vertTexCoord", 2, 5, 3) // Set corner radius and texture size in pixels cornerRadius = fyne.Min(paint.GetMaximumRadius(size), cornerRadius) - p.SetUniform1f(p.program, "cornerRadius", cornerRadius*p.pixScale) - p.SetUniform2f(p.program, "size", inner.Width*p.pixScale, inner.Height*p.pixScale) - p.SetUniform4f(p.program, "inset", insets[0], insets[1], insets[2], insets[3]) // texture coordinate insets (minX, minY, maxX, maxY) + p.SetUniform1f(p.programs.simple, "cornerRadius", cornerRadius*p.pixScale) + p.SetUniform2f(p.programs.simple, "size", inner.Width*p.pixScale, inner.Height*p.pixScale) + p.SetUniform4f(p.programs.simple, "inset", insets[0], insets[1], insets[2], insets[3]) // texture coordinate insets (minX, minY, maxX, maxY) - p.SetUniform1f(p.program, "alpha", alpha) + p.SetUniform1f(p.programs.simple, "alpha", alpha) p.ctx.BlendFunc(one, oneMinusSrcAlpha) p.logError() diff --git a/internal/painter/gl/gl_core.go b/internal/painter/gl/gl_core.go index 26631846dd..45bd8a03e7 100644 --- a/internal/painter/gl/gl_core.go +++ b/internal/painter/gl/gl_core.go @@ -80,70 +80,70 @@ func (p *painter) Init() { gl.Disable(gl.DEPTH_TEST) gl.Enable(gl.BLEND) p.logError() - p.program = programState{ + p.programs.simple = programState{ ref: p.createProgram("simple"), buff: p.createBuffer(20), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.blurProgram = programState{ + p.programs.blur = programState{ ref: p.createProgram("blur"), buff: p.createBuffer(20), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.lineProgram = programState{ + p.programs.line = programState{ ref: p.createProgram("line"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.rectangleProgram = programState{ + p.programs.rectangle = programState{ ref: p.createProgram("rectangle"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.roundRectangleProgram = programState{ + p.programs.roundRectangle = programState{ ref: p.createProgram("round_rectangle"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.polygonProgram = programState{ + p.programs.polygon = programState{ ref: p.createProgram("polygon"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.arcProgram = programState{ + p.programs.arc = programState{ ref: p.createProgram("arc"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.bezierCurveProgram = programState{ + p.programs.bezierCurve = programState{ ref: p.createProgram("bezier_curve"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.arbitraryPolygonProgram = programState{ + p.programs.arbitraryPolygon = programState{ ref: p.createProgram("arbitrary_polygon"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.ellipseProgram = programState{ + p.programs.ellipse = programState{ ref: p.createProgram("ellipse"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), diff --git a/internal/painter/gl/gl_es.go b/internal/painter/gl/gl_es.go index 18e5cb49d5..0136742bc5 100644 --- a/internal/painter/gl/gl_es.go +++ b/internal/painter/gl/gl_es.go @@ -80,70 +80,70 @@ func (p *painter) Init() { gl.Disable(gl.DEPTH_TEST) gl.Enable(gl.BLEND) p.logError() - p.program = programState{ + p.programs.simple = programState{ ref: p.createProgram("simple_es"), buff: p.createBuffer(20), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.blurProgram = programState{ + p.programs.blur = programState{ ref: p.createProgram("blur_es"), buff: p.createBuffer(20), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.lineProgram = programState{ + p.programs.line = programState{ ref: p.createProgram("line_es"), buff: p.createBuffer(24), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.rectangleProgram = programState{ + p.programs.rectangle = programState{ ref: p.createProgram("rectangle_es"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.roundRectangleProgram = programState{ + p.programs.roundRectangle = programState{ ref: p.createProgram("round_rectangle_es"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.polygonProgram = programState{ + p.programs.polygon = programState{ ref: p.createProgram("polygon_es"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.arcProgram = programState{ + p.programs.arc = programState{ ref: p.createProgram("arc_es"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.bezierCurveProgram = programState{ + p.programs.bezierCurve = programState{ ref: p.createProgram("bezier_curve_es"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.arbitraryPolygonProgram = programState{ + p.programs.arbitraryPolygon = programState{ ref: p.createProgram("arbitrary_polygon_es"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.ellipseProgram = programState{ + p.programs.ellipse = programState{ ref: p.createProgram("ellipse_es"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), diff --git a/internal/painter/gl/gl_gomobile.go b/internal/painter/gl/gl_gomobile.go index 0c3d0238aa..818d166090 100644 --- a/internal/painter/gl/gl_gomobile.go +++ b/internal/painter/gl/gl_gomobile.go @@ -77,70 +77,70 @@ func (p *painter) Init() { p.glctx().Disable(gl.DepthTest) p.glctx().Enable(gl.Blend) if compiled == nil { - p.program = programState{ + p.programs.simple = programState{ ref: p.createProgram("simple_es"), buff: p.createBuffer(20), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.blurProgram = programState{ + p.programs.blur = programState{ ref: p.createProgram("blur_es"), buff: p.createBuffer(20), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.lineProgram = programState{ + p.programs.line = programState{ ref: p.createProgram("line_es"), buff: p.createBuffer(24), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.rectangleProgram = programState{ + p.programs.rectangle = programState{ ref: p.createProgram("rectangle_es"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.roundRectangleProgram = programState{ + p.programs.roundRectangle = programState{ ref: p.createProgram("round_rectangle_es"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.polygonProgram = programState{ + p.programs.polygon = programState{ ref: p.createProgram("polygon_es"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.arcProgram = programState{ + p.programs.arc = programState{ ref: p.createProgram("arc_es"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.bezierCurveProgram = programState{ + p.programs.bezierCurve = programState{ ref: p.createProgram("bezier_curve_es"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.arbitraryPolygonProgram = programState{ + p.programs.arbitraryPolygon = programState{ ref: p.createProgram("arbitrary_polygon_es"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.ellipseProgram = programState{ + p.programs.ellipse = programState{ ref: p.createProgram("ellipse_es"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), @@ -148,29 +148,29 @@ func (p *painter) Init() { } compiled = []programState{ - p.program, - p.blurProgram, - p.lineProgram, - p.rectangleProgram, - p.roundRectangleProgram, - p.polygonProgram, - p.arcProgram, - p.bezierCurveProgram, - p.arbitraryPolygonProgram, - p.ellipseProgram, + p.programs.simple, + p.programs.blur, + p.programs.line, + p.programs.rectangle, + p.programs.roundRectangle, + p.programs.polygon, + p.programs.arc, + p.programs.bezierCurve, + p.programs.arbitraryPolygon, + p.programs.ellipse, } } - p.program = compiled[0] - p.blurProgram = compiled[1] - p.lineProgram = compiled[2] - p.rectangleProgram = compiled[3] - p.roundRectangleProgram = compiled[4] - p.polygonProgram = compiled[5] - p.arcProgram = compiled[6] - p.bezierCurveProgram = compiled[7] - p.arbitraryPolygonProgram = compiled[8] - p.ellipseProgram = compiled[9] + p.programs.simple = compiled[0] + p.programs.blur = compiled[1] + p.programs.line = compiled[2] + p.programs.rectangle = compiled[3] + p.programs.roundRectangle = compiled[4] + p.programs.polygon = compiled[5] + p.programs.arc = compiled[6] + p.programs.bezierCurve = compiled[7] + p.programs.arbitraryPolygon = compiled[8] + p.programs.ellipse = compiled[9] } type mobileContext struct { diff --git a/internal/painter/gl/gl_wasm.go b/internal/painter/gl/gl_wasm.go index 7326f7205b..7870c42ae3 100644 --- a/internal/painter/gl/gl_wasm.go +++ b/internal/painter/gl/gl_wasm.go @@ -70,70 +70,70 @@ func (p *painter) Init() { gl.Disable(gl.DEPTH_TEST) gl.Enable(gl.BLEND) p.logError() - p.program = programState{ + p.programs.simple = programState{ ref: p.createProgram("simple_es"), buff: p.createBuffer(20), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.blurProgram = programState{ + p.programs.blur = programState{ ref: p.createProgram("blur_es"), buff: p.createBuffer(20), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.lineProgram = programState{ + p.programs.line = programState{ ref: p.createProgram("line_es"), buff: p.createBuffer(24), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.rectangleProgram = programState{ + p.programs.rectangle = programState{ ref: p.createProgram("rectangle_es"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.roundRectangleProgram = programState{ + p.programs.roundRectangle = programState{ ref: p.createProgram("round_rectangle_es"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.polygonProgram = programState{ + p.programs.polygon = programState{ ref: p.createProgram("polygon_es"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.arcProgram = programState{ + p.programs.arc = programState{ ref: p.createProgram("arc_es"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.bezierCurveProgram = programState{ + p.programs.bezierCurve = programState{ ref: p.createProgram("bezier_curve_es"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.arbitraryPolygonProgram = programState{ + p.programs.arbitraryPolygon = programState{ ref: p.createProgram("arbitrary_polygon_es"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), attributes: make(map[string]Attribute), } - p.ellipseProgram = programState{ + p.programs.ellipse = programState{ ref: p.createProgram("ellipse_es"), buff: p.createBuffer(16), uniforms: make(map[string]*uniformState), diff --git a/internal/painter/gl/painter.go b/internal/painter/gl/painter.go index 8ef6695a9f..dbdf0edb69 100644 --- a/internal/painter/gl/painter.go +++ b/internal/painter/gl/painter.go @@ -43,27 +43,18 @@ func NewPainter(c fyne.Canvas, ctx driver.WithContext) Painter { } type painter struct { - canvas fyne.Canvas - ctx context - contextProvider driver.WithContext - program programState - blurProgram programState - lineProgram programState - rectangleProgram programState - roundRectangleProgram programState - polygonProgram programState - arcProgram programState - bezierCurveProgram programState - arbitraryPolygonProgram programState - ellipseProgram programState - shaderPrograms map[string]*shaderState // lazily compiled programs for user shaders, keyed by Shader.Name - texScale float32 - pixScale float32 // pre-calculate scale*texScale for each draw - blurSnap blurSnap // cached texture for GPU-side blur snapshot - blurKernel blurKernel // cached 1D kernel texture on GPU - fbHeight int // current framebuffer height in pixels - maxTextureSize int - clippedTextTextures map[*canvas.Text]clippedTextTexture + canvas fyne.Canvas + ctx context + contextProvider driver.WithContext + programs programs + shaderPrograms map[string]*shaderState // lazily compiled programs for user shaders, keyed by Shader.Name + texScale float32 + pixScale float32 // pre-calculate scale*texScale for each draw + blurSnap blurSnap // cached texture for GPU-side blur snapshot + blurKernel blurKernel // cached 1D kernel texture on GPU + fbHeight int // current framebuffer height in pixels + maxTextureSize int + clippedTextTextures map[*canvas.Text]clippedTextTexture } // Declare conformity to Painter interface @@ -330,6 +321,19 @@ type programState struct { attributes map[string]Attribute } +type programs struct { + arbitraryPolygon programState + arc programState + bezierCurve programState + blur programState + ellipse programState + line programState + polygon programState + rectangle programState + roundRectangle programState + simple programState +} + // shaderState caches a user shader's compiled program and uploaded textures. // valid is false when the source failed to compile, so we can record the // failure without comparing the (not always comparable) program reference. From 6cbd3c3a4ed77eafb0bdf591f654bb7944cc2402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilo=20Pr=C3=BCtz?= Date: Tue, 30 Jun 2026 10:20:03 +0200 Subject: [PATCH 4/5] [internal/painter/gl] sort painter properties --- internal/painter/gl/painter.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/painter/gl/painter.go b/internal/painter/gl/painter.go index dbdf0edb69..8cef8710d0 100644 --- a/internal/painter/gl/painter.go +++ b/internal/painter/gl/painter.go @@ -43,18 +43,18 @@ func NewPainter(c fyne.Canvas, ctx driver.WithContext) Painter { } type painter struct { + blurKernel blurKernel // cached 1D kernel texture on GPU + blurSnap blurSnap // cached texture for GPU-side blur snapshot canvas fyne.Canvas - ctx context + clippedTextTextures map[*canvas.Text]clippedTextTexture contextProvider driver.WithContext + ctx context + fbHeight int // current framebuffer height in pixels + maxTextureSize int + pixScale float32 // pre-calculate scale*texScale for each draw programs programs shaderPrograms map[string]*shaderState // lazily compiled programs for user shaders, keyed by Shader.Name texScale float32 - pixScale float32 // pre-calculate scale*texScale for each draw - blurSnap blurSnap // cached texture for GPU-side blur snapshot - blurKernel blurKernel // cached 1D kernel texture on GPU - fbHeight int // current framebuffer height in pixels - maxTextureSize int - clippedTextTextures map[*canvas.Text]clippedTextTexture } // Declare conformity to Painter interface From 70cff4ea8245a613cf90983e5f81dd8bd03681e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilo=20Pr=C3=BCtz?= Date: Tue, 30 Jun 2026 10:48:35 +0200 Subject: [PATCH 5/5] [internal/painter/gl] change painter#rectCoords to return an array instead of a slice This makes the size of the returned data more explicit. The `[:]` to pass the array as slice to other helpers has no impact on the performance. In both cases an array and a slice based upon it are created. --- internal/painter/gl/draw.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/painter/gl/draw.go b/internal/painter/gl/draw.go index 55ee8c0e3d..5703213f27 100644 --- a/internal/painter/gl/draw.go +++ b/internal/painter/gl/draw.go @@ -101,7 +101,7 @@ func (p *painter) drawBlur(b *canvas.Blur, pos fyne.Position, frame fyne.Size) { points[14], points[19] = points[19], points[14] p.ctx.UseProgram(p.programs.blur.ref) - p.updateBuffer(p.programs.blur.buff, points) + p.updateBuffer(p.programs.blur.buff, points[:]) p.UpdateVertexArray(p.programs.blur, "vert", 3, 5, 0) p.UpdateVertexArray(p.programs.blur, "vertTexCoord", 2, 5, 3) @@ -915,7 +915,7 @@ func (p *painter) drawTextureRegion(texture Texture, pos fyne.Position, size, fr inner, _ := rectInnerCoords(size, pos, canvas.ImageFillStretch, 1) p.ctx.UseProgram(p.programs.simple.ref) - p.updateBuffer(p.programs.simple.buff, points) + p.updateBuffer(p.programs.simple.buff, points[:]) p.UpdateVertexArray(p.programs.simple, "vert", 3, 5, 0) p.UpdateVertexArray(p.programs.simple, "vertTexCoord", 2, 5, 3) @@ -958,7 +958,7 @@ func (p *painter) drawTextureWithDetails(o fyne.CanvasObject, creator func(canva inner, _ := rectInnerCoords(size, pos, fill, aspect) p.ctx.UseProgram(p.programs.simple.ref) - p.updateBuffer(p.programs.simple.buff, points) + p.updateBuffer(p.programs.simple.buff, points[:]) p.UpdateVertexArray(p.programs.simple, "vert", 3, 5, 0) p.UpdateVertexArray(p.programs.simple, "vertTexCoord", 2, 5, 3) @@ -1040,7 +1040,7 @@ func (p *painter) lineCoords(pos, pos1, pos2 fyne.Position, lineWidth, feather f // rectCoords calculates the openGL coordinate space of a rectangle func (p *painter) rectCoords(size fyne.Size, pos fyne.Position, frame fyne.Size, fill canvas.ImageFill, aspect float32, pad float32, -) ([]float32, [4]float32) { +) ([20]float32, [4]float32) { size, pos = rectInnerCoords(size, pos, fill, aspect) size, pos = roundToPixelCoords(size, pos, p.pixScale) @@ -1073,7 +1073,7 @@ func (p *painter) rectCoords(size fyne.Size, pos fyne.Position, frame fyne.Size, insets := [4]float32{xInset, yInset, 1.0 - xInset, 1.0 - yInset} - return []float32{ + return [20]float32{ // coord x, y, z texture x, y x1, y2, 0, insets[0], insets[3], // top left x1, y1, 0, insets[0], insets[1], // bottom left