diff --git a/Ryujinx.Graphics.GAL/IPipeline.cs b/Ryujinx.Graphics.GAL/IPipeline.cs
index c02f84d471..75c3077eb8 100644
--- a/Ryujinx.Graphics.GAL/IPipeline.cs
+++ b/Ryujinx.Graphics.GAL/IPipeline.cs
@@ -1,4 +1,3 @@
-using Ryujinx.Graphics.Shader;
using System;
namespace Ryujinx.Graphics.GAL
@@ -15,9 +14,9 @@ namespace Ryujinx.Graphics.GAL
void ClearRenderTargetDepthStencil(
float depthValue,
- bool depthMask,
- int stencilValue,
- int stencilMask);
+ bool depthMask,
+ int stencilValue,
+ int stencilMask);
void CommandBufferBarrier();
diff --git a/Ryujinx.Graphics.GAL/RectangleF.cs b/Ryujinx.Graphics.GAL/RectangleF.cs
index c58aabf0e8..cf1667812a 100644
--- a/Ryujinx.Graphics.GAL/RectangleF.cs
+++ b/Ryujinx.Graphics.GAL/RectangleF.cs
@@ -2,16 +2,16 @@ namespace Ryujinx.Graphics.GAL
{
public struct RectangleF
{
- public float X { get; }
- public float Y { get; }
- public float Width { get; }
+ public float X { get; }
+ public float Y { get; }
+ public float Width { get; }
public float Height { get; }
public RectangleF(float x, float y, float width, float height)
{
- X = x;
- Y = y;
- Width = width;
+ X = x;
+ Y = y;
+ Width = width;
Height = height;
}
}
diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs
index 8f69eaa74d..b705d63e32 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs
@@ -1,5 +1,6 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Types;
+using System;
using System.Text;
namespace Ryujinx.Graphics.Gpu.Engine.Threed
@@ -489,14 +490,62 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
return;
}
- // Scissor and rasterizer discard also affect clears.
- engine.UpdateState((1UL << StateUpdater.RasterizerStateIndex) | (1UL << StateUpdater.ScissorStateIndex));
-
int index = (argument >> 6) & 0xf;
engine.UpdateRenderTargetState(useControl: false, singleUse: index);
- _channel.TextureManager.UpdateRenderTargets();
+ // If there is a mismatch on the host clip region and the one explicitly defined by the guest
+ // on the screen scissor state, then we need to force only one texture to be bound to avoid
+ // host clipping.
+ var screenScissorState = _state.State.ScreenScissorState;
+
+ // Must happen after UpdateRenderTargetState to have up-to-date clip region values.
+ bool clipMismatch = (screenScissorState.X | screenScissorState.Y) != 0 ||
+ screenScissorState.Width != _channel.TextureManager.ClipRegionWidth ||
+ screenScissorState.Height != _channel.TextureManager.ClipRegionHeight;
+
+ bool clearAffectedByStencilMask = (_state.State.ClearFlags & 1) != 0;
+ bool clearAffectedByScissor = (_state.State.ClearFlags & 0x100) != 0;
+ bool needsCustomScissor = !clearAffectedByScissor || clipMismatch;
+
+ // Scissor and rasterizer discard also affect clears.
+ ulong updateMask = 1UL << StateUpdater.RasterizerStateIndex;
+
+ if (!needsCustomScissor)
+ {
+ updateMask |= 1UL << StateUpdater.ScissorStateIndex;
+ }
+
+ engine.UpdateState(updateMask);
+
+ if (needsCustomScissor)
+ {
+ int scissorX = screenScissorState.X;
+ int scissorY = screenScissorState.Y;
+ int scissorW = screenScissorState.Width;
+ int scissorH = screenScissorState.Height;
+
+ if (clearAffectedByScissor)
+ {
+ ref var scissorState = ref _state.State.ScissorState[0];
+
+ scissorX = Math.Max(scissorX, scissorState.X1);
+ scissorY = Math.Max(scissorY, scissorState.Y1);
+ scissorW = Math.Min(scissorW, scissorState.X2 - scissorState.X1);
+ scissorH = Math.Min(scissorH, scissorState.Y2 - scissorState.Y1);
+ }
+
+ _context.Renderer.Pipeline.SetScissor(0, true, scissorX, scissorY, scissorW, scissorH);
+ }
+
+ if (clipMismatch)
+ {
+ _channel.TextureManager.UpdateRenderTarget(index);
+ }
+ else
+ {
+ _channel.TextureManager.UpdateRenderTargets();
+ }
bool clearDepth = (argument & 1) != 0;
bool clearStencil = (argument & 2) != 0;
@@ -521,7 +570,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
if (clearStencil)
{
- stencilMask = _state.State.StencilTestState.FrontMask;
+ stencilMask = clearAffectedByStencilMask ? _state.State.StencilTestState.FrontMask : 0xff;
+ }
+
+ if (clipMismatch)
+ {
+ _channel.TextureManager.UpdateRenderTargetDepthStencil();
}
_context.Renderer.Pipeline.ClearRenderTargetDepthStencil(
@@ -531,6 +585,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
stencilMask);
}
+ if (needsCustomScissor)
+ {
+ engine.UpdateScissorState();
+ }
+
engine.UpdateRenderTargetState(useControl: true);
if (renderEnable == ConditionalRenderEnabled.Host)
diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
index 9f6ee17ce2..1228a9440b 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
@@ -339,6 +339,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
var scissor = _state.State.ScreenScissorState;
Size sizeHint = new Size(scissor.X + scissor.Width, scissor.Y + scissor.Height, 1);
+ int clipRegionWidth = int.MaxValue;
+ int clipRegionHeight = int.MaxValue;
+
bool changedScale = false;
for (int index = 0; index < Constants.TotalRenderTargets; index++)
@@ -363,6 +366,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
sizeHint);
changedScale |= _channel.TextureManager.SetRenderTargetColor(index, color);
+
+ if (color != null)
+ {
+ if (clipRegionWidth > color.Width)
+ {
+ clipRegionWidth = color.Width;
+ }
+
+ if (clipRegionHeight > color.Height)
+ {
+ clipRegionHeight = color.Height;
+ }
+ }
}
bool dsEnable = _state.State.RtDepthStencilEnable;
@@ -381,6 +397,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
samplesInX,
samplesInY,
sizeHint);
+
+ if (depthStencil != null)
+ {
+ if (clipRegionWidth > depthStencil.Width)
+ {
+ clipRegionWidth = depthStencil.Width;
+ }
+
+ if (clipRegionHeight > depthStencil.Height)
+ {
+ clipRegionHeight = depthStencil.Height;
+ }
+ }
}
changedScale |= _channel.TextureManager.SetRenderTargetDepthStencil(depthStencil);
@@ -398,6 +427,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
UpdateScissorState();
}
}
+
+ _channel.TextureManager.SetClipRegion(clipRegionWidth, clipRegionHeight);
}
///
@@ -414,7 +445,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
///
/// Updates host scissor test state based on current GPU state.
///
- private void UpdateScissorState()
+ public void UpdateScissorState()
{
for (int index = 0; index < Constants.TotalViewports; index++)
{
diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs
index f6de2730fb..a2e8c64c15 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs
@@ -137,6 +137,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
_stateUpdater.UpdateRenderTargetState(useControl, singleUse);
}
+ ///
+ /// Updates scissor based on current render target state.
+ ///
+ public void UpdateScissorState()
+ {
+ _stateUpdater.UpdateScissorState();
+ }
+
///
/// Marks the entire state as dirty, forcing a full host state update before the next draw.
///
diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs
index 9d8ad765d0..a6a5a2ab92 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs
@@ -754,7 +754,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
public int DrawTextureTextureId;
public int DrawTextureSrcX;
public int DrawTextureSrcY;
- public fixed uint Reserved10B0[44];
+ public fixed uint Reserved10B0[18];
+ public uint ClearFlags;
+ public fixed uint Reserved10FC[25];
public Array16 VertexAttribState;
public fixed uint Reserved11A0[31];
public RtControl RtControl;
diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs
index eacfa4f529..b2fa15a257 100644
--- a/Ryujinx.Graphics.Gpu/Image/Texture.cs
+++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs
@@ -47,6 +47,16 @@ namespace Ryujinx.Graphics.Gpu.Image
///
public Target Target { get; private set; }
+ ///
+ /// Texture width.
+ ///
+ public int Width { get; private set; }
+
+ ///
+ /// Texture height.
+ ///
+ public int Height { get; private set; }
+
///
/// Texture information.
///
@@ -926,7 +936,7 @@ namespace Ryujinx.Graphics.Gpu.Image
FlushTextureDataToGuest(tracked);
}
}
-
+
///
/// Gets a host texture to use for flushing the texture, at 1x resolution.
/// If the HostTexture is already at 1x resolution, it is returned directly.
@@ -1322,6 +1332,8 @@ namespace Ryujinx.Graphics.Gpu.Image
{
Info = info;
Target = info.Target;
+ Width = info.Width;
+ Height = info.Height;
CanForceAnisotropy = CanTextureForceAnisotropy();
_depth = info.GetDepth();
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
index 70cb57d09a..90e26442ba 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
@@ -19,6 +19,9 @@ namespace Ryujinx.Graphics.Gpu.Image
private Texture _rtDepthStencil;
private ITexture _rtHostDs;
+ public int ClipRegionWidth { get; private set; }
+ public int ClipRegionHeight { get; private set; }
+
///
/// The scaling factor applied to all currently bound render targets.
///
@@ -210,6 +213,17 @@ namespace Ryujinx.Graphics.Gpu.Image
return changesScale || ScaleNeedsUpdated(depthStencil);
}
+ ///
+ /// Sets the host clip region, which should be the intersection of all render target texture sizes.
+ ///
+ /// Width of the clip region, defined as the minimum width across all bound textures
+ /// Height of the clip region, defined as the minimum height across all bound textures
+ public void SetClipRegion(int width, int height)
+ {
+ ClipRegionWidth = width;
+ ClipRegionHeight = height;
+ }
+
///
/// Gets the first available bound colour target, or the depth stencil target if not present.
///
@@ -409,6 +423,35 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
+ ///
+ /// Update host framebuffer attachments based on currently bound render target buffers.
+ ///
+ ///
+ /// All attachments other than will be unbound.
+ ///
+ /// Index of the render target color to be updated
+ public void UpdateRenderTarget(int index)
+ {
+ new Span(_rtHostColors).Fill(null);
+ _rtHostColors[index] = _rtColors[index]?.HostTexture;
+
+ _context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, null);
+ }
+
+ ///
+ /// Update host framebuffer attachments based on currently bound render target buffers.
+ ///
+ ///
+ /// All color attachments will be unbound.
+ ///
+ public void UpdateRenderTargetDepthStencil()
+ {
+ new Span(_rtHostColors).Fill(null);
+ _rtHostDs = _rtDepthStencil?.HostTexture;
+
+ _context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, _rtHostDs);
+ }
+
///
/// Forces all textures, samplers, images and render targets to be rebound the next time
/// CommitGraphicsBindings is called.
diff --git a/Ryujinx.Graphics.OpenGL/Pipeline.cs b/Ryujinx.Graphics.OpenGL/Pipeline.cs
index 6d6e07457b..ff5af42d13 100644
--- a/Ryujinx.Graphics.OpenGL/Pipeline.cs
+++ b/Ryujinx.Graphics.OpenGL/Pipeline.cs
@@ -6,7 +6,6 @@ using Ryujinx.Graphics.OpenGL.Queries;
using Ryujinx.Graphics.Shader;
using System;
using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.OpenGL
{
@@ -1058,14 +1057,17 @@ namespace Ryujinx.Graphics.OpenGL
_framebuffer.AttachColor(index, color);
- int isBgra = color != null && color.Format.IsBgr() ? 1 : 0;
-
- if (_fpIsBgra[index].X != isBgra)
+ if (color != null)
{
- _fpIsBgra[index].X = isBgra;
- isBgraChanged = true;
+ int isBgra = color.Format.IsBgr() ? 1 : 0;
- RestoreComponentMask(index);
+ if (_fpIsBgra[index].X != isBgra)
+ {
+ _fpIsBgra[index].X = isBgra;
+ isBgraChanged = true;
+
+ RestoreComponentMask(index);
+ }
}
}