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); + } } }