Compare commits
121 Commits
v0.12.11
...
f2b757c606
| Author | SHA1 | Date | |
|---|---|---|---|
| f2b757c606 | |||
| 3be4ad9c45 | |||
| bbc8652292 | |||
| 0d34e0fe6e | |||
| 870653019c | |||
| 79cb6805c4 | |||
| ff7fe4e531 | |||
| cb20e8ba8b | |||
| 9e6fdacb48 | |||
| f13db47918 | |||
| dcfe254052 | |||
| 1d71715cb4 | |||
| 581d17d1d9 | |||
| 3795a7123f | |||
| 5aa0f41085 | |||
| c782e8c312 | |||
| f0a12879f8 | |||
| 6ea08e9826 | |||
| 83c6f635e5 | |||
| cf6b2655e7 | |||
| 7b1e3ea7b4 | |||
| c884d2624d | |||
| 8c6b1d5821 | |||
| dfd1fe9c5e | |||
| 24613823a7 | |||
| 0386f441d6 | |||
| 57ab851534 | |||
| d523c0951b | |||
| abd7079e61 | |||
| 4d8ccdaf56 | |||
| a131e1b52d | |||
| f35c217d73 | |||
| fbfcbaa156 | |||
| c4b1dd1b3d | |||
| a5bea5a661 | |||
| 22ba9ca891 | |||
| 92855c52f9 | |||
| 594d342bf0 | |||
| bb1946b930 | |||
| ef2b01059a | |||
| 5fa6a06079 | |||
| b718611149 | |||
| be85e20024 | |||
| 040228319e | |||
| ddd8db3cb0 | |||
| 692167ada2 | |||
| 524ef068f0 | |||
| b060dcdbe9 | |||
| e22525e2ee | |||
| ee61373069 | |||
| 1f922b6a47 | |||
| 9d7bdc0196 | |||
| 83922f1908 | |||
| c00f6d97dd | |||
| 3c0f82a735 | |||
| c058b82a92 | |||
| 908e5e96aa | |||
| c83e263476 | |||
| 01f06cce1e | |||
| 20ed804d2a | |||
| 80ce6d60d2 | |||
| c998fc26ce | |||
| 81b515197d | |||
| d703a5270c | |||
| caa76c2a5e | |||
| da50d597f9 | |||
| 9f9744a142 | |||
| b101d54049 | |||
| 41b5aea185 | |||
| 3574318552 | |||
| 05ccf3e158 | |||
| 4f5fd50660 | |||
| aaea27b543 | |||
| 039d09f888 | |||
| 1b83d7f9a7 | |||
| 201d9546b2 | |||
| c1d5033eb0 | |||
| 6f646540f9 | |||
| a99dd304ed | |||
| 4e45995ed0 | |||
| a735e01a77 | |||
| abb45e4c4a | |||
| 78ea3ae747 | |||
| b44b00d7e2 | |||
| 70dccd757e | |||
| d7cd5bfc8d | |||
| 3b8e5c06de | |||
| 8e9dbee002 | |||
| 7b2fb19618 | |||
| 9651f77348 | |||
| a16654107b | |||
| 855cbfaba3 | |||
| b025afe1b4 | |||
| 84cd8c28c8 | |||
| 7d5e3e2d82 | |||
| 23a6689346 | |||
| 36488ead04 | |||
| de77d5464e | |||
| 305982deca | |||
| 653a315631 | |||
| c971324b5a | |||
| b5a2479c16 | |||
| 6f54aecb5f | |||
| 7a25aea6ba | |||
| 3071b52c85 | |||
| 1b858bd4ac | |||
| d550767cb6 | |||
| 271b1c0cea | |||
| 0da031aa57 | |||
| 62194c4cad | |||
| bd79f6e274 | |||
| ac0ca8ee39 | |||
| 35ff496a9a | |||
| 52b77e017e | |||
| b85056dd31 | |||
| e5ea6f986f | |||
| c4853792a5 | |||
| 8cf9be2830 | |||
| 71acc2e9ab | |||
| 2690014fc5 | |||
| fe2aef6b6d |
@ -1,22 +1,28 @@
|
||||
name: build-nmage
|
||||
|
||||
on:
|
||||
create:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-nmage-macos:
|
||||
runs-on: macos-10.15
|
||||
runs-on: macos-12
|
||||
steps:
|
||||
- name: Install golang 1.17
|
||||
uses: actions/setup-go@v2
|
||||
|
||||
- name: Install golang
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '^1.17'
|
||||
go-version: '>=1.22'
|
||||
|
||||
- name: Install assimp-go dylib
|
||||
run: sudo mkdir -p /usr/local/lib && sudo wget https://github.com/bloeys/assimp-go/releases/download/v0.4.2/libassimp_darwin_amd64.dylib -O /usr/local/lib/libassimp.5.dylib
|
||||
|
||||
- name: Install SDL2
|
||||
run: brew install sdl2{,_image,_mixer,_ttf,_gfx} pkg-config
|
||||
|
||||
- name: Clone nmage
|
||||
run: git clone https://github.com/bloeys/nmage
|
||||
|
||||
- name: build nmage
|
||||
working-directory: nmage
|
||||
run: go build .
|
||||
5
.gitignore
vendored
@ -15,4 +15,7 @@
|
||||
vendor/
|
||||
.vscode/
|
||||
imgui.ini
|
||||
*~
|
||||
*~
|
||||
|
||||
# Custom
|
||||
*.pprof
|
||||
@ -1,6 +1,6 @@
|
||||
# nMage
|
||||
|
||||
[](https://github.com/bloeys/nmage/actions/workflows/run-nmage.yml)
|
||||
[](https://github.com/bloeys/nmage/actions/workflows/build-nmage.yml)
|
||||
|
||||
nMage is a (hopefully!) high performance 3D Game Engine written in Go being developed [live](https://twitch.tv/bloeys), with recordings posted on [YouTube](https://www.youtube.com/channel/UCCf4qyNGPVwpj1HYFGahs_A).
|
||||
|
||||
|
||||
13
assert/assert.go
Executable file
@ -0,0 +1,13 @@
|
||||
package assert
|
||||
|
||||
import (
|
||||
"github.com/bloeys/nmage/consts"
|
||||
"github.com/bloeys/nmage/logging"
|
||||
)
|
||||
|
||||
func T(check bool, msg string, args ...any) {
|
||||
|
||||
if consts.Debug && !check {
|
||||
logging.ErrLog.Panicf("Assert failed: "+msg, args...)
|
||||
}
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
package asserts
|
||||
|
||||
import (
|
||||
"github.com/bloeys/nmage/consts"
|
||||
"github.com/bloeys/nmage/logging"
|
||||
)
|
||||
|
||||
func T(check bool, msg string) {
|
||||
if consts.Debug && !check {
|
||||
logging.ErrLog.Panicln("Assert failed:", msg)
|
||||
}
|
||||
}
|
||||
@ -1,18 +1,23 @@
|
||||
package assets
|
||||
|
||||
var (
|
||||
Textures map[uint32]Texture = make(map[uint32]Texture)
|
||||
TexturePaths map[string]uint32 = make(map[string]uint32)
|
||||
Textures = make(map[uint32]Texture)
|
||||
TexturePaths = make(map[string]uint32)
|
||||
)
|
||||
|
||||
func AddTextureToCache(t Texture) {
|
||||
|
||||
if _, ok := TexturePaths[t.Path]; ok {
|
||||
if t.Path != "" {
|
||||
if _, ok := TexturePaths[t.Path]; ok {
|
||||
return
|
||||
}
|
||||
println("Loaded texture from path:", t.Path)
|
||||
Textures[t.TexID] = t
|
||||
TexturePaths[t.Path] = t.TexID
|
||||
return
|
||||
}
|
||||
|
||||
println("Loaded texture:", t.Path)
|
||||
Textures[t.TexID] = t
|
||||
println("Loaded in-mem texture with ID:", t.TexID)
|
||||
TexturePaths[t.Path] = t.TexID
|
||||
}
|
||||
|
||||
|
||||
@ -2,25 +2,51 @@ package assets
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image/color"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/go-gl/gl/v4.1-core/gl"
|
||||
"github.com/mandykoh/prism"
|
||||
)
|
||||
|
||||
type ColorFormat int
|
||||
|
||||
const (
|
||||
ColorFormat_RGBA8 ColorFormat = iota
|
||||
ColorFormat_Unknown ColorFormat = iota
|
||||
ColorFormat_RGBA8
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultBlackTexId Texture
|
||||
DefaultWhiteTexId Texture
|
||||
DefaultDiffuseTexId Texture
|
||||
DefaultSpecularTexId Texture
|
||||
DefaultNormalTexId Texture
|
||||
DefaultEmissionTexId Texture
|
||||
)
|
||||
|
||||
type Texture struct {
|
||||
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
|
||||
}
|
||||
|
||||
@ -28,9 +54,22 @@ type TextureLoadOptions struct {
|
||||
TryLoadFromCache bool
|
||||
WriteToCache bool
|
||||
GenMipMaps bool
|
||||
KeepPixelsInMem bool
|
||||
NoSrgba bool
|
||||
}
|
||||
|
||||
func LoadPNGTexture(file string, loadOptions *TextureLoadOptions) (Texture, error) {
|
||||
type Cubemap struct {
|
||||
// These only exists for textures loaded from disk
|
||||
RightPath string
|
||||
LeftPath string
|
||||
TopPath string
|
||||
BotPath string
|
||||
FrontPath string
|
||||
BackPath string
|
||||
TexID uint32
|
||||
}
|
||||
|
||||
func LoadTexturePNG(file string, loadOptions *TextureLoadOptions) (Texture, error) {
|
||||
|
||||
if loadOptions == nil {
|
||||
loadOptions = &TextureLoadOptions{}
|
||||
@ -48,34 +87,20 @@ func LoadPNGTexture(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,
|
||||
Width: int32(img.Bounds().Dx()),
|
||||
Height: int32(img.Bounds().Dy()),
|
||||
Pixels: make([]byte, img.Bounds().Dx()*img.Bounds().Dy()*4),
|
||||
}
|
||||
|
||||
//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
|
||||
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)
|
||||
|
||||
tex.Pixels[i] = c.R
|
||||
tex.Pixels[i+1] = c.G
|
||||
tex.Pixels[i+2] = c.B
|
||||
tex.Pixels[i+3] = c.A
|
||||
|
||||
i += 4
|
||||
}
|
||||
Pixels: nrgbaImg.Pix,
|
||||
Width: int32(nrgbaImg.Bounds().Dx()),
|
||||
Height: int32(nrgbaImg.Bounds().Dy()),
|
||||
}
|
||||
flipImgPixelsVertically(tex.Pixels, int(tex.Width), int(tex.Height), 4)
|
||||
|
||||
//Prepare opengl stuff
|
||||
gl.GenTextures(1, &tex.TexID)
|
||||
@ -88,7 +113,12 @@ func LoadPNGTexture(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.SRGB_ALPHA)
|
||||
if loadOptions.NoSrgba {
|
||||
internalFormat = gl.RGBA8
|
||||
}
|
||||
|
||||
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)
|
||||
@ -98,5 +128,208 @@ func LoadPNGTexture(file string, loadOptions *TextureLoadOptions) (Texture, erro
|
||||
AddTextureToCache(tex)
|
||||
}
|
||||
|
||||
if !loadOptions.KeepPixelsInMem {
|
||||
tex.Pixels = nil
|
||||
}
|
||||
|
||||
return tex, nil
|
||||
}
|
||||
|
||||
func LoadTextureInMemPngImg(img image.Image, loadOptions *TextureLoadOptions) (Texture, error) {
|
||||
|
||||
if loadOptions == nil {
|
||||
loadOptions = &TextureLoadOptions{}
|
||||
}
|
||||
|
||||
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)
|
||||
gl.BindTexture(gl.TEXTURE_2D, tex.TexID)
|
||||
|
||||
// set the texture wrapping/filtering options (on the currently bound texture object)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
|
||||
|
||||
// load and generate the texture
|
||||
internalFormat := int32(gl.SRGB_ALPHA)
|
||||
if loadOptions.NoSrgba {
|
||||
internalFormat = gl.RGBA8
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if loadOptions.WriteToCache {
|
||||
AddTextureToCache(tex)
|
||||
}
|
||||
|
||||
if !loadOptions.KeepPixelsInMem {
|
||||
tex.Pixels = nil
|
||||
}
|
||||
|
||||
return tex, nil
|
||||
}
|
||||
|
||||
func LoadTextureJpeg(file string, loadOptions *TextureLoadOptions) (Texture, error) {
|
||||
|
||||
if loadOptions == nil {
|
||||
loadOptions = &TextureLoadOptions{}
|
||||
}
|
||||
|
||||
if loadOptions.TryLoadFromCache {
|
||||
if tex, ok := GetTextureFromCachePath(file); ok {
|
||||
return tex, nil
|
||||
}
|
||||
}
|
||||
|
||||
//Load from disk
|
||||
fileBytes, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return Texture{}, err
|
||||
}
|
||||
|
||||
img, err := jpeg.Decode(bytes.NewReader(fileBytes))
|
||||
if err != nil {
|
||||
return Texture{}, err
|
||||
}
|
||||
|
||||
nrgbaImg := prism.ConvertImageToNRGBA(img, 2)
|
||||
tex := Texture{
|
||||
Path: file,
|
||||
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)
|
||||
gl.BindTexture(gl.TEXTURE_2D, tex.TexID)
|
||||
|
||||
// set the texture wrapping/filtering options (on the currently bound texture object)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
|
||||
|
||||
// load and generate the texture
|
||||
internalFormat := int32(gl.SRGB_ALPHA)
|
||||
if loadOptions.NoSrgba {
|
||||
internalFormat = gl.RGBA8
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if loadOptions.WriteToCache {
|
||||
AddTextureToCache(tex)
|
||||
}
|
||||
|
||||
if !loadOptions.KeepPixelsInMem {
|
||||
tex.Pixels = nil
|
||||
}
|
||||
|
||||
return tex, nil
|
||||
}
|
||||
|
||||
// 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))
|
||||
if ext == ".jpg" || ext == ".jpeg" {
|
||||
imgDecoder = jpeg.Decode
|
||||
} else if ext == ".png" {
|
||||
imgDecoder = png.Decode
|
||||
} else {
|
||||
return Cubemap{}, fmt.Errorf("unknown image extension: %s. Expected one of: .jpg, .jpeg, .png", ext)
|
||||
}
|
||||
|
||||
cmap := Cubemap{
|
||||
RightPath: rightTex,
|
||||
LeftPath: leftTex,
|
||||
TopPath: topTex,
|
||||
BotPath: botTex,
|
||||
FrontPath: frontTex,
|
||||
BackPath: backTex,
|
||||
}
|
||||
|
||||
gl.GenTextures(1, &cmap.TexID)
|
||||
gl.BindTexture(gl.TEXTURE_CUBE_MAP, cmap.TexID)
|
||||
|
||||
// The order here matters
|
||||
texturePaths := []string{rightTex, leftTex, topTex, botTex, frontTex, backTex}
|
||||
for i := uint32(0); i < uint32(len(texturePaths)); i++ {
|
||||
|
||||
fPath := texturePaths[i]
|
||||
|
||||
//Load from disk
|
||||
fileBytes, err := os.ReadFile(fPath)
|
||||
if err != nil {
|
||||
return Cubemap{}, err
|
||||
}
|
||||
|
||||
img, err := imgDecoder(bytes.NewReader(fileBytes))
|
||||
if err != nil {
|
||||
return Cubemap{}, err
|
||||
}
|
||||
|
||||
nrgbaImg := prism.ConvertImageToNRGBA(img, 2)
|
||||
height := int32(nrgbaImg.Bounds().Dy())
|
||||
width := int32(nrgbaImg.Bounds().Dx())
|
||||
|
||||
internalFormat := int32(gl.SRGB_ALPHA)
|
||||
if loadOptions.NoSrgba {
|
||||
internalFormat = gl.RGBA8
|
||||
}
|
||||
|
||||
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)
|
||||
gl.TexParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
|
||||
gl.TexParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
|
||||
gl.TexParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
|
||||
gl.TexParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
|
||||
gl.TexParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE)
|
||||
|
||||
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)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,15 +3,17 @@ package buffers
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/bloeys/nmage/asserts"
|
||||
"github.com/bloeys/nmage/assert"
|
||||
"github.com/go-gl/gl/v4.1-core/gl"
|
||||
)
|
||||
|
||||
type BufUsage int
|
||||
|
||||
const (
|
||||
BufUsage_Unknown BufUsage = iota
|
||||
|
||||
//Buffer is set only once and used many times
|
||||
BufUsage_Static BufUsage = iota
|
||||
BufUsage_Static
|
||||
//Buffer is changed a lot and used many times
|
||||
BufUsage_Dynamic
|
||||
//Buffer is set only once and used by the GPU at most a few times
|
||||
@ -28,6 +30,6 @@ func (b BufUsage) ToGL() uint32 {
|
||||
return gl.STREAM_DRAW
|
||||
}
|
||||
|
||||
asserts.T(false, fmt.Sprintf("Unexpected BufUsage value '%v'", b))
|
||||
assert.T(false, fmt.Sprintf("Unexpected BufUsage value '%v'", b))
|
||||
return 0
|
||||
}
|
||||
|
||||
@ -1,105 +0,0 @@
|
||||
package buffers
|
||||
|
||||
import (
|
||||
"github.com/bloeys/nmage/logging"
|
||||
"github.com/go-gl/gl/v4.1-core/gl"
|
||||
)
|
||||
|
||||
type Buffer struct {
|
||||
VAOID uint32
|
||||
//BufID is the ID of the VBO
|
||||
BufID uint32
|
||||
//IndexBufID is the ID of the index/element buffer
|
||||
IndexBufID uint32
|
||||
IndexBufCount int32
|
||||
Stride int32
|
||||
|
||||
layout []Element
|
||||
}
|
||||
|
||||
func (b *Buffer) Bind() {
|
||||
gl.BindVertexArray(b.VAOID)
|
||||
}
|
||||
|
||||
func (b *Buffer) UnBind() {
|
||||
gl.BindVertexArray(0)
|
||||
}
|
||||
|
||||
func (b *Buffer) SetData(values []float32) {
|
||||
|
||||
gl.BindVertexArray(b.VAOID)
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, b.BufID)
|
||||
|
||||
gl.BufferData(gl.ARRAY_BUFFER, len(values)*4, gl.Ptr(values), BufUsage_Static.ToGL())
|
||||
|
||||
gl.BindVertexArray(0)
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
|
||||
}
|
||||
|
||||
func (b *Buffer) SetIndexBufData(values []uint32) {
|
||||
|
||||
b.IndexBufCount = int32(len(values))
|
||||
gl.BindVertexArray(b.VAOID)
|
||||
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, b.IndexBufID)
|
||||
|
||||
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, len(values)*4, gl.Ptr(values), BufUsage_Static.ToGL())
|
||||
|
||||
gl.BindVertexArray(0)
|
||||
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)
|
||||
}
|
||||
|
||||
func (b *Buffer) GetLayout() []Element {
|
||||
e := make([]Element, len(b.layout))
|
||||
copy(e, b.layout)
|
||||
return e
|
||||
}
|
||||
|
||||
//SetLayout updates the layout object and the corresponding vertex attributes.
|
||||
//Vertex attributes are also enabled.
|
||||
func (b *Buffer) SetLayout(layout ...Element) {
|
||||
|
||||
b.layout = layout
|
||||
|
||||
b.Stride = 0
|
||||
for i := 0; i < len(b.layout); i++ {
|
||||
|
||||
b.layout[i].Offset = int(b.Stride)
|
||||
b.Stride += b.layout[i].Size()
|
||||
}
|
||||
|
||||
//Set opengl stuff
|
||||
b.Bind()
|
||||
|
||||
//NOTE: VBOs are only bound at 'VertexAttribPointer', not BindBUffer, so we need to bind the buffer and vao here
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, b.BufID)
|
||||
|
||||
for i := 0; i < len(layout); i++ {
|
||||
gl.EnableVertexAttribArray(uint32(i))
|
||||
gl.VertexAttribPointer(uint32(i), layout[i].ElementType.CompCount(), layout[i].ElementType.GLType(), false, b.Stride, gl.PtrOffset(layout[i].Offset))
|
||||
}
|
||||
|
||||
b.UnBind()
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
|
||||
}
|
||||
|
||||
func NewBuffer(layout ...Element) Buffer {
|
||||
|
||||
b := Buffer{}
|
||||
gl.GenVertexArrays(1, &b.VAOID)
|
||||
if b.VAOID == 0 {
|
||||
logging.ErrLog.Println("Failed to create openGL vertex array object")
|
||||
}
|
||||
|
||||
gl.GenBuffers(1, &b.BufID)
|
||||
if b.BufID == 0 {
|
||||
logging.ErrLog.Println("Failed to create openGL buffer")
|
||||
}
|
||||
|
||||
gl.GenBuffers(1, &b.IndexBufID)
|
||||
if b.IndexBufID == 0 {
|
||||
logging.ErrLog.Println("Failed to create openGL buffer")
|
||||
}
|
||||
|
||||
b.SetLayout(layout...)
|
||||
return b
|
||||
}
|
||||
@ -1,23 +1,23 @@
|
||||
package buffers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/bloeys/nmage/asserts"
|
||||
"github.com/bloeys/nmage/assert"
|
||||
"github.com/bloeys/nmage/logging"
|
||||
"github.com/go-gl/gl/v4.1-core/gl"
|
||||
)
|
||||
|
||||
//Element represents an element that makes up a buffer (e.g. Vec3 at an offset of 12 bytes)
|
||||
// Element represents an element that makes up a buffer (e.g. Vec3 at an offset of 12 bytes)
|
||||
type Element struct {
|
||||
Offset int
|
||||
ElementType
|
||||
}
|
||||
|
||||
//ElementType is the type of an element thats makes up a buffer (e.g. Vec3)
|
||||
type ElementType int
|
||||
// ElementType is the type of an element thats makes up a buffer (e.g. Vec3)
|
||||
type ElementType uint8
|
||||
|
||||
const (
|
||||
DataTypeUnknown ElementType = iota
|
||||
|
||||
DataTypeUint32
|
||||
DataTypeInt32
|
||||
DataTypeFloat32
|
||||
@ -25,35 +25,54 @@ const (
|
||||
DataTypeVec2
|
||||
DataTypeVec3
|
||||
DataTypeVec4
|
||||
|
||||
DataTypeMat2
|
||||
DataTypeMat3
|
||||
DataTypeMat4
|
||||
|
||||
DataTypeStruct
|
||||
)
|
||||
|
||||
func (dt ElementType) GLType() uint32 {
|
||||
|
||||
switch dt {
|
||||
|
||||
case DataTypeUint32:
|
||||
return gl.UNSIGNED_INT
|
||||
case DataTypeInt32:
|
||||
return gl.INT
|
||||
|
||||
case DataTypeFloat32:
|
||||
fallthrough
|
||||
|
||||
case DataTypeVec2:
|
||||
fallthrough
|
||||
case DataTypeVec3:
|
||||
fallthrough
|
||||
case DataTypeVec4:
|
||||
fallthrough
|
||||
case DataTypeMat2:
|
||||
fallthrough
|
||||
case DataTypeMat3:
|
||||
fallthrough
|
||||
case DataTypeMat4:
|
||||
return gl.FLOAT
|
||||
|
||||
case DataTypeStruct:
|
||||
logging.ErrLog.Fatalf("ElementType.GLType of DataTypeStruct is not supported")
|
||||
return 0
|
||||
|
||||
default:
|
||||
asserts.T(false, fmt.Sprintf("Unknown data type passed. DataType '%v'", dt))
|
||||
assert.T(false, "Unknown data type passed. DataType '%d'", dt)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
//CompSize returns the size in bytes for one component of the type (e.g. for Vec2 its 4)
|
||||
// CompSize returns the size in bytes for one component of the type (e.g. for Vec2 its 4).
|
||||
// Bools return 1, although in layout=std140 its 4
|
||||
func (dt ElementType) CompSize() int32 {
|
||||
|
||||
switch dt {
|
||||
|
||||
case DataTypeUint32:
|
||||
fallthrough
|
||||
case DataTypeFloat32:
|
||||
@ -65,15 +84,25 @@ func (dt ElementType) CompSize() int32 {
|
||||
case DataTypeVec3:
|
||||
fallthrough
|
||||
case DataTypeVec4:
|
||||
fallthrough
|
||||
case DataTypeMat2:
|
||||
fallthrough
|
||||
case DataTypeMat3:
|
||||
fallthrough
|
||||
case DataTypeMat4:
|
||||
return 4
|
||||
|
||||
case DataTypeStruct:
|
||||
logging.ErrLog.Fatalf("ElementType.CompSize of DataTypeStruct is not supported")
|
||||
return 0
|
||||
|
||||
default:
|
||||
asserts.T(false, fmt.Sprintf("Unknown data type passed. DataType '%v'", dt))
|
||||
assert.T(false, "Unknown data type passed. DataType '%d'", dt)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
//CompCount returns the number of components in the element (e.g. for Vec2 its 2)
|
||||
// CompCount returns the number of components in the element (e.g. for Vec2 its 2)
|
||||
func (dt ElementType) CompCount() int32 {
|
||||
|
||||
switch dt {
|
||||
@ -91,16 +120,28 @@ func (dt ElementType) CompCount() int32 {
|
||||
case DataTypeVec4:
|
||||
return 4
|
||||
|
||||
case DataTypeMat2:
|
||||
return 2 * 2
|
||||
case DataTypeMat3:
|
||||
return 3 * 3
|
||||
case DataTypeMat4:
|
||||
return 4 * 4
|
||||
|
||||
case DataTypeStruct:
|
||||
logging.ErrLog.Fatalf("ElementType.CompCount of DataTypeStruct is not supported")
|
||||
return 0
|
||||
|
||||
default:
|
||||
asserts.T(false, fmt.Sprintf("Unknown data type passed. DataType '%v'", dt))
|
||||
assert.T(false, "Unknown data type passed. DataType '%d'", dt)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
//Size returns the total size in bytes (e.g. for vec3 its 3*4=12 bytes)
|
||||
// Size returns the total size in bytes (e.g. for vec3 its 3*4=12 bytes)
|
||||
func (dt ElementType) Size() int32 {
|
||||
|
||||
switch dt {
|
||||
|
||||
case DataTypeUint32:
|
||||
fallthrough
|
||||
case DataTypeFloat32:
|
||||
@ -115,8 +156,123 @@ func (dt ElementType) Size() int32 {
|
||||
case DataTypeVec4:
|
||||
return 4 * 4
|
||||
|
||||
case DataTypeMat2:
|
||||
return 2 * 2 * 4
|
||||
case DataTypeMat3:
|
||||
return 3 * 3 * 4
|
||||
case DataTypeMat4:
|
||||
return 4 * 4 * 4
|
||||
|
||||
case DataTypeStruct:
|
||||
logging.ErrLog.Fatalf("ElementType.Size of DataTypeStruct is not supported")
|
||||
return 0
|
||||
|
||||
default:
|
||||
asserts.T(false, fmt.Sprintf("Unknown data type passed. DataType '%v'", dt))
|
||||
assert.T(false, "Unknown data type passed. DataType '%d'", dt)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func (dt ElementType) GlStd140BaseAlignment() uint8 {
|
||||
|
||||
switch dt {
|
||||
|
||||
case DataTypeUint32:
|
||||
fallthrough
|
||||
case DataTypeFloat32:
|
||||
fallthrough
|
||||
case DataTypeInt32:
|
||||
return 4
|
||||
|
||||
case DataTypeVec2:
|
||||
return 4 * 2
|
||||
|
||||
// Vec3 has the same alignment as vec4
|
||||
case DataTypeVec3:
|
||||
fallthrough
|
||||
case DataTypeVec4:
|
||||
return 4 * 4
|
||||
|
||||
// Matrices follow: (vec4Alignment) * numColumns
|
||||
case DataTypeMat2:
|
||||
return (4 * 4) * 2
|
||||
case DataTypeMat3:
|
||||
return (4 * 4) * 3
|
||||
case DataTypeMat4:
|
||||
return (4 * 4) * 4
|
||||
|
||||
case DataTypeStruct:
|
||||
logging.ErrLog.Fatalf("ElementType.GlStd140BaseAlignment of DataTypeStruct is not supported")
|
||||
return 0
|
||||
|
||||
default:
|
||||
assert.T(false, "Unknown data type passed. DataType '%d'", dt)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func (dt ElementType) GlStd140AlignmentBoundary() uint16 {
|
||||
|
||||
switch dt {
|
||||
|
||||
case DataTypeUint32:
|
||||
fallthrough
|
||||
case DataTypeFloat32:
|
||||
fallthrough
|
||||
case DataTypeInt32:
|
||||
return 4
|
||||
|
||||
case DataTypeVec2:
|
||||
return 8
|
||||
|
||||
case DataTypeVec3:
|
||||
fallthrough
|
||||
case DataTypeVec4:
|
||||
fallthrough
|
||||
case DataTypeMat2:
|
||||
fallthrough
|
||||
case DataTypeMat3:
|
||||
fallthrough
|
||||
case DataTypeMat4:
|
||||
fallthrough
|
||||
case DataTypeStruct:
|
||||
return 16
|
||||
|
||||
default:
|
||||
assert.T(false, "Unknown data type passed. DataType '%d'", dt)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func (dt ElementType) String() string {
|
||||
|
||||
switch dt {
|
||||
|
||||
case DataTypeUint32:
|
||||
return "uint32"
|
||||
case DataTypeFloat32:
|
||||
return "float32"
|
||||
case DataTypeInt32:
|
||||
return "int32"
|
||||
|
||||
case DataTypeVec2:
|
||||
return "Vec2"
|
||||
case DataTypeVec3:
|
||||
return "Vec3"
|
||||
case DataTypeVec4:
|
||||
return "Vec4"
|
||||
|
||||
case DataTypeMat2:
|
||||
return "Mat2"
|
||||
case DataTypeMat3:
|
||||
return "Mat3"
|
||||
case DataTypeMat4:
|
||||
return "Mat4"
|
||||
|
||||
case DataTypeStruct:
|
||||
return "Struct"
|
||||
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
667
buffers/framebuffer.go
Executable file
@ -0,0 +1,667 @@
|
||||
package buffers
|
||||
|
||||
import (
|
||||
"github.com/bloeys/nmage/assert"
|
||||
"github.com/bloeys/nmage/logging"
|
||||
"github.com/go-gl/gl/v4.1-core/gl"
|
||||
)
|
||||
|
||||
type FramebufferAttachmentType int32
|
||||
|
||||
const (
|
||||
FramebufferAttachmentType_Unknown FramebufferAttachmentType = iota
|
||||
FramebufferAttachmentType_Texture
|
||||
FramebufferAttachmentType_Texture_Array
|
||||
FramebufferAttachmentType_Renderbuffer
|
||||
FramebufferAttachmentType_Cubemap
|
||||
FramebufferAttachmentType_Cubemap_Array
|
||||
)
|
||||
|
||||
func (f FramebufferAttachmentType) IsValid() bool {
|
||||
|
||||
switch f {
|
||||
case FramebufferAttachmentType_Texture:
|
||||
fallthrough
|
||||
case FramebufferAttachmentType_Texture_Array:
|
||||
fallthrough
|
||||
case FramebufferAttachmentType_Renderbuffer:
|
||||
fallthrough
|
||||
case FramebufferAttachmentType_Cubemap:
|
||||
fallthrough
|
||||
case FramebufferAttachmentType_Cubemap_Array:
|
||||
return true
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type FramebufferAttachmentDataFormat int32
|
||||
|
||||
const (
|
||||
FramebufferAttachmentDataFormat_Unknown FramebufferAttachmentDataFormat = iota
|
||||
FramebufferAttachmentDataFormat_R32Int
|
||||
FramebufferAttachmentDataFormat_RGBA8
|
||||
FramebufferAttachmentDataFormat_RGBAF16
|
||||
FramebufferAttachmentDataFormat_SRGBA
|
||||
FramebufferAttachmentDataFormat_DepthF32
|
||||
FramebufferAttachmentDataFormat_Depth24Stencil8
|
||||
)
|
||||
|
||||
func (f FramebufferAttachmentDataFormat) IsColorFormat() bool {
|
||||
return f == FramebufferAttachmentDataFormat_R32Int ||
|
||||
f == FramebufferAttachmentDataFormat_RGBA8 ||
|
||||
f == FramebufferAttachmentDataFormat_SRGBA ||
|
||||
f == FramebufferAttachmentDataFormat_RGBAF16
|
||||
}
|
||||
|
||||
func (f FramebufferAttachmentDataFormat) IsDepthFormat() bool {
|
||||
return f == FramebufferAttachmentDataFormat_Depth24Stencil8 ||
|
||||
f == FramebufferAttachmentDataFormat_DepthF32
|
||||
}
|
||||
|
||||
func (f FramebufferAttachmentDataFormat) GlInternalFormat() int32 {
|
||||
|
||||
switch f {
|
||||
case FramebufferAttachmentDataFormat_R32Int:
|
||||
return gl.R32I
|
||||
case FramebufferAttachmentDataFormat_RGBA8:
|
||||
return gl.RGB8
|
||||
case FramebufferAttachmentDataFormat_RGBAF16:
|
||||
return gl.RGBA16F
|
||||
case FramebufferAttachmentDataFormat_SRGBA:
|
||||
return gl.SRGB_ALPHA
|
||||
case FramebufferAttachmentDataFormat_DepthF32:
|
||||
return gl.DEPTH_COMPONENT
|
||||
case FramebufferAttachmentDataFormat_Depth24Stencil8:
|
||||
return gl.DEPTH24_STENCIL8
|
||||
default:
|
||||
logging.ErrLog.Fatalf("unknown framebuffer attachment data format. Format=%d\n", f)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func (f FramebufferAttachmentDataFormat) GlFormat() uint32 {
|
||||
|
||||
switch f {
|
||||
case FramebufferAttachmentDataFormat_R32Int:
|
||||
return gl.RED_INTEGER
|
||||
|
||||
case FramebufferAttachmentDataFormat_RGBA8:
|
||||
fallthrough
|
||||
case FramebufferAttachmentDataFormat_RGBAF16:
|
||||
fallthrough
|
||||
case FramebufferAttachmentDataFormat_SRGBA:
|
||||
return gl.RGBA
|
||||
|
||||
case FramebufferAttachmentDataFormat_DepthF32:
|
||||
return gl.DEPTH_COMPONENT
|
||||
|
||||
case FramebufferAttachmentDataFormat_Depth24Stencil8:
|
||||
return gl.DEPTH_STENCIL
|
||||
|
||||
default:
|
||||
logging.ErrLog.Fatalf("unknown framebuffer attachment data format. Format=%d\n", f)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func (f FramebufferAttachmentDataFormat) GlComponentType() uint32 {
|
||||
|
||||
switch f {
|
||||
|
||||
case FramebufferAttachmentDataFormat_R32Int:
|
||||
return gl.INT
|
||||
|
||||
case FramebufferAttachmentDataFormat_RGBA8:
|
||||
fallthrough
|
||||
case FramebufferAttachmentDataFormat_SRGBA:
|
||||
return gl.UNSIGNED_BYTE
|
||||
|
||||
case FramebufferAttachmentDataFormat_RGBAF16:
|
||||
// Seems this is fine to be float instead of half float
|
||||
fallthrough
|
||||
case FramebufferAttachmentDataFormat_DepthF32:
|
||||
return gl.FLOAT
|
||||
|
||||
case FramebufferAttachmentDataFormat_Depth24Stencil8:
|
||||
return gl.UNSIGNED_INT_24_8
|
||||
|
||||
default:
|
||||
logging.ErrLog.Fatalf("unknown framebuffer attachment data format. Format=%d\n", f)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
type FramebufferAttachment struct {
|
||||
Id uint32
|
||||
Type FramebufferAttachmentType
|
||||
Format FramebufferAttachmentDataFormat
|
||||
}
|
||||
|
||||
type Framebuffer struct {
|
||||
Id uint32
|
||||
ClearFlags uint32
|
||||
Attachments []FramebufferAttachment
|
||||
ColorAttachmentsCount uint32
|
||||
Width uint32
|
||||
Height uint32
|
||||
}
|
||||
|
||||
func (fbo *Framebuffer) Bind() {
|
||||
gl.BindFramebuffer(gl.FRAMEBUFFER, fbo.Id)
|
||||
}
|
||||
|
||||
func (fbo *Framebuffer) BindWithViewport() {
|
||||
gl.BindFramebuffer(gl.FRAMEBUFFER, fbo.Id)
|
||||
gl.Viewport(0, 0, int32(fbo.Width), int32(fbo.Height))
|
||||
}
|
||||
|
||||
// Clear calls gl.Clear with the fbo's clear flags.
|
||||
// Note that the fbo must be complete and bound.
|
||||
// Calling this without a bound fbo will clear something else, like your screen.
|
||||
func (fbo *Framebuffer) Clear() {
|
||||
gl.Clear(fbo.ClearFlags)
|
||||
}
|
||||
|
||||
func (fbo *Framebuffer) UnBind() {
|
||||
gl.BindFramebuffer(gl.FRAMEBUFFER, 0)
|
||||
}
|
||||
|
||||
func (fbo *Framebuffer) UnBindWithViewport(width, height uint32) {
|
||||
gl.BindFramebuffer(gl.FRAMEBUFFER, 0)
|
||||
gl.Viewport(0, 0, int32(width), int32(height))
|
||||
}
|
||||
|
||||
// IsComplete returns true if OpenGL reports that the fbo is complete/usable.
|
||||
// Note that this function binds and then unbinds the fbo
|
||||
func (fbo *Framebuffer) IsComplete() bool {
|
||||
fbo.Bind()
|
||||
isComplete := gl.CheckFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE
|
||||
fbo.UnBind()
|
||||
return isComplete
|
||||
}
|
||||
|
||||
func (fbo *Framebuffer) HasColorAttachment() bool {
|
||||
return fbo.ColorAttachmentsCount > 0
|
||||
}
|
||||
|
||||
func (fbo *Framebuffer) HasDepthAttachment() bool {
|
||||
|
||||
for i := 0; i < len(fbo.Attachments); i++ {
|
||||
|
||||
a := &fbo.Attachments[i]
|
||||
if a.Format.IsDepthFormat() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (fbo *Framebuffer) NewColorAttachment(
|
||||
attachType FramebufferAttachmentType,
|
||||
attachFormat FramebufferAttachmentDataFormat,
|
||||
) {
|
||||
|
||||
if fbo.ColorAttachmentsCount == 8 {
|
||||
logging.ErrLog.Fatalf("failed creating color attachment for framebuffer due it already having %d attached\n", fbo.ColorAttachmentsCount)
|
||||
}
|
||||
|
||||
if !attachType.IsValid() {
|
||||
logging.ErrLog.Fatalf("failed creating color attachment for framebuffer due to unknown attachment type. Type=%d\n", attachType)
|
||||
}
|
||||
|
||||
if attachType == FramebufferAttachmentType_Cubemap || attachType == FramebufferAttachmentType_Cubemap_Array {
|
||||
logging.ErrLog.Fatalf("failed creating color attachment because cubemaps can not be color attachments (at least in this implementation. You might be able to do it manually)\n")
|
||||
}
|
||||
|
||||
if attachType == FramebufferAttachmentType_Texture_Array {
|
||||
logging.ErrLog.Fatalf("failed creating color attachment because texture arrays can not be color attachments (implementation can be updated to support it or you can do it manually)\n")
|
||||
}
|
||||
|
||||
if !attachFormat.IsColorFormat() {
|
||||
logging.ErrLog.Fatalf("failed creating color attachment for framebuffer due to attachment data format not being a valid color type. Data format=%d\n", attachFormat)
|
||||
}
|
||||
|
||||
a := FramebufferAttachment{
|
||||
Type: attachType,
|
||||
Format: attachFormat,
|
||||
}
|
||||
|
||||
fbo.Bind()
|
||||
|
||||
if attachType == FramebufferAttachmentType_Texture {
|
||||
|
||||
// Create texture
|
||||
gl.GenTextures(1, &a.Id)
|
||||
if a.Id == 0 {
|
||||
logging.ErrLog.Fatalf("failed to generate texture for framebuffer. GlError=%d\n", gl.GetError())
|
||||
}
|
||||
|
||||
gl.BindTexture(gl.TEXTURE_2D, a.Id)
|
||||
gl.TexImage2D(
|
||||
gl.TEXTURE_2D,
|
||||
0,
|
||||
attachFormat.GlInternalFormat(),
|
||||
int32(fbo.Width),
|
||||
int32(fbo.Height),
|
||||
0,
|
||||
attachFormat.GlFormat(),
|
||||
attachFormat.GlComponentType(),
|
||||
nil,
|
||||
)
|
||||
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
|
||||
gl.BindTexture(gl.TEXTURE_2D, 0)
|
||||
|
||||
// Attach to fbo
|
||||
gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0+fbo.ColorAttachmentsCount, gl.TEXTURE_2D, a.Id, 0)
|
||||
|
||||
} else if attachType == FramebufferAttachmentType_Renderbuffer {
|
||||
|
||||
// Create rbo
|
||||
gl.GenRenderbuffers(1, &a.Id)
|
||||
if a.Id == 0 {
|
||||
logging.ErrLog.Fatalf("failed to generate render buffer for framebuffer. GlError=%d\n", gl.GetError())
|
||||
}
|
||||
|
||||
gl.BindRenderbuffer(gl.RENDERBUFFER, a.Id)
|
||||
gl.RenderbufferStorage(gl.RENDERBUFFER, uint32(attachFormat.GlInternalFormat()), int32(fbo.Width), int32(fbo.Height))
|
||||
gl.BindRenderbuffer(gl.RENDERBUFFER, 0)
|
||||
|
||||
// Attach to fbo
|
||||
gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0+fbo.ColorAttachmentsCount, gl.RENDERBUFFER, a.Id)
|
||||
}
|
||||
|
||||
fbo.UnBind()
|
||||
fbo.ColorAttachmentsCount++
|
||||
fbo.ClearFlags |= gl.COLOR_BUFFER_BIT
|
||||
fbo.Attachments = append(fbo.Attachments, a)
|
||||
}
|
||||
|
||||
// SetNoColorBuffer sets the read and draw buffers of this fbo to 'NONE',
|
||||
// which tells the graphics driver that we don't want a color buffer for this fbo.
|
||||
//
|
||||
// This is required because normally an fbo must have a color buffer to be considered complete, but by
|
||||
// doing this we get marked as complete even without one.
|
||||
//
|
||||
// Usually used when you only care about some other buffer, like a depth buffer.
|
||||
func (fbo *Framebuffer) SetNoColorBuffer() {
|
||||
|
||||
if fbo.HasColorAttachment() {
|
||||
logging.ErrLog.Fatalf("failed SetNoColorBuffer because framebuffer already has a color attachment\n")
|
||||
}
|
||||
|
||||
fbo.Bind()
|
||||
gl.DrawBuffer(gl.NONE)
|
||||
gl.ReadBuffer(gl.NONE)
|
||||
fbo.UnBind()
|
||||
}
|
||||
|
||||
func (fbo *Framebuffer) NewDepthAttachment(
|
||||
attachType FramebufferAttachmentType,
|
||||
attachFormat FramebufferAttachmentDataFormat,
|
||||
) {
|
||||
|
||||
if fbo.HasDepthAttachment() {
|
||||
logging.ErrLog.Fatalf("failed creating depth attachment for framebuffer because a depth attachment already exists\n")
|
||||
}
|
||||
|
||||
if !attachType.IsValid() {
|
||||
logging.ErrLog.Fatalf("failed creating depth attachment for framebuffer due to unknown attachment type. Type=%d\n", attachType)
|
||||
}
|
||||
|
||||
if !attachFormat.IsDepthFormat() {
|
||||
logging.ErrLog.Fatalf("failed creating depth attachment for framebuffer due to attachment data format not being a valid depth-stencil type. Data format=%d\n", attachFormat)
|
||||
}
|
||||
|
||||
if attachType == FramebufferAttachmentType_Cubemap_Array {
|
||||
logging.ErrLog.Fatalf("failed creating cubemap array depth attachment because 'NewDepthCubemapArrayAttachment' must be used for that\n")
|
||||
}
|
||||
|
||||
if attachType == FramebufferAttachmentType_Texture_Array {
|
||||
logging.ErrLog.Fatalf("failed creating texture array depth attachment because 'NewDepthTextureArrayAttachment' must be used for that\n")
|
||||
}
|
||||
|
||||
a := FramebufferAttachment{
|
||||
Type: attachType,
|
||||
Format: attachFormat,
|
||||
}
|
||||
|
||||
fbo.Bind()
|
||||
|
||||
if attachType == FramebufferAttachmentType_Texture {
|
||||
|
||||
// Create texture
|
||||
gl.GenTextures(1, &a.Id)
|
||||
if a.Id == 0 {
|
||||
logging.ErrLog.Fatalf("failed to generate texture for framebuffer. GlError=%d\n", gl.GetError())
|
||||
}
|
||||
|
||||
gl.BindTexture(gl.TEXTURE_2D, a.Id)
|
||||
gl.TexImage2D(
|
||||
gl.TEXTURE_2D,
|
||||
0,
|
||||
attachFormat.GlInternalFormat(),
|
||||
int32(fbo.Width),
|
||||
int32(fbo.Height),
|
||||
0,
|
||||
attachFormat.GlFormat(),
|
||||
attachFormat.GlComponentType(),
|
||||
nil,
|
||||
)
|
||||
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
|
||||
|
||||
// This is so that any sampling outside the depth map gives a full depth value.
|
||||
// Useful for example when doing shadow maps where we want things outside
|
||||
// the range of the texture to not show shadow
|
||||
borderColor := []float32{1, 1, 1, 1}
|
||||
gl.TexParameterfv(gl.TEXTURE_2D, gl.TEXTURE_BORDER_COLOR, &borderColor[0])
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_BORDER)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_BORDER)
|
||||
|
||||
gl.BindTexture(gl.TEXTURE_2D, 0)
|
||||
|
||||
// Attach to fbo
|
||||
gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, a.Id, 0)
|
||||
|
||||
} else if attachType == FramebufferAttachmentType_Renderbuffer {
|
||||
|
||||
// Create rbo
|
||||
gl.GenRenderbuffers(1, &a.Id)
|
||||
if a.Id == 0 {
|
||||
logging.ErrLog.Fatalf("failed to generate render buffer for framebuffer. GlError=%d\n", gl.GetError())
|
||||
}
|
||||
|
||||
gl.BindRenderbuffer(gl.RENDERBUFFER, a.Id)
|
||||
gl.RenderbufferStorage(gl.RENDERBUFFER, uint32(attachFormat.GlInternalFormat()), int32(fbo.Width), int32(fbo.Height))
|
||||
gl.BindRenderbuffer(gl.RENDERBUFFER, 0)
|
||||
|
||||
// Attach to fbo
|
||||
gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, a.Id)
|
||||
|
||||
} else if attachType == FramebufferAttachmentType_Cubemap {
|
||||
|
||||
// Create cubemap
|
||||
gl.GenTextures(1, &a.Id)
|
||||
if a.Id == 0 {
|
||||
logging.ErrLog.Fatalf("failed to generate texture for framebuffer. GlError=%d\n", gl.GetError())
|
||||
}
|
||||
|
||||
gl.BindTexture(gl.TEXTURE_CUBE_MAP, a.Id)
|
||||
for i := 0; i < 6; i++ {
|
||||
gl.TexImage2D(
|
||||
uint32(gl.TEXTURE_CUBE_MAP_POSITIVE_X+i),
|
||||
0,
|
||||
attachFormat.GlInternalFormat(),
|
||||
int32(fbo.Width),
|
||||
int32(fbo.Height),
|
||||
0,
|
||||
attachFormat.GlFormat(),
|
||||
attachFormat.GlComponentType(),
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
gl.TexParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
|
||||
gl.TexParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
|
||||
gl.TexParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
|
||||
gl.TexParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
|
||||
gl.TexParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE)
|
||||
|
||||
gl.BindTexture(gl.TEXTURE_2D, 0)
|
||||
|
||||
// Attach to fbo
|
||||
gl.FramebufferTexture(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, a.Id, 0)
|
||||
}
|
||||
|
||||
fbo.UnBind()
|
||||
fbo.ClearFlags |= gl.DEPTH_BUFFER_BIT
|
||||
fbo.Attachments = append(fbo.Attachments, a)
|
||||
}
|
||||
|
||||
func (fbo *Framebuffer) NewDepthCubemapArrayAttachment(
|
||||
attachFormat FramebufferAttachmentDataFormat,
|
||||
numCubemaps int32,
|
||||
) {
|
||||
|
||||
if fbo.HasDepthAttachment() {
|
||||
logging.ErrLog.Fatalf("failed creating cubemap array depth attachment for framebuffer because a depth attachment already exists\n")
|
||||
}
|
||||
|
||||
if !attachFormat.IsDepthFormat() {
|
||||
logging.ErrLog.Fatalf("failed creating depth attachment for framebuffer due to attachment data format not being a valid depth-stencil type. Data format=%d\n", attachFormat)
|
||||
}
|
||||
|
||||
a := FramebufferAttachment{
|
||||
Type: FramebufferAttachmentType_Cubemap_Array,
|
||||
Format: attachFormat,
|
||||
}
|
||||
|
||||
fbo.Bind()
|
||||
|
||||
// Create cubemap array
|
||||
gl.GenTextures(1, &a.Id)
|
||||
if a.Id == 0 {
|
||||
logging.ErrLog.Fatalf("failed to generate texture for framebuffer. GlError=%d\n", gl.GetError())
|
||||
}
|
||||
|
||||
gl.BindTexture(gl.TEXTURE_CUBE_MAP_ARRAY, a.Id)
|
||||
|
||||
gl.TexImage3D(
|
||||
gl.TEXTURE_CUBE_MAP_ARRAY,
|
||||
0,
|
||||
attachFormat.GlInternalFormat(),
|
||||
int32(fbo.Width),
|
||||
int32(fbo.Height),
|
||||
6*numCubemaps,
|
||||
0,
|
||||
attachFormat.GlFormat(),
|
||||
attachFormat.GlComponentType(),
|
||||
nil,
|
||||
)
|
||||
|
||||
gl.TexParameteri(gl.TEXTURE_CUBE_MAP_ARRAY, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
|
||||
gl.TexParameteri(gl.TEXTURE_CUBE_MAP_ARRAY, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
|
||||
gl.TexParameteri(gl.TEXTURE_CUBE_MAP_ARRAY, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
|
||||
gl.TexParameteri(gl.TEXTURE_CUBE_MAP_ARRAY, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
|
||||
gl.TexParameteri(gl.TEXTURE_CUBE_MAP_ARRAY, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE)
|
||||
|
||||
gl.BindTexture(gl.TEXTURE_2D, 0)
|
||||
|
||||
// Attach to fbo
|
||||
gl.FramebufferTexture(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, a.Id, 0)
|
||||
|
||||
fbo.UnBind()
|
||||
fbo.ClearFlags |= gl.DEPTH_BUFFER_BIT
|
||||
fbo.Attachments = append(fbo.Attachments, a)
|
||||
}
|
||||
|
||||
func (fbo *Framebuffer) NewDepthTextureArrayAttachment(
|
||||
attachFormat FramebufferAttachmentDataFormat,
|
||||
numTextures int32,
|
||||
) {
|
||||
|
||||
if fbo.HasDepthAttachment() {
|
||||
logging.ErrLog.Fatalf("failed creating texture array depth attachment for framebuffer because a depth attachment already exists\n")
|
||||
}
|
||||
|
||||
if !attachFormat.IsDepthFormat() {
|
||||
logging.ErrLog.Fatalf("failed creating depth attachment for framebuffer due to attachment data format not being a valid depth-stencil type. Data format=%d\n", attachFormat)
|
||||
}
|
||||
|
||||
a := FramebufferAttachment{
|
||||
Type: FramebufferAttachmentType_Texture_Array,
|
||||
Format: attachFormat,
|
||||
}
|
||||
|
||||
fbo.Bind()
|
||||
|
||||
// Create cubemap array
|
||||
gl.GenTextures(1, &a.Id)
|
||||
if a.Id == 0 {
|
||||
logging.ErrLog.Fatalf("failed to generate texture for framebuffer. GlError=%d\n", gl.GetError())
|
||||
}
|
||||
|
||||
gl.BindTexture(gl.TEXTURE_2D_ARRAY, a.Id)
|
||||
|
||||
gl.TexImage3D(
|
||||
gl.TEXTURE_2D_ARRAY,
|
||||
0,
|
||||
attachFormat.GlInternalFormat(),
|
||||
int32(fbo.Width),
|
||||
int32(fbo.Height),
|
||||
numTextures,
|
||||
0,
|
||||
attachFormat.GlFormat(),
|
||||
attachFormat.GlComponentType(),
|
||||
nil,
|
||||
)
|
||||
|
||||
gl.TexParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
|
||||
gl.TexParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
|
||||
|
||||
// This is so that any sampling outside the depth map gives a full depth value.
|
||||
// Useful for example when doing shadow maps where we want things outside
|
||||
// the range of the texture to not show shadow
|
||||
borderColor := []float32{1, 1, 1, 1}
|
||||
gl.TexParameterfv(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_BORDER_COLOR, &borderColor[0])
|
||||
gl.TexParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_BORDER)
|
||||
gl.TexParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_BORDER)
|
||||
|
||||
gl.BindTexture(gl.TEXTURE_2D_ARRAY, 0)
|
||||
|
||||
// Attach to fbo
|
||||
gl.FramebufferTexture(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, a.Id, 0)
|
||||
|
||||
fbo.UnBind()
|
||||
fbo.ClearFlags |= gl.DEPTH_BUFFER_BIT
|
||||
fbo.Attachments = append(fbo.Attachments, a)
|
||||
}
|
||||
|
||||
func (fbo *Framebuffer) NewDepthStencilAttachment(
|
||||
attachType FramebufferAttachmentType,
|
||||
attachFormat FramebufferAttachmentDataFormat,
|
||||
) {
|
||||
|
||||
if fbo.HasDepthAttachment() {
|
||||
logging.ErrLog.Fatalf("failed creating depth-stencil attachment for framebuffer because a depth-stencil attachment already exists\n")
|
||||
}
|
||||
|
||||
if !attachType.IsValid() {
|
||||
logging.ErrLog.Fatalf("failed creating depth-stencil attachment for framebuffer due to unknown attachment type. Type=%d\n", attachType)
|
||||
}
|
||||
|
||||
if !attachFormat.IsDepthFormat() {
|
||||
logging.ErrLog.Fatalf("failed creating depth-stencil attachment for framebuffer due to attachment data format not being a valid depth-stencil type. Data format=%d\n", attachFormat)
|
||||
}
|
||||
|
||||
a := FramebufferAttachment{
|
||||
Type: attachType,
|
||||
Format: attachFormat,
|
||||
}
|
||||
|
||||
fbo.Bind()
|
||||
|
||||
if attachType == FramebufferAttachmentType_Texture {
|
||||
|
||||
// Create texture
|
||||
gl.GenTextures(1, &a.Id)
|
||||
if a.Id == 0 {
|
||||
logging.ErrLog.Fatalf("failed to generate texture for framebuffer. GlError=%d\n", gl.GetError())
|
||||
}
|
||||
|
||||
gl.BindTexture(gl.TEXTURE_2D, a.Id)
|
||||
gl.TexImage2D(
|
||||
gl.TEXTURE_2D,
|
||||
0,
|
||||
attachFormat.GlInternalFormat(),
|
||||
int32(fbo.Width),
|
||||
int32(fbo.Height),
|
||||
0,
|
||||
attachFormat.GlFormat(),
|
||||
attachFormat.GlComponentType(),
|
||||
nil,
|
||||
)
|
||||
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
|
||||
gl.BindTexture(gl.TEXTURE_2D, 0)
|
||||
|
||||
// Attach to fbo
|
||||
gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.TEXTURE_2D, a.Id, 0)
|
||||
|
||||
} else if attachType == FramebufferAttachmentType_Renderbuffer {
|
||||
|
||||
// Create rbo
|
||||
gl.GenRenderbuffers(1, &a.Id)
|
||||
if a.Id == 0 {
|
||||
logging.ErrLog.Fatalf("failed to generate render buffer for framebuffer. GlError=%d\n", gl.GetError())
|
||||
}
|
||||
|
||||
gl.BindRenderbuffer(gl.RENDERBUFFER, a.Id)
|
||||
gl.RenderbufferStorage(gl.RENDERBUFFER, uint32(attachFormat.GlInternalFormat()), int32(fbo.Width), int32(fbo.Height))
|
||||
gl.BindRenderbuffer(gl.RENDERBUFFER, 0)
|
||||
|
||||
// Attach to fbo
|
||||
gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, a.Id)
|
||||
}
|
||||
|
||||
fbo.UnBind()
|
||||
fbo.ClearFlags |= gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT
|
||||
fbo.Attachments = append(fbo.Attachments, a)
|
||||
}
|
||||
|
||||
// SetCubemapArrayLayerFace 'binds' a single face of a cubemap from the cubemap
|
||||
// array to the fbo, such that rendering only affects that one face and the others inaccessible.
|
||||
//
|
||||
// If this is not called, the default is that the entire cubemap array and all the faces in it
|
||||
// are bound and available for use when binding the fbo.
|
||||
func (fbo *Framebuffer) SetCubemapArrayLayerFace(layerFace int32) {
|
||||
|
||||
for i := 0; i < len(fbo.Attachments); i++ {
|
||||
|
||||
a := &fbo.Attachments[i]
|
||||
if a.Type != FramebufferAttachmentType_Cubemap_Array {
|
||||
continue
|
||||
}
|
||||
|
||||
assert.T(a.Format.IsDepthFormat(), "SetCubemapFromArray called but a cubemap array is set on a color attachment, which is not currently handled. Code must be updated!")
|
||||
gl.FramebufferTextureLayer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, a.Id, 0, layerFace)
|
||||
return
|
||||
}
|
||||
|
||||
logging.ErrLog.Fatalf("SetCubemapFromArray failed because no cubemap array attachment was found on fbo. Fbo=%+v\n", *fbo)
|
||||
}
|
||||
|
||||
func (fbo *Framebuffer) Delete() {
|
||||
|
||||
if fbo.Id == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
gl.DeleteFramebuffers(1, &fbo.Id)
|
||||
fbo.Id = 0
|
||||
}
|
||||
|
||||
func NewFramebuffer(width, height uint32) Framebuffer {
|
||||
|
||||
// It is allowed to have attachments of differnt sizes in one FBO,
|
||||
// but that complicates things (e.g. which size to use for gl.viewport) and I don't see much use
|
||||
// for it now, so we will have all attachments share size
|
||||
fbo := Framebuffer{
|
||||
Width: width,
|
||||
Height: height,
|
||||
}
|
||||
|
||||
gl.GenFramebuffers(1, &fbo.Id)
|
||||
if fbo.Id == 0 {
|
||||
logging.ErrLog.Fatalf("failed to generate framebuffer. GlError=%d\n", gl.GetError())
|
||||
}
|
||||
|
||||
return fbo
|
||||
}
|
||||
46
buffers/index_buffer.go
Executable file
@ -0,0 +1,46 @@
|
||||
package buffers
|
||||
|
||||
import (
|
||||
"github.com/bloeys/nmage/logging"
|
||||
"github.com/go-gl/gl/v4.1-core/gl"
|
||||
)
|
||||
|
||||
type IndexBuffer struct {
|
||||
Id uint32
|
||||
// IndexBufCount is the number of elements in the index buffer. Updated in IndexBuffer.SetData
|
||||
IndexBufCount int32
|
||||
}
|
||||
|
||||
func (ib *IndexBuffer) Bind() {
|
||||
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, ib.Id)
|
||||
}
|
||||
|
||||
func (ib *IndexBuffer) UnBind() {
|
||||
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)
|
||||
}
|
||||
|
||||
func (ib *IndexBuffer) SetData(values []uint32) {
|
||||
|
||||
ib.Bind()
|
||||
|
||||
sizeInBytes := len(values) * 4
|
||||
ib.IndexBufCount = int32(len(values))
|
||||
|
||||
if sizeInBytes == 0 {
|
||||
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, 0, gl.Ptr(nil), BufUsage_Static.ToGL())
|
||||
} else {
|
||||
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, sizeInBytes, gl.Ptr(&values[0]), BufUsage_Static.ToGL())
|
||||
}
|
||||
}
|
||||
|
||||
func NewIndexBuffer() IndexBuffer {
|
||||
|
||||
ib := IndexBuffer{}
|
||||
|
||||
gl.GenBuffers(1, &ib.Id)
|
||||
if ib.Id == 0 {
|
||||
logging.ErrLog.Println("Failed to create OpenGL buffer")
|
||||
}
|
||||
|
||||
return ib
|
||||
}
|
||||
702
buffers/uniform_buffer.go
Executable file
@ -0,0 +1,702 @@
|
||||
package buffers
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
|
||||
"github.com/bloeys/gglm/gglm"
|
||||
"github.com/bloeys/nmage/assert"
|
||||
"github.com/bloeys/nmage/logging"
|
||||
"github.com/go-gl/gl/v4.1-core/gl"
|
||||
)
|
||||
|
||||
type UniformBufferFieldInput struct {
|
||||
Id uint16
|
||||
Type ElementType
|
||||
// Count should be set in case this field is an array of type `[Count]Type`.
|
||||
// Count=0 is valid and is equivalent to Count=1, which means the type is NOT an array, but a single field.
|
||||
Count uint16
|
||||
|
||||
// Subfields is used when type is a struct, in which case it holds the fields of the struct.
|
||||
// Ids do not have to be unique across structs.
|
||||
Subfields []UniformBufferFieldInput
|
||||
}
|
||||
|
||||
type UniformBufferField struct {
|
||||
Id uint16
|
||||
AlignedOffset uint16
|
||||
// Count should be set in case this field is an array of type `[Count]Type`.
|
||||
// Count=0 is valid and is equivalent to Count=1, which means the type is NOT an array, but a single field.
|
||||
Count uint16
|
||||
Type ElementType
|
||||
|
||||
// Subfields is used when type is a struct, in which case it holds the fields of the struct.
|
||||
// Ids do not have to be unique across structs.
|
||||
Subfields []UniformBufferField
|
||||
}
|
||||
|
||||
type UniformBuffer struct {
|
||||
Id uint32
|
||||
// Size is the allocated memory in bytes on the GPU for this uniform buffer
|
||||
Size uint32
|
||||
Fields []UniformBufferField
|
||||
}
|
||||
|
||||
func (ub *UniformBuffer) Bind() {
|
||||
gl.BindBuffer(gl.UNIFORM_BUFFER, ub.Id)
|
||||
}
|
||||
|
||||
func (ub *UniformBuffer) UnBind() {
|
||||
gl.BindBuffer(gl.UNIFORM_BUFFER, 0)
|
||||
}
|
||||
|
||||
func (ub *UniformBuffer) SetBindPoint(bindPointIndex uint32) {
|
||||
gl.BindBufferBase(gl.UNIFORM_BUFFER, bindPointIndex, ub.Id)
|
||||
}
|
||||
|
||||
func addUniformBufferFieldsToArray(startAlignedOffset uint16, arrayToAddTo *[]UniformBufferField, fieldsToAdd []UniformBufferFieldInput) (totalSize uint32) {
|
||||
|
||||
if len(fieldsToAdd) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// This function is recursive so only size the array once
|
||||
if cap(*arrayToAddTo) == 0 {
|
||||
*arrayToAddTo = make([]UniformBufferField, 0, len(fieldsToAdd))
|
||||
}
|
||||
|
||||
var alignedOffset uint16 = 0
|
||||
fieldIdToTypeMap := make(map[uint16]ElementType, len(fieldsToAdd))
|
||||
|
||||
for i := 0; i < len(fieldsToAdd); i++ {
|
||||
|
||||
f := fieldsToAdd[i]
|
||||
if f.Count == 0 {
|
||||
f.Count = 1
|
||||
}
|
||||
|
||||
existingFieldType, ok := fieldIdToTypeMap[f.Id]
|
||||
assert.T(!ok, "Uniform buffer field id is reused within the same uniform buffer. FieldId=%d was first used on a field with type=%s and then used on a different field with type=%s\n", f.Id, existingFieldType.String(), f.Type.String())
|
||||
|
||||
// To understand this take an example. Say we have a total offset of 100 and we are adding a vec4.
|
||||
// Vec4s must be aligned to a 16 byte boundary but 100 is not (100 % 16 != 0).
|
||||
//
|
||||
// To fix this, we take the alignment error which is alignErr=100 % 16=4, but this is error to the nearest
|
||||
// boundary, which is below the offset.
|
||||
//
|
||||
// To get the nearest boundary larger than the offset we can:
|
||||
// offset + (boundary - alignErr) == 100 + (16 - 4) == 112; 112 % 16 == 0, meaning its a boundary
|
||||
//
|
||||
// Note that arrays of scalars/vectors are always aligned to 16 bytes, like a vec4
|
||||
var alignmentBoundary uint16 = 16
|
||||
if f.Count == 1 {
|
||||
alignmentBoundary = f.Type.GlStd140AlignmentBoundary()
|
||||
}
|
||||
|
||||
alignmentError := alignedOffset % alignmentBoundary
|
||||
if alignmentError != 0 {
|
||||
alignedOffset += alignmentBoundary - alignmentError
|
||||
}
|
||||
|
||||
newField := UniformBufferField{Id: f.Id, Type: f.Type, AlignedOffset: startAlignedOffset + alignedOffset, Count: f.Count}
|
||||
*arrayToAddTo = append(*arrayToAddTo, newField)
|
||||
|
||||
// Prepare aligned offset for the next field.
|
||||
//
|
||||
// Matrices are treated as an array of column vectors, where each column is a vec4,
|
||||
// that's why we have a multiplier depending on how many columns we have when calculating
|
||||
// the offset
|
||||
multiplier := uint16(1)
|
||||
if f.Type == DataTypeMat2 {
|
||||
multiplier = 2
|
||||
} else if f.Type == DataTypeMat3 {
|
||||
multiplier = 3
|
||||
} else if f.Type == DataTypeMat4 {
|
||||
multiplier = 4
|
||||
}
|
||||
|
||||
if f.Type == DataTypeStruct {
|
||||
|
||||
subfieldsAlignedOffset := uint16(addUniformBufferFieldsToArray(startAlignedOffset+alignedOffset, arrayToAddTo, f.Subfields))
|
||||
|
||||
// Pad structs to 16 byte boundary
|
||||
subfieldsAlignmentError := subfieldsAlignedOffset % 16
|
||||
if subfieldsAlignmentError != 0 {
|
||||
subfieldsAlignedOffset += 16 - subfieldsAlignmentError
|
||||
}
|
||||
|
||||
alignedOffset += subfieldsAlignedOffset * f.Count
|
||||
|
||||
} else {
|
||||
alignedOffset = newField.AlignedOffset + alignmentBoundary*f.Count*multiplier - startAlignedOffset
|
||||
}
|
||||
}
|
||||
|
||||
return uint32(alignedOffset)
|
||||
}
|
||||
|
||||
func (ub *UniformBuffer) getField(fieldId uint16, fieldType ElementType) UniformBufferField {
|
||||
|
||||
for i := 0; i < len(ub.Fields); i++ {
|
||||
|
||||
f := ub.Fields[i]
|
||||
|
||||
if f.Id != fieldId {
|
||||
continue
|
||||
}
|
||||
|
||||
assert.T(f.Type == fieldType, "Uniform buffer field id is reused within the same uniform buffer. FieldId=%d was first used on a field with type=%v, but is now being used on a field with type=%v\n", fieldId, f.Type.String(), fieldType.String())
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
logging.ErrLog.Panicf("couldn't find uniform buffer field of id=%d and type=%s\n", fieldId, fieldType.String())
|
||||
return UniformBufferField{}
|
||||
}
|
||||
|
||||
func (ub *UniformBuffer) SetInt32(fieldId uint16, val int32) {
|
||||
|
||||
f := ub.getField(fieldId, DataTypeInt32)
|
||||
gl.BufferSubData(gl.UNIFORM_BUFFER, int(f.AlignedOffset), 4, gl.Ptr(&val))
|
||||
}
|
||||
|
||||
func (ub *UniformBuffer) SetUint32(fieldId uint16, val uint32) {
|
||||
|
||||
f := ub.getField(fieldId, DataTypeUint32)
|
||||
gl.BufferSubData(gl.UNIFORM_BUFFER, int(f.AlignedOffset), 4, gl.Ptr(&val))
|
||||
}
|
||||
|
||||
func (ub *UniformBuffer) SetFloat32(fieldId uint16, val float32) {
|
||||
|
||||
f := ub.getField(fieldId, DataTypeFloat32)
|
||||
gl.BufferSubData(gl.UNIFORM_BUFFER, int(f.AlignedOffset), 4, gl.Ptr(&val))
|
||||
}
|
||||
|
||||
func (ub *UniformBuffer) SetVec2(fieldId uint16, val *gglm.Vec2) {
|
||||
f := ub.getField(fieldId, DataTypeVec2)
|
||||
gl.BufferSubData(gl.UNIFORM_BUFFER, int(f.AlignedOffset), 4*2, gl.Ptr(&val.Data[0]))
|
||||
}
|
||||
|
||||
func (ub *UniformBuffer) SetVec3(fieldId uint16, val *gglm.Vec3) {
|
||||
f := ub.getField(fieldId, DataTypeVec3)
|
||||
gl.BufferSubData(gl.UNIFORM_BUFFER, int(f.AlignedOffset), 4*3, gl.Ptr(&val.Data[0]))
|
||||
}
|
||||
|
||||
func (ub *UniformBuffer) SetVec4(fieldId uint16, val *gglm.Vec4) {
|
||||
f := ub.getField(fieldId, DataTypeVec4)
|
||||
gl.BufferSubData(gl.UNIFORM_BUFFER, int(f.AlignedOffset), 4*4, gl.Ptr(&val.Data[0]))
|
||||
}
|
||||
|
||||
func (ub *UniformBuffer) SetMat2(fieldId uint16, val *gglm.Mat2) {
|
||||
f := ub.getField(fieldId, DataTypeMat2)
|
||||
gl.BufferSubData(gl.UNIFORM_BUFFER, int(f.AlignedOffset), 4*4, gl.Ptr(&val.Data[0][0]))
|
||||
}
|
||||
|
||||
func (ub *UniformBuffer) SetMat3(fieldId uint16, val *gglm.Mat3) {
|
||||
f := ub.getField(fieldId, DataTypeMat3)
|
||||
gl.BufferSubData(gl.UNIFORM_BUFFER, int(f.AlignedOffset), 4*9, gl.Ptr(&val.Data[0][0]))
|
||||
}
|
||||
|
||||
func (ub *UniformBuffer) SetMat4(fieldId uint16, val *gglm.Mat4) {
|
||||
f := ub.getField(fieldId, DataTypeMat4)
|
||||
gl.BufferSubData(gl.UNIFORM_BUFFER, int(f.AlignedOffset), 4*16, gl.Ptr(&val.Data[0][0]))
|
||||
}
|
||||
|
||||
func (ub *UniformBuffer) SetStruct(inputStruct any) {
|
||||
setStruct(ub.Fields, make([]byte, ub.Size), inputStruct, 1000_000, false)
|
||||
}
|
||||
|
||||
func setStruct(fields []UniformBufferField, buf []byte, inputStruct any, maxFieldsToConsume int, onlyBufWrite bool) (bytesWritten, fieldsConsumed int) {
|
||||
|
||||
if len(fields) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if inputStruct == nil {
|
||||
logging.ErrLog.Panicf("UniformBuffer.SetStruct called with a value that is nil")
|
||||
}
|
||||
|
||||
structVal := reflect.ValueOf(inputStruct)
|
||||
if structVal.Kind() != reflect.Struct {
|
||||
logging.ErrLog.Panicf("UniformBuffer.SetStruct called with a value that is not a struct. Val=%v\n", inputStruct)
|
||||
}
|
||||
|
||||
structFieldIndex := 0
|
||||
// structFieldCount := structVal.NumField()
|
||||
for fieldIndex := 0; fieldIndex < len(fields) && fieldIndex < maxFieldsToConsume; fieldIndex++ {
|
||||
|
||||
ubField := &fields[fieldIndex]
|
||||
valField := structVal.Field(structFieldIndex)
|
||||
|
||||
fieldsConsumed++
|
||||
structFieldIndex++
|
||||
|
||||
kind := valField.Kind()
|
||||
if kind == reflect.Pointer {
|
||||
valField = valField.Elem()
|
||||
}
|
||||
|
||||
var elementType reflect.Type
|
||||
isArray := kind == reflect.Slice || kind == reflect.Array
|
||||
if isArray {
|
||||
elementType = valField.Type().Elem()
|
||||
} else {
|
||||
elementType = valField.Type()
|
||||
}
|
||||
|
||||
if isArray {
|
||||
assert.T(valField.Len() == int(ubField.Count), "ubo field of id=%d is an array/slice field of length=%d but got input of length=%d\n", ubField.Id, ubField.Count, valField.Len())
|
||||
}
|
||||
|
||||
typeMatches := false
|
||||
bytesWritten = int(ubField.AlignedOffset)
|
||||
|
||||
switch ubField.Type {
|
||||
|
||||
case DataTypeUint32:
|
||||
|
||||
typeMatches = elementType.Name() == "uint32"
|
||||
if typeMatches {
|
||||
|
||||
if isArray {
|
||||
Write32BitIntegerSliceToByteBufWithAlignment(buf, &bytesWritten, 16, valField.Slice(0, valField.Len()).Interface().([]uint32))
|
||||
} else {
|
||||
Write32BitIntegerToByteBuf(buf, &bytesWritten, uint32(valField.Uint()))
|
||||
}
|
||||
}
|
||||
|
||||
case DataTypeFloat32:
|
||||
|
||||
typeMatches = elementType.Name() == "float32"
|
||||
if typeMatches {
|
||||
|
||||
if isArray {
|
||||
WriteF32SliceToByteBufWithAlignment(buf, &bytesWritten, 16, valField.Slice(0, valField.Len()).Interface().([]float32))
|
||||
} else {
|
||||
WriteF32ToByteBuf(buf, &bytesWritten, float32(valField.Float()))
|
||||
}
|
||||
}
|
||||
|
||||
case DataTypeInt32:
|
||||
|
||||
typeMatches = elementType.Name() == "int32"
|
||||
if typeMatches {
|
||||
|
||||
if isArray {
|
||||
Write32BitIntegerSliceToByteBufWithAlignment(buf, &bytesWritten, 16, valField.Slice(0, valField.Len()).Interface().([]int32))
|
||||
} else {
|
||||
Write32BitIntegerToByteBuf(buf, &bytesWritten, uint32(valField.Int()))
|
||||
}
|
||||
}
|
||||
|
||||
case DataTypeVec2:
|
||||
|
||||
typeMatches = elementType.Name() == "Vec2"
|
||||
|
||||
if typeMatches {
|
||||
|
||||
if isArray {
|
||||
WriteVec2SliceToByteBufWithAlignment(buf, &bytesWritten, 16, valField.Slice(0, valField.Len()).Interface().([]gglm.Vec2))
|
||||
} else {
|
||||
v2 := valField.Interface().(gglm.Vec2)
|
||||
WriteF32SliceToByteBuf(buf, &bytesWritten, v2.Data[:])
|
||||
}
|
||||
}
|
||||
|
||||
case DataTypeVec3:
|
||||
|
||||
typeMatches = elementType.Name() == "Vec3"
|
||||
|
||||
if typeMatches {
|
||||
|
||||
if isArray {
|
||||
WriteVec3SliceToByteBufWithAlignment(buf, &bytesWritten, 16, valField.Slice(0, valField.Len()).Interface().([]gglm.Vec3))
|
||||
} else {
|
||||
v3 := valField.Interface().(gglm.Vec3)
|
||||
WriteF32SliceToByteBuf(buf, &bytesWritten, v3.Data[:])
|
||||
}
|
||||
}
|
||||
|
||||
case DataTypeVec4:
|
||||
|
||||
typeMatches = elementType.Name() == "Vec4"
|
||||
|
||||
if typeMatches {
|
||||
|
||||
if isArray {
|
||||
WriteVec4SliceToByteBufWithAlignment(buf, &bytesWritten, 16, valField.Slice(0, valField.Len()).Interface().([]gglm.Vec4))
|
||||
} else {
|
||||
v3 := valField.Interface().(gglm.Vec4)
|
||||
WriteF32SliceToByteBuf(buf, &bytesWritten, v3.Data[:])
|
||||
}
|
||||
}
|
||||
|
||||
case DataTypeMat2:
|
||||
|
||||
typeMatches = elementType.Name() == "Mat2"
|
||||
|
||||
if typeMatches {
|
||||
|
||||
if isArray {
|
||||
m2Arr := valField.Interface().([]gglm.Mat2)
|
||||
WriteMat2SliceToByteBufWithAlignment(buf, &bytesWritten, 16*2, m2Arr)
|
||||
} else {
|
||||
m := valField.Interface().(gglm.Mat2)
|
||||
WriteF32SliceToByteBuf(buf, &bytesWritten, m.Data[0][:])
|
||||
WriteF32SliceToByteBuf(buf, &bytesWritten, m.Data[1][:])
|
||||
}
|
||||
}
|
||||
|
||||
case DataTypeMat3:
|
||||
|
||||
typeMatches = elementType.Name() == "Mat3"
|
||||
|
||||
if typeMatches {
|
||||
|
||||
if isArray {
|
||||
m3Arr := valField.Interface().([]gglm.Mat3)
|
||||
WriteMat3SliceToByteBufWithAlignment(buf, &bytesWritten, 16*3, m3Arr)
|
||||
} else {
|
||||
m := valField.Interface().(gglm.Mat3)
|
||||
WriteF32SliceToByteBuf(buf, &bytesWritten, m.Data[0][:])
|
||||
WriteF32SliceToByteBuf(buf, &bytesWritten, m.Data[1][:])
|
||||
WriteF32SliceToByteBuf(buf, &bytesWritten, m.Data[2][:])
|
||||
}
|
||||
}
|
||||
|
||||
case DataTypeMat4:
|
||||
|
||||
typeMatches = elementType.Name() == "Mat4"
|
||||
|
||||
if typeMatches {
|
||||
|
||||
if isArray {
|
||||
m4Arr := valField.Interface().([]gglm.Mat4)
|
||||
WriteMat4SliceToByteBufWithAlignment(buf, &bytesWritten, 16*4, m4Arr)
|
||||
} else {
|
||||
m := valField.Interface().(gglm.Mat4)
|
||||
WriteF32SliceToByteBuf(buf, &bytesWritten, m.Data[0][:])
|
||||
WriteF32SliceToByteBuf(buf, &bytesWritten, m.Data[1][:])
|
||||
WriteF32SliceToByteBuf(buf, &bytesWritten, m.Data[2][:])
|
||||
WriteF32SliceToByteBuf(buf, &bytesWritten, m.Data[3][:])
|
||||
}
|
||||
}
|
||||
|
||||
case DataTypeStruct:
|
||||
typeMatches = kind == reflect.Struct
|
||||
|
||||
if typeMatches {
|
||||
|
||||
setStructBytesWritten, setStructFieldsConsumed := setStruct(fields[fieldIndex+1:], buf, valField.Interface(), valField.NumField(), true)
|
||||
|
||||
bytesWritten += setStructBytesWritten
|
||||
fieldIndex += setStructFieldsConsumed
|
||||
fieldsConsumed += setStructFieldsConsumed
|
||||
}
|
||||
|
||||
default:
|
||||
assert.T(false, "Unknown uniform buffer data type passed. DataType '%d'", ubField.Type)
|
||||
}
|
||||
|
||||
if !typeMatches {
|
||||
logging.ErrLog.Panicf("Struct field ordering and types must match uniform buffer fields, but at field index %d got UniformBufferField=%v but a struct field of type %s\n", fieldIndex, ubField, valField.String())
|
||||
}
|
||||
}
|
||||
|
||||
if bytesWritten == 0 {
|
||||
return 0, fieldsConsumed
|
||||
}
|
||||
|
||||
if !onlyBufWrite {
|
||||
gl.BufferSubData(gl.UNIFORM_BUFFER, 0, bytesWritten, gl.Ptr(&buf[0]))
|
||||
}
|
||||
|
||||
return bytesWritten - int(fields[0].AlignedOffset), fieldsConsumed
|
||||
}
|
||||
|
||||
func Write32BitIntegerToByteBuf[T uint32 | int32](buf []byte, startIndex *int, val T) {
|
||||
|
||||
assert.T(*startIndex+4 <= len(buf), "failed to write uint32/int32 to buffer because the buffer doesn't have enough space. Start index=%d, Buffer length=%d", *startIndex, len(buf))
|
||||
|
||||
buf[*startIndex] = byte(val)
|
||||
buf[*startIndex+1] = byte(val >> 8)
|
||||
buf[*startIndex+2] = byte(val >> 16)
|
||||
buf[*startIndex+3] = byte(val >> 24)
|
||||
|
||||
*startIndex += 4
|
||||
}
|
||||
|
||||
func Write32BitIntegerSliceToByteBufWithAlignment[T uint32 | int32](buf []byte, startIndex *int, alignmentPerField int, vals []T) {
|
||||
|
||||
assert.T(*startIndex+len(vals)*alignmentPerField <= len(buf), "failed to write uint32/int32 with custom alignment=%d to buffer because the buffer doesn't have enough space. Start index=%d, Buffer length=%d, but needs %d bytes free", alignmentPerField, *startIndex, len(buf), len(vals)*alignmentPerField)
|
||||
|
||||
for i := 0; i < len(vals); i++ {
|
||||
|
||||
val := vals[i]
|
||||
|
||||
buf[*startIndex] = byte(val)
|
||||
buf[*startIndex+1] = byte(val >> 8)
|
||||
buf[*startIndex+2] = byte(val >> 16)
|
||||
buf[*startIndex+3] = byte(val >> 24)
|
||||
|
||||
*startIndex += alignmentPerField
|
||||
}
|
||||
}
|
||||
|
||||
func WriteF32ToByteBuf(buf []byte, startIndex *int, val float32) {
|
||||
|
||||
assert.T(*startIndex+4 <= len(buf), "failed to write float32 to buffer because the buffer doesn't have enough space. Start index=%d, Buffer length=%d", *startIndex, len(buf))
|
||||
|
||||
bits := math.Float32bits(val)
|
||||
|
||||
buf[*startIndex] = byte(bits)
|
||||
buf[*startIndex+1] = byte(bits >> 8)
|
||||
buf[*startIndex+2] = byte(bits >> 16)
|
||||
buf[*startIndex+3] = byte(bits >> 24)
|
||||
|
||||
*startIndex += 4
|
||||
}
|
||||
|
||||
func WriteF32SliceToByteBuf(buf []byte, startIndex *int, vals []float32) {
|
||||
|
||||
assert.T(*startIndex+len(vals)*4 <= len(buf), "failed to write slice of float32 to buffer because the buffer doesn't have enough space. Start index=%d, Buffer length=%d, but needs %d bytes free", *startIndex, len(buf), len(vals)*4)
|
||||
|
||||
for i := 0; i < len(vals); i++ {
|
||||
|
||||
bits := math.Float32bits(vals[i])
|
||||
|
||||
buf[*startIndex] = byte(bits)
|
||||
buf[*startIndex+1] = byte(bits >> 8)
|
||||
buf[*startIndex+2] = byte(bits >> 16)
|
||||
buf[*startIndex+3] = byte(bits >> 24)
|
||||
|
||||
*startIndex += 4
|
||||
}
|
||||
}
|
||||
|
||||
func WriteF32SliceToByteBufWithAlignment(buf []byte, startIndex *int, alignmentPerField int, vals []float32) {
|
||||
|
||||
assert.T(*startIndex+len(vals)*alignmentPerField <= len(buf), "failed to write slice of float32 with custom alignment=%d to buffer because the buffer doesn't have enough space. Start index=%d, Buffer length=%d, but needs %d bytes free", alignmentPerField, *startIndex, len(buf), len(vals)*alignmentPerField)
|
||||
|
||||
for i := 0; i < len(vals); i++ {
|
||||
|
||||
bits := math.Float32bits(vals[i])
|
||||
|
||||
buf[*startIndex] = byte(bits)
|
||||
buf[*startIndex+1] = byte(bits >> 8)
|
||||
buf[*startIndex+2] = byte(bits >> 16)
|
||||
buf[*startIndex+3] = byte(bits >> 24)
|
||||
|
||||
*startIndex += alignmentPerField
|
||||
}
|
||||
}
|
||||
|
||||
func WriteVec2SliceToByteBufWithAlignment(buf []byte, startIndex *int, alignmentPerVector int, vals []gglm.Vec2) {
|
||||
|
||||
assert.T(*startIndex+len(vals)*alignmentPerVector <= len(buf), "failed to write slice of gglm.Vec2 with custom alignment=%d to buffer because the buffer doesn't have enough space. Start index=%d, Buffer length=%d, but needs %d bytes free", alignmentPerVector, *startIndex, len(buf), len(vals)*alignmentPerVector)
|
||||
|
||||
for i := 0; i < len(vals); i++ {
|
||||
|
||||
bitsX := math.Float32bits(vals[i].X())
|
||||
bitsY := math.Float32bits(vals[i].Y())
|
||||
|
||||
buf[*startIndex] = byte(bitsX)
|
||||
buf[*startIndex+1] = byte(bitsX >> 8)
|
||||
buf[*startIndex+2] = byte(bitsX >> 16)
|
||||
buf[*startIndex+3] = byte(bitsX >> 24)
|
||||
|
||||
buf[*startIndex+4] = byte(bitsY)
|
||||
buf[*startIndex+5] = byte(bitsY >> 8)
|
||||
buf[*startIndex+6] = byte(bitsY >> 16)
|
||||
buf[*startIndex+7] = byte(bitsY >> 24)
|
||||
|
||||
*startIndex += alignmentPerVector
|
||||
}
|
||||
}
|
||||
|
||||
func WriteVec3SliceToByteBufWithAlignment(buf []byte, startIndex *int, alignmentPerVector int, vals []gglm.Vec3) {
|
||||
|
||||
assert.T(*startIndex+len(vals)*alignmentPerVector <= len(buf), "failed to write slice of gglm.Vec3 with custom alignment=%d to buffer because the buffer doesn't have enough space. Start index=%d, Buffer length=%d, but needs %d bytes free", alignmentPerVector, *startIndex, len(buf), len(vals)*alignmentPerVector)
|
||||
|
||||
for i := 0; i < len(vals); i++ {
|
||||
|
||||
bitsX := math.Float32bits(vals[i].X())
|
||||
bitsY := math.Float32bits(vals[i].Y())
|
||||
bitsZ := math.Float32bits(vals[i].Z())
|
||||
|
||||
buf[*startIndex] = byte(bitsX)
|
||||
buf[*startIndex+1] = byte(bitsX >> 8)
|
||||
buf[*startIndex+2] = byte(bitsX >> 16)
|
||||
buf[*startIndex+3] = byte(bitsX >> 24)
|
||||
|
||||
buf[*startIndex+4] = byte(bitsY)
|
||||
buf[*startIndex+5] = byte(bitsY >> 8)
|
||||
buf[*startIndex+6] = byte(bitsY >> 16)
|
||||
buf[*startIndex+7] = byte(bitsY >> 24)
|
||||
|
||||
buf[*startIndex+8] = byte(bitsZ)
|
||||
buf[*startIndex+9] = byte(bitsZ >> 8)
|
||||
buf[*startIndex+10] = byte(bitsZ >> 16)
|
||||
buf[*startIndex+11] = byte(bitsZ >> 24)
|
||||
|
||||
*startIndex += alignmentPerVector
|
||||
}
|
||||
}
|
||||
|
||||
func WriteVec4SliceToByteBufWithAlignment(buf []byte, startIndex *int, alignmentPerVector int, vals []gglm.Vec4) {
|
||||
|
||||
assert.T(*startIndex+len(vals)*alignmentPerVector <= len(buf), "failed to write slice of gglm.Vec4 with custom alignment=%d to buffer because the buffer doesn't have enough space. Start index=%d, Buffer length=%d, but needs %d bytes free", alignmentPerVector, *startIndex, len(buf), len(vals)*alignmentPerVector)
|
||||
|
||||
for i := 0; i < len(vals); i++ {
|
||||
|
||||
bitsX := math.Float32bits(vals[i].X())
|
||||
bitsY := math.Float32bits(vals[i].Y())
|
||||
bitsZ := math.Float32bits(vals[i].Z())
|
||||
bitsW := math.Float32bits(vals[i].W())
|
||||
|
||||
buf[*startIndex] = byte(bitsX)
|
||||
buf[*startIndex+1] = byte(bitsX >> 8)
|
||||
buf[*startIndex+2] = byte(bitsX >> 16)
|
||||
buf[*startIndex+3] = byte(bitsX >> 24)
|
||||
|
||||
buf[*startIndex+4] = byte(bitsY)
|
||||
buf[*startIndex+5] = byte(bitsY >> 8)
|
||||
buf[*startIndex+6] = byte(bitsY >> 16)
|
||||
buf[*startIndex+7] = byte(bitsY >> 24)
|
||||
|
||||
buf[*startIndex+8] = byte(bitsZ)
|
||||
buf[*startIndex+9] = byte(bitsZ >> 8)
|
||||
buf[*startIndex+10] = byte(bitsZ >> 16)
|
||||
buf[*startIndex+11] = byte(bitsZ >> 24)
|
||||
|
||||
buf[*startIndex+12] = byte(bitsW)
|
||||
buf[*startIndex+13] = byte(bitsW >> 8)
|
||||
buf[*startIndex+14] = byte(bitsW >> 16)
|
||||
buf[*startIndex+15] = byte(bitsW >> 24)
|
||||
|
||||
*startIndex += alignmentPerVector
|
||||
}
|
||||
}
|
||||
|
||||
func WriteMat2SliceToByteBufWithAlignment(buf []byte, startIndex *int, alignmentPerMatrix int, vals []gglm.Mat2) {
|
||||
|
||||
assert.T(*startIndex+len(vals)*alignmentPerMatrix <= len(buf), "failed to write slice of gglm.Mat2 with custom alignment=%d to buffer because the buffer doesn't have enough space. Start index=%d, Buffer length=%d, but needs %d bytes free", alignmentPerMatrix, *startIndex, len(buf), len(vals)*alignmentPerMatrix)
|
||||
|
||||
for i := 0; i < len(vals); i++ {
|
||||
|
||||
m := &vals[i]
|
||||
|
||||
WriteVec2SliceToByteBufWithAlignment(
|
||||
buf,
|
||||
startIndex,
|
||||
16,
|
||||
[]gglm.Vec2{
|
||||
{Data: m.Data[0]},
|
||||
{Data: m.Data[1]},
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func WriteMat3SliceToByteBufWithAlignment(buf []byte, startIndex *int, alignmentPerMatrix int, vals []gglm.Mat3) {
|
||||
|
||||
assert.T(*startIndex+len(vals)*alignmentPerMatrix <= len(buf), "failed to write slice of gglm.Mat3 with custom alignment=%d to buffer because the buffer doesn't have enough space. Start index=%d, Buffer length=%d, but needs %d bytes free", alignmentPerMatrix, *startIndex, len(buf), len(vals)*alignmentPerMatrix)
|
||||
|
||||
for i := 0; i < len(vals); i++ {
|
||||
|
||||
m := &vals[i]
|
||||
|
||||
WriteVec3SliceToByteBufWithAlignment(
|
||||
buf,
|
||||
startIndex,
|
||||
16,
|
||||
[]gglm.Vec3{
|
||||
{Data: m.Data[0]},
|
||||
{Data: m.Data[1]},
|
||||
{Data: m.Data[2]},
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func WriteMat4SliceToByteBufWithAlignment(buf []byte, startIndex *int, alignmentPerMatrix int, vals []gglm.Mat4) {
|
||||
|
||||
assert.T(*startIndex+len(vals)*alignmentPerMatrix <= len(buf), "failed to write slice of gglm.Mat2 with custom alignment=%d to buffer because the buffer doesn't have enough space. Start index=%d, Buffer length=%d, but needs %d bytes free", alignmentPerMatrix, *startIndex, len(buf), len(vals)*alignmentPerMatrix)
|
||||
|
||||
for i := 0; i < len(vals); i++ {
|
||||
|
||||
m := &vals[i]
|
||||
|
||||
WriteVec4SliceToByteBufWithAlignment(
|
||||
buf,
|
||||
startIndex,
|
||||
16,
|
||||
[]gglm.Vec4{
|
||||
{Data: m.Data[0]},
|
||||
{Data: m.Data[1]},
|
||||
{Data: m.Data[2]},
|
||||
{Data: m.Data[3]},
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func ReflectValueMatchesUniformBufferField(v reflect.Value, ubField *UniformBufferField) bool {
|
||||
|
||||
if v.Kind() == reflect.Pointer {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
switch ubField.Type {
|
||||
|
||||
case DataTypeUint32:
|
||||
t := v.Type()
|
||||
return t.Name() == "uint32"
|
||||
case DataTypeFloat32:
|
||||
t := v.Type()
|
||||
return t.Name() == "float32"
|
||||
case DataTypeInt32:
|
||||
t := v.Type()
|
||||
return t.Name() == "int32"
|
||||
case DataTypeVec2:
|
||||
_, ok := v.Interface().(gglm.Vec2)
|
||||
return ok
|
||||
case DataTypeVec3:
|
||||
_, ok := v.Interface().(gglm.Vec3)
|
||||
return ok
|
||||
case DataTypeVec4:
|
||||
_, ok := v.Interface().(gglm.Vec4)
|
||||
return ok
|
||||
case DataTypeMat2:
|
||||
_, ok := v.Interface().(gglm.Mat2)
|
||||
return ok
|
||||
case DataTypeMat3:
|
||||
_, ok := v.Interface().(gglm.Mat3)
|
||||
return ok
|
||||
case DataTypeMat4:
|
||||
_, ok := v.Interface().(gglm.Mat4)
|
||||
return ok
|
||||
|
||||
default:
|
||||
assert.T(false, "Unknown uniform buffer data type passed. DataType '%d'", ubField.Type)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func NewUniformBuffer(fields []UniformBufferFieldInput) UniformBuffer {
|
||||
|
||||
ub := UniformBuffer{}
|
||||
|
||||
ub.Size = addUniformBufferFieldsToArray(0, &ub.Fields, fields)
|
||||
|
||||
gl.GenBuffers(1, &ub.Id)
|
||||
if ub.Id == 0 {
|
||||
logging.ErrLog.Panicln("Failed to create OpenGL buffer for a uniform buffer")
|
||||
}
|
||||
|
||||
ub.Bind()
|
||||
gl.BufferData(gl.UNIFORM_BUFFER, int(ub.Size), gl.Ptr(nil), gl.STATIC_DRAW)
|
||||
ub.UnBind()
|
||||
|
||||
return ub
|
||||
}
|
||||
54
buffers/vertex_array.go
Executable file
@ -0,0 +1,54 @@
|
||||
package buffers
|
||||
|
||||
import (
|
||||
"github.com/bloeys/nmage/logging"
|
||||
"github.com/go-gl/gl/v4.1-core/gl"
|
||||
)
|
||||
|
||||
type VertexArray struct {
|
||||
Id uint32
|
||||
Vbos []VertexBuffer
|
||||
IndexBuffer IndexBuffer
|
||||
}
|
||||
|
||||
func (va *VertexArray) Bind() {
|
||||
gl.BindVertexArray(va.Id)
|
||||
}
|
||||
|
||||
func (va *VertexArray) UnBind() {
|
||||
gl.BindVertexArray(0)
|
||||
}
|
||||
|
||||
func (va *VertexArray) AddVertexBuffer(vbo VertexBuffer) {
|
||||
|
||||
// NOTE: VBOs are only bound at 'VertexAttribPointer' (and related) calls
|
||||
|
||||
va.Bind()
|
||||
vbo.Bind()
|
||||
|
||||
for i := 0; i < len(vbo.layout); i++ {
|
||||
|
||||
l := &vbo.layout[i]
|
||||
|
||||
gl.EnableVertexAttribArray(uint32(i))
|
||||
gl.VertexAttribPointerWithOffset(uint32(i), l.ElementType.CompCount(), l.ElementType.GLType(), false, vbo.Stride, uintptr(l.Offset))
|
||||
}
|
||||
}
|
||||
|
||||
func (va *VertexArray) SetIndexBuffer(ib IndexBuffer) {
|
||||
va.Bind()
|
||||
ib.Bind()
|
||||
va.IndexBuffer = ib
|
||||
}
|
||||
|
||||
func NewVertexArray() VertexArray {
|
||||
|
||||
vao := VertexArray{}
|
||||
|
||||
gl.GenVertexArrays(1, &vao.Id)
|
||||
if vao.Id == 0 {
|
||||
logging.ErrLog.Println("Failed to create OpenGL vertex array object")
|
||||
}
|
||||
|
||||
return vao
|
||||
}
|
||||
63
buffers/vertex_buffer.go
Executable file
@ -0,0 +1,63 @@
|
||||
package buffers
|
||||
|
||||
import (
|
||||
"github.com/bloeys/nmage/logging"
|
||||
"github.com/go-gl/gl/v4.1-core/gl"
|
||||
)
|
||||
|
||||
type VertexBuffer struct {
|
||||
Id uint32
|
||||
Stride int32
|
||||
layout []Element
|
||||
}
|
||||
|
||||
func (vb *VertexBuffer) Bind() {
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, vb.Id)
|
||||
}
|
||||
|
||||
func (vb *VertexBuffer) UnBind() {
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
|
||||
}
|
||||
|
||||
func (vb *VertexBuffer) SetData(values []float32, usage BufUsage) {
|
||||
|
||||
vb.Bind()
|
||||
|
||||
sizeInBytes := len(values) * 4
|
||||
if sizeInBytes == 0 {
|
||||
gl.BufferData(gl.ARRAY_BUFFER, 0, gl.Ptr(nil), usage.ToGL())
|
||||
} else {
|
||||
gl.BufferData(gl.ARRAY_BUFFER, sizeInBytes, gl.Ptr(&values[0]), usage.ToGL())
|
||||
}
|
||||
}
|
||||
|
||||
func (vb *VertexBuffer) GetLayout() []Element {
|
||||
e := make([]Element, len(vb.layout))
|
||||
copy(e, vb.layout)
|
||||
return e
|
||||
}
|
||||
|
||||
func (vb *VertexBuffer) SetLayout(layout ...Element) {
|
||||
|
||||
vb.Stride = 0
|
||||
vb.layout = layout
|
||||
|
||||
for i := 0; i < len(vb.layout); i++ {
|
||||
|
||||
vb.layout[i].Offset = int(vb.Stride)
|
||||
vb.Stride += vb.layout[i].Size()
|
||||
}
|
||||
}
|
||||
|
||||
func NewVertexBuffer(layout ...Element) VertexBuffer {
|
||||
|
||||
vb := VertexBuffer{}
|
||||
|
||||
gl.GenBuffers(1, &vb.Id)
|
||||
if vb.Id == 0 {
|
||||
logging.ErrLog.Panicln("Failed to create OpenGL buffer")
|
||||
}
|
||||
|
||||
vb.SetLayout(layout...)
|
||||
return vb
|
||||
}
|
||||
100
camera/camera.go
Executable file
@ -0,0 +1,100 @@
|
||||
package camera
|
||||
|
||||
import (
|
||||
"github.com/bloeys/gglm/gglm"
|
||||
)
|
||||
|
||||
type Type int32
|
||||
|
||||
const (
|
||||
Type_Unknown Type = iota
|
||||
Type_Perspective
|
||||
Type_Orthographic
|
||||
)
|
||||
|
||||
type Camera struct {
|
||||
Type Type
|
||||
|
||||
Pos gglm.Vec3
|
||||
Forward gglm.Vec3
|
||||
WorldUp gglm.Vec3
|
||||
|
||||
NearClip float32
|
||||
FarClip float32
|
||||
|
||||
// Perspective data
|
||||
Fov float32
|
||||
AspectRatio float32
|
||||
|
||||
// Ortho data
|
||||
Left, Right, Top, Bottom float32
|
||||
|
||||
// Matrices
|
||||
ViewMat gglm.Mat4
|
||||
ProjMat gglm.Mat4
|
||||
}
|
||||
|
||||
// Update recalculates view matrix and projection matrix.
|
||||
// Should be called whenever a camera parameter changes
|
||||
func (c *Camera) Update() {
|
||||
|
||||
c.ViewMat = gglm.LookAtRH(&c.Pos, c.Pos.Clone().Add(&c.Forward), &c.WorldUp).Mat4
|
||||
|
||||
if c.Type == Type_Perspective {
|
||||
c.ProjMat = gglm.Perspective(c.Fov, c.AspectRatio, c.NearClip, c.FarClip)
|
||||
} else {
|
||||
c.ProjMat = gglm.Ortho(c.Left, c.Right, c.Top, c.Bottom, c.NearClip, c.FarClip).Mat4
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateRotation calculates a new forward vector and then calls camera.Update()
|
||||
func (c *Camera) UpdateRotation(pitch, yaw float32) {
|
||||
|
||||
dir := gglm.NewVec3(
|
||||
gglm.Cos32(yaw)*gglm.Cos32(pitch),
|
||||
gglm.Sin32(pitch),
|
||||
gglm.Sin32(yaw)*gglm.Cos32(pitch),
|
||||
)
|
||||
c.Forward = *dir.Normalize()
|
||||
c.Update()
|
||||
}
|
||||
|
||||
func NewPerspective(pos, forward, worldUp *gglm.Vec3, nearClip, farClip, fovRadians, aspectRatio float32) Camera {
|
||||
|
||||
cam := Camera{
|
||||
Type: Type_Perspective,
|
||||
Pos: *pos,
|
||||
Forward: *forward,
|
||||
WorldUp: *worldUp,
|
||||
|
||||
NearClip: nearClip,
|
||||
FarClip: farClip,
|
||||
|
||||
Fov: fovRadians,
|
||||
AspectRatio: aspectRatio,
|
||||
}
|
||||
cam.Update()
|
||||
|
||||
return cam
|
||||
}
|
||||
|
||||
func NewOrthographic(pos, forward, worldUp *gglm.Vec3, nearClip, farClip, left, right, top, bottom float32) Camera {
|
||||
|
||||
cam := Camera{
|
||||
Type: Type_Orthographic,
|
||||
Pos: *pos,
|
||||
Forward: *forward,
|
||||
WorldUp: *worldUp,
|
||||
|
||||
NearClip: nearClip,
|
||||
FarClip: farClip,
|
||||
|
||||
Left: left,
|
||||
Right: right,
|
||||
Top: top,
|
||||
Bottom: bottom,
|
||||
}
|
||||
cam.Update()
|
||||
|
||||
return cam
|
||||
}
|
||||
204
engine/engine.go
@ -1,32 +1,45 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"runtime"
|
||||
|
||||
"github.com/bloeys/nmage/asserts"
|
||||
imgui "github.com/AllenDang/cimgui-go"
|
||||
"github.com/bloeys/nmage/assert"
|
||||
"github.com/bloeys/nmage/assets"
|
||||
"github.com/bloeys/nmage/input"
|
||||
"github.com/bloeys/nmage/renderer"
|
||||
"github.com/bloeys/nmage/timing"
|
||||
nmageimgui "github.com/bloeys/nmage/ui/imgui"
|
||||
"github.com/go-gl/gl/v4.1-core/gl"
|
||||
"github.com/inkyblackness/imgui-go/v4"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
var (
|
||||
isInited = false
|
||||
|
||||
isSdlButtonLeftDown = false
|
||||
isSdlButtonMiddleDown = false
|
||||
isSdlButtonRightDown = false
|
||||
|
||||
ImguiRelativeMouseModePosX float32
|
||||
ImguiRelativeMouseModePosY float32
|
||||
)
|
||||
|
||||
type Window struct {
|
||||
SDLWin *sdl.Window
|
||||
GlCtx sdl.GLContext
|
||||
EventCallbacks []func(sdl.Event)
|
||||
Rend renderer.Render
|
||||
}
|
||||
|
||||
func (w *Window) handleInputs() {
|
||||
|
||||
input.EventLoopStart()
|
||||
imIO := imgui.CurrentIO()
|
||||
imIo := imgui.CurrentIO()
|
||||
|
||||
imguiCaptureMouse := imIo.WantCaptureMouse()
|
||||
imguiCaptureKeyboard := imIo.WantCaptureKeyboard()
|
||||
|
||||
input.EventLoopStart(imguiCaptureMouse, imguiCaptureKeyboard)
|
||||
|
||||
for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
|
||||
|
||||
@ -41,29 +54,42 @@ func (w *Window) handleInputs() {
|
||||
case *sdl.MouseWheelEvent:
|
||||
|
||||
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 e.Type == sdl.KEYDOWN {
|
||||
imIO.KeyPress(int(e.Keysym.Scancode))
|
||||
} else if e.Type == sdl.KEYUP {
|
||||
imIO.KeyRelease(int(e.Keysym.Scancode))
|
||||
}
|
||||
// Send modifier key updates to imgui (based on the imgui SDL backend)
|
||||
imIo.AddKeyEvent(imgui.ModCtrl, e.Keysym.Mod&sdl.KMOD_CTRL != 0)
|
||||
imIo.AddKeyEvent(imgui.ModShift, e.Keysym.Mod&sdl.KMOD_SHIFT != 0)
|
||||
imIo.AddKeyEvent(imgui.ModAlt, e.Keysym.Mod&sdl.KMOD_ALT != 0)
|
||||
imIo.AddKeyEvent(imgui.ModSuper, e.Keysym.Mod&sdl.KMOD_GUI != 0)
|
||||
|
||||
imIo.AddKeyEvent(nmageimgui.SdlScancodeToImGuiKey(e.Keysym.Scancode), e.Type == sdl.KEYDOWN)
|
||||
|
||||
case *sdl.TextInputEvent:
|
||||
imIO.AddInputCharacters(string(e.Text[:]))
|
||||
imIo.AddInputCharactersUTF8(e.GetText())
|
||||
|
||||
case *sdl.MouseButtonEvent:
|
||||
|
||||
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)
|
||||
|
||||
case *sdl.WindowEvent:
|
||||
|
||||
if e.Event == sdl.WINDOWEVENT_SIZE_CHANGED {
|
||||
w.handleWindowResize()
|
||||
}
|
||||
@ -73,17 +99,17 @@ func (w *Window) handleInputs() {
|
||||
}
|
||||
}
|
||||
|
||||
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)})
|
||||
}
|
||||
|
||||
// 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.SetMousePosition(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))
|
||||
|
||||
imIO.KeyShift(sdl.SCANCODE_LSHIFT, sdl.SCANCODE_RSHIFT)
|
||||
imIO.KeyCtrl(sdl.SCANCODE_LCTRL, sdl.SCANCODE_RCTRL)
|
||||
imIO.KeyAlt(sdl.SCANCODE_LALT, sdl.SCANCODE_RALT)
|
||||
imIo.SetMouseButtonDown(imgui.MouseButtonLeft, isSdlButtonLeftDown)
|
||||
imIo.SetMouseButtonDown(imgui.MouseButtonRight, isSdlButtonRightDown)
|
||||
imIo.SetMouseButtonDown(imgui.MouseButtonMiddle, isSdlButtonMiddleDown)
|
||||
}
|
||||
|
||||
func (w *Window) handleWindowResize() {
|
||||
@ -112,7 +138,7 @@ func Init() error {
|
||||
|
||||
func initSDL() error {
|
||||
|
||||
err := sdl.Init(sdl.INIT_EVERYTHING)
|
||||
err := sdl.Init(sdl.INIT_TIMER | sdl.INIT_VIDEO)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -122,56 +148,66 @@ 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
|
||||
}
|
||||
|
||||
func CreateOpenGLWindow(title string, x, y, width, height int32, flags WindowFlags, rend renderer.Render) (*Window, error) {
|
||||
return createWindow(title, x, y, width, height, WindowFlags_OPENGL|flags, rend)
|
||||
func CreateOpenGLWindow(title string, x, y, width, height int32, flags WindowFlags) (Window, error) {
|
||||
return createWindow(title, x, y, width, height, WindowFlags_OPENGL|flags)
|
||||
}
|
||||
|
||||
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)
|
||||
func CreateOpenGLWindowCentered(title string, width, height int32, flags WindowFlags) (Window, error) {
|
||||
return createWindow(title, sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED, width, height, WindowFlags_OPENGL|flags)
|
||||
}
|
||||
|
||||
func createWindow(title string, x, y, width, height int32, flags WindowFlags, rend renderer.Render) (*Window, error) {
|
||||
func createWindow(title string, x, y, width, height int32, flags WindowFlags) (Window, error) {
|
||||
|
||||
asserts.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 {
|
||||
return nil, err
|
||||
}
|
||||
win := &Window{
|
||||
SDLWin: sdlWin,
|
||||
win := Window{
|
||||
SDLWin: nil,
|
||||
EventCallbacks: make([]func(sdl.Event), 0),
|
||||
Rend: rend,
|
||||
}
|
||||
|
||||
win.GlCtx, err = sdlWin.GLCreateContext()
|
||||
var err error
|
||||
|
||||
win.SDLWin, err = sdl.CreateWindow(title, x, y, width, height, uint32(flags))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return win, err
|
||||
}
|
||||
|
||||
win.GlCtx, err = win.SDLWin.GLCreateContext()
|
||||
if err != nil {
|
||||
return win, err
|
||||
}
|
||||
|
||||
err = initOpenGL()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return win, err
|
||||
}
|
||||
|
||||
setupDefaultTextures()
|
||||
|
||||
// Get rid of the blinding white startup screen (unfortunately there is still one frame of white)
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
|
||||
win.SDLWin.GLSwap()
|
||||
|
||||
return win, err
|
||||
}
|
||||
|
||||
@ -182,19 +218,82 @@ func initOpenGL() error {
|
||||
}
|
||||
|
||||
gl.Enable(gl.DEPTH_TEST)
|
||||
gl.Enable(gl.STENCIL_TEST)
|
||||
gl.Enable(gl.CULL_FACE)
|
||||
gl.CullFace(gl.BACK)
|
||||
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 setupDefaultTextures() error {
|
||||
|
||||
// 1x1 black texture
|
||||
defaultBlackImg := image.NewNRGBA(image.Rect(0, 0, 1, 1))
|
||||
defaultBlackImg.Set(0, 0, color.NRGBA{R: 0, G: 0, B: 0, A: 1})
|
||||
defaultBlackImgTex, err := assets.LoadTextureInMemPngImg(defaultBlackImg, &assets.TextureLoadOptions{NoSrgba: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
assets.DefaultBlackTexId = defaultBlackImgTex
|
||||
|
||||
// 1x1 white texture
|
||||
defaultWhiteImg := image.NewNRGBA(image.Rect(0, 0, 1, 1))
|
||||
defaultWhiteImg.Set(0, 0, color.NRGBA{R: 255, G: 255, B: 255, A: 1})
|
||||
defaultWhiteImgTex, err := assets.LoadTextureInMemPngImg(defaultWhiteImg, &assets.TextureLoadOptions{NoSrgba: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
assets.DefaultWhiteTexId = defaultWhiteImgTex
|
||||
|
||||
// Default diffuse
|
||||
assets.DefaultDiffuseTexId = defaultWhiteImgTex
|
||||
|
||||
// Default specular
|
||||
assets.DefaultSpecularTexId = defaultBlackImgTex
|
||||
|
||||
// Default Normal map which is created to be RGB(0.5,0.5,1), which when multiplied by TBN matrix gives the vertex normal.
|
||||
// 128 is better than 127 for normal maps. See 'Flat Color' section here: http://wiki.polycount.com/wiki/Normal_map
|
||||
// Basically, 127 can create seams while 128 looks correct
|
||||
defaultNormalMapImg := image.NewNRGBA(image.Rect(0, 0, 1, 1))
|
||||
defaultNormalMapImg.Set(0, 0, color.NRGBA{R: 128, G: 128, B: 255, A: 1})
|
||||
defaultNormalMapTex, err := assets.LoadTextureInMemPngImg(defaultNormalMapImg, &assets.TextureLoadOptions{NoSrgba: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
assets.DefaultNormalTexId = defaultNormalMapTex
|
||||
|
||||
// Default emission
|
||||
assets.DefaultEmissionTexId = defaultBlackImgTex
|
||||
|
||||
assert.T(assets.DefaultBlackTexId.TexID != 0, "The default black texture handle is zero. Either texture wasn't created or handle wasn't updated")
|
||||
assert.T(assets.DefaultWhiteTexId.TexID != 0, "The default white texture handle is zero. Either texture wasn't created or handle wasn't updated")
|
||||
assert.T(assets.DefaultDiffuseTexId.TexID != 0, "The default diffuse texture handle is zero. Either texture wasn't created or handle wasn't updated")
|
||||
assert.T(assets.DefaultSpecularTexId.TexID != 0, "The default specular texture handle is zero. Either texture wasn't created or handle wasn't updated")
|
||||
assert.T(assets.DefaultNormalTexId.TexID != 0, "The default normal texture handle is zero. Either texture wasn't created or handle wasn't updated")
|
||||
assert.T(assets.DefaultEmissionTexId.TexID != 0, "The default emission texture handle is zero. Either texture wasn't created or handle wasn't updated")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetSrgbFramebuffer(isEnabled bool) {
|
||||
|
||||
if isEnabled {
|
||||
gl.Enable(gl.FRAMEBUFFER_SRGB)
|
||||
} else {
|
||||
gl.Disable(gl.FRAMEBUFFER_SRGB)
|
||||
}
|
||||
}
|
||||
|
||||
func SetVSync(enabled bool) {
|
||||
asserts.T(isInited, "engine.Init was not called!")
|
||||
|
||||
if enabled {
|
||||
sdl.GLSetSwapInterval(1)
|
||||
@ -202,3 +301,12 @@ func SetVSync(enabled bool) {
|
||||
sdl.GLSetSwapInterval(0)
|
||||
}
|
||||
}
|
||||
|
||||
func SetMSAA(isEnabled bool) {
|
||||
|
||||
if isEnabled {
|
||||
gl.Enable(gl.MULTISAMPLE)
|
||||
} else {
|
||||
gl.Disable(gl.MULTISAMPLE)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"github.com/bloeys/nmage/renderer"
|
||||
"github.com/bloeys/nmage/timing"
|
||||
nmageimgui "github.com/bloeys/nmage/ui/imgui"
|
||||
"github.com/go-gl/gl/v4.1-core/gl"
|
||||
@ -20,22 +21,28 @@ type Game interface {
|
||||
DeInit()
|
||||
}
|
||||
|
||||
func Run(g Game, w *Window, ui nmageimgui.ImguiInfo) {
|
||||
func Run(g Game, w *Window, rend renderer.Render, ui nmageimgui.ImguiInfo) {
|
||||
|
||||
isRunning = true
|
||||
|
||||
// Run init with an active Imgui frame to allow init full imgui access
|
||||
timing.FrameStarted()
|
||||
w.handleInputs()
|
||||
|
||||
width, height := w.SDLWin.GetSize()
|
||||
ui.FrameStart(float32(width), float32(height))
|
||||
|
||||
g.Init()
|
||||
|
||||
//Simulate an imgui frame during init so any imgui calls are allowed within init
|
||||
tempWidth, tempHeight := w.SDLWin.GetSize()
|
||||
tempFBWidth, tempFBHeight := w.SDLWin.GLGetDrawableSize()
|
||||
ui.FrameStart(float32(tempWidth), float32(tempHeight))
|
||||
ui.Render(float32(tempWidth), float32(tempHeight), tempFBWidth, tempFBHeight)
|
||||
fbWidth, fbHeight := w.SDLWin.GLGetDrawableSize()
|
||||
ui.Render(float32(width), float32(height), fbWidth, fbHeight)
|
||||
|
||||
timing.FrameEnded()
|
||||
|
||||
for isRunning {
|
||||
|
||||
//PERF: Cache these
|
||||
width, height := w.SDLWin.GetSize()
|
||||
fbWidth, fbHeight := w.SDLWin.GLGetDrawableSize()
|
||||
width, height = w.SDLWin.GetSize()
|
||||
fbWidth, fbHeight = w.SDLWin.GLGetDrawableSize()
|
||||
|
||||
timing.FrameStarted()
|
||||
w.handleInputs()
|
||||
@ -43,13 +50,13 @@ func Run(g Game, w *Window, ui nmageimgui.ImguiInfo) {
|
||||
|
||||
g.Update()
|
||||
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
|
||||
g.Render()
|
||||
ui.Render(float32(width), float32(height), fbWidth, fbHeight)
|
||||
w.SDLWin.GLSwap()
|
||||
|
||||
g.FrameEnd()
|
||||
w.Rend.FrameEnd()
|
||||
rend.FrameEnd()
|
||||
timing.FrameEnded()
|
||||
}
|
||||
|
||||
|
||||
26
entity/base_comp.go
Executable file
@ -0,0 +1,26 @@
|
||||
package entity
|
||||
|
||||
import "github.com/bloeys/nmage/registry"
|
||||
|
||||
var _ Comp = &BaseComp{}
|
||||
|
||||
type BaseComp struct {
|
||||
Handle registry.Handle
|
||||
}
|
||||
|
||||
func (b BaseComp) baseComp() {
|
||||
}
|
||||
|
||||
func (b *BaseComp) Init(parentHandle registry.Handle) {
|
||||
b.Handle = parentHandle
|
||||
}
|
||||
|
||||
func (b BaseComp) Name() string {
|
||||
return "Base Component"
|
||||
}
|
||||
|
||||
func (b BaseComp) Update() {
|
||||
}
|
||||
|
||||
func (b BaseComp) Destroy() {
|
||||
}
|
||||
73
entity/comp.go
Executable file
@ -0,0 +1,73 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"github.com/bloeys/nmage/assert"
|
||||
"github.com/bloeys/nmage/registry"
|
||||
)
|
||||
|
||||
type Comp interface {
|
||||
// This ensures that implementors of the Comp interface
|
||||
// always embed BaseComp
|
||||
baseComp()
|
||||
|
||||
Name() string
|
||||
Init(parentHandle registry.Handle)
|
||||
Update()
|
||||
Destroy()
|
||||
}
|
||||
|
||||
func NewCompContainer() CompContainer {
|
||||
return CompContainer{Comps: []Comp{}}
|
||||
}
|
||||
|
||||
type CompContainer struct {
|
||||
Comps []Comp
|
||||
}
|
||||
|
||||
func AddComp[T Comp](entityHandle registry.Handle, cc *CompContainer, c T) {
|
||||
|
||||
assert.T(!HasComp[T](cc), "Entity with id '%v' already has component of type '%T'", entityHandle, c)
|
||||
|
||||
cc.Comps = append(cc.Comps, c)
|
||||
c.Init(entityHandle)
|
||||
}
|
||||
|
||||
func HasComp[T Comp](e *CompContainer) bool {
|
||||
|
||||
for i := 0; i < len(e.Comps); i++ {
|
||||
|
||||
_, ok := e.Comps[i].(T)
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func GetComp[T Comp](e *CompContainer) (out T) {
|
||||
|
||||
for i := 0; i < len(e.Comps); i++ {
|
||||
|
||||
comp, ok := e.Comps[i].(T)
|
||||
if ok {
|
||||
return comp
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// DestroyComp calls Destroy on the component and then removes it from the entities component list
|
||||
func DestroyComp[T Comp](e *CompContainer) {
|
||||
|
||||
for i := 0; i < len(e.Comps); i++ {
|
||||
|
||||
comp, ok := e.Comps[i].(T)
|
||||
if ok {
|
||||
comp.Destroy()
|
||||
e.Comps = append(e.Comps[:i], e.Comps[i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
7
entity/entity.go
Executable file
@ -0,0 +1,7 @@
|
||||
package entity
|
||||
|
||||
import "github.com/bloeys/nmage/registry"
|
||||
|
||||
type Entity interface {
|
||||
GetHandle() registry.Handle
|
||||
}
|
||||
21
go.mod
@ -1,13 +1,22 @@
|
||||
module github.com/bloeys/nmage
|
||||
|
||||
go 1.17
|
||||
go 1.22
|
||||
|
||||
require github.com/veandco/go-sdl2 v0.4.10
|
||||
require github.com/veandco/go-sdl2 v0.4.35
|
||||
|
||||
require github.com/go-gl/gl v0.0.0-20211025173605-bda47ffaa784
|
||||
require github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6
|
||||
|
||||
require (
|
||||
github.com/bloeys/assimp-go v0.4.2
|
||||
github.com/bloeys/gglm v0.41.10
|
||||
github.com/inkyblackness/imgui-go/v4 v4.3.0
|
||||
github.com/bloeys/assimp-go v0.4.4
|
||||
github.com/bloeys/gglm v0.50.0
|
||||
)
|
||||
|
||||
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
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
|
||||
)
|
||||
|
||||
61
go.sum
@ -1,19 +1,42 @@
|
||||
github.com/bloeys/assimp-go v0.4.2 h1:ArVK74BCFcTO/rCGj2NgZG9xtbjnJdEn5npIeJx1Z04=
|
||||
github.com/bloeys/assimp-go v0.4.2/go.mod h1:my3yRxT7CfOztmvi+0svmwbaqw0KFrxaHxncoyaEIP0=
|
||||
github.com/bloeys/gglm v0.3.1 h1:Sy9upW7SBsBfDXrSmEhid3aQ+7J7itej+upwcxOnPMQ=
|
||||
github.com/bloeys/gglm v0.3.1/go.mod h1:qwJQ0WzV191wAMwlGicbfbChbKoSedMk7gFFX6GnyOk=
|
||||
github.com/bloeys/gglm v0.41.10 h1:R9FMiI+VQVXAI+vDwCB7z9xqzy5VAR1657u8TQTDNKA=
|
||||
github.com/bloeys/gglm v0.41.10/go.mod h1:qwJQ0WzV191wAMwlGicbfbChbKoSedMk7gFFX6GnyOk=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-gl/gl v0.0.0-20211025173605-bda47ffaa784 h1:1Zi56D0LNfvkzM+BdoxKryvUEdyWO7LP8oRT+oSYJW0=
|
||||
github.com/go-gl/gl v0.0.0-20211025173605-bda47ffaa784/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
|
||||
github.com/inkyblackness/imgui-go/v4 v4.3.0 h1:iyAzqWXq/dG5+6ckDPhGivtrIo6AywGQMvENKzun04s=
|
||||
github.com/inkyblackness/imgui-go/v4 v4.3.0/go.mod h1:g8SAGtOYUP7rYaOB2AsVKCEHmPMDmJKgt4z6d+flhb0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/veandco/go-sdl2 v0.4.10 h1:8QoD2bhWl7SbQDflIAUYWfl9Vq+mT8/boJFAUzAScgY=
|
||||
github.com/veandco/go-sdl2 v0.4.10/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY=
|
||||
github.com/AllenDang/cimgui-go v0.0.0-20230720025235-f2ff398a66b2 h1:3HA/5qD8Rimxz/y1sLyVaM7ws1dzjXzMt4hOBiwHggo=
|
||||
github.com/AllenDang/cimgui-go v0.0.0-20230720025235-f2ff398a66b2/go.mod h1:iNfbIyOBN8k3XScMxULbrwYbPsXEAUD0Jb6UwrspQb8=
|
||||
github.com/bloeys/assimp-go v0.4.4 h1:Yn5e/RpE0Oes0YMBy8O7KkwAO4R/RpgrZPJCt08dVIU=
|
||||
github.com/bloeys/assimp-go v0.4.4/go.mod h1:my3yRxT7CfOztmvi+0svmwbaqw0KFrxaHxncoyaEIP0=
|
||||
github.com/bloeys/gglm v0.50.0 h1:DlGLp9z8KMNx+hNR6PjnPmC0HjDRC19QwAKL1iwhOxs=
|
||||
github.com/bloeys/gglm v0.50.0/go.mod h1:5s2U/NiOrtJyrSup1j8wK+QOBmGIO03ub0LHMvuNSK8=
|
||||
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/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
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=
|
||||
|
||||
259
input/input.go
@ -1,6 +1,23 @@
|
||||
// The input package provides an interface to mouse and keyboard inputs
|
||||
// like key clicks and releases, along with some higher level constructs like
|
||||
// pressed/released this frames, double clicks, and normalized inputs.
|
||||
//
|
||||
// The input package has two sets of functions for most cases, where one
|
||||
// is in the form 'xy' and the other 'xyCaptured'. The captured form
|
||||
// always returns normal events even if the mouse or keyboard are captured
|
||||
// by the UI system. The 'xy' form however will return zero/false if the
|
||||
// respective input device is currently captured (with the exception of mouse position, that is always correctly returned).
|
||||
//
|
||||
// For most cases, you want to use the 'xy' form. For example, you only want to receive
|
||||
// key down events for game character movement when the UI isn't capturing the keyboard,
|
||||
// because otherwise the character will move while typing in a UI textbox.
|
||||
//
|
||||
// The functions IsMouseCaptured and IsKeyboardCaptured are also available.
|
||||
package input
|
||||
|
||||
import "github.com/veandco/go-sdl2/sdl"
|
||||
import (
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
type keyState struct {
|
||||
Key sdl.Keycode
|
||||
@ -31,24 +48,33 @@ type mouseWheelState struct {
|
||||
}
|
||||
|
||||
var (
|
||||
keyMap = make(map[sdl.Keycode]*keyState)
|
||||
mouseBtnMap = make(map[int]*mouseBtnState)
|
||||
mouseMotion = mouseMotionState{}
|
||||
mouseWheel = mouseWheelState{}
|
||||
quitRequested bool
|
||||
mouseWheel = mouseWheelState{}
|
||||
mouseMotion = mouseMotionState{}
|
||||
mouseBtnMap = make(map[int]mouseBtnState)
|
||||
keyMap = make(map[sdl.Keycode]keyState)
|
||||
|
||||
isQuitRequested bool
|
||||
isMouseCaptured bool
|
||||
isKeyboardCaptured bool
|
||||
)
|
||||
|
||||
func EventLoopStart() {
|
||||
func EventLoopStart(mouseGotCaptured, keyboardGotCaptured bool) {
|
||||
|
||||
for _, v := range keyMap {
|
||||
isMouseCaptured = mouseGotCaptured
|
||||
isKeyboardCaptured = keyboardGotCaptured
|
||||
|
||||
// Update per-frame state
|
||||
for k, v := range keyMap {
|
||||
v.IsPressedThisFrame = false
|
||||
v.IsReleasedThisFrame = false
|
||||
keyMap[k] = v
|
||||
}
|
||||
|
||||
for _, v := range mouseBtnMap {
|
||||
for k, v := range mouseBtnMap {
|
||||
v.IsPressedThisFrame = false
|
||||
v.IsReleasedThisFrame = false
|
||||
v.IsDoubleClicked = false
|
||||
mouseBtnMap[k] = v
|
||||
}
|
||||
|
||||
mouseMotion.XDelta = 0
|
||||
@ -57,42 +83,62 @@ func EventLoopStart() {
|
||||
mouseWheel.XDelta = 0
|
||||
mouseWheel.YDelta = 0
|
||||
|
||||
quitRequested = false
|
||||
isQuitRequested = false
|
||||
}
|
||||
|
||||
func ClearKeyboardState() {
|
||||
clear(keyMap)
|
||||
}
|
||||
|
||||
func ClearMouseState() {
|
||||
clear(mouseBtnMap)
|
||||
mouseMotion = mouseMotionState{}
|
||||
mouseWheel = mouseWheelState{}
|
||||
}
|
||||
|
||||
func HandleQuitEvent(e *sdl.QuitEvent) {
|
||||
quitRequested = true
|
||||
isQuitRequested = true
|
||||
}
|
||||
|
||||
func IsMouseCaptured() bool {
|
||||
return isMouseCaptured
|
||||
}
|
||||
|
||||
func IsKeyboardCaptured() bool {
|
||||
return isKeyboardCaptured
|
||||
}
|
||||
|
||||
func IsQuitClicked() bool {
|
||||
return quitRequested
|
||||
return isQuitRequested
|
||||
}
|
||||
|
||||
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,18 +155,36 @@ 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 regardless of whether the mouse is captured or not
|
||||
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) {
|
||||
|
||||
if isMouseCaptured {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
return GetMouseMotionCaptured()
|
||||
}
|
||||
|
||||
func GetMouseMotionCaptured() (xDelta, yDelta int32) {
|
||||
return mouseMotion.XDelta, mouseMotion.YDelta
|
||||
}
|
||||
|
||||
func GetMouseMotionNorm() (xDelta, yDelta int32) {
|
||||
|
||||
if isMouseCaptured {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
return GetMouseMotionNormCaptured()
|
||||
}
|
||||
|
||||
func GetMouseMotionNormCaptured() (xDelta, yDelta int32) {
|
||||
|
||||
x, y := mouseMotion.XDelta, mouseMotion.YDelta
|
||||
if x > 0 {
|
||||
x = 1
|
||||
@ -138,12 +202,31 @@ func GetMouseMotionNorm() (xDelta, yDelta int32) {
|
||||
}
|
||||
|
||||
func GetMouseWheelMotion() (xDelta, yDelta int32) {
|
||||
|
||||
if isMouseCaptured {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
return GetMouseWheelMotionCaptured()
|
||||
}
|
||||
|
||||
func GetMouseWheelMotionCaptured() (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 isMouseCaptured {
|
||||
return 0
|
||||
}
|
||||
|
||||
return GetMouseWheelXNormCaptured()
|
||||
}
|
||||
|
||||
// GetMouseWheelXNormCaptured returns 1 if mouse wheel xDelta > 0, -1 if xDelta < 0, and 0 otherwise
|
||||
func GetMouseWheelXNormCaptured() int32 {
|
||||
|
||||
if mouseWheel.XDelta > 0 {
|
||||
return 1
|
||||
} else if mouseWheel.XDelta < 0 {
|
||||
@ -153,9 +236,19 @@ func GetMouseWheelXNorm() int32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
//returns 1 if mouse wheel yDelta > 0, -1 if yDelta < 0, and 0 otherwise
|
||||
// GetMouseWheelYNorm returns 1 if mouse wheel yDelta > 0, -1 if yDelta < 0, and 0 otherwise
|
||||
func GetMouseWheelYNorm() int32 {
|
||||
|
||||
if isMouseCaptured {
|
||||
return 0
|
||||
}
|
||||
|
||||
return GetMouseWheelYNormCaptured()
|
||||
}
|
||||
|
||||
// GetMouseWheelYNormCaptured returns 1 if mouse wheel yDelta > 0, -1 if yDelta < 0, and 0 otherwise
|
||||
func GetMouseWheelYNormCaptured() int32 {
|
||||
|
||||
if mouseWheel.YDelta > 0 {
|
||||
return 1
|
||||
} else if mouseWheel.YDelta < 0 {
|
||||
@ -167,8 +260,17 @@ func GetMouseWheelYNorm() int32 {
|
||||
|
||||
func KeyClicked(kc sdl.Keycode) bool {
|
||||
|
||||
ks := keyMap[kc]
|
||||
if ks == nil {
|
||||
if isKeyboardCaptured {
|
||||
return false
|
||||
}
|
||||
|
||||
return KeyClickedCaptured(kc)
|
||||
}
|
||||
|
||||
func KeyClickedCaptured(kc sdl.Keycode) bool {
|
||||
|
||||
ks, ok := keyMap[kc]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -177,8 +279,17 @@ func KeyClicked(kc sdl.Keycode) bool {
|
||||
|
||||
func KeyReleased(kc sdl.Keycode) bool {
|
||||
|
||||
ks := keyMap[kc]
|
||||
if ks == nil {
|
||||
if isKeyboardCaptured {
|
||||
return false
|
||||
}
|
||||
|
||||
return KeyReleasedCaptured(kc)
|
||||
}
|
||||
|
||||
func KeyReleasedCaptured(kc sdl.Keycode) bool {
|
||||
|
||||
ks, ok := keyMap[kc]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -187,8 +298,17 @@ func KeyReleased(kc sdl.Keycode) bool {
|
||||
|
||||
func KeyDown(kc sdl.Keycode) bool {
|
||||
|
||||
ks := keyMap[kc]
|
||||
if ks == nil {
|
||||
if isKeyboardCaptured {
|
||||
return false
|
||||
}
|
||||
|
||||
return KeyDownCaptured(kc)
|
||||
}
|
||||
|
||||
func KeyDownCaptured(kc sdl.Keycode) bool {
|
||||
|
||||
ks, ok := keyMap[kc]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -197,8 +317,17 @@ func KeyDown(kc sdl.Keycode) bool {
|
||||
|
||||
func KeyUp(kc sdl.Keycode) bool {
|
||||
|
||||
ks := keyMap[kc]
|
||||
if ks == nil {
|
||||
if isKeyboardCaptured {
|
||||
return false
|
||||
}
|
||||
|
||||
return KeyUpCaptured(kc)
|
||||
}
|
||||
|
||||
func KeyUpCaptured(kc sdl.Keycode) bool {
|
||||
|
||||
ks, ok := keyMap[kc]
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
@ -207,8 +336,17 @@ func KeyUp(kc sdl.Keycode) bool {
|
||||
|
||||
func MouseClicked(mb int) bool {
|
||||
|
||||
btn := mouseBtnMap[mb]
|
||||
if btn == nil {
|
||||
if isMouseCaptured {
|
||||
return false
|
||||
}
|
||||
|
||||
return MouseClickedCaptued(mb)
|
||||
}
|
||||
|
||||
func MouseClickedCaptued(mb int) bool {
|
||||
|
||||
btn, ok := mouseBtnMap[mb]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -217,8 +355,17 @@ func MouseClicked(mb int) bool {
|
||||
|
||||
func MouseDoubleClicked(mb int) bool {
|
||||
|
||||
btn := mouseBtnMap[mb]
|
||||
if btn == nil {
|
||||
if isMouseCaptured {
|
||||
return false
|
||||
}
|
||||
|
||||
return MouseDoubleClickedCaptured(mb)
|
||||
}
|
||||
|
||||
func MouseDoubleClickedCaptured(mb int) bool {
|
||||
|
||||
btn, ok := mouseBtnMap[mb]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -226,8 +373,18 @@ func MouseDoubleClicked(mb int) bool {
|
||||
}
|
||||
|
||||
func MouseReleased(mb int) bool {
|
||||
btn := mouseBtnMap[mb]
|
||||
if btn == nil {
|
||||
|
||||
if isMouseCaptured {
|
||||
return false
|
||||
}
|
||||
|
||||
return MouseReleasedCaptured(mb)
|
||||
}
|
||||
|
||||
func MouseReleasedCaptured(mb int) bool {
|
||||
|
||||
btn, ok := mouseBtnMap[mb]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -236,8 +393,17 @@ func MouseReleased(mb int) bool {
|
||||
|
||||
func MouseDown(mb int) bool {
|
||||
|
||||
btn := mouseBtnMap[mb]
|
||||
if btn == nil {
|
||||
if isMouseCaptured {
|
||||
return false
|
||||
}
|
||||
|
||||
return MouseDownCaptued(mb)
|
||||
}
|
||||
|
||||
func MouseDownCaptued(mb int) bool {
|
||||
|
||||
btn, ok := mouseBtnMap[mb]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -246,8 +412,17 @@ func MouseDown(mb int) bool {
|
||||
|
||||
func MouseUp(mb int) bool {
|
||||
|
||||
btn := mouseBtnMap[mb]
|
||||
if btn == nil {
|
||||
if isMouseCaptured {
|
||||
return false
|
||||
}
|
||||
|
||||
return MouseUpCaptured(mb)
|
||||
}
|
||||
|
||||
func MouseUpCaptured(mb int) bool {
|
||||
|
||||
btn, ok := mouseBtnMap[mb]
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
17
level/level.go
Executable file
@ -0,0 +1,17 @@
|
||||
package level
|
||||
|
||||
import (
|
||||
"github.com/bloeys/nmage/assert"
|
||||
)
|
||||
|
||||
type Level struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func NewLevel(name string) *Level {
|
||||
|
||||
assert.T(name != "", "Level name can not be empty")
|
||||
return &Level{
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
@ -1,37 +1,142 @@
|
||||
package materials
|
||||
|
||||
import (
|
||||
_ "unsafe"
|
||||
|
||||
"github.com/bloeys/gglm/gglm"
|
||||
"github.com/bloeys/nmage/asserts"
|
||||
"github.com/bloeys/nmage/assert"
|
||||
"github.com/bloeys/nmage/assets"
|
||||
"github.com/bloeys/nmage/logging"
|
||||
"github.com/bloeys/nmage/shaders"
|
||||
"github.com/go-gl/gl/v4.1-core/gl"
|
||||
)
|
||||
|
||||
// @TODO: This noescape magic is to avoid heap allocations done when
|
||||
// passing vectors or matrices into cgo via set uniform calls.
|
||||
//
|
||||
// But I would rather this kind of stuff is done on the gl wrapper level.
|
||||
// Should we wrap the OpenGL APIs we use ourself?
|
||||
|
||||
var (
|
||||
lastMatId uint32
|
||||
)
|
||||
|
||||
type TextureSlot uint32
|
||||
|
||||
const (
|
||||
TextureSlot_Diffuse TextureSlot = 0
|
||||
TextureSlot_Specular TextureSlot = 1
|
||||
TextureSlot_Normal TextureSlot = 2
|
||||
TextureSlot_Emission TextureSlot = 3
|
||||
TextureSlot_Cubemap TextureSlot = 10
|
||||
TextureSlot_Cubemap_Array TextureSlot = 11
|
||||
TextureSlot_ShadowMap1 TextureSlot = 12
|
||||
TextureSlot_ShadowMap_Array1 TextureSlot = 13
|
||||
)
|
||||
|
||||
type MaterialSettings uint64
|
||||
|
||||
const (
|
||||
MaterialSettings_None MaterialSettings = iota
|
||||
MaterialSettings_HasModelMtx MaterialSettings = 1 << (iota - 1)
|
||||
MaterialSettings_HasNormalMtx
|
||||
)
|
||||
|
||||
func (ms *MaterialSettings) Set(flags MaterialSettings) {
|
||||
*ms |= flags
|
||||
}
|
||||
|
||||
func (ms *MaterialSettings) Remove(flags MaterialSettings) {
|
||||
*ms &= ^flags
|
||||
}
|
||||
|
||||
func (ms *MaterialSettings) Has(flags MaterialSettings) bool {
|
||||
return *ms&flags == flags
|
||||
}
|
||||
|
||||
type Material struct {
|
||||
Id uint32
|
||||
Name string
|
||||
ShaderProg shaders.ShaderProgram
|
||||
|
||||
DiffuseTex uint32
|
||||
Settings MaterialSettings
|
||||
|
||||
UnifLocs map[string]int32
|
||||
AttribLocs map[string]int32
|
||||
|
||||
// @TODO: Do this in a better way?. Perhaps something like how we do fbo attachments? Or keep it?
|
||||
// Phong shading
|
||||
DiffuseTex uint32
|
||||
SpecularTex uint32
|
||||
NormalTex uint32
|
||||
EmissionTex uint32
|
||||
|
||||
// Shininess of specular highlights
|
||||
Shininess float32
|
||||
|
||||
// Cubemaps
|
||||
CubemapTex uint32
|
||||
CubemapArrayTex uint32
|
||||
|
||||
// Shadowmaps
|
||||
ShadowMapTex1 uint32
|
||||
ShadowMapTexArray1 uint32
|
||||
}
|
||||
|
||||
func (m *Material) Bind() {
|
||||
|
||||
gl.UseProgram(m.ShaderProg.ID)
|
||||
m.ShaderProg.Bind()
|
||||
|
||||
gl.ActiveTexture(gl.TEXTURE0)
|
||||
gl.ActiveTexture(uint32(gl.TEXTURE0 + TextureSlot_Diffuse))
|
||||
gl.BindTexture(gl.TEXTURE_2D, m.DiffuseTex)
|
||||
|
||||
gl.ActiveTexture(uint32(gl.TEXTURE0 + TextureSlot_Specular))
|
||||
gl.BindTexture(gl.TEXTURE_2D, m.SpecularTex)
|
||||
|
||||
gl.ActiveTexture(uint32(gl.TEXTURE0 + TextureSlot_Normal))
|
||||
gl.BindTexture(gl.TEXTURE_2D, m.NormalTex)
|
||||
|
||||
gl.ActiveTexture(uint32(gl.TEXTURE0 + TextureSlot_Emission))
|
||||
gl.BindTexture(gl.TEXTURE_2D, m.EmissionTex)
|
||||
|
||||
// @TODO: Have defaults for these
|
||||
if m.CubemapTex != 0 {
|
||||
gl.ActiveTexture(uint32(gl.TEXTURE0 + TextureSlot_Cubemap))
|
||||
gl.BindTexture(gl.TEXTURE_CUBE_MAP, m.CubemapTex)
|
||||
}
|
||||
|
||||
if m.CubemapArrayTex != 0 {
|
||||
gl.ActiveTexture(uint32(gl.TEXTURE0 + TextureSlot_Cubemap_Array))
|
||||
gl.BindTexture(gl.TEXTURE_CUBE_MAP_ARRAY, m.CubemapArrayTex)
|
||||
}
|
||||
|
||||
if m.ShadowMapTex1 != 0 {
|
||||
gl.ActiveTexture(uint32(gl.TEXTURE0 + TextureSlot_ShadowMap1))
|
||||
gl.BindTexture(gl.TEXTURE_2D, m.ShadowMapTex1)
|
||||
}
|
||||
|
||||
if m.ShadowMapTexArray1 != 0 {
|
||||
gl.ActiveTexture(uint32(gl.TEXTURE0 + TextureSlot_ShadowMap_Array1))
|
||||
gl.BindTexture(gl.TEXTURE_2D_ARRAY, m.ShadowMapTexArray1)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Material) UnBind() {
|
||||
gl.UseProgram(0)
|
||||
}
|
||||
|
||||
//TODO: Should we unbind textures here? Are these two lines needed?
|
||||
// gl.ActiveTexture(gl.TEXTURE0)
|
||||
// gl.BindTexture(gl.TEXTURE_2D, 0)
|
||||
func (m *Material) SetUniformBlockBindingPoint(uniformBlockName string, bindPointIndex uint32) {
|
||||
|
||||
nullStr := gl.Str(uniformBlockName + "\x00")
|
||||
index := gl.GetUniformBlockIndex(m.ShaderProg.Id, nullStr)
|
||||
assert.T(
|
||||
index != gl.INVALID_INDEX,
|
||||
"SetUniformBlockBindingPoint for material=%s (matId=%d; shaderId=%d) failed because the uniform block=%s wasn't found",
|
||||
m.Name,
|
||||
m.Id,
|
||||
m.ShaderProg.Id,
|
||||
uniformBlockName,
|
||||
)
|
||||
gl.UniformBlockBinding(m.ShaderProg.Id, index, bindPointIndex)
|
||||
}
|
||||
|
||||
func (m *Material) GetAttribLoc(attribName string) int32 {
|
||||
@ -41,8 +146,8 @@ func (m *Material) GetAttribLoc(attribName string) int32 {
|
||||
return loc
|
||||
}
|
||||
|
||||
loc = gl.GetAttribLocation(m.ShaderProg.ID, gl.Str(attribName+"\x00"))
|
||||
asserts.T(loc != -1, "Attribute '"+attribName+"' doesn't exist on material "+m.Name)
|
||||
loc = gl.GetAttribLocation(m.ShaderProg.Id, gl.Str(attribName+"\x00"))
|
||||
assert.T(loc != -1, "Attribute '"+attribName+"' doesn't exist on material "+m.Name)
|
||||
m.AttribLocs[attribName] = loc
|
||||
return loc
|
||||
}
|
||||
@ -54,8 +159,8 @@ func (m *Material) GetUnifLoc(uniformName string) int32 {
|
||||
return loc
|
||||
}
|
||||
|
||||
loc = gl.GetUniformLocation(m.ShaderProg.ID, gl.Str(uniformName+"\x00"))
|
||||
asserts.T(loc != -1, "Uniform '"+uniformName+"' doesn't exist on material "+m.Name)
|
||||
loc = gl.GetUniformLocation(m.ShaderProg.Id, gl.Str(uniformName+"\x00"))
|
||||
assert.T(loc != -1, "Uniform '"+uniformName+"' doesn't exist on material "+m.Name)
|
||||
m.UnifLocs[uniformName] = loc
|
||||
return loc
|
||||
}
|
||||
@ -69,61 +174,132 @@ func (m *Material) DisableAttribute(attribName string) {
|
||||
}
|
||||
|
||||
func (m *Material) SetUnifInt32(uniformName string, val int32) {
|
||||
gl.ProgramUniform1i(m.ShaderProg.ID, m.GetUnifLoc(uniformName), val)
|
||||
gl.ProgramUniform1i(m.ShaderProg.Id, m.GetUnifLoc(uniformName), val)
|
||||
}
|
||||
|
||||
func (m *Material) SetUnifFloat32(uniformName string, val float32) {
|
||||
gl.ProgramUniform1f(m.ShaderProg.ID, m.GetUnifLoc(uniformName), val)
|
||||
gl.ProgramUniform1f(m.ShaderProg.Id, m.GetUnifLoc(uniformName), val)
|
||||
}
|
||||
|
||||
func (m *Material) SetUnifVec2(uniformName string, vec2 *gglm.Vec2) {
|
||||
gl.ProgramUniform2fv(m.ShaderProg.ID, m.GetUnifLoc(uniformName), 1, &vec2.Data[0])
|
||||
internalSetUnifVec2(m.ShaderProg.Id, m.GetUnifLoc(uniformName), vec2)
|
||||
}
|
||||
|
||||
//go:noescape
|
||||
//go:linkname internalSetUnifVec2 github.com/bloeys/nmage/materials.SetUnifVec2
|
||||
func internalSetUnifVec2(shaderProgId uint32, unifLoc int32, vec2 *gglm.Vec2)
|
||||
|
||||
func SetUnifVec2(shaderProgId uint32, unifLoc int32, vec2 *gglm.Vec2) {
|
||||
gl.ProgramUniform2fv(shaderProgId, unifLoc, 1, &vec2.Data[0])
|
||||
}
|
||||
|
||||
func (m *Material) SetUnifVec3(uniformName string, vec3 *gglm.Vec3) {
|
||||
gl.ProgramUniform3fv(m.ShaderProg.ID, m.GetUnifLoc(uniformName), 1, &vec3.Data[0])
|
||||
internalSetUnifVec3(m.ShaderProg.Id, m.GetUnifLoc(uniformName), vec3)
|
||||
}
|
||||
|
||||
//go:noescape
|
||||
//go:linkname internalSetUnifVec3 github.com/bloeys/nmage/materials.SetUnifVec3
|
||||
func internalSetUnifVec3(shaderProgId uint32, unifLoc int32, vec3 *gglm.Vec3)
|
||||
|
||||
func SetUnifVec3(shaderProgId uint32, unifLoc int32, vec3 *gglm.Vec3) {
|
||||
gl.ProgramUniform3fv(shaderProgId, unifLoc, 1, &vec3.Data[0])
|
||||
}
|
||||
|
||||
func (m *Material) SetUnifVec4(uniformName string, vec4 *gglm.Vec4) {
|
||||
gl.ProgramUniform4fv(m.ShaderProg.ID, m.GetUnifLoc(uniformName), 1, &vec4.Data[0])
|
||||
internalSetUnifVec4(m.ShaderProg.Id, m.GetUnifLoc(uniformName), vec4)
|
||||
}
|
||||
|
||||
//go:noescape
|
||||
//go:linkname internalSetUnifVec4 github.com/bloeys/nmage/materials.SetUnifVec4
|
||||
func internalSetUnifVec4(shaderProgId uint32, unifLoc int32, vec4 *gglm.Vec4)
|
||||
|
||||
func SetUnifVec4(shaderProgId uint32, unifLoc int32, vec4 *gglm.Vec4) {
|
||||
gl.ProgramUniform4fv(shaderProgId, unifLoc, 1, &vec4.Data[0])
|
||||
}
|
||||
|
||||
func (m *Material) SetUnifMat2(uniformName string, mat2 *gglm.Mat2) {
|
||||
gl.ProgramUniformMatrix2fv(m.ShaderProg.ID, m.GetUnifLoc(uniformName), 1, false, &mat2.Data[0][0])
|
||||
internalSetUnifMat2(m.ShaderProg.Id, m.GetUnifLoc(uniformName), mat2)
|
||||
}
|
||||
|
||||
//go:noescape
|
||||
//go:linkname internalSetUnifMat2 github.com/bloeys/nmage/materials.SetUnifMat2
|
||||
func internalSetUnifMat2(shaderProgId uint32, unifLoc int32, mat2 *gglm.Mat2)
|
||||
|
||||
func SetUnifMat2(shaderProgId uint32, unifLoc int32, mat2 *gglm.Mat2) {
|
||||
gl.ProgramUniformMatrix2fv(shaderProgId, unifLoc, 1, false, &mat2.Data[0][0])
|
||||
}
|
||||
|
||||
func (m *Material) SetUnifMat3(uniformName string, mat3 *gglm.Mat3) {
|
||||
gl.ProgramUniformMatrix3fv(m.ShaderProg.ID, m.GetUnifLoc(uniformName), 1, false, &mat3.Data[0][0])
|
||||
internalSetUnifMat3(m.ShaderProg.Id, m.GetUnifLoc(uniformName), mat3)
|
||||
}
|
||||
|
||||
//go:noescape
|
||||
//go:linkname internalSetUnifMat3 github.com/bloeys/nmage/materials.SetUnifMat3
|
||||
func internalSetUnifMat3(shaderProgId uint32, unifLoc int32, mat3 *gglm.Mat3)
|
||||
|
||||
func SetUnifMat3(shaderProgId uint32, unifLoc int32, mat3 *gglm.Mat3) {
|
||||
gl.ProgramUniformMatrix3fv(shaderProgId, unifLoc, 1, false, &mat3.Data[0][0])
|
||||
}
|
||||
|
||||
func (m *Material) SetUnifMat4(uniformName string, mat4 *gglm.Mat4) {
|
||||
gl.ProgramUniformMatrix4fv(m.ShaderProg.ID, m.GetUnifLoc(uniformName), 1, false, &mat4.Data[0][0])
|
||||
internalSetUnifMat4(m.ShaderProg.Id, m.GetUnifLoc(uniformName), mat4)
|
||||
}
|
||||
|
||||
//go:noescape
|
||||
//go:linkname internalSetUnifMat4 github.com/bloeys/nmage/materials.SetUnifMat4
|
||||
func internalSetUnifMat4(shaderProgId uint32, unifLoc int32, mat4 *gglm.Mat4)
|
||||
|
||||
func SetUnifMat4(shaderProgId uint32, unifLoc int32, mat4 *gglm.Mat4) {
|
||||
gl.ProgramUniformMatrix4fv(shaderProgId, unifLoc, 1, false, &mat4.Data[0][0])
|
||||
}
|
||||
|
||||
func (m *Material) Delete() {
|
||||
gl.DeleteProgram(m.ShaderProg.ID)
|
||||
gl.DeleteProgram(m.ShaderProg.Id)
|
||||
}
|
||||
|
||||
func NewMaterial(matName, shaderPath string) *Material {
|
||||
|
||||
shdrProg, err := shaders.NewShaderProgram()
|
||||
if err != nil {
|
||||
logging.ErrLog.Fatalln("Failed to create new shader program. Err: ", err)
|
||||
}
|
||||
|
||||
vertShader, err := shaders.LoadAndCompilerShader(shaderPath+".vert.glsl", shaders.VertexShaderType)
|
||||
if err != nil {
|
||||
logging.ErrLog.Fatalln("Failed to load and create vertex shader. Err: ", err)
|
||||
}
|
||||
|
||||
fragShader, err := shaders.LoadAndCompilerShader(shaderPath+".frag.glsl", shaders.FragmentShaderType)
|
||||
if err != nil {
|
||||
logging.ErrLog.Fatalln("Failed to load and create fragment shader. Err: ", err)
|
||||
}
|
||||
|
||||
shdrProg.AttachShader(vertShader)
|
||||
shdrProg.AttachShader(fragShader)
|
||||
shdrProg.Link()
|
||||
|
||||
return &Material{Name: matName, ShaderProg: shdrProg, UnifLocs: make(map[string]int32), AttribLocs: make(map[string]int32)}
|
||||
func getNewMatId() uint32 {
|
||||
lastMatId++
|
||||
return lastMatId
|
||||
}
|
||||
|
||||
func NewMaterial(matName, shaderPath string) Material {
|
||||
|
||||
shdrProg, err := shaders.LoadAndCompileCombinedShader(shaderPath)
|
||||
if err != nil {
|
||||
logging.ErrLog.Fatalf("Failed to create new material '%s'. Err: %s\n", matName, err.Error())
|
||||
}
|
||||
|
||||
return Material{
|
||||
Id: getNewMatId(),
|
||||
Name: matName,
|
||||
ShaderProg: shdrProg,
|
||||
UnifLocs: make(map[string]int32),
|
||||
AttribLocs: make(map[string]int32),
|
||||
|
||||
DiffuseTex: assets.DefaultDiffuseTexId.TexID,
|
||||
SpecularTex: assets.DefaultSpecularTexId.TexID,
|
||||
NormalTex: assets.DefaultNormalTexId.TexID,
|
||||
EmissionTex: assets.DefaultEmissionTexId.TexID,
|
||||
}
|
||||
}
|
||||
|
||||
func NewMaterialSrc(matName string, shaderSrc []byte) Material {
|
||||
|
||||
shdrProg, err := shaders.LoadAndCompileCombinedShaderSrc(shaderSrc)
|
||||
if err != nil {
|
||||
logging.ErrLog.Fatalf("Failed to create new material '%s'. Err: %s\n", matName, err.Error())
|
||||
}
|
||||
|
||||
return Material{
|
||||
Id: getNewMatId(),
|
||||
Name: matName,
|
||||
ShaderProg: shdrProg,
|
||||
UnifLocs: make(map[string]int32),
|
||||
AttribLocs: make(map[string]int32),
|
||||
|
||||
DiffuseTex: assets.DefaultDiffuseTexId.TexID,
|
||||
SpecularTex: assets.DefaultSpecularTexId.TexID,
|
||||
NormalTex: assets.DefaultNormalTexId.TexID,
|
||||
EmissionTex: assets.DefaultEmissionTexId.TexID,
|
||||
}
|
||||
}
|
||||
|
||||
208
meshes/mesh.go
@ -2,54 +2,165 @@ package meshes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/bloeys/assimp-go/asig"
|
||||
"github.com/bloeys/gglm/gglm"
|
||||
"github.com/bloeys/nmage/asserts"
|
||||
"github.com/bloeys/nmage/assert"
|
||||
"github.com/bloeys/nmage/buffers"
|
||||
)
|
||||
|
||||
type Mesh struct {
|
||||
Name string
|
||||
Buf buffers.Buffer
|
||||
type SubMesh struct {
|
||||
BaseVertex int32
|
||||
BaseIndex uint32
|
||||
IndexCount int32
|
||||
}
|
||||
|
||||
func NewMesh(name, modelPath string, postProcessFlags asig.PostProcess) (*Mesh, error) {
|
||||
type Mesh struct {
|
||||
Name string
|
||||
/*
|
||||
Vao has the following shader attribute layout:
|
||||
- Loc0: Pos
|
||||
- Loc1: Normal
|
||||
- Loc2: UV0
|
||||
- Loc3: Tangent
|
||||
- (Optional) Color
|
||||
|
||||
scene, release, err := asig.ImportFile(modelPath, asig.PostProcessTriangulate|postProcessFlags)
|
||||
Optional stuff appear in the order in this list, depending on what other optional stuff exists.
|
||||
|
||||
For example:
|
||||
- If color exists it will be in Loc3, otherwise it is unset
|
||||
*/
|
||||
Vao buffers.VertexArray
|
||||
SubMeshes []SubMesh
|
||||
}
|
||||
|
||||
var (
|
||||
// DefaultMeshLoadFlags are the flags always applied when loading a new mesh regardless
|
||||
// of what post process flags are used when loading a mesh.
|
||||
//
|
||||
// Defaults to: asig.PostProcessTriangulate | asig.PostProcessCalcTangentSpace;
|
||||
// Note: changing this will break the normal lit shaders, which expect tangents to be there
|
||||
DefaultMeshLoadFlags asig.PostProcess = asig.PostProcessTriangulate | asig.PostProcessCalcTangentSpace
|
||||
)
|
||||
|
||||
func NewMesh(name, modelPath string, postProcessFlags asig.PostProcess) (Mesh, error) {
|
||||
|
||||
finalPostProcessFlags := DefaultMeshLoadFlags | postProcessFlags
|
||||
|
||||
scene, release, err := asig.ImportFile(modelPath, finalPostProcessFlags)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to load model. Err: " + err.Error())
|
||||
return Mesh{}, errors.New("Failed to load model. Err: " + err.Error())
|
||||
}
|
||||
defer release()
|
||||
|
||||
if len(scene.Meshes) == 0 {
|
||||
return nil, errors.New("No meshes found in file: " + modelPath)
|
||||
return Mesh{}, errors.New("No meshes found in file: " + modelPath)
|
||||
}
|
||||
|
||||
mesh := &Mesh{Name: name}
|
||||
sceneMesh := scene.Meshes[0]
|
||||
mesh.Buf = buffers.NewBuffer()
|
||||
|
||||
asserts.T(len(sceneMesh.TexCoords[0]) > 0, "Mesh has no UV0")
|
||||
layoutToUse := []buffers.Element{{ElementType: buffers.DataTypeVec3}, {ElementType: buffers.DataTypeVec3}, {ElementType: buffers.DataTypeVec2}}
|
||||
|
||||
if len(sceneMesh.ColorSets) > 0 && len(sceneMesh.ColorSets[0]) > 0 {
|
||||
layoutToUse = append(layoutToUse, buffers.Element{ElementType: buffers.DataTypeVec4})
|
||||
}
|
||||
mesh.Buf.SetLayout(layoutToUse...)
|
||||
|
||||
var values []float32
|
||||
arrs := []arrToInterleave{{V3s: sceneMesh.Vertices}, {V3s: sceneMesh.Normals}, {V2s: v3sToV2s(sceneMesh.TexCoords[0])}}
|
||||
|
||||
if len(sceneMesh.ColorSets) > 0 && len(sceneMesh.ColorSets[0]) > 0 {
|
||||
arrs = append(arrs, arrToInterleave{V4s: sceneMesh.ColorSets[0]})
|
||||
mesh := Mesh{
|
||||
Name: name,
|
||||
Vao: buffers.NewVertexArray(),
|
||||
SubMeshes: make([]SubMesh, 0, 1),
|
||||
}
|
||||
|
||||
values = interleave(arrs...)
|
||||
vbo := buffers.NewVertexBuffer()
|
||||
ibo := buffers.NewIndexBuffer()
|
||||
|
||||
// Estimate a useful prealloc capacity based on the first submesh that has vertex pos+normals+tangents+texCoords
|
||||
vertexBufDataCapacity := len(scene.Meshes[0].Vertices) * 3 * 3 * 3 * 2
|
||||
|
||||
// Increase capacity depending on what the mesh has
|
||||
if len(scene.Meshes[0].ColorSets) > 0 && len(scene.Meshes[0].ColorSets[0]) > 0 {
|
||||
vertexBufDataCapacity *= 4
|
||||
}
|
||||
|
||||
var vertexBufData []float32 = make([]float32, 0, vertexBufDataCapacity)
|
||||
|
||||
// Initial size assumes 3 indices per face
|
||||
var indexBufData []uint32 = make([]uint32, 0, len(scene.Meshes[0].Faces)*3)
|
||||
|
||||
// fmt.Printf("\nMesh %s has %d meshe(s) with first mesh having %d vertices\n", name, len(scene.Meshes), len(scene.Meshes[0].Vertices))
|
||||
|
||||
for i := 0; i < len(scene.Meshes); i++ {
|
||||
|
||||
sceneMesh := scene.Meshes[i]
|
||||
|
||||
// We always want tangents and UV0
|
||||
if len(sceneMesh.Tangents) == 0 {
|
||||
sceneMesh.Tangents = make([]gglm.Vec3, len(sceneMesh.Vertices))
|
||||
}
|
||||
|
||||
if len(sceneMesh.TexCoords[0]) == 0 {
|
||||
sceneMesh.TexCoords[0] = make([]gglm.Vec3, len(sceneMesh.Vertices))
|
||||
}
|
||||
|
||||
hasColorSet0 := len(sceneMesh.ColorSets) > 0 && len(sceneMesh.ColorSets[0]) > 0
|
||||
|
||||
layoutToUse := []buffers.Element{
|
||||
{ElementType: buffers.DataTypeVec3}, // Position
|
||||
{ElementType: buffers.DataTypeVec3}, // Normals
|
||||
{ElementType: buffers.DataTypeVec3}, // Tangents
|
||||
{ElementType: buffers.DataTypeVec2}, // UV0
|
||||
}
|
||||
|
||||
if hasColorSet0 {
|
||||
layoutToUse = append(layoutToUse, buffers.Element{ElementType: buffers.DataTypeVec4})
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
vbo.SetLayout(layoutToUse...)
|
||||
} else {
|
||||
|
||||
// @TODO @NOTE: This requirement is because we are using one VAO+VBO for all
|
||||
// the meshes and so the buffer must have one format.
|
||||
//
|
||||
// If we want to allow different layouts then we can simply create one vbo per layout and put
|
||||
// meshes of the same layout in the same vbo, and we store the index of the vbo the mesh
|
||||
// uses in the submesh struct.
|
||||
firstSubmeshLayout := vbo.GetLayout()
|
||||
assert.T(len(firstSubmeshLayout) == len(layoutToUse), "Vertex layout of submesh '%d' of mesh '%s' at path '%s' does not equal vertex layout of the first submesh. Original layout: %v; This layout: %v", i, name, modelPath, firstSubmeshLayout, layoutToUse)
|
||||
|
||||
for i := 0; i < len(firstSubmeshLayout); i++ {
|
||||
assert.T(firstSubmeshLayout[i].ElementType == layoutToUse[i].ElementType, "Vertex layout of submesh '%d' of mesh '%s' at path '%s' does not equal vertex layout of the first submesh. Original layout: %v; This layout: %v", i, name, modelPath, firstSubmeshLayout, layoutToUse)
|
||||
}
|
||||
}
|
||||
|
||||
arrs := []arrToInterleave{
|
||||
{V3s: sceneMesh.Vertices},
|
||||
{V3s: sceneMesh.Normals},
|
||||
{V3s: sceneMesh.Tangents},
|
||||
{V2s: v3sToV2s(sceneMesh.TexCoords[0])},
|
||||
}
|
||||
|
||||
if hasColorSet0 {
|
||||
arrs = append(arrs, arrToInterleave{V4s: sceneMesh.ColorSets[0]})
|
||||
}
|
||||
|
||||
indices := flattenFaces(sceneMesh.Faces)
|
||||
mesh.SubMeshes = append(mesh.SubMeshes, SubMesh{
|
||||
|
||||
// Index of the vertex to start from (e.g. if index buffer says use vertex 5, and BaseVertex=3, the vertex used will be vertex 8)
|
||||
BaseVertex: int32(len(vertexBufData)*4) / vbo.Stride,
|
||||
// Which index (in the index buffer) to start from
|
||||
BaseIndex: uint32(len(indexBufData)),
|
||||
// How many indices in this submesh
|
||||
IndexCount: int32(len(indices)),
|
||||
})
|
||||
|
||||
vertexBufData = append(vertexBufData, interleave(arrs...)...)
|
||||
indexBufData = append(indexBufData, indices...)
|
||||
}
|
||||
|
||||
vbo.SetData(vertexBufData, buffers.BufUsage_Static)
|
||||
ibo.SetData(indexBufData)
|
||||
|
||||
mesh.Vao.AddVertexBuffer(vbo)
|
||||
mesh.Vao.SetIndexBuffer(ibo)
|
||||
|
||||
// This is needed so that if you load meshes one after the other the
|
||||
// following mesh doesn't attach its vbo/ibo to this vao
|
||||
mesh.Vao.UnBind()
|
||||
|
||||
mesh.Buf.SetData(values)
|
||||
mesh.Buf.SetIndexBufData(flattenFaces(sceneMesh.Faces))
|
||||
return mesh, nil
|
||||
}
|
||||
|
||||
@ -73,9 +184,9 @@ type arrToInterleave struct {
|
||||
|
||||
func (a *arrToInterleave) get(i int) []float32 {
|
||||
|
||||
asserts.T(len(a.V2s) == 0 || len(a.V3s) == 0, "One array should be set in arrToInterleave, but both arrays are set")
|
||||
asserts.T(len(a.V2s) == 0 || len(a.V4s) == 0, "One array should be set in arrToInterleave, but both arrays are set")
|
||||
asserts.T(len(a.V3s) == 0 || len(a.V4s) == 0, "One array should be set in arrToInterleave, but both arrays are set")
|
||||
assert.T(len(a.V2s) == 0 || len(a.V3s) == 0, "One array should be set in arrToInterleave, but multiple arrays are set")
|
||||
assert.T(len(a.V2s) == 0 || len(a.V4s) == 0, "One array should be set in arrToInterleave, but multiple arrays are set")
|
||||
assert.T(len(a.V3s) == 0 || len(a.V4s) == 0, "One array should be set in arrToInterleave, but multiple arrays are set")
|
||||
|
||||
if len(a.V2s) > 0 {
|
||||
return a.V2s[i].Data[:]
|
||||
@ -88,8 +199,8 @@ func (a *arrToInterleave) get(i int) []float32 {
|
||||
|
||||
func interleave(arrs ...arrToInterleave) []float32 {
|
||||
|
||||
asserts.T(len(arrs) > 0, "No input sent to interleave")
|
||||
asserts.T(len(arrs[0].V2s) > 0 || len(arrs[0].V3s) > 0 || len(arrs[0].V4s) > 0, "Interleave arrays are empty")
|
||||
assert.T(len(arrs) > 0, "No input sent to interleave")
|
||||
assert.T(len(arrs[0].V2s) > 0 || len(arrs[0].V3s) > 0 || len(arrs[0].V4s) > 0, "Interleave arrays are empty")
|
||||
|
||||
elementCount := 0
|
||||
if len(arrs[0].V2s) > 0 {
|
||||
@ -104,7 +215,7 @@ func interleave(arrs ...arrToInterleave) []float32 {
|
||||
totalSize := 0
|
||||
for i := 0; i < len(arrs); i++ {
|
||||
|
||||
asserts.T(len(arrs[i].V2s) == elementCount || len(arrs[i].V3s) == elementCount || len(arrs[i].V4s) == elementCount, "Mesh vertex data given to interleave is not the same length")
|
||||
assert.T(len(arrs[i].V2s) == elementCount || len(arrs[i].V3s) == elementCount || len(arrs[i].V4s) == elementCount, "Mesh vertex data given to interleave is not the same length")
|
||||
|
||||
if len(arrs[i].V2s) > 0 {
|
||||
totalSize += len(arrs[i].V2s) * 2
|
||||
@ -125,34 +236,9 @@ func interleave(arrs ...arrToInterleave) []float32 {
|
||||
return out
|
||||
}
|
||||
|
||||
func flattenVec3(vec3s []gglm.Vec3) []float32 {
|
||||
|
||||
floats := make([]float32, len(vec3s)*3)
|
||||
for i := 0; i < len(vec3s); i++ {
|
||||
floats[i*3+0] = vec3s[i].X()
|
||||
floats[i*3+1] = vec3s[i].Y()
|
||||
floats[i*3+2] = vec3s[i].Z()
|
||||
}
|
||||
|
||||
return floats
|
||||
}
|
||||
|
||||
func flattenVec4(vec4s []gglm.Vec4) []float32 {
|
||||
|
||||
floats := make([]float32, len(vec4s)*4)
|
||||
for i := 0; i < len(vec4s); i++ {
|
||||
floats[i*4+0] = vec4s[i].X()
|
||||
floats[i*4+1] = vec4s[i].Y()
|
||||
floats[i*4+2] = vec4s[i].Z()
|
||||
floats[i*4+3] = vec4s[i].W()
|
||||
}
|
||||
|
||||
return floats
|
||||
}
|
||||
|
||||
func flattenFaces(faces []asig.Face) []uint32 {
|
||||
|
||||
asserts.T(len(faces[0].Indices) == 3, fmt.Sprintf("Face doesn't have 3 indices. Index count: %v\n", len(faces[0].Indices)))
|
||||
assert.T(len(faces[0].Indices) == 3, "Face doesn't have 3 indices. Index count: %v\n", len(faces[0].Indices))
|
||||
|
||||
uints := make([]uint32, len(faces)*3)
|
||||
for i := 0; i < len(faces); i++ {
|
||||
|
||||
73
registry/iterator.go
Executable file
@ -0,0 +1,73 @@
|
||||
package registry
|
||||
|
||||
// Iterator goes through the entire registry it was created from and
|
||||
// returns all alive items, and nil after its done.
|
||||
//
|
||||
// The iterator will still work if items are added/removed to the registry
|
||||
// after it was created, but the following conditions apply:
|
||||
// - If items are removed, iterator will not show the removed items (assuming it didn't return them before their removal)
|
||||
// - If items are added, the iterator will either only return older items (i.e. is not affected), or only return newer items (i.e. items that were going to be returned before will now not get returned in favor of newly inserted items), or a mix of old and new items.
|
||||
// However, in all cases the iterator will *never* returns more items than were alive at the time of the iterator's creation.
|
||||
// - If items were both added and removed, the iterator might follow either of the previous 2 cases or a combination of them
|
||||
//
|
||||
// To summarize: The iterator will *never* return more items than were alive at the time of its creation, and will *never* return freed items
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// for item, handle := it.Next(); !it.IsDone(); item, handle = it.Next() {
|
||||
// // Do stuff
|
||||
// }
|
||||
type Iterator[T any] struct {
|
||||
registry *Registry[T]
|
||||
remainingItems uint
|
||||
currIndex int
|
||||
}
|
||||
|
||||
func (it *Iterator[T]) Next() (*T, Handle) {
|
||||
|
||||
if it.IsDone() {
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// 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
|
||||
}
|
||||
|
||||
for ; it.currIndex < len(it.registry.Handles); it.currIndex++ {
|
||||
|
||||
handle := it.registry.Handles[it.currIndex]
|
||||
if !handle.HasFlag(HandleFlag_Alive) {
|
||||
continue
|
||||
}
|
||||
|
||||
item := &it.registry.Items[it.currIndex]
|
||||
it.currIndex++
|
||||
it.remainingItems--
|
||||
return item, handle
|
||||
}
|
||||
|
||||
// If we reached here means we iterated to the end and didn't find anything, which probably
|
||||
// means that the registry changed since we were created, and that remainingItems is not accurate.
|
||||
//
|
||||
// As such, we zero remaining items so that this iterator is considered done
|
||||
it.currIndex = -1
|
||||
it.remainingItems = 0
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
func (it *Iterator[T]) IsDone() bool {
|
||||
|
||||
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
|
||||
}
|
||||
135
registry/registry.go
Executable file
@ -0,0 +1,135 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/bloeys/nmage/assert"
|
||||
)
|
||||
|
||||
type freeListitem struct {
|
||||
ItemIndex uint64
|
||||
nextFree *freeListitem
|
||||
}
|
||||
|
||||
// Registry is a storage data structure that can efficiently create/get/free items using generational indices.
|
||||
// Each item stored in the registry is associated with a 'handle' object that is used to get and free objects
|
||||
//
|
||||
// The registry 'owns' all items it stores and returns pointers to items in its array. All items are allocated upfront.
|
||||
//
|
||||
// It is NOT safe to concurrently create or free items. However, it is SAFE to concurrently get items
|
||||
type Registry[T any] struct {
|
||||
ItemCount uint
|
||||
Handles []Handle
|
||||
Items []T
|
||||
|
||||
FreeList *freeListitem
|
||||
FreeListSize uint32
|
||||
|
||||
// The number of slots required to be in the free list before the free list
|
||||
// is used for creating new entries
|
||||
FreeListUsageThreshold uint32
|
||||
}
|
||||
|
||||
func (r *Registry[T]) New() (*T, Handle) {
|
||||
|
||||
assert.T(r.ItemCount < uint(len(r.Handles)), "Can not add more entities to registry because it is full")
|
||||
|
||||
var index uint64 = math.MaxUint64
|
||||
|
||||
// Find index to use for the new item
|
||||
if r.FreeList != nil && r.FreeListSize > r.FreeListUsageThreshold {
|
||||
|
||||
index = r.FreeList.ItemIndex
|
||||
|
||||
r.FreeList = r.FreeList.nextFree
|
||||
r.FreeListSize--
|
||||
} else {
|
||||
|
||||
for i := 0; i < len(r.Handles); i++ {
|
||||
|
||||
handle := r.Handles[i]
|
||||
|
||||
if handle.HasFlag(HandleFlag_Alive) {
|
||||
continue
|
||||
}
|
||||
|
||||
index = uint64(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if index == math.MaxUint64 {
|
||||
panic("failed to create new entity because we did not find a free spot in the registry. Why did the item count assert not go off?")
|
||||
}
|
||||
|
||||
var newItem T
|
||||
newHandle := NewHandle(r.Handles[index].Generation()+1, HandleFlag_Alive, index)
|
||||
assert.T(newHandle != 0, "Entity handle must not be zero")
|
||||
|
||||
r.ItemCount++
|
||||
r.Handles[index] = newHandle
|
||||
r.Items[index] = newItem
|
||||
|
||||
// It is very important we return directly from the items array, because if we return
|
||||
// a pointer to newItem, and T is a value not a pointer, then newItem and what's stored in items will be different
|
||||
return &r.Items[index], newHandle
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
handle := r.Handles[index]
|
||||
if handle.Generation() != id.Generation() || !handle.HasFlag(HandleFlag_Alive) {
|
||||
return nil
|
||||
}
|
||||
|
||||
item := &r.Items[index]
|
||||
return item
|
||||
}
|
||||
|
||||
// Free resets the entity flags then adds this entity to the free list
|
||||
func (r *Registry[T]) Free(id Handle) {
|
||||
|
||||
index := id.Index()
|
||||
assert.T(index < uint64(len(r.Handles)), "Failed to free entity because of invalid entity handle. Handle index is %d while registry only has %d slots. Handle: %+v", index, r.ItemCount, id)
|
||||
|
||||
// Nothing to do if already free
|
||||
handle := r.Handles[index]
|
||||
if handle.Generation() != id.Generation() || !handle.HasFlag(HandleFlag_Alive) {
|
||||
return
|
||||
}
|
||||
|
||||
// Generation is incremented on aquire, so here we just reset flags
|
||||
r.ItemCount--
|
||||
r.Handles[index] = NewHandle(id.Generation(), HandleFlag_None, index)
|
||||
|
||||
// Add to free list
|
||||
r.FreeList = &freeListitem{
|
||||
ItemIndex: index,
|
||||
nextFree: r.FreeList,
|
||||
}
|
||||
r.FreeListSize++
|
||||
}
|
||||
|
||||
func (r *Registry[T]) NewIterator() Iterator[T] {
|
||||
return Iterator[T]{
|
||||
registry: r,
|
||||
remainingItems: r.ItemCount,
|
||||
currIndex: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func NewRegistry[T any](size uint32) *Registry[T] {
|
||||
assert.T(size > 0, "Registry size must be more than zero")
|
||||
return &Registry[T]{
|
||||
Handles: make([]Handle, size),
|
||||
Items: make([]T, size),
|
||||
FreeListUsageThreshold: 30,
|
||||
}
|
||||
}
|
||||
43
registry/registry_handle.go
Executable file
@ -0,0 +1,43 @@
|
||||
package registry
|
||||
|
||||
type HandleFlag byte
|
||||
|
||||
const (
|
||||
HandleFlag_None HandleFlag = 0
|
||||
HandleFlag_Alive HandleFlag = 1 << (iota - 1)
|
||||
)
|
||||
|
||||
const (
|
||||
GenerationShiftBits = 64 - 8
|
||||
FlagsShiftBits = 64 - 16
|
||||
IndexBitMask = 0x00_00_FFFF_FFFF_FFFF
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func (h Handle) Generation() byte {
|
||||
return byte(h >> GenerationShiftBits)
|
||||
}
|
||||
|
||||
func (h Handle) Flags() HandleFlag {
|
||||
return HandleFlag(h >> FlagsShiftBits)
|
||||
}
|
||||
|
||||
func (h Handle) Index() uint64 {
|
||||
return uint64(h & IndexBitMask)
|
||||
}
|
||||
|
||||
func NewHandle(generation byte, flags HandleFlag, index uint64) Handle {
|
||||
return Handle(index | (uint64(generation) << GenerationShiftBits) | (uint64(flags) << FlagsShiftBits))
|
||||
}
|
||||
@ -2,6 +2,7 @@ package rend3dgl
|
||||
|
||||
import (
|
||||
"github.com/bloeys/gglm/gglm"
|
||||
"github.com/bloeys/nmage/buffers"
|
||||
"github.com/bloeys/nmage/materials"
|
||||
"github.com/bloeys/nmage/meshes"
|
||||
"github.com/bloeys/nmage/renderer"
|
||||
@ -11,29 +12,73 @@ import (
|
||||
var _ renderer.Render = &Rend3DGL{}
|
||||
|
||||
type Rend3DGL struct {
|
||||
BoundMesh *meshes.Mesh
|
||||
BoundMat *materials.Material
|
||||
BoundVaoId uint32
|
||||
BoundMatId uint32
|
||||
BoundMeshVaoId uint32
|
||||
}
|
||||
|
||||
func (r3d *Rend3DGL) Draw(mesh *meshes.Mesh, trMat *gglm.TrMat, mat *materials.Material) {
|
||||
func (r *Rend3DGL) DrawMesh(mesh *meshes.Mesh, modelMat *gglm.TrMat, mat *materials.Material) {
|
||||
|
||||
if mesh != r3d.BoundMesh {
|
||||
mesh.Buf.Bind()
|
||||
r3d.BoundMesh = mesh
|
||||
if mesh.Vao.Id != r.BoundMeshVaoId {
|
||||
mesh.Vao.Bind()
|
||||
r.BoundMeshVaoId = mesh.Vao.Id
|
||||
}
|
||||
|
||||
if mat != r3d.BoundMat {
|
||||
if mat.Id != r.BoundMatId {
|
||||
mat.Bind()
|
||||
r3d.BoundMat = mat
|
||||
r.BoundMatId = mat.Id
|
||||
}
|
||||
|
||||
mat.SetUnifMat4("modelMat", &trMat.Mat4)
|
||||
gl.DrawElements(gl.TRIANGLES, mesh.Buf.IndexBufCount, gl.UNSIGNED_INT, gl.PtrOffset(0))
|
||||
if mat.Settings.Has(materials.MaterialSettings_HasModelMtx) {
|
||||
mat.SetUnifMat4("modelMat", &modelMat.Mat4)
|
||||
}
|
||||
|
||||
if mat.Settings.Has(materials.MaterialSettings_HasNormalMtx) {
|
||||
normalMat := modelMat.Clone().InvertAndTranspose().ToMat3()
|
||||
mat.SetUnifMat3("normalMat", &normalMat)
|
||||
}
|
||||
|
||||
for i := 0; i < len(mesh.SubMeshes); i++ {
|
||||
gl.DrawElementsBaseVertexWithOffset(gl.TRIANGLES, mesh.SubMeshes[i].IndexCount, gl.UNSIGNED_INT, uintptr(mesh.SubMeshes[i].BaseIndex), mesh.SubMeshes[i].BaseVertex)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Rend3DGL) DrawVertexArray(mat *materials.Material, vao *buffers.VertexArray, firstElement int32, elementCount int32) {
|
||||
|
||||
if vao.Id != r.BoundVaoId {
|
||||
vao.Bind()
|
||||
r.BoundVaoId = vao.Id
|
||||
}
|
||||
|
||||
if mat.Id != r.BoundMatId {
|
||||
mat.Bind()
|
||||
r.BoundMatId = mat.Id
|
||||
}
|
||||
|
||||
gl.DrawArrays(gl.TRIANGLES, firstElement, elementCount)
|
||||
}
|
||||
|
||||
func (r *Rend3DGL) DrawCubemap(mesh *meshes.Mesh, mat *materials.Material) {
|
||||
|
||||
if mesh.Vao.Id != r.BoundMeshVaoId {
|
||||
mesh.Vao.Bind()
|
||||
r.BoundMeshVaoId = mesh.Vao.Id
|
||||
}
|
||||
|
||||
if mat.Id != r.BoundMatId {
|
||||
mat.Bind()
|
||||
r.BoundMatId = mat.Id
|
||||
}
|
||||
|
||||
for i := 0; i < len(mesh.SubMeshes); i++ {
|
||||
gl.DrawElementsBaseVertexWithOffset(gl.TRIANGLES, mesh.SubMeshes[i].IndexCount, gl.UNSIGNED_INT, uintptr(mesh.SubMeshes[i].BaseIndex), mesh.SubMeshes[i].BaseVertex)
|
||||
}
|
||||
}
|
||||
|
||||
func (r3d *Rend3DGL) FrameEnd() {
|
||||
r3d.BoundMesh = nil
|
||||
r3d.BoundMat = nil
|
||||
r3d.BoundVaoId = 0
|
||||
r3d.BoundMatId = 0
|
||||
r3d.BoundMeshVaoId = 0
|
||||
}
|
||||
|
||||
func NewRend3DGL() *Rend3DGL {
|
||||
|
||||
@ -2,11 +2,14 @@ package renderer
|
||||
|
||||
import (
|
||||
"github.com/bloeys/gglm/gglm"
|
||||
"github.com/bloeys/nmage/buffers"
|
||||
"github.com/bloeys/nmage/materials"
|
||||
"github.com/bloeys/nmage/meshes"
|
||||
)
|
||||
|
||||
type Render interface {
|
||||
Draw(mesh *meshes.Mesh, trMat *gglm.TrMat, mat *materials.Material)
|
||||
DrawMesh(mesh *meshes.Mesh, trMat *gglm.TrMat, mat *materials.Material)
|
||||
DrawVertexArray(mat *materials.Material, vao *buffers.VertexArray, firstElement int32, count int32)
|
||||
DrawCubemap(mesh *meshes.Mesh, mat *materials.Material)
|
||||
FrameEnd()
|
||||
}
|
||||
|
||||
BIN
res/models/cube.fbx
Executable file
@ -1,46 +0,0 @@
|
||||
# Blender v2.92.0 OBJ File: ''
|
||||
# www.blender.org
|
||||
mtllib obj.mtl
|
||||
o Cube
|
||||
v 2.275618 1.000000 0.349413
|
||||
v 3.520138 -1.000000 0.102233
|
||||
v 2.275618 1.000000 0.752820
|
||||
v 3.520138 -1.000000 1.000000
|
||||
v 0.244520 1.000000 0.349413
|
||||
v -1.000000 -1.000000 0.102233
|
||||
v 0.244520 1.000000 0.752820
|
||||
v -1.000000 -1.000000 1.000000
|
||||
vt 0.806168 0.568832
|
||||
vt 0.693832 0.681168
|
||||
vt 0.693832 0.568832
|
||||
vt 0.375000 1.000000
|
||||
vt 0.375000 0.750000
|
||||
vt 0.375000 0.000000
|
||||
vt 0.625000 0.250000
|
||||
vt 0.375000 0.250000
|
||||
vt 0.375000 0.500000
|
||||
vt 0.125000 0.750000
|
||||
vt 0.125000 0.500000
|
||||
vt 0.806168 0.681168
|
||||
vt 0.625000 0.931168
|
||||
vt 0.625000 0.068832
|
||||
vn 0.0000 1.0000 0.0000
|
||||
vn 0.0000 0.1227 0.9924
|
||||
vn -0.8490 0.5283 0.0000
|
||||
vn 0.0000 -1.0000 0.0000
|
||||
vn 0.8490 0.5283 0.0000
|
||||
vn 0.0000 0.1227 -0.9924
|
||||
usemtl Material
|
||||
s off
|
||||
f 5/1/1 3/2/1 1/3/1
|
||||
f 3/2/2 8/4/2 4/5/2
|
||||
f 8/6/3 5/7/3 6/8/3
|
||||
f 2/9/4 8/10/4 6/11/4
|
||||
f 1/3/5 4/5/5 2/9/5
|
||||
f 5/7/6 2/9/6 6/8/6
|
||||
f 5/1/1 7/12/1 3/2/1
|
||||
f 3/2/2 7/13/2 8/4/2
|
||||
f 8/6/3 7/14/3 5/7/3
|
||||
f 2/9/4 4/5/4 8/10/4
|
||||
f 1/3/5 3/2/5 4/5/5
|
||||
f 5/7/6 1/3/6 2/9/6
|
||||
BIN
res/models/plane.fbx
Executable file
38
res/models/skybox-cube.obj
Executable file
@ -0,0 +1,38 @@
|
||||
# Blender v2.92.0 OBJ File: 'chair.blend'
|
||||
# www.blender.org
|
||||
o Cube.002_Cube.005
|
||||
v -1.000000 -1.000000 1.000000
|
||||
v -1.000000 1.000000 1.000000
|
||||
v -1.000000 -1.000000 -1.000000
|
||||
v -1.000000 1.000000 -1.000000
|
||||
v 1.000000 -1.000000 1.000000
|
||||
v 1.000000 1.000000 1.000000
|
||||
v 1.000000 -1.000000 -1.000000
|
||||
v 1.000000 1.000000 -1.000000
|
||||
vt 0.375000 0.000000
|
||||
vt 0.625000 0.000000
|
||||
vt 0.625000 0.250000
|
||||
vt 0.375000 0.250000
|
||||
vt 0.625000 0.500000
|
||||
vt 0.375000 0.500000
|
||||
vt 0.625000 0.750000
|
||||
vt 0.375000 0.750000
|
||||
vt 0.625000 1.000000
|
||||
vt 0.375000 1.000000
|
||||
vt 0.125000 0.500000
|
||||
vt 0.125000 0.750000
|
||||
vt 0.875000 0.500000
|
||||
vt 0.875000 0.750000
|
||||
vn -1.0000 0.0000 0.0000
|
||||
vn 0.0000 0.0000 -1.0000
|
||||
vn 1.0000 0.0000 0.0000
|
||||
vn 0.0000 0.0000 1.0000
|
||||
vn 0.0000 -1.0000 0.0000
|
||||
vn 0.0000 1.0000 0.0000
|
||||
s off
|
||||
f 1/1/1 2/2/1 4/3/1 3/4/1
|
||||
f 3/4/2 4/3/2 8/5/2 7/6/2
|
||||
f 7/6/3 8/5/3 6/7/3 5/8/3
|
||||
f 5/8/4 6/7/4 2/9/4 1/10/4
|
||||
f 3/11/5 7/6/5 5/8/5 1/12/5
|
||||
f 8/5/6 4/13/6 2/14/6 6/7/6
|
||||
BIN
res/models/sphere.fbx
Executable file
54
res/shaders/array-depth-map.glsl
Executable file
@ -0,0 +1,54 @@
|
||||
//shader:vertex
|
||||
#version 410
|
||||
|
||||
layout(location=0) in vec3 vertPosIn;
|
||||
|
||||
uniform mat4 modelMat;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = modelMat * vec4(vertPosIn, 1);
|
||||
}
|
||||
|
||||
//shader:geometry
|
||||
#version 410
|
||||
|
||||
layout (triangles) in;
|
||||
|
||||
#define NUM_PROJ_VIEW_MATS 4
|
||||
|
||||
// 3 * NUM_PROJ_VIEW_MATS
|
||||
layout (triangle_strip, max_vertices=12) out;
|
||||
|
||||
// This is the same number as max spot lights or whatever else is being rendered
|
||||
uniform mat4 projViewMats[NUM_PROJ_VIEW_MATS];
|
||||
|
||||
out vec4 FragPos;
|
||||
|
||||
void main()
|
||||
{
|
||||
for(int projViewMatIndex = 0; projViewMatIndex < NUM_PROJ_VIEW_MATS; projViewMatIndex++){
|
||||
|
||||
gl_Layer = projViewMatIndex;
|
||||
mat4 projViewMat = projViewMats[projViewMatIndex];
|
||||
|
||||
for(int i = 0; i < 3; i++)
|
||||
{
|
||||
FragPos = gl_in[i].gl_Position;
|
||||
gl_Position = projViewMat * FragPos;
|
||||
EmitVertex();
|
||||
}
|
||||
EndPrimitive();
|
||||
}
|
||||
}
|
||||
|
||||
//shader:fragment
|
||||
#version 410
|
||||
|
||||
in vec4 FragPos;
|
||||
|
||||
void main()
|
||||
{
|
||||
// This implicitly writes to the depth buffer with no color operations
|
||||
// Equivalent: gl_FragDepth = gl_FragCoord.z;
|
||||
}
|
||||
49
res/shaders/debug-depth.glsl
Executable file
@ -0,0 +1,49 @@
|
||||
//shader:vertex
|
||||
#version 410
|
||||
|
||||
layout(location=0) in vec3 vertPosIn;
|
||||
layout(location=2) in vec3 vertTangentIn;
|
||||
layout(location=3) in vec2 vertUV0In;
|
||||
layout(location=4) in vec3 vertColorIn;
|
||||
|
||||
out vec2 vertUV0;
|
||||
out vec3 vertColor;
|
||||
out vec3 fragPos;
|
||||
|
||||
uniform mat4 modelMat;
|
||||
uniform mat4 projViewMat;
|
||||
|
||||
void main()
|
||||
{
|
||||
vertUV0 = vertUV0In;
|
||||
vertColor = vertColorIn;
|
||||
|
||||
vec4 modelVert = modelMat * vec4(vertPosIn, 1);
|
||||
fragPos = modelVert.xyz;
|
||||
|
||||
gl_Position = projViewMat * modelVert;
|
||||
}
|
||||
|
||||
//shader:fragment
|
||||
#version 410
|
||||
|
||||
in vec3 vertColor;
|
||||
in vec2 vertUV0;
|
||||
in vec3 fragPos;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform float near = 0.1;
|
||||
uniform float far = 200.0;
|
||||
|
||||
float LinearizeDepth(float depth)
|
||||
{
|
||||
float z = depth * 2.0 - 1.0; // back to NDC
|
||||
return (2.0 * near * far) / (far + near - z * (far - near));
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
float depth = LinearizeDepth(gl_FragCoord.z) / far;
|
||||
fragColor = vec4(vec3(depth), 1.0);
|
||||
}
|
||||
21
res/shaders/depth-map.glsl
Executable file
@ -0,0 +1,21 @@
|
||||
//shader:vertex
|
||||
#version 410
|
||||
|
||||
layout(location=0) in vec3 vertPosIn;
|
||||
|
||||
uniform mat4 modelMat;
|
||||
uniform mat4 projViewMat;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = projViewMat * modelMat * vec4(vertPosIn, 1);
|
||||
}
|
||||
|
||||
//shader:fragment
|
||||
#version 410
|
||||
|
||||
void main()
|
||||
{
|
||||
// This implicitly writes to the depth buffer with no color operations
|
||||
// Equivalent: gl_FragDepth = gl_FragCoord.z;
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
#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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
#version 410
|
||||
|
||||
uniform mat4 ProjMtx;
|
||||
|
||||
in vec2 Position;
|
||||
in vec2 UV;
|
||||
in vec4 Color;
|
||||
|
||||
out vec2 Frag_UV;
|
||||
out vec4 Frag_Color;
|
||||
|
||||
void main()
|
||||
{
|
||||
Frag_UV = UV;
|
||||
Frag_Color = Color;
|
||||
gl_Position = ProjMtx * vec4(Position.xy, 0, 1);
|
||||
}
|
||||
65
res/shaders/omnidirectional-depth-map.glsl
Executable file
@ -0,0 +1,65 @@
|
||||
//shader:vertex
|
||||
#version 410
|
||||
|
||||
layout(location=0) in vec3 vertPosIn;
|
||||
|
||||
uniform mat4 modelMat;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = modelMat * vec4(vertPosIn, 1);
|
||||
}
|
||||
|
||||
//shader:geometry
|
||||
#version 410
|
||||
|
||||
layout (triangles) in;
|
||||
|
||||
// Cubemap means 6 faces, and the
|
||||
// input 3 triangle vertices are drawn once per face, so 6*3=18
|
||||
layout (triangle_strip, max_vertices=18) out;
|
||||
|
||||
uniform int cubemapIndex;
|
||||
uniform mat4 cubemapProjViewMats[6];
|
||||
|
||||
out vec4 FragPos;
|
||||
|
||||
void main()
|
||||
{
|
||||
for(int face = 0; face < 6; ++face)
|
||||
{
|
||||
// Built in variable that specifies which cubemap face we are rendering to
|
||||
// and only works when a cubemap is attached to the active fbo.
|
||||
//
|
||||
// We use an additional index here because our fbo has a cubemap array
|
||||
gl_Layer = (cubemapIndex * 6) + face;
|
||||
|
||||
// Transform each triangle vertex
|
||||
for(int i = 0; i < 3; ++i)
|
||||
{
|
||||
FragPos = gl_in[i].gl_Position;
|
||||
gl_Position = cubemapProjViewMats[face] * FragPos;
|
||||
EmitVertex();
|
||||
}
|
||||
EndPrimitive();
|
||||
}
|
||||
}
|
||||
|
||||
//shader:fragment
|
||||
#version 410
|
||||
|
||||
in vec4 FragPos;
|
||||
|
||||
uniform vec3 lightPos;
|
||||
uniform float farPlane;
|
||||
|
||||
void main()
|
||||
{
|
||||
// Get distance between fragment and light source
|
||||
float lightDistance = length(FragPos.xyz - lightPos);
|
||||
|
||||
// Map to [0, 1] by dividing by far plane and use it as our depth
|
||||
lightDistance = lightDistance / farPlane;
|
||||
|
||||
gl_FragDepth = lightDistance;
|
||||
}
|
||||
45
res/shaders/screen-quad.glsl
Executable file
@ -0,0 +1,45 @@
|
||||
//shader:vertex
|
||||
#version 410
|
||||
|
||||
out vec2 vertUV0;
|
||||
|
||||
// Hardcoded vertex positions for a fullscreen quad.
|
||||
// Format: vec4(pos.x, pos.y, uv0.x, uv0.y)
|
||||
vec4 quadData[6] = vec4[](
|
||||
vec4(-1.0, 1.0, 0.0, 1.0),
|
||||
vec4(-1.0, -1.0, 0.0, 0.0),
|
||||
vec4(1.0, -1.0, 1.0, 0.0),
|
||||
vec4(-1.0, 1.0, 0.0, 1.0),
|
||||
vec4(1.0, -1.0, 1.0, 0.0),
|
||||
vec4(1.0, 1.0, 1.0, 1.0)
|
||||
);
|
||||
|
||||
uniform vec2 scale = vec2(1, 1);
|
||||
uniform vec2 offset = vec2(0, 0);
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 vertData = quadData[gl_VertexID];
|
||||
|
||||
vertUV0 = vertData.zw;
|
||||
gl_Position = vec4((vertData.xy * scale) + offset, 0.0, 1.0);
|
||||
}
|
||||
|
||||
//shader:fragment
|
||||
#version 410
|
||||
|
||||
struct Material {
|
||||
sampler2D diffuse;
|
||||
};
|
||||
|
||||
uniform Material material;
|
||||
|
||||
in vec2 vertUV0;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 diffuseTexColor = texture(material.diffuse, vertUV0);
|
||||
fragColor = vec4(diffuseTexColor.rgb, 1);
|
||||
}
|
||||
46
res/shaders/simple-unlit.glsl
Executable file
@ -0,0 +1,46 @@
|
||||
//shader:vertex
|
||||
#version 410
|
||||
|
||||
layout(location=0) in vec3 vertPosIn;
|
||||
layout(location=1) in vec3 vertNormalIn;
|
||||
layout(location=2) in vec3 vertTangentIn;
|
||||
layout(location=3) in vec2 vertUV0In;
|
||||
layout(location=4) in vec3 vertColorIn;
|
||||
|
||||
out vec2 vertUV0;
|
||||
out vec3 vertColor;
|
||||
out vec3 fragPos;
|
||||
|
||||
uniform mat4 modelMat;
|
||||
uniform mat4 projViewMat;
|
||||
|
||||
void main()
|
||||
{
|
||||
vertUV0 = vertUV0In;
|
||||
vertColor = vertColorIn;
|
||||
|
||||
vec4 modelVert = modelMat * vec4(vertPosIn, 1);
|
||||
fragPos = modelVert.xyz;
|
||||
gl_Position = projViewMat * modelVert;
|
||||
}
|
||||
|
||||
//shader:fragment
|
||||
#version 410
|
||||
|
||||
struct Material {
|
||||
sampler2D diffuse;
|
||||
};
|
||||
|
||||
uniform Material material;
|
||||
|
||||
in vec3 vertColor;
|
||||
in vec2 vertUV0;
|
||||
in vec3 fragPos;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 diffuseTexColor = texture(material.diffuse, vertUV0);
|
||||
fragColor = vec4(diffuseTexColor.rgb, 1);
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
#version 410
|
||||
|
||||
uniform float ambientStrength = 0.1;
|
||||
uniform vec3 ambientLightColor = vec3(1, 1, 1);
|
||||
|
||||
uniform vec3 lightPos1;
|
||||
uniform vec3 lightColor1;
|
||||
|
||||
uniform sampler2D diffTex;
|
||||
|
||||
in vec3 vertColor;
|
||||
in vec3 vertNormal;
|
||||
in vec2 vertUV0;
|
||||
in vec3 fragPos;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 lightDir = normalize(lightPos1 - fragPos);
|
||||
float diffStrength = max(0.0, dot(normalize(vertNormal), lightDir));
|
||||
|
||||
vec3 finalAmbientColor = ambientLightColor * ambientStrength;
|
||||
vec4 texColor = texture(diffTex, vertUV0);
|
||||
fragColor = vec4(texColor.rgb * vertColor * (finalAmbientColor + diffStrength*lightColor1) , texColor.a);
|
||||
}
|
||||
500
res/shaders/simple.glsl
Executable file
@ -0,0 +1,500 @@
|
||||
//shader:vertex
|
||||
#version 410
|
||||
|
||||
#define NUM_SPOT_LIGHTS 4
|
||||
#define NUM_POINT_LIGHTS 8
|
||||
|
||||
//
|
||||
// Inputs
|
||||
//
|
||||
layout(location=0) in vec3 vertPosIn;
|
||||
layout(location=1) in vec3 vertNormalIn;
|
||||
layout(location=2) in vec3 vertTangentIn;
|
||||
layout(location=3) in vec2 vertUV0In;
|
||||
layout(location=4) in vec3 vertColorIn;
|
||||
|
||||
//
|
||||
// UBOs
|
||||
//
|
||||
struct DirLight {
|
||||
vec3 dir;
|
||||
vec3 diffuseColor;
|
||||
vec3 specularColor;
|
||||
};
|
||||
uniform sampler2D dirLightShadowMap;
|
||||
|
||||
struct PointLight {
|
||||
vec3 pos;
|
||||
vec3 diffuseColor;
|
||||
vec3 specularColor;
|
||||
float falloff;
|
||||
float radius;
|
||||
float maxBias;
|
||||
float nearPlane;
|
||||
float farPlane;
|
||||
};
|
||||
|
||||
struct SpotLight {
|
||||
vec3 pos;
|
||||
vec3 dir;
|
||||
vec3 diffuseColor;
|
||||
vec3 specularColor;
|
||||
float innerCutoff;
|
||||
float outerCutoff;
|
||||
};
|
||||
|
||||
layout (std140) uniform GlobalMatrices {
|
||||
vec3 camPos;
|
||||
mat4 projViewMat;
|
||||
};
|
||||
|
||||
layout (std140) uniform Lights {
|
||||
DirLight dirLight;
|
||||
};
|
||||
|
||||
//
|
||||
// Uniforms
|
||||
//
|
||||
uniform PointLight pointLights[NUM_POINT_LIGHTS];
|
||||
uniform SpotLight spotLights[NUM_SPOT_LIGHTS];
|
||||
uniform mat4 modelMat;
|
||||
uniform mat4 dirLightProjViewMat;
|
||||
uniform mat4 spotLightProjViewMats[NUM_SPOT_LIGHTS];
|
||||
|
||||
//
|
||||
// Outputs
|
||||
//
|
||||
out vec2 vertUV0;
|
||||
out vec3 vertColor;
|
||||
|
||||
out vec3 fragPos;
|
||||
out vec3 fragPosDirLight;
|
||||
out vec4 fragPosSpotLight[NUM_SPOT_LIGHTS];
|
||||
|
||||
out vec3 tangentCamPos;
|
||||
out vec3 tangentFragPos;
|
||||
out vec3 tangentDirLightDir;
|
||||
out vec3 tangentSpotLightPositions[NUM_SPOT_LIGHTS];
|
||||
out vec3 tangentSpotLightDirections[NUM_SPOT_LIGHTS];
|
||||
out vec3 tangentPointLightPositions[NUM_POINT_LIGHTS];
|
||||
|
||||
struct Test1 {
|
||||
float ff;
|
||||
vec3 v3;
|
||||
};
|
||||
|
||||
layout (std140) uniform Test2 {
|
||||
float f1;
|
||||
Test1 s;
|
||||
};
|
||||
|
||||
void main()
|
||||
{
|
||||
vertUV0 = vertUV0In;
|
||||
vertColor = vertColorIn;
|
||||
vec4 modelVert = modelMat * vec4(vertPosIn, 1);
|
||||
|
||||
// Tangent-BiTangent-Normal matrix for normal mapping
|
||||
vec3 T = normalize(vec3(modelMat * vec4(vertTangentIn, 0.0)));
|
||||
vec3 N = normalize(vec3(modelMat * vec4(vertNormalIn, 0.0)));
|
||||
|
||||
// Ensure T is orthogonal with respect to N
|
||||
T = normalize(T - dot(T, N) * N);
|
||||
|
||||
vec3 B = cross(N, T);
|
||||
mat3 tbnMtx = transpose(mat3(T, B, N));
|
||||
|
||||
// Lighting related
|
||||
fragPos = modelVert.xyz;
|
||||
fragPosDirLight = vec3(dirLightProjViewMat * vec4(fragPos, 1));
|
||||
|
||||
tangentCamPos = tbnMtx * camPos;
|
||||
tangentFragPos = tbnMtx * fragPos;
|
||||
tangentDirLightDir = tbnMtx * dirLight.dir;
|
||||
|
||||
for (int i = 0; i < NUM_POINT_LIGHTS; i++)
|
||||
tangentPointLightPositions[i] = tbnMtx * pointLights[i].pos;
|
||||
|
||||
for (int i = 0; i < NUM_SPOT_LIGHTS; i++)
|
||||
{
|
||||
fragPosSpotLight[i] = spotLightProjViewMats[i] * vec4(fragPos, 1);
|
||||
|
||||
tangentSpotLightPositions[i] = tbnMtx * spotLights[i].pos;
|
||||
tangentSpotLightDirections[i] = tbnMtx * spotLights[i].dir;
|
||||
}
|
||||
|
||||
gl_Position = projViewMat * modelVert;
|
||||
}
|
||||
|
||||
//shader:fragment
|
||||
#version 410
|
||||
|
||||
/*
|
||||
Note that while all lighting calculations are done in tangent space,
|
||||
shadow mapping is done in world space.
|
||||
|
||||
The exception is the bias calculation. Since the bias relies on the normal
|
||||
and the normal is in tangent space, we use a tangent space fragment position
|
||||
with it, but the rest of shadow processing is in world space.
|
||||
*/
|
||||
|
||||
#define NUM_SPOT_LIGHTS 4
|
||||
#define NUM_POINT_LIGHTS 8
|
||||
|
||||
//
|
||||
// Inputs
|
||||
//
|
||||
in vec3 fragPos;
|
||||
in vec2 vertUV0;
|
||||
in vec3 vertColor;
|
||||
in vec3 fragPosDirLight;
|
||||
in vec4 fragPosSpotLight[NUM_SPOT_LIGHTS];
|
||||
|
||||
in vec3 tangentCamPos;
|
||||
in vec3 tangentFragPos;
|
||||
in vec3 tangentDirLightDir;
|
||||
in vec3 tangentSpotLightPositions[NUM_SPOT_LIGHTS];
|
||||
in vec3 tangentSpotLightDirections[NUM_SPOT_LIGHTS];
|
||||
in vec3 tangentPointLightPositions[NUM_POINT_LIGHTS];
|
||||
|
||||
//
|
||||
// Uniforms
|
||||
//
|
||||
struct Material {
|
||||
sampler2D diffuse;
|
||||
sampler2D specular;
|
||||
sampler2D normal;
|
||||
sampler2D emission;
|
||||
float shininess;
|
||||
};
|
||||
uniform Material material;
|
||||
|
||||
struct DirLight {
|
||||
vec3 dir;
|
||||
vec3 diffuseColor;
|
||||
vec3 specularColor;
|
||||
};
|
||||
uniform sampler2D dirLightShadowMap;
|
||||
|
||||
struct PointLight {
|
||||
vec3 pos;
|
||||
vec3 diffuseColor;
|
||||
vec3 specularColor;
|
||||
float falloff;
|
||||
float radius;
|
||||
float maxBias;
|
||||
float nearPlane;
|
||||
float farPlane;
|
||||
};
|
||||
uniform PointLight pointLights[NUM_POINT_LIGHTS];
|
||||
uniform samplerCubeArray pointLightCubeShadowMaps;
|
||||
|
||||
struct SpotLight {
|
||||
vec3 pos;
|
||||
vec3 dir;
|
||||
vec3 diffuseColor;
|
||||
vec3 specularColor;
|
||||
float innerCutoff;
|
||||
float outerCutoff;
|
||||
};
|
||||
uniform SpotLight spotLights[NUM_SPOT_LIGHTS];
|
||||
uniform sampler2DArray spotLightShadowMaps;
|
||||
|
||||
layout (std140) uniform GlobalMatrices {
|
||||
vec3 camPos;
|
||||
mat4 projViewMat;
|
||||
};
|
||||
|
||||
layout (std140) uniform Lights {
|
||||
DirLight dirLight;
|
||||
};
|
||||
|
||||
uniform vec3 ambientColor = vec3(0.2, 0.2, 0.2);
|
||||
|
||||
//
|
||||
// Outputs
|
||||
//
|
||||
out vec4 fragColor;
|
||||
|
||||
//
|
||||
// Global variables used as cache for lighting calculations
|
||||
//
|
||||
vec3 tangentViewDir;
|
||||
vec4 diffuseTexColor;
|
||||
vec4 specularTexColor;
|
||||
vec4 emissionTexColor;
|
||||
vec3 normalizedVertNorm;
|
||||
|
||||
float CalcDirShadow(sampler2D shadowMap, vec3 tangentLightDir)
|
||||
{
|
||||
// Move from [-1,1] to [0, 1]
|
||||
vec3 projCoords = fragPosDirLight * 0.5 + 0.5;
|
||||
|
||||
// If sampling outside the depth texture then force 'no shadow'
|
||||
if(projCoords.z > 1)
|
||||
return 0;
|
||||
|
||||
// currentDepth is the fragment depth from the light's perspective
|
||||
float currentDepth = projCoords.z;
|
||||
|
||||
// Bias in the range [0.005, 0.05] depending on the angle, where a higher
|
||||
// angle gives a higher bias, as shadow acne gets worse with angle
|
||||
float bias = max(0.05 * (1 - dot(normalizedVertNorm, tangentLightDir)), 0.005);
|
||||
|
||||
// 'Percentage Close Filtering'.
|
||||
// Basically get soft shadows by averaging this texel and surrounding ones
|
||||
float shadow = 0;
|
||||
vec2 texelSize = 1 / textureSize(shadowMap, 0);
|
||||
for(int x = -1; x <= 1; x++)
|
||||
{
|
||||
for(int y = -1; y <= 1; y++)
|
||||
{
|
||||
float pcfDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r;
|
||||
|
||||
// If our depth is larger than the lights closest depth at the texel we checked (projCoords),
|
||||
// then there is something closer to the light than us, and so we are in shadow
|
||||
shadow += currentDepth - bias > pcfDepth ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
shadow /= 9;
|
||||
|
||||
return shadow;
|
||||
}
|
||||
|
||||
vec3 CalcDirLight()
|
||||
{
|
||||
vec3 lightDir = normalize(-tangentDirLightDir);
|
||||
|
||||
// Diffuse
|
||||
float diffuseAmount = max(0.0, dot(normalizedVertNorm, lightDir));
|
||||
vec3 finalDiffuse = diffuseAmount * dirLight.diffuseColor * diffuseTexColor.rgb;
|
||||
|
||||
// Specular
|
||||
vec3 halfwayDir = normalize(lightDir + tangentViewDir);
|
||||
float specularAmount = pow(max(dot(normalizedVertNorm, halfwayDir), 0.0), material.shininess);
|
||||
vec3 finalSpecular = specularAmount * dirLight.specularColor * specularTexColor.rgb;
|
||||
|
||||
// Shadow
|
||||
float shadow = CalcDirShadow(dirLightShadowMap, lightDir);
|
||||
|
||||
return (finalDiffuse + finalSpecular) * (1 - shadow);
|
||||
}
|
||||
|
||||
float CalcPointShadow(int lightIndex, vec3 worldLightPos, vec3 tangentLightDir, float maxBias, float nearPlane, float farPlane) {
|
||||
|
||||
vec3 lightToFrag = fragPos - worldLightPos;
|
||||
|
||||
// Get depth of current fragment
|
||||
float currentDepth = length(lightToFrag);
|
||||
|
||||
if (currentDepth < nearPlane) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
float closestDepth = texture(pointLightCubeShadowMaps, vec4(lightToFrag, lightIndex)).r;
|
||||
|
||||
// We stored depth in the cubemap in the range [0, 1], so now we move back to [0, farPlane]
|
||||
closestDepth *= farPlane;
|
||||
|
||||
float bias = max(maxBias * (1 - dot(normalizedVertNorm, tangentLightDir)), 0.005);
|
||||
|
||||
float shadow = currentDepth - bias > closestDepth ? 1 : 0;
|
||||
|
||||
return shadow;
|
||||
}
|
||||
|
||||
//
|
||||
// The following point light attenuation formulas
|
||||
// are from https://lisyarus.github.io/blog/posts/point-light-attenuation.html
|
||||
//
|
||||
// I found them more intuitive than the standard implementation and it also ensures
|
||||
// we have zero light at the selected distance.
|
||||
//
|
||||
float sqr(float x)
|
||||
{
|
||||
return x * x;
|
||||
}
|
||||
|
||||
// This version doesn't have a harsh cutoff at radius
|
||||
float AttenuateNoCusp(float dist, float radius, float falloff)
|
||||
{
|
||||
// Since we only use this as attenuation and max intensity defines
|
||||
// the max output value, anything more than 1 would increase
|
||||
// the output of the light, which I don't think makes sense for
|
||||
// our attenuation purposes.
|
||||
//
|
||||
// Seems to me this can be done simply by increasing color values above 255.
|
||||
//
|
||||
// Forcing to 1 for now.
|
||||
#define MAX_INTENSITY 1
|
||||
|
||||
float s = dist / radius;
|
||||
|
||||
if (s >= 1.0)
|
||||
return 0.0;
|
||||
|
||||
float s2 = sqr(s);
|
||||
|
||||
return MAX_INTENSITY * sqr(1 - s2) / (1 + falloff * s2);
|
||||
}
|
||||
|
||||
// This version has a harsh/immediate cutoff at radius
|
||||
float AttenuateCusp(float dist, float radius, float falloff)
|
||||
{
|
||||
#define MAX_INTENSITY 1
|
||||
|
||||
float s = dist / radius;
|
||||
|
||||
if (s >= 1.0)
|
||||
return 0.0;
|
||||
|
||||
float s2 = sqr(s);
|
||||
|
||||
return MAX_INTENSITY * sqr(1 - s2) / (1 + falloff * s);
|
||||
}
|
||||
|
||||
vec3 CalcPointLight(PointLight pointLight, int lightIndex)
|
||||
{
|
||||
// Ignore inactive lights
|
||||
if (pointLight.radius == 0){
|
||||
return vec3(0);
|
||||
}
|
||||
|
||||
vec3 tangentLightPos = tangentPointLightPositions[lightIndex];
|
||||
vec3 tangentLightDir = normalize(tangentLightPos - tangentFragPos);
|
||||
|
||||
// Diffuse
|
||||
float diffuseAmount = max(0.0, dot(normalizedVertNorm, tangentLightDir));
|
||||
vec3 finalDiffuse = diffuseAmount * pointLight.diffuseColor * diffuseTexColor.rgb;
|
||||
|
||||
// Specular
|
||||
vec3 halfwayDir = normalize(tangentLightDir + tangentViewDir);
|
||||
float specularAmount = pow(max(dot(normalizedVertNorm, halfwayDir), 0.0), material.shininess);
|
||||
vec3 finalSpecular = specularAmount * pointLight.specularColor * specularTexColor.rgb;
|
||||
|
||||
// Attenuation
|
||||
float distToLight = length(tangentLightPos - tangentFragPos);
|
||||
float attenuation = AttenuateNoCusp(distToLight, pointLight.radius, pointLight.falloff);
|
||||
|
||||
// Shadow
|
||||
float shadow = CalcPointShadow(lightIndex, pointLight.pos, tangentLightDir, pointLight.maxBias, pointLight.nearPlane, pointLight.farPlane);
|
||||
|
||||
return (finalDiffuse + finalSpecular) * attenuation * (1 - shadow);
|
||||
}
|
||||
|
||||
float CalcSpotShadow(vec3 tangentLightDir, int lightIndex)
|
||||
{
|
||||
// Move from clip space to NDC
|
||||
vec3 projCoords = fragPosSpotLight[lightIndex].xyz / fragPosSpotLight[lightIndex].w;
|
||||
|
||||
// Move from [-1,1] to [0, 1]
|
||||
projCoords = projCoords * 0.5 + 0.5;
|
||||
|
||||
// If sampling outside the depth texture then force 'no shadow'
|
||||
if(projCoords.z > 1)
|
||||
return 0;
|
||||
|
||||
// currentDepth is the fragment depth from the light's perspective
|
||||
float currentDepth = projCoords.z;
|
||||
|
||||
// Bias in the range [0.005, 0.05] depending on the angle, where a higher
|
||||
// angle gives a higher bias, as shadow acne gets worse with angle
|
||||
float bias = max(0.05 * (1 - dot(normalizedVertNorm, tangentLightDir)), 0.005);
|
||||
|
||||
// 'Percentage Close Filtering'.
|
||||
// Basically get soft shadows by averaging this texel and surrounding ones
|
||||
float shadow = 0;
|
||||
vec2 texelSize = 1 / textureSize(spotLightShadowMaps, 0).xy;
|
||||
for(int x = -1; x <= 1; x++)
|
||||
{
|
||||
for(int y = -1; y <= 1; y++)
|
||||
{
|
||||
float pcfDepth = texture(spotLightShadowMaps, vec3(projCoords.xy + vec2(x, y) * texelSize, lightIndex)).r;
|
||||
|
||||
// If our depth is larger than the lights closest depth at the texel we checked (projCoords),
|
||||
// then there is something closer to the light than us, and so we are in shadow
|
||||
shadow += currentDepth - bias > pcfDepth ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
shadow /= 9;
|
||||
|
||||
return shadow;
|
||||
}
|
||||
|
||||
vec3 CalcSpotLight(SpotLight light, int lightIndex)
|
||||
{
|
||||
// The inner/outer cutoffs are cosine values,
|
||||
// which means a value of 1 is mainly produced when the input
|
||||
// is 0 degrees or radians. cos(180) will also be 1, but that's too much :)
|
||||
if (light.innerCutoff == 1)
|
||||
return vec3(0);
|
||||
|
||||
vec3 tangentLightDir = tangentSpotLightDirections[lightIndex];
|
||||
vec3 fragToLightDir = normalize(tangentSpotLightPositions[lightIndex] - tangentFragPos);
|
||||
|
||||
// Spot light cone with full intensity within inner cutoff,
|
||||
// and falloff between inner-outer cutoffs, and zero
|
||||
// light after outer cutoff
|
||||
float theta = dot(fragToLightDir, normalize(-tangentLightDir));
|
||||
float epsilon = (light.innerCutoff - light.outerCutoff);
|
||||
float intensity = clamp((theta - light.outerCutoff) / epsilon, float(0), float(1));
|
||||
|
||||
if (intensity == 0)
|
||||
return vec3(0);
|
||||
|
||||
// Diffuse
|
||||
float diffuseAmount = max(0.0, dot(normalizedVertNorm, fragToLightDir));
|
||||
vec3 finalDiffuse = diffuseAmount * light.diffuseColor * diffuseTexColor.rgb;
|
||||
|
||||
// Specular
|
||||
vec3 halfwayDir = normalize(fragToLightDir + tangentViewDir);
|
||||
float specularAmount = pow(max(dot(normalizedVertNorm, halfwayDir), 0.0), material.shininess);
|
||||
vec3 finalSpecular = specularAmount * light.specularColor * specularTexColor.rgb;
|
||||
|
||||
// Shadow
|
||||
float shadow = CalcSpotShadow(fragToLightDir, lightIndex);
|
||||
|
||||
return (finalDiffuse + finalSpecular) * intensity * (1 - shadow);
|
||||
}
|
||||
|
||||
#define DRAW_NORMALS false
|
||||
|
||||
void main()
|
||||
{
|
||||
// Shared values
|
||||
tangentViewDir = normalize(tangentCamPos - tangentFragPos);
|
||||
diffuseTexColor = texture(material.diffuse, vertUV0);
|
||||
specularTexColor = texture(material.specular, vertUV0);
|
||||
emissionTexColor = texture(material.emission, vertUV0);
|
||||
|
||||
// Read normal data encoded [0,1]
|
||||
normalizedVertNorm = texture(material.normal, vertUV0).rgb;
|
||||
|
||||
// Remap normal to [-1,1]
|
||||
normalizedVertNorm = normalize(normalizedVertNorm * 2.0 - 1.0);
|
||||
|
||||
// Light contributions
|
||||
vec3 finalColor = CalcDirLight();
|
||||
|
||||
for (int i = 0; i < NUM_POINT_LIGHTS; i++)
|
||||
{
|
||||
finalColor += CalcPointLight(pointLights[i], i);
|
||||
}
|
||||
|
||||
for (int i = 0; i < NUM_SPOT_LIGHTS; i++)
|
||||
{
|
||||
finalColor += CalcSpotLight(spotLights[i], i);
|
||||
}
|
||||
|
||||
vec3 finalEmission = emissionTexColor.rgb;
|
||||
vec3 finalAmbient = ambientColor * diffuseTexColor.rgb;
|
||||
|
||||
fragColor = vec4(finalColor + finalAmbient + finalEmission, 1);
|
||||
|
||||
if (DRAW_NORMALS)
|
||||
{
|
||||
fragColor = vec4(texture(material.normal, vertUV0).rgb, 1);
|
||||
}
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
#version 410
|
||||
|
||||
layout(location=0) in vec3 vertPosIn;
|
||||
layout(location=1) in vec3 vertNormalIn;
|
||||
layout(location=2) in vec2 vertUV0In;
|
||||
layout(location=3) in vec3 vertColorIn;
|
||||
|
||||
out vec3 vertNormal;
|
||||
out vec2 vertUV0;
|
||||
out vec3 vertColor;
|
||||
out vec3 fragPos;
|
||||
|
||||
//MVP = Model View Projection
|
||||
uniform mat4 modelMat;
|
||||
uniform mat4 viewMat;
|
||||
uniform mat4 projMat;
|
||||
|
||||
void main()
|
||||
{
|
||||
vertNormal = mat3(transpose(inverse(modelMat))) * vertNormalIn;
|
||||
vertUV0 = vertUV0In;
|
||||
vertColor = vertColorIn;
|
||||
fragPos = vec3(modelMat * vec4(vertPosIn, 1.0));
|
||||
|
||||
gl_Position = projMat * viewMat * modelMat * vec4(vertPosIn, 1.0);
|
||||
}
|
||||
33
res/shaders/skybox.glsl
Executable file
@ -0,0 +1,33 @@
|
||||
//shader:vertex
|
||||
#version 410
|
||||
|
||||
layout(location=0) in vec3 vertPosIn;
|
||||
layout(location=1) in vec3 vertNormalIn;
|
||||
layout(location=2) in vec3 vertTangentIn;
|
||||
layout(location=3) in vec2 vertUV0In;
|
||||
layout(location=4) in vec3 vertColorIn;
|
||||
|
||||
out vec3 vertUV0;
|
||||
|
||||
uniform mat4 projViewMat;
|
||||
|
||||
void main()
|
||||
{
|
||||
vertUV0 = vec3(vertPosIn.x, vertPosIn.y, -vertPosIn.z);
|
||||
vec4 pos = projViewMat * vec4(vertPosIn, 1.0);
|
||||
gl_Position = pos.xyww;
|
||||
}
|
||||
|
||||
//shader:fragment
|
||||
#version 410
|
||||
|
||||
in vec3 vertUV0;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform samplerCube skybox;
|
||||
|
||||
void main()
|
||||
{
|
||||
fragColor = texture(skybox, vertUV0);
|
||||
}
|
||||
50
res/shaders/tonemapped-screen-quad.glsl
Executable file
@ -0,0 +1,50 @@
|
||||
//shader:vertex
|
||||
#version 410
|
||||
|
||||
out vec2 vertUV0;
|
||||
|
||||
// Hardcoded vertex positions for a fullscreen quad.
|
||||
// Format: vec4(pos.x, pos.y, uv0.x, uv0.y)
|
||||
vec4 quadData[6] = vec4[](
|
||||
vec4(-1.0, 1.0, 0.0, 1.0),
|
||||
vec4(-1.0, -1.0, 0.0, 0.0),
|
||||
vec4(1.0, -1.0, 1.0, 0.0),
|
||||
vec4(-1.0, 1.0, 0.0, 1.0),
|
||||
vec4(1.0, -1.0, 1.0, 0.0),
|
||||
vec4(1.0, 1.0, 1.0, 1.0)
|
||||
);
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 vertData = quadData[gl_VertexID];
|
||||
|
||||
vertUV0 = vertData.zw;
|
||||
gl_Position = vec4(vertData.xy, 0.0, 1.0);
|
||||
}
|
||||
|
||||
//shader:fragment
|
||||
#version 410
|
||||
|
||||
struct Material {
|
||||
sampler2D diffuse;
|
||||
};
|
||||
|
||||
uniform float exposure = 1;
|
||||
uniform Material material;
|
||||
|
||||
in vec2 vertUV0;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 diffuseTexColor = texture(material.diffuse, vertUV0);
|
||||
|
||||
// Reinhard tone mapping
|
||||
// vec3 mappedColor = diffuseTexColor.rgb / (diffuseTexColor.rgb + vec3(1.0));
|
||||
|
||||
// Exposure tone mapping
|
||||
vec3 mappedColor = vec3(1.0) - exp(-diffuseTexColor.rgb * exposure);
|
||||
|
||||
fragColor = vec4(mappedColor, 1);
|
||||
}
|
||||
BIN
res/textures/brickwall-normal.png
Executable file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
res/textures/brickwall.png
Executable file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
res/textures/container-diffuse.png
Executable file
|
After Width: | Height: | Size: 457 KiB |
BIN
res/textures/container-specular.png
Executable file
|
After Width: | Height: | Size: 141 KiB |
BIN
res/textures/pallete-endesga-64-1x.png
Executable file
|
After Width: | Height: | Size: 355 B |
BIN
res/textures/sb-back.jpg
Executable file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
res/textures/sb-bottom.jpg
Executable file
|
After Width: | Height: | Size: 617 KiB |
BIN
res/textures/sb-front.jpg
Executable file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
res/textures/sb-left.jpg
Executable file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
res/textures/sb-right.jpg
Executable file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
res/textures/sb-top.jpg
Executable file
|
After Width: | Height: | Size: 770 KiB |
BIN
rsrc_windows_386.syso
Executable file
BIN
rsrc_windows_amd64.syso
Executable file
@ -6,32 +6,48 @@ import (
|
||||
)
|
||||
|
||||
type ShaderProgram struct {
|
||||
ID uint32
|
||||
VertShaderID uint32
|
||||
FragShaderID uint32
|
||||
Id uint32
|
||||
VertShaderId uint32
|
||||
FragShaderId uint32
|
||||
GeomShaderId uint32
|
||||
}
|
||||
|
||||
func (sp *ShaderProgram) AttachShader(shader Shader) {
|
||||
|
||||
gl.AttachShader(sp.ID, shader.ID)
|
||||
switch shader.ShaderType {
|
||||
case VertexShaderType:
|
||||
sp.VertShaderID = shader.ID
|
||||
case FragmentShaderType:
|
||||
sp.FragShaderID = shader.ID
|
||||
gl.AttachShader(sp.Id, shader.Id)
|
||||
switch shader.Type {
|
||||
case ShaderType_Vertex:
|
||||
sp.VertShaderId = shader.Id
|
||||
case ShaderType_Fragment:
|
||||
sp.FragShaderId = shader.Id
|
||||
case ShaderType_Geometry:
|
||||
sp.GeomShaderId = shader.Id
|
||||
default:
|
||||
logging.ErrLog.Println("Unknown shader type ", shader.ShaderType, " for ID ", shader.ID)
|
||||
logging.ErrLog.Fatalf("Unknown shader type '%d' for shader id '%d'\n", shader.Type, shader.Id)
|
||||
}
|
||||
}
|
||||
|
||||
func (sp *ShaderProgram) Link() {
|
||||
|
||||
gl.LinkProgram(sp.ID)
|
||||
gl.LinkProgram(sp.Id)
|
||||
|
||||
if sp.VertShaderID != 0 {
|
||||
gl.DeleteShader(sp.VertShaderID)
|
||||
if sp.VertShaderId != 0 {
|
||||
gl.DeleteShader(sp.VertShaderId)
|
||||
}
|
||||
if sp.FragShaderID != 0 {
|
||||
gl.DeleteShader(sp.FragShaderID)
|
||||
|
||||
if sp.FragShaderId != 0 {
|
||||
gl.DeleteShader(sp.FragShaderId)
|
||||
}
|
||||
|
||||
if sp.GeomShaderId != 0 {
|
||||
gl.DeleteShader(sp.GeomShaderId)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ShaderProgram) Bind() {
|
||||
gl.UseProgram(s.Id)
|
||||
}
|
||||
|
||||
func (s *ShaderProgram) UnBind() {
|
||||
gl.UseProgram(0)
|
||||
}
|
||||
|
||||
@ -1,10 +1,31 @@
|
||||
package shaders
|
||||
|
||||
import "github.com/go-gl/gl/v4.1-core/gl"
|
||||
import (
|
||||
"github.com/bloeys/nmage/logging"
|
||||
"github.com/go-gl/gl/v4.1-core/gl"
|
||||
)
|
||||
|
||||
type ShaderType int
|
||||
type ShaderType int32
|
||||
|
||||
func (s ShaderType) ToGl() uint32 {
|
||||
|
||||
switch s {
|
||||
case ShaderType_Vertex:
|
||||
return gl.VERTEX_SHADER
|
||||
case ShaderType_Fragment:
|
||||
return gl.FRAGMENT_SHADER
|
||||
case ShaderType_Geometry:
|
||||
return gl.GEOMETRY_SHADER
|
||||
|
||||
default:
|
||||
logging.ErrLog.Fatalf("Unknown shader type '%d'\n", s)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
VertexShaderType ShaderType = gl.VERTEX_SHADER
|
||||
FragmentShaderType ShaderType = gl.FRAGMENT_SHADER
|
||||
ShaderType_Unknown ShaderType = iota
|
||||
ShaderType_Vertex
|
||||
ShaderType_Fragment
|
||||
ShaderType_Geometry
|
||||
)
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
package shaders
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@ -10,12 +12,13 @@ import (
|
||||
)
|
||||
|
||||
type Shader struct {
|
||||
ID uint32
|
||||
ShaderType ShaderType
|
||||
Id uint32
|
||||
Type ShaderType
|
||||
}
|
||||
|
||||
func (s Shader) Delete() {
|
||||
gl.DeleteShader(s.ID)
|
||||
func (s *Shader) Delete() {
|
||||
gl.DeleteShader(s.Id)
|
||||
s.Id = 0
|
||||
}
|
||||
|
||||
func NewShaderProgram() (ShaderProgram, error) {
|
||||
@ -25,52 +28,117 @@ func NewShaderProgram() (ShaderProgram, error) {
|
||||
return ShaderProgram{}, errors.New("failed to create shader program")
|
||||
}
|
||||
|
||||
return ShaderProgram{ID: id}, nil
|
||||
return ShaderProgram{Id: id}, nil
|
||||
}
|
||||
|
||||
func LoadAndCompilerShader(shaderPath string, shaderType ShaderType) (Shader, error) {
|
||||
func LoadAndCompileCombinedShader(shaderPath string) (ShaderProgram, error) {
|
||||
|
||||
shaderSource, err := os.ReadFile(shaderPath)
|
||||
combinedSource, err := os.ReadFile(shaderPath)
|
||||
if err != nil {
|
||||
logging.ErrLog.Println("Failed to read shader. Err: ", err)
|
||||
return Shader{}, err
|
||||
return ShaderProgram{}, err
|
||||
}
|
||||
|
||||
shaderID := gl.CreateShader(uint32(shaderType))
|
||||
if shaderID == 0 {
|
||||
logging.ErrLog.Println("Failed to create shader.")
|
||||
return Shader{}, errors.New("failed to create shader")
|
||||
return LoadAndCompileCombinedShaderSrc(combinedSource)
|
||||
|
||||
}
|
||||
func LoadAndCompileCombinedShaderSrc(shaderSrc []byte) (ShaderProgram, error) {
|
||||
|
||||
shaderSources := bytes.Split(shaderSrc, []byte("//shader:"))
|
||||
if len(shaderSources) < 2 {
|
||||
return ShaderProgram{}, errors.New("failed to read combined shader. The minimum shader types to have are '//shader:vertex' and '//shader:fragment'")
|
||||
}
|
||||
|
||||
shdrProg, err := NewShaderProgram()
|
||||
if err != nil {
|
||||
return ShaderProgram{}, errors.New("failed to create new shader program. Err: " + err.Error())
|
||||
}
|
||||
|
||||
loadedShdrCount := 0
|
||||
for i := 0; i < len(shaderSources); i++ {
|
||||
|
||||
src := shaderSources[i]
|
||||
|
||||
//This can happen when the shader type is at the start of the file
|
||||
if len(bytes.TrimSpace(src)) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var shdrType ShaderType
|
||||
if bytes.HasPrefix(src, []byte("vertex")) {
|
||||
src = src[6:]
|
||||
shdrType = ShaderType_Vertex
|
||||
} else if bytes.HasPrefix(src, []byte("fragment")) {
|
||||
src = src[8:]
|
||||
shdrType = ShaderType_Fragment
|
||||
} else if bytes.HasPrefix(src, []byte("geometry")) {
|
||||
src = src[8:]
|
||||
shdrType = ShaderType_Geometry
|
||||
} else {
|
||||
return ShaderProgram{}, errors.New("unknown shader type. Must be '//shader:vertex' or '//shader:fragment' or '//shader:geometry'")
|
||||
}
|
||||
|
||||
shdr, err := CompileShaderOfType(src, shdrType)
|
||||
if err != nil {
|
||||
return ShaderProgram{}, err
|
||||
}
|
||||
|
||||
loadedShdrCount++
|
||||
shdrProg.AttachShader(shdr)
|
||||
}
|
||||
|
||||
if loadedShdrCount == 0 {
|
||||
return ShaderProgram{}, errors.New("no valid shaders found. Please put '//shader:vertex' or '//shader:fragment' or '//shader:geometry' before your shaders")
|
||||
}
|
||||
|
||||
if shdrProg.VertShaderId == 0 {
|
||||
return ShaderProgram{}, errors.New("no valid vertex shader found. Please put '//shader:vertex' before your vertex shader")
|
||||
}
|
||||
|
||||
if shdrProg.FragShaderId == 0 {
|
||||
return ShaderProgram{}, errors.New("no valid fragment shader found. Please put '//shader:fragment' before your vertex shader")
|
||||
}
|
||||
|
||||
shdrProg.Link()
|
||||
return shdrProg, nil
|
||||
}
|
||||
|
||||
func CompileShaderOfType(shaderSource []byte, shaderType ShaderType) (Shader, error) {
|
||||
|
||||
shaderId := gl.CreateShader(shaderType.ToGl())
|
||||
if shaderId == 0 {
|
||||
return Shader{}, fmt.Errorf("failed to create OpenGl shader. OpenGl Error=%d", gl.GetError())
|
||||
}
|
||||
|
||||
//Load shader source and compile
|
||||
shaderCStr, shaderFree := gl.Strs(string(shaderSource) + "\x00")
|
||||
defer shaderFree()
|
||||
gl.ShaderSource(shaderID, 1, shaderCStr, nil)
|
||||
gl.ShaderSource(shaderId, 1, shaderCStr, nil)
|
||||
|
||||
gl.CompileShader(shaderID)
|
||||
if err := getShaderCompileErrors(shaderID); err != nil {
|
||||
gl.DeleteShader(shaderID)
|
||||
gl.CompileShader(shaderId)
|
||||
if err := getShaderCompileErrors(shaderId); err != nil {
|
||||
gl.DeleteShader(shaderId)
|
||||
return Shader{}, err
|
||||
}
|
||||
|
||||
return Shader{ID: shaderID, ShaderType: shaderType}, nil
|
||||
return Shader{Id: shaderId, Type: shaderType}, nil
|
||||
}
|
||||
|
||||
func getShaderCompileErrors(shaderID uint32) error {
|
||||
func getShaderCompileErrors(shaderId uint32) error {
|
||||
|
||||
var compiledSuccessfully int32
|
||||
gl.GetShaderiv(shaderID, gl.COMPILE_STATUS, &compiledSuccessfully)
|
||||
gl.GetShaderiv(shaderId, gl.COMPILE_STATUS, &compiledSuccessfully)
|
||||
if compiledSuccessfully == gl.TRUE {
|
||||
return nil
|
||||
}
|
||||
|
||||
var logLength int32
|
||||
gl.GetShaderiv(shaderID, gl.INFO_LOG_LENGTH, &logLength)
|
||||
gl.GetShaderiv(shaderId, gl.INFO_LOG_LENGTH, &logLength)
|
||||
|
||||
log := gl.Str(strings.Repeat("\x00", int(logLength)))
|
||||
gl.GetShaderInfoLog(shaderID, logLength, nil, log)
|
||||
gl.GetShaderInfoLog(shaderId, logLength, nil, log)
|
||||
|
||||
errMsg := gl.GoStr(log)
|
||||
logging.ErrLog.Println("Compilation of shader with id ", shaderID, " failed. Err: ", errMsg)
|
||||
logging.ErrLog.Println("Compilation of shader with id ", shaderId, " failed. Err: ", errMsg)
|
||||
return errors.New(errMsg)
|
||||
}
|
||||
|
||||
@ -1,30 +1,30 @@
|
||||
package nmageimgui
|
||||
|
||||
import (
|
||||
imgui "github.com/AllenDang/cimgui-go"
|
||||
"github.com/bloeys/gglm/gglm"
|
||||
"github.com/bloeys/nmage/asserts"
|
||||
"github.com/bloeys/nmage/materials"
|
||||
"github.com/bloeys/nmage/timing"
|
||||
"github.com/go-gl/gl/v4.1-core/gl"
|
||||
"github.com/inkyblackness/imgui-go/v4"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
type ImguiInfo struct {
|
||||
ImCtx *imgui.Context
|
||||
ImCtx imgui.Context
|
||||
|
||||
Mat *materials.Material
|
||||
Mat materials.Material
|
||||
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) {
|
||||
|
||||
if err := i.ImCtx.SetCurrent(); err != nil {
|
||||
asserts.T(false, "Setting imgui ctx as current failed. Err: "+err.Error())
|
||||
}
|
||||
// if err := i.ImCtx.SetCurrent(); err != nil {
|
||||
// assert.T(false, "Setting imgui ctx as current failed. Err: "+err.Error())
|
||||
// }
|
||||
|
||||
imIO := imgui.CurrentIO()
|
||||
imIO.SetDisplaySize(imgui.Vec2{X: float32(winWidth), Y: float32(winHeight)})
|
||||
@ -35,9 +35,9 @@ func (i *ImguiInfo) FrameStart(winWidth, winHeight float32) {
|
||||
|
||||
func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32) {
|
||||
|
||||
if err := i.ImCtx.SetCurrent(); err != nil {
|
||||
asserts.T(false, "Setting imgui ctx as current failed. Err: "+err.Error())
|
||||
}
|
||||
// if err := i.ImCtx.SetCurrent(); err != nil {
|
||||
// assert.T(false, "Setting imgui ctx as current failed. Err: "+err.Error())
|
||||
// }
|
||||
|
||||
imgui.Render()
|
||||
|
||||
@ -46,7 +46,7 @@ func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32)
|
||||
return
|
||||
}
|
||||
|
||||
drawData := imgui.RenderedDrawData()
|
||||
drawData := imgui.CurrentDrawData()
|
||||
drawData.ScaleClipRects(imgui.Vec2{
|
||||
X: float32(fbWidth) / float32(winWidth),
|
||||
Y: float32(fbHeight) / float32(winHeight),
|
||||
@ -66,10 +66,9 @@ func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32)
|
||||
// DisplayMin is typically (0,0) for single viewport apps.
|
||||
|
||||
i.Mat.Bind()
|
||||
i.Mat.SetUnifInt32("Texture", 0)
|
||||
|
||||
gl.Uniform1i(gl.GetUniformLocation(i.Mat.ShaderProg.ID, gl.Str("Texture\x00")), 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.
|
||||
@ -97,11 +96,11 @@ func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32)
|
||||
// Draw
|
||||
for _, list := range drawData.CommandLists() {
|
||||
|
||||
vertexBuffer, vertexBufferSize := list.VertexBuffer()
|
||||
vertexBuffer, vertexBufferSize := list.GetVertexBuffer()
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, i.VboID)
|
||||
gl.BufferData(gl.ARRAY_BUFFER, vertexBufferSize, vertexBuffer, gl.STREAM_DRAW)
|
||||
|
||||
indexBuffer, indexBufferSize := list.IndexBuffer()
|
||||
indexBuffer, indexBufferSize := list.GetIndexBuffer()
|
||||
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, i.IndexBufID)
|
||||
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, indexBufferSize, indexBuffer, gl.STREAM_DRAW)
|
||||
|
||||
@ -110,11 +109,12 @@ func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32)
|
||||
cmd.CallUserCallback(list)
|
||||
} else {
|
||||
|
||||
gl.BindTexture(gl.TEXTURE_2D, i.TexID)
|
||||
gl.ActiveTexture(gl.TEXTURE0)
|
||||
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.ElementCount()), uint32(drawType), gl.PtrOffset(cmd.IndexOffset()*indexSize), int32(cmd.VertexOffset()))
|
||||
gl.DrawElementsBaseVertexWithOffset(gl.TRIANGLES, int32(cmd.ElemCount()), uint32(drawType), uintptr(int(cmd.IdxOffset())*indexSize), int32(cmd.VtxOffset()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -125,14 +125,14 @@ func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32)
|
||||
gl.Enable(gl.DEPTH_TEST)
|
||||
}
|
||||
|
||||
func (i *ImguiInfo) AddFontTTF(fontPath string, fontSize float32, fontConfig *imgui.FontConfig, glyphRanges *imgui.GlyphRanges) imgui.Font {
|
||||
func (i *ImguiInfo) AddFontTTF(fontPath string, fontSize float32, fontConfig *imgui.FontConfig, glyphRanges *imgui.GlyphRange) imgui.Font {
|
||||
|
||||
fontConfigToUse := imgui.DefaultFontConfig
|
||||
fontConfigToUse := imgui.NewFontConfig()
|
||||
if fontConfig != nil {
|
||||
fontConfigToUse = *fontConfig
|
||||
}
|
||||
|
||||
glyphRangesToUse := imgui.EmptyGlyphRanges
|
||||
glyphRangesToUse := imgui.NewGlyphRange()
|
||||
if glyphRanges != nil {
|
||||
glyphRangesToUse = *glyphRanges
|
||||
}
|
||||
@ -140,41 +140,104 @@ func (i *ImguiInfo) AddFontTTF(fontPath string, fontSize float32, fontConfig *im
|
||||
imIO := imgui.CurrentIO()
|
||||
|
||||
a := imIO.Fonts()
|
||||
f := a.AddFontFromFileTTFV(fontPath, fontSize, fontConfigToUse, glyphRangesToUse)
|
||||
image := a.TextureDataAlpha8()
|
||||
f := a.AddFontFromFileTTFV(fontPath, fontSize, fontConfigToUse, glyphRangesToUse.Data())
|
||||
pixels, width, height, _ := a.GetTextureDataAsAlpha8()
|
||||
|
||||
gl.BindTexture(gl.TEXTURE_2D, i.TexID)
|
||||
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, int32(image.Width), int32(image.Height), 0, gl.RED, gl.UNSIGNED_BYTE, image.Pixels)
|
||||
gl.ActiveTexture(gl.TEXTURE0)
|
||||
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
|
||||
}
|
||||
|
||||
func NewImGUI() ImguiInfo {
|
||||
const DefaultImguiShader = `
|
||||
//shader:vertex
|
||||
#version 410
|
||||
|
||||
imguiInfo := ImguiInfo{
|
||||
ImCtx: imgui.CreateContext(nil),
|
||||
Mat: materials.NewMaterial("ImGUI Mat", "./res/shaders/imgui"),
|
||||
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);
|
||||
}
|
||||
`
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
imIO := imgui.CurrentIO()
|
||||
imIO.SetBackendFlags(imIO.GetBackendFlags() | imgui.BackendFlagsRendererHasVtxOffset)
|
||||
imguiInfo := ImguiInfo{
|
||||
ImCtx: imgui.CreateContext(),
|
||||
Mat: imguiMat,
|
||||
TexID: new(uint32),
|
||||
}
|
||||
|
||||
io := imgui.CurrentIO()
|
||||
io.SetConfigFlags(io.ConfigFlags() | imgui.ConfigFlagsDockingEnable)
|
||||
io.SetBackendFlags(io.BackendFlags() | imgui.BackendFlagsRendererHasVtxOffset)
|
||||
|
||||
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.ActiveTexture(gl.TEXTURE0)
|
||||
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)
|
||||
|
||||
image := imIO.Fonts().TextureDataAlpha8()
|
||||
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, int32(image.Width), int32(image.Height), 0, gl.RED, gl.UNSIGNED_BYTE, image.Pixels)
|
||||
pixels, width, height, _ := io.Fonts().GetTextureDataAsAlpha8()
|
||||
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, int32(width), int32(height), 0, gl.RED, gl.UNSIGNED_BYTE, pixels)
|
||||
|
||||
// Store our identifier
|
||||
imIO.Fonts().SetTextureID(imgui.TextureID(imguiInfo.TexID))
|
||||
io.Fonts().SetTexID(imgui.TextureID(imguiInfo.TexID))
|
||||
|
||||
//Shader attributes
|
||||
imguiInfo.Mat.Bind()
|
||||
@ -183,35 +246,223 @@ func NewImGUI() ImguiInfo {
|
||||
imguiInfo.Mat.EnableAttribute("Color")
|
||||
imguiInfo.Mat.UnBind()
|
||||
|
||||
//Init imgui input mapping
|
||||
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.
|
||||
for imguiKey, nativeKey := range keys {
|
||||
imIO.KeyMap(imguiKey, nativeKey)
|
||||
}
|
||||
|
||||
return imguiInfo
|
||||
}
|
||||
|
||||
func SdlScancodeToImGuiKey(scancode sdl.Scancode) imgui.Key {
|
||||
|
||||
switch scancode {
|
||||
case sdl.SCANCODE_TAB:
|
||||
return imgui.KeyTab
|
||||
case sdl.SCANCODE_LEFT:
|
||||
return imgui.KeyLeftArrow
|
||||
case sdl.SCANCODE_RIGHT:
|
||||
return imgui.KeyRightArrow
|
||||
case sdl.SCANCODE_UP:
|
||||
return imgui.KeyUpArrow
|
||||
case sdl.SCANCODE_DOWN:
|
||||
return imgui.KeyDownArrow
|
||||
case sdl.SCANCODE_PAGEUP:
|
||||
return imgui.KeyPageUp
|
||||
case sdl.SCANCODE_PAGEDOWN:
|
||||
return imgui.KeyPageDown
|
||||
case sdl.SCANCODE_HOME:
|
||||
return imgui.KeyHome
|
||||
case sdl.SCANCODE_END:
|
||||
return imgui.KeyEnd
|
||||
case sdl.SCANCODE_INSERT:
|
||||
return imgui.KeyInsert
|
||||
case sdl.SCANCODE_DELETE:
|
||||
return imgui.KeyDelete
|
||||
case sdl.SCANCODE_BACKSPACE:
|
||||
return imgui.KeyBackspace
|
||||
case sdl.SCANCODE_SPACE:
|
||||
return imgui.KeySpace
|
||||
case sdl.SCANCODE_RETURN:
|
||||
return imgui.KeyEnter
|
||||
case sdl.SCANCODE_ESCAPE:
|
||||
return imgui.KeyEscape
|
||||
case sdl.SCANCODE_APOSTROPHE:
|
||||
return imgui.KeyApostrophe
|
||||
case sdl.SCANCODE_COMMA:
|
||||
return imgui.KeyComma
|
||||
case sdl.SCANCODE_MINUS:
|
||||
return imgui.KeyMinus
|
||||
case sdl.SCANCODE_PERIOD:
|
||||
return imgui.KeyPeriod
|
||||
case sdl.SCANCODE_SLASH:
|
||||
return imgui.KeySlash
|
||||
case sdl.SCANCODE_SEMICOLON:
|
||||
return imgui.KeySemicolon
|
||||
case sdl.SCANCODE_EQUALS:
|
||||
return imgui.KeyEqual
|
||||
case sdl.SCANCODE_LEFTBRACKET:
|
||||
return imgui.KeyLeftBracket
|
||||
case sdl.SCANCODE_BACKSLASH:
|
||||
return imgui.KeyBackslash
|
||||
case sdl.SCANCODE_RIGHTBRACKET:
|
||||
return imgui.KeyRightBracket
|
||||
case sdl.SCANCODE_GRAVE:
|
||||
return imgui.KeyGraveAccent
|
||||
case sdl.SCANCODE_CAPSLOCK:
|
||||
return imgui.KeyCapsLock
|
||||
case sdl.SCANCODE_SCROLLLOCK:
|
||||
return imgui.KeyScrollLock
|
||||
case sdl.SCANCODE_NUMLOCKCLEAR:
|
||||
return imgui.KeyNumLock
|
||||
case sdl.SCANCODE_PRINTSCREEN:
|
||||
return imgui.KeyPrintScreen
|
||||
case sdl.SCANCODE_PAUSE:
|
||||
return imgui.KeyPause
|
||||
case sdl.SCANCODE_KP_0:
|
||||
return imgui.KeyKeypad0
|
||||
case sdl.SCANCODE_KP_1:
|
||||
return imgui.KeyKeypad1
|
||||
case sdl.SCANCODE_KP_2:
|
||||
return imgui.KeyKeypad2
|
||||
case sdl.SCANCODE_KP_3:
|
||||
return imgui.KeyKeypad3
|
||||
case sdl.SCANCODE_KP_4:
|
||||
return imgui.KeyKeypad4
|
||||
case sdl.SCANCODE_KP_5:
|
||||
return imgui.KeyKeypad5
|
||||
case sdl.SCANCODE_KP_6:
|
||||
return imgui.KeyKeypad6
|
||||
case sdl.SCANCODE_KP_7:
|
||||
return imgui.KeyKeypad7
|
||||
case sdl.SCANCODE_KP_8:
|
||||
return imgui.KeyKeypad8
|
||||
case sdl.SCANCODE_KP_9:
|
||||
return imgui.KeyKeypad9
|
||||
case sdl.SCANCODE_KP_PERIOD:
|
||||
return imgui.KeyKeypadDecimal
|
||||
case sdl.SCANCODE_KP_DIVIDE:
|
||||
return imgui.KeyKeypadDivide
|
||||
case sdl.SCANCODE_KP_MULTIPLY:
|
||||
return imgui.KeyKeypadMultiply
|
||||
case sdl.SCANCODE_KP_MINUS:
|
||||
return imgui.KeyKeypadSubtract
|
||||
case sdl.SCANCODE_KP_PLUS:
|
||||
return imgui.KeyKeypadAdd
|
||||
case sdl.SCANCODE_KP_ENTER:
|
||||
return imgui.KeyKeypadEnter
|
||||
case sdl.SCANCODE_KP_EQUALS:
|
||||
return imgui.KeyKeypadEqual
|
||||
case sdl.SCANCODE_LSHIFT:
|
||||
return imgui.KeyLeftShift
|
||||
case sdl.SCANCODE_LCTRL:
|
||||
return imgui.KeyLeftCtrl
|
||||
case sdl.SCANCODE_LALT:
|
||||
return imgui.KeyLeftAlt
|
||||
case sdl.SCANCODE_LGUI:
|
||||
return imgui.KeyLeftSuper
|
||||
case sdl.SCANCODE_RSHIFT:
|
||||
return imgui.KeyRightShift
|
||||
case sdl.SCANCODE_RCTRL:
|
||||
return imgui.KeyRightCtrl
|
||||
case sdl.SCANCODE_RALT:
|
||||
return imgui.KeyRightAlt
|
||||
case sdl.SCANCODE_RGUI:
|
||||
return imgui.KeyRightSuper
|
||||
case sdl.SCANCODE_MENU:
|
||||
return imgui.KeyMenu
|
||||
case sdl.SCANCODE_0:
|
||||
return imgui.Key0
|
||||
case sdl.SCANCODE_1:
|
||||
return imgui.Key1
|
||||
case sdl.SCANCODE_2:
|
||||
return imgui.Key2
|
||||
case sdl.SCANCODE_3:
|
||||
return imgui.Key3
|
||||
case sdl.SCANCODE_4:
|
||||
return imgui.Key4
|
||||
case sdl.SCANCODE_5:
|
||||
return imgui.Key5
|
||||
case sdl.SCANCODE_6:
|
||||
return imgui.Key6
|
||||
case sdl.SCANCODE_7:
|
||||
return imgui.Key7
|
||||
case sdl.SCANCODE_8:
|
||||
return imgui.Key8
|
||||
case sdl.SCANCODE_9:
|
||||
return imgui.Key9
|
||||
case sdl.SCANCODE_A:
|
||||
return imgui.KeyA
|
||||
case sdl.SCANCODE_B:
|
||||
return imgui.KeyB
|
||||
case sdl.SCANCODE_C:
|
||||
return imgui.KeyC
|
||||
case sdl.SCANCODE_D:
|
||||
return imgui.KeyD
|
||||
case sdl.SCANCODE_E:
|
||||
return imgui.KeyE
|
||||
case sdl.SCANCODE_F:
|
||||
return imgui.KeyF
|
||||
case sdl.SCANCODE_G:
|
||||
return imgui.KeyG
|
||||
case sdl.SCANCODE_H:
|
||||
return imgui.KeyH
|
||||
case sdl.SCANCODE_I:
|
||||
return imgui.KeyI
|
||||
case sdl.SCANCODE_J:
|
||||
return imgui.KeyJ
|
||||
case sdl.SCANCODE_K:
|
||||
return imgui.KeyK
|
||||
case sdl.SCANCODE_L:
|
||||
return imgui.KeyL
|
||||
case sdl.SCANCODE_M:
|
||||
return imgui.KeyM
|
||||
case sdl.SCANCODE_N:
|
||||
return imgui.KeyN
|
||||
case sdl.SCANCODE_O:
|
||||
return imgui.KeyO
|
||||
case sdl.SCANCODE_P:
|
||||
return imgui.KeyP
|
||||
case sdl.SCANCODE_Q:
|
||||
return imgui.KeyQ
|
||||
case sdl.SCANCODE_R:
|
||||
return imgui.KeyR
|
||||
case sdl.SCANCODE_S:
|
||||
return imgui.KeyS
|
||||
case sdl.SCANCODE_T:
|
||||
return imgui.KeyT
|
||||
case sdl.SCANCODE_U:
|
||||
return imgui.KeyU
|
||||
case sdl.SCANCODE_V:
|
||||
return imgui.KeyV
|
||||
case sdl.SCANCODE_W:
|
||||
return imgui.KeyW
|
||||
case sdl.SCANCODE_X:
|
||||
return imgui.KeyX
|
||||
case sdl.SCANCODE_Y:
|
||||
return imgui.KeyY
|
||||
case sdl.SCANCODE_Z:
|
||||
return imgui.KeyZ
|
||||
case sdl.SCANCODE_F1:
|
||||
return imgui.KeyF1
|
||||
case sdl.SCANCODE_F2:
|
||||
return imgui.KeyF2
|
||||
case sdl.SCANCODE_F3:
|
||||
return imgui.KeyF3
|
||||
case sdl.SCANCODE_F4:
|
||||
return imgui.KeyF4
|
||||
case sdl.SCANCODE_F5:
|
||||
return imgui.KeyF5
|
||||
case sdl.SCANCODE_F6:
|
||||
return imgui.KeyF6
|
||||
case sdl.SCANCODE_F7:
|
||||
return imgui.KeyF7
|
||||
case sdl.SCANCODE_F8:
|
||||
return imgui.KeyF8
|
||||
case sdl.SCANCODE_F9:
|
||||
return imgui.KeyF9
|
||||
case sdl.SCANCODE_F10:
|
||||
return imgui.KeyF10
|
||||
case sdl.SCANCODE_F11:
|
||||
return imgui.KeyF11
|
||||
case sdl.SCANCODE_F12:
|
||||
return imgui.KeyF12
|
||||
default:
|
||||
return imgui.KeyNone
|
||||
}
|
||||
}
|
||||
|
||||