From 8dc91b34f53cbca07bce3120dca4e56c0b5155f8 Mon Sep 17 00:00:00 2001 From: Thomas Friedel Date: Fri, 22 Feb 2019 12:34:08 +0100 Subject: [PATCH] moved offscreen canvas to backend --- backend/backendbase/base.go | 2 + backend/gogl/clip.go | 4 + backend/gogl/fill.go | 6 ++ backend/gogl/gogl.go | 89 +++++++++++++++------ backend/gogl/gradients.go | 13 ++- backend/gogl/imagedata.go | 4 +- backend/gogl/images.go | 9 +++ canvas.go | 153 ++---------------------------------- images.go | 24 +++--- paths.go | 7 -- sdlcanvas/sdlcanvas.go | 13 +-- text.go | 9 +-- 12 files changed, 122 insertions(+), 211 deletions(-) diff --git a/backend/backendbase/base.go b/backend/backendbase/base.go index f377333..bd50334 100644 --- a/backend/backendbase/base.go +++ b/backend/backendbase/base.go @@ -10,6 +10,8 @@ import ( // drawing. This enables the backend to be implemented by // various methods (OpenGL, but also other APIs or software) type Backend interface { + Size() (int, int) + LoadImage(img image.Image) (Image, error) LoadLinearGradient(data *LinearGradientData) LinearGradient LoadRadialGradient(data *RadialGradientData) RadialGradient diff --git a/backend/gogl/clip.go b/backend/gogl/clip.go index 61aa230..8296253 100644 --- a/backend/gogl/clip.go +++ b/backend/gogl/clip.go @@ -7,11 +7,15 @@ import ( ) func (b *GoGLBackend) ClearClip() { + b.activate() + gl.StencilMask(0xFF) gl.Clear(gl.STENCIL_BUFFER_BIT) } func (b *GoGLBackend) Clip(pts [][2]float64) { + b.activate() + b.ptsBuf = b.ptsBuf[:0] b.ptsBuf = append(b.ptsBuf, 0, 0, diff --git a/backend/gogl/fill.go b/backend/gogl/fill.go index 02b117a..66900d8 100644 --- a/backend/gogl/fill.go +++ b/backend/gogl/fill.go @@ -10,6 +10,8 @@ import ( ) func (b *GoGLBackend) Clear(pts [4][2]float64) { + b.activate() + // first check if the four points are aligned to form a nice rectangle, which can be more easily // cleared using glScissor and glClear aligned := pts[0][0] == pts[1][0] && pts[2][0] == pts[3][0] && pts[0][1] == pts[3][1] && pts[1][1] == pts[2][1] @@ -68,6 +70,8 @@ func (b *GoGLBackend) clearRect(x, y, w, h int) { } func (b *GoGLBackend) Fill(style *backendbase.FillStyle, pts [][2]float64) { + b.activate() + if style.Blur > 0 { b.offscr1.alpha = true b.enableTextureRenderTarget(&b.offscr1) @@ -140,6 +144,8 @@ func (b *GoGLBackend) Fill(style *backendbase.FillStyle, pts [][2]float64) { } func (b *GoGLBackend) FillImageMask(style *backendbase.FillStyle, mask *image.Alpha, pts [][2]float64) { + b.activate() + w, h := mask.Rect.Dx(), mask.Rect.Dy() gl.ActiveTexture(gl.TEXTURE1) diff --git a/backend/gogl/gogl.go b/backend/gogl/gogl.go index 08c2516..188c635 100644 --- a/backend/gogl/gogl.go +++ b/backend/gogl/gogl.go @@ -42,6 +42,10 @@ type GoGLBackend struct { ptsBuf []float32 + offscreen bool + offscrBuf offscreenBuffer + offscrImg Image + glChan chan func() } @@ -54,7 +58,7 @@ type offscreenBuffer struct { alpha bool } -func New(x, y, w, h int) (backendbase.Backend, error) { +func New(x, y, w, h int) (*GoGLBackend, error) { err := gl.Init() if err != nil { return nil, err @@ -210,15 +214,31 @@ func New(x, y, w, h int) (backendbase.Backend, error) { return b, nil } +func NewOffscreen(w, h int, alpha bool) (*GoGLBackend, error) { + b, err := New(0, 0, w, h) + if err != nil { + return nil, err + } + b.offscreen = true + b.offscrBuf.alpha = alpha + return b, nil +} + // SetBounds updates the bounds of the canvas. This would // usually be called for example when the window is resized func (b *GoGLBackend) SetBounds(x, y, w, h int) { - b.x, b.y = x, y - b.fx, b.fy = float64(x), float64(y) + if !b.offscreen { + b.x, b.y = x, y + b.fx, b.fy = float64(x), float64(y) + } b.w, b.h = w, h b.fw, b.fh = float64(w), float64(h) } +func (b *GoGLBackend) Size() (int, int) { + return b.w, b.h +} + func glError() error { glErr := gl.GetError() if glErr != gl.NO_ERROR { @@ -227,21 +247,44 @@ func glError() error { return nil } -// Activate makes this GL backend active and sets the viewport. Only -// needs to be called if any other GL code changes the viewport -func (b *GoGLBackend) Activate() { - // if b.offscreen { - // gl.Viewport(0, 0, int32(cv.w), int32(cv.h)) - // cv.enableTextureRenderTarget(&cv.offscrBuf) - // cv.offscrImg.w = cv.offscrBuf.w - // cv.offscrImg.h = cv.offscrBuf.h - // cv.offscrImg.tex = cv.offscrBuf.tex - // } else { - gl.Viewport(int32(b.x), int32(b.y), int32(b.w), int32(b.h)) - b.disableTextureRenderTarget() - // } - // b.applyScissor() - gl.Clear(gl.STENCIL_BUFFER_BIT) +var activeContext *GoGLBackend + +func (b *GoGLBackend) activate() { + if activeContext != b { + activeContext = b + if b.offscreen { + gl.Viewport(0, 0, int32(b.w), int32(b.h)) + b.enableTextureRenderTarget(&b.offscrBuf) + b.offscrImg.w = b.offscrBuf.w + b.offscrImg.h = b.offscrBuf.h + b.offscrImg.tex = b.offscrBuf.tex + } else { + gl.Viewport(int32(b.x), int32(b.y), int32(b.w), int32(b.h)) + b.disableTextureRenderTarget() + } + } + +loop: + for { + select { + case f := <-b.glChan: + f() + default: + break loop + } + } +} + +func (b *GoGLBackend) DeleteOffscreen() { + if !b.offscreen { + return + } + gl.DeleteTextures(1, &b.offscrBuf.tex) + gl.DeleteFramebuffers(1, &b.offscrBuf.frameBuf) + gl.DeleteRenderbuffers(1, &b.offscrBuf.renderStencilBuf) + b.offscreen = false + + b.activate() } type glColor struct { @@ -439,11 +482,11 @@ func (b *GoGLBackend) enableTextureRenderTarget(offscr *offscreenBuffer) { } func (b *GoGLBackend) disableTextureRenderTarget() { - // if b.offscreen { - // b.enableTextureRenderTarget(&b.offscrBuf) - // } else { - gl.BindFramebuffer(gl.FRAMEBUFFER, 0) - // } + if b.offscreen { + b.enableTextureRenderTarget(&b.offscrBuf) + } else { + gl.BindFramebuffer(gl.FRAMEBUFFER, 0) + } } type mat [9]float64 diff --git a/backend/gogl/gradients.go b/backend/gogl/gradients.go index d9d6a96..ced1826 100644 --- a/backend/gogl/gradients.go +++ b/backend/gogl/gradients.go @@ -25,6 +25,7 @@ type RadialGradient struct { } type gradient struct { + b *GoGLBackend from, to vec tex uint32 loaded bool @@ -33,8 +34,10 @@ type gradient struct { } func (b *GoGLBackend) LoadLinearGradient(data *backendbase.LinearGradientData) backendbase.LinearGradient { + b.activate() + lg := &LinearGradient{ - gradient: gradient{from: vec{data.X0, data.Y0}, to: vec{data.X1, data.Y1}, opaque: true}, + gradient: gradient{b: b, from: vec{data.X0, data.Y0}, to: vec{data.X1, data.Y1}, opaque: true}, } gl.GenTextures(1, &lg.tex) gl.ActiveTexture(gl.TEXTURE0) @@ -53,8 +56,10 @@ func (b *GoGLBackend) LoadLinearGradient(data *backendbase.LinearGradientData) b } func (b *GoGLBackend) LoadRadialGradient(data *backendbase.RadialGradientData) backendbase.RadialGradient { + b.activate() + rg := &RadialGradient{ - gradient: gradient{from: vec{data.X0, data.Y0}, to: vec{data.X1, data.Y1}, opaque: true}, + gradient: gradient{b: b, from: vec{data.X0, data.Y0}, to: vec{data.X1, data.Y1}, opaque: true}, radFrom: data.RadFrom, radTo: data.RadTo, } @@ -76,6 +81,8 @@ func (b *GoGLBackend) LoadRadialGradient(data *backendbase.RadialGradientData) b // Delete explicitly deletes the gradient func (g *gradient) Delete() { + g.b.activate() + gl.DeleteTextures(1, &g.tex) g.deleted = true } @@ -91,6 +98,8 @@ func (g *gradient) load(stops backendbase.Gradient) { return } + g.b.activate() + gl.ActiveTexture(gl.TEXTURE0) gl.BindTexture(gl.TEXTURE_2D, g.tex) var pixels [2048 * 4]byte diff --git a/backend/gogl/imagedata.go b/backend/gogl/imagedata.go index d2fb0e6..7b6ee8b 100644 --- a/backend/gogl/imagedata.go +++ b/backend/gogl/imagedata.go @@ -10,7 +10,7 @@ import ( // GetImageData returns an RGBA image of the current image func (b *GoGLBackend) GetImageData(x, y, w, h int) *image.RGBA { - // cv.activate() + b.activate() if x < 0 { w += x @@ -44,7 +44,7 @@ func (b *GoGLBackend) GetImageData(x, y, w, h int) *image.RGBA { // PutImageData puts the given image at the given x/y coordinates func (b *GoGLBackend) PutImageData(img *image.RGBA, x, y int) { - // cv.activate() + b.activate() gl.ActiveTexture(gl.TEXTURE0) if b.imageBufTex == 0 { diff --git a/backend/gogl/images.go b/backend/gogl/images.go index d9f943c..29715a3 100644 --- a/backend/gogl/images.go +++ b/backend/gogl/images.go @@ -12,6 +12,7 @@ import ( // Image represents a loaded image that can be used in various drawing functions type Image struct { + b *GoGLBackend w, h int tex uint32 deleted bool @@ -19,6 +20,8 @@ type Image struct { } func (b *GoGLBackend) LoadImage(src image.Image) (backendbase.Image, error) { + b.activate() + var tex uint32 gl.GenTextures(1, &tex) gl.ActiveTexture(gl.TEXTURE0) @@ -184,6 +187,8 @@ func (img *Image) Size() (int, int) { return img.w, img.h } // Delete deletes the image from memory. Any draw calls // with a deleted image will not do anything func (img *Image) Delete() { + img.b.activate() + gl.DeleteTextures(1, &img.tex) img.deleted = true } @@ -194,6 +199,8 @@ func (img *Image) IsDeleted() bool { return img.deleted } // Replace replaces the image with the new one func (img *Image) Replace(src image.Image) error { + img.b.activate() + gl.ActiveTexture(gl.TEXTURE0) gl.BindTexture(gl.TEXTURE_2D, img.tex) newImg, err := loadImage(src, img.tex) @@ -209,6 +216,8 @@ func (img *Image) Replace(src image.Image) error { func (img *Image) IsOpaque() bool { return img.opaque } func (b *GoGLBackend) DrawImage(dimg backendbase.Image, sx, sy, sw, sh, dx, dy, dw, dh float64, alpha float64) { + b.activate() + img := dimg.(*Image) sx /= float64(img.w) diff --git a/canvas.go b/canvas.go index 47bb830..a187450 100644 --- a/canvas.go +++ b/canvas.go @@ -20,8 +20,8 @@ import ( type Canvas struct { b backendbase.Backend - x, y, w, h int - fx, fy, fw, fh float64 + w, h int + fw, fh float64 path Path2D convex bool @@ -30,10 +30,6 @@ type Canvas struct { state drawState stateStack []drawState - offscreen bool - offscrBuf offscreenBuffer - offscrImg Image - images map[interface{}]*Image shadowBuf [][2]float64 @@ -136,16 +132,14 @@ var Performance = struct { // While all functions on the canvas use the top left point as // the origin, since GL uses the bottom left coordinate, the // coordinates given here also use the bottom left as origin -func New(backend backendbase.Backend, x, y, w, h int) *Canvas { - if gli == nil { - panic("LoadGL must be called before a canvas can be created") - } +func New(backend backendbase.Backend) *Canvas { cv := &Canvas{ b: backend, stateStack: make([]drawState, 0, 20), images: make(map[interface{}]*Image), } - cv.SetBounds(x, y, w, h) + w, h := backend.Size() + cv.setBounds(w, h) cv.state.lineWidth = 1 cv.state.lineAlpha = 1 cv.state.miterLimitSqr = 100 @@ -156,36 +150,9 @@ func New(backend backendbase.Backend, x, y, w, h int) *Canvas { return cv } -// NewOffscreen creates a new canvas with the given size. It -// does not render directly to the screen but renders to a -// texture instead. If alpha is set to true, the offscreen -// canvas will have an alpha channel -func NewOffscreen(backend backendbase.Backend, w, h int, alpha bool) *Canvas { - cv := New(backend, 0, 0, w, h) - cv.offscreen = true - cv.offscrBuf.alpha = alpha - return cv -} - -func DeleteOffscreen(cv *Canvas) { - if !cv.offscreen { - return - } - gli.DeleteTextures(1, &cv.offscrBuf.tex) - gli.DeleteFramebuffers(1, &cv.offscrBuf.frameBuf) - gli.DeleteRenderbuffers(1, &cv.offscrBuf.renderStencilBuf) -} - -// SetBounds updates the bounds of the canvas. This would -// usually be called for example when the window is resized -func (cv *Canvas) SetBounds(x, y, w, h int) { - if !cv.offscreen { - cv.x, cv.y = x, y - cv.fx, cv.fy = float64(x), float64(y) - } +func (cv *Canvas) setBounds(w, h int) { cv.w, cv.h = w, h cv.fw, cv.fh = float64(w), float64(h) - activeCanvas = nil } // Width returns the internal width of the canvas @@ -202,46 +169,8 @@ func (cv *Canvas) tf(v vec) vec { return v } -// Activate makes the canvas active and sets the viewport. Only needs -// to be called if any other GL code changes the viewport -func (cv *Canvas) Activate() { - if cv.offscreen { - gli.Viewport(0, 0, int32(cv.w), int32(cv.h)) - cv.enableTextureRenderTarget(&cv.offscrBuf) - // cv.offscrImg.w = cv.offscrBuf.w - // cv.offscrImg.h = cv.offscrBuf.h - // cv.offscrImg.tex = cv.offscrBuf.tex - } else { - gli.Viewport(int32(cv.x), int32(cv.y), int32(cv.w), int32(cv.h)) - cv.disableTextureRenderTarget() - } -} - -var activeCanvas *Canvas - -func (cv *Canvas) activate() { - if activeCanvas != cv { - activeCanvas = cv - cv.Activate() - } -loop: - for { - select { - case f := <-glChan: - f() - default: - break loop - } - } -} - const alphaTexSize = 2048 -var ( - gli GL - glChan = make(chan func()) -) - type offscreenBuffer struct { tex uint32 w int @@ -261,23 +190,6 @@ type gaussianShader struct { kernel int32 } -// LoadGL needs to be called once per GL context to load the GL assets -// that canvas needs. The parameter is an implementation of the GL interface -// in this package that should make this package neutral to GL implementations. -// The goglimpl subpackage contains an implementation based on Go-GL v3.2 -func LoadGL(glimpl GL) (err error) { - gli = glimpl - return -} - -func glError() error { - glErr := gli.GetError() - if glErr != gl_NO_ERROR { - return fmt.Errorf("GL Error: %x", glErr) - } - return nil -} - // SetFillStyle sets the color, gradient, or image for any fill calls. To set a // color, there are several acceptable formats: 3 or 4 int values for RGB(A) in // the range 0-255, 3 or 4 float values for RGB(A) in the range 0-1, hex strings @@ -349,57 +261,6 @@ func (cv *Canvas) backendFillStyle(s *drawStyle, alpha float64) backendbase.Fill return stl } -func (cv *Canvas) enableTextureRenderTarget(offscr *offscreenBuffer) { - if offscr.w != cv.w || offscr.h != cv.h { - if offscr.w != 0 && offscr.h != 0 { - gli.DeleteTextures(1, &offscr.tex) - gli.DeleteFramebuffers(1, &offscr.frameBuf) - gli.DeleteRenderbuffers(1, &offscr.renderStencilBuf) - } - offscr.w = cv.w - offscr.h = cv.h - - gli.ActiveTexture(gl_TEXTURE0) - gli.GenTextures(1, &offscr.tex) - gli.BindTexture(gl_TEXTURE_2D, offscr.tex) - // todo do non-power-of-two textures work everywhere? - if offscr.alpha { - gli.TexImage2D(gl_TEXTURE_2D, 0, gl_RGBA, int32(cv.w), int32(cv.h), 0, gl_RGBA, gl_UNSIGNED_BYTE, nil) - } else { - gli.TexImage2D(gl_TEXTURE_2D, 0, gl_RGB, int32(cv.w), int32(cv.h), 0, gl_RGB, gl_UNSIGNED_BYTE, nil) - } - gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_NEAREST) - gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_NEAREST) - - gli.GenFramebuffers(1, &offscr.frameBuf) - gli.BindFramebuffer(gl_FRAMEBUFFER, offscr.frameBuf) - - gli.GenRenderbuffers(1, &offscr.renderStencilBuf) - gli.BindRenderbuffer(gl_RENDERBUFFER, offscr.renderStencilBuf) - gli.RenderbufferStorage(gl_RENDERBUFFER, gl_DEPTH24_STENCIL8, int32(cv.w), int32(cv.h)) - gli.FramebufferRenderbuffer(gl_FRAMEBUFFER, gl_DEPTH_STENCIL_ATTACHMENT, gl_RENDERBUFFER, offscr.renderStencilBuf) - - gli.FramebufferTexture(gl_FRAMEBUFFER, gl_COLOR_ATTACHMENT0, offscr.tex, 0) - - if err := gli.CheckFramebufferStatus(gl_FRAMEBUFFER); err != gl_FRAMEBUFFER_COMPLETE { - // todo this should maybe not panic - panic(fmt.Sprintf("Failed to set up framebuffer for offscreen texture: %x", err)) - } - - gli.Clear(gl_COLOR_BUFFER_BIT | gl_STENCIL_BUFFER_BIT) - } else { - gli.BindFramebuffer(gl_FRAMEBUFFER, offscr.frameBuf) - } -} - -func (cv *Canvas) disableTextureRenderTarget() { - if cv.offscreen { - cv.enableTextureRenderTarget(&cv.offscrBuf) - } else { - gli.BindFramebuffer(gl_FRAMEBUFFER, 0) - } -} - // SetLineWidth sets the line width for any line drawing calls func (cv *Canvas) SetLineWidth(width float64) { if width < 0 { @@ -430,7 +291,7 @@ func (cv *Canvas) SetFont(src interface{}, size float64) { if f, ok := fonts[v]; ok { cv.state.font = f } else { - f, err := LoadFont(v) + f, err := cv.LoadFont(v) if err != nil { fmt.Fprintf(os.Stderr, "Error loading font %s: %v\n", v, err) fonts[v] = nil diff --git a/images.go b/images.go index f8a96e5..b36fc23 100644 --- a/images.go +++ b/images.go @@ -124,13 +124,13 @@ func (img *Image) Replace(src interface{}) error { // source coordinates func (cv *Canvas) DrawImage(image interface{}, coords ...float64) { var img *Image - var flip bool - if cv2, ok := image.(*Canvas); ok && cv2.offscreen { - img = &cv2.offscrImg - flip = true - } else { - img = cv.getImage(image) - } + // var flip bool + // if cv2, ok := image.(*Canvas); ok && cv2.offscreen { + // img = &cv2.offscrImg + // flip = true + // } else { + img = cv.getImage(image) + // } if img == nil { return @@ -140,8 +140,6 @@ func (cv *Canvas) DrawImage(image interface{}, coords ...float64) { return } - cv.activate() - var sx, sy, sw, sh, dx, dy, dw, dh float64 sw, sh = float64(img.Width()), float64(img.Height()) dw, dh = float64(img.Width()), float64(img.Height()) @@ -157,10 +155,10 @@ func (cv *Canvas) DrawImage(image interface{}, coords ...float64) { dw, dh = coords[6], coords[7] } - if flip { - dy += dh - dh = -dh - } + // if flip { + // dy += dh + // dh = -dh + // } var data [4][2]float64 data[0] = cv.tf(vec{dx, dy}) diff --git a/paths.go b/paths.go index 62eeb40..6f1546e 100644 --- a/paths.go +++ b/paths.go @@ -96,8 +96,6 @@ func (cv *Canvas) strokePath(path *Path2D) { return } - cv.activate() - dashedPath := cv.applyLineDash(path.p) var triBuf [500][2]float64 @@ -328,7 +326,6 @@ func (cv *Canvas) FillPath(path *Path2D) { if len(path.p) < 3 { return } - cv.activate() var triBuf [500][2]float64 tris := buildFillTriangles(path, triBuf[:0]) @@ -431,8 +428,6 @@ func (cv *Canvas) StrokeRect(x, y, w, h float64) { // FillRect fills a rectangle with the active fill style func (cv *Canvas) FillRect(x, y, w, h float64) { - cv.activate() - p0 := cv.tf(vec{x, y}) p1 := cv.tf(vec{x, y + h}) p2 := cv.tf(vec{x + w, y + h}) @@ -448,8 +443,6 @@ func (cv *Canvas) FillRect(x, y, w, h float64) { // ClearRect sets the color of the rectangle to transparent black func (cv *Canvas) ClearRect(x, y, w, h float64) { - cv.activate() - p0 := cv.tf(vec{x, y}) p1 := cv.tf(vec{x, y + h}) p2 := cv.tf(vec{x + w, y + h}) diff --git a/sdlcanvas/sdlcanvas.go b/sdlcanvas/sdlcanvas.go index 779af0a..028fdaa 100644 --- a/sdlcanvas/sdlcanvas.go +++ b/sdlcanvas/sdlcanvas.go @@ -12,9 +12,7 @@ import ( "github.com/go-gl/gl/v3.2-core/gl" "github.com/tfriedel6/canvas" - "github.com/tfriedel6/canvas/backend/backendbase" "github.com/tfriedel6/canvas/backend/gogl" - "github.com/tfriedel6/canvas/glimpl/gogl" "github.com/veandco/go-sdl2/sdl" ) @@ -24,7 +22,7 @@ type Window struct { Window *sdl.Window WindowID uint32 GLContext sdl.GLContext - Backend backendbase.Backend + Backend *goglbackend.GoGLBackend canvas *canvas.Canvas frameTimes [10]time.Time frameIndex int @@ -98,12 +96,7 @@ func CreateWindow(w, h int, title string) (*Window, *canvas.Canvas, error) { sdl.GLSetSwapInterval(1) gl.Enable(gl.MULTISAMPLE) - err = canvas.LoadGL(glimplgogl.GLImpl{}) - if err != nil { - return nil, nil, fmt.Errorf("Error loading canvas GL assets: %v", err) - } - - cv := canvas.New(backend, 0, 0, w, h) + cv := canvas.New(backend) wnd := &Window{ Window: window, WindowID: windowID, @@ -195,7 +188,7 @@ func (wnd *Window) StartFrame() error { wnd.SizeChange(int(e.Data1), int(e.Data2)) handled = true } else { - wnd.canvas.SetBounds(0, 0, int(e.Data1), int(e.Data2)) + wnd.Backend.SetBounds(0, 0, int(e.Data1), int(e.Data2)) } } } diff --git a/text.go b/text.go index b075098..730ddf8 100644 --- a/text.go +++ b/text.go @@ -28,10 +28,7 @@ var defaultFont *Font // LoadFont loads a font and returns the result. The font // can be a file name or a byte slice in TTF format -func LoadFont(src interface{}) (*Font, error) { - if gli == nil { - panic("LoadGL must be called before fonts can be loaded") - } +func (cv *Canvas) LoadFont(src interface{}) (*Font, error) { var f *Font switch v := src.(type) { case *truetype.Font: @@ -64,8 +61,6 @@ func LoadFont(src interface{}) (*Font, error) { // FillText draws the given string at the given coordinates // using the currently set font and font height func (cv *Canvas) FillText(str string, x, y float64) { - cv.activate() - if cv.state.font == nil { return } @@ -227,8 +222,6 @@ func (cv *Canvas) FillText(str string, x, y float64) { // using the currently set font and font height and using the // current stroke style func (cv *Canvas) StrokeText(str string, x, y float64) { - cv.activate() - if cv.state.font == nil { return }