A game framework written with osu! in mind.
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}