Compare commits

...

53 Commits
v0.23.0 ... dev

Author SHA1 Message Date
f5a5d72cc4 Working mac arm+mac x86+windows x86 builds 2024-09-15 18:48:20 +04:00
1bec97b128 test 2024-09-15 18:41:31 +04:00
dea2ac965f test 2024-09-15 18:39:49 +04:00
93b5f08352 test 2024-09-15 18:35:55 +04:00
34e19d9c66 Test 2024-09-15 18:27:03 +04:00
372d9ae6b7 Test 2024-09-15 18:25:18 +04:00
befc78c628 Correct windows action 2024-09-15 18:22:44 +04:00
98f8a96bb7 Build on commit 2024-09-15 18:13:55 +04:00
e767f32f2f Update windows gh action 2024-09-15 18:12:53 +04:00
6d94efbf97 Update actions 2024-09-15 17:47:58 +04:00
28f543a579 Update action 2024-09-15 17:35:40 +04:00
2a73a12885 Update action 2024-09-15 17:34:23 +04:00
e4199b8d30 Update imgui 2024-09-15 17:20:37 +04:00
38248822e2 Update to go 1.23 2024-09-15 16:33:22 +04:00
5c98903723 Add all gl BufUsage values+support bufusage in ubo 2024-09-15 16:29:54 +04:00
3cdd40f0a2 Remove test log 2024-09-15 16:18:59 +04:00
9dccb23613 Move ambient color to lightubo 2024-09-15 16:18:03 +04:00
5dfdea9a7b Move spotlights to ubo 2024-09-15 16:09:37 +04:00
bcb46d1699 Fix ubo alignment+Move point lights to ubo 2024-09-15 15:51:26 +04:00
91807a4093 Merge remote-tracking branch 'refs/remotes/origin/dev' into dev 2024-09-15 12:36:32 +04:00
09231c5ebd Update ubo.SetStruct to handle nested struct arrays 2024-09-15 12:33:34 +04:00
0e98dc85f5 Update ubo.SetStruct to handle nested structs 2024-09-15 12:31:26 +04:00
f2b757c606 Move dirLight&camPos&projViewMat to ubos 2024-09-15 08:40:05 +04:00
3be4ad9c45 Optimize 2024-09-14 19:41:39 +04:00
bbc8652292 What if we could have ubos with nested structs 2024-09-14 18:38:59 +04:00
0d34e0fe6e Ubos with nested structs slowly getting there 2024-06-06 04:59:40 +04:00
870653019c Support ubo matrix arrays+fix ubo matrix field bug 2024-05-26 21:14:33 +04:00
79cb6805c4 Scalar uniform array in uniform buffers 2024-05-26 15:03:00 +04:00
ff7fe4e531 Struct to ubo support 2024-05-26 12:55:58 +04:00
cb20e8ba8b Initial uniform buffers implementation 2024-05-23 07:57:46 +04:00
9e6fdacb48 Much nicer point lights! 2024-05-14 09:07:54 +04:00
f13db47918 Much nicer point light formulas 2024-05-14 07:36:14 +04:00
dcfe254052 Stop sudden camera snaps+nicer debug window 2024-05-14 06:25:39 +04:00
1d71715cb4 Make const value naming upper snake case 2024-05-13 05:35:53 +04:00
581d17d1d9 Frame time graph 2024-05-13 05:18:21 +04:00
3795a7123f Ensure renderer calls aren't virtual 2024-05-13 04:57:16 +04:00
5aa0f41085 Internal material func rename 2024-05-13 04:36:53 +04:00
c782e8c312 Get rid of allocations on SetUniform calls, allowing us to pass ref again 2024-05-13 04:33:54 +04:00
f0a12879f8 Add todo 2024-05-13 03:45:16 +04:00
6ea08e9826 Get rid of more pointers to make allocs predictable 2024-05-13 03:42:52 +04:00
83c6f635e5 Show fps in debug window 2024-05-13 03:21:47 +04:00
cf6b2655e7 After all why not, why shouldn't we have HDR 2024-05-12 06:46:46 +04:00
7b1e3ea7b4 Default textures for diffuse/specular/normal/emission mat slots 2024-05-11 05:11:54 +04:00
c884d2624d Normal mapping 2024-05-07 05:23:36 +04:00
8c6b1d5821 Adjust shadow map texture sizes 2024-05-06 23:23:52 +04:00
dfd1fe9c5e Material settings+normal matrices on CPU 2024-05-06 22:55:57 +04:00
24613823a7 Fix gitignore 2024-05-06 22:18:15 +04:00
0386f441d6 Profiling 2024-05-06 22:16:20 +04:00
57ab851534 Update gglm 2024-05-05 00:34:38 +04:00
d523c0951b Get rid of unneeded pointers+update todos 2024-05-01 01:16:33 +04:00
abd7079e61 Correct modifier keys input to imgui 2024-04-24 19:56:35 +04:00
4d8ccdaf56 Captured/uncaptured mode in input package+comments 2024-04-20 11:53:06 +04:00
a131e1b52d Add todo regarding input package 2024-04-20 11:14:29 +04:00
29 changed files with 2567 additions and 664 deletions

View File

@ -1,24 +1,69 @@
name: build-nmage name: build-nmage
on: on:
push:
branches:
- dev
create: create:
workflow_dispatch: workflow_dispatch:
jobs: jobs:
build-nmage-macos: build-nmage-windows:
runs-on: macos-12 runs-on: windows-latest
steps: steps:
- name: Install golang - name: Install golang
uses: actions/setup-go@v3 uses: actions/setup-go@v5
with: with:
go-version: '>=1.22' 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 - 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 - 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 - name: Clone nmage
run: git clone https://github.com/bloeys/nmage run: git clone https://github.com/bloeys/nmage

5
.gitignore vendored
View File

@ -15,4 +15,7 @@
vendor/ vendor/
.vscode/ .vscode/
imgui.ini imgui.ini
*~ *~
# Custom
*.pprof

View File

