diff --git a/OpenRA.Game/Graphics/RenderPostProcessPassVertex.cs b/OpenRA.Game/Graphics/RenderPostProcessPassVertex.cs index cd00106c30..0bb501e49a 100644 --- a/OpenRA.Game/Graphics/RenderPostProcessPassVertex.cs +++ b/OpenRA.Game/Graphics/RenderPostProcessPassVertex.cs @@ -24,6 +24,20 @@ namespace OpenRA.Graphics } } + [StructLayout(LayoutKind.Sequential)] + public readonly struct RenderPostProcessPassTexturedVertex + { + // 3d position + public readonly float X, Y; + public readonly float S, T; + + public RenderPostProcessPassTexturedVertex(float x, float y, float s, float t) + { + X = x; Y = y; + S = s; T = t; + } + } + public sealed class RenderPostProcessPassShaderBindings : ShaderBindings { public RenderPostProcessPassShaderBindings(string name) @@ -34,4 +48,17 @@ namespace OpenRA.Graphics new ShaderVertexAttribute("aVertexPosition", ShaderVertexAttributeType.Float, 2, 0) }; } + + public sealed class RenderPostProcessPassTexturedShaderBindings : ShaderBindings + { + public RenderPostProcessPassTexturedShaderBindings(string name) + : base("postprocess_textured", "postprocess_textured_" + name) + { } + + public override ShaderVertexAttribute[] Attributes { get; } = new[] + { + new ShaderVertexAttribute("aVertexPosition", ShaderVertexAttributeType.Float, 2, 0), + new ShaderVertexAttribute("aVertexTexCoord", ShaderVertexAttributeType.Float, 2, 8), + }; + } } diff --git a/OpenRA.Mods.Cnc/Graphics/ChronoVortexRenderable.cs b/OpenRA.Mods.Cnc/Graphics/ChronoVortexRenderable.cs new file mode 100644 index 0000000000..c1f5f741b3 --- /dev/null +++ b/OpenRA.Mods.Cnc/Graphics/ChronoVortexRenderable.cs @@ -0,0 +1,67 @@ +#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; +using System.Collections.Generic; +using OpenRA.Graphics; +using OpenRA.Mods.Cnc.Traits; +using OpenRA.Primitives; + +namespace OpenRA.Mods.Cnc.Graphics +{ + public class ChronoVortexRenderable : IRenderable, IFinalizedRenderable + { + public static readonly IEnumerable None = Array.Empty(); + readonly ChronoVortexRenderer renderer; + public WPos Pos { get; } + readonly int frame; + + public ChronoVortexRenderable(ChronoVortexRenderer renderer, WPos pos, int frame) + { + if (frame < 0 || frame >= 48) + throw new ArgumentException("frame must be in the range 0-47", nameof(frame)); + + this.renderer = renderer; + Pos = pos; + this.frame = frame; + } + + public int ZOffset => 0; + public bool IsDecoration => false; + + public IRenderable WithZOffset(int newOffset) => this; + public IRenderable OffsetBy(in WVec offset) => this; + public IRenderable AsDecoration() => this; + + public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; } + + public void Render(WorldRenderer wr) + { + renderer.DrawVortex(wr.Screen3DPxPosition(Pos), frame); + } + + public void RenderDebugGeometry(WorldRenderer wr) + { + var pos = wr.Screen3DPxPosition(Pos); + var tl = wr.Viewport.WorldToViewPx(pos); + var br = wr.Viewport.WorldToViewPx(pos + new float3(64, 64, 0)); + Game.Renderer.RgbaColorRenderer.DrawRect(tl, br, 1, Color.Red); + } + + public Rectangle ScreenBounds(WorldRenderer wr) + { + var pos = wr.Screen3DPxPosition(Pos); + var tl = wr.Viewport.WorldToViewPx(pos); + var br = wr.Viewport.WorldToViewPx(pos + new float3(64, 64, 0)); + return new Rectangle(tl.X, tl.Y, br.X - tl.X, br.Y - tl.Y); + } + } +} diff --git a/OpenRA.Mods.Cnc/Traits/World/ChronoVortexRenderer.cs b/OpenRA.Mods.Cnc/Traits/World/ChronoVortexRenderer.cs new file mode 100644 index 0000000000..b62f711456 --- /dev/null +++ b/OpenRA.Mods.Cnc/Traits/World/ChronoVortexRenderer.cs @@ -0,0 +1,109 @@ +#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.Collections.Generic; +using OpenRA.Graphics; +using OpenRA.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.Cnc.Traits +{ + [TraitLocation(SystemActors.World | SystemActors.EditorWorld)] + [Desc("Render chrono vortex")] + public class ChronoVortexRendererInfo : TraitInfo + { + public override object Create(ActorInitializer init) { return new ChronoVortexRenderer(init.Self); } + } + + public sealed class ChronoVortexRenderer : IRenderPostProcessPass + { + readonly Renderer renderer; + readonly IShader shader; + readonly IVertexBuffer vortexBuffer; + readonly Sheet vortexSheet; + readonly List<(float3, int)> vortices = new(); + + public ChronoVortexRenderer(Actor self) + { + renderer = Game.Renderer; + shader = renderer.CreateShader(new RenderPostProcessPassTexturedShaderBindings("vortex")); + + vortexSheet = new Sheet(SheetType.BGRA, new Size(512, 512)); + vortexBuffer = renderer.CreateVertexBuffer(288); + var vertices = new RenderPostProcessPassTexturedVertex[288]; + + var data = vortexSheet.GetData(); + var j = 0; + for (var f = 0; f < 48; f++) + { + var row = f / 8; + var col = f % 8; + + using (var stream = self.World.Map.Open($"hole{f:D04}.lut")) + { + for (var y = 0; y < 64; y++) + { + var i = 2048 * (64 * row + y) + 256 * col; + for (var x = 0; x < 64; x++) + { + data[i++] = (byte)(stream.ReadUInt8() + 128 - x); + data[i++] = (byte)(stream.ReadUInt8() + 128 - y); + data[i++] = stream.ReadUInt8(); + data[i++] = 255; + } + } + } + + var tl = new float2(col, row) / 8; + var br = new float2(col + 1, row + 1) / 8; + vertices[j++] = new RenderPostProcessPassTexturedVertex(-32, -32, tl.X, tl.Y); + vertices[j++] = new RenderPostProcessPassTexturedVertex(32, -32, br.X, tl.Y); + vertices[j++] = new RenderPostProcessPassTexturedVertex(32, 32, br.X, br.Y); + vertices[j++] = new RenderPostProcessPassTexturedVertex(32, 32, br.X, br.Y); + vertices[j++] = new RenderPostProcessPassTexturedVertex(-32, 32, tl.X, br.Y); + vertices[j++] = new RenderPostProcessPassTexturedVertex(-32, -32, tl.X, tl.Y); + } + + vortexBuffer.SetData(ref vertices, 288); + vortexSheet.CommitBufferedData(); + } + + public void DrawVortex(float3 pos, int frame) + { + vortices.Add((pos, frame)); + } + + PostProcessPassType IRenderPostProcessPass.Type => PostProcessPassType.AfterWorld; + bool IRenderPostProcessPass.Enabled => vortices.Count > 0; + + void IRenderPostProcessPass.Draw(WorldRenderer wr, ITexture worldTexture) + { + var scroll = wr.Viewport.TopLeft; + var size = renderer.WorldFrameBufferSize; + var width = 2f / (renderer.WorldDownscaleFactor * size.Width); + var height = 2f / (renderer.WorldDownscaleFactor * size.Height); + + shader.SetVec("Scroll", scroll.X, scroll.Y); + shader.SetVec("p1", width, height); + shader.SetVec("p2", -1, -1); + shader.SetTexture("WorldTexture", worldTexture); + shader.SetTexture("VortexTexture", vortexSheet.GetTexture()); + shader.PrepareRender(); + foreach (var (pos, frame) in vortices) + { + shader.SetVec("Pos", pos.X, pos.Y); + renderer.DrawBatch(vortexBuffer, shader, 6 * frame, 6, PrimitiveType.TriangleList); + } + + vortices.Clear(); + } + } +} diff --git a/glsl/postprocess_textured.vert b/glsl/postprocess_textured.vert new file mode 100644 index 0000000000..e22a9ac19a --- /dev/null +++ b/glsl/postprocess_textured.vert @@ -0,0 +1,14 @@ +#version {VERSION} + +uniform vec2 Pos, Scroll; +uniform vec2 p1, p2; + +in vec2 aVertexPosition; +in vec2 aVertexTexCoord; +out vec2 vTexCoord; + +void main() +{ + gl_Position = vec4((aVertexPosition + Pos - Scroll) * p1 + p2, 0, 1); + vTexCoord = aVertexTexCoord; +} diff --git a/glsl/postprocess_textured_vortex.frag b/glsl/postprocess_textured_vortex.frag new file mode 100644 index 0000000000..c5b191faf8 --- /dev/null +++ b/glsl/postprocess_textured_vortex.frag @@ -0,0 +1,22 @@ +#version {VERSION} +#ifdef GL_ES +precision mediump float; +#endif + +uniform sampler2D VortexTexture; +uniform sampler2D WorldTexture; + +in vec2 vTexCoord; +out vec4 fragColor; + +void main() +{ + vec4 vtx = texture(VortexTexture, vTexCoord.xy); + + vec2 delta = (vtx.bg - 0.5) * 256.0; + float frac = 16.0 * vtx.r + 0.0625; + if (vtx.r > 0.055) + discard; + + fragColor = texelFetch(WorldTexture, ivec2(gl_FragCoord.xy + delta), 0) * vec4(frac, frac, frac, 1); +}