Allow mods to downscale framebuffer resolution for large world viewports.
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user