A game framework written with osu! in mind.
at master 266 lines 8.3 kB view raw
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2// See the LICENCE file in the repository root for full licence text. 3 4using System; 5using System.Collections.Generic; 6using osu.Framework.Graphics.OpenGL; 7using osu.Framework.Threading; 8using osuTK; 9using osuTK.Graphics.ES30; 10using static osu.Framework.Threading.ScheduledDelegate; 11 12namespace osu.Framework.Graphics.Shaders 13{ 14 public class Shader : IShader, IDisposable 15 { 16 private readonly string name; 17 private readonly List<ShaderPart> parts; 18 19 private readonly ScheduledDelegate shaderCompileDelegate; 20 21 internal readonly Dictionary<string, IUniform> Uniforms = new Dictionary<string, IUniform>(); 22 23 /// <summary> 24 /// Holds all the <see cref="Uniforms"/> values for faster access than iterating on <see cref="Dictionary{TKey,TValue}.Values"/>. 25 /// </summary> 26 private IUniform[] uniformsValues; 27 28 public bool IsLoaded { get; private set; } 29 30 internal bool IsBound { get; private set; } 31 32 private int programID = -1; 33 34 internal Shader(string name, List<ShaderPart> parts) 35 { 36 this.name = name; 37 this.parts = parts; 38 39 GLWrapper.ScheduleExpensiveOperation(shaderCompileDelegate = new ScheduledDelegate(compile)); 40 } 41 42 private void compile() 43 { 44 if (IsDisposed) 45 throw new ObjectDisposedException(ToString(), "Can not compile a disposed shader."); 46 47 if (IsLoaded) 48 throw new InvalidOperationException("Attempting to compile an already-compiled shader."); 49 50 parts.RemoveAll(p => p == null); 51 if (parts.Count == 0) 52 return; 53 54 programID = CreateProgram(); 55 56 if (!CompileInternal()) 57 throw new ProgramLinkingFailedException(name, GetProgramLog()); 58 59 IsLoaded = true; 60 61 SetupUniforms(); 62 63 GlobalPropertyManager.Register(this); 64 } 65 66 internal void EnsureShaderCompiled() 67 { 68 if (IsDisposed) 69 throw new ObjectDisposedException(ToString(), "Can not compile a disposed shader."); 70 71 if (shaderCompileDelegate.State == RunState.Waiting) 72 shaderCompileDelegate.RunTask(); 73 } 74 75 public void Bind() 76 { 77 if (IsDisposed) 78 throw new ObjectDisposedException(ToString(), "Can not bind a disposed shader."); 79 80 if (IsBound) 81 return; 82 83 EnsureShaderCompiled(); 84 85 GLWrapper.UseProgram(this); 86 87 foreach (var uniform in uniformsValues) 88 uniform?.Update(); 89 90 IsBound = true; 91 } 92 93 public void Unbind() 94 { 95 if (!IsBound) 96 return; 97 98 GLWrapper.UseProgram(null); 99 100 IsBound = false; 101 } 102 103 /// <summary> 104 /// Returns a uniform from the shader. 105 /// </summary> 106 /// <param name="name">The name of the uniform.</param> 107 /// <returns>Returns a base uniform.</returns> 108 public Uniform<T> GetUniform<T>(string name) 109 where T : struct, IEquatable<T> 110 { 111 if (IsDisposed) 112 throw new ObjectDisposedException(ToString(), "Can not retrieve uniforms from a disposed shader."); 113 114 EnsureShaderCompiled(); 115 116 return (Uniform<T>)Uniforms[name]; 117 } 118 119 private protected virtual bool CompileInternal() 120 { 121 foreach (ShaderPart p in parts) 122 { 123 if (!p.Compiled) p.Compile(); 124 GL.AttachShader(this, p); 125 126 foreach (ShaderInputInfo input in p.ShaderInputs) 127 GL.BindAttribLocation(this, input.Location, input.Name); 128 } 129 130 GL.LinkProgram(this); 131 GL.GetProgram(this, GetProgramParameterName.LinkStatus, out int linkResult); 132 133 foreach (var part in parts) 134 GL.DetachShader(this, part); 135 136 return linkResult == 1; 137 } 138 139 private protected virtual void SetupUniforms() 140 { 141 GL.GetProgram(this, GetProgramParameterName.ActiveUniforms, out int uniformCount); 142 143 uniformsValues = new IUniform[uniformCount]; 144 145 for (int i = 0; i < uniformCount; i++) 146 { 147 GL.GetActiveUniform(this, i, 100, out _, out _, out ActiveUniformType type, out string uniformName); 148 149 IUniform uniform; 150 151 switch (type) 152 { 153 case ActiveUniformType.Bool: 154 uniform = createUniform<bool>(uniformName); 155 break; 156 157 case ActiveUniformType.Float: 158 uniform = createUniform<float>(uniformName); 159 break; 160 161 case ActiveUniformType.Int: 162 uniform = createUniform<int>(uniformName); 163 break; 164 165 case ActiveUniformType.FloatMat3: 166 uniform = createUniform<Matrix3>(uniformName); 167 break; 168 169 case ActiveUniformType.FloatMat4: 170 uniform = createUniform<Matrix4>(uniformName); 171 break; 172 173 case ActiveUniformType.FloatVec2: 174 uniform = createUniform<Vector2>(uniformName); 175 break; 176 177 case ActiveUniformType.FloatVec3: 178 uniform = createUniform<Vector3>(uniformName); 179 break; 180 181 case ActiveUniformType.FloatVec4: 182 uniform = createUniform<Vector4>(uniformName); 183 break; 184 185 case ActiveUniformType.Sampler2D: 186 uniform = createUniform<int>(uniformName); 187 break; 188 189 default: 190 continue; 191 } 192 193 Uniforms.Add(uniformName, uniform); 194 uniformsValues[i] = uniform; 195 } 196 197 IUniform createUniform<T>(string name) 198 where T : struct, IEquatable<T> 199 { 200 int location = GL.GetUniformLocation(this, name); 201 202 if (GlobalPropertyManager.CheckGlobalExists(name)) return new GlobalUniform<T>(this, name, location); 203 204 return new Uniform<T>(this, name, location); 205 } 206 } 207 208 private protected virtual string GetProgramLog() => GL.GetProgramInfoLog(this); 209 210 private protected virtual int CreateProgram() => GL.CreateProgram(); 211 212 private protected virtual void DeleteProgram(int id) => GL.DeleteProgram(id); 213 214 public override string ToString() => $@"{name} Shader (Compiled: {programID != -1})"; 215 216 public static implicit operator int(Shader shader) => shader.programID; 217 218 #region IDisposable Support 219 220 protected internal bool IsDisposed { get; private set; } 221 222 ~Shader() 223 { 224 GLWrapper.ScheduleDisposal(() => Dispose(false)); 225 } 226 227 public void Dispose() 228 { 229 Dispose(true); 230 GC.SuppressFinalize(this); 231 } 232 233 protected virtual void Dispose(bool disposing) 234 { 235 if (!IsDisposed) 236 { 237 IsDisposed = true; 238 239 shaderCompileDelegate?.Cancel(); 240 241 GlobalPropertyManager.Unregister(this); 242 243 if (programID != -1) 244 DeleteProgram(this); 245 } 246 } 247 248 #endregion 249 250 public class PartCompilationFailedException : Exception 251 { 252 public PartCompilationFailedException(string partName, string log) 253 : base($"A {typeof(ShaderPart)} failed to compile: {partName}:\n{log.Trim()}") 254 { 255 } 256 } 257 258 public class ProgramLinkingFailedException : Exception 259 { 260 public ProgramLinkingFailedException(string programName, string log) 261 : base($"A {typeof(Shader)} failed to link: {programName}:\n{log.Trim()}") 262 { 263 } 264 } 265 } 266}