Allow mods to downscale framebuffer resolution for large world viewports.

This commit is contained in:
Paul Chote
2021-05-15 13:44:33 +01:00
committed by reaperrr
parent 84dff779ac
commit c64cfea179
3 changed files with 73 additions and 18 deletions

View File

@@ -209,17 +209,22 @@ namespace OpenRA.Graphics
shader.SetTexture("ColorShifts", colorShifts); shader.SetTexture("ColorShifts", colorShifts);
} }
public void SetViewportParams(Size screen, float depthScale, float depthOffset, int2 scroll) public void SetViewportParams(Size sheetSize, int downscale, float depthMargin, int2 scroll)
{ {
// Calculate the effective size of the render surface in viewport pixels
var width = downscale * sheetSize.Width;
var height = downscale * sheetSize.Height;
var depthScale = height / (height + depthMargin);
var depthOffset = depthScale / 2;
shader.SetVec("Scroll", scroll.X, scroll.Y, scroll.Y); shader.SetVec("Scroll", scroll.X, scroll.Y, scroll.Y);
shader.SetVec("r1", shader.SetVec("r1",
2f / screen.Width, 2f / width,
2f / screen.Height, 2f / height,
-depthScale / screen.Height); -depthScale / height);
shader.SetVec("r2", -1, -1, 1 - depthOffset); shader.SetVec("r2", -1, -1, 1 - depthOffset);
// Texture index is sampled as a float, so convert to pixels then scale // Texture index is sampled as a float, so convert to pixels then scale
shader.SetVec("DepthTextureScale", 128 * depthScale / screen.Height); shader.SetVec("DepthTextureScale", 128 * depthScale / height);
} }
public void SetDepthPreviewEnabled(bool enabled) public void SetDepthPreviewEnabled(bool enabled)

View File

