using OpenTK.Graphics.OpenGL4; using OpenTK.Mathematics; namespace VoxelEngine.Rendering; public sealed class Shader : IDisposable { private readonly int _handle; private readonly Dictionary _uniforms = new(); public Shader(string vertexPath, string fragmentPath) { int vertex = CompileFromFile(ShaderType.VertexShader, vertexPath); int fragment = CompileFromFile(ShaderType.FragmentShader, fragmentPath); _handle = GL.CreateProgram(); GL.AttachShader(_handle, vertex); GL.AttachShader(_handle, fragment); GL.LinkProgram(_handle); GL.GetProgram(_handle, GetProgramParameterName.LinkStatus, out int linked); if (linked == 0) throw new InvalidOperationException($"Program link failed: {GL.GetProgramInfoLog(_handle)}"); GL.DetachShader(_handle, vertex); GL.DetachShader(_handle, fragment); GL.DeleteShader(vertex); GL.DeleteShader(fragment); } public void Use() => GL.UseProgram(_handle); public void SetMatrix4(string name, Matrix4 value) => GL.UniformMatrix4(Location(name), true, ref value); public void SetInt(string name, int value) => GL.Uniform1(Location(name), value); private int Location(string name) { if (!_uniforms.TryGetValue(name, out int location)) { location = GL.GetUniformLocation(_handle, name); _uniforms[name] = location; } return location; } private static int CompileFromFile(ShaderType type, string path) { string source = File.ReadAllText(path); int shader = GL.CreateShader(type); GL.ShaderSource(shader, source); GL.CompileShader(shader); GL.GetShader(shader, ShaderParameter.CompileStatus, out int compiled); if (compiled == 0) throw new InvalidOperationException($"{type} compile failed ({path}): {GL.GetShaderInfoLog(shader)}"); return shader; } public void Dispose() => GL.DeleteProgram(_handle); }