diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs
index d8de14de09..fe35875160 100644
--- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs
+++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs
@@ -466,6 +466,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
engine.UpdateState(ulong.MaxValue & ~(1UL << StateUpdater.ShaderStateIndex));
+ _channel.TextureManager.SignalRenderTargetsModifiable();
_channel.TextureManager.UpdateRenderTargets();
int textureId = _state.State.DrawTextureTextureId;
@@ -803,7 +804,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
int index = (argument >> 6) & 0xf;
int layer = (argument >> 10) & 0x3ff;
- RenderTargetUpdateFlags updateFlags = RenderTargetUpdateFlags.SingleColor;
+ RenderTargetUpdateFlags updateFlags = RenderTargetUpdateFlags.SingleColor | RenderTargetUpdateFlags.ForClear;
if (layer != 0 || layerCount > 1)
{
diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/RenderTargetUpdateFlags.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/RenderTargetUpdateFlags.cs
index 58c7bdb442..ebfa83e790 100644
--- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/RenderTargetUpdateFlags.cs
+++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/RenderTargetUpdateFlags.cs
@@ -38,6 +38,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
///
DiscardClip = 1 << 4,
+ ///
+ /// Indicates that the render target will be used for a clear operation.
+ ///
+ ForClear = 1 << 5,
+
///
/// Default update flags for draw.
///
diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
index 6b4ea89f39..9fc9cc1a77 100644
--- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
+++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
@@ -44,6 +44,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
private ProgramPipelineState _pipeline;
private bool _fsReadsFragCoord;
+ private bool _fsAlwaysDiscards;
private bool _vsUsesDrawParameters;
private bool _vtgWritesRtLayer;
private byte _vsClipDistancesWritten;
@@ -476,6 +477,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
bool changedScale = false;
uint rtNoAlphaMask = 0;
+ bool rtModifiable = updateFlags.HasFlag(RenderTargetUpdateFlags.ForClear) || !_fsAlwaysDiscards;
+
for (int index = 0; index < Constants.TotalRenderTargets; index++)
{
int rtIndex = useControl ? rtControl.UnpackPermutationIndex(index) : index;
@@ -484,7 +487,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
if (index >= count || !IsRtEnabled(colorState) || (singleColor && index != singleUse))
{
- changedScale |= _channel.TextureManager.SetRenderTargetColor(index, null);
+ changedScale |= _channel.TextureManager.SetRenderTargetColor(index, null, rtModifiable);
continue;
}
@@ -503,7 +506,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
samplesInY,
sizeHint);
- changedScale |= _channel.TextureManager.SetRenderTargetColor(index, color);
+ changedScale |= _channel.TextureManager.SetRenderTargetColor(index, color, rtModifiable);
if (color != null)
{
@@ -557,7 +560,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
}
}
- changedScale |= _channel.TextureManager.SetRenderTargetDepthStencil(depthStencil);
+ changedScale |= _channel.TextureManager.SetRenderTargetDepthStencil(depthStencil, rtModifiable);
if (changedScale)
{
@@ -1466,20 +1469,38 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
_currentProgramInfo[stageIndex] = info;
}
- if (gs.Shaders[5]?.Info.UsesFragCoord == true)
- {
- // Make sure we update the viewport size on the support buffer if it will be consumed on the new shader.
+ _fsReadsFragCoord = false;
- if (!_fsReadsFragCoord && _state.State.YControl.HasFlag(YControl.NegateY))
+ ShaderProgramInfo fragmentShaderInfo = gs.Shaders[5]?.Info;
+
+ if (fragmentShaderInfo != null)
+ {
+ if (fragmentShaderInfo.UsesFragCoord)
{
- UpdateSupportBufferViewportSize();
+ // Make sure we update the viewport size on the support buffer if it will be consumed on the new shader.
+
+ if (!_fsReadsFragCoord && _state.State.YControl.HasFlag(YControl.NegateY))
+ {
+ UpdateSupportBufferViewportSize();
+ }
+
+ _fsReadsFragCoord = true;
}
- _fsReadsFragCoord = true;
+ if (_fsAlwaysDiscards != fragmentShaderInfo.HasUnconditionalDiscard)
+ {
+ _fsAlwaysDiscards = fragmentShaderInfo.HasUnconditionalDiscard;
+
+ if (!_fsAlwaysDiscards)
+ {
+ _channel.TextureManager.RefreshModifiedTextures();
+ _channel.TextureManager.SignalRenderTargetsModifiable();
+ }
+ }
}
else
{
- _fsReadsFragCoord = false;
+ _fsAlwaysDiscards = false;
}
if (gs.VertexAsCompute != null)
diff --git a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs
index f1615b388b..2c78f560b0 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs
@@ -1430,11 +1430,6 @@ namespace Ryujinx.Graphics.Gpu.Image
/// True if the texture has been bound, false if it has been unbound
public void SignalModifying(bool bound)
{
- if (bound)
- {
- _scaledSetScore = Math.Max(0, _scaledSetScore - 1);
- }
-
if (_modifiedStale || Group.HasCopyDependencies || Group.HasFlushBuffer)
{
_modifiedStale = false;
@@ -1442,9 +1437,18 @@ namespace Ryujinx.Graphics.Gpu.Image
}
_physicalMemory.TextureCache.Lift(this);
+ }
+ ///
+ /// Signals that a render target texture has been either bound or unbound.
+ ///
+ /// True if the texture has been bound, false if it has been unbound
+ public void SignalBindingChange(bool bound)
+ {
if (bound)
{
+ _scaledSetScore = Math.Max(0, _scaledSetScore - 1);
+
IncrementReferenceCount();
}
else
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
index ef5d0deaad..55d948898b 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
@@ -175,7 +175,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
(TexturePool texturePool, SamplerPool samplerPool) = GetPools();
- return (texturePool.Get(textureId), samplerPool.Get(samplerId));
+ return (texturePool?.Get(textureId), samplerPool?.Get(samplerId));
}
///
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
index 3bf0beefdb..db04155217 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
@@ -18,12 +18,39 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly TexturePoolCache _texturePoolCache;
private readonly SamplerPoolCache _samplerPoolCache;
+ ///
+ /// Bound render target texture modification report state.
+ ///
+ [Flags]
+ private enum BindState : byte
+ {
+ ///
+ /// Render target texture has not been signalled for modification, and can't be modified on the next render operations.
+ ///
+ None = 0,
+
+ ///
+ /// Render target texture has been signalled for modification.
+ ///
+ Bound = 1 << 0,
+
+ ///
+ /// Render target texture might be modified on the next render operations.
+ ///
+ Modified = 1 << 1,
+
+ ///
+ /// Render target texture has been signalled for modification and might be modified on the next render operations.
+ ///
+ BoundModified = Bound | Modified,
+ }
+
private readonly Texture[] _rtColors;
private readonly ITexture[] _rtHostColors;
- private readonly bool[] _rtColorsBound;
+ private readonly BindState[] _rtColorsBound;
private Texture _rtDepthStencil;
private ITexture _rtHostDs;
- private bool _rtDsBound;
+ private BindState _rtDsBound;
public int ClipRegionWidth { get; private set; }
public int ClipRegionHeight { get; private set; }
@@ -53,7 +80,7 @@ namespace Ryujinx.Graphics.Gpu.Image
_rtColors = new Texture[Constants.TotalRenderTargets];
_rtHostColors = new ITexture[Constants.TotalRenderTargets];
- _rtColorsBound = new bool[Constants.TotalRenderTargets];
+ _rtColorsBound = new BindState[Constants.TotalRenderTargets];
}
///
@@ -149,27 +176,39 @@ namespace Ryujinx.Graphics.Gpu.Image
///
/// The index of the color buffer to set (up to 8)
/// The color buffer texture
+ /// Indicates if the following render operations will modidify contents
/// True if render target scale must be updated.
- public bool SetRenderTargetColor(int index, Texture color)
+ public bool SetRenderTargetColor(int index, Texture color, bool modified)
{
bool hasValue = color != null;
bool changesScale = (hasValue != (_rtColors[index] != null)) || (hasValue && RenderTargetScale != color.ScaleFactor);
if (_rtColors[index] != color)
{
- if (_rtColorsBound[index])
+ Texture oldColor = _rtColors[index];
+
+ if (oldColor != null)
{
- _rtColors[index]?.SignalModifying(false);
- }
- else
- {
- _rtColorsBound[index] = true;
+ if (_rtColorsBound[index].HasFlag(BindState.Bound))
+ {
+ oldColor.SignalModifying(false);
+ }
+
+ oldColor.SignalBindingChange(false);
}
+ _rtColorsBound[index] = modified ? BindState.BoundModified : BindState.None;
+
if (color != null)
{
color.SynchronizeMemory();
- color.SignalModifying(true);
+
+ if (modified)
+ {
+ color.SignalModifying(true);
+ }
+
+ color.SignalBindingChange(true);
}
_rtColors[index] = color;
@@ -182,27 +221,39 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Sets the render target depth-stencil buffer.
///
/// The depth-stencil buffer texture
+ /// Indicates if the following render operations will modidify contents
/// True if render target scale must be updated.
- public bool SetRenderTargetDepthStencil(Texture depthStencil)
+ public bool SetRenderTargetDepthStencil(Texture depthStencil, bool modified)
{
bool hasValue = depthStencil != null;
bool changesScale = (hasValue != (_rtDepthStencil != null)) || (hasValue && RenderTargetScale != depthStencil.ScaleFactor);
if (_rtDepthStencil != depthStencil)
{
- if (_rtDsBound)
+ Texture oldDepthStencil = _rtDepthStencil;
+
+ if (oldDepthStencil != null)
{
- _rtDepthStencil?.SignalModifying(false);
- }
- else
- {
- _rtDsBound = true;
+ if (_rtDsBound.HasFlag(BindState.Bound))
+ {
+ oldDepthStencil.SignalModifying(false);
+ }
+
+ oldDepthStencil.SignalBindingChange(false);
}
+ _rtDsBound = modified ? BindState.BoundModified : BindState.None;
+
if (depthStencil != null)
{
depthStencil.SynchronizeMemory();
- depthStencil.SignalModifying(true);
+
+ if (modified)
+ {
+ depthStencil.SignalModifying(true);
+ }
+
+ depthStencil.SignalBindingChange(true);
}
_rtDepthStencil = depthStencil;
@@ -437,10 +488,10 @@ namespace Ryujinx.Graphics.Gpu.Image
{
hostDsTexture = dsTexture.HostTexture;
- if (!_rtDsBound)
+ if (_rtDsBound == BindState.Modified)
{
dsTexture.SignalModifying(true);
- _rtDsBound = true;
+ _rtDsBound |= BindState.Bound;
}
}
@@ -459,10 +510,10 @@ namespace Ryujinx.Graphics.Gpu.Image
{
hostTexture = texture.HostTexture;
- if (!_rtColorsBound[index])
+ if (_rtColorsBound[index] == BindState.Modified)
{
texture.SignalModifying(true);
- _rtColorsBound[index] = true;
+ _rtColorsBound[index] |= BindState.Bound;
}
}
@@ -500,24 +551,37 @@ namespace Ryujinx.Graphics.Gpu.Image
{
Texture dsTexture = _rtDepthStencil;
- if (dsTexture != null && _rtDsBound)
+ if (dsTexture != null && _rtDsBound.HasFlag(BindState.Bound))
{
dsTexture.SignalModifying(false);
- _rtDsBound = false;
+ _rtDsBound &= ~BindState.Bound;
}
for (int index = 0; index < _rtColors.Length; index++)
{
Texture texture = _rtColors[index];
- if (texture != null && _rtColorsBound[index])
+ if (texture != null && _rtColorsBound[index].HasFlag(BindState.Bound))
{
texture.SignalModifying(false);
- _rtColorsBound[index] = false;
+ _rtColorsBound[index] &= ~BindState.Bound;
}
}
}
+ ///
+ /// Indicates that the currently bound render targets might be modified, if they are used on the next render operation.
+ ///
+ public void SignalRenderTargetsModifiable()
+ {
+ _rtDsBound |= BindState.Modified;
+
+ for (int index = 0; index < _rtColorsBound.Length; index++)
+ {
+ _rtColorsBound[index] |= BindState.Modified;
+ }
+ }
+
///
/// Forces the texture and sampler pools to be re-loaded from the cache on next use.
///
@@ -554,19 +618,11 @@ namespace Ryujinx.Graphics.Gpu.Image
for (int i = 0; i < _rtColors.Length; i++)
{
- if (_rtColorsBound[i])
- {
- _rtColors[i]?.DecrementReferenceCount();
- }
-
+ _rtColors[i]?.DecrementReferenceCount();
_rtColors[i] = null;
}
- if (_rtDsBound)
- {
- _rtDepthStencil?.DecrementReferenceCount();
- }
-
+ _rtDepthStencil?.DecrementReferenceCount();
_rtDepthStencil = null;
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
index c5763b0258..ceb88a259a 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
@@ -170,6 +170,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
///
public bool UsesRtLayer;
+ ///
+ /// Indicates that the fragment shader always discards the fragment, not producing any output for the bound render targets.
+ ///
+ public bool HasUnconditionalDiscard;
+
///
/// Bit mask with the clip distances written on the vertex stage.
///
@@ -806,6 +811,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
dataInfo.UsesInstanceId,
dataInfo.UsesDrawParameters,
dataInfo.UsesRtLayer,
+ dataInfo.HasUnconditionalDiscard,
dataInfo.ClipDistancesWritten,
dataInfo.FragmentOutputMap);
}
@@ -836,6 +842,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
UsesInstanceId = info.UsesInstanceId,
UsesDrawParameters = info.UsesDrawParameters,
UsesRtLayer = info.UsesRtLayer,
+ HasUnconditionalDiscard = info.HasUnconditionalDiscard,
ClipDistancesWritten = info.ClipDistancesWritten,
FragmentOutputMap = info.FragmentOutputMap,
};
diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/BasicBlock.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/BasicBlock.cs
index 637e120e1c..4158face90 100644
--- a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/BasicBlock.cs
+++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/BasicBlock.cs
@@ -23,6 +23,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
set => _branch = AddSuccessor(_branch, value);
}
+ public bool HasSuccessor => _branch != null || _next != null;
public bool HasBranch => _branch != null;
public bool Reachable => Index == 0 || Predecessors.Count != 0;
diff --git a/src/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs b/src/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs
index 22823ac38e..1bb9ea06a4 100644
--- a/src/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs
+++ b/src/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs
@@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Shader
public bool UsesInstanceId { get; }
public bool UsesDrawParameters { get; }
public bool UsesRtLayer { get; }
+ public bool HasUnconditionalDiscard { get; }
public byte ClipDistancesWritten { get; }
public int FragmentOutputMap { get; }
@@ -34,6 +35,7 @@ namespace Ryujinx.Graphics.Shader
bool usesInstanceId,
bool usesDrawParameters,
bool usesRtLayer,
+ bool hasUnconditionalDiscard,
byte clipDistancesWritten,
int fragmentOutputMap)
{
@@ -50,6 +52,7 @@ namespace Ryujinx.Graphics.Shader
UsesInstanceId = usesInstanceId;
UsesDrawParameters = usesDrawParameters;
UsesRtLayer = usesRtLayer;
+ HasUnconditionalDiscard = hasUnconditionalDiscard;
ClipDistancesWritten = clipDistancesWritten;
FragmentOutputMap = fragmentOutputMap;
}
diff --git a/src/Ryujinx.Graphics.Shader/Translation/FeatureFlags.cs b/src/Ryujinx.Graphics.Shader/Translation/FeatureFlags.cs
index 82a54db835..ad9bdcb1e4 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/FeatureFlags.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/FeatureFlags.cs
@@ -26,5 +26,6 @@ namespace Ryujinx.Graphics.Shader.Translation
SharedMemory = 1 << 11,
Store = 1 << 12,
VtgAsCompute = 1 << 13,
+ UnconditionalDiscard = 1 << 14,
}
}
diff --git a/src/Ryujinx.Graphics.Shader/Translation/FeatureIdentification.cs b/src/Ryujinx.Graphics.Shader/Translation/FeatureIdentification.cs
new file mode 100644
index 0000000000..82d7e0b019
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/Translation/FeatureIdentification.cs
@@ -0,0 +1,38 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+
+namespace Ryujinx.Graphics.Shader.Translation
+{
+ static class FeatureIdentification
+ {
+ public static void RunPass(BasicBlock[] blocks, ShaderStage stage, ref FeatureFlags usedFeatures)
+ {
+ if (stage == ShaderStage.Fragment)
+ {
+ bool endsWithDiscardOnly = true;
+
+ for (int blockIndex = 0; blockIndex < blocks.Length; blockIndex++)
+ {
+ BasicBlock block = blocks[blockIndex];
+
+ if (block.HasSuccessor)
+ {
+ continue;
+ }
+
+ if (block.Operations.Count == 0 ||
+ block.Operations.Last.Value is not Operation operation ||
+ operation.Inst != Instruction.Discard)
+ {
+ endsWithDiscardOnly = false;
+ break;
+ }
+ }
+
+ if (endsWithDiscardOnly)
+ {
+ usedFeatures |= FeatureFlags.UnconditionalDiscard;
+ }
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs
index a193ab3c4d..7a37365025 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs
@@ -300,6 +300,11 @@ namespace Ryujinx.Graphics.Shader.Translation
Optimizer.RunPass(context);
TransformPasses.RunPass(context);
+
+ if (i == 0)
+ {
+ FeatureIdentification.RunPass(cfg.Blocks, Definitions.Stage, ref usedFeatures);
+ }
}
funcs[i] = new Function(cfg.Blocks, $"fun{i}", false, inArgumentsCount, outArgumentsCount);
@@ -352,6 +357,7 @@ namespace Ryujinx.Graphics.Shader.Translation
usedFeatures.HasFlag(FeatureFlags.InstanceId),
usedFeatures.HasFlag(FeatureFlags.DrawParameters),
usedFeatures.HasFlag(FeatureFlags.RtLayer),
+ usedFeatures.HasFlag(FeatureFlags.UnconditionalDiscard),
clipDistancesWritten,
originalDefinitions.OmapTargets);