Add support for shaders

This commit is contained in:
Ritchie Frodomar 2024-07-08 22:47:23 -04:00
parent 301289554d
commit 99a4b7e289
16 changed files with 308 additions and 53 deletions

43
mgfxc-wine-setup.sh Executable file
View file

@ -0,0 +1,43 @@
#!/bin/bash
# This script is used to setup the needed Wine environment
# so that mgfxc can be run on Linux / macOS systems.
# check dependencies
if ! type "wine64" > /dev/null 2>&1
then
echo "wine64 not found"
exit 1
fi
if ! type "7z" > /dev/null 2>&1
then
echo "7z not found"
exit 1
fi
# init wine stuff
export WINEARCH=win64
export WINEPREFIX=$HOME/.winemonogame
wine64 wineboot
TEMP_DIR="${TMPDIR:-/tmp}"
SCRIPT_DIR="$TEMP_DIR/winemg2"
mkdir -p "$SCRIPT_DIR"
# get dotnet
DOTNET_URL="https://download.visualstudio.microsoft.com/download/pr/adeab8b1-1c44-41b2-b12a-156442f307e9/65ebf805366410c63edeb06e53959383/dotnet-sdk-3.1.201-win-x64.zip"
curl $DOTNET_URL --output "$SCRIPT_DIR/dotnet-sdk.zip"
7z x "$SCRIPT_DIR/dotnet-sdk.zip" -o"$WINEPREFIX/drive_c/windows/system32/"
# get d3dcompiler_47
FIREFOX_URL="https://download-installer.cdn.mozilla.net/pub/firefox/releases/62.0.3/win64/ach/Firefox%20Setup%2062.0.3.exe"
curl $FIREFOX_URL --output "$SCRIPT_DIR/firefox.exe"
7z x "$SCRIPT_DIR/firefox.exe" -o"$SCRIPT_DIR/firefox_data/"
cp -f "$SCRIPT_DIR/firefox_data/core/d3dcompiler_47.dll" "$WINEPREFIX/drive_c/windows/system32/d3dcompiler_47.dll"
# append MGFXC_WINE_PATH env variable
echo "export MGFXC_WINE_PATH=$HOME/.winemonogame" >> ~/.profile
echo "export MGFXC_WINE_PATH=$HOME/.winemonogame" >> ~/.zprofile
# cleanup
rm -rf "$SCRIPT_DIR"

View file

@ -0,0 +1,9 @@
namespace AcidicGUI.Effects;
public interface IEffect
{
int PassesCount { get; }
void Use(int pass);
void UpdateOpacity(float opacity);
}

View file

@ -0,0 +1,13 @@
using AcidicGUI.Rendering;
using AcidicGUI.Widgets;
namespace AcidicGUI.Effects;
public interface IWidgetEffect : IEffect
{
void UpdateParameters(Widget widget, GuiRenderer renderer);
void BeforeRebuildGeometry(GeometryHelper geometry);
void AfterRebuildGeometry(GeometryHelper geometry);
}

View file

@ -253,7 +253,7 @@ public sealed class GuiManager : IFontFamilyProvider
if (previous == current)
{
if (widgetBeingDragged == null)
if (widgetBeingDragged == null || current == ButtonState.Released)
return;
Bubble<IDragHandler, MouseButtonEvent>(widgetBeingDragged, e, x => x.OnDrag);

View file

@ -1,4 +1,5 @@
using System.Buffers;
using AcidicGUI.Effects;
using AcidicGUI.Layout;
using AcidicGUI.Rendering;
using AcidicGUI.TextRendering;
@ -13,7 +14,13 @@ public interface IGuiContext
GraphicsDevice GraphicsDevice { get; }
void Render(VertexPositionColorTexture[] vertices, int[] indices, Texture2D? texture, LayoutRect? clipRect = null);
void Render(VertexBuffer vertices, IndexBuffer indices, int offset, int primitiveCount, Texture2D? texture, LayoutRect? clipRect = null);
void Render(VertexBuffer vertices, IndexBuffer indices, int offset, int primitiveCount, Texture2D? texture, LayoutRect? clipRect = null, IEffect? effectOverride = null, float opacity = 1);
/// <summary>
/// Renders the user interface to the given RenderTarget2D. Useful for certain widget rendering techniques such as background blurs.
/// </summary>
/// <param name="destination"></param>
void Grab(RenderTarget2D destination);
IFontFamily GetFallbackFont();
}

