Compare commits

...

11 Commits

17 changed files with 333 additions and 136 deletions

View File

@ -4,7 +4,6 @@ import (
"bytes"
"fmt"
"image"
"image/color"
"image/jpeg"
"image/png"
"io"
@ -14,6 +13,7 @@ import (
"unsafe"
"github.com/go-gl/gl/v4.1-core/gl"
"github.com/mandykoh/prism"
)
type ColorFormat int
@ -23,11 +23,20 @@ const (
)
type Texture struct {
//Path only exists for textures loaded from disk
Path string
TexID uint32
Width int32
// Path only exists for textures loaded from disk
Path string
TexID uint32
// Width is the width of the texture in pixels (pixels per row).
// Note that the number of bytes constituting a row is MORE than this (e.g. for RGBA8, bytesPerRow=width*4, since we have 4 bytes per pixel)
Width int32
// Height is the height of the texture in pixels (pixels per column).
// Note that the number of bytes constituting a column is MORE than this (e.g. for RGBA8, bytesPerColumn=height*4, since we have 4 bytes per pixel)
Height int32
// Pixels usually stored in RGBA format
Pixels []byte
}
@ -36,6 +45,7 @@ type TextureLoadOptions struct {
WriteToCache bool
GenMipMaps bool
KeepPixelsInMem bool
TextureIsSrgba bool
}
type Cubemap struct {
@ -67,16 +77,20 @@ func LoadTexturePNG(file string, loadOptions *TextureLoadOptions) (Texture, erro
return Texture{}, err
}
img, err := png.Decode(bytes.NewReader(fileBytes))
bytesReader := bytes.NewReader(fileBytes)
img, err := png.Decode(bytesReader)
if err != nil {
return Texture{}, err
}
nrgbaImg := prism.ConvertImageToNRGBA(img, 2)
tex := Texture{
Path: file,
Path: file,
Pixels: nrgbaImg.Pix,
Width: int32(nrgbaImg.Bounds().Dx()),
Height: int32(nrgbaImg.Bounds().Dy()),
}
tex.Pixels, tex.Width, tex.Height = pixelsFromNrgbaPng(img)
flipImgPixelsVertically(tex.Pixels, int(tex.Width), int(tex.Height), 4)
//Prepare opengl stuff
gl.GenTextures(1, &tex.TexID)
@ -89,7 +103,12 @@ func LoadTexturePNG(file string, loadOptions *TextureLoadOptions) (Texture, erro
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
// load and generate the texture
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, tex.Width, tex.Height, 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&tex.Pixels[0]))
internalFormat := int32(gl.RGBA8)
if loadOptions.TextureIsSrgba {
internalFormat = gl.SRGB_ALPHA
}
gl.TexImage2D(gl.TEXTURE_2D, 0, internalFormat, tex.Width, tex.Height, 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&tex.Pixels[0]))
if loadOptions.GenMipMaps {
gl.GenerateMipmap(tex.TexID)
@ -112,8 +131,14 @@ func LoadTextureInMemPngImg(img image.Image, loadOptions *TextureLoadOptions) (T
loadOptions = &TextureLoadOptions{}
}
tex := Texture{}
tex.Pixels, tex.Width, tex.Height = pixelsFromNrgbaPng(img)
nrgbaImg := prism.ConvertImageToNRGBA(img, 2)
tex := Texture{
Path: "",
Pixels: nrgbaImg.Pix,
Height: int32(nrgbaImg.Bounds().Dy()),
Width: int32(nrgbaImg.Bounds().Dx()),
}
flipImgPixelsVertically(tex.Pixels, int(tex.Width), int(tex.Height), 4)
//Prepare opengl stuff
gl.GenTextures(1, &tex.TexID)
@ -126,7 +151,12 @@ func LoadTextureInMemPngImg(img image.Image, loadOptions *TextureLoadOptions) (T
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
// load and generate the texture
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, tex.Width, tex.Height, 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&tex.Pixels[0]))
internalFormat := int32(gl.RGBA8)
if loadOptions.TextureIsSrgba {
internalFormat = gl.SRGB_ALPHA
}
gl.TexImage2D(gl.TEXTURE_2D, 0, internalFormat, tex.Width, tex.Height, 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&tex.Pixels[0]))
if loadOptions.GenMipMaps {
gl.GenerateMipmap(tex.TexID)
@ -166,11 +196,14 @@ func LoadTextureJpeg(file string, loadOptions *TextureLoadOptions) (Texture, err
return Texture{}, err
}
nrgbaImg := prism.ConvertImageToNRGBA(img, 2)
tex := Texture{
Path: file,
Path: file,
Pixels: nrgbaImg.Pix,
Height: int32(nrgbaImg.Bounds().Dy()),
Width: int32(nrgbaImg.Bounds().Dx()),
}
tex.Pixels, tex.Width, tex.Height = pixelsFromNrgbaPng(img)
flipImgPixelsVertically(tex.Pixels, int(tex.Width), int(tex.Height), 4)
//Prepare opengl stuff
gl.GenTextures(1, &tex.TexID)
@ -183,7 +216,12 @@ func LoadTextureJpeg(file string, loadOptions *TextureLoadOptions) (Texture, err
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
// load and generate the texture
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, tex.Width, tex.Height, 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&tex.Pixels[0]))
internalFormat := int32(gl.RGBA8)
if loadOptions.TextureIsSrgba {
internalFormat = gl.SRGB_ALPHA
}
gl.TexImage2D(gl.TEXTURE_2D, 0, internalFormat, tex.Width, tex.Height, 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&tex.Pixels[0]))
if loadOptions.GenMipMaps {
gl.GenerateMipmap(tex.TexID)
@ -200,65 +238,19 @@ func LoadTextureJpeg(file string, loadOptions *TextureLoadOptions) (Texture, err
return tex, nil
}
func pixelsFromNrgbaPng(img image.Image) (pixels []byte, width, height int32) {
// LoadCubemapTextures only supports the 'TextureIsSrgba' option
func LoadCubemapTextures(rightTex, leftTex, topTex, botTex, frontTex, backTex string, loadOptions *TextureLoadOptions) (Cubemap, error) {
//NOTE: Load bottom left to top right because this is the texture coordinate system used by OpenGL
//NOTE: We only support 8-bit channels (32-bit colors) for now
i := 0
width, height = int32(img.Bounds().Dx()), int32(img.Bounds().Dy())
pixels = make([]byte, img.Bounds().Dx()*img.Bounds().Dy()*4)
for y := img.Bounds().Dy() - 1; y >= 0; y-- {
for x := 0; x < img.Bounds().Dx(); x++ {
c := color.NRGBAModel.Convert(img.At(x, y)).(color.NRGBA)
pixels[i] = c.R
pixels[i+1] = c.G
pixels[i+2] = c.B
pixels[i+3] = c.A
i += 4
}
if loadOptions == nil {
loadOptions = &TextureLoadOptions{}
}
return pixels, width, height
}
func pixelsFromNrgbaJpg(img image.Image) (pixels []byte, width, height int32) {
//NOTE: Load bottom left to top right because this is the texture coordinate system used by OpenGL
//NOTE: We only support 8-bit channels (32-bit colors) for now
i := 0
width, height = int32(img.Bounds().Dx()), int32(img.Bounds().Dy())
pixels = make([]byte, img.Bounds().Dx()*img.Bounds().Dy()*4)
for y := img.Bounds().Dy() - 1; y >= 0; y-- {
for x := 0; x < img.Bounds().Dx(); x++ {
c := color.NRGBAModel.Convert(img.At(x, y)).(color.NRGBA)
pixels[i] = c.R
pixels[i+1] = c.G
pixels[i+2] = c.B
pixels[i+3] = c.A
i += 4
}
}
return pixels, width, height
}
func LoadCubemapTextures(rightTex, leftTex, topTex, botTex, frontTex, backTex string) (Cubemap, error) {
var imgDecoder func(r io.Reader) (image.Image, error)
var pixelDecoder func(image.Image) ([]byte, int32, int32)
ext := strings.ToLower(path.Ext(rightTex))
if ext == ".jpg" || ext == ".jpeg" {
imgDecoder = jpeg.Decode
pixelDecoder = pixelsFromNrgbaJpg
} else if ext == ".png" {
imgDecoder = png.Decode
pixelDecoder = pixelsFromNrgbaPng
} else {
return Cubemap{}, fmt.Errorf("unknown image extension: %s. Expected one of: .jpg, .jpeg, .png", ext)
}
@ -292,9 +284,16 @@ func LoadCubemapTextures(rightTex, leftTex, topTex, botTex, frontTex, backTex st
return Cubemap{}, err
}
pixels, width, height := pixelDecoder(img)
nrgbaImg := prism.ConvertImageToNRGBA(img, 2)
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(&pixels[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)
@ -306,3 +305,21 @@ func LoadCubemapTextures(rightTex, leftTex, topTex, botTex, frontTex, backTex st
return cmap, nil
}
func flipImgPixelsVertically(bytes []byte, width, height, bytesPerPixel int) {
// Flip the image vertically such that (e.g. in an image of 10 rows) rows 0<->9, 1<->8, 2<->7 etc are swapped.
// We do this because images are usually stored top-left to bottom-right, while opengl stores textures bottom-left to top-right, so if we don't swap
// rows textures will appear inverted
widthInBytes := width * bytesPerPixel
rowData := make([]byte, width*bytesPerPixel)
for rowNum := 0; rowNum < height/2; rowNum++ {
upperRowStartIndex := rowNum * widthInBytes
lowerRowStartIndex := (height - rowNum - 1) * widthInBytes
copy(rowData, bytes[upperRowStartIndex:upperRowStartIndex+widthInBytes])
copy(bytes[upperRowStartIndex:upperRowStartIndex+widthInBytes], bytes[lowerRowStartIndex:lowerRowStartIndex+widthInBytes])
copy(bytes[lowerRowStartIndex:lowerRowStartIndex+widthInBytes], rowData)
}
}

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,12 +234,23 @@ 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)
gl.ClearColor(0, 0, 0, 1)
return nil
}
func SetSrgbFramebuffer(isEnabled bool) {
if isEnabled {
gl.Enable(gl.FRAMEBUFFER_SRGB)
} else {
gl.Disable(gl.FRAMEBUFFER_SRGB)
}
}
func SetVSync(enabled bool) {
assert.T(isInited, "engine.Init was not called!")
@ -213,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)
}
}

7
go.mod
View File

@ -11,4 +11,9 @@ require (
github.com/bloeys/gglm v0.43.0
)
require github.com/AllenDang/cimgui-go v0.0.0-20230720025235-f2ff398a66b2
require (
github.com/AllenDang/cimgui-go v0.0.0-20230720025235-f2ff398a66b2
github.com/mandykoh/prism v0.35.1
)
require github.com/mandykoh/go-parallel v0.1.0 // indirect

30
go.sum
View File

@ -6,5 +6,35 @@ github.com/bloeys/gglm v0.43.0 h1:ZpOghR3PHfpkigTDh+FqxLsF0gN8CD6s/bWoei6LyxI=
github.com/bloeys/gglm v0.43.0/go.mod h1:qwJQ0WzV191wAMwlGicbfbChbKoSedMk7gFFX6GnyOk=
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk=
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
github.com/mandykoh/go-parallel v0.1.0 h1:7vJMNMC4dsbgZdkAb2A8tV5ENY1v7VxIO1wzQWZoT8k=
github.com/mandykoh/go-parallel v0.1.0/go.mod h1:lkYHqG1JNTaSS6lG+PgFCnyMd2VDy8pH9jN9pY899ig=
github.com/mandykoh/prism v0.35.1 h1:JbQfQarANxSWlgJEpjv+E7DvtrqBaVP1YgJfZPvo6ME=
github.com/mandykoh/prism v0.35.1/go.mod h1:3miB3EAJ0IggYl/4eBB5MmawRbyJI1gKDtbrVvk8Q9I=
github.com/veandco/go-sdl2 v0.4.35 h1:NohzsfageDWGtCd9nf7Pc3sokMK/MOK+UA2QMJARWzQ=
github.com/veandco/go-sdl2 v0.4.35/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

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
}

BIN
res/models/plane.fbx Executable file

Binary file not shown.

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);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1008 KiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

After

Width:  |  Height:  |  Size: 617 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 338 KiB

After

Width:  |  Height:  |  Size: 770 KiB

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) {
@ -67,7 +68,7 @@ func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32)
i.Mat.Bind()
i.Mat.SetUnifInt32("Texture", 0)
//PERF: only update the ortho matrix on window resize
// @PERF: only update the ortho matrix on window resize
orthoMat := gglm.Ortho(0, float32(winWidth), 0, float32(winHeight), 0, 20)
i.Mat.SetUnifMat4("ProjMtx", &orthoMat.Mat4)
gl.BindSampler(0, 0) // Rely on combined texture/sampler state.
@ -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()