using System.Runtime.CompilerServices; using System.Text; using OpenTK.Graphics.OpenGL; using OpenTK.Mathematics; using Serilog; namespace Engine.Renderer.Shader; public class ShaderProgram : OpenGlObject { private readonly Dictionary _uniforms = new(); public static ShaderProgram CreateFromSource(string source) { var vertexSource = new StringBuilder(); var fragmentSource = new StringBuilder(); var inFragment = false; var inVertex = false; foreach (var line in source.Split('\n')) { if (line.StartsWith("#shader vertex")) { inVertex = true; inFragment = false; } else if (line.StartsWith("#shader fragment")) { inVertex = false; inFragment = true; } else if (inVertex) { vertexSource.AppendLine(line); } else if (inFragment) { fragmentSource.AppendLine(line); } } return new ShaderProgram(vertexSource.ToString(), fragmentSource.ToString()); } public ShaderProgram(string vertexSource, string fragmentSource) { var vertexShader = CompileSource(vertexSource, ShaderType.VertexShader); var fragmentShader = CompileSource(fragmentSource, ShaderType.FragmentShader); Handle = LinkProgram(vertexShader, fragmentShader); } public void SetUniform(string name, T value, [CallerMemberName] string caller = "") { try { var location = GetUniformLocation(name); switch (value) { case bool boolValue: GL.ProgramUniform1(Handle, location, boolValue ? 1 : 0); break; case int intValue: GL.ProgramUniform1(Handle, location, intValue); break; case float floatValue: GL.ProgramUniform1(Handle, location, floatValue); break; case Vector2 vec2: GL.ProgramUniform2(Handle, location, vec2); break; case Vector3 vec3: GL.ProgramUniform3(Handle, location, vec3); break; case Vector4 vec4: GL.ProgramUniform4(Handle, location, vec4); break; case Matrix4 matrix: GL.ProgramUniformMatrix4(Handle, location, false, ref matrix); break; default: throw new ArgumentException($"Unsupported uniform type: {typeof(T).Name}"); } } catch (Exception ex) { Log.Error(ex, "Failed to set uniform {UniformName} from {Caller}", name, caller); throw; } } internal override void Bind() { GL.UseProgram(Handle); } internal override void Unbind() { GL.UseProgram(0); } protected override void Destroy() { GL.DeleteProgram(Handle); } private int GetUniformLocation(string name) { if (_uniforms.TryGetValue(name, out var location)) return location; location = GL.GetUniformLocation(Handle, name); if (location < 0) throw new ArgumentException($"Uniform '{name}' not found in shader program"); _uniforms.Add(name, location); return location; } private static int CompileSource(string source, ShaderType type) { var shaderId = GL.CreateShader(type); GL.ShaderSource(shaderId, source); GL.CompileShader(shaderId); GL.GetShader(shaderId, ShaderParameter.CompileStatus, out var status); if (status == 0) { var log = GL.GetShaderInfoLog(shaderId); GL.DeleteShader(shaderId); throw new ShaderCompilationException(type, log); } return shaderId; } private static int LinkProgram(int vertexShader, int fragmentShader) { var programId = GL.CreateProgram(); try { GL.AttachShader(programId, vertexShader); GL.AttachShader(programId, fragmentShader); GL.LinkProgram(programId); GL.GetProgram(programId, GetProgramParameterName.LinkStatus, out var linkStatus); if (linkStatus == 0) { var log = GL.GetProgramInfoLog(programId); throw new ShaderLinkException(log); } GL.ValidateProgram(programId); GL.GetProgram(programId, GetProgramParameterName.ValidateStatus, out var validateStatus); if (validateStatus == 0) { var log = GL.GetProgramInfoLog(programId); throw new ShaderValidationException(log); } } finally { GL.DeleteShader(vertexShader); GL.DeleteShader(fragmentShader); } return programId; } } public class ShaderCompilationException(ShaderType type, string message) : Exception($"Failed to compile {type} shader: {message}") { public ShaderType ShaderType { get; } = type; } public class ShaderLinkException(string message) : Exception($"Failed to link shader program: {message}"); public class ShaderValidationException(string message) : Exception($"Failed to validate shader program: {message}");