create imgui_backend module, add more error handling to platform

This commit is contained in:
Alex Yatskov 2018-12-27 18:28:03 -08:00
parent 5ceb63b86d
commit dd39ca8b8d
2 changed files with 99 additions and 95 deletions

View File

@ -1,49 +1,30 @@
package platform 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"
) )
type imguiSdl2Gl2 struct { var (
renderer *rendererGl2 imguiIsInit bool
platform *platformSdl2 imguiButtonsDown [3]bool
} imguiLastTime uint64
imguiFontTexture uint32
imguiContext *imgui.Context
)
func newImguiSdl2Gl2(window *sdl.Window) *imguiSdl2Gl2 { func Init() error {
return &imguiSdl2Gl2{ if imguiIsInit {
renderer: newRendererGl2(window), return errors.New("imgui backend is already initialized")
platform: newPlatformSdl2(window),
} }
}
func (impl *imguiSdl2Gl2) NewFrame() { imguiContext = imgui.CreateContext(nil)
impl.platform.newFrame()
impl.renderer.newFrame()
}
func (impl *imguiSdl2Gl2) Render(drawData imgui.DrawData) {
impl.renderer.render(drawData)
}
func (impl *imguiSdl2Gl2) ProcessEvent(event sdl.Event) bool {
return impl.platform.processEvent(event)
}
func (impl *imguiSdl2Gl2) Shutdown() {
impl.renderer.shutdown()
}
type platformSdl2 struct {
window *sdl.Window
time uint64
buttonsDown [3]bool
}
func newPlatformSdl2(window *sdl.Window) *platformSdl2 {
keys := map[int]int{ keys := map[int]int{
imgui.KeyTab: sdl.SCANCODE_TAB, imgui.KeyTab: sdl.SCANCODE_TAB,
imgui.KeyLeftArrow: sdl.SCANCODE_LEFT, imgui.KeyLeftArrow: sdl.SCANCODE_LEFT,
@ -74,34 +55,58 @@ func newPlatformSdl2(window *sdl.Window) *platformSdl2 {
io.KeyMap(imguiKey, nativeKey) io.KeyMap(imguiKey, nativeKey)
} }
return &platformSdl2{window: window} imguiFontTexture = createFontTexture()
imguiIsInit = true
return nil
} }
func (plat *platformSdl2) newFrame() { func Shutdown() error {
io := imgui.CurrentIO() if !imguiIsInit {
return errors.New("imgui backend was not initialized")
}
imguiIsInit = false
destroyFontTexture(imguiFontTexture)
imguiFontTexture = 0
imguiContext.Destroy()
imguiContext = nil
return nil
}
func NewFrame(windowSize math.Vec2i) error {
if !imguiIsInit {
return errors.New("imgui backend was not initialized")
}
// Setup display size (every frame to accommodate for window resizing) // Setup display size (every frame to accommodate for window resizing)
windowWidth, windowHeight := plat.window.GetSize() io := imgui.CurrentIO()
io.SetDisplaySize(imgui.Vec2{X: float32(windowWidth), Y: float32(windowHeight)}) io.SetDisplaySize(imgui.Vec2{X: float32(windowSize.X), Y: float32(windowSize.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 plat.time > 0 { if imguiLastTime > 0 {
io.SetDeltaTime(float32(currentTime-plat.time) / float32(frequency)) io.SetDeltaTime(float32(currentTime-imguiLastTime) / float32(frequency))
} else { } else {
io.SetDeltaTime(1.0 / 60.0) io.SetDeltaTime(1.0 / 60.0)
} }
plat.time = currentTime imguiLastTime = 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, plat.buttonsDown[i] || (state&sdl.Button(button)) != 0) io.SetMouseButtonDown(i, imguiButtonsDown[i] || (state&sdl.Button(button)) != 0)
plat.buttonsDown[i] = false imguiButtonsDown[i] = false
} }
io.SetMousePosition(imgui.Vec2{X: float32(x), Y: float32(y)}) io.SetMousePosition(imgui.Vec2{X: float32(x), Y: float32(y)})
imgui.NewFrame()
return nil
} }
// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
@ -109,7 +114,11 @@ func (plat *platformSdl2) newFrame() {
// - 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 (plat *platformSdl2) processEvent(event sdl.Event) bool { func ProcessEvent(event sdl.Event) (bool, error) {
if !imguiIsInit {
return false, errors.New("imgui backend was not initialized")
}
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)
@ -124,25 +133,25 @@ func (plat *platformSdl2) processEvent(event sdl.Event) bool {
} else if wheelEvent.Y < 0 { } else if wheelEvent.Y < 0 {
deltaY-- deltaY--
} }
return true return true, nil
case sdl.MOUSEBUTTONDOWN: case sdl.MOUSEBUTTONDOWN:
buttonEvent := event.(*sdl.MouseButtonEvent) buttonEvent := event.(*sdl.MouseButtonEvent)
switch buttonEvent.Button { switch buttonEvent.Button {
case sdl.BUTTON_LEFT: case sdl.BUTTON_LEFT:
plat.buttonsDown[0] = true imguiButtonsDown[0] = true
break break
case sdl.BUTTON_RIGHT: case sdl.BUTTON_RIGHT:
plat.buttonsDown[1] = true imguiButtonsDown[1] = true
break break
case sdl.BUTTON_MIDDLE: case sdl.BUTTON_MIDDLE:
plat.buttonsDown[2] = true imguiButtonsDown[2] = true
break break
} }
return true return true, nil
case sdl.TEXTINPUT: case sdl.TEXTINPUT:
inputEvent := event.(*sdl.TextInputEvent) inputEvent := event.(*sdl.TextInputEvent)
io.AddInputCharacters(string(inputEvent.Text[:])) io.AddInputCharacters(string(inputEvent.Text[:]))
return true return true, nil
case sdl.KEYDOWN: case sdl.KEYDOWN:
keyEvent := event.(*sdl.KeyboardEvent) keyEvent := event.(*sdl.KeyboardEvent)
io.KeyPress(int(keyEvent.Keysym.Scancode)) io.KeyPress(int(keyEvent.Keysym.Scancode))
@ -157,36 +166,23 @@ func (plat *platformSdl2) processEvent(event sdl.Event) bool {
io.KeyShift(modState&sdl.KMOD_LSHIFT, modState&sdl.KMOD_RSHIFT) io.KeyShift(modState&sdl.KMOD_LSHIFT, modState&sdl.KMOD_RSHIFT)
io.KeyCtrl(modState&sdl.KMOD_LCTRL, modState&sdl.KMOD_RCTRL) io.KeyCtrl(modState&sdl.KMOD_LCTRL, modState&sdl.KMOD_RCTRL)
io.KeyAlt(modState&sdl.KMOD_LALT, modState&sdl.KMOD_RALT) io.KeyAlt(modState&sdl.KMOD_LALT, modState&sdl.KMOD_RALT)
return true return true, nil
} }
return false return false, nil
}
type rendererGl2 struct {
window *sdl.Window
context *sdl.GLContext
fontTexture uint32
}
func newRendererGl2(window *sdl.Window) *rendererGl2 {
return &rendererGl2{window: window}
}
func (rend *rendererGl2) newFrame() {
if rend.fontTexture == 0 {
rend.createFontsTexture()
}
imgui.NewFrame()
} }
// OpenGL2 Render function. // OpenGL2 Render function.
// Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state 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 state explicitly, in order to be able to run within any OpenGL engine that doesn't do so.
func (rend *rendererGl2) render(drawData imgui.DrawData) { func Render(windowSize, fbSize math.Vec2i, drawData imgui.DrawData) error {
displayWidth, displayHeight := rend.window.GetSize() if !imguiIsInit {
fbWidth, fbHeight := rend.window.GLGetDrawableSize() return errors.New("imgui backend was not initialized")
drawData.ScaleClipRects(imgui.Vec2{X: float32(fbWidth) / float32(displayWidth), Y: float32(fbHeight) / float32(displayHeight)}) }
drawData.ScaleClipRects(imgui.Vec2{
X: float32(fbSize.X) / float32(windowSize.X),
Y: float32(fbSize.Y) / float32(windowSize.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!
// Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, vertex/texcoord/color pointers, polygon fill. // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, vertex/texcoord/color pointers, polygon fill.
@ -217,11 +213,11 @@ func (rend *rendererGl2) render(drawData imgui.DrawData) {
// 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(fbWidth), int32(fbHeight)) gl.Viewport(0, 0, int32(fbSize.X), int32(fbSize.Y))
gl.MatrixMode(gl.PROJECTION) gl.MatrixMode(gl.PROJECTION)
gl.PushMatrix() gl.PushMatrix()
gl.LoadIdentity() gl.LoadIdentity()
gl.Ortho(0, float64(displayWidth), float64(displayHeight), 0, -1, 1) gl.Ortho(0, float64(windowSize.X), float64(windowSize.Y), 0, -1, 1)
gl.MatrixMode(gl.MODELVIEW) gl.MatrixMode(gl.MODELVIEW)
gl.PushMatrix() gl.PushMatrix()
gl.LoadIdentity() gl.LoadIdentity()
@ -249,7 +245,7 @@ func (rend *rendererGl2) render(drawData imgui.DrawData) {
command.CallUserCallback(commandList) command.CallUserCallback(commandList)
} else { } else {
clipRect := command.ClipRect() clipRect := command.ClipRect()
gl.Scissor(int32(clipRect.X), int32(fbHeight)-int32(clipRect.W), int32(clipRect.Z-clipRect.X), int32(clipRect.W-clipRect.Y)) gl.Scissor(int32(clipRect.X), int32(fbSize.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))
} }
@ -272,13 +268,11 @@ func (rend *rendererGl2) render(drawData imgui.DrawData) {
gl.PolygonMode(gl.BACK, uint32(lastPolygonMode[1])) gl.PolygonMode(gl.BACK, uint32(lastPolygonMode[1]))
gl.Viewport(lastViewport[0], lastViewport[1], lastViewport[2], lastViewport[3]) gl.Viewport(lastViewport[0], lastViewport[1], lastViewport[2], lastViewport[3])
gl.Scissor(lastScissorBox[0], lastScissorBox[1], lastScissorBox[2], lastScissorBox[3]) gl.Scissor(lastScissorBox[0], lastScissorBox[1], lastScissorBox[2], lastScissorBox[3])
return nil
} }
func (rend *rendererGl2) shutdown() { func createFontTexture() uint32 {
rend.destroyFontsTexture()
}
func (rend *rendererGl2) createFontsTexture() {
// Build texture atlas // Build texture atlas
io := imgui.CurrentIO() io := imgui.CurrentIO()
image := io.Fonts().TextureDataRGBA32() image := io.Fonts().TextureDataRGBA32()
@ -286,24 +280,25 @@ func (rend *rendererGl2) createFontsTexture() {
// Upload texture to graphics system // Upload texture to graphics system
var lastTexture int32 var lastTexture int32
gl.GetIntegerv(gl.TEXTURE_BINDING_2D, &lastTexture) gl.GetIntegerv(gl.TEXTURE_BINDING_2D, &lastTexture)
gl.GenTextures(1, &rend.fontTexture) var fontTexture uint32
gl.BindTexture(gl.TEXTURE_2D, rend.fontTexture) 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_MIN_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.PixelStorei(gl.UNPACK_ROW_LENGTH, 0) 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) 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 // Store our identifier
io.Fonts().SetTextureID(imgui.TextureID(rend.fontTexture)) io.Fonts().SetTextureID(imgui.TextureID(fontTexture))
// Restore state // Restore state
gl.BindTexture(gl.TEXTURE_2D, uint32(lastTexture)) gl.BindTexture(gl.TEXTURE_2D, uint32(lastTexture))
return fontTexture
} }
func (rend *rendererGl2) destroyFontsTexture() { func destroyFontTexture(fontTexture uint32) {
if rend.fontTexture != 0 { if fontTexture != 0 {
gl.DeleteTextures(1, &rend.fontTexture) gl.DeleteTextures(1, &fontTexture)
imgui.CurrentIO().Fonts().SetTextureID(0) imgui.CurrentIO().Fonts().SetTextureID(0)
rend.fontTexture = 0
} }
} }

View File

@ -5,14 +5,12 @@ import (
"runtime" "runtime"
"time" "time"
"github.com/FooSoft/imgui-go"
"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 ( var (
platformIsInit bool platformIsInit bool
platformImguiContext *imgui.Context
platformWindows []Window platformWindows []Window
) )
@ -31,13 +29,16 @@ func Init() error {
return err return err
} }
platformImguiContext = imgui.CreateContext(nil) platformIsInit = true
return nil return nil
} }
func ProcessEvents() error { func ProcessEvents() error {
var terminate bool if !platformIsInit {
return errors.New("platform was not initialized")
}
var terminate bool
for !terminate { for !terminate {
for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() { for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
switch event.(type) { switch event.(type) {
@ -65,14 +66,22 @@ func Shutdown() error {
} }
platformWindows = nil platformWindows = nil
platformIsInit = false
return nil return nil
} }
func CreateWindow(title string, width, height int) (Window, error) { func CreateWindow(title string, width, height int) (Window, error) {
if !platformIsInit {
return nil, errors.New("platform was not initialized")
}
window, err := newWindow(title, width, height) window, err := newWindow(title, width, height)
if err != nil { if err != nil {
return nil, err return nil, err
} }
platformWindows = append(platformWindows, window)
return window, err return window, err
} }