Allow custom resolutions for game window

This commit is contained in:
Simon Lübeß
2025-03-29 15:03:25 +01:00
parent 571bf98be2
commit 400045b005
3 changed files with 262 additions and 14 deletions
@@ -13,14 +13,17 @@ namespace GlitchyEditor.EditWindows
{
public const String s_WindowTitle = "Game";
private ImGui.Vec2 _oldViewportSize = .(100, 100);
private float2 _availableViewportSize = .(100, 100);
private float2 _renderedViewportSize = .(100, 100);
private bool _viewPortChanged;
private bool _visible;
private RenderTargetGroup _renderTarget ~ _?.ReleaseRef();
public float2 ViewportSize => (float2)_oldViewportSize;
public float2 AvailableViewportSize => _availableViewportSize;
public float2 RenderedViewportSize => _renderedViewportSize;
// Occurs when the viewport is resized.
public Event<EventHandler<float2>> ViewportSizeChanged ~ _.Dispose();
// Occurs when an entity was clicked.
@@ -71,19 +74,23 @@ namespace GlitchyEditor.EditWindows
_hasFocus = ImGui.IsWindowFocused();
ShowMenuBar();
if(_renderTarget != null)
{
ImGui.Image(_renderTarget.GetViewBinding(0), viewportSize);
float2 scalingFactor = CalculateFitScalingFactor(_availableViewportSize, _renderedViewportSize);
float2 previewResolution = scalingFactor * _renderedViewportSize;
float2 position = max(0.0f, _availableViewportSize / 2.0f - previewResolution / 2.0f) + (float2)ImGui.GetCursorPos();
ImGui.SetCursorPos((ImGui.Vec2)position);
ImGui.Image(_renderTarget.GetViewBinding(0), (ImGui.Vec2)previewResolution);
}
ImGui.End();
if(_oldViewportSize != viewportSize)
{
ViewportSizeChanged.Invoke(this, (float2)viewportSize);
_viewPortChanged = true;
_oldViewportSize = viewportSize;
}
SetAvailableViewportSize((float2)viewportSize);
}
/// Provides the ImGui Drop target and handles dropped payload.
@@ -102,5 +109,175 @@ namespace GlitchyEditor.EditWindows
ImGui.EndDragDropTarget();
}
}
private void SetAvailableViewportSize(float2 viewportSize)
{
if(any(_availableViewportSize != viewportSize))
{
_availableViewportSize = viewportSize;
UpdateCustomResolution();
}
}
private void UpdateCustomResolution()
{
float2 newRenderViewport = _renderedViewportSize;
switch (resolutionMode)
{
case .Window:
newRenderViewport = _availableViewportSize;
case .CustomAspect:
float scalingFactor = CalculateFitScalingFactor(_availableViewportSize, _customAspectRatio);
newRenderViewport = max(ceil(_customAspectRatio * scalingFactor), 16);
case .CustomResolution:
newRenderViewport = _customResolution;
case .SixteenToNine:
case .FullHd:
newRenderViewport = .(1920, 1080);
case .WQHD:
newRenderViewport = .(2560, 1440);
case .UltraHd:
newRenderViewport = .(3840, 2160);
}
if (any(newRenderViewport != _renderedViewportSize))
{
_viewPortChanged = true;
_renderedViewportSize = newRenderViewport;
ViewportSizeChanged.Invoke(this, (float2)_renderedViewportSize);
}
}
private float CalculateFitScalingFactor(float2 outerBox, float2 innerBox)
{
float outerAspectRatio = outerBox.X / outerBox.Y;
float innerAspectRatio = innerBox.X / innerBox.Y;
float scalingFactor;
// If our target shape is wider than our viewport shape, we have to fit in x-direction
// otherwise fit in y-direction
if (innerAspectRatio > outerAspectRatio)
{
scalingFactor = outerBox.X / innerBox.X;
}
else
{
scalingFactor = outerBox.Y / innerBox.Y;
}
return scalingFactor;
}
private enum ResolutionMode
{
case Window;
case CustomAspect;
case CustomResolution;
case SixteenToNine;
case FullHd;
case WQHD;
case UltraHd;
public StringView GetPreviewName()
{
switch (this)
{
case .Window:
return "Window";
case .CustomAspect:
return "Custom Aspect Ratio";
case .CustomResolution:
return "Custom Resolution";
case .SixteenToNine:
return "16:9";
case .FullHd:
return "1080p (Full HD)";
case .WQHD:
return "1440p (WQHD)";
case .UltraHd:
return "2160p (4k)";
}
}
}
private float2 _customAspectRatio = .(16, 9);
private float2 _customResolution = .(-1, -1);
ResolutionMode resolutionMode = .Window;
private void ShowMenuBar()
{
if (ImGui.BeginMenuBar())
{
ImGui.TextUnformatted("Resolution: ");
if (ImGui.BeginCombo("##resolution", resolutionMode.GetPreviewName().ToScopeCStr!(), .WidthFitPreview))
{
for (ResolutionMode mode in Enum.EnumValuesEnumerator<ResolutionMode>())
{
if (ImGui.Selectable(mode.GetPreviewName().ToScopeCStr!(), mode == resolutionMode))
{
resolutionMode = mode;
if (mode == .CustomResolution && any(_customResolution == -1))
{
_customResolution = _availableViewportSize;
}
UpdateCustomResolution();
}
}
ImGui.EndCombo();
}
float itemWidth = ImGui.CalcTextSize("5.1234").x;
ImGui.PushItemWidth(itemWidth);
if (resolutionMode == .CustomAspect)
{
if (ImGui.DragFloat("##aspectX", &_customAspectRatio.X, 1, 1.0f, 16384.0f, format: "%.4g"))
{
UpdateCustomResolution();
}
if (ImGui.DragFloat("##aspectY", &_customAspectRatio.Y, 1, 1.0f, 16384.0f, format: "%.4g"))
{
UpdateCustomResolution();
}
ImGui.TextUnformatted(scope $"({_renderedViewportSize.X}px x {_renderedViewportSize.Y}px)");
}
else if (resolutionMode == .CustomResolution)
{
int32 resX = (int32)_customResolution.X;
if (ImGui.DragInt("##resolutionX", &resX, 1, 1))
{
_customResolution.X = resX;
UpdateCustomResolution();
}
int32 resY = (int32)_customResolution.Y;
if (ImGui.DragInt("##resolutionY", &resY, 1, 1))
{
_customResolution.Y = resY;
UpdateCustomResolution();
}
int gcd = gcd((int)_customResolution.X, (int)_customResolution.Y);
ImGui.TextUnformatted(scope $"({_customResolution.X / gcd}:{_customResolution.Y / gcd})");
}
else
{
ImGui.TextUnformatted(scope $"({_renderedViewportSize.X}px x {_renderedViewportSize.Y}px)");
}
ImGui.PopItemWidth();
ImGui.EndMenuBar();
}
}
}
}
+5 -5
View File
@@ -365,7 +365,7 @@ namespace GlitchyEditor
ColorTargetDescriptions = TargetDescription[](
.(.R8G8B8A8_UNorm))
});
_editorViewportTarget.[Friend]Identifier = "Game Viewport Target";
_gameViewportTarget.[Friend]Identifier = "Game Viewport Target";
_editorIcons = new EditorIcons("Resources/Textures/EditorIcons.dds", .(64, 64));
_editorIcons.SamplerState = SamplerStateManager.AnisotropicClamp;
@@ -460,8 +460,8 @@ namespace GlitchyEditor
{
_gameSceneRenderer.Scene = _activeScene;
_activeScene.SetViewportSize((uint32)_editor.GameViewportWindow.ViewportSize.X, (uint32)_editor.GameViewportWindow.ViewportSize.Y);
_gameSceneRenderer.SetViewportSize((uint32)_editor.GameViewportWindow.ViewportSize.X, (uint32)_editor.GameViewportWindow.ViewportSize.Y);
_activeScene.SetViewportSize((uint32)_editor.GameViewportWindow.RenderedViewportSize.X, (uint32)_editor.GameViewportWindow.RenderedViewportSize.Y);
_gameSceneRenderer.SetViewportSize((uint32)_editor.GameViewportWindow.RenderedViewportSize.X, (uint32)_editor.GameViewportWindow.RenderedViewportSize.Y);
RenderCommand.Clear(_gameViewportTarget, .Color | .Depth, .(0.2f, 0.2f, 0.2f), 1.0f, 0);
_gameSceneRenderer.RenderRuntime(gameTime, _gameViewportTarget);
@@ -960,10 +960,10 @@ namespace GlitchyEditor
{
/*
* Update the viewport size because if the game windows size changed in
* "Game"-mode the updated aspect-rations will reset once we go back
* "Game"-mode the updated aspect-ratios will reset once we go back
* into "Editor"-mode (because Game-Mode works on a copy of the scene).
*/
GameViewportSizeChanged(null, _editor.GameViewportWindow.ViewportSize);
GameViewportSizeChanged(null, _editor.GameViewportWindow.RenderedViewportSize);
}
if (Application.Instance.Settings.EditorSettings.SwitchToEditorOnStop)
+71
View File
@@ -864,4 +864,75 @@ static
// transpose und determinante für Matrizen
// sin, cos, tan, asin, acos, atan, atan2, cosh, sinh, tanh
#region Fancy Stuff
/// @brief Calculates the greates common divisor (greates common factor) of two unsgined integer a and b.
/// Based on https://en.wikipedia.org/wiki/Binary_GCD_algorithm
public static uint64 gcd(uint64 u, uint64 v)
{
if (u == 0)
return v;
if (v == 0)
return u;
var u, v;
// Essentially we want to know how many trailing zeros a and b have.
// TODO: If we have an efficient algorithm for that, this might become a lot faster.
uint64 trailingZerosU = 0;
while ((u % 2) == 0)
{
u >>= 1;
trailingZerosU++;
}
uint64 trailingZerosV = 0;
while ((v % 2) == 0)
{
v >>= 1;
trailingZerosV++;
}
uint64 k = Math.Min(trailingZerosU, trailingZerosV);
while (true)
{
Log.EngineLogger.AssertDebug(u % 2 == 1, "a should be odd");
Log.EngineLogger.AssertDebug(v % 2 == 1, "b should be odd");
if (u > v)
{
(u, v) = (v, u);
}
v -= u;
// b is now even
if (v == 0)
{
return u << k;
}
// Make v odd again
while ((v % 2) == 0)
{
v >>= 1;
}
}
}
/// @brief Calculates the greates common divisor (greates common factor) of two signed integer a and b.
/// Based on https://en.wikipedia.org/wiki/Binary_GCD_algorithm
public static int64 gcd(int64 u, int64 v)
{
return (int64)gcd((uint64)Math.Abs(u), (uint64)Math.Abs(v));
}
public static this()
{
int64 i = (int64)gcd(38, 123);
}
#endregion
}