Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 80ce6d60d2 | |||
| c998fc26ce | |||
| 81b515197d | |||
| d703a5270c | |||
| caa76c2a5e | |||
| da50d597f9 | |||
| 9f9744a142 | |||
| b101d54049 | |||
| 41b5aea185 | |||
| 3574318552 | |||
| 05ccf3e158 |
@ -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)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,15 +133,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 +158,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 +200,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 +226,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
@ -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
@ -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=
|
||||
|
||||
7
main.go
@ -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)
|
||||
|
||||
@ -29,12 +29,10 @@ func (it *Iterator[T]) Next() (*T, Handle) {
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
// This does two things:
|
||||
//
|
||||
// First is if IsDone() only checked 'remainingItems', then when Next() returns the last item IsDone() will immediately be true which will cause loops to exit before processing that last item!
|
||||
// If IsDone() only checked 'remainingItems', then when Next() returns the last item IsDone() will immediately be true which will cause loops to exit before processing the last item!
|
||||
// However, with this check IsDone will remain false until Next() is called at least one more time after returning the last item which ensures the last item is processed in the loop.
|
||||
//
|
||||
// Secondly, if the iterator is created on an empty registry, the IsDone() check above won't pass, however the check here will correctly handle the case and make IsDone start returning true
|
||||
// In cases where iterator is created on an empty registry, IsDone() will report true and the above check will return early
|
||||
if it.remainingItems == 0 {
|
||||
it.currIndex = -1
|
||||
return nil, 0
|
||||
@ -47,9 +45,10 @@ func (it *Iterator[T]) Next() (*T, Handle) {
|
||||
continue
|
||||
}
|
||||
|
||||
it.remainingItems--
|
||||
item := &it.registry.Items[it.currIndex]
|
||||
it.currIndex++
|
||||
return &it.registry.Items[it.currIndex], handle
|
||||
it.remainingItems--
|
||||
return item, handle
|
||||
}
|
||||
|
||||
// If we reached here means we iterated to the end and didn't find anything, which probably
|
||||
@ -62,5 +61,13 @@ func (it *Iterator[T]) Next() (*T, Handle) {
|
||||
}
|
||||
|
||||
func (it *Iterator[T]) IsDone() bool {
|
||||
return it.currIndex == -1 && it.remainingItems == 0
|
||||
|
||||
if it.remainingItems != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// We have two cases here:
|
||||
// 1. Index of zero means Next() never returned an item. Remaining items of zero without returning anything means we have an empty registry and so its safe to report done
|
||||
// 2. Negative index means Next() has detected we reached the end and that its safe to report being done
|
||||
return it.currIndex <= 0
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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
47
res/shaders/imgui.glsl
Executable 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);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1008 KiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 274 KiB After Width: | Height: | Size: 617 KiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 338 KiB After Width: | Height: | Size: 770 KiB |
@ -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()
|
||||
|
||||