Compare commits
95 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f5a5d72cc4 | |||
| 1bec97b128 | |||
| dea2ac965f | |||
| 93b5f08352 | |||
| 34e19d9c66 | |||
| 372d9ae6b7 | |||
| befc78c628 | |||
| 98f8a96bb7 | |||
| e767f32f2f | |||
| 6d94efbf97 | |||
| 28f543a579 | |||
| 2a73a12885 | |||
| e4199b8d30 | |||
| 38248822e2 | |||
| 5c98903723 | |||
| 3cdd40f0a2 | |||
| 9dccb23613 | |||
| 5dfdea9a7b | |||
| bcb46d1699 | |||
| 91807a4093 | |||
| 09231c5ebd | |||
| 0e98dc85f5 | |||
| 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 |
61
.github/workflows/build-nmage.yml
vendored
@ -1,24 +1,69 @@
|
||||
name: build-nmage
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
create:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-nmage-macos:
|
||||
runs-on: macos-12
|
||||
build-nmage-windows:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
|
||||
- name: Install golang 1.18
|
||||
uses: actions/setup-go@v3
|
||||
- name: Install golang
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '^1.18'
|
||||
go-version: ">=1.23"
|
||||
|
||||
- name: Install assimp-go dll
|
||||
run: |
|
||||
Invoke-WebRequest -Uri "https://github.com/bloeys/assimp-go/releases/download/v0.4.2/libassimp-5.dll" -OutFile "C:\Windows\System32\libassimp-5.dll"
|
||||
|
||||
- name: Download and setup SDL2
|
||||
run: |
|
||||
Invoke-WebRequest -Uri "https://github.com/libsdl-org/SDL/releases/download/release-2.30.7/SDL2-devel-2.30.7-mingw.zip" -OutFile "SDL2.zip"
|
||||
Expand-Archive -Path "SDL2.zip" -DestinationPath "SDL2"
|
||||
Copy-Item -Path "SDL2\SDL2-2.30.7\x86_64-w64-mingw32" -Destination "C:\mingw64" -Recurse -Force
|
||||
|
||||
- name: Clone nmage
|
||||
run: git clone https://github.com/bloeys/nmage
|
||||
|
||||
- name: Build nmage
|
||||
working-directory: nmage
|
||||
run: go build .
|
||||
|
||||
build-nmage-macos:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
# Based on: https://github.com/actions/runner-images?tab=readme-ov-file#available-images
|
||||
os:
|
||||
- macos-13 # x86
|
||||
- macos-14 # Arm
|
||||
|
||||
steps:
|
||||
- name: Install golang
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ">=1.23"
|
||||
|
||||
- name: Determine architecture
|
||||
id: arch
|
||||
run: |
|
||||
if [ "$(uname -m)" = "arm64" ]; then
|
||||
echo "arch=arm64" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "arch=amd64" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- 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
|
||||
run: sudo mkdir -p /usr/local/lib && sudo wget https://github.com/bloeys/assimp-go/releases/download/v0.4.2/libassimp_darwin_${{ steps.arch.outputs.arch }}.dylib -O /usr/local/lib/libassimp.5.dylib
|
||||
|
||||
- name: Install SDL2
|
||||
run: brew install sdl2{,_image,_mixer,_ttf,_gfx} pkg-config
|
||||
run: brew install sdl2{,_image,_ttf,_gfx} pkg-config
|
||||
|
||||
- name: Clone nmage
|
||||
run: git clone https://github.com/bloeys/nmage
|
||||
|
||||
5
.gitignore
vendored
@ -15,4 +15,7 @@
|
||||
vendor/
|
||||
.vscode/
|
||||
imgui.ini
|
||||
*~
|
||||
*~
|
||||
|
||||
# Custom
|
||||
*.pprof
|
||||
@ -4,7 +4,6 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"io"
|
||||
@ -14,20 +13,40 @@ import (
|
||||
"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 only exists for textures loaded from disk
|
||||
Path string
|
||||
TexID uint32
|
||||
Width int32
|
||||
// Path only exists for textures loaded from disk
|
||||
Path string
|
||||
|
||||
TexID uint32
|
||||
|
||||
// Width is the width of the texture in pixels (pixels per row).
|
||||
// Note that the number of bytes constituting a row is MORE than this (e.g. for RGBA8, bytesPerRow=width*4, since we have 4 bytes per pixel)
|
||||
Width int32
|
||||
|
||||
// Height is the height of the texture in pixels (pixels per column).
|
||||
// Note that the number of bytes constituting a column is MORE than this (e.g. for RGBA8, bytesPerColumn=height*4, since we have 4 bytes per pixel)
|
||||
Height int32
|
||||
|
||||
// Pixels usually stored in RGBA format
|
||||
Pixels []byte
|
||||
}
|
||||
|
||||
@ -36,6 +55,7 @@ type TextureLoadOptions struct {
|
||||
WriteToCache bool
|
||||
GenMipMaps bool
|
||||
KeepPixelsInMem bool
|
||||
NoSrgba bool
|
||||
}
|
||||
|
||||
type Cubemap struct {
|
||||
@ -67,16 +87,20 @@ func LoadTexturePNG(file string, loadOptions *TextureLoadOptions) (Texture, erro
|
||||
return Texture{}, err
|
||||
}
|
||||
|
||||
img, err := png.Decode(bytes.NewReader(fileBytes))
|
||||
bytesReader := bytes.NewReader(fileBytes)
|
||||
img, err := png.Decode(bytesReader)
|
||||
if err != nil {
|
||||
return Texture{}, err
|
||||
}
|
||||
|
||||
nrgbaImg := prism.ConvertImageToNRGBA(img, 2)
|
||||
tex := Texture{
|
||||
Path: file,
|
||||
Path: file,
|
||||
Pixels: nrgbaImg.Pix,
|
||||
Width: int32(nrgbaImg.Bounds().Dx()),
|
||||
Height: int32(nrgbaImg.Bounds().Dy()),
|
||||
}
|
||||
|
||||
tex.Pixels, tex.Width, tex.Height = pixelsFromNrgbaPng(img)
|
||||
flipImgPixelsVertically(tex.Pixels, int(tex.Width), int(tex.Height), 4)
|
||||
|
||||
//Prepare opengl stuff
|
||||
gl.GenTextures(1, &tex.TexID)
|
||||
@ -89,7 +113,12 @@ func LoadTexturePNG(file string, loadOptions *TextureLoadOptions) (Texture, erro
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
|
||||
|
||||
// load and generate the texture
|
||||
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, tex.Width, tex.Height, 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&tex.Pixels[0]))
|
||||
internalFormat := int32(gl.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)
|
||||
@ -112,8 +141,14 @@ func LoadTextureInMemPngImg(img image.Image, loadOptions *TextureLoadOptions) (T
|
||||
loadOptions = &TextureLoadOptions{}
|
||||
}
|
||||
|
||||
tex := Texture{}
|
||||
tex.Pixels, tex.Width, tex.Height = pixelsFromNrgbaPng(img)
|
||||
nrgbaImg := prism.ConvertImageToNRGBA(img, 2)
|
||||
tex := Texture{
|
||||
Path: "",
|
||||
Pixels: nrgbaImg.Pix,
|
||||
Height: int32(nrgbaImg.Bounds().Dy()),
|
||||
Width: int32(nrgbaImg.Bounds().Dx()),
|
||||
}
|
||||
flipImgPixelsVertically(tex.Pixels, int(tex.Width), int(tex.Height), 4)
|
||||
|
||||
//Prepare opengl stuff
|
||||
gl.GenTextures(1, &tex.TexID)
|
||||
@ -126,7 +161,12 @@ func LoadTextureInMemPngImg(img image.Image, loadOptions *TextureLoadOptions) (T
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
|
||||
|
||||
// load and generate the texture
|
||||
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, tex.Width, tex.Height, 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&tex.Pixels[0]))
|
||||
internalFormat := int32(gl.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)
|
||||
@ -166,11 +206,14 @@ func LoadTextureJpeg(file string, loadOptions *TextureLoadOptions) (Texture, err
|
||||
return Texture{}, err
|
||||
}
|
||||
|
||||
nrgbaImg := prism.ConvertImageToNRGBA(img, 2)
|
||||
tex := Texture{
|
||||
Path: file,
|
||||
Path: file,
|
||||
Pixels: nrgbaImg.Pix,
|
||||
Height: int32(nrgbaImg.Bounds().Dy()),
|
||||
Width: int32(nrgbaImg.Bounds().Dx()),
|
||||
}
|
||||
|
||||
tex.Pixels, tex.Width, tex.Height = pixelsFromNrgbaPng(img)
|
||||
flipImgPixelsVertically(tex.Pixels, int(tex.Width), int(tex.Height), 4)
|
||||
|
||||
//Prepare opengl stuff
|
||||
gl.GenTextures(1, &tex.TexID)
|
||||
@ -183,7 +226,12 @@ func LoadTextureJpeg(file string, loadOptions *TextureLoadOptions) (Texture, err
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
|
||||
|
||||
// load and generate the texture
|
||||
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, tex.Width, tex.Height, 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&tex.Pixels[0]))
|
||||
internalFormat := int32(gl.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)
|
||||
@ -200,65 +248,19 @@ func LoadTextureJpeg(file string, loadOptions *TextureLoadOptions) (Texture, err
|
||||
return tex, nil
|
||||
}
|
||||
|
||||
func pixelsFromNrgbaPng(img image.Image) (pixels []byte, width, height int32) {
|
||||
// LoadCubemapTextures only supports the 'TextureIsSrgba' option
|
||||
func LoadCubemapTextures(rightTex, leftTex, topTex, botTex, frontTex, backTex string, loadOptions *TextureLoadOptions) (Cubemap, error) {
|
||||
|
||||
//NOTE: Load bottom left to top right because this is the texture coordinate system used by OpenGL
|
||||
//NOTE: We only support 8-bit channels (32-bit colors) for now
|
||||
i := 0
|
||||
width, height = int32(img.Bounds().Dx()), int32(img.Bounds().Dy())
|
||||
pixels = make([]byte, img.Bounds().Dx()*img.Bounds().Dy()*4)
|
||||
for y := img.Bounds().Dy() - 1; y >= 0; y-- {
|
||||
for x := 0; x < img.Bounds().Dx(); x++ {
|
||||
|
||||
c := color.NRGBAModel.Convert(img.At(x, y)).(color.NRGBA)
|
||||
|
||||
pixels[i] = c.R
|
||||
pixels[i+1] = c.G
|
||||
pixels[i+2] = c.B
|
||||
pixels[i+3] = c.A
|
||||
|
||||
i += 4
|
||||
}
|
||||
if loadOptions == nil {
|
||||
loadOptions = &TextureLoadOptions{}
|
||||
}
|
||||
|
||||
return pixels, width, height
|
||||
}
|
||||
|
||||
func pixelsFromNrgbaJpg(img image.Image) (pixels []byte, width, height int32) {
|
||||
|
||||
//NOTE: Load bottom left to top right because this is the texture coordinate system used by OpenGL
|
||||
//NOTE: We only support 8-bit channels (32-bit colors) for now
|
||||
i := 0
|
||||
width, height = int32(img.Bounds().Dx()), int32(img.Bounds().Dy())
|
||||
pixels = make([]byte, img.Bounds().Dx()*img.Bounds().Dy()*4)
|
||||
for y := img.Bounds().Dy() - 1; y >= 0; y-- {
|
||||
for x := 0; x < img.Bounds().Dx(); x++ {
|
||||
|
||||
c := color.NRGBAModel.Convert(img.At(x, y)).(color.NRGBA)
|
||||
|
||||
pixels[i] = c.R
|
||||
pixels[i+1] = c.G
|
||||
pixels[i+2] = c.B
|
||||
pixels[i+3] = c.A
|
||||
|
||||
i += 4
|
||||
}
|
||||
}
|
||||
|
||||
return pixels, width, height
|
||||
}
|
||||
|
||||
func LoadCubemapTextures(rightTex, leftTex, topTex, botTex, frontTex, backTex string) (Cubemap, error) {
|
||||
|
||||
var imgDecoder func(r io.Reader) (image.Image, error)
|
||||
var pixelDecoder func(image.Image) ([]byte, int32, int32)
|
||||
ext := strings.ToLower(path.Ext(rightTex))
|
||||
if ext == ".jpg" || ext == ".jpeg" {
|
||||
imgDecoder = jpeg.Decode
|
||||
pixelDecoder = pixelsFromNrgbaJpg
|
||||
} else if ext == ".png" {
|
||||
imgDecoder = png.Decode
|
||||
pixelDecoder = pixelsFromNrgbaPng
|
||||
} else {
|
||||
return Cubemap{}, fmt.Errorf("unknown image extension: %s. Expected one of: .jpg, .jpeg, .png", ext)
|
||||
}
|
||||
@ -292,9 +294,16 @@ func LoadCubemapTextures(rightTex, leftTex, topTex, botTex, frontTex, backTex st
|
||||
return Cubemap{}, err
|
||||
}
|
||||
|
||||
pixels, width, height := pixelDecoder(img)
|
||||
nrgbaImg := prism.ConvertImageToNRGBA(img, 2)
|
||||
height := int32(nrgbaImg.Bounds().Dy())
|
||||
width := int32(nrgbaImg.Bounds().Dx())
|
||||
|
||||
gl.TexImage2D(uint32(gl.TEXTURE_CUBE_MAP_POSITIVE_X)+i, 0, gl.RGBA8, int32(width), int32(height), 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&pixels[0]))
|
||||
internalFormat := int32(gl.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)
|
||||
@ -306,3 +315,21 @@ func LoadCubemapTextures(rightTex, leftTex, topTex, botTex, frontTex, backTex st
|
||||
|
||||
return cmap, nil
|
||||
}
|
||||
|
||||
func flipImgPixelsVertically(bytes []byte, width, height, bytesPerPixel int) {
|
||||
|
||||
// Flip the image vertically such that (e.g. in an image of 10 rows) rows 0<->9, 1<->8, 2<->7 etc are swapped.
|
||||
// We do this because images are usually stored top-left to bottom-right, while opengl stores textures bottom-left to top-right, so if we don't swap
|
||||
// rows textures will appear inverted
|
||||
widthInBytes := width * bytesPerPixel
|
||||
rowData := make([]byte, width*bytesPerPixel)
|
||||
for rowNum := 0; rowNum < height/2; rowNum++ {
|
||||
|
||||
upperRowStartIndex := rowNum * widthInBytes
|
||||
lowerRowStartIndex := (height - rowNum - 1) * widthInBytes
|
||||
copy(rowData, bytes[upperRowStartIndex:upperRowStartIndex+widthInBytes])
|
||||
copy(bytes[upperRowStartIndex:upperRowStartIndex+widthInBytes], bytes[lowerRowStartIndex:lowerRowStartIndex+widthInBytes])
|
||||
copy(bytes[lowerRowStartIndex:lowerRowStartIndex+widthInBytes], rowData)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,23 +9,48 @@ import (
|
||||
|
||||
type BufUsage int
|
||||
|
||||
// Full docs for buffer usage can be found here: https://registry.khronos.org/OpenGL-Refpages/gl4/html/glBufferData.xhtml
|
||||
const (
|
||||
BufUsage_Unknown BufUsage = iota
|
||||
|
||||
//Buffer is set only once and used many times
|
||||
BufUsage_Static BufUsage = iota
|
||||
BufUsage_Static_Draw
|
||||
//Buffer is changed a lot and used many times
|
||||
BufUsage_Dynamic
|
||||
BufUsage_Dynamic_Draw
|
||||
//Buffer is set only once and used by the GPU at most a few times
|
||||
BufUsage_Stream
|
||||
BufUsage_Stream_Draw
|
||||
|
||||
BufUsage_Static_Read
|
||||
BufUsage_Dynamic_Read
|
||||
BufUsage_Stream_Read
|
||||
|
||||
BufUsage_Static_Copy
|
||||
BufUsage_Dynamic_Copy
|
||||
BufUsage_Stream_Copy
|
||||
)
|
||||
|
||||
func (b BufUsage) ToGL() uint32 {
|
||||
switch b {
|
||||
case BufUsage_Static:
|
||||
case BufUsage_Static_Draw:
|
||||
return gl.STATIC_DRAW
|
||||
case BufUsage_Dynamic:
|
||||
case BufUsage_Dynamic_Draw:
|
||||
return gl.DYNAMIC_DRAW
|
||||
case BufUsage_Stream:
|
||||
case BufUsage_Stream_Draw:
|
||||
return gl.STREAM_DRAW
|
||||
|
||||
case BufUsage_Static_Read:
|
||||
return gl.STATIC_READ
|
||||
case BufUsage_Dynamic_Read:
|
||||
return gl.DYNAMIC_READ
|
||||
case BufUsage_Stream_Read:
|
||||
return gl.STREAM_READ
|
||||
|
||||
case BufUsage_Static_Copy:
|
||||
return gl.STATIC_COPY
|
||||
case BufUsage_Dynamic_Copy:
|
||||
return gl.DYNAMIC_COPY
|
||||
case BufUsage_Stream_Copy:
|
||||
return gl.STREAM_COPY
|
||||
}
|
||||
|
||||
assert.T(false, fmt.Sprintf("Unexpected BufUsage value '%v'", b))
|
||||
|
||||
@ -1,134 +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 is the number of elements in the index buffer
|
||||
// Updated on SetIndexBufData
|
||||
IndexBufCount int32
|
||||
// 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)
|
||||
|
||||
sizeInBytes := len(values) * 4
|
||||
if sizeInBytes == 0 {
|
||||
gl.BufferData(gl.ARRAY_BUFFER, 0, gl.Ptr(nil), BufUsage_Static.ToGL())
|
||||
} else {
|
||||
gl.BufferData(gl.ARRAY_BUFFER, sizeInBytes, gl.Ptr(&values[0]), BufUsage_Static.ToGL())
|
||||
}
|
||||
|
||||
gl.BindVertexArray(0)
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
|
||||
}
|
||||
|
||||
func (b *Buffer) SetDataWithUsage(values []float32, usage BufUsage) {
|
||||
|
||||
gl.BindVertexArray(b.VAOID)
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, b.BufID)
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
sizeInBytes := len(values) * 4
|
||||
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())
|
||||
}
|
||||
|
||||
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.VertexAttribPointerWithOffset(uint32(i), layout[i].ElementType.CompCount(), layout[i].ElementType.GLType(), false, b.Stride, uintptr(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/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:
|
||||
assert.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:
|
||||
assert.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:
|
||||
assert.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:
|
||||
assert.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) GlStd140SizeBytes() uint8 {
|
||||
|
||||
switch dt {
|
||||
|
||||
case DataTypeUint32:
|
||||
fallthrough
|
||||
case DataTypeFloat32:
|
||||
fallthrough
|
||||
case DataTypeInt32:
|
||||
return 4
|
||||
|
||||
case DataTypeVec2:
|
||||
return 4 * 2
|
||||
|
||||
case DataTypeVec3:
|
||||
return 4 * 3
|
||||
|
||||
case DataTypeVec4:
|
||||
return 4 * 4
|
||||
|
||||
// Matrices follow: (vec4Alignment) * numColumns
|
||||
case DataTypeMat2:
|
||||
return 2 * 2 * 4
|
||||
case DataTypeMat3:
|
||||
return 3 * 3 * 4
|
||||
case DataTypeMat4:
|
||||
return 4 * 4 * 4
|
||||
|
||||
case DataTypeStruct:
|
||||
logging.ErrLog.Fatalf("ElementType.GlStd140SizeBytes 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_Draw.ToGL())
|
||||
} else {
|
||||
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, sizeInBytes, gl.Ptr(&values[0]), BufUsage_Static_Draw.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
|
||||
}
|
||||
747
buffers/uniform_buffer.go
Executable file
@ -0,0 +1,747 @@
|
||||
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
|
||||
//
|
||||
// Official spec and full details in subsection 'Standard Uniform Block Layout' at http://www.opengl.org/registry/specs/ARB/uniform_buffer_object.txt
|
||||
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
|
||||
padTo16Boundary(&subfieldsAlignedOffset)
|
||||
alignedOffset += subfieldsAlignedOffset * f.Count
|
||||
|
||||
} else {
|
||||
|
||||
// Elements advance the alignedOffset by their actual byte size.
|
||||
// Aligned offset is padded if the place its at is not aligned to the boundary required by the next element.
|
||||
//
|
||||
// The exception is structs, because fields after a struct field are always aligned at a 16 byte boundary.
|
||||
//
|
||||
// For example, a vec3 starting at offset 80, taking 12 bytes, would put the aligned offset at 92.
|
||||
// If the next element is a float32 (alignment boundary = 4) then no padding is required and
|
||||
// the float will start at 92 and end at 96.
|
||||
// However, if the element after the vec3 is a vec3 (alignment boundary = 16), then it would require
|
||||
// a padding of 4 bytes so that it can start at 96, which is aligned to 16. In this case the second vec3
|
||||
// would start at 96 and end at 96+12=108.
|
||||
alignedOffset = newField.AlignedOffset + uint16(f.Type.GlStd140SizeBytes())*f.Count*multiplier - startAlignedOffset
|
||||
}
|
||||
}
|
||||
|
||||
return uint32(alignedOffset)
|
||||
}
|
||||
|
||||
func padTo16Boundary[T uint16 | int | int32](val *T) {
|
||||
alignmentError := *val % 16
|
||||
if alignmentError != 0 {
|
||||
*val += 16 - alignmentError
|
||||
}
|
||||
}
|
||||
|
||||
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, 0)
|
||||
}
|
||||
|
||||
func setStruct(fields []UniformBufferField, buf []byte, inputStruct any, maxFieldsToConsume int, onlyBufWrite bool, writeOffset int) (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)
|
||||
}
|
||||
|
||||
// Needed because fieldIndex can move faster than struct fields in case of struct fields
|
||||
structFieldIndex := 0
|
||||
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()
|
||||
kind = elementType.Kind()
|
||||
} 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) + writeOffset
|
||||
|
||||
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 {
|
||||
|
||||
if isArray {
|
||||
|
||||
offset := 0
|
||||
arrSize := valField.Len()
|
||||
fieldsToUse := fields[fieldIndex+1:]
|
||||
for i := 0; i < arrSize; i++ {
|
||||
|
||||
setStructBytesWritten, setStructFieldsConsumed := setStruct(fieldsToUse, buf, valField.Index(i).Interface(), elementType.NumField(), true, offset*i)
|
||||
|
||||
if offset == 0 {
|
||||
offset = setStructBytesWritten
|
||||
padTo16Boundary(&offset)
|
||||
|
||||
bytesWritten += offset * arrSize
|
||||
|
||||
// Tracking consumed fields is needed because if we have a struct inside another struct
|
||||
// elementType.NumField() will only give us the fields consumed by the first struct,
|
||||
// but we need to count all fields of all nested structs inside this one
|
||||
fieldIndex += setStructFieldsConsumed
|
||||
fieldsConsumed += setStructFieldsConsumed
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
setStructBytesWritten, setStructFieldsConsumed := setStruct(fields[fieldIndex+1:], buf, valField.Interface(), valField.NumField(), true, writeOffset)
|
||||
|
||||
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) - writeOffset, 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, usage BufUsage) 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), usage.ToGL())
|
||||
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
|
||||
}
|
||||
@ -41,7 +41,7 @@ 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)
|
||||
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
|
||||
}
|
||||
@ -59,9 +59,9 @@ func (c *Camera) UpdateRotation(pitch, yaw float32) {
|
||||
c.Update()
|
||||
}
|
||||
|
||||
func NewPerspective(pos, forward, worldUp *gglm.Vec3, nearClip, farClip, fovRadians, aspectRatio float32) *Camera {
|
||||
func NewPerspective(pos, forward, worldUp *gglm.Vec3, nearClip, farClip, fovRadians, aspectRatio float32) Camera {
|
||||
|
||||
cam := &Camera{
|
||||
cam := Camera{
|
||||
Type: Type_Perspective,
|
||||
Pos: *pos,
|
||||
Forward: *forward,
|
||||
@ -78,9 +78,9 @@ func NewPerspective(pos, forward, worldUp *gglm.Vec3, nearClip, farClip, fovRadi
|
||||
return cam
|
||||
}
|
||||
|
||||
func NewOrthographic(pos, forward, worldUp *gglm.Vec3, nearClip, farClip, left, right, top, bottom float32) *Camera {
|
||||
func NewOrthographic(pos, forward, worldUp *gglm.Vec3, nearClip, farClip, left, right, top, bottom float32) Camera {
|
||||
|
||||
cam := &Camera{
|
||||
cam := Camera{
|
||||
Type: Type_Orthographic,
|
||||
Pos: *pos,
|
||||
Forward: *forward,
|
||||
|
||||
201
engine/engine.go
@ -1,12 +1,14 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"runtime"
|
||||
|
||||
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"
|
||||
@ -15,21 +17,30 @@ import (
|
||||
|
||||
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()
|
||||
|
||||
// @TODO: Would be nice to have imgui package process its own events via a callback instead of it being part of engine code
|
||||
imguiCaptureMouse := imIo.WantCaptureMouse()
|
||||
imguiCaptureKeyboard := imIo.WantCaptureKeyboard()
|
||||
|
||||
input.EventLoopStart(imguiCaptureMouse, imguiCaptureKeyboard)
|
||||
|
||||
for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
|
||||
|
||||
//Fire callbacks
|
||||
@ -43,42 +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)
|
||||
|
||||
// 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)
|
||||
|
||||
// Send modifier key updates to imgui
|
||||
if e.Keysym.Sym == sdl.K_LCTRL || e.Keysym.Sym == sdl.K_RCTRL {
|
||||
imIo.SetKeyCtrl(e.Type == sdl.KEYDOWN)
|
||||
}
|
||||
|
||||
if e.Keysym.Sym == sdl.K_LSHIFT || e.Keysym.Sym == sdl.K_RSHIFT {
|
||||
imIo.SetKeyShift(e.Type == sdl.KEYDOWN)
|
||||
}
|
||||
|
||||
if e.Keysym.Sym == sdl.K_LALT || e.Keysym.Sym == sdl.K_RALT {
|
||||
imIo.SetKeyAlt(e.Type == sdl.KEYDOWN)
|
||||
}
|
||||
|
||||
if e.Keysym.Sym == sdl.K_LGUI || e.Keysym.Sym == sdl.K_RGUI {
|
||||
imIo.SetKeySuper(e.Type == sdl.KEYDOWN)
|
||||
}
|
||||
|
||||
case *sdl.TextInputEvent:
|
||||
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()
|
||||
}
|
||||
@ -88,13 +99,17 @@ func (w *Window) handleInputs() {
|
||||
}
|
||||
}
|
||||
|
||||
// If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame.
|
||||
x, y, _ := sdl.GetMouseState()
|
||||
imIo.SetMousePos(imgui.Vec2{X: float32(x), Y: float32(y)})
|
||||
if sdl.GetRelativeMouseMode() {
|
||||
imIo.SetMousePos(imgui.Vec2{X: ImguiRelativeMouseModePosX, Y: ImguiRelativeMouseModePosY})
|
||||
} else {
|
||||
x, y, _ := sdl.GetMouseState()
|
||||
imIo.SetMousePos(imgui.Vec2{X: float32(x), Y: float32(y)})
|
||||
}
|
||||
|
||||
imIo.SetMouseButtonDown(0, input.MouseDown(sdl.BUTTON_LEFT))
|
||||
imIo.SetMouseButtonDown(1, input.MouseDown(sdl.BUTTON_RIGHT))
|
||||
imIo.SetMouseButtonDown(2, input.MouseDown(sdl.BUTTON_MIDDLE))
|
||||
// If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame.
|
||||
imIo.SetMouseButtonDown(int(imgui.MouseButtonLeft), isSdlButtonLeftDown)
|
||||
imIo.SetMouseButtonDown(int(imgui.MouseButtonRight), isSdlButtonRightDown)
|
||||
imIo.SetMouseButtonDown(int(imgui.MouseButtonMiddle), isSdlButtonMiddleDown)
|
||||
}
|
||||
|
||||
func (w *Window) handleWindowResize() {
|
||||
@ -133,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) {
|
||||
|
||||
assert.T(isInited, "engine.Init was not called!")
|
||||
if x == -1 && y == -1 {
|
||||
x = sdl.WINDOWPOS_CENTERED
|
||||
y = sdl.WINDOWPOS_CENTERED
|
||||
}
|
||||
assert.T(isInited, "engine.Init() was not called!")
|
||||
|
||||
sdlWin, err := sdl.CreateWindow(title, x, y, width, height, uint32(flags))
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
@ -193,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) {
|
||||
assert.T(isInited, "engine.Init was not called!")
|
||||
|
||||
if enabled {
|
||||
sdl.GLSetSwapInterval(1)
|
||||
@ -213,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,7 +21,7 @@ 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
|
||||
|
||||
@ -40,7 +41,6 @@ func Run(g Game, w *Window, ui nmageimgui.ImguiInfo) {
|
||||
|
||||
for isRunning {
|
||||
|
||||
//PERF: Cache these
|
||||
width, height = w.SDLWin.GetSize()
|
||||
fbWidth, fbHeight = w.SDLWin.GLGetDrawableSize()
|
||||
|
||||
@ -56,7 +56,7 @@ func Run(g Game, w *Window, ui nmageimgui.ImguiInfo) {
|
||||
w.SDLWin.GLSwap()
|
||||
|
||||
g.FrameEnd()
|
||||
w.Rend.FrameEnd()
|
||||
rend.FrameEnd()
|
||||
timing.FrameEnded()
|
||||
}
|
||||
|
||||
|
||||
14
go.mod
@ -1,6 +1,6 @@
|
||||
module github.com/bloeys/nmage
|
||||
|
||||
go 1.18
|
||||
go 1.23
|
||||
|
||||
require github.com/veandco/go-sdl2 v0.4.35
|
||||
|
||||
@ -8,7 +8,15 @@ require github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6
|
||||
|
||||
require (
|
||||
github.com/bloeys/assimp-go v0.4.4
|
||||
github.com/bloeys/gglm v0.43.0
|
||||
github.com/bloeys/gglm v0.50.0
|
||||
)
|
||||
|
||||
require github.com/AllenDang/cimgui-go v0.0.0-20230720025235-f2ff398a66b2
|
||||
require (
|
||||
github.com/AllenDang/cimgui-go v0.0.0-20240912193335-545751598105
|
||||
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
|
||||
)
|
||||
|
||||
38
go.sum
@ -1,10 +1,44 @@
|
||||
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/AllenDang/cimgui-go v0.0.0-20240912193335-545751598105 h1:bhXqv2EG5YCLdgkLSFCpqeVz4cCoNbi4RDFrHrwxQ1o=
|
||||
github.com/AllenDang/cimgui-go v0.0.0-20240912193335-545751598105/go.mod h1:CYfBRenCaNtSvKVzChYh6gswUSo6c5IUcYeV6eCCRw0=
|
||||
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.43.0 h1:ZpOghR3PHfpkigTDh+FqxLsF0gN8CD6s/bWoei6LyxI=
|
||||
github.com/bloeys/gglm v0.43.0/go.mod h1:qwJQ0WzV191wAMwlGicbfbChbKoSedMk7gFFX6GnyOk=
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@ -1,37 +1,142 @@
|
||||
package materials
|
||||
|
||||
import (
|
||||
_ "unsafe"
|
||||
|
||||
"github.com/bloeys/gglm/gglm"
|
||||
"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,7 +146,8 @@ func (m *Material) GetAttribLoc(attribName string) int32 {
|
||||
return loc
|
||||
}
|
||||
|
||||
loc = gl.GetAttribLocation(m.ShaderProg.ID, gl.Str(attribName+"\x00"))
|
||||
name := gl.Str(attribName + "\x00")
|
||||
loc = gl.GetAttribLocation(m.ShaderProg.Id, name)
|
||||
assert.T(loc != -1, "Attribute '"+attribName+"' doesn't exist on material "+m.Name)
|
||||
m.AttribLocs[attribName] = loc
|
||||
return loc
|
||||
@ -54,7 +160,8 @@ func (m *Material) GetUnifLoc(uniformName string) int32 {
|
||||
return loc
|
||||
}
|
||||
|
||||
loc = gl.GetUniformLocation(m.ShaderProg.ID, gl.Str(uniformName+"\x00"))
|
||||
name := gl.Str(uniformName + "\x00")
|
||||
loc = gl.GetUniformLocation(m.ShaderProg.Id, name)
|
||||
assert.T(loc != -1, "Uniform '"+uniformName+"' doesn't exist on material "+m.Name)
|
||||
m.UnifLocs[uniformName] = loc
|
||||
return loc
|
||||
@ -69,57 +176,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 {
|
||||
func getNewMatId() uint32 {
|
||||
lastMatId++
|
||||
return lastMatId
|
||||
}
|
||||
|
||||
func NewMaterial(matName, shaderPath string) Material {
|
||||
|
||||
shdrProg, err := shaders.LoadAndCompileCombinedShader(shaderPath)
|
||||
if err != nil {
|
||||
logging.ErrLog.Fatalln("Failed to create new material. Err: ", err)
|
||||
logging.ErrLog.Fatalf("Failed to create new material '%s'. Err: %s\n", matName, err.Error())
|
||||
}
|
||||
|
||||
return &Material{Name: matName, ShaderProg: shdrProg, UnifLocs: make(map[string]int32), AttribLocs: make(map[string]int32)}
|
||||
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 {
|
||||
func NewMaterialSrc(matName string, shaderSrc []byte) Material {
|
||||
|
||||
shdrProg, err := shaders.LoadAndCompileCombinedShaderSrc(shaderSrc)
|
||||
if err != nil {
|
||||
logging.ErrLog.Fatalln("Failed to create new material. Err: ", err)
|
||||
logging.ErrLog.Fatalf("Failed to create new material '%s'. Err: %s\n", matName, err.Error())
|
||||
}
|
||||
|
||||
return &Material{Name: matName, ShaderProg: shdrProg, UnifLocs: make(map[string]int32), AttribLocs: make(map[string]int32)}
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
131
meshes/mesh.go
@ -2,7 +2,6 @@ package meshes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/bloeys/assimp-go/asig"
|
||||
"github.com/bloeys/gglm/gglm"
|
||||
@ -17,64 +16,123 @@ type SubMesh struct {
|
||||
}
|
||||
|
||||
type Mesh struct {
|
||||
Name string
|
||||
Buf buffers.Buffer
|
||||
Name string
|
||||
/*
|
||||
Vao has the following shader attribute layout:
|
||||
- Loc0: Pos
|
||||
- Loc1: Normal
|
||||
- Loc2: UV0
|
||||
- Loc3: Tangent
|
||||
- (Optional) Color
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func NewMesh(name, modelPath string, postProcessFlags asig.PostProcess) (*Mesh, error) {
|
||||
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
|
||||
)
|
||||
|
||||
scene, release, err := asig.ImportFile(modelPath, asig.PostProcessTriangulate|postProcessFlags)
|
||||
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{
|
||||
mesh := Mesh{
|
||||
Name: name,
|
||||
Buf: buffers.NewBuffer(),
|
||||
Vao: buffers.NewVertexArray(),
|
||||
SubMeshes: make([]SubMesh, 0, 1),
|
||||
}
|
||||
|
||||
// Initial sizes assuming one submesh that has vertex pos+normals+texCoords, and 3 indices per face
|
||||
var vertexBufData []float32 = make([]float32, 0, len(scene.Meshes[0].Vertices)*3*3*2)
|
||||
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]
|
||||
|
||||
if len(sceneMesh.TexCoords[0]) == 0 {
|
||||
sceneMesh.TexCoords[0] = make([]gglm.Vec3, len(sceneMesh.Vertices))
|
||||
println("Zeroing tex coords for submesh", i)
|
||||
// We always want tangents and UV0
|
||||
if len(sceneMesh.Tangents) == 0 {
|
||||
sceneMesh.Tangents = make([]gglm.Vec3, len(sceneMesh.Vertices))
|
||||
}
|
||||
|
||||
layoutToUse := []buffers.Element{{ElementType: buffers.DataTypeVec3}, {ElementType: buffers.DataTypeVec3}, {ElementType: buffers.DataTypeVec2}}
|
||||
if len(sceneMesh.ColorSets) > 0 && len(sceneMesh.ColorSets[0]) > 0 {
|
||||
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 {
|
||||
mesh.Buf.SetLayout(layoutToUse...)
|
||||
vbo.SetLayout(layoutToUse...)
|
||||
} else {
|
||||
|
||||
// @NOTE: Require that all submeshes have the same vertex buffer layout
|
||||
firstSubmeshLayout := mesh.Buf.GetLayout()
|
||||
assert.T(len(firstSubmeshLayout) == len(layoutToUse), fmt.Sprintf("Vertex layout of submesh %d does not equal vertex layout of the first submesh. Original layout: %v; This layout: %v", i, firstSubmeshLayout, layoutToUse))
|
||||
// @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++ {
|
||||
if firstSubmeshLayout[i].ElementType != layoutToUse[i].ElementType {
|
||||
panic(fmt.Sprintf("Vertex layout of submesh %d does not equal vertex layout of the first submesh. Original layout: %v; This layout: %v", i, firstSubmeshLayout, layoutToUse))
|
||||
}
|
||||
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}, {V2s: v3sToV2s(sceneMesh.TexCoords[0])}}
|
||||
if len(sceneMesh.ColorSets) > 0 && len(sceneMesh.ColorSets[0]) > 0 {
|
||||
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]})
|
||||
}
|
||||
|
||||
@ -82,7 +140,7 @@ func NewMesh(name, modelPath string, postProcessFlags asig.PostProcess) (*Mesh,
|
||||
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) / mesh.Buf.Stride,
|
||||
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
|
||||
@ -93,9 +151,16 @@ func NewMesh(name, modelPath string, postProcessFlags asig.PostProcess) (*Mesh,
|
||||
indexBufData = append(indexBufData, indices...)
|
||||
}
|
||||
|
||||
// fmt.Printf("!!! Vertex count: %d; Submeshes: %+v\n", len(vertexBufData)*4/int(mesh.Buf.Stride), mesh.SubMeshes)
|
||||
mesh.Buf.SetData(vertexBufData)
|
||||
mesh.Buf.SetIndexBufData(indexBufData)
|
||||
vbo.SetData(vertexBufData, buffers.BufUsage_Static_Draw)
|
||||
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()
|
||||
|
||||
return mesh, nil
|
||||
}
|
||||
|
||||
@ -119,9 +184,9 @@ type arrToInterleave struct {
|
||||
|
||||
func (a *arrToInterleave) get(i int) []float32 {
|
||||
|
||||
assert.T(len(a.V2s) == 0 || len(a.V3s) == 0, "One array should be set in arrToInterleave, but both arrays are set")
|
||||
assert.T(len(a.V2s) == 0 || len(a.V4s) == 0, "One array should be set in arrToInterleave, but both arrays are set")
|
||||
assert.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[:]
|
||||
@ -173,7 +238,7 @@ func interleave(arrs ...arrToInterleave) []float32 {
|
||||
|
||||
func flattenFaces(faces []asig.Face) []uint32 {
|
||||
|
||||
assert.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
|
||||
}
|
||||
@ -18,7 +18,7 @@ type freeListitem struct {
|
||||
//
|
||||
// It is NOT safe to concurrently create or free items. However, it is SAFE to concurrently get items
|
||||
type Registry[T any] struct {
|
||||
ItemCount uint64
|
||||
ItemCount uint
|
||||
Handles []Handle
|
||||
Items []T
|
||||
|
||||
@ -32,7 +32,7 @@ type Registry[T any] struct {
|
||||
|
||||
func (r *Registry[T]) New() (*T, Handle) {
|
||||
|
||||
assert.T(r.ItemCount < uint64(len(r.Handles)), "Can not add more entities to registry because it is full")
|
||||
assert.T(r.ItemCount < uint(len(r.Handles)), "Can not add more entities to registry because it is full")
|
||||
|
||||
var index uint64 = math.MaxUint64
|
||||
|
||||
@ -77,6 +77,10 @@ func (r *Registry[T]) New() (*T, Handle) {
|
||||
|
||||
func (r *Registry[T]) Get(id Handle) *T {
|
||||
|
||||
if id.IsZero() {
|
||||
return nil
|
||||
}
|
||||
|
||||
index := id.Index()
|
||||
assert.T(index < uint64(len(r.Handles)), "Failed to get entity because of invalid entity handle. Handle index is %d while registry only has %d slots. Handle: %+v", index, r.ItemCount, id)
|
||||
|
||||
@ -113,6 +117,14 @@ func (r *Registry[T]) Free(id Handle) {
|
||||
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]{
|
||||
|
||||
@ -16,6 +16,12 @@ const (
|
||||
// Byte 1: Generation; Byte 2: Flags; Bytes 3-8: Index
|
||||
type Handle uint64
|
||||
|
||||
// IsZero reports whether the handle is in its default 'zero' state.
|
||||
// A zero handle is an invalid handle that does NOT point to any entity
|
||||
func (h Handle) IsZero() bool {
|
||||
return h == 0
|
||||
}
|
||||
|
||||
func (h Handle) HasFlag(ef HandleFlag) bool {
|
||||
return h.Flags()&ef > 0
|
||||
}
|
||||
|
||||
@ -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,23 +12,63 @@ 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)
|
||||
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)
|
||||
@ -35,8 +76,9 @@ func (r3d *Rend3DGL) Draw(mesh *meshes.Mesh, trMat *gglm.TrMat, mat *materials.M
|
||||
}
|
||||
|
||||
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
BIN
res/models/plane.fbx
Executable file
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;
|
||||
}
|
||||
@ -2,35 +2,32 @@
|
||||
#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;
|
||||
layout(location=2) in vec3 vertTangentIn;
|
||||
layout(location=3) in vec2 vertUV0In;
|
||||
layout(location=4) 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;
|
||||
uniform mat4 projViewMat;
|
||||
|
||||
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);
|
||||
vec4 modelVert = modelMat * vec4(vertPosIn, 1);
|
||||
fragPos = modelVert.xyz;
|
||||
|
||||
gl_Position = projViewMat * modelVert;
|
||||
}
|
||||
|
||||
//shader:fragment
|
||||
#version 410
|
||||
|
||||
in vec3 vertColor;
|
||||
in vec3 vertNormal;
|
||||
in vec2 vertUV0;
|
||||
in vec3 fragPos;
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
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,55 +1,490 @@
|
||||
//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 vec2 vertUV0In;
|
||||
layout(location=3) in vec3 vertColorIn;
|
||||
layout(location=2) in vec3 vertTangentIn;
|
||||
layout(location=3) in vec2 vertUV0In;
|
||||
layout(location=4) in vec3 vertColorIn;
|
||||
|
||||
out vec3 vertNormal;
|
||||
//
|
||||
// UBOs
|
||||
//
|
||||
struct DirLight {
|
||||
vec3 dir;
|
||||
vec3 diffuseColor;
|
||||
vec3 specularColor;
|
||||
};
|
||||
uniform sampler2D dirLightShadowMap;
|
||||
|
||||
struct PointLight {
|
||||
vec3 pos;
|
||||
vec3 diffuseColor;
|
||||
vec3 specularColor;
|
||||
float radius;
|
||||
float falloff;
|
||||
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;
|
||||
PointLight pointLights[NUM_POINT_LIGHTS];
|
||||
SpotLight spotLights[NUM_SPOT_LIGHTS];
|
||||
vec3 ambientColor;
|
||||
};
|
||||
|
||||
//
|
||||
// Uniforms
|
||||
//
|
||||
uniform mat4 modelMat;
|
||||
uniform mat4 dirLightProjViewMat;
|
||||
uniform mat4 spotLightProjViewMats[NUM_SPOT_LIGHTS];
|
||||
|
||||
//
|
||||
// Outputs
|
||||
//
|
||||
out vec2 vertUV0;
|
||||
out vec3 vertColor;
|
||||
out vec3 fragPos;
|
||||
|
||||
//MVP = Model View Projection
|
||||
uniform mat4 modelMat;
|
||||
uniform mat4 viewMat;
|
||||
uniform mat4 projMat;
|
||||
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];
|
||||
|
||||
void main()
|
||||
{
|
||||
vertNormal = mat3(transpose(inverse(modelMat))) * vertNormalIn;
|
||||
vertUV0 = vertUV0In;
|
||||
vertColor = vertColorIn;
|
||||
fragPos = vec3(modelMat * vec4(vertPosIn, 1.0));
|
||||
vec4 modelVert = modelMat * vec4(vertPosIn, 1);
|
||||
|
||||
gl_Position = projMat * viewMat * modelMat * vec4(vertPosIn, 1.0);
|
||||
// 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
|
||||
|
||||
uniform float ambientStrength = 0;
|
||||
uniform vec3 ambientLightColor = vec3(1, 1, 1);
|
||||
/*
|
||||
Note that while all lighting calculations are done in tangent space,
|
||||
shadow mapping is done in world space.
|
||||
|
||||
uniform vec3 lightPos1;
|
||||
uniform vec3 lightColor1;
|
||||
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.
|
||||
*/
|
||||
|
||||
uniform sampler2D diffTex;
|
||||
#define NUM_SPOT_LIGHTS 4
|
||||
#define NUM_POINT_LIGHTS 8
|
||||
|
||||
in vec3 vertColor;
|
||||
in vec3 vertNormal;
|
||||
in vec2 vertUV0;
|
||||
//
|
||||
// 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 radius;
|
||||
float falloff;
|
||||
float maxBias;
|
||||
float nearPlane;
|
||||
float farPlane;
|
||||
};
|
||||
uniform samplerCubeArray pointLightCubeShadowMaps;
|
||||
|
||||
struct SpotLight {
|
||||
vec3 pos;
|
||||
vec3 dir;
|
||||
vec3 diffuseColor;
|
||||
vec3 specularColor;
|
||||
float innerCutoff;
|
||||
float outerCutoff;
|
||||
};
|
||||
uniform sampler2DArray spotLightShadowMaps;
|
||||
|
||||
layout (std140) uniform GlobalMatrices {
|
||||
vec3 camPos;
|
||||
mat4 projViewMat;
|
||||
};
|
||||
|
||||
layout (std140) uniform Lights {
|
||||
DirLight dirLight;
|
||||
PointLight pointLights[NUM_POINT_LIGHTS];
|
||||
SpotLight spotLights[NUM_SPOT_LIGHTS];
|
||||
vec3 ambientColor;
|
||||
};
|
||||
|
||||
//
|
||||
// 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()
|
||||
{
|
||||
vec3 lightDir = normalize(lightPos1 - fragPos);
|
||||
float diffStrength = max(0.0, dot(normalize(vertNormal), lightDir));
|
||||
// Shared values
|
||||
tangentViewDir = normalize(tangentCamPos - tangentFragPos);
|
||||
diffuseTexColor = texture(material.diffuse, vertUV0);
|
||||
specularTexColor = texture(material.specular, vertUV0);
|
||||
emissionTexColor = texture(material.emission, vertUV0);
|
||||
|
||||
vec3 finalAmbientColor = ambientLightColor * ambientStrength;
|
||||
vec4 texColor = texture(diffTex, vertUV0);
|
||||
fragColor = vec4(texColor.rgb * vertColor * (finalAmbientColor + diffStrength*lightColor1) , texColor.a);
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,18 +3,18 @@
|
||||
|
||||
layout(location=0) in vec3 vertPosIn;
|
||||
layout(location=1) in vec3 vertNormalIn;
|
||||
layout(location=2) in vec2 vertUV0In;
|
||||
layout(location=3) in vec3 vertColorIn;
|
||||
layout(location=2) in vec3 vertTangentIn;
|
||||
layout(location=3) in vec2 vertUV0In;
|
||||
layout(location=4) in vec3 vertColorIn;
|
||||
|
||||
out vec3 vertUV0;
|
||||
|
||||
uniform mat4 viewMat;
|
||||
uniform mat4 projMat;
|
||||
uniform mat4 projViewMat;
|
||||
|
||||
void main()
|
||||
{
|
||||
vertUV0 = vec3(vertPosIn.x, vertPosIn.y, -vertPosIn.z);
|
||||
vec4 pos = projMat * viewMat * vec4(vertPosIn, 1.0);
|
||||
vec4 pos = projViewMat * vec4(vertPosIn, 1.0);
|
||||
gl_Position = pos.xyww;
|
||||
}
|
||||
|
||||
|
||||
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 |
|
Before Width: | Height: | Size: 1008 KiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 274 KiB After Width: | Height: | Size: 617 KiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 338 KiB After Width: | Height: | Size: 770 KiB |
@ -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
|
||||
)
|
||||
|
||||
@ -3,6 +3,7 @@ package shaders
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@ -11,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) {
|
||||
@ -26,7 +28,7 @@ func NewShaderProgram() (ShaderProgram, error) {
|
||||
return ShaderProgram{}, errors.New("failed to create shader program")
|
||||
}
|
||||
|
||||
return ShaderProgram{ID: id}, nil
|
||||
return ShaderProgram{Id: id}, nil
|
||||
}
|
||||
|
||||
func LoadAndCompileCombinedShader(shaderPath string) (ShaderProgram, error) {
|
||||
@ -43,8 +45,8 @@ func LoadAndCompileCombinedShader(shaderPath string) (ShaderProgram, error) {
|
||||
func LoadAndCompileCombinedShaderSrc(shaderSrc []byte) (ShaderProgram, error) {
|
||||
|
||||
shaderSources := bytes.Split(shaderSrc, []byte("//shader:"))
|
||||
if len(shaderSources) == 1 {
|
||||
return ShaderProgram{}, errors.New("failed to read combined shader. Did not find '//shader:vertex' or '//shader:fragment'")
|
||||
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()
|
||||
@ -65,12 +67,15 @@ func LoadAndCompileCombinedShaderSrc(shaderSrc []byte) (ShaderProgram, error) {
|
||||
var shdrType ShaderType
|
||||
if bytes.HasPrefix(src, []byte("vertex")) {
|
||||
src = src[6:]
|
||||
shdrType = VertexShaderType
|
||||
shdrType = ShaderType_Vertex
|
||||
} else if bytes.HasPrefix(src, []byte("fragment")) {
|
||||
src = src[8:]
|
||||
shdrType = FragmentShaderType
|
||||
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'")
|
||||
return ShaderProgram{}, errors.New("unknown shader type. Must be '//shader:vertex' or '//shader:fragment' or '//shader:geometry'")
|
||||
}
|
||||
|
||||
shdr, err := CompileShaderOfType(src, shdrType)
|
||||
@ -83,7 +88,15 @@ func LoadAndCompileCombinedShaderSrc(shaderSrc []byte) (ShaderProgram, error) {
|
||||
}
|
||||
|
||||
if loadedShdrCount == 0 {
|
||||
return ShaderProgram{}, errors.New("no valid shaders found. Please put '//shader:vertex' or '//shader:fragment' before your shaders")
|
||||
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()
|
||||
@ -92,41 +105,40 @@ func LoadAndCompileCombinedShaderSrc(shaderSrc []byte) (ShaderProgram, error) {
|
||||
|
||||
func CompileShaderOfType(shaderSource []byte, shaderType ShaderType) (Shader, error) {
|
||||
|
||||
shaderID := gl.CreateShader(uint32(shaderType))
|
||||
if shaderID == 0 {
|
||||
logging.ErrLog.Println("Failed to create shader.")
|
||||
return Shader{}, errors.New("failed to create shader")
|
||||
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,6 +1,17 @@
|
||||
package nmageimgui
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
// The following is included just so we can get
|
||||
// the c imports and cgo configs defined here: https://github.com/AllenDang/cimgui-go/blob/main/sdlbackend/sdl_backend.go
|
||||
//
|
||||
// This is needed because AllenDang/cimgui-go links sdl2 statically, which requires us
|
||||
// to explicitly define all the libs it needs, which isn't normally needed if we were dynamically linking.
|
||||
//
|
||||
// Without this, we get compilation errors as sdl2 can't find libs it relies on.
|
||||
_ "github.com/AllenDang/cimgui-go/sdlbackend"
|
||||
|
||||
imgui "github.com/AllenDang/cimgui-go"
|
||||
"github.com/bloeys/gglm/gglm"
|
||||
"github.com/bloeys/nmage/materials"
|
||||
@ -12,11 +23,12 @@ import (
|
||||
type ImguiInfo struct {
|
||||
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) {
|
||||
@ -67,7 +79,7 @@ func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32)
|
||||
i.Mat.Bind()
|
||||
i.Mat.SetUnifInt32("Texture", 0)
|
||||
|
||||
//PERF: only update the ortho matrix on window resize
|
||||
// @PERF: only update the ortho matrix on window resize
|
||||
orthoMat := gglm.Ortho(0, float32(winWidth), 0, float32(winHeight), 0, 20)
|
||||
i.Mat.SetUnifMat4("ProjMtx", &orthoMat.Mat4)
|
||||
gl.BindSampler(0, 0) // Rely on combined texture/sampler state.
|
||||
@ -108,11 +120,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.ElemCount()), uint32(drawType), gl.PtrOffset(int(cmd.IdxOffset())*indexSize), int32(cmd.VtxOffset()))
|
||||
gl.DrawElementsBaseVertexWithOffset(gl.TRIANGLES, int32(cmd.ElemCount()), uint32(drawType), uintptr(int(cmd.IdxOffset())*indexSize), int32(cmd.VtxOffset()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -127,7 +140,7 @@ func (i *ImguiInfo) AddFontTTF(fontPath string, fontSize float32, fontConfig *im
|
||||
|
||||
fontConfigToUse := imgui.NewFontConfig()
|
||||
if fontConfig != nil {
|
||||
fontConfigToUse = *fontConfig
|
||||
fontConfigToUse = fontConfig
|
||||
}
|
||||
|
||||
glyphRangesToUse := imgui.NewGlyphRange()
|
||||
@ -139,15 +152,16 @@ func (i *ImguiInfo) AddFontTTF(fontPath string, fontSize float32, fontConfig *im
|
||||
|
||||
a := imIO.Fonts()
|
||||
f := a.AddFontFromFileTTFV(fontPath, fontSize, fontConfigToUse, glyphRangesToUse.Data())
|
||||
pixels, width, height, _ := a.GetTextureDataAsAlpha8()
|
||||
pixels, width, height, _ := a.TextureDataAsAlpha8()
|
||||
|
||||
gl.BindTexture(gl.TEXTURE_2D, i.TexID)
|
||||
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
|
||||
return *f
|
||||
}
|
||||
|
||||
const imguiShdrSrc = `
|
||||
const DefaultImguiShader = `
|
||||
//shader:vertex
|
||||
#version 410
|
||||
|
||||
@ -160,10 +174,24 @@ in vec4 Color;
|
||||
out vec2 Frag_UV;
|
||||
out vec4 Frag_Color;
|
||||
|
||||
// Imgui doesn't handle srgb correctly, and looks too bright and wrong in srgb buffers (see: https://github.com/ocornut/imgui/issues/578).
|
||||
// While not a complete fix (that would require changes in imgui itself), moving incoming srgba colors to linear in the vertex shader helps make things look better.
|
||||
vec4 srgba_to_linear(vec4 srgbaColor){
|
||||
|
||||
#define gamma_correction 2.2
|
||||
|
||||
return vec4(
|
||||
pow(srgbaColor.r, gamma_correction),
|
||||
pow(srgbaColor.g, gamma_correction),
|
||||
pow(srgbaColor.b, gamma_correction),
|
||||
srgbaColor.a
|
||||
);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
Frag_UV = UV;
|
||||
Frag_Color = Color;
|
||||
Frag_Color = srgba_to_linear(Color);
|
||||
gl_Position = ProjMtx * vec4(Position.xy, 0, 1);
|
||||
}
|
||||
|
||||
@ -183,11 +211,21 @@ void main()
|
||||
}
|
||||
`
|
||||
|
||||
func NewImGui() ImguiInfo {
|
||||
// NewImGui setups imgui using the passed shader.
|
||||
// If the path is empty a default nMage shader is used
|
||||
func NewImGui(shaderPath string) ImguiInfo {
|
||||
|
||||
var imguiMat materials.Material
|
||||
if shaderPath == "" {
|
||||
imguiMat = materials.NewMaterialSrc("ImGUI Mat", []byte(DefaultImguiShader))
|
||||
} else {
|
||||
imguiMat = materials.NewMaterial("ImGUI Mat", shaderPath)
|
||||
}
|
||||
|
||||
imguiInfo := ImguiInfo{
|
||||
ImCtx: imgui.CreateContext(),
|
||||
Mat: materials.NewMaterialSrc("ImGUI Mat", []byte(imguiShdrSrc)),
|
||||
ImCtx: *imgui.CreateContext(),
|
||||
Mat: imguiMat,
|
||||
TexID: new(uint32),
|
||||
}
|
||||
|
||||
io := imgui.CurrentIO()
|
||||
@ -197,19 +235,21 @@ func NewImGui() ImguiInfo {
|
||||
gl.GenVertexArrays(1, &imguiInfo.VaoID)
|
||||
gl.GenBuffers(1, &imguiInfo.VboID)
|
||||
gl.GenBuffers(1, &imguiInfo.IndexBufID)
|
||||
gl.GenTextures(1, &imguiInfo.TexID)
|
||||
gl.GenTextures(1, imguiInfo.TexID)
|
||||
|
||||
// Upload font to gpu
|
||||
gl.BindTexture(gl.TEXTURE_2D, imguiInfo.TexID)
|
||||
gl.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)
|
||||
|
||||
pixels, width, height, _ := io.Fonts().GetTextureDataAsAlpha8()
|
||||
pixels, width, height, _ := io.Fonts().TextureDataAsAlpha8()
|
||||
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, int32(width), int32(height), 0, gl.RED, gl.UNSIGNED_BYTE, pixels)
|
||||
|
||||
// Store our identifier
|
||||
io.Fonts().SetTexID(imgui.TextureID(uintptr(imguiInfo.TexID)))
|
||||
|
||||
io.Fonts().SetTexID(imgui.TextureID{Data: uintptr(unsafe.Pointer(imguiInfo.TexID))})
|
||||
|
||||
//Shader attributes
|
||||
imguiInfo.Mat.Bind()
|
||||
|
||||