diff --git a/canvas.go b/canvas.go new file mode 100644 index 0000000000000000000000000000000000000000..be8c8d7708572805c7b3982042f0b2c9a3817108 --- /dev/null +++ b/canvas.go @@ -0,0 +1,222 @@ +package pixel + +import ( + "image/color" + + "github.com/faiface/mainthread" + "github.com/faiface/pixel/pixelgl" + "github.com/go-gl/mathgl/mgl32" + "github.com/pkg/errors" +) + +// Canvas is basically a Picture that you can draw on. +// +// Canvas supports TrianglesPosition, TrianglesColor and TrianglesTexture. +type Canvas struct { + f *pixelgl.Frame + s *pixelgl.Shader + + pic *Picture + mat mgl32.Mat3 + col mgl32.Vec4 + bnd mgl32.Vec4 +} + +// NewCanvas creates a new fully transparent Canvas with specified dimensions in pixels. +func NewCanvas(width, height float64, smooth bool) *Canvas { + c := &Canvas{} + mainthread.Call(func() { + var err error + c.f = pixelgl.NewFrame(int(width), int(height), smooth) + c.s, err = pixelgl.NewShader( + canvasVertexFormat, + canvasUniformFormat, + canvasVertexShader, + canvasFragmentShader, + ) + if err != nil { + panic(errors.Wrap(err, "failed to create canvas")) + } + }) + c.pic = nil + c.mat = mgl32.Ident3() + c.col = mgl32.Vec4{1, 1, 1, 1} + c.bnd = mgl32.Vec4{0, 0, 1, 1} + return c +} + +// Size returns the width and the height of the Canvas in pixels. +func (c *Canvas) Size() (width, height float64) { + return float64(c.f.Width()), float64(c.f.Height()) +} + +// Content returns a Picture that contains the content of this Canvas. The returned Picture changes +// as you draw onto the Canvas, so there is no real need to call this method more than once. +func (c *Canvas) Content() *Picture { + tex := c.f.Texture() + return &Picture{ + texture: tex, + bounds: R(0, 0, float64(tex.Width()), float64(tex.Height())), + } +} + +// Clear fill the whole Canvas with on specified color. +func (c *Canvas) Clear(col color.Color) { + mainthread.CallNonBlock(func() { + c.f.Begin() + col := NRGBAModel.Convert(col).(NRGBA) + pixelgl.Clear(float32(col.R), float32(col.G), float32(col.B), float32(col.A)) + c.f.End() + }) +} + +// MakeTriangles returns Triangles that draw onto this Canvas. +func (c *Canvas) MakeTriangles(t Triangles) Triangles { + tpcs := NewGLTriangles(c.s, t).(trianglesPositionColorTexture) + return &canvasTriangles{ + c: c, + trianglesPositionColorTexture: tpcs, + } +} + +// SetPicture sets a Picture that will be used in further draw operations. +// +// This does not set the Picture that this Canvas draws onto, don't confuse it. +func (c *Canvas) SetPicture(p *Picture) { + if p != nil { + min := pictureBounds(p, V(0, 0)) + max := pictureBounds(p, V(1, 1)) + c.bnd = mgl32.Vec4{ + float32(min.X()), float32(min.Y()), + float32(max.X()), float32(max.Y()), + } + } + c.pic = p +} + +// SetTransform sets the transformations used in further draw operations. +func (c *Canvas) SetTransform(t ...Transform) { + c.mat = transformToMat(t...) +} + +// SetMaskColor sets the mask color used in further draw operations. +func (c *Canvas) SetMaskColor(col color.Color) { + if col == nil { + col = NRGBA{1, 1, 1, 1} + } + nrgba := NRGBAModel.Convert(col).(NRGBA) + r := float32(nrgba.R) + g := float32(nrgba.G) + b := float32(nrgba.B) + a := float32(nrgba.A) + c.col = mgl32.Vec4{r, g, b, a} +} + +type trianglesPositionColorTexture interface { + Triangles + Position(i int) Vec + Color(i int) NRGBA + Texture(i int) Vec +} + +type canvasTriangles struct { + c *Canvas + trianglesPositionColorTexture +} + +func (ct *canvasTriangles) Draw() { + // avoid possible race condition + pic := ct.c.pic + mat := ct.c.mat + col := ct.c.col + bnd := ct.c.bnd + + mainthread.CallNonBlock(func() { + ct.c.f.Begin() + ct.c.s.Begin() + + ct.c.s.SetUniformAttr(canvasTransformMat3, mat) + ct.c.s.SetUniformAttr(canvasMaskColorVec4, col) + ct.c.s.SetUniformAttr(canvasBoundsVec4, bnd) + + if pic != nil { + pic.Texture().Begin() + ct.trianglesPositionColorTexture.Draw() + pic.Texture().End() + } else { + ct.trianglesPositionColorTexture.Draw() + } + + ct.c.s.End() + ct.c.f.End() + }) +} + +const ( + canvasPositionVec2 int = iota + canvasColorVec4 + canvasTextureVec2 +) + +var canvasVertexFormat = pixelgl.AttrFormat{ + canvasPositionVec2: {Name: "position", Type: pixelgl.Vec2}, + canvasColorVec4: {Name: "color", Type: pixelgl.Vec4}, + canvasTextureVec2: {Name: "texture", Type: pixelgl.Vec2}, +} + +const ( + canvasMaskColorVec4 int = iota + canvasTransformMat3 + canvasBoundsVec4 +) + +var canvasUniformFormat = pixelgl.AttrFormat{ + {Name: "maskColor", Type: pixelgl.Vec4}, + {Name: "transform", Type: pixelgl.Mat3}, + {Name: "bounds", Type: pixelgl.Vec4}, +} + +var canvasVertexShader = ` +#version 330 core + +in vec2 position; +in vec4 color; +in vec2 texture; + +out vec4 Color; +out vec2 Texture; + +uniform mat3 transform; + +void main() { + gl_Position = vec4((transform * vec3(position.x, position.y, 1.0)).xy, 0.0, 1.0); + Color = color; + Texture = texture; +} +` + +var canvasFragmentShader = ` +#version 330 core + +in vec4 Color; +in vec2 Texture; + +out vec4 color; + +uniform vec4 maskColor; +uniform vec4 bounds; +uniform sampler2D tex; + +void main() { + vec2 boundsMin = bounds.xy; + vec2 boundsMax = bounds.zw; + + if (Texture == vec2(-1, -1)) { + color = maskColor * Color; + } else { + float tx = boundsMin.x * (1 - Texture.x) + boundsMax.x * Texture.x; + float ty = boundsMin.y * (1 - Texture.y) + boundsMax.y * Texture.y; + color = maskColor * Color * texture(tex, vec2(tx, ty)); + } +} +` diff --git a/window.go b/window.go index 3fa543166c0e1a9cb3d465926bdd458e8721fb78..75a5f03ca6383a68dd92c9d1acb5e004adcfc737 100644 --- a/window.go +++ b/window.go @@ -8,7 +8,6 @@ import ( "github.com/faiface/mainthread" "github.com/faiface/pixel/pixelgl" "github.com/go-gl/glfw/v3.2/glfw" - "github.com/go-gl/mathgl/mgl32" "github.com/pkg/errors" ) @@ -60,16 +59,10 @@ type Window struct { enabled bool window *glfw.Window config WindowConfig - shader *pixelgl.Shader - // cache - width, height float64 - - // Target stuff, Picture, transformation matrix and color - pic *Picture - mat mgl32.Mat3 - col mgl32.Vec4 - bnd mgl32.Vec4 + canvas *Canvas + canvasVs *pixelgl.VertexSlice + shader *pixelgl.Shader // need to save these to correctly restore a fullscreen window restore struct { @@ -80,9 +73,6 @@ type Window struct { buttons [KeyLast + 1]bool scroll Vec } - - //DEBUG - Frame *pixelgl.Frame } var currentWindow *Window @@ -120,42 +110,52 @@ func NewWindow(config WindowConfig) (*Window, error) { if currentWindow != nil { share = currentWindow.window } - w.window, err = glfw.CreateWindow(int(config.Width), int(config.Height), config.Title, nil, share) + w.window, err = glfw.CreateWindow( + int(config.Width), + int(config.Height), + config.Title, + nil, + share, + ) if err != nil { return err } - return nil - }) - if err != nil { - return nil, errors.Wrap(err, "creating window failed") - } - - mainthread.Call(func() { + // enter the OpenGL context w.begin() w.end() w.shader, err = pixelgl.NewShader( - defaultVertexFormat, - defaultUniformFormat, - defaultVertexShader, - defaultFragmentShader, + windowVertexFormat, + windowUniformFormat, + windowVertexShader, + windowFragmentShader, ) if err != nil { - panic(errors.Wrap(err, "NewWindow: failed to create shader")) + return err } + + w.canvasVs = pixelgl.MakeVertexSlice(w.shader, 6, 6) + w.canvasVs.Begin() + w.canvasVs.SetVertexData([]float32{ + -1, -1, 0, 0, + 1, -1, 1, 0, + 1, 1, 1, 1, + -1, -1, 0, 0, + 1, 1, 1, 1, + -1, 1, 0, 1, + }) + w.canvasVs.End() + + return nil }) if err != nil { - w.Destroy() return nil, errors.Wrap(err, "creating window failed") } w.initInput() w.SetFullscreen(config.Fullscreen) - - w.SetPicture(nil) - w.SetTransform() - w.SetMaskColor(NRGBA{1, 1, 1, 1}) + w.Update() runtime.SetFinalizer(w, (*Window).Destroy) @@ -171,18 +171,41 @@ func (w *Window) Destroy() { // Clear clears the window with a color. func (w *Window) Clear(c color.Color) { - mainthread.CallNonBlock(func() { - w.begin() - c := NRGBAModel.Convert(c).(NRGBA) - pixelgl.Clear(float32(c.R), float32(c.G), float32(c.B), float32(c.A)) - w.end() - }) + w.canvas.Clear(c) } // Update swaps buffers and polls events. func (w *Window) Update() { + width, height := w.Size() + if w.canvas == nil || V(w.canvas.Size()) != V(width, height) { + oldCanvas := w.canvas + w.canvas = NewCanvas(width, height, false) + if oldCanvas != nil { + td := TrianglesDrawer{Triangles: &TrianglesData{ + {Position: V(-1, -1), Color: NRGBA{1, 1, 1, 1}, Texture: V(0, 0)}, + {Position: V(1, -1), Color: NRGBA{1, 1, 1, 1}, Texture: V(1, 0)}, + {Position: V(1, 1), Color: NRGBA{1, 1, 1, 1}, Texture: V(1, 1)}, + {Position: V(-1, -1), Color: NRGBA{1, 1, 1, 1}, Texture: V(0, 0)}, + {Position: V(1, 1), Color: NRGBA{1, 1, 1, 1}, Texture: V(1, 1)}, + {Position: V(-1, 1), Color: NRGBA{1, 1, 1, 1}, Texture: V(0, 1)}, + }} + w.canvas.SetPicture(oldCanvas.Content()) + td.Draw(w.canvas) + } + } + mainthread.Call(func() { w.begin() + + pixelgl.Clear(0, 0, 0, 0) + w.shader.Begin() + w.canvas.f.Texture().Begin() + w.canvasVs.Begin() + w.canvasVs.Draw() + w.canvasVs.End() + w.canvas.f.Texture().End() + w.shader.End() + if w.config.VSync { glfw.SwapInterval(1) } @@ -191,8 +214,6 @@ func (w *Window) Update() { }) w.updateInput() - - w.width, w.height = w.Size() } // SetClosed sets the closed flag of a window. @@ -346,63 +367,11 @@ func (w *Window) begin() { pixelgl.Init() currentWindow = w } - if w.shader != nil { - w.shader.Begin() - } - if w.Frame != nil { - w.Frame.Begin() - pixelgl.Viewport(0, 0, int32(w.Frame.Width()), int32(w.Frame.Height())) - } else { - pixelgl.Viewport(0, 0, int32(w.width), int32(w.height)) - } } // Note: must be called inside the main thread. func (w *Window) end() { - if w.Frame != nil { - w.Frame.End() - } - if w.shader != nil { - w.shader.End() - } -} - -type trianglesPositionColorTexture interface { - Triangles - Position(i int) Vec - Color(i int) NRGBA - Texture(i int) Vec -} - -type windowTriangles struct { - w *Window - trianglesPositionColorTexture -} - -func (wt *windowTriangles) Draw() { - // avoid possible race condition - pic := wt.w.pic - mat := wt.w.mat - col := wt.w.col - bnd := wt.w.bnd - - mainthread.CallNonBlock(func() { - wt.w.begin() - - wt.w.shader.SetUniformAttr(transformMat3, mat) - wt.w.shader.SetUniformAttr(maskColorVec4, col) - wt.w.shader.SetUniformAttr(boundsVec4, bnd) - - if pic != nil { - pic.Texture().Begin() - } - wt.trianglesPositionColorTexture.Draw() - if pic != nil { - pic.Texture().End() - } - - wt.w.end() - }) + // nothing really } // MakeTriangles generates a specialized copy of the supplied triangles that will draw onto this @@ -410,112 +379,62 @@ func (wt *windowTriangles) Draw() { // // Window supports TrianglesPosition, TrianglesColor and TrianglesTexture. func (w *Window) MakeTriangles(t Triangles) Triangles { - tpcs := NewGLTriangles(w.shader, t).(trianglesPositionColorTexture) - wt := &windowTriangles{ - w: w, - trianglesPositionColorTexture: tpcs, - } - return wt + return w.canvas.MakeTriangles(t) } // SetPicture sets a Picture that will be used in subsequent drawings onto the window. func (w *Window) SetPicture(p *Picture) { - if p != nil { - min := pictureBounds(p, V(0, 0)) - max := pictureBounds(p, V(1, 1)) - w.bnd = mgl32.Vec4{ - float32(min.X()), float32(min.Y()), - float32(max.X()), float32(max.Y()), - } - } - w.pic = p + w.canvas.SetPicture(p) } // SetTransform sets a global transformation matrix for the Window. // // Transforms are applied right-to-left. func (w *Window) SetTransform(t ...Transform) { - w.mat = transformToMat(t...) + w.canvas.SetTransform(t...) } // SetMaskColor sets a global mask color for the Window. func (w *Window) SetMaskColor(c color.Color) { - if c == nil { - c = NRGBA{1, 1, 1, 1} - } - nrgba := NRGBAModel.Convert(c).(NRGBA) - r := float32(nrgba.R) - g := float32(nrgba.G) - b := float32(nrgba.B) - a := float32(nrgba.A) - w.col = mgl32.Vec4{r, g, b, a} + w.canvas.SetMaskColor(c) } const ( - positionVec2 int = iota - colorVec4 - textureVec2 + windowPositionVec2 = iota + windowTextureVec2 ) -var defaultVertexFormat = pixelgl.AttrFormat{ - positionVec2: {Name: "position", Type: pixelgl.Vec2}, - colorVec4: {Name: "color", Type: pixelgl.Vec4}, - textureVec2: {Name: "texture", Type: pixelgl.Vec2}, +var windowVertexFormat = pixelgl.AttrFormat{ + windowPositionVec2: {Name: "position", Type: pixelgl.Vec2}, + windowTextureVec2: {Name: "texture", Type: pixelgl.Vec2}, } -const ( - maskColorVec4 int = iota - transformMat3 - boundsVec4 -) - -var defaultUniformFormat = pixelgl.AttrFormat{ - {Name: "maskColor", Type: pixelgl.Vec4}, - {Name: "transform", Type: pixelgl.Mat3}, - {Name: "bounds", Type: pixelgl.Vec4}, -} +var windowUniformFormat = pixelgl.AttrFormat{} -var defaultVertexShader = ` +var windowVertexShader = ` #version 330 core in vec2 position; -in vec4 color; in vec2 texture; -out vec4 Color; out vec2 Texture; -uniform mat3 transform; - void main() { - gl_Position = vec4((transform * vec3(position.x, position.y, 1.0)).xy, 0.0, 1.0); - Color = color; + gl_Position = vec4(position, 0.0, 1.0); Texture = texture; } ` -var defaultFragmentShader = ` +var windowFragmentShader = ` #version 330 core -in vec4 Color; in vec2 Texture; out vec4 color; -uniform vec4 maskColor; -uniform vec4 bounds; uniform sampler2D tex; void main() { - vec2 boundsMin = bounds.xy; - vec2 boundsMax = bounds.zw; - - if (Texture == vec2(-1, -1)) { - color = maskColor * Color; - } else { - float tx = boundsMin.x * (1 - Texture.x) + boundsMax.x * Texture.x; - float ty = boundsMin.y * (1 - Texture.y) + boundsMax.y * Texture.y; - color = maskColor * Color * texture(tex, vec2(tx, 1 - ty)); - } + color = texture(tex, Texture); } `