From abb5de31dfe2e489b7ca981a3a4ea3f31ce8602c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 5 Aug 2026 20:41:48 +0200 Subject: [PATCH 01/16] Fix game window resize to maintain placement within screen (eg. centered) #3699 --- Source/Engine/Engine/Screen.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Engine/Screen.cpp b/Source/Engine/Engine/Screen.cpp index 48593b3fa..71f144214 100644 --- a/Source/Engine/Engine/Screen.cpp +++ b/Source/Engine/Engine/Screen.cpp @@ -271,7 +271,11 @@ void ScreenService::Draw() auto win = Engine::MainWindow; if (win) { - win->SetClientSize(Size.GetValue()); + Float2 sizeDelta = Size.GetValue() - win->GetClientSize(); + Rectangle newBiunds(win->GetClientPosition(), Size.GetValue()); + if (!win->IsMaximized()) + newBiunds -= sizeDelta * 0.5f; + win->SetClientBounds(newBiunds); } Size.Reset(); From 39ffdcc0966c1f948656d964cf82bd943edb8cf0 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Thu, 6 Aug 2026 01:46:57 +0300 Subject: [PATCH 02/16] Fix SDL display server name when running on Linux --- Source/Engine/Platform/Linux/LinuxPlatform.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp index 55354347a..680de1dfb 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp +++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp @@ -1803,7 +1803,11 @@ const String& LinuxPlatform::GetHomeDirectory() String LinuxPlatform::GetDisplayServer() { +#if PLATFORM_SDL + return SDLPlatform::GetDisplayServer(); +#else return xDisplay ? TEXT("X11") : TEXT(""); +#endif } bool LinuxPlatform::Is64BitPlatform() From e0346f506e292f51869e1c26733d2f788a2b2beb Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 6 Aug 2026 06:56:41 +0200 Subject: [PATCH 03/16] Fix game window resolution when resizing during fullscreen on Windows #3699 --- .../GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp | 7 ++++--- Source/Engine/Platform/Windows/WindowsWindow.cpp | 11 +++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp index 3856a7cd0..9a32ec076 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp @@ -162,11 +162,8 @@ void GPUSwapChainDX11::Present(bool vsync) bool GPUSwapChainDX11::Resize(int32 width, int32 height) { - // Check if size won't change if (width == _width && height == _height) - { return false; - } _device->WaitForGPU(); GPUDeviceLock lock(_device); @@ -174,6 +171,10 @@ bool GPUSwapChainDX11::Resize(int32 width, int32 height) _allowTearing = _device->_allowTearing; #endif _format = GPU_BACK_BUFFER_PIXEL_FORMAT; + if (_memoryUsage != 0) + { + PROFILE_MEM_DEC(Graphics, _memoryUsage); + } #if PLATFORM_WINDOWS DXGI_SWAP_CHAIN_DESC swapChainDesc; diff --git a/Source/Engine/Platform/Windows/WindowsWindow.cpp b/Source/Engine/Platform/Windows/WindowsWindow.cpp index 3a1cf0dd3..b028e3fc3 100644 --- a/Source/Engine/Platform/Windows/WindowsWindow.cpp +++ b/Source/Engine/Platform/Windows/WindowsWindow.cpp @@ -409,6 +409,17 @@ void WindowsWindow::SetClientBounds(const Rectangle& clientArea) int32 width = (int32)clientArea.GetWidth(); int32 height = (int32)clientArea.GetHeight(); + // Resize during fullscreen + if (changeSize && _swapChain && _swapChain->IsFullscreen()) + { + // Go out fullscreen, resize, and then go back in + _swapChain->SetFullscreen(false); + _clientSize = clientArea.Size; + OnResize(width, height); + _swapChain->SetFullscreen(true); + return; + } + if (changeSize) { _clientSize = clientArea.Size; From ef35bb906d8eee19ea3101b573037bf8e96aaba8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 6 Aug 2026 06:57:26 +0200 Subject: [PATCH 04/16] Fix game window to maintain resolution and be centered when going out the fullscreen on Windows #3699 --- Source/Engine/Engine/Screen.cpp | 2 -- .../Engine/Platform/Windows/WindowsWindow.cpp | 30 +++++++++++++++++-- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Engine/Screen.cpp b/Source/Engine/Engine/Screen.cpp index 71f144214..4902c885a 100644 --- a/Source/Engine/Engine/Screen.cpp +++ b/Source/Engine/Engine/Screen.cpp @@ -209,8 +209,6 @@ void Screen::SetGameWindowMode(GameWindowMode windowMode) switch (windowMode) { case GameWindowMode::Windowed: - if (GetIsFullscreen()) - SetIsFullscreen(false); win->SetBorderless(false, false); break; case GameWindowMode::Fullscreen: diff --git a/Source/Engine/Platform/Windows/WindowsWindow.cpp b/Source/Engine/Platform/Windows/WindowsWindow.cpp index b028e3fc3..bd682c7c1 100644 --- a/Source/Engine/Platform/Windows/WindowsWindow.cpp +++ b/Source/Engine/Platform/Windows/WindowsWindow.cpp @@ -265,8 +265,13 @@ void WindowsWindow::SetBorderless(bool isBorderless, bool maximized) { ASSERT(HasHWND()); + Float2 preserveSize(0, 0); if (IsFullscreen()) + { + if (_swapChain && !maximized) + preserveSize = _swapChain->GetSize(); SetIsFullscreen(false); + } // Fixes issue of borderless window not going full screen if (IsMaximized()) @@ -301,6 +306,16 @@ void WindowsWindow::SetBorderless(bool isBorderless, bool maximized) { ShowWindow(_handle, SW_SHOW); } + + // Maintain resolution when going out the fullscreen + if (preserveSize != Float2::Zero) + { + Int4 monitorBounds; + GetScreenInfo(monitorBounds.X, monitorBounds.Y, monitorBounds.Z, monitorBounds.W); + monitorBounds.X += (monitorBounds.Z - (int32)preserveSize.X) / 2; + monitorBounds.Y += (monitorBounds.W - (int32)preserveSize.Y) / 2; + SetWindowPos(_handle, nullptr, monitorBounds.X, monitorBounds.Y, (int32)preserveSize.X, (int32)preserveSize.Y, SWP_NOZORDER | SWP_NOACTIVATE); + } } else { @@ -313,13 +328,22 @@ void WindowsWindow::SetBorderless(bool isBorderless, bool maximized) if (_settings.HasSizingFrame) lStyle |= WS_THICKFRAME; lStyle |= WS_OVERLAPPED | WS_SYSMENU | WS_BORDER | WS_CAPTION; - SetWindowLong(_handle, GWL_STYLE, lStyle); + const Float2 clientSize = GetClientSize(); const Float2 desktopSize = Platform::GetDesktopSize(); - // Move window and half size if it is larger than desktop size - if (clientSize.X >= desktopSize.X && clientSize.Y >= desktopSize.Y) + if (preserveSize != Float2::Zero) { + // Maintain resolution when going out the fullscreen + Int4 monitorBounds; + GetScreenInfo(monitorBounds.X, monitorBounds.Y, monitorBounds.Z, monitorBounds.W); + monitorBounds.X += (monitorBounds.Z - (int32)preserveSize.X) / 2; + monitorBounds.Y += (monitorBounds.W - (int32)preserveSize.Y) / 2; + SetWindowPos(_handle, nullptr, monitorBounds.X, monitorBounds.Y, (int32)preserveSize.X, (int32)preserveSize.Y, SWP_NOZORDER | SWP_NOACTIVATE); + } + else if (clientSize.X >= desktopSize.X && clientSize.Y >= desktopSize.Y) + { + // Move window and half size if it is larger than desktop size const Float2 halfSize = desktopSize * 0.5f; const Float2 middlePos = halfSize * 0.5f; SetWindowPos(_handle, nullptr, (int)middlePos.X, (int)middlePos.Y, (int)halfSize.X, (int)halfSize.Y, SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOACTIVATE); From de0bf0d552358107a3879cc2b575e1dbccceb505 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 7 Aug 2026 06:16:30 +0200 Subject: [PATCH 05/16] Fix missing distortion rendering on Deformable materials #4045 --- .../Materials/DeformableMaterialShader.cpp | 16 +++++++++++++--- .../Materials/DeformableMaterialShader.h | 4 ++++ .../MaterialGenerator/MaterialGenerator.cpp | 4 +++- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp b/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp index 6260163f4..6c7e5a20c 100644 --- a/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp @@ -112,6 +112,7 @@ bool DeformableMaterialShader::Load() auto psDesc = GPUPipelineState::Description::Default; psDesc.DepthEnable = (_info.FeaturesFlags & MaterialFeaturesFlags::DisableDepthTest) == MaterialFeaturesFlags::None; psDesc.DepthWriteEnable = (_info.FeaturesFlags & MaterialFeaturesFlags::DisableDepthWrite) == MaterialFeaturesFlags::None; + psDesc.VS = _shader->GetVS("VS_SplineModel"); #if GPU_ALLOW_TESSELLATION_SHADERS // Check if use tessellation (both material and runtime supports it) @@ -127,7 +128,6 @@ bool DeformableMaterialShader::Load() if (_shader->HasShader("PS_QuadOverdraw")) { // Quad Overdraw - psDesc.VS = _shader->GetVS("VS_SplineModel"); psDesc.PS = _shader->GetPS("PS_QuadOverdraw"); _cache.QuadOverdraw.Init(psDesc); } @@ -142,7 +142,6 @@ bool DeformableMaterialShader::Load() psDesc.StencilPassOp = StencilOperation::Replace; // GBuffer Pass - psDesc.VS = _shader->GetVS("VS_SplineModel"); psDesc.PS = _shader->GetPS("PS_GBuffer"); _cache.Default.Init(psDesc); @@ -154,7 +153,6 @@ bool DeformableMaterialShader::Load() _drawModes |= DrawPass::Forward; // Forward Pass - psDesc.VS = _shader->GetVS("VS_SplineModel"); psDesc.PS = _shader->GetPS("PS_Forward"); psDesc.DepthWriteEnable = false; psDesc.BlendMode = BlendingMode::AlphaBlend; @@ -171,6 +169,18 @@ bool DeformableMaterialShader::Load() break; } _cache.Default.Init(psDesc); + + // Check if use transparent distortion pass + if (_shader->HasShader("PS_Distortion")) + { + _drawModes |= DrawPass::Distortion; + + // Accumulate Distortion Pass + psDesc.PS = _shader->GetPS("PS_Distortion"); + psDesc.BlendMode = BlendingMode::Add; + psDesc.DepthWriteEnable = false; + _cache.Distortion.Init(psDesc); + } } // Depth Pass diff --git a/Source/Engine/Graphics/Materials/DeformableMaterialShader.h b/Source/Engine/Graphics/Materials/DeformableMaterialShader.h index 45651c090..64c40c165 100644 --- a/Source/Engine/Graphics/Materials/DeformableMaterialShader.h +++ b/Source/Engine/Graphics/Materials/DeformableMaterialShader.h @@ -14,6 +14,7 @@ private: { PipelineStateCache Default; PipelineStateCache Depth; + PipelineStateCache Distortion; #if USE_EDITOR PipelineStateCache QuadOverdraw; #endif @@ -29,6 +30,8 @@ private: case DrawPass::GlobalSurfaceAtlas: case DrawPass::Forward: return &Default; + case DrawPass::Distortion: + return &Distortion; #if USE_EDITOR case DrawPass::QuadOverdraw: return &QuadOverdraw; @@ -42,6 +45,7 @@ private: { Default.Release(); Depth.Release(); + Distortion.Release(); #if USE_EDITOR QuadOverdraw.Release(); #endif diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp index 685aa1040..dab832758 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp @@ -246,7 +246,9 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo ADD_FEATURE(TessellationFeature); if (isOpaque) ADD_FEATURE(DeferredShadingFeature); - if (materialInfo.BlendMode != MaterialBlendMode::Opaque) + if (!isOpaque && (materialInfo.FeaturesFlags & MaterialFeaturesFlags::DisableDistortion) == MaterialFeaturesFlags::None) + ADD_FEATURE(DistortionFeature); + if (!isOpaque) ADD_FEATURE(ForwardShadingFeature); break; default: From 19caa5e533a895403752eda0bec9fa5ef336fd57 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 7 Aug 2026 07:08:17 +0200 Subject: [PATCH 06/16] Fix material drag over highlights to not flicker --- .../Editor/Viewport/MainEditorGizmoViewport.cs | 4 +++- Source/Editor/Viewport/PrefabWindowViewport.cs | 4 +++- Source/Editor/Viewport/ViewportDraggingHelper.cs | 16 +++++++++++++--- Source/Editor/ViewportDebugDrawData.cs | 2 ++ 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index 51b240202..f457edd29 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -787,10 +787,12 @@ namespace FlaxEditor.Viewport /// public override DragDropEffect OnDragMove(ref Float2 location, DragData data) { - DragHandlers.ClearDragEffects(); var result = base.OnDragMove(ref location, data); if (result != DragDropEffect.None) + { + DragHandlers.ClearDragEffects(); return result; + } return DragHandlers.DragEnter(ref location, data); } diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index a599615de..0a3b4b4e5 100644 --- a/Source/Editor/Viewport/PrefabWindowViewport.cs +++ b/Source/Editor/Viewport/PrefabWindowViewport.cs @@ -647,10 +647,12 @@ namespace FlaxEditor.Viewport /// public override DragDropEffect OnDragMove(ref Float2 location, DragData data) { - DragHandlers.ClearDragEffects(); var result = base.OnDragMove(ref location, data); if (result != DragDropEffect.None) + { + DragHandlers.ClearDragEffects(); return result; + } return DragHandlers.DragEnter(ref location, data); } diff --git a/Source/Editor/Viewport/ViewportDraggingHelper.cs b/Source/Editor/Viewport/ViewportDraggingHelper.cs index 3e6fce6ad..5c68b399a 100644 --- a/Source/Editor/Viewport/ViewportDraggingHelper.cs +++ b/Source/Editor/Viewport/ViewportDraggingHelper.cs @@ -65,7 +65,7 @@ namespace FlaxEditor.Viewport { if (_previewStaticModel) debugDrawData.HighlightModel(_previewStaticModel, _previewModelEntryIndex); - if (_previewBrushSurface.Brush != null) + if (_previewBrushSurface.Brush) debugDrawData.HighlightBrushSurface(_previewBrushSurface); } @@ -120,10 +120,12 @@ namespace FlaxEditor.Viewport if (_dragAssets.HasValidDrag && _dragAssets.Objects[0].IsOfType()) { GetHitLocation(ref location, out var hit, out _, out _); - ClearDragEffects(); var material = FlaxEngine.Content.LoadAsync(_dragAssets.Objects[0].ID); - if (material.IsDecal) + if (material && material.IsDecal) + { + ClearDragEffects(); return; + } if (hit is StaticModelNode staticModelNode) { @@ -135,6 +137,14 @@ namespace FlaxEditor.Viewport { _previewBrushSurface = brushSurfaceNode.Surface; } + else + { + ClearDragEffects(); + } + } + else + { + ClearDragEffects(); } } diff --git a/Source/Editor/ViewportDebugDrawData.cs b/Source/Editor/ViewportDebugDrawData.cs index 7b5bee95c..d70ec9cfb 100644 --- a/Source/Editor/ViewportDebugDrawData.cs +++ b/Source/Editor/ViewportDebugDrawData.cs @@ -80,6 +80,8 @@ namespace FlaxEditor /// The surface. public void HighlightBrushSurface(BrushSurface surface) { + if (surface.Brush == null) + return; surface.Brush.GetVertices(surface.Index, out var vertices); if (vertices.Length > 0) { From 6489b7d7b78f87dafcf447eb41bcc68c0f6fa9d4 Mon Sep 17 00:00:00 2001 From: Jake Young Date: Fri, 7 Aug 2026 16:08:29 -0400 Subject: [PATCH 07/16] Add a new option when entering play mode to always restore to the editor window after exiting play mode. Also made it the default option on new projects. --- Source/Editor/Modules/SimulationModule.cs | 6 ++++++ Source/Editor/Options/InterfaceOptions.cs | 9 +++++++-- Source/Editor/Windows/GameWindow.cs | 12 +++++++++--- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/Source/Editor/Modules/SimulationModule.cs b/Source/Editor/Modules/SimulationModule.cs index 663c2ab32..6a7923841 100644 --- a/Source/Editor/Modules/SimulationModule.cs +++ b/Source/Editor/Modules/SimulationModule.cs @@ -287,6 +287,7 @@ namespace FlaxEditor.Modules case Options.InterfaceOptions.PlayModeFocus.None: break; case Options.InterfaceOptions.PlayModeFocus.GameWindow: + case Options.InterfaceOptions.PlayModeFocus.GameWindowThenRestoreEditor: gameWin.FocusGameViewport(); break; @@ -320,6 +321,11 @@ namespace FlaxEditor.Modules _previousWindow.Focus(); } break; + case Options.InterfaceOptions.PlayModeFocus.GameWindowThenRestoreEditor: + var editorWin = Editor.Windows.EditWin; + if (editorWin != null && !editorWin.IsDisposing) + editorWin.Focus(); + break; } } diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index a12b2fda0..f8ceb3aec 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -179,6 +179,11 @@ namespace FlaxEditor.Options /// Focus the Game Window. On play mode end restore focus to the previous window. /// GameWindowThenRestore, + + /// + /// Focus the Game Window. On play mode end restore focus to the editor window. + /// + GameWindowThenRestoreEditor } /// @@ -520,9 +525,9 @@ namespace FlaxEditor.Options /// /// Gets or sets a value indicating what panel should be focused when play mode start. /// - [DefaultValue(PlayModeFocus.GameWindow)] + [DefaultValue(PlayModeFocus.GameWindowThenRestoreEditor)] [EditorDisplay("Play In-Editor", "Focus On Play"), EditorOrder(500), Tooltip("Set what panel to focus on play mode start.")] - public PlayModeFocus FocusOnPlayMode { get; set; } = PlayModeFocus.GameWindow; + public PlayModeFocus FocusOnPlayMode { get; set; } = PlayModeFocus.GameWindowThenRestoreEditor; /// /// Gets or sets a value indicating what action should be taken upon pressing the play button. diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index d25e89f36..8c3793627 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -147,21 +147,27 @@ namespace FlaxEditor.Windows new PlayModeFocusOptions { Name = "None", - Tooltip = "Don't change focus.", + Tooltip = "Don't change window focus when entering play mode.", FocusOption = InterfaceOptions.PlayModeFocus.None, }, new PlayModeFocusOptions { Name = "Game Window", - Tooltip = "Focus the Game Window.", + Tooltip = "Focus the Game Window when entering play mode.", FocusOption = InterfaceOptions.PlayModeFocus.GameWindow, }, new PlayModeFocusOptions { Name = "Game Window Then Restore", - Tooltip = "Focus the Game Window. On play mode end restore focus to the previous window.", + Tooltip = "Focus the Game Window when entering play mode. Restore focus to the previous window when exiting play mode.", FocusOption = InterfaceOptions.PlayModeFocus.GameWindowThenRestore, }, + new PlayModeFocusOptions + { + Name = "Game Window Then Restore Editor", + Tooltip = "Focus the Game Window when entering play mode and then restore the focus to the editor window when exiting play mode.", + FocusOption = InterfaceOptions.PlayModeFocus.GameWindowThenRestoreEditor + } }; /// From 8d0fb85dd596380e3033a603477471a3cea5806b Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 8 Aug 2026 00:10:26 +0300 Subject: [PATCH 08/16] Fix content browser stealing focus from popup asset windows --- Source/Editor/GUI/ContextMenu/ContextMenuBase.cs | 2 ++ Source/Editor/Windows/ContentWindow.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs index 3947430f9..aa1952525 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs @@ -299,7 +299,9 @@ namespace FlaxEditor.GUI.ContextMenu PerformLayout(); if (UseVisibilityControl) { +#if !PLATFORM_SDL _previouslyFocused = parentWin.FocusedControl; +#endif Focus(); OnShow(); } diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 2d9daabeb..b08faeec9 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -768,9 +768,11 @@ namespace FlaxEditor.Windows _newElement.Dispose(); _newElement = null; +#if !PLATFORM_SDL // Focus content window Focus(); RootWindow?.Focus(); +#endif } // Refresh database and view now From de08b3b9a50f7138e84d5795cc9b5ff0095dbd24 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 8 Aug 2026 02:19:32 +0300 Subject: [PATCH 09/16] Fix Wayland window not getting attached to the drag operation --- Source/Engine/Platform/SDL/SDLPlatform.Linux.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Platform/SDL/SDLPlatform.Linux.cpp b/Source/Engine/Platform/SDL/SDLPlatform.Linux.cpp index 5174f4973..3bcd2ea88 100644 --- a/Source/Engine/Platform/SDL/SDLPlatform.Linux.cpp +++ b/Source/Engine/Platform/SDL/SDLPlatform.Linux.cpp @@ -297,12 +297,10 @@ namespace WaylandImpl textData.Text = *DraggingData; wl_data_source_add_listener(dataSource, &DataSourceListener, &textData); - // Begin dragging operation auto draggedWindow = Window->GetSDLWindow(); auto dragStartWindow = DragSourceWindow != nullptr ? DragSourceWindow->GetSDLWindow() : draggedWindow; wl_surface* originSurface = static_cast(SDL_GetPointerProperty(SDL_GetWindowProperties(dragStartWindow), SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, nullptr)); wl_surface* iconSurface = nullptr; - wl_data_device_start_drag(WrappedDataDevice, dataSource, originSurface, iconSurface, DragSerial); Platform::AtomicStore(&StartFlag, 1); @@ -321,11 +319,13 @@ namespace WaylandImpl if (Platform::AtomicRead(&DragOverFlag) == 1 || Platform::AtomicRead(&Serial) != DragSerial) break; - // Attach the window to the ongoing drag operation + // Begin dragging operation wrappedToplevel = static_cast(wl_proxy_create_wrapper(toplevel)); wl_proxy_set_queue(reinterpret_cast(wrappedToplevel), EventQueue); toplevelDrag = xdg_toplevel_drag_manager_v1_get_xdg_toplevel_drag(DragManager, dataSource); - + wl_data_device_start_drag(WrappedDataDevice, dataSource, originSurface, iconSurface, DragSerial); + + // Attach the window to the ongoing drag operation Float2 scaledOffset = DragOffset / Window->GetDpiScale(); xdg_toplevel_drag_v1_attach(toplevelDrag, wrappedToplevel, static_cast(scaledOffset.X), static_cast(scaledOffset.Y)); } From a057ea41ab5b044063c7f780304e788c0003c340 Mon Sep 17 00:00:00 2001 From: David Svez Date: Wed, 5 Aug 2026 23:26:22 -0500 Subject: [PATCH 10/16] Clear inactive DDGI dispatch arguments --- Source/Shaders/GI/DDGI.shader | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Shaders/GI/DDGI.shader b/Source/Shaders/GI/DDGI.shader index bade984fc..90d705f78 100644 --- a/Source/Shaders/GI/DDGI.shader +++ b/Source/Shaders/GI/DDGI.shader @@ -352,10 +352,12 @@ void CS_UpdateProbesInitArgs() { uint activeProbesCount = ActiveProbes.Load(0); // Counter at 0 activeProbesCount = min(activeProbesCount, ProbesCount); + // The CPU submits every allocated batch, so always overwrite all arguments to avoid + // dispatching stale work left by a previous cascade or frame. uint arg = 0; - for (uint probesOffset = 0; probesOffset < activeProbesCount; probesOffset += DDGI_TRACE_RAYS_PROBES_COUNT_LIMIT) + for (uint probesOffset = 0; probesOffset < ProbesCount; probesOffset += DDGI_TRACE_RAYS_PROBES_COUNT_LIMIT) { - uint probesBatchSize = min(activeProbesCount - probesOffset, DDGI_TRACE_RAYS_PROBES_COUNT_LIMIT); + uint probesBatchSize = probesOffset < activeProbesCount ? min(activeProbesCount - probesOffset, DDGI_TRACE_RAYS_PROBES_COUNT_LIMIT) : 0; UpdateProbesInitArgs[arg++] = probesBatchSize; UpdateProbesInitArgs[arg++] = 1; UpdateProbesInitArgs[arg++] = 1; From caa670949b56fd77867b052fd4cb4a84e0ad1aa4 Mon Sep 17 00:00:00 2001 From: David Svez Date: Wed, 5 Aug 2026 23:40:36 -0500 Subject: [PATCH 11/16] Clarify DDGI settings guidance --- Source/Engine/Core/Config/GraphicsSettings.h | 4 ++-- Source/Engine/Graphics/PostProcessSettings.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Core/Config/GraphicsSettings.h b/Source/Engine/Core/Config/GraphicsSettings.h index 9106b406b..95094e761 100644 --- a/Source/Engine/Core/Config/GraphicsSettings.h +++ b/Source/Engine/Core/Config/GraphicsSettings.h @@ -125,13 +125,13 @@ public: Quality GIQuality = Quality::High; /// - /// The Global Illumination probes spacing distance (in world units). Defines the quality of the GI resolution. Adjust to 200-500 to improve performance and lower frequency GI data. + /// The global spacing between Global Illumination probes (in world units). Smaller values improve interior detail at a higher GPU cost. Values around 100-150 are a useful starting point for mixed interiors and exteriors; adjust to 200-500 for mostly outdoor scenes and lower-frequency GI. Changing this value recreates the DDGI probe resources and can change the automatic cascade layout. /// API_FIELD(Attributes="EditorOrder(2120), Limit(50, 1000), EditorDisplay(\"Global Illumination\")") float GIProbesSpacing = 100; /// - /// Enables cascades splits blending for Global Illumination. + /// Enables smooth blending between Global Illumination cascade splits. If disabled, the transition uses dithering intended for temporal anti-aliasing. Smooth blending can expose rounded cascade boundaries when adjacent cascades contain significantly different lighting. /// API_FIELD(Attributes="EditorOrder(2125), DefaultValue(false), EditorDisplay(\"Global Illumination\", \"GI Cascades Blending\")") bool GICascadesBlending = false; diff --git a/Source/Engine/Graphics/PostProcessSettings.h b/Source/Engine/Graphics/PostProcessSettings.h index faebcb6f6..eb705eb58 100644 --- a/Source/Engine/Graphics/PostProcessSettings.h +++ b/Source/Engine/Graphics/PostProcessSettings.h @@ -380,7 +380,7 @@ API_STRUCT() struct FLAXENGINE_API GlobalIlluminationSettings : ISerializable float TemporalResponse = 0.9f; /// - /// Draw distance of the Global Illumination effect. Scene outside the range will use fallback irradiance. + /// Camera-centered draw distance of the Global Illumination effect. Scene outside the range will use fallback irradiance. DDGI automatically derives its cascade layout from this distance and the global probe spacing, so continuously blending this value can recreate probe resources and cause a visible lighting reset. /// API_FIELD(Attributes="EditorOrder(30), Limit(1000), PostProcessSetting((int)GlobalIlluminationSettingsOverride.Distance)") float Distance = 20000.0f; From 236e9c27c727db734ff46f519af39c70d44ec338 Mon Sep 17 00:00:00 2001 From: David Svez Date: Sat, 1 Aug 2026 20:15:18 -0500 Subject: [PATCH 12/16] Window live resize + second monitor High DPI support without turning black --- .../Engine/Platform/Windows/WindowsWindow.cpp | 38 ++++++++++++------- .../Engine/Platform/Windows/WindowsWindow.h | 3 +- Source/Engine/Renderer/PostProcessingPass.cpp | 3 +- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/Source/Engine/Platform/Windows/WindowsWindow.cpp b/Source/Engine/Platform/Windows/WindowsWindow.cpp index bd682c7c1..7a6f53507 100644 --- a/Source/Engine/Platform/Windows/WindowsWindow.cpp +++ b/Source/Engine/Platform/Windows/WindowsWindow.cpp @@ -5,7 +5,7 @@ #include "WindowsWindow.h" #include "WindowsPlatform.h" #include "WindowsInput.h" -#include "Engine/Core/Log.h" +#include "Engine/Engine/Engine.h" #include "Engine/Core/Math/Math.h" #include "Engine/Graphics/GPUSwapChain.h" #include "Engine/Graphics/RenderTask.h" @@ -731,7 +731,7 @@ void WindowsWindow::SetCursor(CursorType type) UpdateCursor(); } -void WindowsWindow::CheckForWindowResize() +void WindowsWindow::CheckForWindowResize(bool force) { // Skip for minimized window (GetClientRect for minimized window returns 0) if (_minimized) @@ -766,7 +766,7 @@ void WindowsWindow::CheckForWindowResize() _clientSize = Float2(static_cast(width), static_cast(height)); // Check if window size has been changed - if (width > 0 && height > 0 && (_swapChain == nullptr || width != _swapChain->GetWidth() || height != _swapChain->GetHeight())) + if (width > 0 && height > 0 && (force || _swapChain == nullptr || width != _swapChain->GetWidth() || height != _swapChain->GetHeight())) { UpdateRegion(); OnResize(width, height); @@ -919,16 +919,24 @@ LRESULT WindowsWindow::WndProc(UINT msg, WPARAM wParam, LPARAM lParam) case WM_PAINT: { // Check if window is during resizing - if (_isResizing && _swapChain) + if ((_isResizing || _forceRedrawOnPaint) && _swapChain) { - // Redraw window backbuffer on DX11 - switch (GPUDevice::Instance->GetRendererType()) + _forceRedrawOnPaint = false; + if (GPUDevice::Instance && !GPUDevice::Instance->IsRendering() && GPUDevice::Instance->CanDraw()) { - case RendererType::DirectX10: - case RendererType::DirectX10_1: - case RendererType::DirectX11: - _swapChain->Present(false); - break; + Engine::OnDraw(); + } + else if (GPUDevice::Instance) + { + // Redraw window backbuffer on DX11 + switch (GPUDevice::Instance->GetRendererType()) + { + case RendererType::DirectX10: + case RendererType::DirectX10_1: + case RendererType::DirectX11: + _swapChain->Present(false); + break; + } } } break; @@ -1228,8 +1236,8 @@ LRESULT WindowsWindow::WndProc(UINT msg, WPARAM wParam, LPARAM lParam) else if (_isResizing) { // If we're neither maximized nor minimized, the window size is changing by the user dragging the window edges. - // In this case, we don't resize yet -- we wait until the user stops dragging, and a WM_EXITSIZEMOVE message comes. - UpdateRegion(); + CheckForWindowResize(); + RedrawWindow(_handle, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW); } else if (_isSwitchingFullScreen) { @@ -1251,6 +1259,10 @@ LRESULT WindowsWindow::WndProc(UINT msg, WPARAM wParam, LPARAM lParam) _dpiScale = (float)_dpi / (float)DefaultDPI; RECT* windowRect = (RECT*)lParam; SetWindowPos(_handle, nullptr, windowRect->left, windowRect->top, windowRect->right - windowRect->left, windowRect->bottom - windowRect->top, SWP_NOZORDER | SWP_NOACTIVATE); + CheckForWindowResize(true); + UpdateRegion(); + _forceRedrawOnPaint = true; + RedrawWindow(_handle, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW); // TODO: Recalculate fonts return 0; } diff --git a/Source/Engine/Platform/Windows/WindowsWindow.h b/Source/Engine/Platform/Windows/WindowsWindow.h index 7055f641e..2a3da4cdb 100644 --- a/Source/Engine/Platform/Windows/WindowsWindow.h +++ b/Source/Engine/Platform/Windows/WindowsWindow.h @@ -25,6 +25,7 @@ private: Windows::ULONG _refCount; #endif bool _isResizing = false; + bool _forceRedrawOnPaint = false; bool _isSwitchingFullScreen = false; bool _trackingMouse = false; bool _clipCursorSet = false; @@ -92,7 +93,7 @@ public: private: - void CheckForWindowResize(); + void CheckForWindowResize(bool force = false); void UpdateCursor(); void UpdateRegion(); diff --git a/Source/Engine/Renderer/PostProcessingPass.cpp b/Source/Engine/Renderer/PostProcessingPass.cpp index 206087303..db21c04d7 100644 --- a/Source/Engine/Renderer/PostProcessingPass.cpp +++ b/Source/Engine/Renderer/PostProcessingPass.cpp @@ -273,6 +273,7 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input, int32 h4 = h2 >> 1; int32 h8 = h4 >> 1; int32 bloomMipCount = CalculateBloomMipCount(w1, h1); + useLensFlares &= bloomMipCount > 1; // Ensure to have valid data and if at least one effect should be applied if (!(useBloom || useToneMapping || useCameraArtifacts || colorGradingLUT) || checkIfSkipPass() || w8 <= 1 || h8 <= 1) @@ -450,7 +451,7 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input, // Set bloom output context->UnBindSR(0); context->UnBindSR(1); - context->BindSR(2, bloomBuffer2->View(0, 0)); + context->BindSR(2, (bloomMipCount > 1 ? bloomBuffer2 : bloomBuffer1)->View(0, 0)); } else { From a67ff38ed5870f0d4d8cbf058d56b8f4a9c8a1b8 Mon Sep 17 00:00:00 2001 From: David Svez Date: Sun, 2 Aug 2026 20:45:36 -0500 Subject: [PATCH 13/16] Fallback when DirectX debug layer is unavailable --- .../GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp index 78750042c..728c12731 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp @@ -549,7 +549,19 @@ bool GPUDeviceDX11::Init() else #endif { - VALIDATE_DIRECTX_CALL(D3D11CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, NULL, flags, &targetFeatureLevel, 1, D3D11_SDK_VERSION, &_device, &createdFeatureLevel, &_imContext)); + HRESULT createResult = D3D11CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, NULL, flags, &targetFeatureLevel, 1, D3D11_SDK_VERSION, &_device, &createdFeatureLevel, &_imContext); +#if GPU_ENABLE_DIAGNOSTICS + if (createResult == DXGI_ERROR_SDK_COMPONENT_MISSING) + { + // The Direct3D debug layer is an optional Windows component. Development builds + // should remain usable when it is not installed, just like adapter probing above. + flags &= ~D3D11_CREATE_DEVICE_DEBUG; + createResult = D3D11CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, NULL, flags, &targetFeatureLevel, 1, D3D11_SDK_VERSION, &_device, &createdFeatureLevel, &_imContext); + if (SUCCEEDED(createResult)) + LOG(Warning, "Direct3D SDK debug layers were requested, but not available. Continuing without them."); + } +#endif + VALIDATE_DIRECTX_CALL(createResult); } if (!_device || !_imContext) return true; From 042c3b7220c6caa5db5c097822e54816d0f14043 Mon Sep 17 00:00:00 2001 From: David Svez Date: Mon, 3 Aug 2026 05:25:36 -0500 Subject: [PATCH 14/16] Skip indexed properties in generic editor --- Source/Editor/CustomEditors/Editors/GenericEditor.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs index ce8c8f749..113e6368b 100644 --- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs +++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs @@ -270,6 +270,12 @@ namespace FlaxEditor.CustomEditors.Editors for (int i = 0; i < properties.Length; i++) { var p = properties[i]; + + // Indexed properties require arguments and cannot be represented by a normal property row. + // Trying to read one (for example IList.Item[index]) throws TargetParameterCountException. + if (p.Type is PropertyInfo managedProperty && managedProperty.GetIndexParameters().Length != 0) + continue; + var attributes = p.GetAttributes(true); var showInEditor = attributes.Any(x => x is ShowInEditorAttribute); From 53df912261cca8f02a980905836506722dac99be Mon Sep 17 00:00:00 2001 From: David Svez Date: Mon, 3 Aug 2026 05:07:28 -0500 Subject: [PATCH 15/16] Add layers settings shortcut to actor layer dropdown --- .../CustomEditors/Editors/ActorLayerEditor.cs | 52 +++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs b/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs index ff00d362f..b500dd61b 100644 --- a/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs @@ -13,7 +13,11 @@ namespace FlaxEditor.CustomEditors.Editors /// public sealed class ActorLayerEditor : CustomEditor { + private const string AddOrEditLayersOption = "Add or Edit Layers"; + private ComboBoxElement element; + private int _layerCount; + private bool _updatingItems; /// public override DisplayStyle Style => DisplayStyle.Inline; @@ -22,14 +26,56 @@ namespace FlaxEditor.CustomEditors.Editors public override void Initialize(LayoutElementsContainer layout) { element = layout.ComboBox(); - element.ComboBox.SetItems(LayersAndTagsSettings.GetCurrentLayers()); - element.ComboBox.SelectedIndex = (int)Values[0]; + UpdateLayerItems((int)Values[0]); + element.ComboBox.PopupShowing += OnPopupShowing; element.ComboBox.SelectedIndexChanged += OnSelectedIndexChanged; } + private void OnPopupShowing(ComboBox comboBox) + { + UpdateLayerItems(HasDifferentValues ? -1 : (int)Values[0]); + } + + private void UpdateLayerItems(int selectedIndex) + { + _updatingItems = true; + var layers = LayersAndTagsSettings.GetCurrentLayers(); + _layerCount = layers.Length; + element.ComboBox.SetItems(layers); + element.ComboBox.AddItem(AddOrEditLayersOption); + element.ComboBox.SelectedIndex = selectedIndex >= 0 && selectedIndex < _layerCount ? selectedIndex : -1; + _updatingItems = false; + } + + private void SelectCurrentLayer() + { + UpdateLayerItems(HasDifferentValues ? -1 : (int)Values[0]); + } + + private void OpenLayersAndTagsSettings() + { + var asset = GameSettings.LoadAsset(); + if (!asset) + { + GameSettings.Save(new LayersAndTagsSettings()); + asset = GameSettings.LoadAsset(); + } + if (asset) + Editor.Instance.ContentEditing.Open(asset); + } + private void OnSelectedIndexChanged(ComboBox comboBox) { + if (_updatingItems) + return; + int value = comboBox.SelectedIndex; + if (value == _layerCount) + { + OpenLayersAndTagsSettings(); + SelectCurrentLayer(); + return; + } if (value == -1) value = 0; @@ -87,7 +133,7 @@ namespace FlaxEditor.CustomEditors.Editors } else { - element.ComboBox.SelectedIndex = (int)Values[0]; + UpdateLayerItems((int)Values[0]); } } } From b8d403f32df1decc1bc342ace97335b740892829 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 8 Aug 2026 06:31:00 +0200 Subject: [PATCH 16/16] Minor changes after backporting changes --- .../CustomEditors/Editors/ActorLayerEditor.cs | 13 +++++++++++-- .../Editor/CustomEditors/Editors/GenericEditor.cs | 3 +-- Source/Editor/GUI/ComboBox.cs | 7 +++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs b/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs index b500dd61b..282e8fdac 100644 --- a/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs @@ -1,10 +1,12 @@ // Copyright (c) Wojciech Figat. All rights reserved. -using System.Collections.Generic; using FlaxEditor.Content.Settings; using FlaxEditor.CustomEditors.Elements; using FlaxEditor.GUI; +using FlaxEditor.GUI.ContextMenu; using FlaxEngine; +using System.Collections.Generic; +using System.Linq; namespace FlaxEditor.CustomEditors.Editors { @@ -13,7 +15,7 @@ namespace FlaxEditor.CustomEditors.Editors /// public sealed class ActorLayerEditor : CustomEditor { - private const string AddOrEditLayersOption = "Add or Edit Layers"; + private const string AddOrEditLayersOption = "Add or Edit Layers..."; private ComboBoxElement element; private int _layerCount; @@ -28,6 +30,7 @@ namespace FlaxEditor.CustomEditors.Editors element = layout.ComboBox(); UpdateLayerItems((int)Values[0]); element.ComboBox.PopupShowing += OnPopupShowing; + element.ComboBox.PopupShown += OnPopupShown; element.ComboBox.SelectedIndexChanged += OnSelectedIndexChanged; } @@ -36,6 +39,12 @@ namespace FlaxEditor.CustomEditors.Editors UpdateLayerItems(HasDifferentValues ? -1 : (int)Values[0]); } + private void OnPopupShown(ComboBox comboBox) + { + var addOrEditLayersOption = (ContextMenuButton)comboBox.Popup.Items.FirstOrDefault(x => x is FlaxEditor.GUI.ContextMenu.ContextMenuButton b && b.Text == AddOrEditLayersOption); + addOrEditLayersOption?.Icon = Editor.Instance.Icons.Settings12; + } + private void UpdateLayerItems(int selectedIndex) { _updatingItems = true; diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs index 113e6368b..f108136d4 100644 --- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs +++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs @@ -271,8 +271,7 @@ namespace FlaxEditor.CustomEditors.Editors { var p = properties[i]; - // Indexed properties require arguments and cannot be represented by a normal property row. - // Trying to read one (for example IList.Item[index]) throws TargetParameterCountException. + // Indexed properties require arguments and cannot be represented by a normal property row (eg. IList.Item[index]) if (p.Type is PropertyInfo managedProperty && managedProperty.GetIndexParameters().Length != 0) continue; diff --git a/Source/Editor/GUI/ComboBox.cs b/Source/Editor/GUI/ComboBox.cs index 06eabd1da..693189889 100644 --- a/Source/Editor/GUI/ComboBox.cs +++ b/Source/Editor/GUI/ComboBox.cs @@ -172,6 +172,11 @@ namespace FlaxEditor.GUI /// public event Action PopupShowing; + /// + /// Occurs when popup is shown (after event). Can be used to customize item controls collection after creation. + /// + public event Action PopupShown; + /// /// Custom popup creation function. /// @@ -435,6 +440,8 @@ namespace FlaxEditor.GUI var position = _popupMenu.RootWindow.Window.Position; _popupMenu.RootWindow.Window.Position = new Float2(position.X, position.Y - Height); } + + PopupShown?.Invoke(this); } }