From 52a3a96d20ca6dde3f8140fdfe14b1a43193fabb Mon Sep 17 00:00:00 2001 From: faiface <faiface@ksp.sk> Date: Fri, 25 Nov 2016 22:49:56 +0100 Subject: [PATCH] replace BeginEnder with Doer and migrate everything --- graphics.go | 58 +++++++++++++ pixelgl/interface.go | 46 +++++------ pixelgl/shader.go | 170 +++++++++++++++++++------------------ pixelgl/texture.go | 67 ++++++++------- pixelgl/vertex.go | 100 +++++++++++----------- window.go | 193 +++++++++++++++++++++---------------------- 6 files changed, 344 insertions(+), 290 deletions(-) create mode 100644 graphics.go diff --git a/graphics.go b/graphics.go new file mode 100644 index 0000000..4381671 --- /dev/null +++ b/graphics.go @@ -0,0 +1,58 @@ +package pixel + +import "github.com/faiface/pixel/pixelgl" + +// Warning: technical stuff below. + +// VertexFormat is an internal format of the OpenGL vertex data. +// +// You can actually change this and all of the Pixel's functions will use the new format. +// Only change when you're implementing an OpenGL effect or something similar. +var VertexFormat = DefaultVertexFormat() + +// DefaultVertexFormat returns the default vertex format used by Pixel. +func DefaultVertexFormat() pixelgl.VertexFormat { + return pixelgl.VertexFormat{ + {Purpose: pixelgl.Position, Size: 2}, + {Purpose: pixelgl.Color, Size: 4}, + {Purpose: pixelgl.TexCoord, Size: 2}, + } +} + +// ConvertVertexData converts data in the oldFormat to the newFormat. Vertex attributes in the new format +// will be copied from the corresponding vertex attributes in the old format. If a vertex attribute in the new format +// has no corresponding attribute in the old format, it will be filled with zeros. +func ConvertVertexData(oldFormat, newFormat pixelgl.VertexFormat, data []float64) []float64 { + // calculate the mapping between old and new format + // if i is a start of a vertex attribute in the new format, then mapping[i] returns + // the index where the same attribute starts in the old format + mapping := make(map[int]int) + i := 0 + for _, newAttr := range newFormat { + j := 0 + for _, oldAttr := range oldFormat { + if newAttr == oldAttr { + mapping[i] = j + break + } + j += oldAttr.Size + } + i += newAttr.Size + } + + oldData, newData := data, []float64{} + + for i := 0; i < len(oldData); i += oldFormat.Size() { + j := 0 + for _, attr := range newFormat { + if oldIndex, ok := mapping[j]; ok { // the attribute was found in the old format + newData = append(newData, oldData[i+oldIndex:i+oldIndex+attr.Size]...) + } else { // the attribute wasn't found in the old format, so fill with zeros + newData = append(newData, make([]float64, attr.Size)...) + } + j += attr.Size + } + } + + return newData +} diff --git a/pixelgl/interface.go b/pixelgl/interface.go index b82542f..cb140a4 100644 --- a/pixelgl/interface.go +++ b/pixelgl/interface.go @@ -1,34 +1,28 @@ package pixelgl -// BeginEnder is an interface for manipulating OpenGL state. +// Doer is an interface for manipulating OpenGL state. // -// OpenGL is a state machine and as such, it is natural to manipulate it in a begin-end manner. -// This interface is intended for all OpenGL objects, that can begin being active and end being active -// such as windows, vertex arrays, vertex buffers, textures, shaders, pretty much everything. +// OpenGL is a state machine. Every object can 'enter' it's state and 'leave' it's state. For example, +// you can bind a buffer and unbind a buffer, bind a texture and unbind it, use shader and unuse it, and so on. // -// It might seem natural to use BeginEnders this way: +// This interface provides a clever and flexible way to do it. A typical workflow of an OpenGL object is that +// you enter (load, bind) that object's state, then do something with it, and then leave the state. That 'something' +// in between, let's call it sub (as in subroutine). // -// window.Begin() -// shader.Begin() -// texture.Begin() -// vertexarray.Begin() -// vertexarray.Draw() -// vertexarray.End() -// texture.End() -// shader.End() -// window.End() +// The recommended way to implement a Doer is to wrap another Doer (vertex array wrap texture and so on), let's call +// it parent. Then the Do method will look like this: // -// Don't do this! A better practice is to make a BeginEnder so that it wraps another BeginEnder like this: +// func (o *MyObject) Do(sub func()) { +// o.parent.Do(func() { +// // enter the object's state +// sub() +// // leave the object's state +// }) +// } // -// shader := NewShader(window) -// texture := NewTexture(shader) -// vertexarray := NewVertexArray(texture) -// // now, somewhere else in your code, instead of calling numerous Begin/Ends, you just call -// vertexarray.Draw() -// -// The final single call to draw a vertex array executes all of the Begins and Ends, because the objects are -// wrapped around each other. -type BeginEnder interface { - Begin() - End() +// It might seem difficult to grasp this kind of recursion at first, but it's really simple. What it's basically saying +// is: "Hey parent, enter your state, then let me enter mine, then I'll do whatever I'm supposed to do in the middle. +// After that I'll leave my state and please leave your state too parent." +type Doer interface { + Do(sub func()) } diff --git a/pixelgl/shader.go b/pixelgl/shader.go index 1cdc766..e3c9bc1 100644 --- a/pixelgl/shader.go +++ b/pixelgl/shader.go @@ -9,95 +9,104 @@ import ( // Shader is an OpenGL shader program. type Shader struct { - parent BeginEnder + parent Doer program uint32 } // NewShader creates a new shader program from the specified vertex shader and fragment shader sources. // // Note that vertexShader and fragmentShader parameters must contain the source code, they're not filenames. -func NewShader(parent BeginEnder, vertexShader, fragmentShader string) (*Shader, error) { - parent.Begin() - defer parent.End() - +func NewShader(parent Doer, vertexShader, fragmentShader string) (*Shader, error) { shader := &Shader{ parent: parent, } - err, glerr := DoErrGLErr(func() error { - var vshader, fshader uint32 - - // vertex shader - { - vshader = gl.CreateShader(gl.VERTEX_SHADER) - src, free := gl.Strs(vertexShader) - defer free() - length := int32(len(vertexShader)) - gl.ShaderSource(vshader, 1, src, &length) - gl.CompileShader(vshader) - - var ( - success int32 - infoLog = make([]byte, 512) - ) - gl.GetShaderiv(vshader, gl.COMPILE_STATUS, &success) - if success == 0 { - gl.GetShaderInfoLog(vshader, int32(len(infoLog)), nil, &infoLog[0]) - return fmt.Errorf("error compiling vertex shader: %s", string(infoLog)) + + errChan := make(chan error, 1) + parent.Do(func() { + err, glerr := DoErrGLErr(func() error { + var vshader, fshader uint32 + + // vertex shader + { + vshader = gl.CreateShader(gl.VERTEX_SHADER) + src, free := gl.Strs(vertexShader) + defer free() + length := int32(len(vertexShader)) + gl.ShaderSource(vshader, 1, src, &length) + gl.CompileShader(vshader) + + var ( + success int32 + infoLog = make([]byte, 512) + ) + gl.GetShaderiv(vshader, gl.COMPILE_STATUS, &success) + if success == 0 { + gl.GetShaderInfoLog(vshader, int32(len(infoLog)), nil, &infoLog[0]) + return fmt.Errorf("error compiling vertex shader: %s", string(infoLog)) + } } - } - // fragment shader - { - fshader = gl.CreateShader(gl.FRAGMENT_SHADER) - src, free := gl.Strs(fragmentShader) - defer free() - length := int32(len(fragmentShader)) - gl.ShaderSource(fshader, 1, src, &length) - gl.CompileShader(fshader) - - var ( - success int32 - infoLog = make([]byte, 512) - ) - gl.GetShaderiv(fshader, gl.COMPILE_STATUS, &success) - if success == 0 { - gl.GetShaderInfoLog(fshader, int32(len(infoLog)), nil, &infoLog[0]) - return fmt.Errorf("error compiling fragment shader: %s", string(infoLog)) + // fragment shader + { + fshader = gl.CreateShader(gl.FRAGMENT_SHADER) + src, free := gl.Strs(fragmentShader) + defer free() + length := int32(len(fragmentShader)) + gl.ShaderSource(fshader, 1, src, &length) + gl.CompileShader(fshader) + + var ( + success int32 + infoLog = make([]byte, 512) + ) + gl.GetShaderiv(fshader, gl.COMPILE_STATUS, &success) + if success == 0 { + gl.GetShaderInfoLog(fshader, int32(len(infoLog)), nil, &infoLog[0]) + return fmt.Errorf("error compiling fragment shader: %s", string(infoLog)) + } } - } - // shader program - { - shader.program = gl.CreateProgram() - gl.AttachShader(shader.program, vshader) - gl.AttachShader(shader.program, fshader) - gl.LinkProgram(shader.program) - - var ( - success int32 - infoLog = make([]byte, 512) - ) - gl.GetProgramiv(shader.program, gl.LINK_STATUS, &success) - if success == 0 { - gl.GetProgramInfoLog(shader.program, int32(len(infoLog)), nil, &infoLog[0]) - return fmt.Errorf("error linking shader program: %s", string(infoLog)) + // shader program + { + shader.program = gl.CreateProgram() + gl.AttachShader(shader.program, vshader) + gl.AttachShader(shader.program, fshader) + gl.LinkProgram(shader.program) + + var ( + success int32 + infoLog = make([]byte, 512) + ) + gl.GetProgramiv(shader.program, gl.LINK_STATUS, &success) + if success == 0 { + gl.GetProgramInfoLog(shader.program, int32(len(infoLog)), nil, &infoLog[0]) + return fmt.Errorf("error linking shader program: %s", string(infoLog)) + } } - } - gl.DeleteShader(vshader) - gl.DeleteShader(fshader) + gl.DeleteShader(vshader) + gl.DeleteShader(fshader) - return nil - }) - if err != nil { + return nil + }) + if err != nil { + if glerr != nil { + err = errors.Wrap(glerr, err.Error()) + } + errChan <- err + return + } if glerr != nil { - err = errors.Wrap(glerr, err.Error()) + errChan <- err + return } + errChan <- nil + }) + err := <-errChan + if err != nil { return nil, err } - if glerr != nil { - return nil, glerr - } + return shader, nil } @@ -108,18 +117,15 @@ func (s *Shader) Delete() { }) } -// Begin starts using a shader program. -func (s *Shader) Begin() { - s.parent.Begin() - DoNoBlock(func() { - gl.UseProgram(s.program) - }) -} - -// End stops using a shader program. -func (s *Shader) End() { - DoNoBlock(func() { - gl.UseProgram(0) +// Do stars using a shader, executes sub, and stops using it. +func (s *Shader) Do(sub func()) { + s.parent.Do(func() { + DoNoBlock(func() { + gl.UseProgram(s.program) + }) + sub() + DoNoBlock(func() { + gl.UseProgram(0) + }) }) - s.parent.End() } diff --git a/pixelgl/texture.go b/pixelgl/texture.go index f948ada..35ec841 100644 --- a/pixelgl/texture.go +++ b/pixelgl/texture.go @@ -7,37 +7,39 @@ import ( // Texture is an OpenGL texture. type Texture struct { - parent BeginEnder + parent Doer tex uint32 } // NewTexture creates a new texture with the specified width and height. // The pixels must be a sequence of RGBA values. -func NewTexture(parent BeginEnder, width, height int, pixels []uint8) (*Texture, error) { - parent.Begin() - defer parent.End() - +func NewTexture(parent Doer, width, height int, pixels []uint8) (*Texture, error) { texture := &Texture{parent: parent} - err := DoGLErr(func() { - gl.GenTextures(1, &texture.tex) - gl.BindTexture(gl.TEXTURE_2D, texture.tex) + errChan := make(chan error, 1) + parent.Do(func() { + errChan <- DoGLErr(func() { + gl.GenTextures(1, &texture.tex) + gl.BindTexture(gl.TEXTURE_2D, texture.tex) + + gl.TexImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + int32(width), + int32(height), + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + gl.Ptr(pixels), + ) - gl.TexImage2D( - gl.TEXTURE_2D, - 0, - gl.RGBA, - int32(width), - int32(height), - 0, - gl.RGBA, - gl.UNSIGNED_BYTE, - gl.Ptr(pixels), - ) - gl.GenerateMipmap(gl.TEXTURE_2D) + gl.GenerateMipmap(gl.TEXTURE_2D) - gl.BindTexture(gl.TEXTURE_2D, 0) + gl.BindTexture(gl.TEXTURE_2D, 0) + }) }) + err := <-errChan if err != nil { return nil, errors.Wrap(err, "failed to create a texture") } @@ -52,18 +54,15 @@ func (t *Texture) Delete() { }) } -// Begin binds a texture. -func (t *Texture) Begin() { - t.parent.Begin() - DoNoBlock(func() { - gl.BindTexture(gl.TEXTURE_2D, t.tex) - }) -} - -// End unbinds a texture. -func (t *Texture) End() { - DoNoBlock(func() { - gl.BindTexture(gl.TEXTURE_2D, 0) +// Do bind a texture, executes sub, and unbinds the texture. +func (t *Texture) Do(sub func()) { + t.parent.Do(func() { + DoNoBlock(func() { + gl.BindTexture(gl.TEXTURE_2D, t.tex) + }) + sub() + DoNoBlock(func() { + gl.BindTexture(gl.TEXTURE_2D, 0) + }) }) - t.parent.End() } diff --git a/pixelgl/vertex.go b/pixelgl/vertex.go index ec0d535..8dde67b 100644 --- a/pixelgl/vertex.go +++ b/pixelgl/vertex.go @@ -90,7 +90,7 @@ const ( // VertexArray is an OpenGL vertex array object that also holds it's own vertex buffer object. // From the user's points of view, VertexArray is an array of vertices that can be drawn. type VertexArray struct { - parent BeginEnder + parent Doer format VertexFormat vao uint32 vbo uint32 @@ -99,44 +99,50 @@ type VertexArray struct { } // NewVertexArray creates a new vertex array and wraps another BeginEnder around it. -func NewVertexArray(parent BeginEnder, format VertexFormat, mode VertexDrawMode, usage VertexUsage, data []float64) (*VertexArray, error) { - parent.Begin() - defer parent.End() - +func NewVertexArray(parent Doer, format VertexFormat, mode VertexDrawMode, usage VertexUsage, data []float64) (*VertexArray, error) { va := &VertexArray{ parent: parent, format: format, mode: mode, } - err := DoGLErr(func() { - gl.GenVertexArrays(1, &va.vao) - gl.BindVertexArray(va.vao) - - gl.GenBuffers(1, &va.vbo) - gl.BindBuffer(gl.ARRAY_BUFFER, va.vbo) - gl.BufferData(gl.ARRAY_BUFFER, 8*len(data), gl.Ptr(data), uint32(usage)) - - stride := format.Size() - va.count = len(data) / stride - - offset := 0 - for i, attr := range format { - gl.VertexAttribPointer( - uint32(i), - int32(attr.Size), - gl.DOUBLE, - false, - int32(8*stride), - gl.PtrOffset(8*offset), - ) - gl.EnableVertexAttribArray(uint32(i)) - offset += attr.Size + errChan := make(chan error, 1) + parent.Do(func() { + err := DoGLErr(func() { + gl.GenVertexArrays(1, &va.vao) + gl.BindVertexArray(va.vao) + + gl.GenBuffers(1, &va.vbo) + gl.BindBuffer(gl.ARRAY_BUFFER, va.vbo) + gl.BufferData(gl.ARRAY_BUFFER, 8*len(data), gl.Ptr(data), uint32(usage)) + + stride := format.Size() + va.count = len(data) / stride + + offset := 0 + for i, attr := range format { + gl.VertexAttribPointer( + uint32(i), + int32(attr.Size), + gl.DOUBLE, + false, + int32(8*stride), + gl.PtrOffset(8*offset), + ) + gl.EnableVertexAttribArray(uint32(i)) + offset += attr.Size + } + + gl.BindBuffer(gl.ARRAY_BUFFER, 0) + gl.BindVertexArray(0) + }) + if err != nil { + errChan <- err + return } - - gl.BindBuffer(gl.ARRAY_BUFFER, 0) - gl.BindVertexArray(0) + errChan <- nil }) + err := <-errChan if err != nil { return nil, errors.Wrap(err, "failed to create a vertex array") } @@ -176,8 +182,7 @@ func (va *VertexArray) DrawMode() VertexDrawMode { // Draw draws a vertex array. func (va *VertexArray) Draw() { - va.Begin() - va.End() + va.Do(func() {}) } // Data returns a copy of data inside a vertex array (actually it's vertex buffer). @@ -207,21 +212,18 @@ func (va *VertexArray) UpdateData(offset int, data []float64) { }) } -// Begin binds a vertex array and it's associated vertex buffer. -func (va *VertexArray) Begin() { - va.parent.Begin() - DoNoBlock(func() { - gl.BindVertexArray(va.vao) - gl.BindBuffer(gl.ARRAY_BUFFER, va.vbo) - }) -} - -// End draws a vertex array and unbinds it alongside with it's associated vertex buffer. -func (va *VertexArray) End() { - DoNoBlock(func() { - gl.DrawArrays(uint32(va.mode), 0, int32(va.count)) - gl.BindBuffer(gl.ARRAY_BUFFER, 0) - gl.BindVertexArray(0) +// Do binds a vertex arrray and it's associated vertex buffer, executes sub, and unbinds the vertex array and it's vertex buffer. +func (va *VertexArray) Do(sub func()) { + va.parent.Do(func() { + DoNoBlock(func() { + gl.BindVertexArray(va.vao) + gl.BindBuffer(gl.ARRAY_BUFFER, va.vbo) + }) + sub() + DoNoBlock(func() { + gl.DrawArrays(uint32(va.mode), 0, int32(va.count)) + gl.BindBuffer(gl.ARRAY_BUFFER, 0) + gl.BindVertexArray(0) + }) }) - va.parent.End() } diff --git a/window.go b/window.go index 1699eb8..723c05d 100644 --- a/window.go +++ b/window.go @@ -104,79 +104,79 @@ func NewWindow(config WindowConfig) (*Window, error) { // Delete destroys a window. The window can't be used any further. func (w *Window) Delete() { - w.Begin() - defer w.End() - pixelgl.Do(func() { - w.window.Destroy() + w.Do(func() { + pixelgl.Do(func() { + w.window.Destroy() + }) }) } // Clear clears the window with a color. func (w *Window) Clear(c color.Color) { - w.Begin() - defer w.End() - pixelgl.Clear(colorToRGBA(c)) + w.Do(func() { + pixelgl.Clear(colorToRGBA(c)) + }) } // Update swaps buffers and polls events. func (w *Window) Update() { - w.Begin() - defer w.End() - pixelgl.Do(func() { - if w.config.VSync { - glfw.SwapInterval(1) - } - w.window.SwapBuffers() - glfw.PollEvents() + w.Do(func() { + pixelgl.Do(func() { + if w.config.VSync { + glfw.SwapInterval(1) + } + w.window.SwapBuffers() + glfw.PollEvents() + }) }) } // SetTitle changes the title of a window. func (w *Window) SetTitle(title string) { - w.Begin() - defer w.End() - pixelgl.Do(func() { - w.window.SetTitle(title) + w.Do(func() { + pixelgl.Do(func() { + w.window.SetTitle(title) + }) }) } // SetSize resizes a 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) { - w.Begin() - defer w.End() - pixelgl.Do(func() { - w.window.SetSize(int(width), int(height)) + w.Do(func() { + pixelgl.Do(func() { + w.window.SetSize(int(width), int(height)) + }) }) } // Size returns the size of the client area of a window (the part you can draw on). func (w *Window) Size() (width, height float64) { - w.Begin() - defer w.End() - pixelgl.Do(func() { - wi, hi := w.window.GetSize() - width = float64(wi) - height = float64(hi) + w.Do(func() { + pixelgl.Do(func() { + wi, hi := w.window.GetSize() + width = float64(wi) + height = float64(hi) + }) }) return width, height } // Show makes a window visible if it was hidden. func (w *Window) Show() { - w.Begin() - defer w.End() - pixelgl.Do(func() { - w.window.Show() + w.Do(func() { + pixelgl.Do(func() { + w.window.Show() + }) }) } // Hide hides a window if it was visible. func (w *Window) Hide() { - w.Begin() - defer w.End() - pixelgl.Do(func() { - w.window.Hide() + w.Do(func() { + pixelgl.Do(func() { + w.window.Hide() + }) }) } @@ -187,37 +187,35 @@ func (w *Window) Hide() { func (w *Window) SetFullscreen(monitor *Monitor) { if w.Monitor() != monitor { if monitor == nil { - w.Begin() - defer w.End() - - pixelgl.Do(func() { - w.window.SetMonitor( - nil, - w.restore.xpos, - w.restore.ypos, - w.restore.width, - w.restore.height, - 0, - ) + w.Do(func() { + pixelgl.Do(func() { + w.window.SetMonitor( + nil, + w.restore.xpos, + w.restore.ypos, + w.restore.width, + w.restore.height, + 0, + ) + }) }) } else { - w.Begin() - defer w.End() - - pixelgl.Do(func() { - w.restore.xpos, w.restore.ypos = w.window.GetPos() - w.restore.width, w.restore.height = w.window.GetSize() - - width, height := monitor.Size() - refreshRate := monitor.RefreshRate() - w.window.SetMonitor( - monitor.monitor, - 0, - 0, - int(width), - int(height), - int(refreshRate), - ) + w.Do(func() { + pixelgl.Do(func() { + w.restore.xpos, w.restore.ypos = w.window.GetPos() + w.restore.width, w.restore.height = w.window.GetSize() + + width, height := monitor.Size() + refreshRate := monitor.RefreshRate() + w.window.SetMonitor( + monitor.monitor, + 0, + 0, + int(width), + int(height), + int(refreshRate), + ) + }) }) } } @@ -230,12 +228,12 @@ func (w *Window) IsFullscreen() bool { // Monitor returns a monitor a fullscreen window is on. If the window is not fullscreen, this function returns nil. func (w *Window) Monitor() *Monitor { - w.Begin() - defer w.End() - - monitor := pixelgl.DoVal(func() interface{} { - return w.window.GetMonitor() - }).(*glfw.Monitor) + var monitor *glfw.Monitor + w.Do(func() { + monitor = pixelgl.DoVal(func() interface{} { + return w.window.GetMonitor() + }).(*glfw.Monitor) + }) if monitor == nil { return nil } @@ -246,37 +244,39 @@ func (w *Window) Monitor() *Monitor { // Focus brings a window to the front and sets input focus. func (w *Window) Focus() { - w.Begin() - defer w.End() - pixelgl.Do(func() { - w.window.Focus() + w.Do(func() { + pixelgl.Do(func() { + w.window.Focus() + }) }) } // Focused returns true if a window has input focus. func (w *Window) Focused() bool { - w.Begin() - defer w.End() - return pixelgl.DoVal(func() interface{} { - return w.window.GetAttrib(glfw.Focused) == glfw.True - }).(bool) + var focused bool + w.Do(func() { + focused = pixelgl.DoVal(func() interface{} { + return w.window.GetAttrib(glfw.Focused) == glfw.True + }).(bool) + }) + return focused } // Maximize puts a windowed window to a maximized state. func (w *Window) Maximize() { - w.Begin() - defer w.End() - pixelgl.Do(func() { - w.window.Maximize() + w.Do(func() { + pixelgl.Do(func() { + w.window.Maximize() + }) }) } // Restore restores a windowed window from a maximized state. func (w *Window) Restore() { - w.Begin() - defer w.End() - pixelgl.Do(func() { - w.window.Restore() + w.Do(func() { + pixelgl.Do(func() { + w.window.Restore() + }) }) } @@ -285,11 +285,11 @@ var currentWindow struct { handler *Window } -// Begin makes the context of this window current. -// -// Note that you only need to use this function if you're designing a low-level technical plugin (such as an effect). -func (w *Window) Begin() { +// Do makes the context of this window current, if it's not already, and executes sub. +func (w *Window) Do(sub func()) { currentWindow.Lock() + defer currentWindow.Unlock() + if currentWindow.handler != w { pixelgl.Do(func() { w.window.MakeContextCurrent() @@ -297,11 +297,6 @@ func (w *Window) Begin() { }) currentWindow.handler = w } -} -// End makes it possible for other windows to make their context current. -// -// Note that you only need to use this function if you're designing a low-level technical plugin (such as an effect). -func (w *Window) End() { - currentWindow.Unlock() + sub() } -- GitLab