Compare commits

...

6 Commits

Author SHA1 Message Date
01f06cce1e Handle relative mouse mode mouse pos for imgui 2024-04-11 22:12:33 +04:00
20ed804d2a Correctly handle imgui mouse/keyboard capture 2024-04-11 22:07:38 +04:00
80ce6d60d2 Proper support for zero handles 2023-10-09 05:03:48 +04:00
c998fc26ce Avoid deprecated gl funcs+Improve imgui with srgb 2023-10-08 04:03:54 +04:00
81b515197d Properly working MSAA and SRGB :D 2023-10-08 03:20:56 +04:00
d703a5270c x8 MSAA 2023-10-07 11:28:59 +04:00
8 changed files with 212 additions and 67 deletions

View File

@ -238,7 +238,12 @@ func LoadTextureJpeg(file string, loadOptions *TextureLoadOptions) (Texture, err
return tex, nil
}
func LoadCubemapTextures(rightTex, leftTex, topTex, botTex, frontTex, backTex string) (Cubemap, error) {
// LoadCubemapTextures only supports the 'TextureIsSrgba' option
func LoadCubemapTextures(rightTex, leftTex, topTex, botTex, frontTex, backTex string, loadOptions *TextureLoadOptions) (Cubemap, error) {
if loadOptions == nil {
loadOptions = &TextureLoadOptions{}
}
var imgDecoder func(r io.Reader) (image.Image, error)
ext := strings.ToLower(path.Ext(rightTex))
@ -283,7 +288,12 @@ func LoadCubemapTextures(rightTex, leftTex, topTex, botTex, frontTex, backTex st
height := int32(nrgbaImg.Bounds().Dy())
width := int32(nrgbaImg.Bounds().Dx())
gl.TexImage2D(uint32(gl.TEXTURE_CUBE_MAP_POSITIVE_X)+i, 0, gl.RGBA8, int32(width), int32(height), 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&nrgbaImg.Pix[0]))
internalFormat := int32(gl.RGBA8)
if loadOptions.TextureIsSrgba {
internalFormat = gl.SRGB_ALPHA
}
gl.TexImage2D(uint32(gl.TEXTURE_CUBE_MAP_POSITIVE_X)+i, 0, internalFormat, int32(width), int32(height), 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&nrgbaImg.Pix[0]))
}
// set the texture wrapping/filtering options (on the currently bound texture object)

View File