View file

@ -7,21 +7,19 @@ namespace AcidicGUI.Rendering;
public class GeometryHelper : IFontStashRenderer2
{
private readonly GuiMeshBuilder whiteMesh;
private readonly GuiMeshBuilder whiteMesh;
private readonly Dictionary<Texture2D, GuiMeshBuilder> meshes = new();
private readonly GuiRenderer guiRenderer;
private readonly bool desaturate;
private readonly float opacity;
private readonly LayoutRect? clipRect;
private readonly GuiRenderer guiRenderer;
private readonly bool desaturate;
private readonly LayoutRect? clipRect;
internal GeometryHelper(GuiRenderer guiRenderer, float opacity, bool desaturate, LayoutRect? clipRect)
internal GeometryHelper(GuiRenderer guiRenderer, bool desaturate, LayoutRect? clipRect)
{
this.opacity = opacity;
this.desaturate = desaturate;
this.guiRenderer = guiRenderer;
this.clipRect = clipRect;
whiteMesh = new GuiMeshBuilder(null, guiRenderer.GetVertexCount(null), guiRenderer.Layer, opacity, desaturate);
whiteMesh = new GuiMeshBuilder(null, guiRenderer.GetVertexCount(null), guiRenderer.Layer, desaturate);
}
public GuiMesh ExportMesh()
@ -47,7 +45,7 @@ public class GeometryHelper : IFontStashRenderer2
if (!meshes.TryGetValue(texture, out GuiMeshBuilder? builder))
{
builder = new GuiMeshBuilder(texture, guiRenderer.GetVertexCount(texture), guiRenderer.Layer, opacity, desaturate);
builder = new GuiMeshBuilder(texture, guiRenderer.GetVertexCount(texture), guiRenderer.Layer, desaturate);
meshes.Add(texture, builder);
}

View file

@ -8,15 +8,13 @@ public sealed class GuiMeshBuilder
private readonly List<VertexPositionColorTexture> vertices = new();
private readonly List<int> indices = new();
private readonly Texture2D? texture;
private readonly float opacity;
private readonly bool desaturate;
private readonly int baseVertex;
private readonly float layer;
public GuiMeshBuilder(Texture2D? texture, int baseVertex, int layer, float opacity, bool desaturate)
public GuiMeshBuilder(Texture2D? texture, int baseVertex, int layer, bool desaturate)
{
this.texture = texture;
this.opacity = opacity;
this.desaturate = desaturate;
//this.baseVertex = baseVertex;
//this.layer = layer * float.Epsilon;
@ -37,7 +35,6 @@ public sealed class GuiMeshBuilder
int index = vertices.Count;
vertex.Position.Z = layer;
vertex.Color.A = (byte)(vertex.Color.A * opacity);
if (desaturate)
vertex.Color.A = (byte) (vertex.Color.A / 2);

View file

@ -1,5 +1,6 @@
using System.Collections;
using System.Numerics;
using AcidicGUI.Effects;
using Microsoft.Xna.Framework.Graphics;
namespace AcidicGUI.Rendering;
@ -80,12 +81,12 @@ public sealed class GuiRenderer
whiteBatch.Submit(subMesh);
}
public void RenderBatches()
public void RenderBatches(IEffect? effectOverride = null, float opacity = 1)
{
whiteBatch?.DrawBatch();
whiteBatch?.DrawBatch(effectOverride, opacity);
foreach (GuiBatch batch in batches.Values)
batch.DrawBatch();
batch.DrawBatch(effectOverride, opacity);
}
public GraphicsDevice GraphicsDevice => context.GraphicsDevice;
@ -171,7 +172,7 @@ public sealed class GuiBatch
cpuIndices.AddRange(subMesh.Indices);
}
public void DrawBatch()
public void DrawBatch(IEffect? effectOverride, float opacity)
{
if (cpuIndices.Count == 0 || cpuVertices.Count == 0)
return;
@ -203,7 +204,7 @@ public sealed class GuiBatch
dirty = false;
}
context.Render(vertexBuffer, indexBuffer, 0, cpuIndices.Count / 3, texture, null);
context.Render(vertexBuffer, indexBuffer, 0, cpuIndices.Count / 3, texture, null, effectOverride, opacity);
cpuVertices.Clear();
cpuIndices.Clear();

