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);
}
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("r1",
2f / screen.Width,
2f / screen.Height,
-depthScale / screen.Height);
2f / width,
2f / height,
-depthScale / height);
shader.SetVec("r2", -1, -1, 1 - depthOffset);
// 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)

View File

@@ -52,8 +52,12 @@ namespace OpenRA
IFrameBuffer worldBuffer;
Sheet worldSheet;
Sprite worldSprite;
int worldDownscaleFactor = 1;
Size lastMaximumViewportSize;
Size lastWorldViewportSize;
public Size WorldFrameBufferSize => worldSheet.Size;
public int WorldDownscaleFactor => worldDownscaleFactor;
SheetBuilder fontSheetBuilder;
readonly IPlatform platform;
@@ -123,6 +127,9 @@ namespace OpenRA
{
Game.RunAfterTick(() =>
{
// Recalculate downscaling factor for the new window scale
SetMaximumViewportSize(lastMaximumViewportSize);
ChromeProvider.SetDPIScale(newEffective);
foreach (var f in Fonts)
@@ -175,19 +182,33 @@ namespace OpenRA
var bufferSize = new Size((int)(surfaceBufferSize.Width / scale), (int)(surfaceBufferSize.Height / scale));
if (lastBufferSize != bufferSize)
{
SpriteRenderer.SetViewportParams(bufferSize, 0f, 0f, int2.Zero);
SpriteRenderer.SetViewportParams(bufferSize, 1, 0f, int2.Zero);
lastBufferSize = bufferSize;
}
}
public void SetMaximumViewportSize(Size size)
{
var worldBufferSize = size.NextPowerOf2();
if (worldSprite == null || worldSprite.Sheet.Size != worldBufferSize)
// Aim to render the world into a framebuffer at 1:1 scaling which is then up/downscaled using a custom
// 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();
// 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);
// Pixel art scaling mode is a customized bilinear sampling
@@ -198,6 +219,8 @@ namespace OpenRA
lastWorldViewport = Rectangle.Empty;
worldSprite = null;
}
lastMaximumViewportSize = size;
}
public void BeginWorld(Rectangle worldViewport)
@@ -210,15 +233,27 @@ namespace OpenRA
if (worldSheet == null)
throw new InvalidOperationException($"BeginWorld called before SetMaximumViewportSize has been set.");
if (worldSprite == null || worldViewport.Size != worldSprite.Bounds.Size)
worldSprite = new Sprite(worldSheet, new Rectangle(int2.Zero, worldViewport.Size), TextureChannel.RGBA);
if (worldSprite == null || worldViewport.Size != lastWorldViewportSize)
{
// 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();
if (lastWorldViewport != worldViewport)
{
var depthScale = worldSheet.Size.Height / (worldSheet.Size.Height + depthMargin);
WorldSpriteRenderer.SetViewportParams(worldSheet.Size, depthScale, depthScale / 2, worldViewport.Location);
WorldSpriteRenderer.SetViewportParams(worldSheet.Size, worldDownscaleFactor, depthMargin, worldViewport.Location);
WorldModelRenderer.SetViewportParams(worldSheet.Size, worldViewport.Location);
lastWorldViewport = worldViewport;
@@ -239,10 +274,10 @@ namespace OpenRA
screenBuffer.Bind();
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);
RgbaSpriteRenderer.DrawSprite(worldSprite, float3.Zero, new float2(bufferSize));
RgbaSpriteRenderer.DrawSprite(worldSprite, float3.Zero, bufferSize);
Flush();
SpriteRenderer.SetAntialiasingPixelsPerTexel(0);
}
@@ -348,7 +383,14 @@ namespace OpenRA
Flush();
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
Context.EnableScissor(rect.X, rect.Y, rect.Width, rect.Height);
@@ -364,7 +406,15 @@ namespace OpenRA
{
// Restore previous scissor rect
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
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" +
$"Render {Game.RenderFrame} @ {PerfHistory.Items["render"].Average(Game.Settings.Debug.Samples):F1} ms\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}";
};
}