@ -23,6 +23,15 @@ const (
ColorFormat_RGBA8 ColorFormat_RGBA8
) )
var (
DefaultBlackTexId Texture
DefaultWhiteTexId Texture
DefaultDiffuseTexId Texture
DefaultSpecularTexId Texture
DefaultNormalTexId Texture
DefaultEmissionTexId Texture
)
type Texture struct { type Texture struct {
// Path only exists for textures loaded from disk // Path only exists for textures loaded from disk
Path string Path string

View File

@ -9,25 +9,48 @@ import (
type BufUsage int type BufUsage int
// Full docs for buffer usage can be found here: https://registry.khronos.org/OpenGL-Refpages/gl4/html/glBufferData.xhtml
const ( const (
BufUsage_Unknown BufUsage = iota BufUsage_Unknown BufUsage = iota
//Buffer is set only once and used many times //Buffer is set only once and used many times
BufUsage_Static BufUsage_Static_Draw
//Buffer is changed a lot and used many times //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 //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 { func (b BufUsage) ToGL() uint32 {
switch b { switch b {
case BufUsage_Static: case BufUsage_Static_Draw:
return gl.STATIC_DRAW return gl.STATIC_DRAW
case BufUsage_Dynamic: case BufUsage_Dynamic_Draw:
return gl.DYNAMIC_DRAW return gl.DYNAMIC_DRAW
case BufUsage_Stream: case BufUsage_Stream_Draw:
return gl.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)) assert.T(false, fmt.Sprintf("Unexpected BufUsage value '%v'", b))

View File

@ -1,23 +1,23 @@
package buffers package buffers
import ( import (
"fmt"
"github.com/bloeys/nmage/assert" "github.com/bloeys/nmage/assert"
"github.com/bloeys/nmage/logging"
"github.com/go-gl/gl/v4.1-core/gl" "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 { type Element struct {
Offset int Offset int
ElementType ElementType
} }
//ElementType is the type of an element thats makes up a buffer (e.g. Vec3) // ElementType is the type of an element thats makes up a buffer (e.g. Vec3)
type ElementType int type ElementType uint8
const ( const (
DataTypeUnknown ElementType = iota DataTypeUnknown ElementType = iota
DataTypeUint32 DataTypeUint32
DataTypeInt32 DataTypeInt32
DataTypeFloat32 DataTypeFloat32
@ -25,35 +25,54 @@ const (
DataTypeVec2 DataTypeVec2
DataTypeVec3 DataTypeVec3
DataTypeVec4 DataTypeVec4
DataTypeMat2
DataTypeMat3
DataTypeMat4
DataTypeStruct
) )
func (dt ElementType) GLType() uint32 { func (dt ElementType) GLType() uint32 {
switch dt { switch dt {
case DataTypeUint32: case DataTypeUint32:
return gl.UNSIGNED_INT return gl.UNSIGNED_INT
case DataTypeInt32: case DataTypeInt32:
return gl.INT return gl.INT
case DataTypeFloat32: case DataTypeFloat32:
fallthrough fallthrough
case DataTypeVec2: case DataTypeVec2:
fallthrough fallthrough
case DataTypeVec3: case DataTypeVec3:
fallthrough fallthrough
case DataTypeVec4: case DataTypeVec4:
fallthrough
case DataTypeMat2:
fallthrough
case DataTypeMat3:
fallthrough
case DataTypeMat4:
return gl.FLOAT return gl.FLOAT
case DataTypeStruct:
logging.ErrLog.Fatalf("ElementType.GLType of DataTypeStruct is not supported")
return 0
default: 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 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 { func (dt ElementType) CompSize() int32 {
switch dt { switch dt {
case DataTypeUint32: case DataTypeUint32:
fallthrough fallthrough
case DataTypeFloat32: case DataTypeFloat32:
@ -65,15 +84,25 @@ func (dt ElementType) CompSize() int32 {
case DataTypeVec3: case DataTypeVec3:
fallthrough fallthrough
case DataTypeVec4: case DataTypeVec4:
fallthrough
case DataTypeMat2:
fallthrough
case DataTypeMat3:
fallthrough
case DataTypeMat4:
return 4 return 4
case DataTypeStruct:
logging.ErrLog.Fatalf("ElementType.CompSize of DataTypeStruct is not supported")
return 0
default: 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 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 { func (dt ElementType) CompCount() int32 {
switch dt { switch dt {
@ -91,16 +120,28 @@ func (dt ElementType) CompCount() int32 {
case DataTypeVec4: case DataTypeVec4:
return 4 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: 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 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 { func (dt ElementType) Size() int32 {
switch dt { switch dt {
case DataTypeUint32: case DataTypeUint32:
fallthrough fallthrough
case DataTypeFloat32: case DataTypeFloat32:
@ -115,8 +156,123 @@ func (dt ElementType) Size() int32 {
case DataTypeVec4: case DataTypeVec4:
return 4 * 4 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: 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 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"
}
}

View File

@ -42,6 +42,7 @@ const (
FramebufferAttachmentDataFormat_Unknown FramebufferAttachmentDataFormat = iota FramebufferAttachmentDataFormat_Unknown FramebufferAttachmentDataFormat = iota
FramebufferAttachmentDataFormat_R32Int FramebufferAttachmentDataFormat_R32Int
FramebufferAttachmentDataFormat_RGBA8 FramebufferAttachmentDataFormat_RGBA8
FramebufferAttachmentDataFormat_RGBAF16
FramebufferAttachmentDataFormat_SRGBA FramebufferAttachmentDataFormat_SRGBA
FramebufferAttachmentDataFormat_DepthF32 FramebufferAttachmentDataFormat_DepthF32
FramebufferAttachmentDataFormat_Depth24Stencil8 FramebufferAttachmentDataFormat_Depth24Stencil8
@ -50,7 +51,8 @@ const (
func (f FramebufferAttachmentDataFormat) IsColorFormat() bool { func (f FramebufferAttachmentDataFormat) IsColorFormat() bool {
return f == FramebufferAttachmentDataFormat_R32Int || return f == FramebufferAttachmentDataFormat_R32Int ||
f == FramebufferAttachmentDataFormat_RGBA8 || f == FramebufferAttachmentDataFormat_RGBA8 ||
f == FramebufferAttachmentDataFormat_SRGBA f == FramebufferAttachmentDataFormat_SRGBA ||
f == FramebufferAttachmentDataFormat_RGBAF16
} }
func (f FramebufferAttachmentDataFormat) IsDepthFormat() bool { func (f FramebufferAttachmentDataFormat) IsDepthFormat() bool {
@ -65,6 +67,8 @@ func (f FramebufferAttachmentDataFormat) GlInternalFormat() int32 {
return gl.R32I return gl.R32I
case FramebufferAttachmentDataFormat_RGBA8: case FramebufferAttachmentDataFormat_RGBA8:
return gl.RGB8 return gl.RGB8
case FramebufferAttachmentDataFormat_RGBAF16:
return gl.RGBA16F
case FramebufferAttachmentDataFormat_SRGBA: case FramebufferAttachmentDataFormat_SRGBA:
return gl.SRGB_ALPHA return gl.SRGB_ALPHA
case FramebufferAttachmentDataFormat_DepthF32: case FramebufferAttachmentDataFormat_DepthF32:
@ -85,6 +89,8 @@ func (f FramebufferAttachmentDataFormat) GlFormat() uint32 {
case FramebufferAttachmentDataFormat_RGBA8: case FramebufferAttachmentDataFormat_RGBA8:
fallthrough fallthrough
case FramebufferAttachmentDataFormat_RGBAF16:
fallthrough
case FramebufferAttachmentDataFormat_SRGBA: case FramebufferAttachmentDataFormat_SRGBA:
return gl.RGBA return gl.RGBA
@ -100,6 +106,33 @@ func (f FramebufferAttachmentDataFormat) GlFormat() uint32 {
} }
} }
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 { type FramebufferAttachment struct {
Id uint32 Id uint32
Type FramebufferAttachmentType Type FramebufferAttachmentType
@ -124,7 +157,7 @@ func (fbo *Framebuffer) BindWithViewport() {
gl.Viewport(0, 0, int32(fbo.Width), int32(fbo.Height)) gl.Viewport(0, 0, int32(fbo.Width), int32(fbo.Height))
} }
// Clear calls gl.Clear with the fob's clear flags. // Clear calls gl.Clear with the fbo's clear flags.
// Note that the fbo must be complete and bound. // Note that the fbo must be complete and bound.
// Calling this without a bound fbo will clear something else, like your screen. // Calling this without a bound fbo will clear something else, like your screen.
func (fbo *Framebuffer) Clear() { func (fbo *Framebuffer) Clear() {
@ -207,7 +240,17 @@ func (fbo *Framebuffer) NewColorAttachment(
} }
gl.BindTexture(gl.TEXTURE_2D, a.Id) gl.BindTexture(gl.TEXTURE_2D, a.Id)
gl.TexImage2D(gl.TEXTURE_2D, 0, attachFormat.GlInternalFormat(), int32(fbo.Width), int32(fbo.Height), 0, attachFormat.GlFormat(), gl.UNSIGNED_BYTE, nil) 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_MIN_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
@ -298,7 +341,17 @@ func (fbo *Framebuffer) NewDepthAttachment(
} }
gl.BindTexture(gl.TEXTURE_2D, a.Id) gl.BindTexture(gl.TEXTURE_2D, a.Id)
gl.TexImage2D(gl.TEXTURE_2D, 0, attachFormat.GlInternalFormat(), int32(fbo.Width), int32(fbo.Height), 0, attachFormat.GlFormat(), gl.FLOAT, nil) 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_MIN_FILTER, gl.NEAREST)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
@ -341,7 +394,17 @@ func (fbo *Framebuffer) NewDepthAttachment(
gl.BindTexture(gl.TEXTURE_CUBE_MAP, a.Id) gl.BindTexture(gl.TEXTURE_CUBE_MAP, a.Id)
for i := 0; i < 6; i++ { 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(), gl.FLOAT, nil) 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_MIN_FILTER, gl.NEAREST)
@ -398,7 +461,7 @@ func (fbo *Framebuffer) NewDepthCubemapArrayAttachment(
6*numCubemaps, 6*numCubemaps,
0, 0,
attachFormat.GlFormat(), attachFormat.GlFormat(),
gl.FLOAT, attachFormat.GlComponentType(),
nil, nil,
) )
@ -455,7 +518,7 @@ func (fbo *Framebuffer) NewDepthTextureArrayAttachment(
numTextures, numTextures,
0, 0,
attachFormat.GlFormat(), attachFormat.GlFormat(),
gl.FLOAT, attachFormat.GlComponentType(),
nil, nil,
) )
@ -513,7 +576,17 @@ func (fbo *Framebuffer) NewDepthStencilAttachment(
} }
gl.BindTexture(gl.TEXTURE_2D, a.Id) gl.BindTexture(gl.TEXTURE_2D, a.Id)
gl.TexImage2D(gl.TEXTURE_2D, 0, attachFormat.GlInternalFormat(), int32(fbo.Width), int32(fbo.Height), 0, attachFormat.GlFormat(), gl.UNSIGNED_INT_24_8, nil) 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_MIN_FILTER, gl.NEAREST)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)

View File

@ -27,9 +27,9 @@ func (ib *IndexBuffer) SetData(values []uint32) {
ib.IndexBufCount = int32(len(values)) ib.IndexBufCount = int32(len(values))
if sizeInBytes == 0 { if sizeInBytes == 0 {
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, 0, gl.Ptr(nil), BufUsage_Static.ToGL()) gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, 0, gl.Ptr(nil), BufUsage_Static_Draw.ToGL())
} else { } else {
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, sizeInBytes, gl.Ptr(&values[0]), BufUsage_Static.ToGL()) gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, sizeInBytes, gl.Ptr(&values[0]), BufUsage_Static_Draw.ToGL())
} }
} }

747
buffers/uniform_buffer.go Executable file
View 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
}

View File

@ -55,7 +55,7 @@ func NewVertexBuffer(layout ...Element) VertexBuffer {
gl.GenBuffers(1, &vb.Id) gl.GenBuffers(1, &vb.Id)
if vb.Id == 0 { if vb.Id == 0 {
logging.ErrLog.Println("Failed to create OpenGL buffer") logging.ErrLog.Panicln("Failed to create OpenGL buffer")
} }
vb.SetLayout(layout...) vb.SetLayout(layout...)

View File

@ -41,7 +41,7 @@ func (c *Camera) Update() {
c.ViewMat = gglm.LookAtRH(&c.Pos, c.Pos.Clone().Add(&c.Forward), &c.WorldUp).Mat4 c.ViewMat = gglm.LookAtRH(&c.Pos, c.Pos.Clone().Add(&c.Forward), &c.WorldUp).Mat4
if c.Type == Type_Perspective { 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 { } else {
c.ProjMat = gglm.Ortho(c.Left, c.Right, c.Top, c.Bottom, c.NearClip, c.FarClip).Mat4 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() 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, Type: Type_Perspective,
Pos: *pos, Pos: *pos,
Forward: *forward, Forward: *forward,
@ -78,9 +78,9 @@ func NewPerspective(pos, forward, worldUp *gglm.Vec3, nearClip, farClip, fovRadi
return cam 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, Type: Type_Orthographic,
Pos: *pos, Pos: *pos,
Forward: *forward, Forward: *forward,

View File

@ -1,12 +1,14 @@
package engine package engine
import ( import (
"image"
"image/color"
"runtime" "runtime"
imgui "github.com/AllenDang/cimgui-go" imgui "github.com/AllenDang/cimgui-go"
"github.com/bloeys/nmage/assert" "github.com/bloeys/nmage/assert"
"github.com/bloeys/nmage/assets"
"github.com/bloeys/nmage/input" "github.com/bloeys/nmage/input"
"github.com/bloeys/nmage/renderer"
"github.com/bloeys/nmage/timing" "github.com/bloeys/nmage/timing"
nmageimgui "github.com/bloeys/nmage/ui/imgui" nmageimgui "github.com/bloeys/nmage/ui/imgui"
"github.com/go-gl/gl/v4.1-core/gl" "github.com/go-gl/gl/v4.1-core/gl"
@ -28,30 +30,16 @@ type Window struct {
SDLWin *sdl.Window SDLWin *sdl.Window
GlCtx sdl.GLContext GlCtx sdl.GLContext
EventCallbacks []func(sdl.Event) EventCallbacks []func(sdl.Event)
Rend renderer.Render
} }
func (w *Window) handleInputs() { func (w *Window) handleInputs() {
input.EventLoopStart()
imIo := imgui.CurrentIO() imIo := imgui.CurrentIO()
imguiCaptureMouse := imIo.WantCaptureMouse() imguiCaptureMouse := imIo.WantCaptureMouse()
imguiCaptureKeyboard := imIo.WantCaptureKeyboard() imguiCaptureKeyboard := imIo.WantCaptureKeyboard()
// These two are to fix a bug where state isn't cleared input.EventLoopStart(imguiCaptureMouse, imguiCaptureKeyboard)
// even after imgui captures the keyboard/mouse.
//
// For example, if player is moving due to key held and then imgui captures the keyboard,
// the player keeps moving even when the key is no longer pressed because the input system never
// receives the key up event.
if imguiCaptureMouse {
input.ClearMouseState()
}
if imguiCaptureKeyboard {
input.ClearKeyboardState()
}
for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() { for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
@ -65,46 +53,27 @@ func (w *Window) handleInputs() {
case *sdl.MouseWheelEvent: case *sdl.MouseWheelEvent:
if !imguiCaptureMouse { input.HandleMouseWheelEvent(e)
input.HandleMouseWheelEvent(e)
}
imIo.AddMouseWheelDelta(float32(e.X), float32(e.Y)) imIo.AddMouseWheelDelta(float32(e.X), float32(e.Y))
case *sdl.KeyboardEvent: case *sdl.KeyboardEvent:
if !imguiCaptureKeyboard { input.HandleKeyboardEvent(e)
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) 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: case *sdl.TextInputEvent:
imIo.AddInputCharactersUTF8(e.GetText()) imIo.AddInputCharactersUTF8(e.GetText())
case *sdl.MouseButtonEvent: case *sdl.MouseButtonEvent:
if !imguiCaptureMouse { input.HandleMouseBtnEvent(e)
input.HandleMouseBtnEvent(e)
}
isPressed := e.State == sdl.PRESSED isPressed := e.State == sdl.PRESSED
if e.Button == sdl.BUTTON_LEFT { if e.Button == sdl.BUTTON_LEFT {
@ -117,9 +86,7 @@ func (w *Window) handleInputs() {
case *sdl.MouseMotionEvent: case *sdl.MouseMotionEvent:
if !imguiCaptureMouse { input.HandleMouseMotionEvent(e)
input.HandleMouseMotionEvent(e)
}
case *sdl.WindowEvent: case *sdl.WindowEvent:
@ -140,9 +107,9 @@ 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. // If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame.
imIo.SetMouseButtonDown(imgui.MouseButtonLeft, isSdlButtonLeftDown) imIo.SetMouseButtonDown(int(imgui.MouseButtonLeft), isSdlButtonLeftDown)
imIo.SetMouseButtonDown(imgui.MouseButtonRight, isSdlButtonRightDown) imIo.SetMouseButtonDown(int(imgui.MouseButtonRight), isSdlButtonRightDown)
imIo.SetMouseButtonDown(imgui.MouseButtonMiddle, isSdlButtonMiddleDown) imIo.SetMouseButtonDown(int(imgui.MouseButtonMiddle), isSdlButtonMiddleDown)
} }
func (w *Window) handleWindowResize() { func (w *Window) handleWindowResize() {
@ -201,42 +168,45 @@ func initSDL() error {
return nil return nil
} }
func CreateOpenGLWindow(title string, x, y, width, height int32, flags WindowFlags, rend renderer.Render) (*Window, error) { func CreateOpenGLWindow(title string, x, y, width, height int32, flags WindowFlags) (Window, error) {
return createWindow(title, x, y, width, height, WindowFlags_OPENGL|flags, rend) return createWindow(title, x, y, width, height, WindowFlags_OPENGL|flags)
} }
func CreateOpenGLWindowCentered(title string, width, height int32, flags WindowFlags, rend renderer.Render) (*Window, error) { 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, rend) 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!") assert.T(isInited, "engine.Init() was not called!")
sdlWin, err := sdl.CreateWindow(title, x, y, width, height, uint32(flags)) win := Window{
if err != nil { SDLWin: nil,
return nil, err
}
win := &Window{
SDLWin: sdlWin,
EventCallbacks: make([]func(sdl.Event), 0), 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 { if err != nil {
return nil, err return win, err
}
win.GlCtx, err = win.SDLWin.GLCreateContext()
if err != nil {
return win, err
} }
err = initOpenGL() err = initOpenGL()
if err != nil { 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) // 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) gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
sdlWin.GLSwap() win.SDLWin.GLSwap()
return win, err return win, err
} }
@ -263,6 +233,57 @@ func initOpenGL() error {
return nil 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) { func SetSrgbFramebuffer(isEnabled bool) {
if isEnabled { if isEnabled {

View File

@ -1,6 +1,7 @@
package engine package engine
import ( import (
"github.com/bloeys/nmage/renderer"
"github.com/bloeys/nmage/timing" "github.com/bloeys/nmage/timing"
nmageimgui "github.com/bloeys/nmage/ui/imgui" nmageimgui "github.com/bloeys/nmage/ui/imgui"
"github.com/go-gl/gl/v4.1-core/gl" "github.com/go-gl/gl/v4.1-core/gl"
@ -20,7 +21,7 @@ type Game interface {
DeInit() DeInit()
} }
func Run(g Game, w *Window, ui nmageimgui.ImguiInfo) { func Run(g Game, w *Window, rend renderer.Render, ui nmageimgui.ImguiInfo) {
isRunning = true isRunning = true
@ -40,7 +41,6 @@ func Run(g Game, w *Window, ui nmageimgui.ImguiInfo) {
for isRunning { for isRunning {
//PERF: Cache these
width, height = w.SDLWin.GetSize() width, height = w.SDLWin.GetSize()
fbWidth, fbHeight = w.SDLWin.GLGetDrawableSize() fbWidth, fbHeight = w.SDLWin.GLGetDrawableSize()
@ -56,7 +56,7 @@ func Run(g Game, w *Window, ui nmageimgui.ImguiInfo) {
w.SDLWin.GLSwap() w.SDLWin.GLSwap()
g.FrameEnd() g.FrameEnd()
w.Rend.FrameEnd() rend.FrameEnd()
timing.FrameEnded() timing.FrameEnded()
} }

11
go.mod
View File

@ -1,6 +1,6 @@
module github.com/bloeys/nmage module github.com/bloeys/nmage
go 1.22 go 1.23
require github.com/veandco/go-sdl2 v0.4.35 require github.com/veandco/go-sdl2 v0.4.35
@ -8,12 +8,15 @@ require github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6
require ( require (
github.com/bloeys/assimp-go v0.4.4 github.com/bloeys/assimp-go v0.4.4
github.com/bloeys/gglm v0.43.0 github.com/bloeys/gglm v0.50.0
) )
require ( require (
github.com/AllenDang/cimgui-go v0.0.0-20230720025235-f2ff398a66b2 github.com/AllenDang/cimgui-go v0.0.0-20240912193335-545751598105
github.com/mandykoh/prism v0.35.1 github.com/mandykoh/prism v0.35.1
) )
require github.com/mandykoh/go-parallel v0.1.0 // indirect require (
github.com/mandykoh/go-parallel v0.1.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
)

8
go.sum
View File

@ -1,9 +1,11 @@
github.com/AllenDang/cimgui-go v0.0.0-20230720025235-f2ff398a66b2 h1:3HA/5qD8Rimxz/y1sLyVaM7ws1dzjXzMt4hOBiwHggo= 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-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 h1:Yn5e/RpE0Oes0YMBy8O7KkwAO4R/RpgrZPJCt08dVIU=
github.com/bloeys/assimp-go v0.4.4/go.mod h1:my3yRxT7CfOztmvi+0svmwbaqw0KFrxaHxncoyaEIP0= 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.50.0 h1:DlGLp9z8KMNx+hNR6PjnPmC0HjDRC19QwAKL1iwhOxs=
github.com/bloeys/gglm v0.43.0/go.mod h1:qwJQ0WzV191wAMwlGicbfbChbKoSedMk7gFFX6GnyOk= 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 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk=
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= 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 h1:7vJMNMC4dsbgZdkAb2A8tV5ENY1v7VxIO1wzQWZoT8k=
@ -15,6 +17,8 @@ github.com/veandco/go-sdl2 v0.4.35/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofe
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 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-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/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 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4= 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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=

View File

@ -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 package input
import "github.com/veandco/go-sdl2/sdl" import (
"github.com/veandco/go-sdl2/sdl"
)
type keyState struct { type keyState struct {
Key sdl.Keycode Key sdl.Keycode
@ -31,15 +48,22 @@ type mouseWheelState struct {
} }
var ( var (
keyMap = make(map[sdl.Keycode]keyState) mouseWheel = mouseWheelState{}
mouseBtnMap = make(map[int]mouseBtnState) mouseMotion = mouseMotionState{}
mouseMotion = mouseMotionState{} mouseBtnMap = make(map[int]mouseBtnState)
mouseWheel = mouseWheelState{} keyMap = make(map[sdl.Keycode]keyState)
quitRequested bool
isQuitRequested bool
isMouseCaptured bool
isKeyboardCaptured bool
) )
func EventLoopStart() { func EventLoopStart(mouseGotCaptured, keyboardGotCaptured bool) {
isMouseCaptured = mouseGotCaptured
isKeyboardCaptured = keyboardGotCaptured
// Update per-frame state
for k, v := range keyMap { for k, v := range keyMap {
v.IsPressedThisFrame = false v.IsPressedThisFrame = false
v.IsReleasedThisFrame = false v.IsReleasedThisFrame = false
@ -59,7 +83,7 @@ func EventLoopStart() {
mouseWheel.XDelta = 0 mouseWheel.XDelta = 0
mouseWheel.YDelta = 0 mouseWheel.YDelta = 0
quitRequested = false isQuitRequested = false
} }
func ClearKeyboardState() { func ClearKeyboardState() {
@ -73,11 +97,19 @@ func ClearMouseState() {
} }
func HandleQuitEvent(e *sdl.QuitEvent) { func HandleQuitEvent(e *sdl.QuitEvent) {
quitRequested = true isQuitRequested = true
}
func IsMouseCaptured() bool {
return isMouseCaptured
}
func IsKeyboardCaptured() bool {
return isKeyboardCaptured
} }
func IsQuitClicked() bool { func IsQuitClicked() bool {
return quitRequested return isQuitRequested
} }
func HandleKeyboardEvent(e *sdl.KeyboardEvent) { func HandleKeyboardEvent(e *sdl.KeyboardEvent) {
@ -123,18 +155,36 @@ func HandleMouseWheelEvent(e *sdl.MouseWheelEvent) {
mouseWheel.YDelta = e.Y 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) { func GetMousePos() (x, y int32) {
return mouseMotion.XPos, mouseMotion.YPos 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) { func GetMouseMotion() (xDelta, yDelta int32) {
if isMouseCaptured {
return 0, 0
}
return GetMouseMotionCaptured()
}
func GetMouseMotionCaptured() (xDelta, yDelta int32) {
return mouseMotion.XDelta, mouseMotion.YDelta return mouseMotion.XDelta, mouseMotion.YDelta
} }
func GetMouseMotionNorm() (xDelta, yDelta int32) { func GetMouseMotionNorm() (xDelta, yDelta int32) {
if isMouseCaptured {
return 0, 0
}
return GetMouseMotionNormCaptured()
}
func GetMouseMotionNormCaptured() (xDelta, yDelta int32) {
x, y := mouseMotion.XDelta, mouseMotion.YDelta x, y := mouseMotion.XDelta, mouseMotion.YDelta
if x > 0 { if x > 0 {
x = 1 x = 1
@ -152,12 +202,31 @@ func GetMouseMotionNorm() (xDelta, yDelta int32) {
} }
func GetMouseWheelMotion() (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 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 { 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 { if mouseWheel.XDelta > 0 {
return 1 return 1
} else if mouseWheel.XDelta < 0 { } else if mouseWheel.XDelta < 0 {
@ -167,9 +236,19 @@ func GetMouseWheelXNorm() int32 {
return 0 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 { 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 { if mouseWheel.YDelta > 0 {
return 1 return 1
} else if mouseWheel.YDelta < 0 { } else if mouseWheel.YDelta < 0 {
@ -181,6 +260,15 @@ func GetMouseWheelYNorm() int32 {
func KeyClicked(kc sdl.Keycode) bool { func KeyClicked(kc sdl.Keycode) bool {
if isKeyboardCaptured {
return false
}
return KeyClickedCaptured(kc)
}
func KeyClickedCaptured(kc sdl.Keycode) bool {
ks, ok := keyMap[kc] ks, ok := keyMap[kc]
if !ok { if !ok {
return false return false
@ -191,6 +279,15 @@ func KeyClicked(kc sdl.Keycode) bool {
func KeyReleased(kc sdl.Keycode) bool { func KeyReleased(kc sdl.Keycode) bool {
if isKeyboardCaptured {
return false
}
return KeyReleasedCaptured(kc)
}
func KeyReleasedCaptured(kc sdl.Keycode) bool {
ks, ok := keyMap[kc] ks, ok := keyMap[kc]
if !ok { if !ok {
return false return false
@ -201,6 +298,15 @@ func KeyReleased(kc sdl.Keycode) bool {
func KeyDown(kc sdl.Keycode) bool { func KeyDown(kc sdl.Keycode) bool {
if isKeyboardCaptured {
return false
}
return KeyDownCaptured(kc)
}
func KeyDownCaptured(kc sdl.Keycode) bool {
ks, ok := keyMap[kc] ks, ok := keyMap[kc]
if !ok { if !ok {
return false return false
@ -211,6 +317,15 @@ func KeyDown(kc sdl.Keycode) bool {
func KeyUp(kc sdl.Keycode) bool { func KeyUp(kc sdl.Keycode) bool {
if isKeyboardCaptured {
return false
}
return KeyUpCaptured(kc)
}
func KeyUpCaptured(kc sdl.Keycode) bool {
ks, ok := keyMap[kc] ks, ok := keyMap[kc]
if !ok { if !ok {
return true return true
@ -221,6 +336,15 @@ func KeyUp(kc sdl.Keycode) bool {
func MouseClicked(mb int) bool { func MouseClicked(mb int) bool {
if isMouseCaptured {
return false
}
return MouseClickedCaptued(mb)
}
func MouseClickedCaptued(mb int) bool {
btn, ok := mouseBtnMap[mb] btn, ok := mouseBtnMap[mb]
if !ok { if !ok {
return false return false
@ -231,6 +355,15 @@ func MouseClicked(mb int) bool {
func MouseDoubleClicked(mb int) bool { func MouseDoubleClicked(mb int) bool {
if isMouseCaptured {
return false
}
return MouseDoubleClickedCaptured(mb)
}
func MouseDoubleClickedCaptured(mb int) bool {
btn, ok := mouseBtnMap[mb] btn, ok := mouseBtnMap[mb]
if !ok { if !ok {
return false return false
@ -240,6 +373,16 @@ func MouseDoubleClicked(mb int) bool {
} }
func MouseReleased(mb int) bool { func MouseReleased(mb int) bool {
if isMouseCaptured {
return false
}
return MouseReleasedCaptured(mb)
}
func MouseReleasedCaptured(mb int) bool {
btn, ok := mouseBtnMap[mb] btn, ok := mouseBtnMap[mb]
if !ok { if !ok {
return false return false
@ -250,6 +393,15 @@ func MouseReleased(mb int) bool {
func MouseDown(mb int) bool { func MouseDown(mb int) bool {
if isMouseCaptured {
return false
}
return MouseDownCaptued(mb)
}
func MouseDownCaptued(mb int) bool {
btn, ok := mouseBtnMap[mb] btn, ok := mouseBtnMap[mb]
if !ok { if !ok {
return false return false
@ -260,6 +412,15 @@ func MouseDown(mb int) bool {
func MouseUp(mb int) bool { func MouseUp(mb int) bool {
if isMouseCaptured {
return false
}
return MouseUpCaptured(mb)
}
func MouseUpCaptured(mb int) bool {
btn, ok := mouseBtnMap[mb] btn, ok := mouseBtnMap[mb]
if !ok { if !ok {
return true return true

941
main.go

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,26 @@
package materials package materials
import ( import (
_ "unsafe"
"github.com/bloeys/gglm/gglm" "github.com/bloeys/gglm/gglm"
"github.com/bloeys/nmage/assert" "github.com/bloeys/nmage/assert"
"github.com/bloeys/nmage/assets"
"github.com/bloeys/nmage/logging" "github.com/bloeys/nmage/logging"
"github.com/bloeys/nmage/shaders" "github.com/bloeys/nmage/shaders"
"github.com/go-gl/gl/v4.1-core/gl" "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 type TextureSlot uint32
const ( const (
@ -21,14 +34,36 @@ const (
TextureSlot_ShadowMap_Array1 TextureSlot = 13 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 { type Material struct {
Id uint32
Name string Name string
ShaderProg shaders.ShaderProgram ShaderProg shaders.ShaderProgram
Settings MaterialSettings
UnifLocs map[string]int32 UnifLocs map[string]int32
AttribLocs map[string]int32 AttribLocs map[string]int32
// @TODO do this in a better way. Perhaps something like how we do fbo attachments // @TODO: Do this in a better way?. Perhaps something like how we do fbo attachments? Or keep it?
// Phong shading // Phong shading
DiffuseTex uint32 DiffuseTex uint32
SpecularTex uint32 SpecularTex uint32
@ -51,26 +86,19 @@ func (m *Material) Bind() {
m.ShaderProg.Bind() m.ShaderProg.Bind()
if m.DiffuseTex != 0 { gl.ActiveTexture(uint32(gl.TEXTURE0 + TextureSlot_Diffuse))
gl.ActiveTexture(uint32(gl.TEXTURE0 + TextureSlot_Diffuse)) gl.BindTexture(gl.TEXTURE_2D, m.DiffuseTex)
gl.BindTexture(gl.TEXTURE_2D, m.DiffuseTex)
}
if m.SpecularTex != 0 { gl.ActiveTexture(uint32(gl.TEXTURE0 + TextureSlot_Specular))
gl.ActiveTexture(uint32(gl.TEXTURE0 + TextureSlot_Specular)) gl.BindTexture(gl.TEXTURE_2D, m.SpecularTex)
gl.BindTexture(gl.TEXTURE_2D, m.SpecularTex)
}
if m.NormalTex != 0 { gl.ActiveTexture(uint32(gl.TEXTURE0 + TextureSlot_Normal))
gl.ActiveTexture(uint32(gl.TEXTURE0 + TextureSlot_Normal)) gl.BindTexture(gl.TEXTURE_2D, m.NormalTex)
gl.BindTexture(gl.TEXTURE_2D, m.NormalTex)
}
if m.EmissionTex != 0 { gl.ActiveTexture(uint32(gl.TEXTURE0 + TextureSlot_Emission))
gl.ActiveTexture(uint32(gl.TEXTURE0 + TextureSlot_Emission)) gl.BindTexture(gl.TEXTURE_2D, m.EmissionTex)
gl.BindTexture(gl.TEXTURE_2D, m.EmissionTex)
}
// @TODO: Have defaults for these
if m.CubemapTex != 0 { if m.CubemapTex != 0 {
gl.ActiveTexture(uint32(gl.TEXTURE0 + TextureSlot_Cubemap)) gl.ActiveTexture(uint32(gl.TEXTURE0 + TextureSlot_Cubemap))
gl.BindTexture(gl.TEXTURE_CUBE_MAP, m.CubemapTex) gl.BindTexture(gl.TEXTURE_CUBE_MAP, m.CubemapTex)
@ -96,6 +124,21 @@ func (m *Material) UnBind() {
gl.UseProgram(0) gl.UseProgram(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 { func (m *Material) GetAttribLoc(attribName string) int32 {
loc, ok := m.AttribLocs[attribName] loc, ok := m.AttribLocs[attribName]
@ -103,7 +146,8 @@ func (m *Material) GetAttribLoc(attribName string) int32 {
return loc 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) assert.T(loc != -1, "Attribute '"+attribName+"' doesn't exist on material "+m.Name)
m.AttribLocs[attribName] = loc m.AttribLocs[attribName] = loc
return loc return loc
@ -116,7 +160,8 @@ func (m *Material) GetUnifLoc(uniformName string) int32 {
return loc 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) assert.T(loc != -1, "Uniform '"+uniformName+"' doesn't exist on material "+m.Name)
m.UnifLocs[uniformName] = loc m.UnifLocs[uniformName] = loc
return loc return loc
@ -139,49 +184,124 @@ func (m *Material) SetUnifFloat32(uniformName string, val float32) {
} }
func (m *Material) SetUnifVec2(uniformName string, vec2 *gglm.Vec2) { 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) { 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) { 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) { 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) { 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) { 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() { 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) shdrProg, err := shaders.LoadAndCompileCombinedShader(shaderPath)
if err != nil { if err != nil {
logging.ErrLog.Fatalf("Failed to create new material '%s'. Err: %s\n", matName, err.Error()) 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) shdrProg, err := shaders.LoadAndCompileCombinedShaderSrc(shaderSrc)
if err != nil { if err != nil {
logging.ErrLog.Fatalf("Failed to create new material '%s'. Err: %s\n", matName, err.Error()) 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,
}
} }

View File

@ -16,24 +16,48 @@ type SubMesh struct {
} }
type Mesh struct { type Mesh struct {
Name string 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 Vao buffers.VertexArray
SubMeshes []SubMesh 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 { 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() defer release()
if len(scene.Meshes) == 0 { 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, Name: name,
Vao: buffers.NewVertexArray(), Vao: buffers.NewVertexArray(),
SubMeshes: make([]SubMesh, 0, 1), SubMeshes: make([]SubMesh, 0, 1),
@ -42,8 +66,17 @@ func NewMesh(name, modelPath string, postProcessFlags asig.PostProcess) (*Mesh,
vbo := buffers.NewVertexBuffer() vbo := buffers.NewVertexBuffer()
ibo := buffers.NewIndexBuffer() ibo := buffers.NewIndexBuffer()
// Initial sizes assuming one submesh that has vertex pos+normals+texCoords, and 3 indices per face // Estimate a useful prealloc capacity based on the first submesh that has vertex pos+normals+tangents+texCoords
var vertexBufData []float32 = make([]float32, 0, len(scene.Meshes[0].Vertices)*3*3*2) 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) 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)) // fmt.Printf("\nMesh %s has %d meshe(s) with first mesh having %d vertices\n", name, len(scene.Meshes), len(scene.Meshes[0].Vertices))
@ -52,12 +85,25 @@ func NewMesh(name, modelPath string, postProcessFlags asig.PostProcess) (*Mesh,
sceneMesh := scene.Meshes[i] sceneMesh := scene.Meshes[i]
// We always want tangents and UV0
if len(sceneMesh.Tangents) == 0 {
sceneMesh.Tangents = make([]gglm.Vec3, len(sceneMesh.Vertices))
}
if len(sceneMesh.TexCoords[0]) == 0 { if len(sceneMesh.TexCoords[0]) == 0 {
sceneMesh.TexCoords[0] = make([]gglm.Vec3, len(sceneMesh.Vertices)) sceneMesh.TexCoords[0] = make([]gglm.Vec3, len(sceneMesh.Vertices))
} }
layoutToUse := []buffers.Element{{ElementType: buffers.DataTypeVec3}, {ElementType: buffers.DataTypeVec3}, {ElementType: buffers.DataTypeVec2}} hasColorSet0 := len(sceneMesh.ColorSets) > 0 && len(sceneMesh.ColorSets[0]) > 0
if 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}) layoutToUse = append(layoutToUse, buffers.Element{ElementType: buffers.DataTypeVec4})
} }
@ -79,8 +125,14 @@ func NewMesh(name, modelPath string, postProcessFlags asig.PostProcess) (*Mesh,
} }
} }
arrs := []arrToInterleave{{V3s: sceneMesh.Vertices}, {V3s: sceneMesh.Normals}, {V2s: v3sToV2s(sceneMesh.TexCoords[0])}} arrs := []arrToInterleave{
if len(sceneMesh.ColorSets) > 0 && len(sceneMesh.ColorSets[0]) > 0 { {V3s: sceneMesh.Vertices},
{V3s: sceneMesh.Normals},
{V3s: sceneMesh.Tangents},
{V2s: v3sToV2s(sceneMesh.TexCoords[0])},
}
if hasColorSet0 {
arrs = append(arrs, arrToInterleave{V4s: sceneMesh.ColorSets[0]}) arrs = append(arrs, arrToInterleave{V4s: sceneMesh.ColorSets[0]})
} }
@ -99,7 +151,7 @@ func NewMesh(name, modelPath string, postProcessFlags asig.PostProcess) (*Mesh,
indexBufData = append(indexBufData, indices...) indexBufData = append(indexBufData, indices...)
} }
vbo.SetData(vertexBufData, buffers.BufUsage_Static) vbo.SetData(vertexBufData, buffers.BufUsage_Static_Draw)
ibo.SetData(indexBufData) ibo.SetData(indexBufData)
mesh.Vao.AddVertexBuffer(vbo) mesh.Vao.AddVertexBuffer(vbo)

View File

@ -12,24 +12,31 @@ import (
var _ renderer.Render = &Rend3DGL{} var _ renderer.Render = &Rend3DGL{}
type Rend3DGL struct { type Rend3DGL struct {
BoundVao *buffers.VertexArray BoundVaoId uint32
BoundMesh *meshes.Mesh BoundMatId uint32
BoundMat *materials.Material BoundMeshVaoId uint32
} }
func (r *Rend3DGL) DrawMesh(mesh *meshes.Mesh, modelMat *gglm.TrMat, mat *materials.Material) { func (r *Rend3DGL) DrawMesh(mesh *meshes.Mesh, modelMat *gglm.TrMat, mat *materials.Material) {
if mesh != r.BoundMesh { if mesh.Vao.Id != r.BoundMeshVaoId {
mesh.Vao.Bind() mesh.Vao.Bind()
r.BoundMesh = mesh r.BoundMeshVaoId = mesh.Vao.Id
} }
if mat != r.BoundMat { if mat.Id != r.BoundMatId {
mat.Bind() mat.Bind()
r.BoundMat = mat r.BoundMatId = mat.Id
} }
mat.SetUnifMat4("modelMat", &modelMat.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++ { 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) gl.DrawElementsBaseVertexWithOffset(gl.TRIANGLES, mesh.SubMeshes[i].IndexCount, gl.UNSIGNED_INT, uintptr(mesh.SubMeshes[i].BaseIndex), mesh.SubMeshes[i].BaseVertex)
@ -38,14 +45,14 @@ func (r *Rend3DGL) DrawMesh(mesh *meshes.Mesh, modelMat *gglm.TrMat, mat *materi
func (r *Rend3DGL) DrawVertexArray(mat *materials.Material, vao *buffers.VertexArray, firstElement int32, elementCount int32) { func (r *Rend3DGL) DrawVertexArray(mat *materials.Material, vao *buffers.VertexArray, firstElement int32, elementCount int32) {
if vao != r.BoundVao { if vao.Id != r.BoundVaoId {
vao.Bind() vao.Bind()
r.BoundVao = vao r.BoundVaoId = vao.Id
} }
if mat != r.BoundMat { if mat.Id != r.BoundMatId {
mat.Bind() mat.Bind()
r.BoundMat = mat r.BoundMatId = mat.Id
} }
gl.DrawArrays(gl.TRIANGLES, firstElement, elementCount) gl.DrawArrays(gl.TRIANGLES, firstElement, elementCount)
@ -53,14 +60,14 @@ func (r *Rend3DGL) DrawVertexArray(mat *materials.Material, vao *buffers.VertexA
func (r *Rend3DGL) DrawCubemap(mesh *meshes.Mesh, mat *materials.Material) { func (r *Rend3DGL) DrawCubemap(mesh *meshes.Mesh, mat *materials.Material) {
if mesh != r.BoundMesh { if mesh.Vao.Id != r.BoundMeshVaoId {
mesh.Vao.Bind() mesh.Vao.Bind()
r.BoundMesh = mesh r.BoundMeshVaoId = mesh.Vao.Id
} }
if mat != r.BoundMat { if mat.Id != r.BoundMatId {
mat.Bind() mat.Bind()
r.BoundMat = mat r.BoundMatId = mat.Id
} }
for i := 0; i < len(mesh.SubMeshes); i++ { for i := 0; i < len(mesh.SubMeshes); i++ {
@ -69,8 +76,9 @@ func (r *Rend3DGL) DrawCubemap(mesh *meshes.Mesh, mat *materials.Material) {
} }
func (r3d *Rend3DGL) FrameEnd() { func (r3d *Rend3DGL) FrameEnd() {
r3d.BoundMesh = nil r3d.BoundVaoId = 0
r3d.BoundMat = nil r3d.BoundMatId = 0
r3d.BoundMeshVaoId = 0
} }
func NewRend3DGL() *Rend3DGL { func NewRend3DGL() *Rend3DGL {

View File

@ -2,22 +2,19 @@
#version 410 #version 410
layout(location=0) in vec3 vertPosIn; layout(location=0) in vec3 vertPosIn;
layout(location=1) in vec3 vertNormalIn; layout(location=2) in vec3 vertTangentIn;
layout(location=2) in vec2 vertUV0In; layout(location=3) in vec2 vertUV0In;
layout(location=3) in vec3 vertColorIn; layout(location=4) in vec3 vertColorIn;
out vec3 vertNormal;
out vec2 vertUV0; out vec2 vertUV0;
out vec3 vertColor; out vec3 vertColor;
out vec3 fragPos; out vec3 fragPos;
//MVP = Model View Projection
uniform mat4 modelMat; uniform mat4 modelMat;
uniform mat4 projViewMat; uniform mat4 projViewMat;
void main() void main()
{ {
vertNormal = mat3(transpose(inverse(modelMat))) * vertNormalIn;
vertUV0 = vertUV0In; vertUV0 = vertUV0In;
vertColor = vertColorIn; vertColor = vertColorIn;
@ -31,7 +28,6 @@ void main()
#version 410 #version 410
in vec3 vertColor; in vec3 vertColor;
in vec3 vertNormal;
in vec2 vertUV0; in vec2 vertUV0;
in vec3 fragPos; in vec3 fragPos;

View File

@ -3,26 +3,19 @@
layout(location=0) in vec3 vertPosIn; layout(location=0) in vec3 vertPosIn;
layout(location=1) in vec3 vertNormalIn; layout(location=1) in vec3 vertNormalIn;
layout(location=2) in vec2 vertUV0In; layout(location=2) in vec3 vertTangentIn;
layout(location=3) in vec3 vertColorIn; layout(location=3) in vec2 vertUV0In;
layout(location=4) in vec3 vertColorIn;
out vec3 vertNormal;
out vec2 vertUV0; out vec2 vertUV0;
out vec3 vertColor; out vec3 vertColor;
out vec3 fragPos; out vec3 fragPos;
//MVP = Model View Projection
uniform mat4 modelMat; uniform mat4 modelMat;
uniform mat4 projViewMat; uniform mat4 projViewMat;
void main() void main()
{ {
// @TODO: Calculate this on the CPU and send it as a uniform
//
// This produces the normal matrix that multiplies with the model normal to produce the
// world space normal. Based on 'One last thing' section from: https://learnopengl.com/Lighting/Basic-Lighting
vertNormal = mat3(transpose(inverse(modelMat))) * vertNormalIn;
vertUV0 = vertUV0In; vertUV0 = vertUV0In;
vertColor = vertColorIn; vertColor = vertColorIn;
@ -41,7 +34,6 @@ struct Material {
uniform Material material; uniform Material material;
in vec3 vertColor; in vec3 vertColor;
in vec3 vertNormal;
in vec2 vertUV0; in vec2 vertUV0;
in vec3 fragPos; in vec3 fragPos;

View File

@ -1,82 +1,39 @@
//shader:vertex //shader:vertex
#version 410 #version 410
#define NUM_SPOT_LIGHTS 4
#define NUM_POINT_LIGHTS 8
//
// Inputs
//
layout(location=0) in vec3 vertPosIn; layout(location=0) in vec3 vertPosIn;
layout(location=1) in vec3 vertNormalIn; layout(location=1) in vec3 vertNormalIn;
layout(location=2) in vec2 vertUV0In; layout(location=2) in vec3 vertTangentIn;
layout(location=3) in vec3 vertColorIn; layout(location=3) in vec2 vertUV0In;
layout(location=4) in vec3 vertColorIn;
uniform mat4 modelMat;
uniform mat4 projViewMat;
uniform mat4 dirLightProjViewMat;
#define NUM_SPOT_LIGHTS 4
uniform mat4 spotLightProjViewMats[NUM_SPOT_LIGHTS];
out vec3 vertNormal;
out vec2 vertUV0;
out vec3 vertColor;
out vec3 fragPos;
out vec4 fragPosDirLight;
out vec4 fragPosSpotLight[NUM_SPOT_LIGHTS];
void main()
{
// @TODO: Calculate this on the CPU and send it as a uniform
//
// This produces the normal matrix that multiplies with the model normal to produce the
// world space normal. Based on 'One last thing' section from: https://learnopengl.com/Lighting/Basic-Lighting
vertNormal = mat3(transpose(inverse(modelMat))) * vertNormalIn;
vertUV0 = vertUV0In;
vertColor = vertColorIn;
vec4 modelVert = modelMat * vec4(vertPosIn, 1);
fragPos = modelVert.xyz;
fragPosDirLight = dirLightProjViewMat * vec4(fragPos, 1);
for (int i = 0; i < NUM_SPOT_LIGHTS; i++)
fragPosSpotLight[i] = spotLightProjViewMats[i] * vec4(fragPos, 1);
gl_Position = projViewMat * modelVert;
}
//shader:fragment
#version 410
struct Material {
sampler2D diffuse;
sampler2D specular;
// sampler2D normal;
sampler2D emission;
float shininess;
};
uniform Material material;
//
// UBOs
//
struct DirLight { struct DirLight {
vec3 dir; vec3 dir;
vec3 diffuseColor; vec3 diffuseColor;
vec3 specularColor; vec3 specularColor;
sampler2D shadowMap;
}; };
uniform sampler2D dirLightShadowMap;
uniform DirLight dirLight;
struct PointLight { struct PointLight {
vec3 pos; vec3 pos;
vec3 diffuseColor; vec3 diffuseColor;
vec3 specularColor; vec3 specularColor;
float constant; float radius;
float linear; float falloff;
float quadratic; float maxBias;
float nearPlane;
float farPlane; float farPlane;
}; };
#define NUM_POINT_LIGHTS 8
uniform PointLight pointLights[NUM_POINT_LIGHTS];
uniform samplerCubeArray pointLightCubeShadowMaps;
struct SpotLight { struct SpotLight {
vec3 pos; vec3 pos;
vec3 dir; vec3 dir;
@ -86,36 +43,182 @@ struct SpotLight {
float outerCutoff; 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;
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()
{
vertUV0 = vertUV0In;
vertColor = vertColorIn;
vec4 modelVert = modelMat * vec4(vertPosIn, 1);
// Tangent-BiTangent-Normal matrix for normal mapping
vec3 T = normalize(vec3(modelMat * vec4(vertTangentIn, 0.0)));
vec3 N = normalize(vec3(modelMat * vec4(vertNormalIn, 0.0)));
// Ensure T is orthogonal with respect to N
T = normalize(T - dot(T, N) * N);
vec3 B = cross(N, T);
mat3 tbnMtx = transpose(mat3(T, B, N));
// Lighting related
fragPos = modelVert.xyz;
fragPosDirLight = vec3(dirLightProjViewMat * vec4(fragPos, 1));
tangentCamPos = tbnMtx * camPos;
tangentFragPos = tbnMtx * fragPos;
tangentDirLightDir = tbnMtx * dirLight.dir;
for (int i = 0; i < NUM_POINT_LIGHTS; i++)
tangentPointLightPositions[i] = tbnMtx * pointLights[i].pos;
for (int i = 0; i < NUM_SPOT_LIGHTS; i++)
{
fragPosSpotLight[i] = spotLightProjViewMats[i] * vec4(fragPos, 1);
tangentSpotLightPositions[i] = tbnMtx * spotLights[i].pos;
tangentSpotLightDirections[i] = tbnMtx * spotLights[i].dir;
}
gl_Position = projViewMat * modelVert;
}
//shader:fragment
#version 410
/*
Note that while all lighting calculations are done in tangent space,
shadow mapping is done in world space.
The exception is the bias calculation. Since the bias relies on the normal
and the normal is in tangent space, we use a tangent space fragment position
with it, but the rest of shadow processing is in world space.
*/
#define NUM_SPOT_LIGHTS 4 #define NUM_SPOT_LIGHTS 4
uniform SpotLight spotLights[NUM_SPOT_LIGHTS]; #define NUM_POINT_LIGHTS 8
uniform sampler2DArray spotLightShadowMaps;
uniform vec3 camPos; //
uniform vec3 ambientColor = vec3(0.2, 0.2, 0.2); // Inputs
//
in vec3 vertColor;
in vec3 vertNormal;
in vec2 vertUV0;
in vec3 fragPos; in vec3 fragPos;
in vec4 fragPosDirLight; in vec2 vertUV0;
in vec3 vertColor;
in vec3 fragPosDirLight;
in vec4 fragPosSpotLight[NUM_SPOT_LIGHTS]; 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; out vec4 fragColor;
//
// Global variables used as cache for lighting calculations // Global variables used as cache for lighting calculations
//
vec3 tangentViewDir;
vec4 diffuseTexColor; vec4 diffuseTexColor;
vec4 specularTexColor; vec4 specularTexColor;
vec4 emissionTexColor; vec4 emissionTexColor;
vec3 normalizedVertNorm; vec3 normalizedVertNorm;
vec3 viewDir;
float CalcDirShadow(sampler2D shadowMap, vec3 lightDir) float CalcDirShadow(sampler2D shadowMap, vec3 tangentLightDir)
{ {
// Move from clip space to NDC
vec3 projCoords = fragPosDirLight.xyz / fragPosDirLight.w;
// Move from [-1,1] to [0, 1] // Move from [-1,1] to [0, 1]
projCoords = projCoords * 0.5 + 0.5; vec3 projCoords = fragPosDirLight * 0.5 + 0.5;
// If sampling outside the depth texture then force 'no shadow' // If sampling outside the depth texture then force 'no shadow'
if(projCoords.z > 1) if(projCoords.z > 1)
@ -126,7 +229,7 @@ float CalcDirShadow(sampler2D shadowMap, vec3 lightDir)
// Bias in the range [0.005, 0.05] depending on the angle, where a higher // 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 // angle gives a higher bias, as shadow acne gets worse with angle
float bias = max(0.05 * (1 - dot(normalizedVertNorm, lightDir)), 0.005); float bias = max(0.05 * (1 - dot(normalizedVertNorm, tangentLightDir)), 0.005);
// 'Percentage Close Filtering'. // 'Percentage Close Filtering'.
// Basically get soft shadows by averaging this texel and surrounding ones // Basically get soft shadows by averaging this texel and surrounding ones
@ -151,70 +254,126 @@ float CalcDirShadow(sampler2D shadowMap, vec3 lightDir)
vec3 CalcDirLight() vec3 CalcDirLight()
{ {
vec3 lightDir = normalize(-dirLight.dir); vec3 lightDir = normalize(-tangentDirLightDir);
// Diffuse // Diffuse
float diffuseAmount = max(0.0, dot(normalizedVertNorm, lightDir)); float diffuseAmount = max(0.0, dot(normalizedVertNorm, lightDir));
vec3 finalDiffuse = diffuseAmount * dirLight.diffuseColor * diffuseTexColor.rgb; vec3 finalDiffuse = diffuseAmount * dirLight.diffuseColor * diffuseTexColor.rgb;
// Specular // Specular
vec3 halfwayDir = normalize(lightDir + viewDir); vec3 halfwayDir = normalize(lightDir + tangentViewDir);
float specularAmount = pow(max(dot(normalizedVertNorm, halfwayDir), 0.0), material.shininess); float specularAmount = pow(max(dot(normalizedVertNorm, halfwayDir), 0.0), material.shininess);
vec3 finalSpecular = specularAmount * dirLight.specularColor * specularTexColor.rgb; vec3 finalSpecular = specularAmount * dirLight.specularColor * specularTexColor.rgb;
// Shadow // Shadow
float shadow = CalcDirShadow(dirLight.shadowMap, lightDir); float shadow = CalcDirShadow(dirLightShadowMap, lightDir);
return (finalDiffuse + finalSpecular) * (1 - shadow); return (finalDiffuse + finalSpecular) * (1 - shadow);
} }
float CalcPointShadow(int lightIndex, vec3 lightPos, vec3 lightDir, float farPlane) { float CalcPointShadow(int lightIndex, vec3 worldLightPos, vec3 tangentLightDir, float maxBias, float nearPlane, float farPlane) {
vec3 lightToFrag = fragPos - lightPos; 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; 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] // We stored depth in the cubemap in the range [0, 1], so now we move back to [0, farPlane]
closestDepth *= farPlane; closestDepth *= farPlane;
// Get depth of current fragment float bias = max(maxBias * (1 - dot(normalizedVertNorm, tangentLightDir)), 0.005);
float currentDepth = length(lightToFrag);
float bias = max(0.05 * (1 - dot(normalizedVertNorm, lightDir)), 0.005); float shadow = currentDepth - bias > closestDepth ? 1 : 0;
float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0;
return shadow; 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) vec3 CalcPointLight(PointLight pointLight, int lightIndex)
{ {
// Ignore unset lights // Ignore inactive lights
if (pointLight.constant == 0){ if (pointLight.radius == 0){
return vec3(0); return vec3(0);
} }
vec3 lightDir = normalize(pointLight.pos - fragPos); vec3 tangentLightPos = tangentPointLightPositions[lightIndex];
vec3 tangentLightDir = normalize(tangentLightPos - tangentFragPos);
// Diffuse // Diffuse
float diffuseAmount = max(0.0, dot(normalizedVertNorm, lightDir)); float diffuseAmount = max(0.0, dot(normalizedVertNorm, tangentLightDir));
vec3 finalDiffuse = diffuseAmount * pointLight.diffuseColor * diffuseTexColor.rgb; vec3 finalDiffuse = diffuseAmount * pointLight.diffuseColor * diffuseTexColor.rgb;
// Specular // Specular
vec3 halfwayDir = normalize(lightDir + viewDir); vec3 halfwayDir = normalize(tangentLightDir + tangentViewDir);
float specularAmount = pow(max(dot(normalizedVertNorm, halfwayDir), 0.0), material.shininess); float specularAmount = pow(max(dot(normalizedVertNorm, halfwayDir), 0.0), material.shininess);
vec3 finalSpecular = specularAmount * pointLight.specularColor * specularTexColor.rgb; vec3 finalSpecular = specularAmount * pointLight.specularColor * specularTexColor.rgb;
// Attenuation // Attenuation
float distToLight = length(pointLight.pos - fragPos); float distToLight = length(tangentLightPos - tangentFragPos);
float attenuation = 1 / (pointLight.constant + pointLight.linear * distToLight + pointLight.quadratic * (distToLight * distToLight)); float attenuation = AttenuateNoCusp(distToLight, pointLight.radius, pointLight.falloff);
// Shadow // Shadow
float shadow = CalcPointShadow(lightIndex, pointLight.pos, lightDir, pointLight.farPlane); float shadow = CalcPointShadow(lightIndex, pointLight.pos, tangentLightDir, pointLight.maxBias, pointLight.nearPlane, pointLight.farPlane);
return (finalDiffuse + finalSpecular) * attenuation * (1 - shadow); return (finalDiffuse + finalSpecular) * attenuation * (1 - shadow);
} }
float CalcSpotShadow(vec3 lightDir, int lightIndex) float CalcSpotShadow(vec3 tangentLightDir, int lightIndex)
{ {
// Move from clip space to NDC // Move from clip space to NDC
vec3 projCoords = fragPosSpotLight[lightIndex].xyz / fragPosSpotLight[lightIndex].w; vec3 projCoords = fragPosSpotLight[lightIndex].xyz / fragPosSpotLight[lightIndex].w;
@ -231,7 +390,7 @@ float CalcSpotShadow(vec3 lightDir, int lightIndex)
// Bias in the range [0.005, 0.05] depending on the angle, where a higher // 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 // angle gives a higher bias, as shadow acne gets worse with angle
float bias = max(0.05 * (1 - dot(normalizedVertNorm, lightDir)), 0.005); float bias = max(0.05 * (1 - dot(normalizedVertNorm, tangentLightDir)), 0.005);
// 'Percentage Close Filtering'. // 'Percentage Close Filtering'.
// Basically get soft shadows by averaging this texel and surrounding ones // Basically get soft shadows by averaging this texel and surrounding ones
@ -256,15 +415,19 @@ float CalcSpotShadow(vec3 lightDir, int lightIndex)
vec3 CalcSpotLight(SpotLight light, int lightIndex) vec3 CalcSpotLight(SpotLight light, int lightIndex)
{ {
if (light.innerCutoff == 0) // 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); return vec3(0);
vec3 fragToLightDir = normalize(light.pos - fragPos); vec3 tangentLightDir = tangentSpotLightDirections[lightIndex];
vec3 fragToLightDir = normalize(tangentSpotLightPositions[lightIndex] - tangentFragPos);
// Spot light cone with full intensity within inner cutoff, // Spot light cone with full intensity within inner cutoff,
// and falloff between inner-outer cutoffs, and zero // and falloff between inner-outer cutoffs, and zero
// light after outer cutoff // light after outer cutoff
float theta = dot(fragToLightDir, normalize(-light.dir)); float theta = dot(fragToLightDir, normalize(-tangentLightDir));
float epsilon = (light.innerCutoff - light.outerCutoff); float epsilon = (light.innerCutoff - light.outerCutoff);
float intensity = clamp((theta - light.outerCutoff) / epsilon, float(0), float(1)); float intensity = clamp((theta - light.outerCutoff) / epsilon, float(0), float(1));
@ -276,7 +439,7 @@ vec3 CalcSpotLight(SpotLight light, int lightIndex)
vec3 finalDiffuse = diffuseAmount * light.diffuseColor * diffuseTexColor.rgb; vec3 finalDiffuse = diffuseAmount * light.diffuseColor * diffuseTexColor.rgb;
// Specular // Specular
vec3 halfwayDir = normalize(fragToLightDir + viewDir); vec3 halfwayDir = normalize(fragToLightDir + tangentViewDir);
float specularAmount = pow(max(dot(normalizedVertNorm, halfwayDir), 0.0), material.shininess); float specularAmount = pow(max(dot(normalizedVertNorm, halfwayDir), 0.0), material.shininess);
vec3 finalSpecular = specularAmount * light.specularColor * specularTexColor.rgb; vec3 finalSpecular = specularAmount * light.specularColor * specularTexColor.rgb;
@ -286,15 +449,21 @@ vec3 CalcSpotLight(SpotLight light, int lightIndex)
return (finalDiffuse + finalSpecular) * intensity * (1 - shadow); return (finalDiffuse + finalSpecular) * intensity * (1 - shadow);
} }
#define DRAW_NORMALS false
void main() void main()
{ {
// Shared values // Shared values
tangentViewDir = normalize(tangentCamPos - tangentFragPos);
diffuseTexColor = texture(material.diffuse, vertUV0); diffuseTexColor = texture(material.diffuse, vertUV0);
specularTexColor = texture(material.specular, vertUV0); specularTexColor = texture(material.specular, vertUV0);
emissionTexColor = texture(material.emission, vertUV0); emissionTexColor = texture(material.emission, vertUV0);
normalizedVertNorm = normalize(vertNormal); // Read normal data encoded [0,1]
viewDir = normalize(camPos - fragPos); normalizedVertNorm = texture(material.normal, vertUV0).rgb;
// Remap normal to [-1,1]
normalizedVertNorm = normalize(normalizedVertNorm * 2.0 - 1.0);
// Light contributions // Light contributions
vec3 finalColor = CalcDirLight(); vec3 finalColor = CalcDirLight();
@ -313,4 +482,9 @@ void main()
vec3 finalAmbient = ambientColor * diffuseTexColor.rgb; vec3 finalAmbient = ambientColor * diffuseTexColor.rgb;
fragColor = vec4(finalColor + finalAmbient + finalEmission, 1); fragColor = vec4(finalColor + finalAmbient + finalEmission, 1);
if (DRAW_NORMALS)
{
fragColor = vec4(texture(material.normal, vertUV0).rgb, 1);
}
} }

View File

@ -3,8 +3,9 @@
layout(location=0) in vec3 vertPosIn; layout(location=0) in vec3 vertPosIn;
layout(location=1) in vec3 vertNormalIn; layout(location=1) in vec3 vertNormalIn;
layout(location=2) in vec2 vertUV0In; layout(location=2) in vec3 vertTangentIn;
layout(location=3) in vec3 vertColorIn; layout(location=3) in vec2 vertUV0In;
layout(location=4) in vec3 vertColorIn;
out vec3 vertUV0; out vec3 vertUV0;

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 B

BIN
res/textures/brickwall-normal.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

BIN
res/textures/brickwall.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 B

View File

@ -1,6 +1,17 @@
package nmageimgui package nmageimgui
import ( 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" imgui "github.com/AllenDang/cimgui-go"
"github.com/bloeys/gglm/gglm" "github.com/bloeys/gglm/gglm"
"github.com/bloeys/nmage/materials" "github.com/bloeys/nmage/materials"
@ -12,7 +23,7 @@ import (
type ImguiInfo struct { type ImguiInfo struct {
ImCtx imgui.Context ImCtx imgui.Context
Mat *materials.Material Mat materials.Material
VaoID uint32 VaoID uint32
VboID uint32 VboID uint32
IndexBufID uint32 IndexBufID uint32
@ -129,7 +140,7 @@ func (i *ImguiInfo) AddFontTTF(fontPath string, fontSize float32, fontConfig *im
fontConfigToUse := imgui.NewFontConfig() fontConfigToUse := imgui.NewFontConfig()
if fontConfig != nil { if fontConfig != nil {
fontConfigToUse = *fontConfig fontConfigToUse = fontConfig
} }
glyphRangesToUse := imgui.NewGlyphRange() glyphRangesToUse := imgui.NewGlyphRange()
@ -141,13 +152,13 @@ func (i *ImguiInfo) AddFontTTF(fontPath string, fontSize float32, fontConfig *im
a := imIO.Fonts() a := imIO.Fonts()
f := a.AddFontFromFileTTFV(fontPath, fontSize, fontConfigToUse, glyphRangesToUse.Data()) f := a.AddFontFromFileTTFV(fontPath, fontSize, fontConfigToUse, glyphRangesToUse.Data())
pixels, width, height, _ := a.GetTextureDataAsAlpha8() pixels, width, height, _ := a.TextureDataAsAlpha8()
gl.ActiveTexture(gl.TEXTURE0) gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, *i.TexID) gl.BindTexture(gl.TEXTURE_2D, *i.TexID)
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, int32(width), int32(height), 0, gl.RED, gl.UNSIGNED_BYTE, pixels) gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, int32(width), int32(height), 0, gl.RED, gl.UNSIGNED_BYTE, pixels)
return f return *f
} }
const DefaultImguiShader = ` const DefaultImguiShader = `
@ -204,7 +215,7 @@ void main()
// If the path is empty a default nMage shader is used // If the path is empty a default nMage shader is used
func NewImGui(shaderPath string) ImguiInfo { func NewImGui(shaderPath string) ImguiInfo {
var imguiMat *materials.Material var imguiMat materials.Material
if shaderPath == "" { if shaderPath == "" {
imguiMat = materials.NewMaterialSrc("ImGUI Mat", []byte(DefaultImguiShader)) imguiMat = materials.NewMaterialSrc("ImGUI Mat", []byte(DefaultImguiShader))
} else { } else {
@ -212,7 +223,7 @@ func NewImGui(shaderPath string) ImguiInfo {
} }
imguiInfo := ImguiInfo{ imguiInfo := ImguiInfo{
ImCtx: imgui.CreateContext(), ImCtx: *imgui.CreateContext(),
Mat: imguiMat, Mat: imguiMat,
TexID: new(uint32), TexID: new(uint32),
} }
@ -233,11 +244,12 @@ func NewImGui(shaderPath string) ImguiInfo {
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.PixelStorei(gl.UNPACK_ROW_LENGTH, 0) 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) gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, int32(width), int32(height), 0, gl.RED, gl.UNSIGNED_BYTE, pixels)
// Store our identifier // Store our identifier
io.Fonts().SetTexID(imgui.TextureID(imguiInfo.TexID))
io.Fonts().SetTexID(imgui.TextureID{Data: uintptr(unsafe.Pointer(imguiInfo.TexID))})
//Shader attributes //Shader attributes
imguiInfo.Mat.Bind() imguiInfo.Mat.Bind()