Unfuck rendering widget (#5362)

This commit is contained in:
Layla
2026-07-15 11:41:10 +01:00
committed by GitHub
parent 15f77a8629
commit a4af9e7702
6 changed files with 18 additions and 86 deletions

View File

@@ -25,12 +25,6 @@ native class ads::CDockManager as Native.CDockManager : QWidget
ads::CDockManager::setConfigFlag( ads::CDockManager::OpaqueSplitterResize, true );
ads::CDockManager::setConfigFlag( ads::CDockManager::TabCloseButtonIsToolButton, true );
ads::CDockManager::setConfigFlag( ads::CDockManager::DisableStylesheet, true );
// Dock containers host native render widgets (swapchains). Making dock/area/floating
// windows native keeps the widget hierarchy natively parented, so floating and
// re-docking doesn't destroy and recreate the render widget's window out from
// under its swapchain.
ads::CDockManager::setConfigFlag( ads::CDockManager::UseNativeWindows, true );
}
static CDockManager Create( QWidget parent ); [new]

View File

@@ -145,12 +145,6 @@ native class QWidget as Native.QWidget : QObject
QWidget parentWidget();
QWidget window();
inline void setTransientParent( QWidget parent )
{
self->winId();
parent->winId();
self->windowHandle()->setTransientParent( parent->windowHandle() );
}
void AddClassName( QString name );
void Polish();

View File

@@ -27,24 +27,19 @@ internal partial class FullScreenManager
/// </summary>
public void Clear()
{
if ( Widget is null )
if ( !Widget.IsValid() )
return;
EditorWindow.DockManager.Visible = true;
if ( EditorWindow.Console.IsValid() )
{
EditorWindow.Console.Input.FocusMode = FocusMode.TabOrClick;
}
if ( Widget.IsValid() )
{
Widget.Parent = PreviousParent;
Widget.Parent = PreviousParent;
if ( PreviousParent.IsValid() )
{
PreviousParent.Layout?.Add( Widget );
}
if ( PreviousParent.IsValid() )
{
PreviousParent.Layout?.Add( Widget );
}
Widget = null;
@@ -54,15 +49,8 @@ internal partial class FullScreenManager
[EditorEvent.Frame]
public void OnFrame()
{
if ( Widget is null )
return;
// If the widget died while fullscreen, restore the docks
if ( !Widget.IsValid() )
{
Clear();
return;
}
if ( Widget.Size != GetTargetSize() || Widget.Position != GetTargetPosition() )
{
@@ -113,10 +101,6 @@ internal partial class FullScreenManager
// Set our target widget's parent to the editor's main window, so we can size it properly
widget.Parent = EditorWindow;
// The docks are native windows, which always draw above sibling widget content,
// so the fullscreen widget can't simply cover them - hide them while it's active
EditorWindow.DockManager.Visible = false;
// Make sure we kill focus from the console
if ( EditorWindow.Console.IsValid() )
{

View File

@@ -12,9 +12,6 @@ public class SceneRenderingWidget : Frame
internal SwapChainHandle_t SwapChain;
// A released swapchain is destroyed at frame end and owns our window until then
private bool _destroyPending;
/// <summary>
/// The active scene that we're rendering
/// </summary>
@@ -39,10 +36,6 @@ public class SceneRenderingWidget : Frame
public SceneRenderingWidget( Widget parent = null ) : base( parent )
{
// Keep ancestors non-native, otherwise every dock splitter above us becomes a
// real HWND and resizes recurse through Win32 until stack overflow. Must be
// set before WA_NativeWindow. See qtoolscenewidget.cpp for the full story.
SetFlag( Flag.WA_DontCreateNativeAncestors, true );
SetFlag( Flag.WA_NativeWindow, true );
SetFlag( Flag.WA_PaintOnScreen, true );
SetFlag( Flag.WA_NoSystemBackground, true );
@@ -69,7 +62,10 @@ public class SceneRenderingWidget : Frame
All.Remove( this );
RenderSettings.Instance.OnVideoSettingsChanged -= HandleVideoChanged;
ReleaseSwapChain();
// The swapchain might still be in use by native, so defer its destruction until the end of the frame.
// Otherwise, a race condition could occur where render targets are accessed after destruction, causing a delayed crash.
EngineLoop.DisposeAtFrameEnd( new Sandbox.Utility.DisposeAction( () => g_pRenderDevice.DestroySwapChain( SwapChain ) ) );
SwapChain = default;
GizmoInstance?.Dispose();
GizmoInstance = default;
@@ -162,43 +158,12 @@ public class SceneRenderingWidget : Frame
UpdateGizmoInputs( ref GizmoInstance.Input, camera, hasMouseFocus );
}
// Qt destroys and recreates our native window when a dock reparents us into
// another window - release the swapchain and Render will rebuild it against
// the new handle.
internal override void OnWinIdChanged()
{
ReleaseSwapChain();
}
void ReleaseSwapChain()
{
if ( SwapChain == default ) return;
// The swapchain might still be in use by native, so defer its destruction until the end of the frame.
// Otherwise, a race condition could occur where render targets are accessed after destruction, causing a delayed crash.
var swapChain = SwapChain;
_destroyPending = true;
EngineLoop.DisposeAtFrameEnd( new Sandbox.Utility.DisposeAction( () =>
{
g_pRenderDevice.DestroySwapChain( swapChain );
_destroyPending = false;
} ) );
SwapChain = default;
}
void Render()
{
if ( !Scene.IsValid() ) return;
if ( !Visible ) return;
if ( SwapChain == default )
{
// The retired swapchain still owns our window until it's destroyed at
// frame end - creating a second one against it would fail, wait a frame.
if ( _destroyPending ) return;
SwapChain = WidgetUtil.CreateSwapChain( _widget, RenderSettings.Instance.AntiAliasQuality.ToEngine() );
}
if ( SwapChain == default ) return;
using ( Scene.Push() )
{
@@ -280,10 +245,15 @@ public class SceneRenderingWidget : Frame
internal void HandleVideoChanged()
{
// No swapchain right now - Render will create one with the current settings
if ( SwapChain == default ) return;
var msaaAmount = RenderSettings.Instance.AntiAliasQuality.ToEngine();
WidgetUtil.UpdateSwapChainMSAA( SwapChain, RenderSettings.Instance.AntiAliasQuality.ToEngine() );
if ( SwapChain == default )
{
SwapChain = WidgetUtil.CreateSwapChain( _widget, msaaAmount );
return;
}
WidgetUtil.UpdateSwapChainMSAA( SwapChain, msaaAmount );
}
internal static void RenderAll()

View File

@@ -1209,15 +1209,6 @@ namespace Editor
{
return QObject.FindOrCreate( _widget.window() ) as Widget;
}
/// <summary>
/// Keeps this window above its owner without making it globally topmost.
/// </summary>
public void SetTransientParent( Widget parent )
{
ArgumentNullException.ThrowIfNull( parent );
_widget.setTransientParent( parent._widget );
}
}

View File

@@ -12,7 +12,6 @@ public class SceneOverlayWidget : Widget
NoSystemBackground = true;
WindowFlags = WindowFlags.FramelessWindowHint | WindowFlags.Tool;
SetTransientParent( EditorWindow );
Active = this;