View file

@ -1,4 +1,5 @@
using AcidicGUI.CustomProperties;
using AcidicGUI.Effects;
using AcidicGUI.Layout;
using AcidicGUI.Rendering;
using AcidicGUI.TextRendering;
@ -12,15 +13,22 @@ public abstract partial class Widget : IFontFamilyProvider
private readonly WidgetCollection children;
private readonly Dictionary<Type, CustomPropertyObject> customProperties = new();
private IVisualStyle? visualStyleOverride;
private Widget? parent;
private GuiManager? guiManager;
private GuiMesh? cachedGeometry;
private float renderOpacity = 1;
private bool enabled = true;
private ClippingMode clippingMode;
private LayoutRect clipRect;
private IVisualStyle? visualStyleOverride;
private Widget? parent;
private GuiManager? guiManager;
private GuiMesh? cachedGeometry;
private float renderOpacity = 1;
private bool enabled = true;
private ClippingMode clippingMode;
private LayoutRect clipRect;
private IWidgetEffect? effectOverride;
public IWidgetEffect? RenderEffect
{
get => effectOverride;
set => effectOverride = value;
}
public bool IsFocused
{
get
@ -191,15 +199,19 @@ public abstract partial class Widget : IFontFamilyProvider
if (visibility != Visibility.Visible)
return;
effectOverride?.UpdateParameters(this, renderer);
if (cachedGeometry == null)
{
var geometryHelper = new GeometryHelper(renderer, ComputedOpacity, !HierarchyEnabled, clipRect);
var geometryHelper = new GeometryHelper(renderer, !HierarchyEnabled, clipRect);
effectOverride?.BeforeRebuildGeometry(geometryHelper);
RebuildGeometry(geometryHelper);
effectOverride?.AfterRebuildGeometry(geometryHelper);
cachedGeometry = geometryHelper.ExportMesh();
}
renderer.RenderGuiMesh(cachedGeometry.Value);
renderer.RenderBatches();
renderer.RenderBatches(effectOverride, ComputedOpacity);
renderer.PushLayer();

View file

@ -0,0 +1,30 @@
using AcidicGUI.Effects;
using AcidicGUI.Rendering;
using AcidicGUI.Widgets;
using Microsoft.Xna.Framework.Graphics;
namespace SociallyDistant.Core.UI.Effects;
public sealed class BackgroundBlurEffect : MonoGameEffect,
IWidgetEffect
{
// TODO
public BackgroundBlurEffect(Effect underlyingEffect, int techniqueIndex = 0) : base(underlyingEffect, techniqueIndex)
{
}
public void UpdateParameters(Widget widget, GuiRenderer renderer)
{
throw new NotImplementedException();
}
public void BeforeRebuildGeometry(GeometryHelper geometry)
{
throw new NotImplementedException();
}
public void AfterRebuildGeometry(GeometryHelper geometry)
{
throw new NotImplementedException();
}
}

View file

@ -0,0 +1,57 @@
using AcidicGUI.Effects;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace SociallyDistant.Core.UI.Effects;
public class MonoGameEffect :
IEffect,
IDisposable
{
public static readonly string TransformMatrixParameterName = "TransformMatrix";
public static readonly string OpacityParameterName = "Opacity";
private readonly Effect underlyingEffect;
private readonly int techniqueIndex;
private readonly EffectParameter transformMatrixParameter;
private readonly EffectParameter opacityParameter;
private Viewport lastViewport;
private Matrix transformMatrix;
public int PassesCount => underlyingEffect.Techniques[techniqueIndex].Passes.Count;
public MonoGameEffect(Effect underlyingEffect, int techniqueIndex = 0)
{
this.underlyingEffect = underlyingEffect;
this.techniqueIndex = techniqueIndex;
transformMatrixParameter = underlyingEffect.Parameters[TransformMatrixParameterName];
opacityParameter = underlyingEffect.Parameters[OpacityParameterName];
}
public void Dispose()
{
underlyingEffect.Dispose();
}
public void UpdateOpacity(float opacity)
{
opacityParameter.SetValue(opacity);
}
public void Use(int pass)
{
underlyingEffect.Techniques[techniqueIndex].Passes[pass].Apply();
Viewport viewport = underlyingEffect.GraphicsDevice.Viewport;
if (viewport.Width != lastViewport.Width || viewport.Height != lastViewport.Height)
{
lastViewport = viewport;
Matrix.CreateOrthographicOffCenter(0, viewport.Width, viewport.Height, 0, 0, -1, out transformMatrix);
}
transformMatrixParameter.SetValue(transformMatrix);
}
}

View file

@ -1,5 +1,6 @@
using AcidicGUI;
using AcidicGUI.CustomProperties;
using AcidicGUI.Effects;
using AcidicGUI.Layout;
using AcidicGUI.Rendering;
using AcidicGUI.TextRendering;
@ -8,6 +9,7 @@ using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using SociallyDistant.Core.Modules;
using SociallyDistant.Core.UI.Effects;
using SociallyDistant.Core.UI.VisualStyles;
namespace SociallyDistant.Core.UI;
@ -23,18 +25,18 @@ public sealed class GuiService :
AlphaSourceBlend = Blend.One,
AlphaDestinationBlend = Blend.InverseSourceAlpha,
};
private readonly IGameContext context;
private readonly GuiManager acidicGui;
private readonly IGuiContext guiContext;
private readonly int[] screenQuad = new int[] { 0, 1, 2, 2, 1, 3 };
private readonly IGameContext context;
private readonly GuiManager acidicGui;
private readonly IGuiContext guiContext;
private readonly int[] screenQuad = new int[] { 0, 1, 2, 2, 1, 3 };
private readonly VertexPositionColorTexture[] screenQuadVerts = new VertexPositionColorTexture[4];
private readonly SociallyDistantVisualStyle visualStyle;
private IFontFamily? fallbackFont;
private SpriteEffect? defaultEffect;
private Texture2D? white = null;
private RenderTarget2D? virtualScreen;
private RasterizerState? scissor;
private RasterizerState? noScissor;
private readonly SociallyDistantVisualStyle visualStyle;
private IFontFamily? fallbackFont;
private MonoGameEffect? defaultEffect;
private Texture2D? white = null;
private RenderTarget2D? virtualScreen;
private RasterizerState? scissor;
private RasterizerState? noScissor;
public GuiManager GuiRoot => acidicGui;
@ -122,12 +124,17 @@ public sealed class GuiService :
public float PhysicalScreenWidget => virtualScreen?.Width ?? Game.GraphicsDevice.Viewport.Width;
public float PhysicalScreenHeight => virtualScreen?.Height ?? Game.GraphicsDevice.Viewport.Height;
private MonoGameEffect LoadDefaultEffect()
{
return new MonoGameEffect(Game.Content.Load<Effect>("/Core/Shaders/UI_Core"));
}
public void Render(VertexPositionColorTexture[] vertices, int[] indices, Texture2D? texture, LayoutRect? clipRect = null)
{
if (defaultEffect == null)
{
defaultEffect = new SpriteEffect(Game.GraphicsDevice);
defaultEffect = LoadDefaultEffect();
}
if (white == null)
@ -144,7 +151,7 @@ public sealed class GuiService :
var graphics = Game.GraphicsDevice;
defaultEffect.Techniques[0].Passes[0].Apply();
defaultEffect.Use(0);
graphics.Textures[0] = texture ?? white;
graphics.SamplerStates[0] = SamplerState.LinearClamp;
graphics.BlendState = blendState;
@ -160,14 +167,16 @@ public sealed class GuiService :
int offset,
int primitiveCount,
Texture2D? texture,
LayoutRect? clipRect = null
LayoutRect? clipRect = null,
IEffect? effectOverride = null,
float opacity = 1
)
{
var device = Game.GraphicsDevice;
if (defaultEffect == null)
{
defaultEffect = new SpriteEffect(Game.GraphicsDevice);
defaultEffect = LoadDefaultEffect();
}
if (white == null)
@ -178,8 +187,11 @@ public sealed class GuiService :
if (primitiveCount == 0)
return;
IEffect effectToUse = effectOverride ?? defaultEffect;
defaultEffect.Techniques[0].Passes[0].Apply();
effectToUse.Use(0);
effectToUse.UpdateOpacity(opacity);
device.Textures[0] = texture ?? white;
device.SamplerStates[0] = SamplerState.LinearClamp;
device.BlendState = blendState;
@ -192,6 +204,11 @@ public sealed class GuiService :
device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, offset, primitiveCount);
}
public void Grab(RenderTarget2D destination)
{
// TODO: Implement grabbing
}
private RasterizerState GetRasterizerState(LayoutRect? clipRect)
{
scissor ??= new RasterizerState()

View file

@ -5,6 +5,15 @@
/profile:Reach
/compress:false
#begin Shaders/UI_Core.fx
/importer:EffectImporter
/processor:EffectProcessor
/processorParam:Importer=EffectImporter
/processorParam:Processor=EffectProcessor
/processorParam:DebugMode=Auto
/processorParam:Defines=
/build:Shaders/UI_Core.fx
#begin Backgrounds/Socially-Distant-Bg-wallpapers-dark-distorted.jpg
/importer:TextureImporter
/processor:TextureProcessor

View file

@ -0,0 +1,56 @@
/**
* SOCIALLY DISTANT - CORE USER INTERFACE SHADER
*
* This effect file contains all necessary shader code needed to render
* the game's user interface. Do not modify it unless you seriously know
* what you're doing, as it is very tightly coupled with the UI renderer
* code. If you've never seen that code before, you may wanna go look at it
* for a few hours before fuckin' around in here. Don't break Ritchie's
* exquisitly-programmed UI render code D:
*/
/* standard MG boilerplate */
#if OPENGL
#define SV_POSITION POSITION
#define VS_SHADERMODEL vs_3_0
#define PS_SHADERMODEL ps_3_0
#else
#define VS_SHADERMODEL vs_4_0_level_9_1
#define PS_SHADERMODEL ps_4_0_level_9_1
#endif
struct VSOut {
float4 position : SV_Position;
float4 color : COLOR0;
float2 texcoord : TEXCOORD0;
};
matrix TransformMatrix;
sampler2D MainTexture : register(s0);
float Opacity;
VSOut VS(float4 position : SV_Position, float4 color : COLOR0, float2 texcoord : TEXCOORD0)
{
VSOut vsout;
vsout.position = mul(position, TransformMatrix);
vsout.color = color * Opacity;
vsout.texcoord = texcoord;
return vsout;
}
float4 BasicPixelShader(VSOut data) : COLOR0
{
return tex2D(MainTexture, data.texcoord) * data.color;
}
technique BasicRender
{
pass BlitThyPixels
{
VertexShader = compile VS_SHADERMODEL VS();
PixelShader = compile PS_SHADERMODEL BasicPixelShader();
}
}

View file

@ -12,14 +12,14 @@ namespace SociallyDistant.UI.Windowing;
public class WindowDecoration : Widget
{
private readonly FlexPanel flexPanel = new();
private readonly FlexPanel titleBar = new();
private readonly Box borderBox = new();
private readonly Box clientBox = new();
private readonly WindowBase window;
private readonly WindowDragSurface dragSurface;
private readonly FlexPanel flexPanel = new();
private readonly FlexPanel titleBar = new();
private readonly Box borderBox = new();
private readonly Box clientBox = new();
private readonly WindowBase window;
private readonly WindowDragSurface dragSurface;
private readonly CompositeIconWidget titleIcon = new();
private readonly WindowTabList tabList = new();
private readonly WindowTabList tabList = new();
private WindowHints hints;

View file

@ -31,6 +31,9 @@ public sealed class WindowDragSurface :
public void OnDrag(MouseButtonEvent e)
{
if (e.Button != MouseButton.Left)
return;
if (!dragging)
return;
@ -46,6 +49,9 @@ public sealed class WindowDragSurface :
public void OnDragEnd(MouseButtonEvent e)
{
if (e.Button != MouseButton.Left)
return;
if (!dragging)
return;