Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add disk cache for shader compilations #5829

Merged
merged 7 commits into from
Jun 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions osu.Framework.Tests/Shaders/TestSceneShaderDisposal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,13 @@ public void TestShadersLoseReferencesOnManagerDisposal()

private class TestGLRenderer : GLRenderer
{
protected override IShader CreateShader(string name, IShaderPart[] parts, IUniformBuffer<GlobalUniformData> globalUniformBuffer)
=> new TestGLShader(this, name, parts.Cast<GLShaderPart>().ToArray());
protected override IShader CreateShader(string name, IShaderPart[] parts, IUniformBuffer<GlobalUniformData> globalUniformBuffer, ShaderCompilationStore compilationStore)
=> new TestGLShader(this, name, parts.Cast<GLShaderPart>().ToArray(), globalUniformBuffer, compilationStore);

private class TestGLShader : GLShader
{
internal TestGLShader(GLRenderer renderer, string name, GLShaderPart[] parts)
: base(renderer, name, parts, null)
internal TestGLShader(GLRenderer renderer, string name, GLShaderPart[] parts, IUniformBuffer<GlobalUniformData> globalUniformBuffer, ShaderCompilationStore compilationStore)
: base(renderer, name, parts, globalUniformBuffer, compilationStore)
{
}

Expand Down
8 changes: 4 additions & 4 deletions osu.Framework.Tests/Shaders/TestShaderLoading.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ public void TestFetchExistentShader()

private class TestGLRenderer : GLRenderer
{
protected override IShader CreateShader(string name, IShaderPart[] parts, IUniformBuffer<GlobalUniformData> globalUniformBuffer)
=> new TestGLShader(this, name, parts.Cast<GLShaderPart>().ToArray());
protected override IShader CreateShader(string name, IShaderPart[] parts, IUniformBuffer<GlobalUniformData> globalUniformBuffer, ShaderCompilationStore compilationStore)
=> new TestGLShader(this, name, parts.Cast<GLShaderPart>().ToArray(), globalUniformBuffer, compilationStore);

private class TestGLShader : GLShader
{
internal TestGLShader(GLRenderer renderer, string name, GLShaderPart[] parts)
: base(renderer, name, parts, null)
internal TestGLShader(GLRenderer renderer, string name, GLShaderPart[] parts, IUniformBuffer<GlobalUniformData> globalUniformBuffer, ShaderCompilationStore compilationStore)
: base(renderer, name, parts, globalUniformBuffer, compilationStore)
{
}

Expand Down
4 changes: 2 additions & 2 deletions osu.Framework/Graphics/OpenGL/GLRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -376,8 +376,8 @@ protected override IShaderPart CreateShaderPart(IShaderStore store, string name,
return new GLShaderPart(this, name, rawData, glType, store);
}

protected override IShader CreateShader(string name, IShaderPart[] parts, IUniformBuffer<GlobalUniformData> globalUniformBuffer)
=> new GLShader(this, name, parts.Cast<GLShaderPart>().ToArray(), globalUniformBuffer);
protected override IShader CreateShader(string name, IShaderPart[] parts, IUniformBuffer<GlobalUniformData> globalUniformBuffer, ShaderCompilationStore compilationStore)
=> new GLShader(this, name, parts.Cast<GLShaderPart>().ToArray(), globalUniformBuffer, compilationStore);

public override IFrameBuffer CreateFrameBuffer(RenderBufferFormat[]? renderBufferFormats = null, TextureFilteringMode filteringMode = TextureFilteringMode.Linear)
{
Expand Down
37 changes: 18 additions & 19 deletions osu.Framework/Graphics/OpenGL/Shaders/GLShader.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

#nullable disable

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics.OpenGL.Buffers;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Shaders;
Expand Down Expand Up @@ -42,14 +40,14 @@ internal class GLShader : IShader

private readonly GLShaderPart vertexPart;
private readonly GLShaderPart fragmentPart;
private readonly VertexFragmentCompilationResult crossCompileResult;
private readonly VertexFragmentShaderCompilation compilation;

internal GLShader(GLRenderer renderer, string name, GLShaderPart[] parts, IUniformBuffer<GlobalUniformData> globalUniformBuffer)
internal GLShader(GLRenderer renderer, string name, GLShaderPart[] parts, IUniformBuffer<GlobalUniformData> globalUniformBuffer, ShaderCompilationStore compilationStore)
{
this.renderer = renderer;
this.name = name;
this.globalUniformBuffer = globalUniformBuffer;
this.parts = parts.Where(p => p != null).ToArray();
this.parts = parts;

vertexPart = parts.Single(p => p.Type == ShaderType.VertexShader);
fragmentPart = parts.Single(p => p.Type == ShaderType.FragmentShader);
Expand All @@ -59,9 +57,9 @@ internal GLShader(GLRenderer renderer, string name, GLShaderPart[] parts, IUnifo
try
{
// Shaders are in "Vulkan GLSL" format. They need to be cross-compiled to GLSL.
crossCompileResult = SpirvCompilation.CompileVertexFragment(
Encoding.UTF8.GetBytes(vertexPart.GetRawText()),
Encoding.UTF8.GetBytes(fragmentPart.GetRawText()),
compilation = compilationStore.CompileVertexFragment(
vertexPart.GetRawText(),
fragmentPart.GetRawText(),
renderer.IsEmbedded ? CrossCompileTarget.ESSL : CrossCompileTarget.GLSL);
}
catch (Exception e)
Expand Down Expand Up @@ -158,8 +156,8 @@ public virtual void BindUniformBlock(string blockName, IUniformBuffer buffer)

private protected virtual bool CompileInternal()
{
vertexPart.Compile(crossCompileResult.VertexShader);
fragmentPart.Compile(crossCompileResult.FragmentShader);
vertexPart.Compile(compilation.VertexText);
fragmentPart.Compile(compilation.FragmentText);

foreach (GLShaderPart p in parts)
GL.AttachShader(this, p);
Expand All @@ -176,7 +174,7 @@ private protected virtual bool CompileInternal()
int blockBindingIndex = 0;
int textureIndex = 0;

foreach (ResourceLayoutDescription layout in crossCompileResult.Reflection.ResourceLayouts)
foreach (ResourceLayoutDescription layout in compilation.Reflection.ResourceLayouts)
{
if (layout.Elements.Length == 0)
continue;
Expand Down Expand Up @@ -226,15 +224,16 @@ public void Dispose()

protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
IsDisposed = true;
if (IsDisposed)
return;

shaderCompileDelegate?.Cancel();
IsDisposed = true;

if (programID != -1)
DeleteProgram(this);
}
if (shaderCompileDelegate.IsNotNull())
shaderCompileDelegate.Cancel();

if (programID != -1)
DeleteProgram(this);
}

#endregion
Expand Down
16 changes: 7 additions & 9 deletions osu.Framework/Graphics/OpenGL/Shaders/GLShaderPart.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

#nullable disable

using System;
using System.Collections.Generic;
using System.Globalization;
Expand Down Expand Up @@ -31,7 +29,7 @@ internal class GLShaderPart : IShaderPart

private int partID = -1;

public GLShaderPart(IRenderer renderer, string name, byte[] data, ShaderType type, IShaderStore store)
public GLShaderPart(IRenderer renderer, string name, byte[]? data, ShaderType type, IShaderStore store)
{
this.renderer = renderer;
this.store = store;
Expand Down Expand Up @@ -62,10 +60,10 @@ public GLShaderPart(IRenderer renderer, string name, byte[] data, ShaderType typ
shaderCodes[i] = uniform_pattern.Replace(shaderCodes[i], match => $"{match.Groups[1].Value}set = {int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture) + 1}{match.Groups[3].Value}");
}

private string loadFile(byte[] bytes, bool mainFile)
private string loadFile(byte[]? bytes, bool mainFile)
{
if (bytes == null)
return null;
return string.Empty;

using (MemoryStream ms = new MemoryStream(bytes))
using (StreamReader sr = new StreamReader(ms))
Expand All @@ -74,7 +72,7 @@ private string loadFile(byte[] bytes, bool mainFile)

while (sr.Peek() != -1)
{
string line = sr.ReadLine();
string? line = sr.ReadLine();

if (string.IsNullOrEmpty(line))
{
Expand Down Expand Up @@ -123,10 +121,10 @@ private string loadFile(byte[] bytes, bool mainFile)

if (!string.IsNullOrEmpty(backbufferCode))
{
string realMainName = "real_main_" + Guid.NewGuid().ToString("N");
const string real_main_name = "__internal_real_main";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this main name change significant in any way? I'm not sure what the guid was doing, and blame doesn't help, since it was added in, of all things, b9406dd, which just barely predates me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's significant by keeping the shader code deterministic. The guid was only present to avoid naming collisions.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, right, good point. I'm assuming the naming collisions you mention are no longer relevant. Makes sense 👍

Copy link
Contributor Author

@smoogipoo smoogipoo Jun 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__internal_ should be as effective as guid, and if not then we could suffix an incrementing value like __internal_real_main_1, but I doubt that'll happen.

One case I can realistically think of is if we end up wanting multiple main trampolines, but there's no support for that right now.


backbufferCode = backbufferCode.Replace("{{ real_main }}", realMainName);
code = Regex.Replace(code, @"void main\((.*)\)", $"void {realMainName}()") + backbufferCode + '\n';
backbufferCode = backbufferCode.Replace("{{ real_main }}", real_main_name);
code = Regex.Replace(code, @"void main\((.*)\)", $"void {real_main_name}()") + backbufferCode + '\n';
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions osu.Framework/Graphics/Rendering/Dummy/DummyRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ public DummyRenderer()

bool IRenderer.AllowTearing { get; set; }

Storage? IRenderer.CacheStorage { set { } }

void IRenderer.Initialise(IGraphicsSurface graphicsSurface)
{
IsInitialised = true;
Expand Down
5 changes: 5 additions & 0 deletions osu.Framework/Graphics/Rendering/IRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ public interface IRenderer

protected internal bool AllowTearing { get; set; }

/// <summary>
/// A <see cref="Storage"/> that can be used to cache objects.
/// </summary>
protected internal Storage? CacheStorage { set; }

/// <summary>
/// The maximum allowed texture size.
/// </summary>
Expand Down
18 changes: 15 additions & 3 deletions osu.Framework/Graphics/Rendering/Renderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ public abstract class Renderer : IRenderer
protected internal abstract bool VerticalSync { get; set; }
protected internal abstract bool AllowTearing { get; set; }

protected internal Storage? CacheStorage
{
set => shaderCompilationStore.CacheStorage = value;
}

public int MaxTextureSize { get; protected set; } = 4096; // default value is to allow roughly normal flow in cases we don't have graphics context, like headless CI.

public int MaxTexturesUploadedPerFrame { get; set; } = 32;
Expand Down Expand Up @@ -94,6 +99,8 @@ public abstract class Renderer : IRenderer
/// </summary>
protected IShader? Shader { get; private set; }

private readonly ShaderCompilationStore shaderCompilationStore = new ShaderCompilationStore();

private readonly GlobalStatistic<int> statExpensiveOperationsQueued;
private readonly GlobalStatistic<int> statTextureUploadsQueued;
private readonly GlobalStatistic<int> statTextureUploadsDequeued;
Expand Down Expand Up @@ -1036,7 +1043,7 @@ internal void SetUniform<T>(IUniformWithValue<T> uniform)
protected abstract IShaderPart CreateShaderPart(IShaderStore store, string name, byte[]? rawData, ShaderPartType partType);

/// <inheritdoc cref="IRenderer.CreateShader"/>
protected abstract IShader CreateShader(string name, IShaderPart[] parts, IUniformBuffer<GlobalUniformData> globalUniformBuffer);
protected abstract IShader CreateShader(string name, IShaderPart[] parts, IUniformBuffer<GlobalUniformData> globalUniformBuffer, ShaderCompilationStore compilationStore);

private IShader? mipmapShader;

Expand All @@ -1056,7 +1063,7 @@ internal IShader GetMipmapShader()
{
CreateShaderPart(store, "mipmap.vs", store.GetRawData("sh_mipmap.vs"), ShaderPartType.Vertex),
CreateShaderPart(store, "mipmap.fs", store.GetRawData("sh_mipmap.fs"), ShaderPartType.Fragment),
}, globalUniformBuffer.AsNonNull());
}, globalUniformBuffer.AsNonNull(), shaderCompilationStore);

return mipmapShader;
}
Expand Down Expand Up @@ -1128,6 +1135,11 @@ bool IRenderer.AllowTearing
set => AllowTearing = value;
}

Storage? IRenderer.CacheStorage
{
set => CacheStorage = value;
}

IVertexBatch<TexturedVertex2D> IRenderer.DefaultQuadBatch => DefaultQuadBatch;
void IRenderer.BeginFrame(Vector2 windowSize) => BeginFrame(windowSize);
void IRenderer.FinishFrame() => FinishFrame();
Expand All @@ -1143,7 +1155,7 @@ bool IRenderer.AllowTearing
void IRenderer.PopQuadBatch() => PopQuadBatch();
Image<Rgba32> IRenderer.TakeScreenshot() => TakeScreenshot();
IShaderPart IRenderer.CreateShaderPart(IShaderStore store, string name, byte[]? rawData, ShaderPartType partType) => CreateShaderPart(store, name, rawData, partType);
IShader IRenderer.CreateShader(string name, IShaderPart[] parts) => CreateShader(name, parts, globalUniformBuffer!);
IShader IRenderer.CreateShader(string name, IShaderPart[] parts) => CreateShader(name, parts, globalUniformBuffer!, shaderCompilationStore);

IVertexBatch<TVertex> IRenderer.CreateLinearBatch<TVertex>(int size, int maxBuffers, PrimitiveTopology topology)
{
Expand Down
30 changes: 30 additions & 0 deletions osu.Framework/Graphics/Shaders/ComputeProgramCompilation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using Veldrid.SPIRV;

namespace osu.Framework.Graphics.Shaders
{
public class ComputeProgramCompilation
{
/// <summary>
/// Whether this compilation was retrieved from cache.
/// </summary>
public bool WasCached { get; set; }

/// <summary>
/// The SpirV bytes for the program.
/// </summary>
public byte[] ProgramBytes { get; set; } = null!;

/// <summary>
/// The cross-compiled program text.
/// </summary>
public string ProgramText { get; set; } = null!;

/// <summary>
/// A reflection of the shader program, describing the layout of resources.
/// </summary>
public SpirvReflection Reflection { get; set; } = null!;
}
}
Loading