.
This commit is contained in:
181
Engine/Renderer/Shader/ShaderProgram.cs
Normal file
181
Engine/Renderer/Shader/ShaderProgram.cs
Normal file
@@ -0,0 +1,181 @@
|
||||
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}");
|
||||
Reference in New Issue
Block a user