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
4#nullable enable
5
6using System;
7using System.Collections.Concurrent;
8using System.Collections.Generic;
9using osu.Framework.Graphics.OpenGL;
10using osu.Framework.IO.Stores;
11using osuTK.Graphics.ES30;
12
13namespace osu.Framework.Graphics.Shaders
14{
15 public class ShaderManager : IDisposable
16 {
17 private const string shader_prefix = @"sh_";
18
19 private readonly ConcurrentDictionary<string, ShaderPart> partCache = new ConcurrentDictionary<string, ShaderPart>();
20 private readonly ConcurrentDictionary<(string, string), Shader> shaderCache = new ConcurrentDictionary<(string, string), Shader>();
21
22 private readonly IResourceStore<byte[]> store;
23
24 /// <summary>
25 /// Constructs a new <see cref="ShaderManager"/>.
26 /// </summary>
27 public ShaderManager(IResourceStore<byte[]> store)
28 {
29 this.store = store;
30 }
31
32 /// <summary>
33 /// Retrieves raw shader data from the store.
34 /// Use <see cref="Load"/> to retrieve a usable <see cref="IShader"/> instead.
35 /// </summary>
36 /// <param name="name">The shader name.</param>
37 public virtual byte[]? LoadRaw(string name) => store.Get(name);
38
39 /// <summary>
40 /// Retrieves a usable <see cref="IShader"/> given the vertex and fragment shaders.
41 /// </summary>
42 /// <param name="vertex">The vertex shader name.</param>
43 /// <param name="fragment">The fragment shader name.</param>
44 /// <param name="continuousCompilation"></param>
45 public IShader Load(string vertex, string fragment, bool continuousCompilation = false)
46 {
47 var tuple = (vertex, fragment);
48
49 if (shaderCache.TryGetValue(tuple, out Shader? shader))
50 return shader;
51
52 List<ShaderPart> parts = new List<ShaderPart>
53 {
54 createShaderPart(vertex, ShaderType.VertexShader),
55 createShaderPart(fragment, ShaderType.FragmentShader)
56 };
57
58 return shaderCache[tuple] = CreateShader($"{vertex}/{fragment}", parts);
59 }
60
61 internal virtual Shader CreateShader(string name, List<ShaderPart> parts) => new Shader(name, parts);
62
63 private ShaderPart createShaderPart(string name, ShaderType type, bool bypassCache = false)
64 {
65 name = ensureValidName(name, type);
66
67 if (!bypassCache && partCache.TryGetValue(name, out ShaderPart? part))
68 return part;
69
70 byte[]? rawData = LoadRaw(name);
71
72 part = new ShaderPart(name, rawData, type, this);
73
74 //cache even on failure so we don't try and fail every time.
75 partCache[name] = part;
76 return part;
77 }
78
79 private string ensureValidName(string name, ShaderType type)
80 {
81 string ending = getFileEnding(type);
82
83 if (!name.StartsWith(shader_prefix, StringComparison.Ordinal))
84 name = shader_prefix + name;
85 if (name.EndsWith(ending, StringComparison.Ordinal))
86 return name;
87
88 return name + ending;
89 }
90
91 private string getFileEnding(ShaderType type)
92 {
93 switch (type)
94 {
95 case ShaderType.FragmentShader:
96 return @".fs";
97
98 case ShaderType.VertexShader:
99 return @".vs";
100 }
101
102 return string.Empty;
103 }
104
105 #region IDisposable Support
106
107 private bool isDisposed;
108
109 public void Dispose()
110 {
111 Dispose(true);
112 GC.SuppressFinalize(this);
113 }
114
115 protected virtual void Dispose(bool disposing)
116 {
117 if (!isDisposed)
118 {
119 isDisposed = true;
120
121 store.Dispose();
122
123 GLWrapper.ScheduleDisposal(() =>
124 {
125 foreach (var shader in shaderCache.Values)
126 shader.Dispose();
127
128 foreach (var part in partCache.Values)
129 part.Dispose();
130 });
131 }
132 }
133
134 #endregion
135 }
136
137 public static class VertexShaderDescriptor
138 {
139 public const string TEXTURE_2 = "Texture2D";
140 public const string TEXTURE_3 = "Texture3D";
141 public const string POSITION = "Position";
142 public const string COLOUR = "Colour";
143 }
144
145 public static class FragmentShaderDescriptor
146 {
147 public const string TEXTURE = "Texture";
148 public const string TEXTURE_ROUNDED = "TextureRounded";
149 public const string COLOUR = "Colour";
150 public const string COLOUR_ROUNDED = "ColourRounded";
151 public const string GLOW = "Glow";
152 public const string BLUR = "Blur";
153 public const string VIDEO = "Video";
154 public const string VIDEO_ROUNDED = "VideoRounded";
155 }
156}