imgui cleanup

This commit is contained in:
Alex Yatskov 2018-12-31 13:10:58 -08:00
parent 6c398bce8e
commit 2a22e7a446
4 changed files with 169 additions and 171 deletions

View File

@ -0,0 +1,104 @@
package imgui_backend
import (
imgui "github.com/FooSoft/imgui-go"
"github.com/FooSoft/lazarus/math"
"github.com/go-gl/gl/v2.1/gl"
"github.com/veandco/go-sdl2/sdl"
)
type Context struct {
buttonsDown [3]bool
lastTime uint64
fontTexture uint32
displaySize math.Vec2i
bufferSize math.Vec2i
}
func CreateContext(displaySize, bufferSize math.Vec2i) (*Context, error) {
singleton.refCount++
if singleton.refCount == 1 {
singleton.context = imgui.CreateContext(nil)
keys := map[int]int{
imgui.KeyTab: sdl.SCANCODE_TAB,
imgui.KeyLeftArrow: sdl.SCANCODE_LEFT,
imgui.KeyRightArrow: sdl.SCANCODE_RIGHT,
imgui.KeyUpArrow: sdl.SCANCODE_UP,
imgui.KeyDownArrow: sdl.SCANCODE_DOWN,
imgui.KeyPageUp: sdl.SCANCODE_PAGEUP,
imgui.KeyPageDown: sdl.SCANCODE_PAGEDOWN,
imgui.KeyHome: sdl.SCANCODE_HOME,
imgui.KeyEnd: sdl.SCANCODE_END,
imgui.KeyInsert: sdl.SCANCODE_INSERT,
imgui.KeyDelete: sdl.SCANCODE_DELETE,
imgui.KeyBackspace: sdl.SCANCODE_BACKSPACE,
imgui.KeySpace: sdl.SCANCODE_BACKSPACE,
imgui.KeyEnter: sdl.SCANCODE_RETURN,
imgui.KeyEscape: sdl.SCANCODE_ESCAPE,
imgui.KeyA: sdl.SCANCODE_A,
imgui.KeyC: sdl.SCANCODE_C,
imgui.KeyV: sdl.SCANCODE_V,
imgui.KeyX: sdl.SCANCODE_X,
imgui.KeyY: sdl.SCANCODE_Y,
imgui.KeyZ: sdl.SCANCODE_Z,
}
// Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array.
io := imgui.CurrentIO()
for imguiKey, nativeKey := range keys {
io.KeyMap(imguiKey, nativeKey)
}
}
c := &Context{displaySize: displaySize, bufferSize: bufferSize}
// Build texture atlas
io := imgui.CurrentIO()
image := io.Fonts().TextureDataRGBA32()
// Store state
var lastTexture int32
gl.GetIntegerv(gl.TEXTURE_BINDING_2D, &lastTexture)
// Create texture
gl.GenTextures(1, &c.fontTexture)
gl.BindTexture(gl.TEXTURE_2D, c.fontTexture)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.PixelStorei(gl.UNPACK_ROW_LENGTH, 0)
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, int32(image.Width), int32(image.Height), 0, gl.RGBA, gl.UNSIGNED_BYTE, image.Pixels)
// Restore state
gl.BindTexture(gl.TEXTURE_2D, uint32(lastTexture))
// Store texture identifier
io.Fonts().SetTextureID(imgui.TextureID(c.fontTexture))
return c, nil
}
func (c *Context) SetDisplaySize(displaySize math.Vec2i) {
c.displaySize = displaySize
}
func (c *Context) SetBufferSize(bufferSize math.Vec2i) {
c.bufferSize = bufferSize
}
func (c *Context) Destroy() error {
if c == nil || c.fontTexture == 0 {
return nil
}
gl.DeleteTextures(1, &c.fontTexture)
imgui.CurrentIO().Fonts().SetTextureID(0)
c.fontTexture = 0
singleton.refCount--
if singleton.refCount == 0 {
singleton.context.Destroy()
singleton.context = nil
}
return nil
}

View File

