Files
sbox-public/engine/Sandbox.Engine/Systems/UI/Render/PanelRenderer.Clip.cs
Antoine Pilote 9ecd7006d8 fix inverse scissor box shadow (#4643)
* fix inverse scissor box shadow

* fix shadows in world panels rendering wrong

the view matrix was applied twice. disable WORLD_PANEL flag when inside a subpanel
2026-04-24 12:42:42 -07:00

156 lines
5.2 KiB
C#

using Sandbox.Engine;
using Sandbox.Rendering;
namespace Sandbox.UI;
internal partial class PanelRenderer
{
/// <summary>
/// Software scissor, panels outside of this should not be rendered
/// </summary>
internal Rect Scissor;
/// <summary>
/// Accumulated clip rect from <see cref="OverflowMode.ClipWhole"/> ancestors.
/// Any panel whose bounds extend outside this rect will be skipped entirely.
/// Null when no clip-whole ancestor is active.
/// </summary>
internal Rect? ClipWholeRect;
internal bool IsOutsideClipWholeRect( Rect r )
{
if ( !ClipWholeRect.HasValue ) return false;
var clip = ClipWholeRect.Value;
return r.Left < clip.Left || r.Top < clip.Top || r.Right > clip.Right || r.Bottom > clip.Bottom;
}
/// <summary>
/// Accumulated clip rect from <see cref="OverflowMode.Scroll"/> and <see cref="OverflowMode.Hidden"/> ancestors.
/// Any panel whose bounds lie completely outside this rect will be skipped entirely (early cull).
/// Uses an overlap test so partially-visible panels still render.
/// Null when no scroll/hidden ancestor is active.
/// </summary>
internal Rect? ScrollCullRect;
internal bool IsOutsideScrollCullRect( Rect r )
{
if ( !ScrollCullRect.HasValue ) return false;
var clip = ScrollCullRect.Value;
return r.Right <= clip.Left || r.Bottom <= clip.Top || r.Left >= clip.Right || r.Top >= clip.Bottom;
}
/// <summary>
/// Scissor passed to gpu shader to be transformed
/// </summary>
internal GPUScissor ScissorGPU;
internal struct GPUScissor
{
public Rect Rect;
public Vector4 CornerRadius;
public Matrix Matrix;
public bool Invert;
}
/// <summary>
/// Scope that updates the renderer's scissor state for child panels to inherit.
/// Does NOT modify any command lists - those are set up separately in BuildCommandList.
/// </summary>
internal ref struct ClipScope
{
Rect Previous;
GPUScissor PreviousGPU;
bool _disposed = false; // Whether this scope actually set a new scissor or not. If the panel had overflow: visible then we won't set a new scissor, so we can skip restoring the old one.
public ClipScope( Rect scissorRect, Vector4 cornerRadius, Matrix globalMatrix )
{
_disposed = true;
var renderer = GlobalContext.Current.UISystem.Renderer;
Previous = renderer.Scissor;
PreviousGPU = renderer.ScissorGPU;
renderer.ScissorGPU.Rect = new Rect()
{
Left = Math.Max( scissorRect.Left, PreviousGPU.Rect.Left ),
Top = Math.Max( scissorRect.Top, PreviousGPU.Rect.Top ),
Right = Math.Min( scissorRect.Right, PreviousGPU.Rect.Right ),
Bottom = Math.Min( scissorRect.Bottom, PreviousGPU.Rect.Bottom ),
};
renderer.ScissorGPU.CornerRadius = cornerRadius;
renderer.ScissorGPU.Matrix = globalMatrix;
var tl = globalMatrix.Transform( scissorRect.TopLeft );
var tr = globalMatrix.Transform( scissorRect.TopRight );
var bl = globalMatrix.Transform( scissorRect.BottomLeft );
var br = globalMatrix.Transform( scissorRect.BottomRight );
var min = Vector2.Min( Vector2.Min( tl, tr ), Vector2.Min( bl, br ) );
var max = Vector2.Max( Vector2.Max( tl, tr ), Vector2.Max( bl, br ) );
scissorRect = new Rect( min, max - min );
renderer.Scissor = new Rect()
{
Left = Math.Max( scissorRect.Left, Previous.Left ),
Top = Math.Max( scissorRect.Top, Previous.Top ),
Right = Math.Min( scissorRect.Right, Previous.Right ),
Bottom = Math.Min( scissorRect.Bottom, Previous.Bottom ),
};
}
public void Dispose()
{
if ( !_disposed ) return;
_disposed = false;
var renderer = GlobalContext.Current.UISystem.Renderer;
renderer.Scissor = Previous;
renderer.ScissorGPU = PreviousGPU;
}
}
/// <summary>
/// Create a clip scope for a panel's children. This updates the renderer's scissor state
/// so child panels will inherit the correct scissor when their command lists are built.
/// </summary>
public ClipScope Clip( Panel panel )
{
var overflow = panel.ComputedStyle?.Overflow ?? OverflowMode.Visible;
if ( overflow == OverflowMode.Visible || overflow == OverflowMode.ClipWhole ) return default;
var size = (panel.Box.Rect.Width + panel.Box.Rect.Height) * 0.5f;
var borderRadius = new Vector4( panel.ComputedStyle.BorderTopLeftRadius?.GetPixels( size ) ?? 0, panel.ComputedStyle.BorderTopRightRadius?.GetPixels( size ) ?? 0, panel.ComputedStyle.BorderBottomLeftRadius?.GetPixels( size ) ?? 0, panel.ComputedStyle.BorderBottomRightRadius?.GetPixels( size ) ?? 0 );
return new ClipScope( panel.Box.ClipRect, borderRadius, panel.GlobalMatrix ?? Matrix.Identity );
}
internal static void SetScissorAttributes( CommandList commandList, GPUScissor scissor )
{
if ( scissor.Rect.Width == 0 && scissor.Rect.Height == 0 )
{
commandList.Attributes.Set( "HasScissor", 0 );
return;
}
commandList.Attributes.Set( "ScissorRect", scissor.Rect.ToVector4() );
commandList.Attributes.Set( "ScissorCornerRadius", scissor.CornerRadius );
commandList.Attributes.Set( "ScissorTransformMat", scissor.Matrix );
commandList.Attributes.Set( "HasScissor", 1 );
}
void InitScissor( Rect rect )
{
Scissor = rect;
ScissorGPU = new() { Rect = rect, Matrix = Matrix.Identity };
}
void InitScissor( Rect rect, CommandList commandList )
{
InitScissor( rect );
SetScissorAttributes( commandList, ScissorGPU );
}
}