Add IPostProcessWorldShader for custom effect render passes.

This commit is contained in:
Paul Chote
2023-10-22 16:58:58 +01:00
committed by Gustas
parent b1f5367822
commit 47af7a9023
10 changed files with 174 additions and 2 deletions

View File

@@ -157,6 +157,7 @@ namespace OpenRA
{ {
void SetData(byte[] colors, int width, int height); void SetData(byte[] colors, int width, int height);
void SetFloatData(float[] data, int width, int height); void SetFloatData(float[] data, int width, int height);
void SetDataFromReadBuffer(Rectangle rect);
byte[] GetData(); byte[] GetData();
Size Size { get; } Size Size { get; }
TextureScaleFilter ScaleFilter { get; set; } TextureScaleFilter ScaleFilter { get; set; }

View File

@@ -0,0 +1,37 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System.Runtime.InteropServices;
namespace OpenRA.Graphics
{
[StructLayout(LayoutKind.Sequential)]
public readonly struct RenderPostProcessPassVertex
{
public readonly float X, Y;
public RenderPostProcessPassVertex(float x, float y)
{
X = x; Y = y;
}
}
public sealed class RenderPostProcessPassShaderBindings : ShaderBindings
{
public RenderPostProcessPassShaderBindings(string name)
: base("postprocess", "postprocess_" + name) { }
public override ShaderVertexAttribute[] Attributes { get; } = new[]
{
new ShaderVertexAttribute("aVertexPosition", 2, 0)
};
}
}

View File

@@ -39,11 +39,14 @@ namespace OpenRA.Graphics
public abstract ShaderVertexAttribute[] Attributes { get; } public abstract ShaderVertexAttribute[] Attributes { get; }
protected ShaderBindings(string name) protected ShaderBindings(string name)
: this(name, name) { }
protected ShaderBindings(string vertexName, string fragmentName)
{ {
Stride = Attributes.Sum(a => a.Components * 4); Stride = Attributes.Sum(a => a.Components * 4);
VertexShaderName = name; VertexShaderName = vertexName;
VertexShaderCode = GetShaderCode(VertexShaderName + ".vert"); VertexShaderCode = GetShaderCode(VertexShaderName + ".vert");
FragmentShaderName = name; FragmentShaderName = fragmentName;
FragmentShaderCode = GetShaderCode(FragmentShaderName + ".frag"); FragmentShaderCode = GetShaderCode(FragmentShaderName + ".frag");
} }

View File

@@ -45,6 +45,8 @@ namespace OpenRA.Graphics
readonly List<IRenderable> renderablesBuffer = new(); readonly List<IRenderable> renderablesBuffer = new();
readonly IRenderer[] renderers; readonly IRenderer[] renderers;
readonly IRenderPostProcessPass[] postProcessPasses;
readonly ITexture postProcessTexture;
internal WorldRenderer(ModData modData, World world) internal WorldRenderer(ModData modData, World world)
{ {
@@ -71,6 +73,10 @@ namespace OpenRA.Graphics
terrainRenderer = world.WorldActor.TraitOrDefault<IRenderTerrain>(); terrainRenderer = world.WorldActor.TraitOrDefault<IRenderTerrain>();
debugVis = Exts.Lazy(() => world.WorldActor.TraitOrDefault<DebugVisualizations>()); debugVis = Exts.Lazy(() => world.WorldActor.TraitOrDefault<DebugVisualizations>());
postProcessPasses = world.WorldActor.TraitsImplementing<IRenderPostProcessPass>().ToArray();
if (postProcessPasses.Length > 0)
postProcessTexture = Game.Renderer.Context.CreateTexture();
} }
public void BeginFrame() public void BeginFrame()
@@ -284,6 +290,8 @@ namespace OpenRA.Graphics
if (enableDepthBuffer) if (enableDepthBuffer)
Game.Renderer.ClearDepthBuffer(); Game.Renderer.ClearDepthBuffer();
ApplyPostProcessing(PostProcessPassType.AfterActors);
World.ApplyToActorsWithTrait<IRenderAboveWorld>((actor, trait) => World.ApplyToActorsWithTrait<IRenderAboveWorld>((actor, trait) =>
{ {
if (actor.IsInWorld && !actor.Disposed) if (actor.IsInWorld && !actor.Disposed)
@@ -293,6 +301,8 @@ namespace OpenRA.Graphics
if (enableDepthBuffer) if (enableDepthBuffer)
Game.Renderer.ClearDepthBuffer(); Game.Renderer.ClearDepthBuffer();
ApplyPostProcessing(PostProcessPassType.AfterWorld);
World.ApplyToActorsWithTrait<IRenderShroud>((actor, trait) => trait.RenderShroud(this)); World.ApplyToActorsWithTrait<IRenderShroud>((actor, trait) => trait.RenderShroud(this));
if (enableDepthBuffer) if (enableDepthBuffer)
@@ -306,9 +316,27 @@ namespace OpenRA.Graphics
foreach (var r in g) foreach (var r in g)
r.Render(this); r.Render(this);
ApplyPostProcessing(PostProcessPassType.AfterShroud);
Game.Renderer.Flush(); Game.Renderer.Flush();
} }
void ApplyPostProcessing(PostProcessPassType type)
{
var size = Game.Renderer.WorldFrameBufferSize;
var rect = new Rectangle(0, 0, size.Width, size.Height);
foreach (var pass in postProcessPasses)
{
if (pass.Type != type || !pass.Enabled)
continue;
// Make a copy of the world texture to avoid reading and writing on the same buffer
Game.Renderer.Flush();
postProcessTexture.SetDataFromReadBuffer(rect);
pass.Draw(this, postProcessTexture);
}
}
public void DrawAnnotations() public void DrawAnnotations()
{ {
Game.Renderer.EnableAntialiasingFilter(); Game.Renderer.EnableAntialiasingFilter();

View File

@@ -459,6 +459,16 @@ namespace OpenRA.Traits
bool SpatiallyPartitionable { get; } bool SpatiallyPartitionable { get; }
} }
public enum PostProcessPassType { AfterShroud, AfterWorld, AfterActors }
[RequireExplicitImplementation]
public interface IRenderPostProcessPass
{
PostProcessPassType Type { get; }
bool Enabled { get; }
void Draw(WorldRenderer wr, ITexture worldTexture);
}
[Flags] [Flags]
public enum SelectionPriorityModifiers public enum SelectionPriorityModifiers
{ {

View File

@@ -0,0 +1,56 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using OpenRA.Graphics;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
public abstract class RenderPostProcessPassBase : IRenderPostProcessPass
{
readonly Renderer renderer;
readonly IShader shader;
readonly IVertexBuffer<RenderPostProcessPassVertex> buffer;
readonly PostProcessPassType type;
protected RenderPostProcessPassBase(string name, PostProcessPassType type)
{
this.type = type;
renderer = Game.Renderer;
shader = renderer.CreateShader(new RenderPostProcessPassShaderBindings(name));
var vertices = new RenderPostProcessPassVertex[]
{
new(-1, -1),
new(1, -1),
new(1, 1),
new(1, 1),
new(-1, 1),
new(-1, -1)
};
buffer = renderer.CreateVertexBuffer<RenderPostProcessPassVertex>(6);
buffer.SetData(ref vertices, 6);
}
PostProcessPassType IRenderPostProcessPass.Type => type;
bool IRenderPostProcessPass.Enabled => Enabled;
void IRenderPostProcessPass.Draw(WorldRenderer wr, ITexture worldTexture)
{
shader.PrepareRender();
shader.SetTexture("WorldTexture", worldTexture);
PrepareRender(wr, shader);
renderer.DrawBatch(buffer, shader, 0, 6, PrimitiveType.TriangleList);
}
protected abstract bool Enabled { get; }
protected abstract void PrepareRender(WorldRenderer wr, IShader shader);
}
}

View File

@@ -442,6 +442,10 @@ namespace OpenRA.Platforms.Default
int width, int height, int border, int format, int type, IntPtr pixels); int width, int height, int border, int format, int type, IntPtr pixels);
public static TexImage2D glTexImage2D { get; private set; } public static TexImage2D glTexImage2D { get; private set; }
public delegate void CopyTexImage2D(int target, int level, int internalFormat,
int x, int y, int width, int height, int border);
public static CopyTexImage2D glCopyTexImage2D { get; private set; }
public delegate void GetTexImage(int target, int level, public delegate void GetTexImage(int target, int level,
int format, int type, IntPtr pixels); int format, int type, IntPtr pixels);
public static GetTexImage glGetTexImage { get; private set; } public static GetTexImage glGetTexImage { get; private set; }
@@ -607,6 +611,7 @@ namespace OpenRA.Platforms.Default
glBindTexture = Bind<BindTexture>("glBindTexture"); glBindTexture = Bind<BindTexture>("glBindTexture");
glActiveTexture = Bind<ActiveTexture>("glActiveTexture"); glActiveTexture = Bind<ActiveTexture>("glActiveTexture");
glTexImage2D = Bind<TexImage2D>("glTexImage2D"); glTexImage2D = Bind<TexImage2D>("glTexImage2D");
glCopyTexImage2D = Bind<CopyTexImage2D>("glCopyTexImage2D");
glTexParameteri = Bind<TexParameteri>("glTexParameteri"); glTexParameteri = Bind<TexParameteri>("glTexParameteri");
glTexParameterf = Bind<TexParameterf>("glTexParameterf"); glTexParameterf = Bind<TexParameterf>("glTexParameterf");

View File

@@ -111,6 +111,19 @@ namespace OpenRA.Platforms.Default
} }
} }
public void SetDataFromReadBuffer(Rectangle rect)
{
VerifyThreadAffinity();
if (!Exts.IsPowerOf2(rect.Width) || !Exts.IsPowerOf2(rect.Height))
throw new InvalidDataException($"Non-power-of-two rectangle {rect.Width}x{rect.Height}");
PrepareTexture();
var glInternalFormat = OpenGL.Profile == GLProfile.Embedded ? OpenGL.GL_BGRA : OpenGL.GL_RGBA8;
OpenGL.glCopyTexImage2D(OpenGL.GL_TEXTURE_2D, 0, glInternalFormat, rect.X, rect.Y, rect.Width, rect.Height, 0);
OpenGL.CheckGLError();
}
public byte[] GetData() public byte[] GetData()
{ {
VerifyThreadAffinity(); VerifyThreadAffinity();

View File

@@ -648,6 +648,7 @@ namespace OpenRA.Platforms.Default
readonly Func<object, object> setData2; readonly Func<object, object> setData2;
readonly Action<object> setData3; readonly Action<object> setData3;
readonly Func<object, object> setData4; readonly Func<object, object> setData4;
readonly Action<object> setData5;
readonly Action dispose; readonly Action dispose;
public ThreadedTexture(ThreadedGraphicsContext device, ITextureInternal texture) public ThreadedTexture(ThreadedGraphicsContext device, ITextureInternal texture)
@@ -663,6 +664,7 @@ namespace OpenRA.Platforms.Default
setData2 = tuple => { setData1(tuple); return null; }; setData2 = tuple => { setData1(tuple); return null; };
setData3 = tuple => { var t = ((float[], int, int))tuple; texture.SetFloatData(t.Item1, t.Item2, t.Item3); }; setData3 = tuple => { var t = ((float[], int, int))tuple; texture.SetFloatData(t.Item1, t.Item2, t.Item3); };
setData4 = tuple => { setData3(tuple); return null; }; setData4 = tuple => { setData3(tuple); return null; };
setData5 = rect => texture.SetDataFromReadBuffer((Rectangle)rect);
dispose = texture.Dispose; dispose = texture.Dispose;
} }
@@ -725,6 +727,11 @@ namespace OpenRA.Platforms.Default
} }
} }
public void SetDataFromReadBuffer(Rectangle rect)
{
device.Post(setData5, rect);
}
public void Dispose() public void Dispose()
{ {
device.Post(dispose); device.Post(dispose);

12
glsl/postprocess.vert Normal file
View File

@@ -0,0 +1,12 @@
#version {VERSION}
#if __VERSION__ == 120
attribute vec2 aVertexPosition;
#else
in vec2 aVertexPosition;
#endif
void main()
{
gl_Position = vec4(aVertexPosition, 0, 1);
}