181 lines
5.4 KiB
C#
181 lines
5.4 KiB
C#
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<string, int> _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<T>(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}"); |