@@ -52,8 +52,12 @@ namespace OpenRA
IFrameBuffer worldBuffer; IFrameBuffer worldBuffer;
Sheet worldSheet; Sheet worldSheet;
Sprite worldSprite; Sprite worldSprite;
int worldDownscaleFactor = 1;
Size lastMaximumViewportSize;
Size lastWorldViewportSize;
public Size WorldFrameBufferSize => worldSheet.Size; public Size WorldFrameBufferSize => worldSheet.Size;
public int WorldDownscaleFactor => worldDownscaleFactor;
SheetBuilder fontSheetBuilder; SheetBuilder fontSheetBuilder;
readonly IPlatform platform; readonly IPlatform platform;
@@ -123,6 +127,9 @@ namespace OpenRA
{ {
Game.RunAfterTick(() => Game.RunAfterTick(() =>
{ {
// Recalculate downscaling factor for the new window scale
SetMaximumViewportSize(lastMaximumViewportSize);
ChromeProvider.SetDPIScale(newEffective); ChromeProvider.SetDPIScale(newEffective);
foreach (var f in Fonts) foreach (var f in Fonts)
@@ -175,19 +182,33 @@ namespace OpenRA
var bufferSize = new Size((int)(surfaceBufferSize.Width / scale), (int)(surfaceBufferSize.Height / scale)); var bufferSize = new Size((int)(surfaceBufferSize.Width / scale), (int)(surfaceBufferSize.Height / scale));
if (lastBufferSize != bufferSize) if (lastBufferSize != bufferSize)
{ {
SpriteRenderer.SetViewportParams(bufferSize, 0f, 0f, int2.Zero); SpriteRenderer.SetViewportParams(bufferSize, 1, 0f, int2.Zero);
lastBufferSize = bufferSize; lastBufferSize = bufferSize;
} }
} }
public void SetMaximumViewportSize(Size size) public void SetMaximumViewportSize(Size size)
{ {
var worldBufferSize = size.NextPowerOf2(); // Aim to render the world into a framebuffer at 1:1 scaling which is then up/downscaled using a custom
if (worldSprite == null || worldSprite.Sheet.Size != worldBufferSize) // filter to provide crisp scaling and avoid rendering glitches when the depth buffer is used and samples don't match.
// This approach does not scale well to large sizes, first saturating GPU fill rate and then crashing when
// reaching the framebuffer size limits (typically 16k). We therefore clamp the maximum framebuffer size to
// twice the window surface size, which strikes a reasonable balance between rendering quality and performance.
// Mods that use the depth buffer must instead limit their artwork resolution or maximum zoom-out levels.
Size worldBufferSize;
if (depthMargin == 0)
{
var surfaceSize = Window.SurfaceSize;
worldBufferSize = new Size(Math.Min(size.Width, 2 * surfaceSize.Width), Math.Min(size.Height, 2 * surfaceSize.Height)).NextPowerOf2();
}
else
worldBufferSize = size.NextPowerOf2();
if (worldSprite == null || worldSheet.Size != worldBufferSize)
{ {
worldBuffer?.Dispose(); worldBuffer?.Dispose();
// Render the world into a framebuffer at 1:1 scaling to allow the depth buffer to match the artwork at all zoom levels // If enableWorldFrameBufferDownscale and the world is more than twice the size of the final output size do we allow it to be downsampled!
worldBuffer = Context.CreateFrameBuffer(worldBufferSize); worldBuffer = Context.CreateFrameBuffer(worldBufferSize);
// Pixel art scaling mode is a customized bilinear sampling // Pixel art scaling mode is a customized bilinear sampling
@@ -198,6 +219,8 @@ namespace OpenRA
lastWorldViewport = Rectangle.Empty; lastWorldViewport = Rectangle.Empty;
worldSprite = null; worldSprite = null;
} }
lastMaximumViewportSize = size;
} }
public void BeginWorld(Rectangle worldViewport) public void BeginWorld(Rectangle worldViewport)
@@ -210,15 +233,27 @@ namespace OpenRA
if (worldSheet == null) if (worldSheet == null)
throw new InvalidOperationException($"BeginWorld called before SetMaximumViewportSize has been set."); throw new InvalidOperationException($"BeginWorld called before SetMaximumViewportSize has been set.");
if (worldSprite == null || worldViewport.Size != worldSprite.Bounds.Size) if (worldSprite == null || worldViewport.Size != lastWorldViewportSize)
worldSprite = new Sprite(worldSheet, new Rectangle(int2.Zero, worldViewport.Size), TextureChannel.RGBA); {
// Downscale world rendering if needed to fit within the framebuffer
var vw = worldViewport.Size.Width;
var vh = worldViewport.Size.Height;
var bw = worldSheet.Size.Width;
var bh = worldSheet.Size.Height;
worldDownscaleFactor = 1;
while (vw / worldDownscaleFactor > bw || vh / worldDownscaleFactor > bh)
worldDownscaleFactor++;
var s = new Size(vw / worldDownscaleFactor, vh / worldDownscaleFactor);
worldSprite = new Sprite(worldSheet, new Rectangle(int2.Zero, s), TextureChannel.RGBA);
lastWorldViewportSize = worldViewport.Size;
}
worldBuffer.Bind(); worldBuffer.Bind();
if (lastWorldViewport != worldViewport) if (lastWorldViewport != worldViewport)
{ {
var depthScale = worldSheet.Size.Height / (worldSheet.Size.Height + depthMargin); WorldSpriteRenderer.SetViewportParams(worldSheet.Size, worldDownscaleFactor, depthMargin, worldViewport.Location);
WorldSpriteRenderer.SetViewportParams(worldSheet.Size, depthScale, depthScale / 2, worldViewport.Location);
WorldModelRenderer.SetViewportParams(worldSheet.Size, worldViewport.Location); WorldModelRenderer.SetViewportParams(worldSheet.Size, worldViewport.Location);
lastWorldViewport = worldViewport; lastWorldViewport = worldViewport;
@@ -239,10 +274,10 @@ namespace OpenRA
screenBuffer.Bind(); screenBuffer.Bind();
var scale = Window.EffectiveWindowScale; var scale = Window.EffectiveWindowScale;
var bufferSize = new Size((int)(screenSprite.Bounds.Width / scale), (int)(-screenSprite.Bounds.Height / scale)); var bufferSize = new float2((int)(screenSprite.Bounds.Width / scale), (int)(-screenSprite.Bounds.Height / scale));
SpriteRenderer.SetAntialiasingPixelsPerTexel(Window.SurfaceSize.Height * 1f / worldSprite.Bounds.Height); SpriteRenderer.SetAntialiasingPixelsPerTexel(Window.SurfaceSize.Height * 1f / worldSprite.Bounds.Height);
RgbaSpriteRenderer.DrawSprite(worldSprite, float3.Zero, new float2(bufferSize)); RgbaSpriteRenderer.DrawSprite(worldSprite, float3.Zero, bufferSize);
Flush(); Flush();
SpriteRenderer.SetAntialiasingPixelsPerTexel(0); SpriteRenderer.SetAntialiasingPixelsPerTexel(0);
} }
@@ -348,7 +383,14 @@ namespace OpenRA
Flush(); Flush();
if (renderType == RenderType.World) if (renderType == RenderType.World)
worldBuffer.EnableScissor(rect); {
var r = Rectangle.FromLTRB(
rect.Left / worldDownscaleFactor,
rect.Top / worldDownscaleFactor,
(rect.Right + worldDownscaleFactor - 1) / worldDownscaleFactor,
(rect.Bottom + worldDownscaleFactor - 1) / worldDownscaleFactor);
worldBuffer.EnableScissor(r);
}
else else
Context.EnableScissor(rect.X, rect.Y, rect.Width, rect.Height); Context.EnableScissor(rect.X, rect.Y, rect.Width, rect.Height);
@@ -364,7 +406,15 @@ namespace OpenRA
{ {
// Restore previous scissor rect // Restore previous scissor rect
if (scissorState.Any()) if (scissorState.Any())
worldBuffer.EnableScissor(scissorState.Peek()); {
var rect = scissorState.Peek();
var r = Rectangle.FromLTRB(
rect.Left / worldDownscaleFactor,
rect.Top / worldDownscaleFactor,
(rect.Right + worldDownscaleFactor - 1) / worldDownscaleFactor,
(rect.Bottom + worldDownscaleFactor - 1) / worldDownscaleFactor);
worldBuffer.EnableScissor(r);
}
else else
worldBuffer.DisableScissor(); worldBuffer.DisableScissor();
} }

View File

@@ -46,7 +46,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
return $"FPS: {fps}\nTick {Game.LocalTick} @ {PerfHistory.Items["tick_time"].Average(Game.Settings.Debug.Samples):F1} ms\n" + return $"FPS: {fps}\nTick {Game.LocalTick} @ {PerfHistory.Items["tick_time"].Average(Game.Settings.Debug.Samples):F1} ms\n" +
$"Render {Game.RenderFrame} @ {PerfHistory.Items["render"].Average(Game.Settings.Debug.Samples):F1} ms\n" + $"Render {Game.RenderFrame} @ {PerfHistory.Items["render"].Average(Game.Settings.Debug.Samples):F1} ms\n" +
$"Batches: {PerfHistory.Items["batches"].LastValue}\n" + $"Batches: {PerfHistory.Items["batches"].LastValue}\n" +
$"Viewport Size: {viewportSize.Width} x {viewportSize.Height}\n" + $"Viewport Size: {viewportSize.Width} x {viewportSize.Height} / {Game.Renderer.WorldDownscaleFactor}\n" +
$"WFB Size: {wfbSize.Width} x {wfbSize.Height}"; $"WFB Size: {wfbSize.Width} x {wfbSize.Height}";
}; };
} }