diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index 8084d638e..f17deb4bd 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -8,6 +8,7 @@ using FlaxEditor.GUI.Input; using FlaxEditor.Options; using FlaxEngine; using FlaxEngine.GUI; +using FlaxEngine.Json; namespace FlaxEditor.Windows { @@ -28,6 +29,14 @@ namespace FlaxEditor.Windows private GUI.Docking.DockPanel _maximizeRestoreDockTo; private CursorLockMode _cursorLockMode = CursorLockMode.None; + // Viewport scaling variables + private List _defaultViewportScaling = new List(); + private List _customViewportScaling = new List(); + private float _viewportAspectRatio = 1; + private float _windowAspectRatio = 1; + private bool _useAspect = false; + private bool _freeAspect = true; + /// /// Gets the viewport. /// @@ -106,6 +115,35 @@ namespace FlaxEditor.Windows /// public bool FocusOnPlay { get; set; } + private enum ViewportScaleType + { + Resolution = 0, + Aspect = 1, + } + + private class ViewportScaleOptions + { + /// + /// The name. + /// + public string Label; + + /// + /// The Type of scaling to do. + /// + public ViewportScaleType ScaleType; + + /// + /// The width and height to scale by. + /// + public Int2 Size; + + /// + /// If the scaling is active. + /// + public bool Active; + } + private class GameRoot : ContainerControl { public bool EnableEvents => !Time.GamePaused; @@ -255,6 +293,9 @@ namespace FlaxEditor.Windows Parent = _viewport }; RootControl.GameRoot = _guiRoot; + + SizeChanged += control => { ResizeViewport(); }; + Editor.StateMachine.PlayingState.SceneDuplicating += PlayingStateOnSceneDuplicating; Editor.StateMachine.PlayingState.SceneRestored += PlayingStateOnSceneRestored; @@ -267,6 +308,85 @@ namespace FlaxEditor.Windows InputActions.Add(options => options.StepFrame, Editor.Simulation.RequestPlayOneFrame); } + private void ChangeViewportRatio(ViewportScaleOptions v) + { + if (v == null) + return; + + if (v.Size.Y <= 0 || v.Size.X <= 0) + { + return; + } + + if (string.Equals(v.Label, "Free Aspect") && v.Size == new Int2(1, 1)) + { + _freeAspect = true; + _useAspect = true; + } + else + { + switch (v.ScaleType) + { + case ViewportScaleType.Aspect: + _useAspect = true; + _freeAspect = false; + break; + case ViewportScaleType.Resolution: + _useAspect = false; + _freeAspect = false; + break; + } + } + + _viewportAspectRatio = (float)v.Size.X / v.Size.Y; + + if (!_freeAspect) + { + if (!_useAspect) + { + _viewport.KeepAspectRatio = true; + _viewport.CustomResolution = new Int2(v.Size.X, v.Size.Y); + } + else + { + _viewport.CustomResolution = new Int2?(); + _viewport.KeepAspectRatio = true; + } + } + else + { + _viewport.CustomResolution = new Int2?(); + _viewport.KeepAspectRatio = false; + } + + ResizeViewport(); + } + + private void ResizeViewport() + { + if (!_freeAspect) + { + _windowAspectRatio = Width / Height; + } + else + { + _windowAspectRatio = 1; + } + + var scaleWidth = _viewportAspectRatio / _windowAspectRatio; + var scaleHeight = _windowAspectRatio / _viewportAspectRatio; + + if (scaleHeight < 1) + { + _viewport.Bounds = new Rectangle(0, Height * (1 - scaleHeight) / 2, Width, Height * scaleHeight); + } + else + { + _viewport.Bounds = new Rectangle(Width * (1 - scaleWidth) / 2, 0, Width * scaleWidth, Height); + } + PerformLayout(); + } + private void OnPostRender(GPUContext context, ref RenderContext renderContext) { // Debug Draw shapes @@ -372,6 +492,53 @@ namespace FlaxEditor.Windows resolutionValue.ValueChanged += () => _viewport.ResolutionScale = resolutionValue.Value; } + // Viewport aspect ratio + { + // Create default scaling options if they dont exist from deserialization. + if (_defaultViewportScaling.Count == 0) + { + _defaultViewportScaling.Add(new ViewportScaleOptions + { + Label = "Free Aspect", + ScaleType = ViewportScaleType.Aspect, + Size = new Int2(1, 1), + Active = true, + }); + _defaultViewportScaling.Add(new ViewportScaleOptions + { + Label = "16:9 Aspect", + ScaleType = ViewportScaleType.Aspect, + Size = new Int2(16, 9), + Active = false, + }); + _defaultViewportScaling.Add(new ViewportScaleOptions + { + Label = "16:10 Aspect", + ScaleType = ViewportScaleType.Aspect, + Size = new Int2(16, 10), + Active = false, + }); + _defaultViewportScaling.Add(new ViewportScaleOptions + { + Label = "1920x1080 Resolution", + ScaleType = ViewportScaleType.Resolution, + Size = new Int2(1920, 1080), + Active = false, + }); + _defaultViewportScaling.Add(new ViewportScaleOptions + { + Label = "2560x1440 Resolution", + ScaleType = ViewportScaleType.Resolution, + Size = new Int2(2560, 1440), + Active = false, + }); + } + + var vsMenu = menu.AddChildMenu("Viewport Size").ContextMenu; + + CreateViewportSizingContextMenu(vsMenu); + } + // Take Screenshot { var takeScreenshot = menu.AddButton("Take Screenshot"); @@ -400,6 +567,243 @@ namespace FlaxEditor.Windows menu.AddSeparator(); } + private void CreateViewportSizingContextMenu(ContextMenu vsMenu) + { + // Add default viewport sizing options + for (int i = 0; i < _defaultViewportScaling.Count; i++) + { + var viewportScale = _defaultViewportScaling[i]; + var button = vsMenu.AddButton(viewportScale.Label); + button.CloseMenuOnClick = false; + button.Icon = viewportScale.Active ? Style.Current.CheckBoxTick : SpriteHandle.Invalid; + button.Tag = viewportScale; + if (viewportScale.Active) + ChangeViewportRatio(viewportScale); + + button.Clicked += () => + { + if (button.Tag == null) + return; + + // Reset selected icon on all buttons + foreach (var child in vsMenu.Items) + { + if (child is ContextMenuButton cmb && cmb.Tag is ViewportScaleOptions v) + { + if (cmb == button) + { + v.Active = true; + button.Icon = Style.Current.CheckBoxTick; + ChangeViewportRatio(v); + } + else if (v.Active) + { + cmb.Icon = SpriteHandle.Invalid; + v.Active = false; + } + } + } + }; + } + if (_defaultViewportScaling.Count != 0) + vsMenu.AddSeparator(); + + // Add custom viewport options + for (int i = 0; i < _customViewportScaling.Count; i++) + { + var viewportScale = _customViewportScaling[i]; + var childCM = vsMenu.AddChildMenu(viewportScale.Label); + childCM.CloseMenuOnClick = false; + childCM.Icon = viewportScale.Active ? Style.Current.CheckBoxTick : SpriteHandle.Invalid; + childCM.Tag = viewportScale; + if (viewportScale.Active) + ChangeViewportRatio(viewportScale); + + var applyButton = childCM.ContextMenu.AddButton("Apply"); + applyButton.Tag = childCM.Tag = viewportScale; + applyButton.CloseMenuOnClick = false; + applyButton.Clicked += () => + { + if (childCM.Tag == null) + return; + + // Reset selected icon on all buttons + foreach (var child in vsMenu.Items) + { + if (child is ContextMenuButton cmb && cmb.Tag is ViewportScaleOptions v) + { + if (child == childCM) + { + v.Active = true; + childCM.Icon = Style.Current.CheckBoxTick; + ChangeViewportRatio(v); + } + else if (v.Active) + { + cmb.Icon = SpriteHandle.Invalid; + v.Active = false; + } + } + } + }; + + var deleteButton = childCM.ContextMenu.AddButton("Delete"); + deleteButton.CloseMenuOnClick = false; + deleteButton.Clicked += () => + { + if (childCM.Tag == null) + return; + + var v = (ViewportScaleOptions)childCM.Tag; + if (v.Active) + { + v.Active = false; + _defaultViewportScaling[0].Active = true; + ChangeViewportRatio(_defaultViewportScaling[0]); + } + _customViewportScaling.Remove(v); + vsMenu.DisposeAllItems(); + CreateViewportSizingContextMenu(vsMenu); + vsMenu.PerformLayout(); + }; + } + if (_customViewportScaling.Count != 0) + vsMenu.AddSeparator(); + + // Add button + var add = vsMenu.AddButton("Add..."); + add.CloseMenuOnClick = false; + add.Clicked += () => + { + var popup = new ContextMenuBase + { + Size = new Float2(230, 125), + ClipChildren = false, + CullChildren = false, + }; + popup.Show(add, new Float2(add.Width, 0)); + + var nameLabel = new Label + { + Parent = popup, + AnchorPreset = AnchorPresets.TopLeft, + Text = "Name", + HorizontalAlignment = TextAlignment.Near, + }; + nameLabel.LocalX += 10; + nameLabel.LocalY += 10; + + var nameTextBox = new TextBox + { + Parent = popup, + AnchorPreset = AnchorPresets.TopLeft, + IsMultiline = false, + }; + nameTextBox.LocalX += 100; + nameTextBox.LocalY += 10; + + var typeLabel = new Label + { + Parent = popup, + AnchorPreset = AnchorPresets.TopLeft, + Text = "Type", + HorizontalAlignment = TextAlignment.Near, + }; + typeLabel.LocalX += 10; + typeLabel.LocalY += 35; + + var typeDropdown = new Dropdown + { + Parent = popup, + AnchorPreset = AnchorPresets.TopLeft, + Items = { "Aspect", "Resolution" }, + SelectedItem = "Aspect", + Width = nameTextBox.Width + }; + typeDropdown.LocalY += 35; + typeDropdown.LocalX += 100; + + var whLabel = new Label + { + Parent = popup, + AnchorPreset = AnchorPresets.TopLeft, + Text = "Width & Height", + HorizontalAlignment = TextAlignment.Near, + }; + whLabel.LocalX += 10; + whLabel.LocalY += 60; + + var wValue = new IntValueBox(16) + { + Parent = popup, + AnchorPreset = AnchorPresets.TopLeft, + MinValue = 1, + Width = 55, + }; + wValue.LocalY += 60; + wValue.LocalX += 100; + + var hValue = new IntValueBox(9) + { + Parent = popup, + AnchorPreset = AnchorPresets.TopLeft, + MinValue = 1, + Width = 55, + }; + hValue.LocalY += 60; + hValue.LocalX += 165; + + var submitButton = new Button + { + Parent = popup, + AnchorPreset = AnchorPresets.TopLeft, + Text = "Submit", + Width = 70, + }; + submitButton.LocalX += 40; + submitButton.LocalY += 90; + + submitButton.Clicked += () => + { + Enum.TryParse(typeDropdown.SelectedItem, out ViewportScaleType type); + + var combineString = type == ViewportScaleType.Aspect ? ":" : "x"; + var name = nameTextBox.Text + " (" + wValue.Value + combineString + hValue.Value + ") " + typeDropdown.SelectedItem; + + var newViewportOption = new ViewportScaleOptions + { + ScaleType = type, + Label = name, + Size = new Int2(wValue.Value, hValue.Value), + }; + + _customViewportScaling.Add(newViewportOption); + vsMenu.DisposeAllItems(); + CreateViewportSizingContextMenu(vsMenu); + vsMenu.PerformLayout(); + }; + + var cancelButton = new Button + { + Parent = popup, + AnchorPreset = AnchorPresets.TopLeft, + Text = "Cancel", + Width = 70, + }; + cancelButton.LocalX += 120; + cancelButton.LocalY += 90; + + cancelButton.Clicked += () => + { + nameTextBox.Clear(); + typeDropdown.SelectedItem = "Aspect"; + hValue.Value = 9; + wValue.Value = 16; + popup.Hide(); + }; + }; + } + /// public override void Draw() { @@ -471,7 +875,7 @@ namespace FlaxEditor.Windows Screen.CursorVisible = true; if (Screen.CursorLock == CursorLockMode.Clipped) Screen.CursorLock = CursorLockMode.None; - + // Defocus _isUnlockingMouse = true; Focus(null); @@ -624,6 +1028,8 @@ namespace FlaxEditor.Windows { writer.WriteAttributeString("ShowGUI", ShowGUI.ToString()); writer.WriteAttributeString("ShowDebugDraw", ShowDebugDraw.ToString()); + writer.WriteAttributeString("DefaultViewportScaling", JsonSerializer.Serialize(_defaultViewportScaling)); + writer.WriteAttributeString("CustomViewportScaling", JsonSerializer.Serialize(_customViewportScaling)); } /// @@ -633,6 +1039,23 @@ namespace FlaxEditor.Windows ShowGUI = value1; if (bool.TryParse(node.GetAttribute("ShowDebugDraw"), out value1)) ShowDebugDraw = value1; + if (node.HasAttribute("CustomViewportScaling")) + _customViewportScaling = JsonSerializer.Deserialize>(node.GetAttribute("CustomViewportScaling")); + + for (int i = 0; i < _customViewportScaling.Count; i++) + { + if (_customViewportScaling[i].Active) + ChangeViewportRatio(_customViewportScaling[i]); + } + + if (node.HasAttribute("DefaultViewportScaling")) + _defaultViewportScaling = JsonSerializer.Deserialize>(node.GetAttribute("DefaultViewportScaling")); + + for (int i = 0; i < _defaultViewportScaling.Count; i++) + { + if (_defaultViewportScaling[i].Active) + ChangeViewportRatio(_defaultViewportScaling[i]); + } } /// diff --git a/Source/Engine/Renderer/AmbientOcclusionPass.cpp b/Source/Engine/Renderer/AmbientOcclusionPass.cpp index a17d46e5f..571e45b12 100644 --- a/Source/Engine/Renderer/AmbientOcclusionPass.cpp +++ b/Source/Engine/Renderer/AmbientOcclusionPass.cpp @@ -206,7 +206,7 @@ void AmbientOcclusionPass::Render(RenderContext& renderContext) if (renderContext.List == nullptr) return; auto& aoSettings = renderContext.List->Settings.AmbientOcclusion; - if (aoSettings.Enabled == false || (renderContext.View.Flags & ViewFlags::AO) == 0) + if (aoSettings.Enabled == false || (renderContext.View.Flags & ViewFlags::AO) == 0 || Math::Min(renderContext.Buffers->GetWidth(), renderContext.Buffers->GetHeight()) < 16) return; // TODO: add support for SSAO in ortho projection