From 69a3c17cfc2bc6c11c4d7c6518968de924f58647 Mon Sep 17 00:00:00 2001 From: faiface Date: Mon, 6 Mar 2017 00:28:52 +0100 Subject: [PATCH] big big commit - rework whole pixelgl --- pixelgl/canvas.go | 387 ++++++++++++++++++++++++----------------- pixelgl/gltriangles.go | 28 +-- pixelgl/util.go | 35 +--- pixelgl/window.go | 216 ++++++++++------------- 4 files changed, 345 insertions(+), 321 deletions(-) diff --git a/pixelgl/canvas.go b/pixelgl/canvas.go index a75d6f7..7d0bd81 100644 --- a/pixelgl/canvas.go +++ b/pixelgl/canvas.go @@ -1,6 +1,7 @@ package pixelgl import ( + "fmt" "image/color" "github.com/faiface/glhf" @@ -10,30 +11,33 @@ import ( "github.com/pkg/errors" ) -// Canvas is basically a Picture that you can draw onto. +//TODO: make Canvas a Picture + +// Canvas is an off-screen rectangular BasicTarget that you can draw onto. // -// Canvas supports TrianglesPosition, TrianglesColor and TrianglesTexture. +// It supports TrianglesPosition, TrianglesColor, TrianglesPicture and PictureColor. type Canvas struct { f *glhf.Frame s *glhf.Shader - copyVs *glhf.VertexSlice + bounds pixel.Rect smooth bool - drawTd pixel.TrianglesDrawer - - pic *pixel.GLPicture 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{smooth: smooth} +// NewCanvas creates a new empty, fully transparent Canvas with given bounds. If the smooth flag +// set, then stretched Pictures will be smoothed and will not be drawn pixely onto this Canvas. +func NewCanvas(bounds pixel.Rect, smooth bool) *Canvas { + c := &Canvas{ + smooth: smooth, + mat: mgl32.Ident3(), + col: mgl32.Vec4{1, 1, 1, 1}, + } + mainthread.Call(func() { var err error - c.f = glhf.NewFrame(int(width), int(height), smooth) c.s, err = glhf.NewShader( canvasVertexFormat, canvasUniformFormat, @@ -41,162 +45,179 @@ func NewCanvas(width, height float64, smooth bool) *Canvas { canvasFragmentShader, ) if err != nil { - panic(errors.Wrap(err, "failed to create canvas")) + panic(errors.Wrap(err, "failed to create Canvas, there's a bug in the shader")) } - - c.copyVs = glhf.MakeVertexSlice(c.s, 6, 6) - c.copyVs.Begin() - c.copyVs.SetVertexData([]float32{ - -1, -1, 1, 1, 1, 1, 0, 0, - 1, -1, 1, 1, 1, 1, 1, 0, - 1, 1, 1, 1, 1, 1, 1, 1, - -1, -1, 1, 1, 1, 1, 0, 0, - 1, 1, 1, 1, 1, 1, 1, 1, - -1, 1, 1, 1, 1, 1, 0, 1, - }) - c.copyVs.End() }) - white := pixel.NRGBA{R: 1, G: 1, B: 1, A: 1} - c.drawTd = pixel.TrianglesDrawer{Triangles: &pixel.TrianglesData{ - {Position: pixel.V(-1, -1), Color: white, Picture: pixel.V(0, 0)}, - {Position: pixel.V(1, -1), Color: white, Picture: pixel.V(1, 0)}, - {Position: pixel.V(1, 1), Color: white, Picture: pixel.V(1, 1)}, - {Position: pixel.V(-1, -1), Color: white, Picture: pixel.V(0, 0)}, - {Position: pixel.V(1, 1), Color: white, Picture: pixel.V(1, 1)}, - {Position: pixel.V(-1, 1), Color: white, Picture: pixel.V(0, 1)}, - }} + c.SetBounds(bounds) - 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 } -// SetSize resizes the Canvas. The original content will be stretched to fit the new size. -func (c *Canvas) SetSize(width, height float64) { - if pixel.V(width, height) == pixel.V(c.Size()) { - return - } - mainthread.Call(func() { - oldF := c.f - c.f = glhf.NewFrame(int(width), int(height), c.smooth) - - c.f.Begin() - c.s.Begin() - - c.s.SetUniformAttr(canvasTransformMat3, mgl32.Ident3()) - c.s.SetUniformAttr(canvasMaskColorVec4, mgl32.Vec4{1, 1, 1, 1}) - c.s.SetUniformAttr(canvasBoundsVec4, mgl32.Vec4{0, 0, 1, 1}) - - oldF.Texture().Begin() - c.copyVs.Begin() - c.copyVs.Draw() - c.copyVs.End() - oldF.Texture().End() - - c.s.End() - c.f.End() - }) -} - -// 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 (but it -// might be beneficial to your code to do so). -func (c *Canvas) Content() *pixel.GLPicture { - return pixel.PictureFromTexture(c.f.Texture()) -} - -// Clear fills the whole Canvas with one specified color. -func (c *Canvas) Clear(col color.Color) { - mainthread.CallNonBlock(func() { - c.f.Begin() - col := pixel.NRGBAModel.Convert(col).(pixel.NRGBA) - glhf.Clear(float32(col.R), float32(col.G), float32(col.B), float32(col.A)) - c.f.End() - }) -} - -// Draw draws the content of the Canvas onto another Target. If no transform is applied, the content -// is fully stretched to fit the Target. -func (c *Canvas) Draw(t pixel.Target) { - c.drawTd.Draw(t) -} - -// MakeTriangles returns Triangles that draw onto this Canvas. -func (c *Canvas) MakeTriangles(t pixel.Triangles) pixel.TargetTriangles { - gt := NewGLTriangles(c.s, t).(*glTriangles) - return &canvasTriangles{ - c: c, - glTriangles: gt, - } -} - -// SetPicture sets a Picture that will be used in further draw operations. +// MakeTriangles creates a specialized copy of the supplied Triangles that draws onto this Canvas. // -// This does not set the Picture that this Canvas draws onto, don't confuse it. -func (c *Canvas) SetPicture(p *pixel.GLPicture) { - if p != nil { - min := pictureBounds(p, pixel.V(0, 0)) - max := pictureBounds(p, pixel.V(1, 1)) - c.bnd = mgl32.Vec4{ - float32(min.X()), float32(min.Y()), - float32(max.X()), float32(max.Y()), - } +// TrianglesPosition, TrianglesColor and TrianglesPicture are supported. +func (c *Canvas) MakeTriangles(t pixel.Triangles) pixel.TargetTriangles { + return &canvasTriangles{ + GLTriangles: NewGLTriangles(c.s, t), + c: c, } - c.pic = p } -// SetTransform sets the transformations used in further draw operations. +// MakePicture create a specialized copy of the supplied Picture that draws onto this Canvas. +// +// PictureColor is supported. +func (c *Canvas) MakePicture(p pixel.Picture) pixel.TargetPicture { + pd := pixel.PictureDataFromPicture(p) + pixels := make([]uint8, 4*len(pd.Pix)) + for i := range pd.Pix { + pixels[i*4+0] = uint8(pd.Pix[i].R * 255) + pixels[i*4+1] = uint8(pd.Pix[i].G * 255) + pixels[i*4+2] = uint8(pd.Pix[i].B * 255) + pixels[i*4+3] = uint8(pd.Pix[i].A * 255) + } + + var tex *glhf.Texture + mainthread.Call(func() { + tex = glhf.NewTexture(pd.Stride, len(pd.Pix)/pd.Stride, c.smooth, pixels) + }) + + return &canvasPicture{ + tex: tex, + bounds: pd.Rect, + c: c, + } +} + +// SetTransform sets a set of Transforms that every position in triangles will be put through. func (c *Canvas) SetTransform(t ...pixel.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 = pixel.NRGBA{R: 1, G: 1, B: 1, A: 1} +// SetColorMask sets a color that every color in triangles or a picture will be multiplied by. +func (c *Canvas) SetColorMask(col color.Color) { + nrgba := pixel.NRGBA{R: 1, G: 1, B: 1, A: 1} + if col != nil { + nrgba = pixel.NRGBAModel.Convert(col).(pixel.NRGBA) } - nrgba := pixel.NRGBAModel.Convert(col).(pixel.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} + c.col = mgl32.Vec4{ + float32(nrgba.R), + float32(nrgba.G), + float32(nrgba.B), + float32(nrgba.A), + } +} + +// SetBounds resizes the Canvas to the new bounds. Old content will be preserved. +func (c *Canvas) SetBounds(bounds pixel.Rect) { + if c.Bounds() == bounds { + return + } + + mainthread.Call(func() { + oldF := c.f + + _, _, w, h := discreteBounds(bounds) + c.f = glhf.NewFrame(w, h, c.smooth) + + // preserve old content + if oldF != nil { + relBounds := c.bounds + relBounds.Pos -= bounds.Pos + ox, oy, ow, oh := discreteBounds(relBounds) + oldF.Blit( + c.f, + ox, oy, ox+ow, oy+oh, + ox, oy, ox+ow, oy+oh, + ) + } + + c.s.Begin() + orig := bounds.Center() + c.s.SetUniformAttr(canvasOrig, mgl32.Vec2{ + float32(orig.X()), + float32(orig.Y()), + }) + c.s.SetUniformAttr(canvasSize, mgl32.Vec2{ + float32(w), + float32(h), + }) + c.s.End() + }) + c.bounds = bounds +} + +// Bounds returns the rectangular bounds of the Canvas. +func (c *Canvas) Bounds() pixel.Rect { + return c.bounds +} + +// SetSmooth sets whether the stretched Pictures drawn onto this Canvas should be drawn smooth or +// pixely. +func (c *Canvas) SetSmooth(smooth bool) { + c.smooth = smooth +} + +// Smooth returns whether the stretched Pictures drawn onto this Canvas are set to be drawn smooth +// or pixely. +func (c *Canvas) Smooth() bool { + return c.smooth +} + +// Clear fill the whole Canvas with a single color. +func (c *Canvas) Clear(color color.Color) { + nrgba := pixel.NRGBAModel.Convert(color).(pixel.NRGBA) + mainthread.CallNonBlock(func() { + c.f.Begin() + glhf.Clear( + float32(nrgba.R), + float32(nrgba.G), + float32(nrgba.B), + float32(nrgba.A), + ) + c.f.End() + }) } type canvasTriangles struct { + *GLTriangles + c *Canvas - *glTriangles } -func (ct *canvasTriangles) Draw() { - // avoid possible race condition - pic := ct.c.pic +func (ct *canvasTriangles) draw(cp *canvasPicture) { + // save the current state vars to avoid race condition 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) + ct.c.s.SetUniformAttr(canvasTransform, mat) + ct.c.s.SetUniformAttr(canvasColorMask, col) - if pic != nil { - pic.Texture().Begin() - ct.glTriangles.Draw() - pic.Texture().End() + if cp == nil { + ct.vs.Begin() + ct.vs.Draw() + ct.vs.End() } else { - ct.glTriangles.Draw() + cp.tex.Begin() + + ct.c.s.SetUniformAttr(canvasTexSize, mgl32.Vec2{ + float32(cp.tex.Width()), + float32(cp.tex.Height()), + }) + + if cp.tex.Smooth() != ct.c.smooth { + cp.tex.SetSmooth(ct.c.smooth) + } + + ct.vs.Begin() + ct.vs.Draw() + ct.vs.End() + + cp.tex.End() } ct.c.s.End() @@ -204,28 +225,64 @@ func (ct *canvasTriangles) Draw() { }) } -const ( - canvasPositionVec2 int = iota - canvasColorVec4 - canvasTextureVec2 -) +func (ct *canvasTriangles) Draw() { + ct.draw(nil) +} -var canvasVertexFormat = glhf.AttrFormat{ - canvasPositionVec2: {Name: "position", Type: glhf.Vec2}, - canvasColorVec4: {Name: "color", Type: glhf.Vec4}, - canvasTextureVec2: {Name: "texture", Type: glhf.Vec2}, +type canvasPicture struct { + tex *glhf.Texture + bounds pixel.Rect + + c *Canvas +} + +func (cp *canvasPicture) Bounds() pixel.Rect { + return cp.bounds +} + +func (cp *canvasPicture) Slice(r pixel.Rect) pixel.Picture { + return &canvasPicture{ + bounds: r, + c: cp.c, + } +} + +func (cp *canvasPicture) Draw(t pixel.TargetTriangles) { + ct := t.(*canvasTriangles) + if cp.c != ct.c { + panic(fmt.Errorf("%T.Draw: TargetTriangles generated by different Canvas", cp)) + } + ct.draw(cp) } const ( - canvasMaskColorVec4 int = iota - canvasTransformMat3 - canvasBoundsVec4 + canvasPosition int = iota + canvasColor + canvasTexture + canvasIntensity +) + +var canvasVertexFormat = glhf.AttrFormat{ + canvasPosition: {Name: "position", Type: glhf.Vec2}, + canvasColor: {Name: "color", Type: glhf.Vec4}, + canvasTexture: {Name: "texture", Type: glhf.Vec2}, + canvasIntensity: {Name: "intensity", Type: glhf.Float}, +} + +const ( + canvasTransform int = iota + canvasColorMask + canvasTexSize + canvasOrig + canvasSize ) var canvasUniformFormat = glhf.AttrFormat{ - {Name: "maskColor", Type: glhf.Vec4}, - {Name: "transform", Type: glhf.Mat3}, - {Name: "bounds", Type: glhf.Vec4}, + canvasTransform: {Name: "transform", Type: glhf.Mat3}, + canvasColorMask: {Name: "colorMask", Type: glhf.Vec4}, + canvasTexSize: {Name: "texSize", Type: glhf.Vec2}, + canvasOrig: {Name: "orig", Type: glhf.Vec2}, + canvasSize: {Name: "size", Type: glhf.Vec2}, } var canvasVertexShader = ` @@ -234,16 +291,23 @@ var canvasVertexShader = ` in vec2 position; in vec4 color; in vec2 texture; +in float intensity; out vec4 Color; out vec2 Texture; +out float Intensity; uniform mat3 transform; +uniform vec2 orig; +uniform vec2 size; void main() { - gl_Position = vec4((transform * vec3(position.x, position.y, 1.0)).xy, 0.0, 1.0); + vec2 transPos = (transform * vec3(position, 1.0)).xy; + vec2 normPos = (transPos - orig) / (size/2); + gl_Position = vec4(normPos, 0.0, 1.0); Color = color; Texture = texture; + Intensity = intensity; } ` @@ -252,23 +316,22 @@ var canvasFragmentShader = ` in vec4 Color; in vec2 Texture; +in float Intensity; out vec4 color; -uniform vec4 maskColor; -uniform vec4 bounds; +uniform vec4 colorMask; +uniform vec2 texSize; uniform sampler2D tex; void main() { - vec2 boundsMin = bounds.xy; - vec2 boundsMax = bounds.zw; - - if (Texture == vec2(-1, -1)) { - color = maskColor * Color; + if (Intensity == 0) { + color = colorMask * 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)); + vec2 t = Texture / texSize; + color = vec4(0, 0, 0, 0); + color += (1 - Intensity) * colorMask * Color; + color += Intensity * colorMask * Color * texture(tex, t); } } ` diff --git a/pixelgl/gltriangles.go b/pixelgl/gltriangles.go index 7957a4a..de3c111 100644 --- a/pixelgl/gltriangles.go +++ b/pixelgl/gltriangles.go @@ -2,7 +2,6 @@ package pixelgl import ( "fmt" - "math" "github.com/faiface/glhf" "github.com/faiface/mainthread" @@ -29,9 +28,9 @@ var ( // // Only draw the Triangles using the provided Shader. func NewGLTriangles(shader *glhf.Shader, t pixel.Triangles) *GLTriangles { - var gt *glTriangles + var gt *GLTriangles mainthread.Call(func() { - gt = &glTriangles{ + gt = &GLTriangles{ vs: glhf.MakeVertexSlice(shader, 0, t.Len()), shader: shader, } @@ -66,7 +65,8 @@ func (gt *GLTriangles) SetLen(len int) { gt.data = append(gt.data, 0, 0, 1, 1, 1, 1, - float32(math.Inf(+1)), float32(math.Inf(+1)), + 0, 0, + 0, ) } } @@ -77,7 +77,7 @@ func (gt *GLTriangles) SetLen(len int) { // Slice returns a sub-Triangles of this GLTriangles in range [i, j). func (gt *GLTriangles) Slice(i, j int) pixel.Triangles { - return &glTriangles{ + return &GLTriangles{ vs: gt.vs.Slice(i, j), data: gt.data[i*gt.vs.Stride() : j*gt.vs.Stride()], shader: gt.shader, @@ -86,7 +86,7 @@ func (gt *GLTriangles) Slice(i, j int) pixel.Triangles { func (gt *GLTriangles) updateData(t pixel.Triangles) { // glTriangles short path - if t, ok := t.(*glTriangles); ok { + if t, ok := t.(*GLTriangles); ok { copy(gt.data, t.data) return } @@ -98,6 +98,7 @@ func (gt *GLTriangles) updateData(t pixel.Triangles) { px, py = (*t)[i].Position.XY() col = (*t)[i].Color tx, ty = (*t)[i].Picture.XY() + in = (*t)[i].Intensity ) gt.data[i*gt.vs.Stride()+0] = float32(px) gt.data[i*gt.vs.Stride()+1] = float32(py) @@ -107,6 +108,7 @@ func (gt *GLTriangles) updateData(t pixel.Triangles) { gt.data[i*gt.vs.Stride()+5] = float32(col.A) gt.data[i*gt.vs.Stride()+6] = float32(tx) gt.data[i*gt.vs.Stride()+7] = float32(ty) + gt.data[i*gt.vs.Stride()+8] = float32(in) } return } @@ -129,15 +131,16 @@ func (gt *GLTriangles) updateData(t pixel.Triangles) { } if t, ok := t.(pixel.TrianglesPicture); ok { for i := 0; i < gt.Len(); i++ { - tx, ty := t.Picture(i).XY() - gt.data[i*gt.vs.Stride()+6] = float32(tx) - gt.data[i*gt.vs.Stride()+7] = float32(ty) + pic, intensity := t.Picture(i) + gt.data[i*gt.vs.Stride()+6] = float32(pic.X()) + gt.data[i*gt.vs.Stride()+7] = float32(pic.Y()) + gt.data[i*gt.vs.Stride()+8] = float32(intensity) } } } func (gt *GLTriangles) submitData() { - data := gt.data // avoid race condition + data := append([]float32{}, gt.data...) // avoid race condition mainthread.CallNonBlock(func() { gt.vs.Begin() dataLen := len(data) / gt.vs.Stride() @@ -187,8 +190,9 @@ func (gt *GLTriangles) Color(i int) pixel.NRGBA { } // Picture returns the Picture property of the i-th vertex. -func (gt *GLTriangles) Picture(i int) pixel.Vec { +func (gt *GLTriangles) Picture(i int) (pic pixel.Vec, intensity float64) { tx := gt.data[i*gt.vs.Stride()+6] ty := gt.data[i*gt.vs.Stride()+7] - return pixel.V(float64(tx), float64(ty)) + intensity = float64(gt.data[i*gt.vs.Stride()+8]) + return pixel.V(float64(tx), float64(ty)), intensity } diff --git a/pixelgl/util.go b/pixelgl/util.go index 1c760df..94138ee 100644 --- a/pixelgl/util.go +++ b/pixelgl/util.go @@ -1,31 +1,12 @@ package pixelgl import ( + "math" + "github.com/faiface/pixel" "github.com/go-gl/mathgl/mgl32" ) -func clamp(x, low, high float64) float64 { - if x < low { - return low - } - if x > high { - return high - } - return x -} - -func lerp(x float64, a, b pixel.Vec) pixel.Vec { - return a.Scaled(1-x) + b.Scaled(x) -} - -func lerp2d(x, a, b pixel.Vec) pixel.Vec { - return pixel.V( - lerp(x.X(), a, b).X(), - lerp(x.Y(), a, b).Y(), - ) -} - func transformToMat(t ...pixel.Transform) mgl32.Mat3 { mat := mgl32.Ident3() for i := range t { @@ -34,10 +15,10 @@ func transformToMat(t ...pixel.Transform) mgl32.Mat3 { return mat } -func pictureBounds(p *pixel.GLPicture, v pixel.Vec) pixel.Vec { - w, h := float64(p.Texture().Width()), float64(p.Texture().Height()) - a := p.Bounds().Pos - b := p.Bounds().Pos + p.Bounds().Size - u := lerp2d(v, a, b) - return pixel.V(u.X()/w, u.Y()/h) +func discreteBounds(bounds pixel.Rect) (x, y, w, h int) { + x0 := int(math.Floor(bounds.Pos.X())) + y0 := int(math.Floor(bounds.Pos.Y())) + x1 := int(math.Ceil(bounds.Pos.X() + bounds.Size.X())) + y1 := int(math.Ceil(bounds.Pos.Y() + bounds.Size.Y())) + return x0, y0, x1 - x0, y1 - y0 } diff --git a/pixelgl/window.go b/pixelgl/window.go index 82d010a..5cd63ec 100644 --- a/pixelgl/window.go +++ b/pixelgl/window.go @@ -2,7 +2,7 @@ package pixelgl import ( "image/color" - + "math" "runtime" "github.com/faiface/glhf" @@ -16,16 +16,13 @@ import ( // chosen in such a way, that you usually only need to set a few of them - defaults (zeros) should // usually be sensible. // -// Note that you always need to set the width and the height of a window. +// Note that you always need to set the Bounds of the Window. type WindowConfig struct { - // Title at the top of a window. + // Title at the top of the Window Title string - // Width of a window in pixels. - Width float64 - - // Height of a window in pixels. - Height float64 + // Bounds specify the bounds of the Window in pixels. + Bounds pixel.Rect // If set to nil, a window will be windowed. Otherwise it will be fullscreen on the // specified monitor. @@ -46,23 +43,22 @@ type WindowConfig struct { // Whether a window is maximized. Maximized bool - // VSync (vertical synchronization) synchronizes window's framerate with the framerate - // of the monitor. + // VSync (vertical synchronization) synchronizes window's framerate with the framerate of + // the monitor. VSync bool - // Number of samples for multi-sample anti-aliasing (edge-smoothing). Usual values - // are 0, 2, 4, 8 (powers of 2 and not much more than this). + // Number of samples for multi-sample anti-aliasing (edge-smoothing). Usual values are 0, 2, + // 4, 8 (powers of 2 and not much more than this). MSAASamples int } // Window is a window handler. Use this type to manipulate a window (input, drawing, ...). type Window struct { window *glfw.Window - config WindowConfig - canvas *Canvas - canvasVs *glhf.VertexSlice - shader *glhf.Shader + bounds pixel.Rect + canvas *Canvas + vsync bool // need to save these to correctly restore a fullscreen window restore struct { @@ -80,13 +76,15 @@ var currentWindow *Window // NewWindow creates a new Window with it's properties specified in the provided config. // // If Window creation fails, an error is returned (e.g. due to unavailable graphics device). -func NewWindow(config WindowConfig) (*Window, error) { +func NewWindow(cfg WindowConfig) (*Window, error) { bool2int := map[bool]int{ true: glfw.True, false: glfw.False, } - w := &Window{config: config} + w := &Window{ + bounds: cfg.Bounds, + } err := mainthread.CallErr(func() error { var err error @@ -96,21 +94,22 @@ func NewWindow(config WindowConfig) (*Window, error) { glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True) - glfw.WindowHint(glfw.Resizable, bool2int[config.Resizable]) - glfw.WindowHint(glfw.Visible, bool2int[!config.Hidden]) - glfw.WindowHint(glfw.Decorated, bool2int[!config.Undecorated]) - glfw.WindowHint(glfw.Focused, bool2int[!config.Unfocused]) - glfw.WindowHint(glfw.Maximized, bool2int[config.Maximized]) - glfw.WindowHint(glfw.Samples, config.MSAASamples) + glfw.WindowHint(glfw.Resizable, bool2int[cfg.Resizable]) + glfw.WindowHint(glfw.Visible, bool2int[!cfg.Hidden]) + glfw.WindowHint(glfw.Decorated, bool2int[!cfg.Undecorated]) + glfw.WindowHint(glfw.Focused, bool2int[!cfg.Unfocused]) + glfw.WindowHint(glfw.Maximized, bool2int[cfg.Maximized]) + glfw.WindowHint(glfw.Samples, cfg.MSAASamples) var share *glfw.Window if currentWindow != nil { share = currentWindow.window } + _, _, width, height := discreteBounds(cfg.Bounds) w.window, err = glfw.CreateWindow( - int(config.Width), - int(config.Height), - config.Title, + width, + height, + cfg.Title, nil, share, ) @@ -122,38 +121,18 @@ func NewWindow(config WindowConfig) (*Window, error) { w.begin() w.end() - w.shader, err = glhf.NewShader( - windowVertexFormat, - windowUniformFormat, - windowVertexShader, - windowFragmentShader, - ) - if err != nil { - return err - } - - w.canvasVs = glhf.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 { return nil, errors.Wrap(err, "creating window failed") } - w.initInput() - w.SetMonitor(config.Fullscreen) + w.SetVSync(cfg.VSync) - w.canvas = NewCanvas(config.Width, config.Height, false) + w.initInput() + w.SetMonitor(cfg.Fullscreen) + + w.canvas = NewCanvas(cfg.Bounds, false) w.Update() runtime.SetFinalizer(w, (*Window).Destroy) @@ -168,29 +147,40 @@ func (w *Window) Destroy() { }) } -// Clear clears the Window with a color. -func (w *Window) Clear(c color.Color) { - w.canvas.Clear(c) -} - // Update swaps buffers and polls events. func (w *Window) Update() { - w.canvas.SetSize(w.Size()) + mainthread.Call(func() { + wi, hi := w.window.GetSize() + w.bounds.Size = pixel.V(float64(wi), float64(hi)) + // fractional positions end up covering more pixels with less size + if w.bounds.X() != math.Floor(w.bounds.X()) { + w.bounds.Size -= pixel.V(1, 0) + } + if w.bounds.Y() != math.Floor(w.bounds.Y()) { + w.bounds.Size -= pixel.V(0, 1) + } + }) + + w.canvas.SetBounds(w.Bounds()) mainthread.Call(func() { w.begin() - glhf.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() + glhf.Bounds(0, 0, w.canvas.f.Width(), w.canvas.f.Height()) - if w.config.VSync { + glhf.Clear(0, 0, 0, 0) + w.canvas.f.Begin() + w.canvas.f.Blit( + nil, + 0, 0, w.canvas.f.Width(), w.canvas.f.Height(), + 0, 0, w.canvas.f.Width(), w.canvas.f.Height(), + ) + w.canvas.f.End() + + if w.vsync { glfw.SwapInterval(1) + } else { + glfw.SwapInterval(0) } w.window.SwapBuffers() w.end() @@ -225,22 +215,19 @@ func (w *Window) SetTitle(title string) { }) } -// SetSize resizes the client area of the Window to the specified size in pixels. In case of a -// fullscreen Window, it changes the resolution of that Window. -func (w *Window) SetSize(width, height float64) { +// SetBounds sets the bounds of the Window in pixels. Bounds can be fractional, but the size will be +// changed in the next Update to a real possible size of the Window. +func (w *Window) SetBounds(bounds pixel.Rect) { + w.bounds = bounds mainthread.Call(func() { - w.window.SetSize(int(width), int(height)) + _, _, width, height := discreteBounds(bounds) + w.window.SetSize(width, height) }) } -// Size returns the size of the client area of the Window (the part you can draw on). -func (w *Window) Size() (width, height float64) { - mainthread.Call(func() { - wi, hi := w.window.GetSize() - width = float64(wi) - height = float64(hi) - }) - return width, height +// Bounds returns the current bounds of the Window. +func (w *Window) Bounds() pixel.Rect { + return w.bounds } // Show makes the Window visible if it was hidden. @@ -351,6 +338,16 @@ func (w *Window) Restore() { }) } +// SetVSync sets whether the Window should synchronize with the monitor refresh rate. +func (w *Window) SetVSync(vsync bool) { + w.vsync = vsync +} + +// VSync returns whether the Window is set to synchronize with the monitor refresh rate. +func (w *Window) VSync() bool { + return w.vsync +} + // Note: must be called inside the main thread. func (w *Window) begin() { if currentWindow != w { @@ -365,7 +362,7 @@ func (w *Window) end() { // nothing, really } -// MakeTriangles generates a specialized copy of the supplied triangles that will draw onto this +// MakeTriangles generates a specialized copy of the supplied Triangles that will draw onto this // Window. // // Window supports TrianglesPosition, TrianglesColor and TrianglesTexture. @@ -373,9 +370,11 @@ func (w *Window) MakeTriangles(t pixel.Triangles) pixel.TargetTriangles { return w.canvas.MakeTriangles(t) } -// SetPicture sets a Picture that will be used in subsequent drawings onto the Window. -func (w *Window) SetPicture(p *pixel.GLPicture) { - w.canvas.SetPicture(p) +// MakePicture generates a specialized copy of the supplied Picture that will draw onto this Window. +// +// Window support PictureColor. +func (w *Window) MakePicture(p pixel.Picture) pixel.TargetPicture { + return w.canvas.MakePicture(p) } // SetTransform sets a global transformation matrix for the Window. @@ -385,47 +384,24 @@ func (w *Window) SetTransform(t ...pixel.Transform) { w.canvas.SetTransform(t...) } -// SetMaskColor sets a global mask color for the Window. -func (w *Window) SetMaskColor(c color.Color) { - w.canvas.SetMaskColor(c) +// SetColorMask sets a global color mask for the Window. +func (w *Window) SetColorMask(c color.Color) { + w.canvas.SetColorMask(c) } -const ( - windowPositionVec2 = iota - windowTextureVec2 -) - -var windowVertexFormat = glhf.AttrFormat{ - windowPositionVec2: {Name: "position", Type: glhf.Vec2}, - windowTextureVec2: {Name: "texture", Type: glhf.Vec2}, +// SetSmooth sets whether the stretched Pictures drawn onto this Window should be drawn smooth or +// pixely. +func (w *Window) SetSmooth(smooth bool) { + w.canvas.SetSmooth(smooth) } -var windowUniformFormat = glhf.AttrFormat{} - -var windowVertexShader = ` -#version 330 core - -in vec2 position; -in vec2 texture; - -out vec2 Texture; - -void main() { - gl_Position = vec4(position, 0.0, 1.0); - Texture = texture; +// Smooth returns whether the stretched Pictures drawn onto this Window are set to be drawn smooth +// or pixely. +func (w *Window) Smooth() bool { + return w.canvas.Smooth() } -` -var windowFragmentShader = ` -#version 330 core - -in vec2 Texture; - -out vec4 color; - -uniform sampler2D tex; - -void main() { - color = texture(tex, Texture); +// Clear clears the Window with a color. +func (w *Window) Clear(c color.Color) { + w.canvas.Clear(c) } -`