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

Fix gamma blending in new renderers #5713

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 6 additions & 5 deletions osu.Framework/Graphics/OpenGL/Batches/GLVertexBatch.cs
Expand Up @@ -105,21 +105,22 @@ public void Add(T v)

public int Draw()
{
if (currentVertexIndex == 0)
int count = currentVertexIndex;
currentVertexIndex = 0;

if (count == 0)
return 0;

GLVertexBuffer<T> vertexBuffer = currentVertexBuffer;
if (changeBeginIndex >= 0)
vertexBuffer.UpdateRange(changeBeginIndex, changeEndIndex);

vertexBuffer.DrawRange(0, currentVertexIndex);

int count = currentVertexIndex;
vertexBuffer.DrawRange(0, count);

// When using multiple buffers we advance to the next one with every draw to prevent contention on the same buffer with future vertex updates.
//TODO: let us know if we exceed and roll over to zero here.
currentBufferIndex = (currentBufferIndex + 1) % maxBuffers;
currentVertexIndex = 0;
count = 0;
changeBeginIndex = -1;

FrameStatistics.Increment(StatisticsCounterType.DrawCalls);
Expand Down
2 changes: 2 additions & 0 deletions osu.Framework/Graphics/OpenGL/Buffers/GLFrameBuffer.cs
Expand Up @@ -126,6 +126,8 @@ protected virtual void Dispose(bool disposing)

private class FrameBufferTexture : GLTexture
{
public override bool IsFrameBufferTexture => true;

public FrameBufferTexture(GLRenderer renderer, All filteringMode = All.Linear)
: base(renderer, 1, 1, true, filteringMode)
{
Expand Down
3 changes: 1 addition & 2 deletions osu.Framework/Graphics/OpenGL/Buffers/GLVertexBuffer.cs
Expand Up @@ -131,8 +131,7 @@ public void DrawRange(int startIndex, int endIndex)
{
Bind(true);

int countVertices = endIndex - startIndex;
GL.DrawElements(Type, ToElements(countVertices), DrawElementsType.UnsignedShort, (IntPtr)(ToElementIndex(startIndex) * sizeof(ushort)));
Renderer.DrawVertices(Type, ToElementIndex(startIndex), ToElements(endIndex - startIndex));
}

public void Update()
Expand Down
37 changes: 28 additions & 9 deletions osu.Framework/Graphics/OpenGL/GLRenderer.cs
Expand Up @@ -50,8 +50,7 @@ protected internal override bool VerticalSync

private int backbufferFramebuffer;

protected override bool GammaCorrection => base.GammaCorrection || !IsEmbedded;

private readonly GLTexture?[] lastBoundTextures = new GLTexture?[16];
private readonly int[] lastBoundBuffers = new int[2];

private bool? lastBlendingEnabledState;
Expand All @@ -76,12 +75,6 @@ protected override void Initialise(IGraphicsSurface graphicsSurface)
GL.Disable(EnableCap.StencilTest);
GL.Enable(EnableCap.Blend);

// For whatever reason, changing this value breaks colour rendering inside BufferedContainers only on android.
// We're going to eventually have to figure out why this is the case, but for now this workaround fixes the issue very locally.
// See https://github.com/ppy/osu-framework/issues/5694.
if (RuntimeInfo.OS != RuntimeInfo.Platform.Android)
GL.Disable((EnableCap)36281); // GL_FRAMEBUFFER_SRGB

Logger.Log($@"GL Initialized
GL Version: {GL.GetString(StringName.Version)}
GL Renderer: {GL.GetString(StringName.Renderer)}
Expand Down Expand Up @@ -111,6 +104,7 @@ protected internal override void BeginFrame(Vector2 windowSize)
lastBlendingEnabledState = null;
lastBoundBuffers.AsSpan().Clear();
lastBoundVertexArray = 0;
lastBoundTextures.AsSpan().Clear();

// Seems to be required on some drivers as the context is lost from the draw thread.
MakeCurrent();
Expand Down Expand Up @@ -154,6 +148,22 @@ public bool BindBuffer(BufferTarget target, int buffer)
return true;
}

public void DrawVertices(PrimitiveType type, int indexStart, int indicesCount)
{
for (int i = 0; i < lastBoundTextures.Length; i++)
{
if (lastBoundTextures[i] == null)
continue;

((GLShader)Shader!).SetAuxTextureData(i, new AuxTextureData
{
IsFrameBufferTexture = lastBoundTextures[i]!.IsFrameBufferTexture
});
}

GL.DrawElements(type, indicesCount, DrawElementsType.UnsignedShort, (IntPtr)(indexStart * sizeof(ushort)));
}

protected override void SetShaderImplementation(IShader shader) => GL.UseProgram((GLShader)shader);

protected override void SetUniformImplementation<T>(IUniformWithValue<T> uniform)
Expand Down Expand Up @@ -200,6 +210,8 @@ protected override void SetUniformImplementation<T>(IUniformWithValue<T> uniform

protected override bool SetTextureImplementation(INativeTexture? texture, int unit)
{
lastBoundTextures[unit] = texture as GLTexture;

if (texture == null)
{
GL.ActiveTexture(TextureUnit.Texture0 + unit);
Expand Down Expand Up @@ -233,9 +245,16 @@ protected override bool SetTextureImplementation(INativeTexture? texture, int un
return true;
}

protected override void SetFrameBufferImplementation(IFrameBuffer? frameBuffer) =>
protected override void SetFrameBufferImplementation(IFrameBuffer? frameBuffer)
{
GL.BindFramebuffer(FramebufferTarget.Framebuffer, ((GLFrameBuffer?)frameBuffer)?.FrameBuffer ?? backbufferFramebuffer);

if (frameBuffer == null)
GL.Disable((EnableCap)36281); // GL_FRAMEBUFFER_SRGB
else
GL.Enable((EnableCap)36281); // GL_FRAMEBUFFER_SRGB
}

/// <summary>
/// Deletes a frame buffer.
/// </summary>
Expand Down
31 changes: 30 additions & 1 deletion osu.Framework/Graphics/OpenGL/Shaders/GLShader.cs
Expand Up @@ -7,6 +7,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using osu.Framework.Graphics.OpenGL.Buffers;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Shaders;
using osu.Framework.Threading;
Expand All @@ -32,6 +33,8 @@ internal class GLShader : IShader

private readonly Dictionary<string, GLUniformBlock> uniformBlocks = new Dictionary<string, GLUniformBlock>();
private readonly List<Uniform<int>> textureUniforms = new List<Uniform<int>>();
private readonly GLUniformBlock[] textureAuxDataBlocks = new GLUniformBlock[16];
private readonly GLUniformBuffer<AuxTextureData>[] textureAuxDataBuffers = new GLUniformBuffer<AuxTextureData>[16];

/// <summary>
/// Holds all <see cref="uniformBlocks"/> values for faster access than iterating on <see cref="Dictionary{TKey,TValue}.Values"/>.
Expand Down Expand Up @@ -125,6 +128,9 @@ public void Bind()
foreach (var block in uniformBlocksValues)
block?.Bind();

foreach (var block in textureAuxDataBlocks)
block?.Bind();

IsBound = true;
}

Expand All @@ -149,6 +155,14 @@ public Uniform<T> GetUniform<T>(string name)
return (Uniform<T>)Uniforms[name];
}

public void SetAuxTextureData(int index, AuxTextureData data)
{
if (textureAuxDataBuffers[index] == null)
return;

textureAuxDataBuffers[index].Data = data;
}

public virtual void BindUniformBlock(string blockName, IUniformBuffer buffer)
{
if (IsDisposed)
Expand Down Expand Up @@ -187,10 +201,22 @@ private protected virtual bool CompileInternal()
if (layout.Elements.Any(e => e.Kind == ResourceKind.TextureReadOnly || e.Kind == ResourceKind.TextureReadWrite))
{
var textureElement = layout.Elements.First(e => e.Kind == ResourceKind.TextureReadOnly || e.Kind == ResourceKind.TextureReadWrite);

textureUniforms.Add(new Uniform<int>(renderer, this, textureElement.Name, GL.GetUniformLocation(this, textureElement.Name))
{
Value = textureIndex++
Value = textureIndex
});

if (layout.Elements.Any(e => e.Kind == ResourceKind.UniformBuffer))
{
var auxDataBuffer = layout.Elements.FirstOrDefault(e => e.Kind == ResourceKind.UniformBuffer);
var block = new GLUniformBlock(renderer, this, GL.GetUniformBlockIndex(this, auxDataBuffer.Name), blockBindingIndex++);
textureAuxDataBlocks[textureIndex] = block;
textureAuxDataBuffers[textureIndex] = new GLUniformBuffer<AuxTextureData>(renderer);
textureAuxDataBlocks[textureIndex].Assign(textureAuxDataBuffers[textureIndex]);
}

textureIndex++;
}
else if (layout.Elements[0].Kind == ResourceKind.UniformBuffer)
{
Expand Down Expand Up @@ -238,6 +264,9 @@ protected virtual void Dispose(bool disposing)

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

foreach (var buf in textureAuxDataBuffers)
buf?.Dispose();
}
}

Expand Down
11 changes: 10 additions & 1 deletion osu.Framework/Graphics/OpenGL/Shaders/GLShaderPart.cs
Expand Up @@ -59,7 +59,10 @@ public GLShaderPart(IRenderer renderer, string name, byte[] data, ShaderType typ
// After this transformation, the g_GlobalUniforms block is placed in set 0 and all other user blocks begin from 1.
// The difference in implementation here (compared to above) is intentional, as uniform blocks must be consistent between the shader stages, so they can't be easily appended.
for (int i = 0; i < shaderCodes.Count; i++)
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}");
{
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)
Expand Down Expand Up @@ -115,6 +118,12 @@ private string loadFile(byte[] bytes, bool mainFile)
{
string internalIncludes = loadFile(manager.LoadRaw("Internal/sh_Compatibility.h"), false) + "\n";
internalIncludes += loadFile(manager.LoadRaw("Internal/sh_GlobalUniforms.h"), false) + "\n";

if (Type == ShaderType.VertexShader)
internalIncludes += loadFile(manager.LoadRaw("Internal/sh_Vertex_Utils.h"), false) + "\n";
else
internalIncludes += loadFile(manager.LoadRaw("Internal/sh_Fragment_Utils.h"), false) + "\n";

code = internalIncludes + code;

if (Type == ShaderType.VertexShader)
Expand Down
2 changes: 2 additions & 0 deletions osu.Framework/Graphics/OpenGL/Textures/GLTexture.cs
Expand Up @@ -221,6 +221,8 @@ public bool UploadComplete
/// </summary>
public bool IsQueuedForUpload { get; set; }

public virtual bool IsFrameBufferTexture => false;

private bool tryGetNextUpload(out ITextureUpload upload)
{
lock (uploadQueue)
Expand Down
15 changes: 15 additions & 0 deletions osu.Framework/Graphics/Rendering/AuxTextureData.cs
@@ -0,0 +1,15 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System.Runtime.InteropServices;
using osu.Framework.Graphics.Shaders.Types;

namespace osu.Framework.Graphics.Rendering
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public record struct AuxTextureData
{
public UniformBool IsFrameBufferTexture;
private readonly UniformPadding12 pad1;
}
}
10 changes: 3 additions & 7 deletions osu.Framework/Graphics/Rendering/GlobalUniformData.cs
Expand Up @@ -10,9 +10,10 @@ namespace osu.Framework.Graphics.Rendering
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public record struct GlobalUniformData
{
public UniformBool GammaCorrection;
public UniformBool BackbufferDraw;
private readonly UniformPadding8 pad1;
public UniformBool IsDepthRangeZeroToOne;
public UniformBool IsClipSpaceYInverted;
public UniformBool IsUvOriginTopLeft;

public UniformMatrix4 ProjMatrix;
public UniformMatrix3 ToMaskingSpace;
Expand All @@ -33,10 +34,5 @@ public record struct GlobalUniformData
public UniformFloat InnerCornerRadius;
public UniformInt WrapModeS;
public UniformInt WrapModeT;

public UniformBool IsDepthRangeZeroToOne;
public UniformBool IsClipSpaceYInverted;
public UniformBool IsUvOriginTopLeft;
private readonly UniformPadding4 pad4;
}
}
9 changes: 1 addition & 8 deletions osu.Framework/Graphics/Rendering/Renderer.cs
Expand Up @@ -69,12 +69,6 @@ public abstract class Renderer : IRenderer
public bool UsingBackbuffer => frameBufferStack.Count == 0;
public Texture WhitePixel => whitePixel.Value;

/// <summary>
/// Whether this renderer should apply gamma correction (toSRGB/toLinear) functions in fragment shaders.
/// By default, this is only applied to the main framebuffer (i.e. "backbuffer").
/// </summary>
protected virtual bool GammaCorrection => UsingBackbuffer;

public bool IsInitialised { get; private set; }

protected ClearInfo CurrentClearInfo { get; private set; }
Expand Down Expand Up @@ -942,8 +936,7 @@ private void setFrameBuffer(IFrameBuffer? frameBuffer, bool force = false)

globalUniformBuffer!.Data = globalUniformBuffer.Data with
{
BackbufferDraw = UsingBackbuffer,
GammaCorrection = GammaCorrection,
BackbufferDraw = UsingBackbuffer
};

FrameBuffer = frameBuffer;
Expand Down
2 changes: 2 additions & 0 deletions osu.Framework/Graphics/Veldrid/Buffers/VeldridFrameBuffer.cs
Expand Up @@ -135,6 +135,8 @@ private class FrameBufferTexture : VeldridTexture
{
protected override TextureUsage Usages => base.Usages | TextureUsage.RenderTarget;

protected override bool IsFrameBufferTexture => true;

public FrameBufferTexture(VeldridRenderer renderer, SamplerFilter filteringMode = SamplerFilter.MinLinear_MagLinear_MipLinear)
: base(renderer, 1, 1, true, filteringMode)
{
Expand Down
29 changes: 3 additions & 26 deletions osu.Framework/Graphics/Veldrid/Shaders/VeldridShader.cs
Expand Up @@ -169,34 +169,11 @@ private void compile()

if (layout.Elements.Any(e => e.Kind == ResourceKind.TextureReadOnly || e.Kind == ResourceKind.TextureReadWrite))
{
// Todo: We should enforce that a texture set contains both a texture and a sampler.
var textureElement = layout.Elements.First(e => e.Kind == ResourceKind.TextureReadOnly || e.Kind == ResourceKind.TextureReadWrite);
var samplerElement = layout.Elements.First(e => e.Kind == ResourceKind.Sampler);

textureLayouts.Add(new VeldridUniformLayout(
set,
renderer.Factory.CreateResourceLayout(
new ResourceLayoutDescription(
new ResourceLayoutElementDescription(
textureElement.Name,
ResourceKind.TextureReadOnly,
ShaderStages.Fragment),
new ResourceLayoutElementDescription(
samplerElement.Name,
ResourceKind.Sampler,
ShaderStages.Fragment)))));
bool hasAuxData = layout.Elements.Any(e => e.Kind == ResourceKind.UniformBuffer);
textureLayouts.Add(new VeldridTextureUniformLayout(set, renderer.Factory.CreateResourceLayout(layout), hasAuxData));
}
else if (layout.Elements[0].Kind == ResourceKind.UniformBuffer)
{
uniformLayouts[layout.Elements[0].Name] = new VeldridUniformLayout(
set,
renderer.Factory.CreateResourceLayout(
new ResourceLayoutDescription(
new ResourceLayoutElementDescription(
layout.Elements[0].Name,
ResourceKind.UniformBuffer,
ShaderStages.Fragment | ShaderStages.Vertex))));
}
uniformLayouts[layout.Elements[0].Name] = new VeldridUniformLayout(set, renderer.Factory.CreateResourceLayout(layout));
}
}
catch (SpirvCompilationException e)
Expand Down
6 changes: 6 additions & 0 deletions osu.Framework/Graphics/Veldrid/Shaders/VeldridShaderPart.cs
Expand Up @@ -107,6 +107,12 @@ private string loadFile(byte[]? bytes, bool mainFile)
{
string internalIncludes = loadFile(manager.LoadRaw("Internal/sh_Compatibility.h"), false) + "\n";
internalIncludes += loadFile(manager.LoadRaw("Internal/sh_GlobalUniforms.h"), false) + "\n";

if (Type == ShaderPartType.Vertex)
internalIncludes += loadFile(manager.LoadRaw("Internal/sh_Vertex_Utils.h"), false) + "\n";
else
internalIncludes += loadFile(manager.LoadRaw("Internal/sh_Fragment_Utils.h"), false) + "\n";

code = internalIncludes + code;

if (Type == ShaderPartType.Vertex)
Expand Down
@@ -0,0 +1,18 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using Veldrid;

namespace osu.Framework.Graphics.Veldrid.Shaders
{
internal class VeldridTextureUniformLayout : VeldridUniformLayout
{
public readonly bool HasAuxData;

public VeldridTextureUniformLayout(int set, ResourceLayout layout, bool hasAuxData)
: base(set, layout)
{
HasAuxData = hasAuxData;
}
}
}
4 changes: 3 additions & 1 deletion osu.Framework/Graphics/Veldrid/Textures/VeldridTexture.cs
Expand Up @@ -321,9 +321,11 @@ protected virtual void DoUpload(ITextureUpload upload)
sampler = Renderer.Factory.CreateSampler(ref samplerDescription);
}

resources = new VeldridTextureResources(texture, sampler);
resources = new VeldridTextureResources(texture, sampler, IsFrameBufferTexture);
}

protected virtual bool IsFrameBufferTexture => false;

private unsafe void initialiseLevel(Texture texture, int level, int width, int height)
{
using (var image = createBackingImage(width, height))
Expand Down