@ -15,6 +15,13 @@ import (
var (
isInited = false
isSdlButtonLeftDown = false
isSdlButtonMiddleDown = false
isSdlButtonRightDown = false
ImguiRelativeMouseModePosX float32
ImguiRelativeMouseModePosY float32
)
type Window struct {
@ -29,7 +36,9 @@ func (w *Window) handleInputs() {
input.EventLoopStart()
imIo := imgui.CurrentIO()
// @TODO: Would be nice to have imgui package process its own events via a callback instead of it being part of engine code
imguiCaptureMouse := imIo.WantCaptureMouse()
imguiCaptureKeyboard := imIo.WantCaptureKeyboard()
for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
//Fire callbacks
@ -42,14 +51,18 @@ func (w *Window) handleInputs() {
case *sdl.MouseWheelEvent:
input.HandleMouseWheelEvent(e)
if !imguiCaptureMouse {
input.HandleMouseWheelEvent(e)
}
xDelta, yDelta := input.GetMouseWheelMotion()
imIo.AddMouseWheelDelta(float32(xDelta), float32(yDelta))
imIo.AddMouseWheelDelta(float32(e.X), float32(e.Y))
case *sdl.KeyboardEvent:
input.HandleKeyboardEvent(e)
if !imguiCaptureKeyboard {
input.HandleKeyboardEvent(e)
}
imIo.AddKeyEvent(nmageimgui.SdlScancodeToImGuiKey(e.Keysym.Scancode), e.Type == sdl.KEYDOWN)
// Send modifier key updates to imgui
@ -73,12 +86,29 @@ func (w *Window) handleInputs() {
imIo.AddInputCharactersUTF8(e.GetText())
case *sdl.MouseButtonEvent:
input.HandleMouseBtnEvent(e)
if !imguiCaptureMouse {
input.HandleMouseBtnEvent(e)
}
isPressed := e.State == sdl.PRESSED
if e.Button == sdl.BUTTON_LEFT {
isSdlButtonLeftDown = isPressed
} else if e.Button == sdl.BUTTON_MIDDLE {
isSdlButtonMiddleDown = isPressed
} else if e.Button == sdl.BUTTON_RIGHT {
isSdlButtonRightDown = isPressed
}
case *sdl.MouseMotionEvent:
input.HandleMouseMotionEvent(e)
if !imguiCaptureMouse {
input.HandleMouseMotionEvent(e)
}
case *sdl.WindowEvent:
if e.Event == sdl.WINDOWEVENT_SIZE_CHANGED {
w.handleWindowResize()
}
@ -88,13 +118,17 @@ func (w *Window) handleInputs() {
}
}
// 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, _ := sdl.GetMouseState()
imIo.SetMousePos(imgui.Vec2{X: float32(x), Y: float32(y)})
if sdl.GetRelativeMouseMode() {
imIo.SetMousePos(imgui.Vec2{X: ImguiRelativeMouseModePosX, Y: ImguiRelativeMouseModePosY})
} else {
x, y, _ := sdl.GetMouseState()
imIo.SetMousePos(imgui.Vec2{X: float32(x), Y: float32(y)})
}
imIo.SetMouseButtonDown(0, input.MouseDown(sdl.BUTTON_LEFT))
imIo.SetMouseButtonDown(1, input.MouseDown(sdl.BUTTON_RIGHT))
imIo.SetMouseButtonDown(2, input.MouseDown(sdl.BUTTON_MIDDLE))
// 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.
imIo.SetMouseButtonDown(imgui.MouseButtonLeft, isSdlButtonLeftDown)
imIo.SetMouseButtonDown(imgui.MouseButtonRight, isSdlButtonRightDown)
imIo.SetMouseButtonDown(imgui.MouseButtonMiddle, isSdlButtonMiddleDown)
}
func (w *Window) handleWindowResize() {
@ -133,15 +167,21 @@ func initSDL() error {
sdl.GLSetAttribute(sdl.MAJOR_VERSION, 4)
sdl.GLSetAttribute(sdl.MINOR_VERSION, 1)
// R(0-255) G(0-255) B(0-255)
sdl.GLSetAttribute(sdl.GL_RED_SIZE, 8)
sdl.GLSetAttribute(sdl.GL_GREEN_SIZE, 8)
sdl.GLSetAttribute(sdl.GL_BLUE_SIZE, 8)
sdl.GLSetAttribute(sdl.GL_ALPHA_SIZE, 8)
sdl.GLSetAttribute(sdl.GL_DOUBLEBUFFER, 1)
sdl.GLSetAttribute(sdl.GL_DEPTH_SIZE, 24)
sdl.GLSetAttribute(sdl.GL_STENCIL_SIZE, 8)
sdl.GLSetAttribute(sdl.GL_FRAMEBUFFER_SRGB_CAPABLE, 1)
// Allows us to do MSAA
sdl.GLSetAttribute(sdl.GL_MULTISAMPLEBUFFERS, 1)
sdl.GLSetAttribute(sdl.GL_MULTISAMPLESAMPLES, 4)
sdl.GLSetAttribute(sdl.GL_CONTEXT_PROFILE_MASK, sdl.GL_CONTEXT_PROFILE_CORE)
return nil
@ -152,16 +192,12 @@ func CreateOpenGLWindow(title string, x, y, width, height int32, flags WindowFla
}
func CreateOpenGLWindowCentered(title string, width, height int32, flags WindowFlags, rend renderer.Render) (*Window, error) {
return createWindow(title, -1, -1, width, height, WindowFlags_OPENGL|flags, rend)
return createWindow(title, sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED, width, height, WindowFlags_OPENGL|flags, rend)
}
func createWindow(title string, x, y, width, height int32, flags WindowFlags, rend renderer.Render) (*Window, error) {
assert.T(isInited, "engine.Init was not called!")
if x == -1 && y == -1 {
x = sdl.WINDOWPOS_CENTERED
y = sdl.WINDOWPOS_CENTERED
}
assert.T(isInited, "engine.Init() was not called!")
sdlWin, err := sdl.CreateWindow(title, x, y, width, height, uint32(flags))
if err != nil {
@ -198,6 +234,7 @@ func initOpenGL() error {
gl.FrontFace(gl.CCW)
gl.Enable(gl.BLEND)
gl.Enable(gl.MULTISAMPLE)
gl.Enable(gl.FRAMEBUFFER_SRGB)
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
@ -223,3 +260,12 @@ func SetVSync(enabled bool) {
sdl.GLSetSwapInterval(0)
}
}
func SetMSAA(isEnabled bool) {
if isEnabled {
gl.Enable(gl.MULTISAMPLE)
} else {
gl.Disable(gl.MULTISAMPLE)
}
}

View File

@ -31,8 +31,8 @@ type mouseWheelState struct {
}
var (
keyMap = make(map[sdl.Keycode]*keyState)
mouseBtnMap = make(map[int]*mouseBtnState)
keyMap = make(map[sdl.Keycode]keyState)
mouseBtnMap = make(map[int]mouseBtnState)
mouseMotion = mouseMotionState{}
mouseWheel = mouseWheelState{}
quitRequested bool
@ -70,29 +70,31 @@ func IsQuitClicked() bool {
func HandleKeyboardEvent(e *sdl.KeyboardEvent) {
ks := keyMap[e.Keysym.Sym]
if ks == nil {
ks = &keyState{Key: e.Keysym.Sym}
keyMap[ks.Key] = ks
ks, ok := keyMap[e.Keysym.Sym]
if !ok {
ks = keyState{Key: e.Keysym.Sym}
}
ks.State = int(e.State)
ks.IsPressedThisFrame = e.State == sdl.PRESSED && e.Repeat == 0
ks.IsReleasedThisFrame = e.State == sdl.RELEASED && e.Repeat == 0
keyMap[ks.Key] = ks
}
func HandleMouseBtnEvent(e *sdl.MouseButtonEvent) {
mb := mouseBtnMap[int(e.Button)]
if mb == nil {
mb = &mouseBtnState{Btn: int(e.Button)}
mouseBtnMap[int(e.Button)] = mb
mb, ok := mouseBtnMap[int(e.Button)]
if !ok {
mb = mouseBtnState{Btn: int(e.Button)}
}
mb.State = int(e.State)
mb.IsDoubleClicked = e.Clicks == 2 && e.State == sdl.PRESSED
mb.IsPressedThisFrame = e.State == sdl.PRESSED
mb.IsReleasedThisFrame = e.State == sdl.RELEASED
mouseBtnMap[int(e.Button)] = mb
}
func HandleMouseMotionEvent(e *sdl.MouseMotionEvent) {
@ -109,12 +111,12 @@ func HandleMouseWheelEvent(e *sdl.MouseWheelEvent) {
mouseWheel.YDelta = e.Y
}
//GetMousePos returns the window coordinates of the mouse
// GetMousePos returns the window coordinates of the mouse
func GetMousePos() (x, y int32) {
return mouseMotion.XPos, mouseMotion.YPos
}
//GetMouseMotion returns how many pixels were moved last frame
// GetMouseMotion returns how many pixels were moved last frame
func GetMouseMotion() (xDelta, yDelta int32) {
return mouseMotion.XDelta, mouseMotion.YDelta
}
@ -141,7 +143,7 @@ func GetMouseWheelMotion() (xDelta, yDelta int32) {
return mouseWheel.XDelta, mouseWheel.YDelta
}
//GetMouseWheelXNorm returns 1 if mouse wheel xDelta > 0, -1 if xDelta < 0, and 0 otherwise
// GetMouseWheelXNorm returns 1 if mouse wheel xDelta > 0, -1 if xDelta < 0, and 0 otherwise
func GetMouseWheelXNorm() int32 {
if mouseWheel.XDelta > 0 {
@ -153,7 +155,7 @@ func GetMouseWheelXNorm() int32 {
return 0
}
//returns 1 if mouse wheel yDelta > 0, -1 if yDelta < 0, and 0 otherwise
// returns 1 if mouse wheel yDelta > 0, -1 if yDelta < 0, and 0 otherwise
func GetMouseWheelYNorm() int32 {
if mouseWheel.YDelta > 0 {
@ -167,8 +169,8 @@ func GetMouseWheelYNorm() int32 {
func KeyClicked(kc sdl.Keycode) bool {
ks := keyMap[kc]
if ks == nil {
ks, ok := keyMap[kc]
if !ok {
return false
}
@ -177,8 +179,8 @@ func KeyClicked(kc sdl.Keycode) bool {
func KeyReleased(kc sdl.Keycode) bool {
ks := keyMap[kc]
if ks == nil {
ks, ok := keyMap[kc]
if !ok {
return false
}
@ -187,8 +189,8 @@ func KeyReleased(kc sdl.Keycode) bool {
func KeyDown(kc sdl.Keycode) bool {
ks := keyMap[kc]
if ks == nil {
ks, ok := keyMap[kc]
if !ok {
return false
}
@ -197,8 +199,8 @@ func KeyDown(kc sdl.Keycode) bool {
func KeyUp(kc sdl.Keycode) bool {
ks := keyMap[kc]
if ks == nil {
ks, ok := keyMap[kc]
if !ok {
return true
}
@ -207,8 +209,8 @@ func KeyUp(kc sdl.Keycode) bool {
func MouseClicked(mb int) bool {
btn := mouseBtnMap[mb]
if btn == nil {
btn, ok := mouseBtnMap[mb]
if !ok {
return false
}
@ -217,8 +219,8 @@ func MouseClicked(mb int) bool {
func MouseDoubleClicked(mb int) bool {
btn := mouseBtnMap[mb]
if btn == nil {
btn, ok := mouseBtnMap[mb]
if !ok {
return false
}
@ -226,8 +228,8 @@ func MouseDoubleClicked(mb int) bool {
}
func MouseReleased(mb int) bool {
btn := mouseBtnMap[mb]
if btn == nil {
btn, ok := mouseBtnMap[mb]
if !ok {
return false
}
@ -236,8 +238,8 @@ func MouseReleased(mb int) bool {
func MouseDown(mb int) bool {
btn := mouseBtnMap[mb]
if btn == nil {
btn, ok := mouseBtnMap[mb]
if !ok {
return false
}
@ -246,8 +248,8 @@ func MouseDown(mb int) bool {
func MouseUp(mb int) bool {
btn := mouseBtnMap[mb]
if btn == nil {
btn, ok := mouseBtnMap[mb]
if !ok {
return true
}

View File

@ -141,11 +141,13 @@ func main() {
}
defer window.Destroy()
engine.SetMSAA(true)
engine.SetVSync(false)
engine.SetSrgbFramebuffer(true)
game := &OurGame{
Win: window,
ImGUIInfo: nmageimgui.NewImGui(),
ImGUIInfo: nmageimgui.NewImGui("./res/shaders/imgui.glsl"),
}
window.EventCallbacks = append(window.EventCallbacks, game.handleWindowEvents)
@ -229,7 +231,7 @@ func (g *OurGame) Init() {
}
//Load textures
tex, err := assets.LoadTexturePNG("./res/textures/pallete-endesga-64-1x.png", nil)
tex, err := assets.LoadTexturePNG("./res/textures/pallete-endesga-64-1x.png", &assets.TextureLoadOptions{TextureIsSrgba: true})
if err != nil {
logging.ErrLog.Fatalln("Failed to load texture. Err: ", err)
}
@ -238,6 +240,7 @@ func (g *OurGame) Init() {
"./res/textures/sb-right.jpg", "./res/textures/sb-left.jpg",
"./res/textures/sb-top.jpg", "./res/textures/sb-bottom.jpg",
"./res/textures/sb-front.jpg", "./res/textures/sb-back.jpg",
&assets.TextureLoadOptions{TextureIsSrgba: true},
)
if err != nil {
logging.ErrLog.Fatalln("Failed to load cubemap. Err: ", err)
@ -271,6 +274,8 @@ func (g *OurGame) Init() {
//Lights
simpleMat.SetUnifVec3("lightPos1", lightPos1)
simpleMat.SetUnifVec3("lightColor1", lightColor1)
sdl.SetRelativeMouseMode(true)
}
func (g *OurGame) Update() {

View File

@ -77,6 +77,10 @@ func (r *Registry[T]) New() (*T, Handle) {
func (r *Registry[T]) Get(id Handle) *T {
if id.IsZero() {
return nil
}
index := id.Index()
assert.T(index < uint64(len(r.Handles)), "Failed to get entity because of invalid entity handle. Handle index is %d while registry only has %d slots. Handle: %+v", index, r.ItemCount, id)

View File

@ -16,6 +16,12 @@ const (
// Byte 1: Generation; Byte 2: Flags; Bytes 3-8: Index
type Handle uint64
// IsZero reports whether the handle is in its default 'zero' state.
// A zero handle is an invalid handle that does NOT point to any entity
func (h Handle) IsZero() bool {
return h == 0
}
func (h Handle) HasFlag(ef HandleFlag) bool {
return h.Flags()&ef > 0
}

47
res/shaders/imgui.glsl Executable file
View File

@ -0,0 +1,47 @@
//shader:vertex
#version 410
uniform mat4 ProjMtx;
in vec2 Position;
in vec2 UV;
in vec4 Color;
out vec2 Frag_UV;
out vec4 Frag_Color;
// Imgui doesn't handle srgb correctly, and looks too bright and wrong in srgb buffers (see: https://github.com/ocornut/imgui/issues/578).
// While not a complete fix (that would require changes in imgui itself), moving incoming srgba colors to linear in the vertex shader helps make things look better.
vec4 srgba_to_linear(vec4 srgbaColor){
#define gamma_correction 2.2
return vec4(
pow(srgbaColor.r, gamma_correction),
pow(srgbaColor.g, gamma_correction),
pow(srgbaColor.b, gamma_correction),
srgbaColor.a
);
}
void main()
{
Frag_UV = UV;
Frag_Color = srgba_to_linear(Color);
gl_Position = ProjMtx * vec4(Position.xy, 0, 1);
}
//shader:fragment
#version 410
uniform sampler2D Texture;
in vec2 Frag_UV;
in vec4 Frag_Color;
out vec4 Out_Color;
void main()
{
Out_Color = vec4(Frag_Color.rgb, Frag_Color.a * texture(Texture, Frag_UV.st).r);
}

View File

@ -16,7 +16,8 @@ type ImguiInfo struct {
VaoID uint32
VboID uint32
IndexBufID uint32
TexID uint32
// This is a pointer so we can send a stable pointer to C code
TexID *uint32
}
func (i *ImguiInfo) FrameStart(winWidth, winHeight float32) {
@ -108,11 +109,11 @@ func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32)
cmd.CallUserCallback(list)
} else {
gl.BindTexture(gl.TEXTURE_2D, i.TexID)
gl.BindTexture(gl.TEXTURE_2D, *i.TexID)
clipRect := cmd.ClipRect()
gl.Scissor(int32(clipRect.X), int32(fbHeight)-int32(clipRect.W), int32(clipRect.Z-clipRect.X), int32(clipRect.W-clipRect.Y))
gl.DrawElementsBaseVertex(gl.TRIANGLES, int32(cmd.ElemCount()), uint32(drawType), gl.PtrOffset(int(cmd.IdxOffset())*indexSize), int32(cmd.VtxOffset()))
gl.DrawElementsBaseVertexWithOffset(gl.TRIANGLES, int32(cmd.ElemCount()), uint32(drawType), uintptr(int(cmd.IdxOffset())*indexSize), int32(cmd.VtxOffset()))
}
}
}
@ -141,13 +142,13 @@ func (i *ImguiInfo) AddFontTTF(fontPath string, fontSize float32, fontConfig *im
f := a.AddFontFromFileTTFV(fontPath, fontSize, fontConfigToUse, glyphRangesToUse.Data())
pixels, width, height, _ := a.GetTextureDataAsAlpha8()
gl.BindTexture(gl.TEXTURE_2D, i.TexID)
gl.BindTexture(gl.TEXTURE_2D, *i.TexID)
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, int32(width), int32(height), 0, gl.RED, gl.UNSIGNED_BYTE, pixels)
return f
}
const imguiShdrSrc = `
const DefaultImguiShader = `
//shader:vertex
#version 410
@ -160,10 +161,24 @@ in vec4 Color;
out vec2 Frag_UV;
out vec4 Frag_Color;
// Imgui doesn't handle srgb correctly, and looks too bright and wrong in srgb buffers (see: https://github.com/ocornut/imgui/issues/578).
// While not a complete fix (that would require changes in imgui itself), moving incoming srgba colors to linear in the vertex shader helps make things look better.
vec4 srgba_to_linear(vec4 srgbaColor){
#define gamma_correction 2.2
return vec4(
pow(srgbaColor.r, gamma_correction),
pow(srgbaColor.g, gamma_correction),
pow(srgbaColor.b, gamma_correction),
srgbaColor.a
);
}
void main()
{
Frag_UV = UV;
Frag_Color = Color;
Frag_Color = srgba_to_linear(Color);
gl_Position = ProjMtx * vec4(Position.xy, 0, 1);
}
@ -183,11 +198,21 @@ void main()
}
`
func NewImGui() ImguiInfo {
// NewImGui setups imgui using the passed shader.
// If the path is empty a default nMage shader is used
func NewImGui(shaderPath string) ImguiInfo {
var imguiMat *materials.Material
if shaderPath == "" {
imguiMat = materials.NewMaterialSrc("ImGUI Mat", []byte(DefaultImguiShader))
} else {
imguiMat = materials.NewMaterial("ImGUI Mat", shaderPath)
}
imguiInfo := ImguiInfo{
ImCtx: imgui.CreateContext(),
Mat: materials.NewMaterialSrc("ImGUI Mat", []byte(imguiShdrSrc)),
Mat: imguiMat,
TexID: new(uint32),
}
io := imgui.CurrentIO()
@ -197,10 +222,10 @@ func NewImGui() ImguiInfo {
gl.GenVertexArrays(1, &imguiInfo.VaoID)
gl.GenBuffers(1, &imguiInfo.VboID)
gl.GenBuffers(1, &imguiInfo.IndexBufID)
gl.GenTextures(1, &imguiInfo.TexID)
gl.GenTextures(1, imguiInfo.TexID)
// Upload font to gpu
gl.BindTexture(gl.TEXTURE_2D, imguiInfo.TexID)
gl.BindTexture(gl.TEXTURE_2D, *imguiInfo.TexID)
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)
@ -209,7 +234,7 @@ func NewImGui() ImguiInfo {
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, int32(width), int32(height), 0, gl.RED, gl.UNSIGNED_BYTE, pixels)
// Store our identifier
io.Fonts().SetTexID(imgui.TextureID(uintptr(imguiInfo.TexID)))
io.Fonts().SetTexID(imgui.TextureID(imguiInfo.TexID))
//Shader attributes
imguiInfo.Mat.Bind()