A game framework written with osu! in mind.
at master 181 lines 5.9 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 System.IO; 7using System.Text.RegularExpressions; 8using osu.Framework.Graphics.OpenGL; 9using osuTK.Graphics.ES30; 10 11namespace osu.Framework.Graphics.Shaders 12{ 13 internal class ShaderPart : IDisposable 14 { 15 internal const string SHADER_ATTRIBUTE_PATTERN = "^\\s*(?>attribute|in)\\s+(?:(?:lowp|mediump|highp)\\s+)?\\w+\\s+(\\w+)"; 16 17 internal List<ShaderInputInfo> ShaderInputs = new List<ShaderInputInfo>(); 18 19 internal string Name; 20 internal bool HasCode; 21 internal bool Compiled; 22 23 internal ShaderType Type; 24 25 private bool isVertexShader => Type == ShaderType.VertexShader || Type == ShaderType.VertexShaderArb; 26 27 private int partID = -1; 28 29 private int lastShaderInputIndex; 30 31 private readonly List<string> shaderCodes = new List<string>(); 32 33 private readonly Regex includeRegex = new Regex("^\\s*#\\s*include\\s+[\"<](.*)[\">]"); 34 private readonly Regex shaderInputRegex = new Regex(SHADER_ATTRIBUTE_PATTERN); 35 36 private readonly ShaderManager manager; 37 38 internal ShaderPart(string name, byte[] data, ShaderType type, ShaderManager manager) 39 { 40 Name = name; 41 Type = type; 42 43 this.manager = manager; 44 45 shaderCodes.Add(loadFile(data, true)); 46 shaderCodes.RemoveAll(string.IsNullOrEmpty); 47 48 if (shaderCodes.Count == 0) 49 return; 50 51 HasCode = true; 52 } 53 54 private string loadFile(byte[] bytes, bool mainFile) 55 { 56 if (bytes == null) 57 return null; 58 59 using (MemoryStream ms = new MemoryStream(bytes)) 60 using (StreamReader sr = new StreamReader(ms)) 61 { 62 string code = string.Empty; 63 64 while (sr.Peek() != -1) 65 { 66 string line = sr.ReadLine(); 67 68 if (string.IsNullOrEmpty(line)) 69 continue; 70 71 if (line.StartsWith("#version", StringComparison.Ordinal)) // the version directive has to appear before anything else in the shader 72 { 73 shaderCodes.Add(line); 74 continue; 75 } 76 77 Match includeMatch = includeRegex.Match(line); 78 79 if (includeMatch.Success) 80 { 81 string includeName = includeMatch.Groups[1].Value.Trim(); 82 83 //#if DEBUG 84 // byte[] rawData = null; 85 // if (File.Exists(includeName)) 86 // rawData = File.ReadAllBytes(includeName); 87 //#endif 88 code += loadFile(manager.LoadRaw(includeName), false) + '\n'; 89 } 90 else 91 code += line + '\n'; 92 93 if (Type == ShaderType.VertexShader || Type == ShaderType.VertexShaderArb) 94 { 95 Match inputMatch = shaderInputRegex.Match(line); 96 97 if (inputMatch.Success) 98 { 99 ShaderInputs.Add(new ShaderInputInfo 100 { 101 Location = lastShaderInputIndex++, 102 Name = inputMatch.Groups[1].Value.Trim() 103 }); 104 } 105 } 106 } 107 108 if (mainFile) 109 { 110 code = loadFile(manager.LoadRaw("sh_Precision_Internal.h"), false) + "\n" + code; 111 112 if (isVertexShader) 113 { 114 string realMainName = "real_main_" + Guid.NewGuid().ToString("N"); 115 116 string backbufferCode = loadFile(manager.LoadRaw("sh_Backbuffer_Internal.h"), false); 117 118 backbufferCode = backbufferCode.Replace("{{ real_main }}", realMainName); 119 code = Regex.Replace(code, @"void main\((.*)\)", $"void {realMainName}()") + backbufferCode + '\n'; 120 } 121 } 122 123 return code; 124 } 125 } 126 127 internal bool Compile() 128 { 129 if (!HasCode) 130 return false; 131 132 if (partID == -1) 133 partID = GL.CreateShader(Type); 134 135 int[] codeLengths = new int[shaderCodes.Count]; 136 for (int i = 0; i < shaderCodes.Count; i++) 137 codeLengths[i] = shaderCodes[i].Length; 138 139 GL.ShaderSource(this, shaderCodes.Count, shaderCodes.ToArray(), codeLengths); 140 GL.CompileShader(this); 141 142 GL.GetShader(this, ShaderParameter.CompileStatus, out int compileResult); 143 Compiled = compileResult == 1; 144 145 if (!Compiled) 146 throw new Shader.PartCompilationFailedException(Name, GL.GetShaderInfoLog(this)); 147 148 return Compiled; 149 } 150 151 public static implicit operator int(ShaderPart program) => program.partID; 152 153 #region IDisposable Support 154 155 protected internal bool IsDisposed { get; private set; } 156 157 ~ShaderPart() 158 { 159 GLWrapper.ScheduleDisposal(() => Dispose(false)); 160 } 161 162 public void Dispose() 163 { 164 Dispose(true); 165 GC.SuppressFinalize(this); 166 } 167 168 protected virtual void Dispose(bool disposing) 169 { 170 if (!IsDisposed) 171 { 172 IsDisposed = true; 173 174 if (partID != -1) 175 GL.DeleteShader(this); 176 } 177 } 178 179 #endregion 180 } 181}