@ -1,120 +1,41 @@
package imgui_backend package imgui_backend
import ( import (
"errors"
"unsafe" "unsafe"
"github.com/FooSoft/imgui-go" "github.com/FooSoft/imgui-go"
"github.com/FooSoft/lazarus/math"
"github.com/go-gl/gl/v2.1/gl" "github.com/go-gl/gl/v2.1/gl"
"github.com/veandco/go-sdl2/sdl" "github.com/veandco/go-sdl2/sdl"
) )
var (
ErrAlreadyInit = errors.New("imgui backend is already initialized")
ErrWasNotInit = errors.New("imgui backend was not initialized")
)
var singleton struct { var singleton struct {
isInit bool
buttonsDown [3]bool
lastTime uint64
fontTexture uint32
context *imgui.Context context *imgui.Context
refCount int
windowSize math.Vec2i
bufferSize math.Vec2i
} }
func Init() error { func (c *Context) BeginFrame() error {
if singleton.isInit {
return ErrAlreadyInit
}
singleton.context = imgui.CreateContext(nil)
keys := map[int]int{
imgui.KeyTab: sdl.SCANCODE_TAB,
imgui.KeyLeftArrow: sdl.SCANCODE_LEFT,
imgui.KeyRightArrow: sdl.SCANCODE_RIGHT,
imgui.KeyUpArrow: sdl.SCANCODE_UP,
imgui.KeyDownArrow: sdl.SCANCODE_DOWN,
imgui.KeyPageUp: sdl.SCANCODE_PAGEUP,
imgui.KeyPageDown: sdl.SCANCODE_PAGEDOWN,
imgui.KeyHome: sdl.SCANCODE_HOME,
imgui.KeyEnd: sdl.SCANCODE_END,
imgui.KeyInsert: sdl.SCANCODE_INSERT,
imgui.KeyDelete: sdl.SCANCODE_DELETE,
imgui.KeyBackspace: sdl.SCANCODE_BACKSPACE,
imgui.KeySpace: sdl.SCANCODE_BACKSPACE,
imgui.KeyEnter: sdl.SCANCODE_RETURN,
imgui.KeyEscape: sdl.SCANCODE_ESCAPE,
imgui.KeyA: sdl.SCANCODE_A,
imgui.KeyC: sdl.SCANCODE_C,
imgui.KeyV: sdl.SCANCODE_V,
imgui.KeyX: sdl.SCANCODE_X,
imgui.KeyY: sdl.SCANCODE_Y,
imgui.KeyZ: sdl.SCANCODE_Z,
}
// Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array.
io := imgui.CurrentIO()
for imguiKey, nativeKey := range keys {
io.KeyMap(imguiKey, nativeKey)
}
singleton.isInit = true
return nil
}
func Shutdown() error {
if !singleton.isInit {
return ErrWasNotInit
}
singleton.isInit = false
destroyFontTexture(singleton.fontTexture)
singleton.fontTexture = 0
singleton.context.Destroy()
singleton.context = nil
return nil
}
func BeginFrame(windowSize, bufferSize math.Vec2i) error {
if !singleton.isInit {
return ErrWasNotInit
}
singleton.windowSize = windowSize
singleton.bufferSize = bufferSize
if singleton.fontTexture == 0 {
singleton.fontTexture = createFontTexture()
}
// Setup display size (every frame to accommodate for window resizing) // Setup display size (every frame to accommodate for window resizing)
io := imgui.CurrentIO() io := imgui.CurrentIO()
io.SetDisplaySize(imgui.Vec2{X: float32(windowSize.X), Y: float32(windowSize.Y)}) io.SetDisplaySize(imgui.Vec2{
X: float32(c.displaySize.X),
Y: float32(c.displaySize.Y),
})
// Setup time step (we don't use SDL_GetTicks() because it is using millisecond resolution) // Setup time step (we don't use SDL_GetTicks() because it is using millisecond resolution)
frequency := sdl.GetPerformanceFrequency() frequency := sdl.GetPerformanceFrequency()
currentTime := sdl.GetPerformanceCounter() currentTime := sdl.GetPerformanceCounter()
if singleton.lastTime > 0 { if c.lastTime > 0 {
io.SetDeltaTime(float32(currentTime-singleton.lastTime) / float32(frequency)) io.SetDeltaTime(float32(currentTime-c.lastTime) / float32(frequency))
} else { } else {
io.SetDeltaTime(1.0 / 60.0) io.SetDeltaTime(1.0 / 60.0)
} }
singleton.lastTime = currentTime c.lastTime = currentTime
// If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame. // If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame.
x, y, state := sdl.GetMouseState() x, y, state := sdl.GetMouseState()
for i, button := range []uint32{sdl.BUTTON_LEFT, sdl.BUTTON_RIGHT, sdl.BUTTON_MIDDLE} { for i, button := range []uint32{sdl.BUTTON_LEFT, sdl.BUTTON_RIGHT, sdl.BUTTON_MIDDLE} {
io.SetMouseButtonDown(i, singleton.buttonsDown[i] || (state&sdl.Button(button)) != 0) io.SetMouseButtonDown(i, c.buttonsDown[i] || (state&sdl.Button(button)) != 0)
singleton.buttonsDown[i] = false c.buttonsDown[i] = false
} }
io.SetMousePosition(imgui.Vec2{X: float32(x), Y: float32(y)}) io.SetMousePosition(imgui.Vec2{X: float32(x), Y: float32(y)})
@ -128,11 +49,7 @@ func BeginFrame(windowSize, bufferSize math.Vec2i) error {
// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application. // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application.
// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
// If you have multiple SDL events and some of them are not meant to be used by dear imgui, you may need to filter events based on their windowID field. // If you have multiple SDL events and some of them are not meant to be used by dear imgui, you may need to filter events based on their windowID field.
func ProcessEvent(event sdl.Event) (bool, error) { func (c *Context) ProcessEvent(event sdl.Event) (bool, error) {
if !singleton.isInit {
return false, ErrWasNotInit
}
switch io := imgui.CurrentIO(); event.GetType() { switch io := imgui.CurrentIO(); event.GetType() {
case sdl.MOUSEWHEEL: case sdl.MOUSEWHEEL:
wheelEvent := event.(*sdl.MouseWheelEvent) wheelEvent := event.(*sdl.MouseWheelEvent)
@ -152,13 +69,13 @@ func ProcessEvent(event sdl.Event) (bool, error) {
buttonEvent := event.(*sdl.MouseButtonEvent) buttonEvent := event.(*sdl.MouseButtonEvent)
switch buttonEvent.Button { switch buttonEvent.Button {
case sdl.BUTTON_LEFT: case sdl.BUTTON_LEFT:
singleton.buttonsDown[0] = true c.buttonsDown[0] = true
break break
case sdl.BUTTON_RIGHT: case sdl.BUTTON_RIGHT:
singleton.buttonsDown[1] = true c.buttonsDown[1] = true
break break
case sdl.BUTTON_MIDDLE: case sdl.BUTTON_MIDDLE:
singleton.buttonsDown[2] = true c.buttonsDown[2] = true
break break
} }
return true, nil return true, nil
@ -188,16 +105,12 @@ func ProcessEvent(event sdl.Event) (bool, error) {
// OpenGL2 Render function. // OpenGL2 Render function.
// Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL singleton explicitly, in order to be able to run within any OpenGL engine that doesn't do so. // Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL singleton explicitly, in order to be able to run within any OpenGL engine that doesn't do so.
func EndFrame() error { func (c *Context) EndFrame() error {
if !singleton.isInit {
return ErrWasNotInit
}
imgui.Render() imgui.Render()
drawData := imgui.RenderedDrawData() drawData := imgui.RenderedDrawData()
drawData.ScaleClipRects(imgui.Vec2{ drawData.ScaleClipRects(imgui.Vec2{
X: float32(singleton.bufferSize.X) / float32(singleton.windowSize.X), X: float32(c.bufferSize.X) / float32(c.displaySize.X),
Y: float32(singleton.bufferSize.Y) / float32(singleton.windowSize.Y), Y: float32(c.bufferSize.Y) / float32(c.displaySize.Y),
}) })
// We are using the OpenGL fixed pipeline to make the example code simpler to read! // We are using the OpenGL fixed pipeline to make the example code simpler to read!
@ -229,11 +142,11 @@ func EndFrame() error {
// Setup viewport, orthographic projection matrix // Setup viewport, orthographic projection matrix
// Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayMin is typically (0,0) for single viewport apps. // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayMin is typically (0,0) for single viewport apps.
gl.Viewport(0, 0, int32(singleton.bufferSize.X), int32(singleton.bufferSize.Y)) gl.Viewport(0, 0, int32(c.bufferSize.X), int32(c.bufferSize.Y))
gl.MatrixMode(gl.PROJECTION) gl.MatrixMode(gl.PROJECTION)
gl.PushMatrix() gl.PushMatrix()
gl.LoadIdentity() gl.LoadIdentity()
gl.Ortho(0, float64(singleton.windowSize.X), float64(singleton.windowSize.Y), 0, -1, 1) gl.Ortho(0, float64(c.displaySize.X), float64(c.displaySize.Y), 0, -1, 1)
gl.MatrixMode(gl.MODELVIEW) gl.MatrixMode(gl.MODELVIEW)
gl.PushMatrix() gl.PushMatrix()
gl.LoadIdentity() gl.LoadIdentity()
@ -261,7 +174,7 @@ func EndFrame() error {
command.CallUserCallback(commandList) command.CallUserCallback(commandList)
} else { } else {
clipRect := command.ClipRect() clipRect := command.ClipRect()
gl.Scissor(int32(clipRect.X), int32(singleton.bufferSize.Y)-int32(clipRect.W), int32(clipRect.Z-clipRect.X), int32(clipRect.W-clipRect.Y)) gl.Scissor(int32(clipRect.X), int32(c.bufferSize.Y)-int32(clipRect.W), int32(clipRect.Z-clipRect.X), int32(clipRect.W-clipRect.Y))
gl.BindTexture(gl.TEXTURE_2D, uint32(command.TextureID())) gl.BindTexture(gl.TEXTURE_2D, uint32(command.TextureID()))
gl.DrawElements(gl.TRIANGLES, int32(command.ElementCount()), uint32(drawType), unsafe.Pointer(indexBufferOffset)) gl.DrawElements(gl.TRIANGLES, int32(command.ElementCount()), uint32(drawType), unsafe.Pointer(indexBufferOffset))
} }
@ -287,34 +200,3 @@ func EndFrame() error {
return nil return nil
} }
func createFontTexture() uint32 {
// Build texture atlas
io := imgui.CurrentIO()
image := io.Fonts().TextureDataRGBA32()
// Upload texture to graphics system
var lastTexture int32
gl.GetIntegerv(gl.TEXTURE_BINDING_2D, &lastTexture)
var fontTexture uint32
gl.GenTextures(1, &fontTexture)
gl.BindTexture(gl.TEXTURE_2D, fontTexture)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.PixelStorei(gl.UNPACK_ROW_LENGTH, 0)
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, int32(image.Width), int32(image.Height), 0, gl.RGBA, gl.UNSIGNED_BYTE, image.Pixels)
// Store our identifier
io.Fonts().SetTextureID(imgui.TextureID(fontTexture))
// Restore state
gl.BindTexture(gl.TEXTURE_2D, uint32(lastTexture))
return fontTexture
}
func destroyFontTexture(fontTexture uint32) {
if fontTexture != 0 {
gl.DeleteTextures(1, &fontTexture)
imgui.CurrentIO().Fonts().SetTextureID(0)
}
}

View File

@ -5,7 +5,6 @@ import (
"runtime" "runtime"
"time" "time"
"github.com/FooSoft/lazarus/platform/imgui_backend"
"github.com/go-gl/gl/v2.1/gl" "github.com/go-gl/gl/v2.1/gl"
"github.com/veandco/go-sdl2/sdl" "github.com/veandco/go-sdl2/sdl"
) )
@ -43,10 +42,6 @@ func Init() error {
return err return err
} }
if err := imgui_backend.Init(); err != nil {
return err
}
state.isInit = true state.isInit = true
return nil return nil
} }
@ -62,10 +57,6 @@ func Shutdown() error {
} }
} }
if err := imgui_backend.Shutdown(); err != nil {
return err
}
state.windows = nil state.windows = nil
state.isInit = false state.isInit = false
@ -115,15 +106,17 @@ func advanceWindows() {
} }
func processEvent(event sdl.Event) bool { func processEvent(event sdl.Event) bool {
handled, _ := imgui_backend.ProcessEvent(event) for _, window := range state.windows {
handled, _ := window.processEvent(event)
if handled { if handled {
return true return true
} }
}
switch event.(type) { switch event.(type) {
case *sdl.QuitEvent: case *sdl.QuitEvent:
return false return false
} default:
return true return true
}
} }

View File

@ -10,10 +10,15 @@ import (
type Window struct { type Window struct {
sdlWindow *sdl.Window sdlWindow *sdl.Window
sdlGlContext sdl.GLContext sdlGlContext sdl.GLContext
imguiContext *imgui_backend.Context
scene Scene scene Scene
} }
func newWindow(title string, width, height int, scene Scene) (*Window, error) { func newWindow(title string, width, height int, scene Scene) (*Window, error) {
sdl.GLSetAttribute(sdl.GL_CONTEXT_MAJOR_VERSION, 2)
sdl.GLSetAttribute(sdl.GL_CONTEXT_MINOR_VERSION, 1)
sdl.GLSetAttribute(sdl.GL_DOUBLEBUFFER, 1)
sdlWindow, err := sdl.CreateWindow( sdlWindow, err := sdl.CreateWindow(
title, title,
sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED,
@ -32,11 +37,18 @@ func newWindow(title string, width, height int, scene Scene) (*Window, error) {
return nil, err return nil, err
} }
sdl.GLSetAttribute(sdl.GL_CONTEXT_MAJOR_VERSION, 2) w := &Window{
sdl.GLSetAttribute(sdl.GL_CONTEXT_MINOR_VERSION, 1) sdlWindow: sdlWindow,
sdl.GLSetAttribute(sdl.GL_DOUBLEBUFFER, 1) sdlGlContext: sdlGlContext,
scene: scene,
}
w.imguiContext, err = imgui_backend.CreateContext(w.DisplaySize(), w.BufferSize())
if err != nil {
w.Destroy()
return nil, err
}
w := &Window{sdlWindow, sdlGlContext, scene}
if err := scene.Init(w); err != nil { if err := scene.Init(w); err != nil {
w.Destroy() w.Destroy()
return nil, err return nil, err
@ -46,7 +58,7 @@ func newWindow(title string, width, height int, scene Scene) (*Window, error) {
} }
func (w *Window) Destroy() error { func (w *Window) Destroy() error {
if w.sdlWindow == nil { if w == nil || w.sdlWindow == nil {
return nil return nil
} }
@ -92,12 +104,25 @@ func (w *Window) RenderTexture(texture *Texture, position math.Vec2i) {
gl.End() gl.End()
} }
func (w *Window) DisplaySize() math.Vec2i {
width, height := w.sdlWindow.GetSize()
return math.Vec2i{X: int(width), Y: int(height)}
}
func (w *Window) BufferSize() math.Vec2i {
width, height := w.sdlWindow.GLGetDrawableSize()
return math.Vec2i{X: int(width), Y: int(height)}
}
func (w *Window) advance() { func (w *Window) advance() {
w.sdlWindow.GLMakeCurrent(w.sdlGlContext) w.sdlWindow.GLMakeCurrent(w.sdlGlContext)
displaySize := w.displaySize() displaySize := w.DisplaySize()
bufferSize := w.bufferSize() w.imguiContext.SetDisplaySize(displaySize)
imgui_backend.BeginFrame(displaySize, bufferSize) bufferSize := w.BufferSize()
w.imguiContext.SetBufferSize(bufferSize)
w.imguiContext.BeginFrame()
gl.Viewport(0, 0, int32(displaySize.X), int32(displaySize.Y)) gl.Viewport(0, 0, int32(displaySize.X), int32(displaySize.Y))
gl.Clear(gl.COLOR_BUFFER_BIT) gl.Clear(gl.COLOR_BUFFER_BIT)
@ -109,16 +134,10 @@ func (w *Window) advance() {
w.scene.Advance(w) w.scene.Advance(w)
imgui_backend.EndFrame() w.imguiContext.EndFrame()
w.sdlWindow.GLSwap() w.sdlWindow.GLSwap()
} }
func (w *Window) displaySize() math.Vec2i { func (w *Window) processEvent(event sdl.Event) (bool, error) {
width, height := w.sdlWindow.GetSize() return w.imguiContext.ProcessEvent(event)
return math.Vec2i{X: int(width), Y: int(height)}
}
func (w *Window) bufferSize() math.Vec2i {
width, height := w.sdlWindow.GLGetDrawableSize()
return math.Vec2i{X: int(width), Y: int(height)}
} }