/* Copyright (c) 2024-present miku-666 * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1.The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. **/ using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using OpenTK; using OpenTK.Graphics.OpenGL; namespace PckStudio.Rendering.Shader { public sealed class ShaderProgram : IDisposable { private int _programId; private Dictionary locationCache = new Dictionary(); private ShaderProgram(int programId) { _programId = programId; } public void Bind() { GL.UseProgram(_programId); } public void Unbind() { GL.UseProgram(0); } public void Dispose() { Unbind(); GL.DeleteProgram(_programId); } public void SetUniform1(string name, int value) { int location = GetUniformLocation(name); GL.Uniform1(location, value); } public void SetUniform1(string name, float value) { int location = GetUniformLocation(name); GL.Uniform1(location, value); } public void SetUniform2(string name, Size value) => SetUniform2(name, new Vector2(value.Width, value.Height)); public void SetUniform2(string name, Vector2 value) { int location = GetUniformLocation(name); GL.Uniform2(location, value); } public void SetUniform4(string name, Vector4 value) { int location = GetUniformLocation(name); GL.Uniform4(location, value); } public void SetUniform4(string name, Color color) { int location = GetUniformLocation(name); GL.Uniform4(location, color); } public void SetUniformMat4(string name, ref Matrix4 matrix) { int location = GetUniformLocation(name); GL.UniformMatrix4(location, false, ref matrix); } private int GetUniformLocation(string name) { if (locationCache.ContainsKey(name)) return locationCache[name]; Debug.Assert(false, $"Uniform location '{name}' not found"); return -1; } private bool Link() { GL.LinkProgram(_programId); GL.GetProgram(_programId, GetProgramParameterName.LinkStatus, out int status); bool success = status != 0; if (!success) Debug.WriteLine(GL.GetProgramInfoLog(_programId), category: nameof(ShaderProgram)); GetActiveUniformLocations(); return success; } private void GetActiveUniformLocations() { GL.GetProgram(_programId, GetProgramParameterName.ActiveUniforms, out int count); Debug.WriteLine("Active Uniforms: {0}", count); for (int i = 0; i < count; i++) { GL.GetActiveUniform(_programId, i, 256, out int length, out int size, out ActiveUniformType type, out string name); int id = GL.GetUniformLocation(_programId, name); Debug.Assert(id != -1); RegisterUniform(name, id, type); Debug.WriteLine("Uniform {0}(id:{1}) Type: {2} Name: {3}", i, id, type, name); } } private void RegisterUniform(string name, int id, ActiveUniformType type) => locationCache.Add(name, id); public bool Validate() { #if DEBUG GL.ValidateProgram(_programId); GL.GetProgram(_programId, GetProgramParameterName.ValidateStatus, out int status); bool success = status != 0; if (!success) Debug.WriteLine(GL.GetProgramInfoLog(_programId), category: nameof(ShaderProgram)); return success; #else return true; #endif } public static ShaderProgram Create(string vertexSource, string fragmentSource) { return Create( new ShaderSource(ShaderType.VertexShader, vertexSource), new ShaderSource(ShaderType.FragmentShader, fragmentSource) ); } public static ShaderProgram Create(params ShaderSource[] shaderSources) { int programId = GL.CreateProgram(); var shaderObjects = new List(shaderSources.Length); foreach (ShaderSource shaderSource in shaderSources) { ShaderObject shaderObject = ShaderObject.Create(shaderSource); shaderObject.AttachToProgram(programId); shaderObjects.Add(shaderObject); } var shader = new ShaderProgram(programId); bool success = shader.Link(); Debug.Assert(success, "Shader Program linking failed."); foreach (ShaderObject shaderObject in shaderObjects) { shaderObject.Delete(); } return shader; } } }