200 lines
5.4 KiB
Go
200 lines
5.4 KiB
Go
package shaderManager
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/go-gl/gl/v3.3-core/gl"
|
|
)
|
|
|
|
// Shader represents a compiled GLSL program and its associated data.
|
|
type Shader struct {
|
|
Name string
|
|
ProgramID uint32
|
|
Render func() // This will be used in the future.
|
|
|
|
// Cached uniform locations (looked up once after linking)
|
|
LocMVPMatrix int32
|
|
LocTexture int32
|
|
LocTextColor int32
|
|
LocColor int32
|
|
}
|
|
|
|
// ShaderManager handles the loading, compiling, and management of GLSL shader programs.
|
|
type ShaderManager struct {
|
|
shaders map[string]*Shader
|
|
}
|
|
|
|
var (
|
|
instance *ShaderManager
|
|
once sync.Once
|
|
)
|
|
|
|
// InitShaderManager initializes the ShaderManager singleton.
|
|
func InitShaderManager(shaderDir string) error {
|
|
var err error
|
|
once.Do(func() {
|
|
sm := &ShaderManager{
|
|
shaders: make(map[string]*Shader),
|
|
}
|
|
if e := sm.LoadShaders(shaderDir); e != nil {
|
|
err = fmt.Errorf("could not load shaders from %s: %w", shaderDir, e)
|
|
return
|
|
}
|
|
instance = sm
|
|
})
|
|
return err
|
|
}
|
|
|
|
// Get returns the singleton ShaderManager instance.
|
|
func Get() *ShaderManager {
|
|
if instance == nil {
|
|
panic("ShaderManager has not been initialized. Call InitShaderManager at application startup.")
|
|
}
|
|
return instance
|
|
}
|
|
|
|
// Get retrieves a compiled Shader object by its name.
|
|
func (sm *ShaderManager) Get(name string) (*Shader, bool) {
|
|
shader, ok := sm.shaders[name]
|
|
return shader, ok
|
|
}
|
|
|
|
// ListShaders returns all shaders.
|
|
func (sm *ShaderManager) ListShaders() map[string]*Shader {
|
|
return sm.shaders
|
|
}
|
|
|
|
// Close deletes all loaded shader programs from the GPU to free up resources.
|
|
func (sm *ShaderManager) Close() {
|
|
for name, shader := range sm.shaders {
|
|
gl.DeleteProgram(shader.ProgramID)
|
|
delete(sm.shaders, name)
|
|
}
|
|
}
|
|
|
|
// LoadShaders scans a directory for .vert and .frag files and creates a shader program for each pair.
|
|
func (sm *ShaderManager) LoadShaders(shaderDir string) error {
|
|
// Get assets directory from database
|
|
shaderFiles := make(map[string]string)
|
|
|
|
err := filepath.Walk(shaderDir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !info.IsDir() && strings.HasSuffix(info.Name(), ".vert") {
|
|
name := strings.TrimSuffix(info.Name(), ".vert")
|
|
shaderFiles[name] = path
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("error walking shader directory: %w", err)
|
|
}
|
|
|
|
for name, vertPath := range shaderFiles {
|
|
fragPath := strings.Replace(vertPath, ".vert", ".frag", 1)
|
|
if _, err := os.Stat(fragPath); os.IsNotExist(err) {
|
|
continue
|
|
}
|
|
|
|
programID, err := createShaderProgram(vertPath, fragPath)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
newShader := &Shader{
|
|
Name: name,
|
|
ProgramID: programID,
|
|
Render: nil, // Render function is not yet assigned.
|
|
}
|
|
newShader.cacheUniformLocations()
|
|
sm.shaders[name] = newShader
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// cacheUniformLocations looks up common uniform locations once and stores them on the Shader.
|
|
func (s *Shader) cacheUniformLocations() {
|
|
s.LocMVPMatrix = gl.GetUniformLocation(s.ProgramID, gl.Str("u_mvpMatrix\x00"))
|
|
s.LocTexture = gl.GetUniformLocation(s.ProgramID, gl.Str("u_texture\x00"))
|
|
s.LocTextColor = gl.GetUniformLocation(s.ProgramID, gl.Str("u_textColor\x00"))
|
|
s.LocColor = gl.GetUniformLocation(s.ProgramID, gl.Str("u_color\x00"))
|
|
}
|
|
|
|
// createShaderProgram reads, compiles, and links a vertex and fragment shader from file paths.
|
|
func createShaderProgram(vertexPath, fragmentPath string) (uint32, error) {
|
|
vertexSource, err := os.ReadFile(vertexPath)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("reading vertex shader %s: %w", vertexPath, err)
|
|
}
|
|
fragmentSource, err := os.ReadFile(fragmentPath)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("reading fragment shader %s: %w", fragmentPath, err)
|
|
}
|
|
|
|
vertexShader, err := compileShader(string(vertexSource)+"\x00", gl.VERTEX_SHADER)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer gl.DeleteShader(vertexShader)
|
|
|
|
fragmentShader, err := compileShader(string(fragmentSource)+"\x00", gl.FRAGMENT_SHADER)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer gl.DeleteShader(fragmentShader)
|
|
|
|
program := gl.CreateProgram()
|
|
gl.AttachShader(program, vertexShader)
|
|
gl.AttachShader(program, fragmentShader)
|
|
gl.LinkProgram(program)
|
|
|
|
var status int32
|
|
gl.GetProgramiv(program, gl.LINK_STATUS, &status)
|
|
if status == gl.FALSE {
|
|
var logLength int32
|
|
gl.GetProgramiv(program, gl.INFO_LOG_LENGTH, &logLength)
|
|
log := strings.Repeat("\x00", int(logLength+1))
|
|
gl.GetProgramInfoLog(program, logLength, nil, gl.Str(log))
|
|
return 0, fmt.Errorf("failed to link program: %v", log)
|
|
}
|
|
|
|
return program, nil
|
|
}
|
|
|
|
// compileShader is a helper function that compiles a single shader source string.
|
|
func compileShader(source string, shaderType uint32) (uint32, error) {
|
|
shader := gl.CreateShader(shaderType)
|
|
csources, free := gl.Strs(source)
|
|
gl.ShaderSource(shader, 1, csources, nil)
|
|
free()
|
|
gl.CompileShader(shader)
|
|
|
|
var status int32
|
|
gl.GetShaderiv(shader, gl.COMPILE_STATUS, &status)
|
|
if status == gl.FALSE {
|
|
var logLength int32
|
|
gl.GetShaderiv(shader, gl.INFO_LOG_LENGTH, &logLength)
|
|
log := strings.Repeat("\x00", int(logLength+1))
|
|
gl.GetShaderInfoLog(shader, logLength, nil, gl.Str(log))
|
|
|
|
var shaderTypeName string
|
|
switch shaderType {
|
|
case gl.VERTEX_SHADER:
|
|
shaderTypeName = "Vertex"
|
|
case gl.FRAGMENT_SHADER:
|
|
shaderTypeName = "Fragment"
|
|
default:
|
|
shaderTypeName = "Unknown"
|
|
}
|
|
return 0, fmt.Errorf("failed to compile %s shader: %v", shaderTypeName, log)
|
|
}
|
|
|
|
return shader, nil
|
|
}
|