Add smooth scrolling

This commit is contained in:
Gustas
2025-08-28 16:32:27 +03:00
committed by Paul Chote
parent 270c37764e
commit 1a8a3eca4d
6 changed files with 48 additions and 25 deletions

View File

@@ -724,7 +724,7 @@ namespace OpenRA
// Use worldRenderer.World instead of OrderManager.World to avoid a rendering mismatch while processing orders // Use worldRenderer.World instead of OrderManager.World to avoid a rendering mismatch while processing orders
if (worldRenderer != null && !worldRenderer.World.IsLoadingGameSave) if (worldRenderer != null && !worldRenderer.World.IsLoadingGameSave)
{ {
Renderer.BeginWorld(worldRenderer.Viewport.Rectangle); Renderer.BeginWorld(worldRenderer.Viewport.CenterLocation, worldRenderer.Viewport.ViewportSize);
Sound.SetListenerPosition(worldRenderer.Viewport.CenterPosition); Sound.SetListenerPosition(worldRenderer.Viewport.CenterPosition);
using (new PerfSample("render_world")) using (new PerfSample("render_world"))
worldRenderer.Draw(); worldRenderer.Draw();

View File

@@ -49,14 +49,13 @@ namespace OpenRA.Graphics
readonly Size tileSize; readonly Size tileSize;
// Viewport geometry (world-px) // Viewport geometry (world-px)
public int2 CenterLocation { get; private set; } public float2 CenterLocation { get; private set; }
public WPos CenterPosition => worldRenderer.ProjectedPosition(CenterLocation); public WPos CenterPosition => worldRenderer.ProjectedPosition(CenterLocation.ToInt2());
public Rectangle Rectangle => new(TopLeft, new Size(viewportSize.X, viewportSize.Y)); public int2 TopLeft => CenterLocation.ToInt2() - ViewportSize.ToInt2() / 2;
public int2 TopLeft => CenterLocation - viewportSize / 2; public int2 BottomRight => CenterLocation.ToInt2() + ViewportSize.ToInt2() / 2;
public int2 BottomRight => CenterLocation + viewportSize / 2; public Size ViewportSize { get; private set; }
int2 viewportSize;
ProjectedCellRegion cells; ProjectedCellRegion cells;
bool cellsDirty = true; bool cellsDirty = true;
@@ -79,7 +78,7 @@ namespace OpenRA.Graphics
private set private set
{ {
zoom = value; zoom = value;
viewportSize = (1f / zoom * new float2(Game.Renderer.NativeResolution)).ToInt2(); ViewportSize = Size.FromInt2((1f / zoom * new float2(Game.Renderer.NativeResolution)).ToInt2());
cellsDirty = true; cellsDirty = true;
allCellsDirty = true; allCellsDirty = true;
} }
@@ -341,7 +340,7 @@ namespace OpenRA.Graphics
public void Scroll(float2 delta, bool ignoreBorders) public void Scroll(float2 delta, bool ignoreBorders)
{ {
// Convert scroll delta from world-px to viewport-px // Convert scroll delta from world-px to viewport-px
CenterLocation += (1f / Zoom * delta).ToInt2(); CenterLocation += 1f / Zoom * delta;
cellsDirty = true; cellsDirty = true;
allCellsDirty = true; allCellsDirty = true;

View File

@@ -93,6 +93,21 @@ namespace OpenRA
public static float2 Max(float2 a, float2 b) { return new float2(Math.Max(a.X, b.X), Math.Max(a.Y, b.Y)); } public static float2 Max(float2 a, float2 b) { return new float2(Math.Max(a.X, b.X), Math.Max(a.Y, b.Y)); }
public static float2 Min(float2 a, float2 b) { return new float2(Math.Min(a.X, b.X), Math.Min(a.Y, b.Y)); } public static float2 Min(float2 a, float2 b) { return new float2(Math.Min(a.X, b.X), Math.Min(a.Y, b.Y)); }
public float2 Clamp(Rectangle r)
{
var x = X;
var y = Y;
if (r.Left > X)
x = r.Left;
if (r.Top > Y)
y = r.Top;
if (r.Right < X)
x = r.Right;
if (r.Bottom < Y)
y = r.Bottom;
return new float2(x, y);
}
public float LengthSquared => X * X + Y * Y; public float LengthSquared => X * X + Y * Y;
public float Length => (float)Math.Sqrt(LengthSquared); public float Length => (float)Math.Sqrt(LengthSquared);
} }

View File

@@ -77,7 +77,8 @@ namespace OpenRA
Size lastBufferSize = new(-1, -1); Size lastBufferSize = new(-1, -1);
Rectangle lastWorldViewport = Rectangle.Empty; Rectangle lastWorldViewport;
float2 lastViewportLocation;
ITexture currentPaletteTexture; ITexture currentPaletteTexture;
int currentPaletteHeight = 0; int currentPaletteHeight = 0;
IBatchRenderer currentBatchRenderer; IBatchRenderer currentBatchRenderer;
@@ -233,7 +234,7 @@ namespace OpenRA
lastMaximumViewportSize = size; lastMaximumViewportSize = size;
} }
public void BeginWorld(Rectangle worldViewport) public void BeginWorld(float2 viewportLocation, Size viewportSize)
{ {
if (renderType != RenderType.None) if (renderType != RenderType.None)
throw new InvalidOperationException($"BeginWorld called with renderType = {renderType}, expected RenderType.None."); throw new InvalidOperationException($"BeginWorld called with renderType = {renderType}, expected RenderType.None.");
@@ -243,28 +244,34 @@ 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 != lastWorldViewportSize) var centerLocation = viewportLocation.ToInt2();
if (worldSprite == null || viewportSize != lastWorldViewportSize || viewportLocation != lastViewportLocation)
{ {
lastViewportLocation = viewportLocation;
lastWorldViewportSize = viewportSize;
// Downscale world rendering if needed to fit within the framebuffer // Downscale world rendering if needed to fit within the framebuffer
var vw = worldViewport.Size.Width; var vw = viewportSize.Width;
var vh = worldViewport.Size.Height; var vh = viewportSize.Height;
var bw = worldSheet.Size.Width; var bw = worldSheet.Size.Width;
var bh = worldSheet.Size.Height; var bh = worldSheet.Size.Height;
WorldDownscaleFactor = 1; WorldDownscaleFactor = 1;
while (vw / WorldDownscaleFactor > bw || vh / WorldDownscaleFactor > bh) while (vw / WorldDownscaleFactor > bw || vh / WorldDownscaleFactor > bh)
WorldDownscaleFactor++; WorldDownscaleFactor++;
var s = new Size(vw / WorldDownscaleFactor, vh / WorldDownscaleFactor); // We need to add 1 to scroll in order to handle interpixel 0-0.99 fractionalOffset.
worldSprite = new Sprite(worldSheet, new Rectangle(int2.Zero, s), TextureChannel.RGBA); var s = new Size(vw / WorldDownscaleFactor + 1, vh / WorldDownscaleFactor + 1);
lastWorldViewportSize = worldViewport.Size; var fractionalOffset = centerLocation - viewportLocation;
worldSprite = new Sprite(worldSheet, new Rectangle(int2.Zero, s), 0, fractionalOffset, TextureChannel.RGBA);
} }
worldBuffer.Bind(); worldBuffer.Bind();
var rect = new Rectangle(centerLocation, viewportSize);
if (lastWorldViewport != worldViewport) if (lastWorldViewport != rect)
{ {
WorldSpriteRenderer.SetViewportParams(worldSheet.Size, WorldDownscaleFactor, depthMargin, worldViewport.Location); var topLeft = centerLocation - viewportSize.ToInt2() / 2;
lastWorldViewport = worldViewport; WorldSpriteRenderer.SetViewportParams(worldSheet.Size, WorldDownscaleFactor, depthMargin, topLeft);
lastWorldViewport = rect;
} }
renderType = RenderType.World; renderType = RenderType.World;
@@ -282,9 +289,11 @@ namespace OpenRA
screenBuffer.Bind(); screenBuffer.Bind();
var scale = Window.EffectiveWindowScale; var scale = Window.EffectiveWindowScale;
// We added 1 to worldSprite now we need to subtract.
var bufferScale = new float3( var bufferScale = new float3(
(int)(screenSprite.Bounds.Width / scale) / worldSprite.Size.X, (int)(screenSprite.Bounds.Width / scale) / (worldSprite.Size.X - 1),
(int)(-screenSprite.Bounds.Height / scale) / worldSprite.Size.Y, (int)(-screenSprite.Bounds.Height / scale) / (worldSprite.Size.Y - 1),
1f); 1f);
SpriteRenderer.EnablePixelArtScaling(true); SpriteRenderer.EnablePixelArtScaling(true);

View File

@@ -240,7 +240,7 @@ namespace OpenRA.Mods.Common.Traits
void IRenderAboveWorld.RenderAboveWorld(Actor self, WorldRenderer wr) void IRenderAboveWorld.RenderAboveWorld(Actor self, WorldRenderer wr)
{ {
var center = wr.Viewport.CenterLocation; var center = wr.Viewport.CenterLocation.ToInt2();
var viewport = new Rectangle(center - new int2(viewportSize) / 2, viewportSize); var viewport = new Rectangle(center - new int2(viewportSize) / 2, viewportSize);
var wcr = Game.Renderer.WorldRgbaColorRenderer; var wcr = Game.Renderer.WorldRgbaColorRenderer;

View File

@@ -47,7 +47,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var fps = (newestFrame - oldestFrame) / (newestTime - oldestTime).TotalSeconds; var fps = (newestFrame - oldestFrame) / (newestTime - oldestTime).TotalSeconds;
var wfbSize = Game.Renderer.WorldFrameBufferSize; var wfbSize = Game.Renderer.WorldFrameBufferSize;
var viewportSize = worldRenderer.Viewport.Rectangle.Size; var viewportSize = worldRenderer.Viewport.ViewportSize;
return $"FPS: {fps:0}\nTick {Game.LocalTick} @ {PerfHistory.Items["tick_time"].Average(Game.Settings.Debug.Samples):F1} ms\n" + return $"FPS: {fps:0}\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" +