mirror of
https://github.com/Ryujinx/Ryujinx.git
synced 2024-12-27 09:59:33 +01:00
Implement Zero-Configuration Resolution Scaling (#1365)
* Initial implementation of Render Target Scaling Works with most games I have. No GUI option right now, it is hardcoded. Missing handling for texelFetch operation. * Realtime Configuration, refactoring. * texelFetch scaling on fragment shader (WIP) * Improve Shader-Side changes. * Fix potential crash when no color/depth bound * Workaround random uses of textures in compute. This was blacklisting textures in a few games despite causing no bugs. Will eventually add full support so this doesn't break anything. * Fix scales oscillating when changing between non-native scales. * Scaled textures on compute, cleanup, lazier uniform update. * Cleanup. * Fix stupidity * Address Thog Feedback. * Cover most of GDK's feedback (two comments remain) * Fix bad rename * Move IsDepthStencil to FormatExtensions, add docs. * Fix default config, square texture detection. * Three final fixes: - Nearest copy when texture is integer format. - Texture2D -> Texture3D copy correctly blacklists the texture before trying an unscaled copy (caused driver error) - Discount small textures. * Remove scale threshold. Not needed right now - we'll see if we run into problems. * All CPU modification blacklists scale. * Fix comment.
This commit is contained in:
parent
43b78ae157
commit
484eb645ae
49 changed files with 1163 additions and 131 deletions
|
@ -13,10 +13,20 @@ namespace Ryujinx.Configuration
|
|||
/// <summary>
|
||||
/// The current version of the file format
|
||||
/// </summary>
|
||||
public const int CurrentVersion = 10;
|
||||
public const int CurrentVersion = 11;
|
||||
|
||||
public int Version { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Resolution Scale. An integer scale applied to applicable render targets. Values 1-4, or -1 to use a custom floating point scale instead.
|
||||
/// </summary>
|
||||
public int ResScale { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Custom Resolution Scale. A custom floating point scale applied to applicable render targets. Only active when Resolution Scale is -1.
|
||||
/// </summary>
|
||||
public float ResScaleCustom { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Max Anisotropy. Values range from 0 - 16. Set to -1 to let the game decide.
|
||||
/// </summary>
|
||||
|
|
|
@ -271,6 +271,16 @@ namespace Ryujinx.Configuration
|
|||
/// </summary>
|
||||
public ReactiveObject<float> MaxAnisotropy { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Resolution Scale. An integer scale applied to applicable render targets. Values 1-4, or -1 to use a custom floating point scale instead.
|
||||
/// </summary>
|
||||
public ReactiveObject<int> ResScale { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Custom Resolution Scale. A custom floating point scale applied to applicable render targets. Only active when Resolution Scale is -1.
|
||||
/// </summary>
|
||||
public ReactiveObject<float> ResScaleCustom { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Dumps shaders in this local directory
|
||||
/// </summary>
|
||||
|
@ -283,6 +293,8 @@ namespace Ryujinx.Configuration
|
|||
|
||||
public GraphicsSection()
|
||||
{
|
||||
ResScale = new ReactiveObject<int>();
|
||||
ResScaleCustom = new ReactiveObject<float>();
|
||||
MaxAnisotropy = new ReactiveObject<float>();
|
||||
ShadersDumpPath = new ReactiveObject<string>();
|
||||
EnableVsync = new ReactiveObject<bool>();
|
||||
|
@ -354,6 +366,8 @@ namespace Ryujinx.Configuration
|
|||
ConfigurationFileFormat configurationFile = new ConfigurationFileFormat
|
||||
{
|
||||
Version = ConfigurationFileFormat.CurrentVersion,
|
||||
ResScale = Graphics.ResScale,
|
||||
ResScaleCustom = Graphics.ResScaleCustom,
|
||||
MaxAnisotropy = Graphics.MaxAnisotropy,
|
||||
GraphicsShadersDumpPath = Graphics.ShadersDumpPath,
|
||||
LoggingEnableDebug = Logger.EnableDebug,
|
||||
|
@ -410,6 +424,8 @@ namespace Ryujinx.Configuration
|
|||
|
||||
public void LoadDefault()
|
||||
{
|
||||
Graphics.ResScale.Value = 1;
|
||||
Graphics.ResScaleCustom.Value = 1.0f;
|
||||
Graphics.MaxAnisotropy.Value = -1;
|
||||
Graphics.ShadersDumpPath.Value = "";
|
||||
Logger.EnableDebug.Value = false;
|
||||
|
@ -652,10 +668,22 @@ namespace Ryujinx.Configuration
|
|||
configurationFileUpdated = true;
|
||||
}
|
||||
|
||||
if (configurationFileFormat.Version < 11)
|
||||
{
|
||||
Common.Logging.Logger.PrintWarning(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 11.");
|
||||
|
||||
configurationFileFormat.ResScale = 1;
|
||||
configurationFileFormat.ResScaleCustom = 1.0f;
|
||||
|
||||
configurationFileUpdated = true;
|
||||
}
|
||||
|
||||
List<InputConfig> inputConfig = new List<InputConfig>();
|
||||
inputConfig.AddRange(configurationFileFormat.ControllerConfig);
|
||||
inputConfig.AddRange(configurationFileFormat.KeyboardConfig);
|
||||
|
||||
Graphics.ResScale.Value = configurationFileFormat.ResScale;
|
||||
Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom;
|
||||
Graphics.MaxAnisotropy.Value = configurationFileFormat.MaxAnisotropy;
|
||||
Graphics.ShadersDumpPath.Value = configurationFileFormat.GraphicsShadersDumpPath;
|
||||
Logger.EnableDebug.Value = configurationFileFormat.LoggingEnableDebug;
|
||||
|
|
|
@ -162,11 +162,21 @@ namespace Ryujinx.Graphics.GAL
|
|||
|
||||
public static class FormatExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the texture format is an ASTC format.
|
||||
/// </summary>
|
||||
/// <param name="format">Texture format</param>
|
||||
/// <returns>True if the texture format is an ASTC format, false otherwise</returns>
|
||||
public static bool IsAstc(this Format format)
|
||||
{
|
||||
return format.IsAstcUnorm() || format.IsAstcSrgb();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the texture format is an ASTC Unorm format.
|
||||
/// </summary>
|
||||
/// <param name="format">Texture format</param>
|
||||
/// <returns>True if the texture format is an ASTC Unorm format, false otherwise</returns>
|
||||
public static bool IsAstcUnorm(this Format format)
|
||||
{
|
||||
switch (format)
|
||||
|
@ -191,6 +201,11 @@ namespace Ryujinx.Graphics.GAL
|
|||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the texture format is an ASTC SRGB format.
|
||||
/// </summary>
|
||||
/// <param name="format">Texture format</param>
|
||||
/// <returns>True if the texture format is an ASTC SRGB format, false otherwise</returns>
|
||||
public static bool IsAstcSrgb(this Format format)
|
||||
{
|
||||
switch (format)
|
||||
|
@ -214,5 +229,131 @@ namespace Ryujinx.Graphics.GAL
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the texture format is a depth, stencil or depth-stencil format.
|
||||
/// </summary>
|
||||
/// <param name="format">Texture format</param>
|
||||
/// <returns>True if the format is a depth, stencil or depth-stencil format, false otherwise</returns>
|
||||
public static bool IsDepthOrStencil(this Format format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case Format.D16Unorm:
|
||||
case Format.D24UnormS8Uint:
|
||||
case Format.D24X8Unorm:
|
||||
case Format.D32Float:
|
||||
case Format.D32FloatS8Uint:
|
||||
case Format.S8Uint:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the texture format is an unsigned integer color format.
|
||||
/// </summary>
|
||||
/// <param name="format">Texture format</param>
|
||||
/// <returns>True if the texture format is an unsigned integer color format, false otherwise</returns>
|
||||
public static bool IsUint(this Format format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case Format.R8Uint:
|
||||
case Format.R16Uint:
|
||||
case Format.R32Uint:
|
||||
case Format.R8G8Uint:
|
||||
case Format.R16G16Uint:
|
||||
case Format.R32G32Uint:
|
||||
case Format.R8G8B8Uint:
|
||||
case Format.R16G16B16Uint:
|
||||
case Format.R32G32B32Uint:
|
||||
case Format.R8G8B8A8Uint:
|
||||
case Format.R16G16B16A16Uint:
|
||||
case Format.R32G32B32A32Uint:
|
||||
case Format.R10G10B10A2Uint:
|
||||
case Format.R8G8B8X8Uint:
|
||||
case Format.R16G16B16X16Uint:
|
||||
case Format.R32G32B32X32Uint:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the texture format is a signed integer color format.
|
||||
/// </summary>
|
||||
/// <param name="format">Texture format</param>
|
||||
/// <returns>True if the texture format is a signed integer color format, false otherwise</returns>
|
||||
public static bool IsSint(this Format format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case Format.R8Sint:
|
||||
case Format.R16Sint:
|
||||
case Format.R32Sint:
|
||||
case Format.R8G8Sint:
|
||||
case Format.R16G16Sint:
|
||||
case Format.R32G32Sint:
|
||||
case Format.R8G8B8Sint:
|
||||
case Format.R16G16B16Sint:
|
||||
case Format.R32G32B32Sint:
|
||||
case Format.R8G8B8A8Sint:
|
||||
case Format.R16G16B16A16Sint:
|
||||
case Format.R32G32B32A32Sint:
|
||||
case Format.R10G10B10A2Sint:
|
||||
case Format.R8G8B8X8Sint:
|
||||
case Format.R16G16B16X16Sint:
|
||||
case Format.R32G32B32X32Sint:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the texture format is an integer color format.
|
||||
/// </summary>
|
||||
/// <param name="format">Texture format</param>
|
||||
/// <returns>True if the texture format is an integer color format, false otherwise</returns>
|
||||
public static bool IsInteger(this Format format)
|
||||
{
|
||||
return format.IsUint() || format.IsSint();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the texture format only has one component.
|
||||
/// </summary>
|
||||
/// <param name="format">Texture format</param>
|
||||
/// <returns>True if the texture format only has one component, false otherwise</returns>
|
||||
public static bool HasOneComponent(this Format format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case Format.R8Unorm:
|
||||
case Format.R8Snorm:
|
||||
case Format.R8Uint:
|
||||
case Format.R8Sint:
|
||||
case Format.R16Float:
|
||||
case Format.R16Unorm:
|
||||
case Format.R16Snorm:
|
||||
case Format.R16Uint:
|
||||
case Format.R16Sint:
|
||||
case Format.R32Float:
|
||||
case Format.R32Uint:
|
||||
case Format.R32Sint:
|
||||
case Format.R8Uscaled:
|
||||
case Format.R8Sscaled:
|
||||
case Format.R16Uscaled:
|
||||
case Format.R16Sscaled:
|
||||
case Format.R32Uscaled:
|
||||
case Format.R32Sscaled:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -54,6 +54,8 @@ namespace Ryujinx.Graphics.GAL
|
|||
|
||||
void SetRasterizerDiscard(bool discard);
|
||||
|
||||
void SetRenderTargetScale(float scale);
|
||||
|
||||
void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask);
|
||||
|
||||
void SetRenderTargets(ITexture[] colors, ITexture depthStencil);
|
||||
|
@ -84,5 +86,7 @@ namespace Ryujinx.Graphics.GAL
|
|||
bool TryHostConditionalRendering(ICounterEvent value, ulong compare, bool isEqual);
|
||||
bool TryHostConditionalRendering(ICounterEvent value, ICounterEvent compare, bool isEqual);
|
||||
void EndHostConditionalRendering();
|
||||
|
||||
void UpdateRenderScale(ShaderStage stage, int textureCount);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace Ryujinx.Graphics.GAL
|
|||
IProgram CreateProgram(IShader[] shaders);
|
||||
|
||||
ISampler CreateSampler(SamplerCreateInfo info);
|
||||
ITexture CreateTexture(TextureCreateInfo info);
|
||||
ITexture CreateTexture(TextureCreateInfo info, float scale);
|
||||
|
||||
void DeleteBuffer(BufferHandle buffer);
|
||||
|
||||
|
|
|
@ -4,6 +4,10 @@ namespace Ryujinx.Graphics.GAL
|
|||
{
|
||||
public interface ITexture : IDisposable
|
||||
{
|
||||
int Width { get; }
|
||||
int Height { get; }
|
||||
float ScaleFactor { get; }
|
||||
|
||||
void CopyTo(ITexture destination, int firstLayer, int firstLevel);
|
||||
void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter);
|
||||
|
||||
|
|
|
@ -132,11 +132,11 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
|||
|
||||
if (descriptor.IsBindless)
|
||||
{
|
||||
textureBindings[index] = new TextureBindingInfo(target, descriptor.CbufOffset, descriptor.CbufSlot);
|
||||
textureBindings[index] = new TextureBindingInfo(target, descriptor.CbufOffset, descriptor.CbufSlot, descriptor.Flags);
|
||||
}
|
||||
else
|
||||
{
|
||||
textureBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex);
|
||||
textureBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex, descriptor.Flags);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,7 +150,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
|||
|
||||
Target target = GetTarget(descriptor.Type);
|
||||
|
||||
imageBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex);
|
||||
imageBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex, descriptor.Flags);
|
||||
}
|
||||
|
||||
TextureManager.SetComputeImages(imageBindings);
|
||||
|
|
|
@ -26,7 +26,9 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
|||
UpdateScissorState(state);
|
||||
}
|
||||
|
||||
UpdateRenderTargetState(state, useControl: false);
|
||||
int index = (argument >> 6) & 0xf;
|
||||
|
||||
UpdateRenderTargetState(state, useControl: false, singleUse: index);
|
||||
|
||||
TextureManager.CommitGraphicsBindings();
|
||||
|
||||
|
@ -35,8 +37,6 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
|||
|
||||
uint componentMask = (uint)((argument >> 2) & 0xf);
|
||||
|
||||
int index = (argument >> 6) & 0xf;
|
||||
|
||||
if (componentMask != 0)
|
||||
{
|
||||
var clearColor = state.Get<ClearColors>(MethodOffset.ClearColors);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.State;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine
|
||||
{
|
||||
|
@ -32,13 +33,18 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
|||
dstCopyTexture.Format = RtFormat.D32Float;
|
||||
}
|
||||
|
||||
Texture dstTexture = TextureManager.FindOrCreateTexture(dstCopyTexture);
|
||||
Texture dstTexture = TextureManager.FindOrCreateTexture(dstCopyTexture, srcTexture.ScaleMode == Image.TextureScaleMode.Scaled);
|
||||
|
||||
if (dstTexture == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (srcTexture.ScaleFactor != dstTexture.ScaleFactor)
|
||||
{
|
||||
srcTexture.PropagateScale(dstTexture);
|
||||
}
|
||||
|
||||
var control = state.Get<CopyTextureControl>(MethodOffset.CopyTextureControl);
|
||||
|
||||
var region = state.Get<CopyRegion>(MethodOffset.CopyRegion);
|
||||
|
@ -55,17 +61,19 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
|||
int dstX2 = region.DstX + region.DstWidth;
|
||||
int dstY2 = region.DstY + region.DstHeight;
|
||||
|
||||
float scale = srcTexture.ScaleFactor; // src and dest scales are identical now.
|
||||
|
||||
Extents2D srcRegion = new Extents2D(
|
||||
srcX1 / srcTexture.Info.SamplesInX,
|
||||
srcY1 / srcTexture.Info.SamplesInY,
|
||||
srcX2 / srcTexture.Info.SamplesInX,
|
||||
srcY2 / srcTexture.Info.SamplesInY);
|
||||
(int)Math.Ceiling(scale * (srcX1 / srcTexture.Info.SamplesInX)),
|
||||
(int)Math.Ceiling(scale * (srcY1 / srcTexture.Info.SamplesInY)),
|
||||
(int)Math.Ceiling(scale * (srcX2 / srcTexture.Info.SamplesInX)),
|
||||
(int)Math.Ceiling(scale * (srcY2 / srcTexture.Info.SamplesInY)));
|
||||
|
||||
Extents2D dstRegion = new Extents2D(
|
||||
dstX1 / dstTexture.Info.SamplesInX,
|
||||
dstY1 / dstTexture.Info.SamplesInY,
|
||||
dstX2 / dstTexture.Info.SamplesInX,
|
||||
dstY2 / dstTexture.Info.SamplesInY);
|
||||
(int)Math.Ceiling(scale * (dstX1 / dstTexture.Info.SamplesInX)),
|
||||
(int)Math.Ceiling(scale * (dstY1 / dstTexture.Info.SamplesInY)),
|
||||
(int)Math.Ceiling(scale * (dstX2 / dstTexture.Info.SamplesInX)),
|
||||
(int)Math.Ceiling(scale * (dstY2 / dstTexture.Info.SamplesInY)));
|
||||
|
||||
bool linearFilter = control.UnpackLinearFilter();
|
||||
|
||||
|
@ -79,17 +87,21 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
|||
// the second handles the region outside of the bounds).
|
||||
// We must also extend the source texture by one line to ensure we can wrap on the last line.
|
||||
// This is required by the (guest) OpenGL driver.
|
||||
if (srcRegion.X2 > srcTexture.Info.Width)
|
||||
if (srcX2 / srcTexture.Info.SamplesInX > srcTexture.Info.Width)
|
||||
{
|
||||
srcCopyTexture.Height++;
|
||||
|
||||
srcTexture = TextureManager.FindOrCreateTexture(srcCopyTexture);
|
||||
srcTexture = TextureManager.FindOrCreateTexture(srcCopyTexture, srcTexture.ScaleMode == Image.TextureScaleMode.Scaled);
|
||||
if (srcTexture.ScaleFactor != dstTexture.ScaleFactor)
|
||||
{
|
||||
srcTexture.PropagateScale(dstTexture);
|
||||
}
|
||||
|
||||
srcRegion = new Extents2D(
|
||||
srcRegion.X1 - srcTexture.Info.Width,
|
||||
srcRegion.Y1 + 1,
|
||||
srcRegion.X2 - srcTexture.Info.Width,
|
||||
srcRegion.Y2 + 1);
|
||||
(int)Math.Ceiling(scale * ((srcX1 / srcTexture.Info.SamplesInX) - srcTexture.Info.Width)),
|
||||
(int)Math.Ceiling(scale * ((srcY1 / srcTexture.Info.SamplesInY) + 1)),
|
||||
(int)Math.Ceiling(scale * ((srcX2 / srcTexture.Info.SamplesInX) - srcTexture.Info.Width)),
|
||||
(int)Math.Ceiling(scale * ((srcY2 / srcTexture.Info.SamplesInY) + 1)));
|
||||
|
||||
srcTexture.HostTexture.CopyTo(dstTexture.HostTexture, srcRegion, dstRegion, linearFilter);
|
||||
}
|
||||
|
|
|
@ -313,7 +313,8 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
|||
/// </summary>
|
||||
/// <param name="state">Current GPU state</param>
|
||||
/// <param name="useControl">Use draw buffers information from render target control register</param>
|
||||
private void UpdateRenderTargetState(GpuState state, bool useControl)
|
||||
/// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param>
|
||||
private void UpdateRenderTargetState(GpuState state, bool useControl, int singleUse = -1)
|
||||
{
|
||||
var rtControl = state.Get<RtControl>(MethodOffset.RtControl);
|
||||
|
||||
|
@ -324,6 +325,8 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
|||
int samplesInX = msaaMode.SamplesInX();
|
||||
int samplesInY = msaaMode.SamplesInY();
|
||||
|
||||
bool changedScale = false;
|
||||
|
||||
for (int index = 0; index < Constants.TotalRenderTargets; index++)
|
||||
{
|
||||
int rtIndex = useControl ? rtControl.UnpackPermutationIndex(index) : index;
|
||||
|
@ -332,14 +335,14 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
|||
|
||||
if (index >= count || !IsRtEnabled(colorState))
|
||||
{
|
||||
TextureManager.SetRenderTargetColor(index, null);
|
||||
changedScale |= TextureManager.SetRenderTargetColor(index, null);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
Texture color = TextureManager.FindOrCreateTexture(colorState, samplesInX, samplesInY);
|
||||
|
||||
TextureManager.SetRenderTargetColor(index, color);
|
||||
changedScale |= TextureManager.SetRenderTargetColor(index, color);
|
||||
|
||||
if (color != null)
|
||||
{
|
||||
|
@ -359,7 +362,16 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
|||
depthStencil = TextureManager.FindOrCreateTexture(dsState, dsSize, samplesInX, samplesInY);
|
||||
}
|
||||
|
||||
TextureManager.SetRenderTargetDepthStencil(depthStencil);
|
||||
changedScale |= TextureManager.SetRenderTargetDepthStencil(depthStencil);
|
||||
|
||||
if (changedScale)
|
||||
{
|
||||
TextureManager.UpdateRenderTargetScale(singleUse);
|
||||
_context.Renderer.Pipeline.SetRenderTargetScale(TextureManager.RenderTargetScale);
|
||||
|
||||
UpdateViewportTransform(state);
|
||||
UpdateScissorState(state);
|
||||
}
|
||||
|
||||
if (depthStencil != null)
|
||||
{
|
||||
|
@ -394,7 +406,21 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
|||
|
||||
if (enable)
|
||||
{
|
||||
_context.Renderer.Pipeline.SetScissor(index, scissor.X1, scissor.Y1, scissor.X2 - scissor.X1, scissor.Y2 - scissor.Y1);
|
||||
int x = scissor.X1;
|
||||
int y = scissor.Y1;
|
||||
int width = scissor.X2 - x;
|
||||
int height = scissor.Y2 - y;
|
||||
|
||||
float scale = TextureManager.RenderTargetScale;
|
||||
if (scale != 1f)
|
||||
{
|
||||
x = (int)(x * scale);
|
||||
y = (int)(y * scale);
|
||||
width = (int)Math.Ceiling(width * scale);
|
||||
height = (int)Math.Ceiling(height * scale);
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.SetScissor(index, x, y, width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -460,6 +486,15 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
|||
float width = MathF.Abs(transform.ScaleX) * 2;
|
||||
float height = MathF.Abs(transform.ScaleY) * 2;
|
||||
|
||||
float scale = TextureManager.RenderTargetScale;
|
||||
if (scale != 1f)
|
||||
{
|
||||
x *= scale;
|
||||
y *= scale;
|
||||
width *= scale;
|
||||
height *= scale;
|
||||
}
|
||||
|
||||
RectangleF region = new RectangleF(x, y, width, height);
|
||||
|
||||
ViewportSwizzle swizzleX = transform.UnpackSwizzleX();
|
||||
|
@ -909,11 +944,11 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
|||
|
||||
if (descriptor.IsBindless)
|
||||
{
|
||||
textureBindings[index] = new TextureBindingInfo(target, descriptor.CbufSlot, descriptor.CbufOffset);
|
||||
textureBindings[index] = new TextureBindingInfo(target, descriptor.CbufSlot, descriptor.CbufOffset, descriptor.Flags);
|
||||
}
|
||||
else
|
||||
{
|
||||
textureBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex);
|
||||
textureBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex, descriptor.Flags);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -927,7 +962,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
|||
|
||||
Target target = GetTarget(descriptor.Type);
|
||||
|
||||
imageBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex);
|
||||
imageBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex, descriptor.Flags);
|
||||
}
|
||||
|
||||
TextureManager.SetGraphicsImages(stage, imageBindings);
|
||||
|
|
|
@ -5,6 +5,11 @@ namespace Ryujinx.Graphics.Gpu
|
|||
/// </summary>
|
||||
public static class GraphicsConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolution scale.
|
||||
/// </summary>
|
||||
public static float ResScale = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Max Anisotropy. Values range from 0 - 16. Set to -1 to let the game decide.
|
||||
/// </summary>
|
||||
|
|
|
@ -29,10 +29,20 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// </summary>
|
||||
public TextureInfo Info { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Host scale factor.
|
||||
/// </summary>
|
||||
public float ScaleFactor { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Upscaling mode. Informs if a texture is scaled, or is eligible for scaling.
|
||||
/// </summary>
|
||||
public TextureScaleMode ScaleMode { get; private set; }
|
||||
|
||||
private int _depth;
|
||||
private int _layers;
|
||||
private readonly int _firstLayer;
|
||||
private readonly int _firstLevel;
|
||||
private int _firstLayer;
|
||||
private int _firstLevel;
|
||||
|
||||
private bool _hasData;
|
||||
|
||||
|
@ -92,18 +102,25 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// <param name="sizeInfo">Size information of the texture</param>
|
||||
/// <param name="firstLayer">The first layer of the texture, or 0 if the texture has no parent</param>
|
||||
/// <param name="firstLevel">The first mipmap level of the texture, or 0 if the texture has no parent</param>
|
||||
/// <param name="scaleFactor">The floating point scale factor to initialize with</param>
|
||||
/// <param name="scaleMode">The scale mode to initialize with</param>
|
||||
private Texture(
|
||||
GpuContext context,
|
||||
TextureInfo info,
|
||||
SizeInfo sizeInfo,
|
||||
int firstLayer,
|
||||
int firstLevel)
|
||||
GpuContext context,
|
||||
TextureInfo info,
|
||||
SizeInfo sizeInfo,
|
||||
int firstLayer,
|
||||
int firstLevel,
|
||||
float scaleFactor,
|
||||
TextureScaleMode scaleMode)
|
||||
{
|
||||
InitializeTexture(context, info, sizeInfo);
|
||||
|
||||
_firstLayer = firstLayer;
|
||||
_firstLevel = firstLevel;
|
||||
|
||||
ScaleFactor = scaleFactor;
|
||||
ScaleMode = scaleMode;
|
||||
|
||||
_hasData = true;
|
||||
}
|
||||
|
||||
|
@ -113,13 +130,23 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// <param name="context">GPU context that the texture belongs to</param>
|
||||
/// <param name="info">Texture information</param>
|
||||
/// <param name="sizeInfo">Size information of the texture</param>
|
||||
public Texture(GpuContext context, TextureInfo info, SizeInfo sizeInfo)
|
||||
/// <param name="scaleMode">The scale mode to initialize with. If scaled, the texture's data is loaded immediately and scaled up</param>
|
||||
public Texture(GpuContext context, TextureInfo info, SizeInfo sizeInfo, TextureScaleMode scaleMode)
|
||||
{
|
||||
ScaleFactor = 1f; // Texture is first loaded at scale 1x.
|
||||
ScaleMode = scaleMode;
|
||||
|
||||
InitializeTexture(context, info, sizeInfo);
|
||||
|
||||
TextureCreateInfo createInfo = TextureManager.GetCreateInfo(info, context.Capabilities);
|
||||
|
||||
HostTexture = _context.Renderer.CreateTexture(createInfo);
|
||||
HostTexture = _context.Renderer.CreateTexture(createInfo, ScaleFactor);
|
||||
|
||||
if (scaleMode == TextureScaleMode.Scaled)
|
||||
{
|
||||
SynchronizeMemory(); // Load the data and then scale it up.
|
||||
SetScale(GraphicsConfig.ResScale);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -162,7 +189,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
info,
|
||||
sizeInfo,
|
||||
_firstLayer + firstLayer,
|
||||
_firstLevel + firstLevel);
|
||||
_firstLevel + firstLevel,
|
||||
ScaleFactor,
|
||||
ScaleMode);
|
||||
|
||||
TextureCreateInfo createInfo = TextureManager.GetCreateInfo(info, _context.Capabilities);
|
||||
|
||||
|
@ -282,7 +311,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
}
|
||||
else
|
||||
{
|
||||
ITexture newStorage = _context.Renderer.CreateTexture(createInfo);
|
||||
ITexture newStorage = _context.Renderer.CreateTexture(createInfo, ScaleFactor);
|
||||
|
||||
HostTexture.CopyTo(newStorage, 0, 0);
|
||||
|
||||
|
@ -290,6 +319,149 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blacklists this texture from being scaled. Resets its scale to 1 if needed.
|
||||
/// </summary>
|
||||
public void BlacklistScale()
|
||||
{
|
||||
ScaleMode = TextureScaleMode.Blacklisted;
|
||||
SetScale(1f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Propagates the scale between this texture and another to ensure they have the same scale.
|
||||
/// If one texture is blacklisted from scaling, the other will become blacklisted too.
|
||||
/// </summary>
|
||||
/// <param name="other">The other texture</param>
|
||||
public void PropagateScale(Texture other)
|
||||
{
|
||||
if (other.ScaleMode == TextureScaleMode.Blacklisted || ScaleMode == TextureScaleMode.Blacklisted)
|
||||
{
|
||||
BlacklistScale();
|
||||
other.BlacklistScale();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Prefer the configured scale if present. If not, prefer the max.
|
||||
float targetScale = GraphicsConfig.ResScale;
|
||||
float sharedScale = (ScaleFactor == targetScale || other.ScaleFactor == targetScale) ? targetScale : Math.Max(ScaleFactor, other.ScaleFactor);
|
||||
|
||||
SetScale(sharedScale);
|
||||
other.SetScale(sharedScale);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method for copying our Texture2DArray texture to the given target, with scaling.
|
||||
/// This creates temporary views for each array layer on both textures, copying each one at a time.
|
||||
/// </summary>
|
||||
/// <param name="target">The texture array to copy to</param>
|
||||
private void CopyArrayScaled(ITexture target)
|
||||
{
|
||||
TextureInfo viewInfo = new TextureInfo(
|
||||
Info.Address,
|
||||
Info.Width,
|
||||
Info.Height,
|
||||
1,
|
||||
Info.Levels,
|
||||
Info.SamplesInX,
|
||||
Info.SamplesInY,
|
||||
Info.Stride,
|
||||
Info.IsLinear,
|
||||
Info.GobBlocksInY,
|
||||
Info.GobBlocksInZ,
|
||||
Info.GobBlocksInTileX,
|
||||
Target.Texture2D,
|
||||
Info.FormatInfo,
|
||||
Info.DepthStencilMode,
|
||||
Info.SwizzleR,
|
||||
Info.SwizzleG,
|
||||
Info.SwizzleB,
|
||||
Info.SwizzleA);
|
||||
|
||||
TextureCreateInfo createInfo = TextureManager.GetCreateInfo(viewInfo, _context.Capabilities);
|
||||
|
||||
for (int i = 0; i < Info.DepthOrLayers; i++)
|
||||
{
|
||||
ITexture from = HostTexture.CreateView(createInfo, i, 0);
|
||||
ITexture to = target.CreateView(createInfo, i, 0);
|
||||
|
||||
from.CopyTo(to, new Extents2D(0, 0, from.Width, from.Height), new Extents2D(0, 0, to.Width, to.Height), true);
|
||||
|
||||
from.Dispose();
|
||||
to.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the Scale Factor on this texture, and immediately recreates it at the correct size.
|
||||
/// When a texture is resized, a scaled copy is performed from the old texture to the new one, to ensure no data is lost.
|
||||
/// If scale is equivalent, this only propagates the blacklisted/scaled mode.
|
||||
/// If called on a view, its storage is resized instead.
|
||||
/// When resizing storage, all texture views are recreated.
|
||||
/// </summary>
|
||||
/// <param name="scale">The new scale factor for this texture</param>
|
||||
public void SetScale(float scale)
|
||||
{
|
||||
TextureScaleMode newScaleMode = ScaleMode == TextureScaleMode.Blacklisted ? ScaleMode : TextureScaleMode.Scaled;
|
||||
|
||||
if (_viewStorage != this)
|
||||
{
|
||||
_viewStorage.ScaleMode = newScaleMode;
|
||||
_viewStorage.SetScale(scale);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ScaleFactor != scale)
|
||||
{
|
||||
Logger.PrintDebug(LogClass.Gpu, $"Rescaling {Info.Width}x{Info.Height} {Info.FormatInfo.Format.ToString()} to ({ScaleFactor} to {scale}). ");
|
||||
TextureCreateInfo createInfo = TextureManager.GetCreateInfo(Info, _context.Capabilities);
|
||||
|
||||
ScaleFactor = scale;
|
||||
|
||||
ITexture newStorage = _context.Renderer.CreateTexture(createInfo, ScaleFactor);
|
||||
|
||||
if (Info.Target == Target.Texture2DArray)
|
||||
{
|
||||
CopyArrayScaled(newStorage);
|
||||
}
|
||||
else
|
||||
{
|
||||
HostTexture.CopyTo(newStorage, new Extents2D(0, 0, HostTexture.Width, HostTexture.Height), new Extents2D(0, 0, newStorage.Width, newStorage.Height), true);
|
||||
}
|
||||
|
||||
Logger.PrintDebug(LogClass.Gpu, $" Copy performed: {HostTexture.Width}x{HostTexture.Height} to {newStorage.Width}x{newStorage.Height}");
|
||||
|
||||
ReplaceStorage(newStorage);
|
||||
|
||||
// All views must be recreated against the new storage.
|
||||
|
||||
foreach (var view in _views)
|
||||
{
|
||||
Logger.PrintDebug(LogClass.Gpu, $" Recreating view {Info.Width}x{Info.Height} {Info.FormatInfo.Format.ToString()}.");
|
||||
view.ScaleFactor = scale;
|
||||
|
||||
TextureCreateInfo viewCreateInfo = TextureManager.GetCreateInfo(view.Info, _context.Capabilities);
|
||||
|
||||
ITexture newView = HostTexture.CreateView(viewCreateInfo, view._firstLayer - _firstLayer, view._firstLevel - _firstLevel);
|
||||
|
||||
view.ReplaceStorage(newView);
|
||||
|
||||
view.ScaleMode = newScaleMode;
|
||||
}
|
||||
}
|
||||
|
||||
if (ScaleMode != newScaleMode)
|
||||
{
|
||||
ScaleMode = newScaleMode;
|
||||
|
||||
foreach (var view in _views)
|
||||
{
|
||||
view.ScaleMode = newScaleMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronizes guest and host memory.
|
||||
/// This will overwrite the texture data with the texture data on the guest memory, if a CPU
|
||||
|
@ -310,9 +482,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
int modifiedCount = _context.PhysicalMemory.QueryModified(Address, Size, ResourceName.Texture, _modifiedRanges);
|
||||
|
||||
if (modifiedCount == 0 && _hasData)
|
||||
if (_hasData)
|
||||
{
|
||||
return;
|
||||
if (modifiedCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BlacklistScale();
|
||||
}
|
||||
|
||||
ReadOnlySpan<byte> data = _context.PhysicalMemory.GetSpan(Address, (int)Size);
|
||||
|
@ -432,6 +609,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// </summary>
|
||||
public void Flush()
|
||||
{
|
||||
BlacklistScale();
|
||||
_context.PhysicalMemory.Write(Address, GetTextureDataFromGpu());
|
||||
}
|
||||
|
||||
|
@ -445,6 +623,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// <returns>Host texture data</returns>
|
||||
private Span<byte> GetTextureDataFromGpu()
|
||||
{
|
||||
BlacklistScale();
|
||||
Span<byte> data = HostTexture.GetData();
|
||||
|
||||
if (Info.IsLinear)
|
||||
|
@ -980,10 +1159,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// <param name="parent">The parent texture</param>
|
||||
/// <param name="info">The new view texture information</param>
|
||||
/// <param name="hostTexture">The new host texture</param>
|
||||
public void ReplaceView(Texture parent, TextureInfo info, ITexture hostTexture)
|
||||
/// <param name="firstLayer">The first layer of the view</param>
|
||||
/// <param name="firstLevel">The first level of the view</param>
|
||||
public void ReplaceView(Texture parent, TextureInfo info, ITexture hostTexture, int firstLayer, int firstLevel)
|
||||
{
|
||||
ReplaceStorage(hostTexture);
|
||||
|
||||
_firstLayer = parent._firstLayer + firstLayer;
|
||||
_firstLevel = parent._firstLevel + firstLevel;
|
||||
parent._viewStorage.AddView(this);
|
||||
|
||||
SetInfo(info);
|
||||
|
@ -1075,7 +1258,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
// already deleted (views count is 0).
|
||||
if (_referenceCount == 0 && _views.Count == 0)
|
||||
{
|
||||
DisposeTextures();
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1088,8 +1271,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
_arrayViewTexture?.Dispose();
|
||||
_arrayViewTexture = null;
|
||||
|
||||
Disposed?.Invoke(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1098,6 +1279,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
public void Dispose()
|
||||
{
|
||||
DisposeTextures();
|
||||
|
||||
Disposed?.Invoke(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
|
@ -37,12 +38,18 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// </summary>
|
||||
public int CbufOffset { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Flags from the texture descriptor that indicate how the texture is used.
|
||||
/// </summary>
|
||||
public TextureUsageFlags Flags { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the texture binding information structure.
|
||||
/// </summary>
|
||||
/// <param name="target">The shader sampler target type</param>
|
||||
/// <param name="handle">The shader texture handle (read index into the texture constant buffer)</param>
|
||||
public TextureBindingInfo(Target target, int handle)
|
||||
/// <param name="flags">The texture's usage flags, indicating how it is used in the shader</param>
|
||||
public TextureBindingInfo(Target target, int handle, TextureUsageFlags flags)
|
||||
{
|
||||
Target = target;
|
||||
Handle = handle;
|
||||
|
@ -51,6 +58,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
CbufSlot = 0;
|
||||
CbufOffset = 0;
|
||||
|
||||
Flags = flags;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -59,7 +68,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// <param name="target">The shader sampler target type</param>
|
||||
/// <param name="cbufSlot">Constant buffer slot where the bindless texture handle is located</param>
|
||||
/// <param name="cbufOffset">Constant buffer offset of the bindless texture handle</param>
|
||||
public TextureBindingInfo(Target target, int cbufSlot, int cbufOffset)
|
||||
/// <param name="flags">The texture's usage flags, indicating how it is used in the shader</param>
|
||||
public TextureBindingInfo(Target target, int cbufSlot, int cbufOffset, TextureUsageFlags flags)
|
||||
{
|
||||
Target = target;
|
||||
Handle = 0;
|
||||
|
@ -68,6 +78,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
CbufSlot = cbufSlot;
|
||||
CbufOffset = cbufOffset;
|
||||
|
||||
Flags = flags;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -174,6 +174,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
return;
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
|
||||
for (int index = 0; index < _textureBindings[stageIndex].Length; index++)
|
||||
{
|
||||
TextureBindingInfo binding = _textureBindings[stageIndex][index];
|
||||
|
@ -216,6 +218,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
Texture texture = pool.Get(textureId);
|
||||
|
||||
if ((binding.Flags & TextureUsageFlags.ResScaleUnsupported) != 0)
|
||||
{
|
||||
texture?.BlacklistScale();
|
||||
}
|
||||
|
||||
ITexture hostTexture = texture?.GetTargetTexture(binding.Target);
|
||||
|
||||
if (_textureState[stageIndex][index].Texture != hostTexture || _rebind)
|
||||
|
@ -223,6 +230,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
_textureState[stageIndex][index].Texture = hostTexture;
|
||||
|
||||
_context.Renderer.Pipeline.SetTexture(index, stage, hostTexture);
|
||||
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (hostTexture != null && texture.Info.Target == Target.TextureBuffer)
|
||||
|
@ -244,6 +253,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
_context.Renderer.Pipeline.SetSampler(index, stage, hostSampler);
|
||||
}
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
_context.Renderer.Pipeline.UpdateRenderScale(stage, _textureBindings[stageIndex].Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -269,6 +283,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
Texture texture = pool.Get(textureId);
|
||||
|
||||
if ((binding.Flags & TextureUsageFlags.ResScaleUnsupported) != 0)
|
||||
{
|
||||
texture?.BlacklistScale();
|
||||
}
|
||||
|
||||
ITexture hostTexture = texture?.GetTargetTexture(binding.Target);
|
||||
|
||||
if (_imageState[stageIndex][index].Texture != hostTexture || _rebind)
|
||||
|
|
|
@ -39,6 +39,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
private readonly HashSet<Texture> _modified;
|
||||
private readonly HashSet<Texture> _modifiedLinear;
|
||||
|
||||
/// <summary>
|
||||
/// The scaling factor applied to all currently bound render targets.
|
||||
/// </summary>
|
||||
public float RenderTargetScale { get; private set; } = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of the texture manager.
|
||||
/// </summary>
|
||||
|
@ -169,18 +174,112 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// </summary>
|
||||
/// <param name="index">The index of the color buffer to set (up to 8)</param>
|
||||
/// <param name="color">The color buffer texture</param>
|
||||
public void SetRenderTargetColor(int index, Texture color)
|
||||
/// <returns>True if render target scale must be updated.</returns>
|
||||
public bool SetRenderTargetColor(int index, Texture color)
|
||||
{
|
||||
bool hasValue = color != null;
|
||||
bool changesScale = (hasValue != (_rtColors[index] != null)) || (hasValue && RenderTargetScale != color.ScaleFactor);
|
||||
_rtColors[index] = color;
|
||||
|
||||
return changesScale || (hasValue && color.ScaleMode != TextureScaleMode.Blacklisted && color.ScaleFactor != GraphicsConfig.ResScale);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the Render Target scale, given the currently bound render targets.
|
||||
/// This will update scale to match the configured scale, scale textures that are eligible but not scaled,
|
||||
/// and propagate blacklisted status from one texture to the ones bound with it.
|
||||
/// </summary>
|
||||
/// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param>
|
||||
public void UpdateRenderTargetScale(int singleUse)
|
||||
{
|
||||
// Make sure all scales for render targets are at the highest they should be. Blacklisted targets should propagate their scale to the other targets.
|
||||
bool mismatch = false;
|
||||
bool blacklisted = false;
|
||||
bool hasUpscaled = false;
|
||||
float targetScale = GraphicsConfig.ResScale;
|
||||
|
||||
void ConsiderTarget(Texture target)
|
||||
{
|
||||
if (target == null) return;
|
||||
float scale = target.ScaleFactor;
|
||||
|
||||
switch (target.ScaleMode)
|
||||
{
|
||||
case TextureScaleMode.Blacklisted:
|
||||
mismatch |= scale != 1f;
|
||||
blacklisted = true;
|
||||
break;
|
||||
case TextureScaleMode.Eligible:
|
||||
mismatch = true; // We must make a decision.
|
||||
break;
|
||||
case TextureScaleMode.Scaled:
|
||||
hasUpscaled = true;
|
||||
mismatch |= scale != targetScale; // If the target scale has changed, reset the scale for all targets.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (singleUse != -1)
|
||||
{
|
||||
// If only one target is in use (by a clear, for example) the others do not need to be checked for mismatching scale.
|
||||
ConsiderTarget(_rtColors[singleUse]);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (Texture color in _rtColors)
|
||||
{
|
||||
ConsiderTarget(color);
|
||||
}
|
||||
}
|
||||
|
||||
ConsiderTarget(_rtDepthStencil);
|
||||
|
||||
mismatch |= blacklisted && hasUpscaled;
|
||||
|
||||
if (blacklisted)
|
||||
{
|
||||
targetScale = 1f;
|
||||
}
|
||||
|
||||
if (mismatch)
|
||||
{
|
||||
if (blacklisted)
|
||||
{
|
||||
// Propagate the blacklisted state to the other textures.
|
||||
foreach (Texture color in _rtColors)
|
||||
{
|
||||
color?.BlacklistScale();
|
||||
}
|
||||
|
||||
_rtDepthStencil?.BlacklistScale();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set the scale of the other textures.
|
||||
foreach (Texture color in _rtColors)
|
||||
{
|
||||
color?.SetScale(targetScale);
|
||||
}
|
||||
|
||||
_rtDepthStencil?.SetScale(targetScale);
|
||||
}
|
||||
}
|
||||
|
||||
RenderTargetScale = targetScale;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the render target depth-stencil buffer.
|
||||
/// </summary>
|
||||
/// <param name="depthStencil">The depth-stencil buffer texture</param>
|
||||
public void SetRenderTargetDepthStencil(Texture depthStencil)
|
||||
/// <returns>True if render target scale must be updated.</returns>
|
||||
public bool SetRenderTargetDepthStencil(Texture depthStencil)
|
||||
{
|
||||
bool hasValue = depthStencil != null;
|
||||
bool changesScale = (hasValue != (_rtDepthStencil != null)) || (hasValue && RenderTargetScale != depthStencil.ScaleFactor);
|
||||
_rtDepthStencil = depthStencil;
|
||||
|
||||
return changesScale || (hasValue && depthStencil.ScaleMode != TextureScaleMode.Blacklisted && depthStencil.ScaleFactor != GraphicsConfig.ResScale);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -262,12 +361,59 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a given texture is eligible for upscaling from its info.
|
||||
/// </summary>
|
||||
/// <param name="info">The texture info to check</param>
|
||||
/// <returns>True if eligible</returns>
|
||||
public bool IsUpscaleCompatible(TextureInfo info)
|
||||
{
|
||||
return (info.Target == Target.Texture2D || info.Target == Target.Texture2DArray) && info.Levels == 1 && !info.FormatInfo.IsCompressed && UpscaleSafeMode(info);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a given texture is "safe" for upscaling from its info.
|
||||
/// Note that this is different from being compatible - this elilinates targets that would have detrimental effects when scaled.
|
||||
/// </summary>
|
||||
/// <param name="info">The texture info to check</param>
|
||||
/// <returns>True if safe</returns>
|
||||
public bool UpscaleSafeMode(TextureInfo info)
|
||||
{
|
||||
// While upscaling works for all targets defined by IsUpscaleCompatible, we additionally blacklist targets here that
|
||||
// may have undesirable results (upscaling blur textures) or simply waste GPU resources (upscaling texture atlas).
|
||||
|
||||
if (!(info.FormatInfo.Format.IsDepthOrStencil() || info.FormatInfo.Format.HasOneComponent()))
|
||||
{
|
||||
// Discount square textures that aren't depth-stencil like. (excludes game textures, cubemap faces, most 3D texture LUT, texture atlas)
|
||||
// Detect if the texture is possibly square. Widths may be aligned, so to remove the uncertainty we align both the width and height.
|
||||
|
||||
int widthAlignment = (info.IsLinear ? 32 : 64) / info.FormatInfo.BytesPerPixel;
|
||||
|
||||
bool possiblySquare = BitUtils.AlignUp(info.Width, widthAlignment) == BitUtils.AlignUp(info.Height, widthAlignment);
|
||||
|
||||
if (possiblySquare)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int aspect = (int)Math.Round((info.Width / (float)info.Height) * 9);
|
||||
if (aspect == 16 && info.Height < 360)
|
||||
{
|
||||
// Targets that are roughly 16:9 can only be rescaled if they're equal to or above 360p. (excludes blur and bloom textures)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find an existing texture, or create a new one if not found.
|
||||
/// </summary>
|
||||
/// <param name="copyTexture">Copy texture to find or create</param>
|
||||
/// <param name="preferScaling">Indicates if the texture should be scaled from the start</param>
|
||||
/// <returns>The texture</returns>
|
||||
public Texture FindOrCreateTexture(CopyTexture copyTexture)
|
||||
public Texture FindOrCreateTexture(CopyTexture copyTexture, bool preferScaling = true)
|
||||
{
|
||||
ulong address = _context.MemoryManager.Translate(copyTexture.Address.Pack());
|
||||
|
||||
|
@ -308,7 +454,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
Target.Texture2D,
|
||||
formatInfo);
|
||||
|
||||
Texture texture = FindOrCreateTexture(info, TextureSearchFlags.IgnoreMs);
|
||||
TextureSearchFlags flags = TextureSearchFlags.IgnoreMs;
|
||||
|
||||
if (preferScaling)
|
||||
{
|
||||
flags |= TextureSearchFlags.WithUpscale;
|
||||
}
|
||||
|
||||
Texture texture = FindOrCreateTexture(info, flags);
|
||||
|
||||
texture.SynchronizeMemory();
|
||||
|
||||
|
@ -391,7 +544,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
target,
|
||||
formatInfo);
|
||||
|
||||
Texture texture = FindOrCreateTexture(info);
|
||||
Texture texture = FindOrCreateTexture(info, TextureSearchFlags.WithUpscale);
|
||||
|
||||
texture.SynchronizeMemory();
|
||||
|
||||
|
@ -440,7 +593,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
target,
|
||||
formatInfo);
|
||||
|
||||
Texture texture = FindOrCreateTexture(info);
|
||||
Texture texture = FindOrCreateTexture(info, TextureSearchFlags.WithUpscale);
|
||||
|
||||
texture.SynchronizeMemory();
|
||||
|
||||
|
@ -457,6 +610,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
{
|
||||
bool isSamplerTexture = (flags & TextureSearchFlags.Sampler) != 0;
|
||||
|
||||
bool isScalable = IsUpscaleCompatible(info);
|
||||
|
||||
TextureScaleMode scaleMode = TextureScaleMode.Blacklisted;
|
||||
if (isScalable)
|
||||
{
|
||||
scaleMode = (flags & TextureSearchFlags.WithUpscale) != 0 ? TextureScaleMode.Scaled : TextureScaleMode.Eligible;
|
||||
}
|
||||
|
||||
// Try to find a perfect texture match, with the same address and parameters.
|
||||
int sameAddressOverlapsCount = _textures.FindOverlaps(info.Address, ref _textureOverlaps);
|
||||
|
||||
|
@ -556,7 +717,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
// No match, create a new texture.
|
||||
if (texture == null)
|
||||
{
|
||||
texture = new Texture(_context, info, sizeInfo);
|
||||
texture = new Texture(_context, info, sizeInfo, scaleMode);
|
||||
|
||||
// We need to synchronize before copying the old view data to the texture,
|
||||
// otherwise the copied data would be overwritten by a future synchronization.
|
||||
|
@ -572,6 +733,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities);
|
||||
|
||||
if (texture.ScaleFactor != overlap.ScaleFactor)
|
||||
{
|
||||
// A bit tricky, our new texture may need to contain an existing texture that is upscaled, but isn't itself.
|
||||
// In that case, we prefer the higher scale only if our format is render-target-like, otherwise we scale the view down before copy.
|
||||
|
||||
texture.PropagateScale(overlap);
|
||||
}
|
||||
|
||||
ITexture newView = texture.HostTexture.CreateView(createInfo, firstLayer, firstLevel);
|
||||
|
||||
overlap.HostTexture.CopyTo(newView, 0, 0);
|
||||
|
@ -583,7 +752,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
CacheTextureModified(texture);
|
||||
}
|
||||
|
||||
overlap.ReplaceView(texture, overlapInfo, newView);
|
||||
overlap.ReplaceView(texture, overlapInfo, newView, firstLayer, firstLevel);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -602,6 +771,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
out int firstLayer,
|
||||
out int firstLevel))
|
||||
{
|
||||
overlap.BlacklistScale();
|
||||
|
||||
overlap.HostTexture.CopyTo(texture.HostTexture, firstLayer, firstLevel);
|
||||
|
||||
if (IsTextureModified(overlap))
|
||||
|
|
|
@ -174,7 +174,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
swizzleB,
|
||||
swizzleA);
|
||||
|
||||
if (IsDepthStencil(formatInfo.Format))
|
||||
if (formatInfo.Format.IsDepthOrStencil())
|
||||
{
|
||||
swizzleR = SwizzleComponent.Red;
|
||||
swizzleG = SwizzleComponent.Red;
|
||||
|
@ -263,26 +263,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
component == SwizzleComponent.Green;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the texture format is a depth, stencil or depth-stencil format.
|
||||
/// </summary>
|
||||
/// <param name="format">Texture format</param>
|
||||
/// <returns>True if the format is a depth, stencil or depth-stencil format, false otherwise</returns>
|
||||
private static bool IsDepthStencil(Format format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case Format.D16Unorm:
|
||||
case Format.D24UnormS8Uint:
|
||||
case Format.D24X8Unorm:
|
||||
case Format.D32Float:
|
||||
case Format.D32FloatS8Uint:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrements the reference count of the texture.
|
||||
/// This indicates that the texture pool is not using it anymore.
|
||||
|
|
14
Ryujinx.Graphics.Gpu/Image/TextureScaleMode.cs
Normal file
14
Ryujinx.Graphics.Gpu/Image/TextureScaleMode.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// The scale mode for a given texture.
|
||||
/// Blacklisted textures cannot be scaled, Eligible textures have not been scaled yet,
|
||||
/// and Scaled textures have been scaled already.
|
||||
/// </summary>
|
||||
enum TextureScaleMode
|
||||
{
|
||||
Eligible = 0,
|
||||
Scaled = 1,
|
||||
Blacklisted = 2
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
None = 0,
|
||||
IgnoreMs = 1 << 0,
|
||||
Strict = 1 << 1 | Sampler,
|
||||
Sampler = 1 << 2
|
||||
Sampler = 1 << 2,
|
||||
WithUpscale = 1 << 3
|
||||
}
|
||||
}
|
|
@ -146,7 +146,7 @@ namespace Ryujinx.Graphics.Gpu
|
|||
{
|
||||
pt.AcquireCallback(_context, pt.UserObj);
|
||||
|
||||
Texture texture = _context.Methods.TextureManager.FindOrCreateTexture(pt.Info);
|
||||
Texture texture = _context.Methods.TextureManager.FindOrCreateTexture(pt.Info, TextureSearchFlags.WithUpscale);
|
||||
|
||||
texture.SynchronizeMemory();
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using OpenTK.Graphics.OpenGL;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.OpenGL.Image
|
||||
{
|
||||
|
@ -9,15 +10,19 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
|
||||
protected TextureCreateInfo Info { get; }
|
||||
|
||||
public int Width => Info.Width;
|
||||
public int Height => Info.Height;
|
||||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
public float ScaleFactor { get; }
|
||||
|
||||
public Target Target => Info.Target;
|
||||
public Format Format => Info.Format;
|
||||
|
||||
public TextureBase(TextureCreateInfo info)
|
||||
public TextureBase(TextureCreateInfo info, float scaleFactor = 1f)
|
||||
{
|
||||
Info = info;
|
||||
Width = (int)Math.Ceiling(Info.Width * scaleFactor);
|
||||
Height = (int)Math.Ceiling(Info.Height * scaleFactor);
|
||||
ScaleFactor = scaleFactor;
|
||||
|
||||
Handle = GL.GenTexture();
|
||||
}
|
||||
|
|
|
@ -33,6 +33,11 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
|
||||
ClearBufferMask mask = GetMask(src.Format);
|
||||
|
||||
if ((mask & (ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit)) != 0 || src.Format.IsInteger())
|
||||
{
|
||||
linearFilter = false;
|
||||
}
|
||||
|
||||
BlitFramebufferFilter filter = linearFilter
|
||||
? BlitFramebufferFilter.Linear
|
||||
: BlitFramebufferFilter.Nearest;
|
||||
|
@ -55,6 +60,9 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
mask,
|
||||
filter);
|
||||
|
||||
Attach(FramebufferTarget.ReadFramebuffer, src.Format, 0);
|
||||
Attach(FramebufferTarget.DrawFramebuffer, dst.Format, 0);
|
||||
|
||||
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, oldReadFramebufferHandle);
|
||||
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, oldDrawFramebufferHandle);
|
||||
|
||||
|
|
|
@ -15,15 +15,16 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
int srcLayer,
|
||||
int dstLayer,
|
||||
int srcLevel,
|
||||
int dstLevel)
|
||||
int dstLevel,
|
||||
float scaleFactor = 1f)
|
||||
{
|
||||
int srcWidth = srcInfo.Width;
|
||||
int srcHeight = srcInfo.Height;
|
||||
int srcWidth = (int)Math.Ceiling(srcInfo.Width * scaleFactor);
|
||||
int srcHeight = (int)Math.Ceiling(srcInfo.Height * scaleFactor);
|
||||
int srcDepth = srcInfo.GetDepthOrLayers();
|
||||
int srcLevels = srcInfo.Levels;
|
||||
|
||||
int dstWidth = dstInfo.Width;
|
||||
int dstHeight = dstInfo.Height;
|
||||
int dstWidth = (int)Math.Ceiling(dstInfo.Width * scaleFactor);
|
||||
int dstHeight = (int)Math.Ceiling(dstInfo.Height * scaleFactor);
|
||||
int dstDepth = dstInfo.GetDepthOrLayers();
|
||||
int dstLevels = dstInfo.Levels;
|
||||
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
using OpenTK.Graphics.OpenGL;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.OpenGL.Image
|
||||
{
|
||||
class TextureStorage
|
||||
{
|
||||
public int Handle { get; private set; }
|
||||
public float ScaleFactor { get; private set; }
|
||||
|
||||
public TextureCreateInfo Info { get; }
|
||||
|
||||
|
@ -14,12 +16,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
|
||||
private int _viewsCount;
|
||||
|
||||
public TextureStorage(Renderer renderer, TextureCreateInfo info)
|
||||
public TextureStorage(Renderer renderer, TextureCreateInfo info, float scaleFactor)
|
||||
{
|
||||
_renderer = renderer;
|
||||
Info = info;
|
||||
|
||||
Handle = GL.GenTexture();
|
||||
ScaleFactor = scaleFactor;
|
||||
|
||||
CreateImmutableStorage();
|
||||
}
|
||||
|
@ -32,6 +35,9 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
|
||||
GL.BindTexture(target, Handle);
|
||||
|
||||
int width = (int)Math.Ceiling(Info.Width * ScaleFactor);
|
||||
int height = (int)Math.Ceiling(Info.Height * ScaleFactor);
|
||||
|
||||
FormatInfo format = FormatTable.GetFormatInfo(Info.Format);
|
||||
|
||||
SizedInternalFormat internalFormat;
|
||||
|
@ -52,7 +58,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
TextureTarget1d.Texture1D,
|
||||
Info.Levels,
|
||||
internalFormat,
|
||||
Info.Width);
|
||||
width);
|
||||
break;
|
||||
|
||||
case Target.Texture1DArray:
|
||||
|
@ -60,8 +66,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
TextureTarget2d.Texture1DArray,
|
||||
Info.Levels,
|
||||
internalFormat,
|
||||
Info.Width,
|
||||
Info.Height);
|
||||
width,
|
||||
height);
|
||||
break;
|
||||
|
||||
case Target.Texture2D:
|
||||
|
@ -69,8 +75,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
TextureTarget2d.Texture2D,
|
||||
Info.Levels,
|
||||
internalFormat,
|
||||
Info.Width,
|
||||
Info.Height);
|
||||
width,
|
||||
height);
|
||||
break;
|
||||
|
||||
case Target.Texture2DArray:
|
||||
|
@ -78,8 +84,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
TextureTarget3d.Texture2DArray,
|
||||
Info.Levels,
|
||||
internalFormat,
|
||||
Info.Width,
|
||||
Info.Height,
|
||||
width,
|
||||
height,
|
||||
Info.Depth);
|
||||
break;
|
||||
|
||||
|
@ -88,8 +94,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
TextureTargetMultisample2d.Texture2DMultisample,
|
||||
Info.Samples,
|
||||
internalFormat,
|
||||
Info.Width,
|
||||
Info.Height,
|
||||
width,
|
||||
height,
|
||||
true);
|
||||
break;
|
||||
|
||||
|
@ -98,8 +104,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
TextureTargetMultisample3d.Texture2DMultisampleArray,
|
||||
Info.Samples,
|
||||
internalFormat,
|
||||
Info.Width,
|
||||
Info.Height,
|
||||
width,
|
||||
height,
|
||||
Info.Depth,
|
||||
true);
|
||||
break;
|
||||
|
@ -109,8 +115,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
TextureTarget3d.Texture3D,
|
||||
Info.Levels,
|
||||
internalFormat,
|
||||
Info.Width,
|
||||
Info.Height,
|
||||
width,
|
||||
height,
|
||||
Info.Depth);
|
||||
break;
|
||||
|
||||
|
@ -119,8 +125,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
TextureTarget2d.TextureCubeMap,
|
||||
Info.Levels,
|
||||
internalFormat,
|
||||
Info.Width,
|
||||
Info.Height);
|
||||
width,
|
||||
height);
|
||||
break;
|
||||
|
||||
case Target.CubemapArray:
|
||||
|
@ -128,8 +134,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
(TextureTarget3d)All.TextureCubeMapArray,
|
||||
Info.Levels,
|
||||
internalFormat,
|
||||
Info.Width,
|
||||
Info.Height,
|
||||
width,
|
||||
height,
|
||||
Info.Depth);
|
||||
break;
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
TextureStorage parent,
|
||||
TextureCreateInfo info,
|
||||
int firstLayer,
|
||||
int firstLevel) : base(info)
|
||||
int firstLevel) : base(info, parent.ScaleFactor)
|
||||
{
|
||||
_renderer = renderer;
|
||||
_parent = parent;
|
||||
|
@ -101,7 +101,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
// So we emulate that here with a texture copy (see the first CopyTo overload).
|
||||
// However right now it only does a single copy right after the view is created,
|
||||
// so it doesn't work for all cases.
|
||||
TextureView emulatedView = (TextureView)_renderer.CreateTexture(info);
|
||||
TextureView emulatedView = (TextureView)_renderer.CreateTexture(info, ScaleFactor);
|
||||
|
||||
emulatedView._emulatedViewParent = this;
|
||||
|
||||
|
@ -122,10 +122,10 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
{
|
||||
if (_incompatibleFormatView == null)
|
||||
{
|
||||
_incompatibleFormatView = (TextureView)_renderer.CreateTexture(Info);
|
||||
_incompatibleFormatView = (TextureView)_renderer.CreateTexture(Info, ScaleFactor);
|
||||
}
|
||||
|
||||
TextureCopyUnscaled.Copy(_parent.Info, _incompatibleFormatView.Info, _parent.Handle, _incompatibleFormatView.Handle, FirstLayer, 0, FirstLevel, 0);
|
||||
TextureCopyUnscaled.Copy(_parent.Info, _incompatibleFormatView.Info, _parent.Handle, _incompatibleFormatView.Handle, FirstLayer, 0, FirstLevel, 0, ScaleFactor);
|
||||
|
||||
return _incompatibleFormatView.Handle;
|
||||
}
|
||||
|
@ -137,7 +137,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
{
|
||||
if (_incompatibleFormatView != null)
|
||||
{
|
||||
TextureCopyUnscaled.Copy(_incompatibleFormatView.Info, _parent.Info, _incompatibleFormatView.Handle, _parent.Handle, 0, FirstLayer, 0, FirstLevel);
|
||||
TextureCopyUnscaled.Copy(_incompatibleFormatView.Info, _parent.Info, _incompatibleFormatView.Handle, _parent.Handle, 0, FirstLayer, 0, FirstLevel, ScaleFactor);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,7 +145,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
{
|
||||
TextureView destinationView = (TextureView)destination;
|
||||
|
||||
TextureCopyUnscaled.Copy(Info, destinationView.Info, Handle, destinationView.Handle, 0, firstLayer, 0, firstLevel);
|
||||
TextureCopyUnscaled.Copy(Info, destinationView.Info, Handle, destinationView.Handle, 0, firstLayer, 0, firstLevel, ScaleFactor);
|
||||
|
||||
if (destinationView._emulatedViewParent != null)
|
||||
{
|
||||
|
@ -157,7 +157,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
0,
|
||||
destinationView.FirstLayer,
|
||||
0,
|
||||
destinationView.FirstLevel);
|
||||
destinationView.FirstLevel,
|
||||
ScaleFactor);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,12 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
private int _boundDrawFramebuffer;
|
||||
private int _boundReadFramebuffer;
|
||||
|
||||
private float[] _fpRenderScale = new float[33];
|
||||
private float[] _cpRenderScale = new float[32];
|
||||
|
||||
private TextureBase _unit0Texture;
|
||||
private TextureBase _rtColor0Texture;
|
||||
private TextureBase _rtDepthTexture;
|
||||
|
||||
private ClipOrigin _clipOrigin;
|
||||
private ClipDepthMode _clipDepthMode;
|
||||
|
@ -54,6 +59,16 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
{
|
||||
_componentMasks[index] = 0xf;
|
||||
}
|
||||
|
||||
for (int index = 0; index < _fpRenderScale.Length; index++)
|
||||
{
|
||||
_fpRenderScale[index] = 1f;
|
||||
}
|
||||
|
||||
for (int index = 0; index < _cpRenderScale.Length; index++)
|
||||
{
|
||||
_cpRenderScale[index] = 1f;
|
||||
}
|
||||
}
|
||||
|
||||
public void Barrier()
|
||||
|
@ -685,6 +700,8 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
{
|
||||
_program = (Program)program;
|
||||
_program.Bind();
|
||||
|
||||
SetRenderTargetScale(_fpRenderScale[0]);
|
||||
}
|
||||
|
||||
public void SetRasterizerDiscard(bool discard)
|
||||
|
@ -701,6 +718,16 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
_rasterizerDiscard = discard;
|
||||
}
|
||||
|
||||
public void SetRenderTargetScale(float scale)
|
||||
{
|
||||
_fpRenderScale[0] = scale;
|
||||
|
||||
if (_program != null && _program.FragmentRenderScaleUniform != -1)
|
||||
{
|
||||
GL.Uniform1(_program.FragmentRenderScaleUniform, 1, _fpRenderScale); // Just the first element.
|
||||
}
|
||||
}
|
||||
|
||||
public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMasks)
|
||||
{
|
||||
for (int index = 0; index < componentMasks.Length; index++)
|
||||
|
@ -715,6 +742,9 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
{
|
||||
EnsureFramebuffer();
|
||||
|
||||
_rtColor0Texture = (TextureBase)colors[0];
|
||||
_rtDepthTexture = (TextureBase)depthStencil;
|
||||
|
||||
for (int index = 0; index < colors.Length; index++)
|
||||
{
|
||||
TextureView color = (TextureView)colors[index];
|
||||
|
@ -826,6 +856,37 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
{
|
||||
((TextureBase)texture).Bind(unit);
|
||||
}
|
||||
|
||||
// Update scale factor for bound textures.
|
||||
|
||||
switch (stage)
|
||||
{
|
||||
case ShaderStage.Fragment:
|
||||
if (_program.FragmentRenderScaleUniform != -1)
|
||||
{
|
||||
// Only update and send sampled texture scales if the shader uses them.
|
||||
bool interpolate = false;
|
||||
float scale = texture.ScaleFactor;
|
||||
|
||||
if (scale != 1)
|
||||
{
|
||||
TextureBase activeTarget = _rtColor0Texture ?? _rtDepthTexture;
|
||||
|
||||
if (activeTarget != null && activeTarget.Width / (float)texture.Width == activeTarget.Height / (float)texture.Height)
|
||||
{
|
||||
// If the texture's size is a multiple of the sampler size, enable interpolation using gl_FragCoord. (helps "invent" new integer values between scaled pixels)
|
||||
interpolate = true;
|
||||
}
|
||||
}
|
||||
|
||||
_fpRenderScale[index + 1] = interpolate ? -scale : scale;
|
||||
}
|
||||
break;
|
||||
|
||||
case ShaderStage.Compute:
|
||||
_cpRenderScale[index] = texture.ScaleFactor;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1089,5 +1150,28 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
_framebuffer?.Dispose();
|
||||
_vertexArray?.Dispose();
|
||||
}
|
||||
|
||||
public void UpdateRenderScale(ShaderStage stage, int textureCount)
|
||||
{
|
||||
if (_program != null)
|
||||
{
|
||||
switch (stage)
|
||||
{
|
||||
case ShaderStage.Fragment:
|
||||
if (_program.FragmentRenderScaleUniform != -1)
|
||||
{
|
||||
GL.Uniform1(_program.FragmentRenderScaleUniform, textureCount + 1, _fpRenderScale);
|
||||
}
|
||||
break;
|
||||
|
||||
case ShaderStage.Compute:
|
||||
if (_program.ComputeRenderScaleUniform != -1)
|
||||
{
|
||||
GL.Uniform1(_program.ComputeRenderScaleUniform, textureCount, _cpRenderScale);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,9 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
|
||||
public int Handle { get; private set; }
|
||||
|
||||
public int FragmentRenderScaleUniform { get; }
|
||||
public int ComputeRenderScaleUniform { get; }
|
||||
|
||||
public bool IsLinked { get; private set; }
|
||||
|
||||
private int[] _ubBindingPoints;
|
||||
|
@ -162,6 +165,9 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
imageUnit++;
|
||||
}
|
||||
}
|
||||
|
||||
FragmentRenderScaleUniform = GL.GetUniformLocation(Handle, "fp_renderScale");
|
||||
ComputeRenderScaleUniform = GL.GetUniformLocation(Handle, "cp_renderScale");
|
||||
}
|
||||
|
||||
public void Bind()
|
||||
|
|
|
@ -54,9 +54,9 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
return new Sampler(info);
|
||||
}
|
||||
|
||||
public ITexture CreateTexture(TextureCreateInfo info)
|
||||
public ITexture CreateTexture(TextureCreateInfo info, float scaleFactor)
|
||||
{
|
||||
return info.Target == Target.TextureBuffer ? new TextureBuffer(info) : new TextureStorage(this, info).CreateDefaultView();
|
||||
return info.Target == Target.TextureBuffer ? new TextureBuffer(info) : new TextureStorage(this, info, scaleFactor).CreateDefaultView();
|
||||
}
|
||||
|
||||
public void DeleteBuffer(BufferHandle buffer)
|
||||
|
|
|
@ -65,11 +65,12 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
GL.Clear(ClearBufferMask.ColorBufferBit);
|
||||
|
||||
int srcX0, srcX1, srcY0, srcY1;
|
||||
float scale = view.ScaleFactor;
|
||||
|
||||
if (crop.Left == 0 && crop.Right == 0)
|
||||
{
|
||||
srcX0 = 0;
|
||||
srcX1 = view.Width;
|
||||
srcX1 = (int)(view.Width / scale);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -80,7 +81,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
if (crop.Top == 0 && crop.Bottom == 0)
|
||||
{
|
||||
srcY0 = 0;
|
||||
srcY1 = view.Height;
|
||||
srcY1 = (int)(view.Height / scale);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -88,6 +89,14 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
srcY1 = crop.Bottom;
|
||||
}
|
||||
|
||||
if (scale != 1f)
|
||||
{
|
||||
srcX0 = (int)(srcX0 * scale);
|
||||
srcY0 = (int)(srcY0 * scale);
|
||||
srcX1 = (int)Math.Ceiling(srcX1 * scale);
|
||||
srcY1 = (int)Math.Ceiling(srcY1 * scale);
|
||||
}
|
||||
|
||||
float ratioX = MathF.Min(1f, (_height * (float)NativeWidth) / ((float)NativeHeight * _width));
|
||||
float ratioY = MathF.Min(1f, (_width * (float)NativeHeight) / ((float)NativeWidth * _height));
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||
using Ryujinx.Graphics.Shader.StructuredIr;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
@ -75,6 +77,21 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||
AppendLine("}" + suffix);
|
||||
}
|
||||
|
||||
public int FindTextureDescriptorIndex(AstTextureOperation texOp)
|
||||
{
|
||||
AstOperand operand = texOp.GetSource(0) as AstOperand;
|
||||
bool bindless = (texOp.Flags & TextureFlags.Bindless) > 0;
|
||||
|
||||
int cBufSlot = bindless ? operand.CbufSlot : 0;
|
||||
int cBufOffset = bindless ? operand.CbufOffset : 0;
|
||||
|
||||
return TextureDescriptors.FindIndex(descriptor =>
|
||||
descriptor.Type == texOp.Type &&
|
||||
descriptor.HandleIndex == texOp.Handle &&
|
||||
descriptor.CbufSlot == cBufSlot &&
|
||||
descriptor.CbufOffset == cBufOffset);
|
||||
}
|
||||
|
||||
private void UpdateIndentation()
|
||||
{
|
||||
_indentation = GetIndentation(_level);
|
||||
|
|
|
@ -137,6 +137,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||
context.AppendLine();
|
||||
}
|
||||
|
||||
if (context.Config.Stage == ShaderStage.Fragment || context.Config.Stage == ShaderStage.Compute)
|
||||
{
|
||||
if (DeclareRenderScale(context))
|
||||
{
|
||||
context.AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
if ((info.HelperFunctionsMask & HelperFunctionsMask.MultiplyHighS32) != 0)
|
||||
{
|
||||
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighS32.glsl");
|
||||
|
@ -219,6 +227,33 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||
}
|
||||
}
|
||||
|
||||
private static bool DeclareRenderScale(CodeGenContext context)
|
||||
{
|
||||
if ((context.Config.UsedFeatures & (FeatureFlags.FragCoordXY | FeatureFlags.IntegerSampling)) != 0)
|
||||
{
|
||||
string stage = OperandManager.GetShaderStagePrefix(context.Config.Stage);
|
||||
|
||||
int scaleElements = context.TextureDescriptors.Count;
|
||||
|
||||
if (context.Config.Stage == ShaderStage.Fragment)
|
||||
{
|
||||
scaleElements++; // Also includes render target scale, for gl_FragCoord.
|
||||
}
|
||||
|
||||
context.AppendLine($"uniform float {stage}_renderScale[{scaleElements}];");
|
||||
|
||||
if (context.Config.UsedFeatures.HasFlag(FeatureFlags.IntegerSampling))
|
||||
{
|
||||
context.AppendLine();
|
||||
AppendHelperFunction(context, $"Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_{stage}.glsl");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void DeclareStorages(CodeGenContext context, StructuredProgramInfo info)
|
||||
{
|
||||
string sbName = OperandManager.GetShaderStagePrefix(context.Config.Stage);
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
ivec2 Helper_TexelFetchScale(ivec2 inputVec, int samplerIndex) {
|
||||
float scale = cp_renderScale[samplerIndex];
|
||||
if (scale == 1.0) {
|
||||
return inputVec;
|
||||
}
|
||||
return ivec2(vec2(inputVec) * scale);
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
ivec2 Helper_TexelFetchScale(ivec2 inputVec, int samplerIndex) {
|
||||
float scale = fp_renderScale[1 + samplerIndex];
|
||||
if (scale == 1.0) {
|
||||
return inputVec;
|
||||
}
|
||||
if (scale < 0.0) { // If less than 0, try interpolate between texels by using the screen position.
|
||||
return ivec2(vec2(inputVec) * (-scale) + mod(gl_FragCoord.xy, -scale));
|
||||
} else {
|
||||
return ivec2(vec2(inputVec) * scale);
|
||||
}
|
||||
}
|
|
@ -390,7 +390,34 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||
}
|
||||
}
|
||||
|
||||
Append(AssemblePVector(pCount));
|
||||
string ApplyScaling(string vector)
|
||||
{
|
||||
if (intCoords)
|
||||
{
|
||||
int index = context.FindTextureDescriptorIndex(texOp);
|
||||
|
||||
if ((context.Config.Stage == ShaderStage.Fragment || context.Config.Stage == ShaderStage.Compute) &&
|
||||
(texOp.Flags & TextureFlags.Bindless) == 0 &&
|
||||
texOp.Type != SamplerType.Indexed &&
|
||||
pCount == 2)
|
||||
{
|
||||
return "Helper_TexelFetchScale(" + vector + ", " + index + ")";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Resolution scaling cannot be applied to this texture right now.
|
||||
// Flag so that we know to blacklist scaling on related textures when binding them.
|
||||
|
||||
TextureDescriptor descriptor = context.TextureDescriptors[index];
|
||||
descriptor.Flags |= TextureUsageFlags.ResScaleUnsupported;
|
||||
context.TextureDescriptors[index] = descriptor;
|
||||
}
|
||||
}
|
||||
|
||||
return vector;
|
||||
}
|
||||
|
||||
Append(ApplyScaling(AssemblePVector(pCount)));
|
||||
|
||||
string AssembleDerivativesVector(int count)
|
||||
{
|
||||
|
|
|
@ -185,8 +185,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||
{
|
||||
switch (value & ~3)
|
||||
{
|
||||
case AttributeConsts.PositionX: return "gl_FragCoord.x";
|
||||
case AttributeConsts.PositionY: return "gl_FragCoord.y";
|
||||
case AttributeConsts.PositionX: return "(gl_FragCoord.x / fp_renderScale[0])";
|
||||
case AttributeConsts.PositionY: return "(gl_FragCoord.y / fp_renderScale[0])";
|
||||
case AttributeConsts.PositionZ: return "gl_FragCoord.z";
|
||||
case AttributeConsts.PositionW: return "gl_FragCoord.w";
|
||||
}
|
||||
|
|
|
@ -32,6 +32,8 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
|
||||
Operand src = Attribute(op.AttributeOffset + index * 4);
|
||||
|
||||
context.FlagAttributeRead(src.Value);
|
||||
|
||||
context.Copy(Register(rd), context.LoadAttribute(src, primVertex));
|
||||
}
|
||||
}
|
||||
|
@ -96,6 +98,8 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
{
|
||||
OpCodeIpa op = (OpCodeIpa)context.CurrOp;
|
||||
|
||||
context.FlagAttributeRead(op.AttributeOffset);
|
||||
|
||||
Operand res = Attribute(op.AttributeOffset);
|
||||
|
||||
if (op.AttributeOffset >= AttributeConsts.UserAttributeBase &&
|
||||
|
|
|
@ -283,11 +283,15 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
|
||||
public static void Tld(EmitterContext context)
|
||||
{
|
||||
context.UsedFeatures |= FeatureFlags.IntegerSampling;
|
||||
|
||||
EmitTextureSample(context, TextureFlags.IntCoords);
|
||||
}
|
||||
|
||||
public static void TldB(EmitterContext context)
|
||||
{
|
||||
context.UsedFeatures |= FeatureFlags.IntegerSampling;
|
||||
|
||||
EmitTextureSample(context, TextureFlags.IntCoords | TextureFlags.Bindless);
|
||||
}
|
||||
|
||||
|
@ -428,6 +432,8 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
return;
|
||||
}
|
||||
|
||||
context.UsedFeatures |= FeatureFlags.IntegerSampling;
|
||||
|
||||
flags = ConvertTextureFlags(tldsOp.Target) | TextureFlags.IntCoords;
|
||||
|
||||
if (tldsOp.Target == TexelLoadTarget.Texture1DLodZero && context.Config.GpuAccessor.QueryIsTextureBuffer(tldsOp.Immediate))
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\ShuffleUp.glsl" />
|
||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\ShuffleXor.glsl" />
|
||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\SwizzleAdd.glsl" />
|
||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\TexelFetchScale_fp.glsl" />
|
||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\TexelFetchScale_cp.glsl" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -13,6 +13,8 @@ namespace Ryujinx.Graphics.Shader
|
|||
public int CbufSlot { get; }
|
||||
public int CbufOffset { get; }
|
||||
|
||||
public TextureUsageFlags Flags { get; set; }
|
||||
|
||||
public TextureDescriptor(string name, SamplerType type, int handleIndex)
|
||||
{
|
||||
Name = name;
|
||||
|
@ -23,6 +25,8 @@ namespace Ryujinx.Graphics.Shader
|
|||
|
||||
CbufSlot = 0;
|
||||
CbufOffset = 0;
|
||||
|
||||
Flags = TextureUsageFlags.None;
|
||||
}
|
||||
|
||||
public TextureDescriptor(string name, SamplerType type, int cbufSlot, int cbufOffset)
|
||||
|
@ -35,6 +39,8 @@ namespace Ryujinx.Graphics.Shader
|
|||
|
||||
CbufSlot = cbufSlot;
|
||||
CbufOffset = cbufOffset;
|
||||
|
||||
Flags = TextureUsageFlags.None;
|
||||
}
|
||||
}
|
||||
}
|
16
Ryujinx.Graphics.Shader/TextureUsageFlags.cs
Normal file
16
Ryujinx.Graphics.Shader/TextureUsageFlags.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Shader
|
||||
{
|
||||
/// <summary>
|
||||
/// Flags that indicate how a texture will be used in a shader.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum TextureUsageFlags
|
||||
{
|
||||
None = 0,
|
||||
|
||||
// Integer sampled textures must be noted for resolution scaling.
|
||||
ResScaleUnsupported = 1 << 0
|
||||
}
|
||||
}
|
|
@ -11,6 +11,8 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
public Block CurrBlock { get; set; }
|
||||
public OpCode CurrOp { get; set; }
|
||||
|
||||
public FeatureFlags UsedFeatures { get; set; }
|
||||
|
||||
public ShaderConfig Config { get; }
|
||||
|
||||
private List<Operation> _operations;
|
||||
|
@ -40,6 +42,20 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
_operations.Add(operation);
|
||||
}
|
||||
|
||||
public void FlagAttributeRead(int attribute)
|
||||
{
|
||||
if (Config.Stage == ShaderStage.Fragment)
|
||||
{
|
||||
switch (attribute)
|
||||
{
|
||||
case AttributeConsts.PositionX:
|
||||
case AttributeConsts.PositionY:
|
||||
UsedFeatures |= FeatureFlags.FragCoordXY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void MarkLabel(Operand label)
|
||||
{
|
||||
Add(Instruction.MarkLabel, label);
|
||||
|
|
18
Ryujinx.Graphics.Shader/Translation/FeatureFlags.cs
Normal file
18
Ryujinx.Graphics.Shader/Translation/FeatureFlags.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Shader.Translation
|
||||
{
|
||||
/// <summary>
|
||||
/// Features used by the shader that are important for the code generator to know in advance.
|
||||
/// These typically change the declarations in the shader header.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum FeatureFlags
|
||||
{
|
||||
None = 0,
|
||||
|
||||
// Affected by resolution scaling.
|
||||
FragCoordXY = 1 << 1,
|
||||
IntegerSampling = 1 << 0
|
||||
}
|
||||
}
|
|
@ -22,6 +22,8 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
|
||||
public TranslationFlags Flags { get; }
|
||||
|
||||
public FeatureFlags UsedFeatures { get; set; }
|
||||
|
||||
public ShaderConfig(IGpuAccessor gpuAccessor, TranslationFlags flags)
|
||||
{
|
||||
Stage = ShaderStage.Compute;
|
||||
|
@ -34,6 +36,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
OmapDepth = false;
|
||||
GpuAccessor = gpuAccessor;
|
||||
Flags = flags;
|
||||
UsedFeatures = FeatureFlags.None;
|
||||
}
|
||||
|
||||
public ShaderConfig(ShaderHeader header, IGpuAccessor gpuAccessor, TranslationFlags flags)
|
||||
|
@ -48,6 +51,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
OmapDepth = header.OmapDepth;
|
||||
GpuAccessor = gpuAccessor;
|
||||
Flags = flags;
|
||||
UsedFeatures = FeatureFlags.None;
|
||||
}
|
||||
|
||||
public int GetDepthRegister()
|
||||
|
|
|
@ -16,15 +16,19 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
|
||||
public static ShaderProgram Translate(ulong address, IGpuAccessor gpuAccessor, TranslationFlags flags)
|
||||
{
|
||||
Operation[] ops = DecodeShader(address, gpuAccessor, flags, out ShaderConfig config, out int size);
|
||||
Operation[] ops = DecodeShader(address, gpuAccessor, flags, out ShaderConfig config, out int size, out FeatureFlags featureFlags);
|
||||
|
||||
config.UsedFeatures = featureFlags;
|
||||
|
||||
return Translate(ops, config, size);
|
||||
}
|
||||
|
||||
public static ShaderProgram Translate(ulong addressA, ulong addressB, IGpuAccessor gpuAccessor, TranslationFlags flags)
|
||||
{
|
||||
Operation[] opsA = DecodeShader(addressA, gpuAccessor, flags | TranslationFlags.VertexA, out _, out int sizeA);
|
||||
Operation[] opsB = DecodeShader(addressB, gpuAccessor, flags, out ShaderConfig config, out int sizeB);
|
||||
Operation[] opsA = DecodeShader(addressA, gpuAccessor, flags | TranslationFlags.VertexA, out _, out int sizeA, out FeatureFlags featureFlagsA);
|
||||
Operation[] opsB = DecodeShader(addressB, gpuAccessor, flags, out ShaderConfig config, out int sizeB, out FeatureFlags featureFlagsB);
|
||||
|
||||
config.UsedFeatures = featureFlagsA | featureFlagsB;
|
||||
|
||||
return Translate(Combine(opsA, opsB), config, sizeB, sizeA);
|
||||
}
|
||||
|
@ -67,7 +71,8 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
IGpuAccessor gpuAccessor,
|
||||
TranslationFlags flags,
|
||||
out ShaderConfig config,
|
||||
out int size)
|
||||
out int size,
|
||||
out FeatureFlags featureFlags)
|
||||
{
|
||||
Block[] cfg;
|
||||
|
||||
|
@ -90,6 +95,8 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
|
||||
size = 0;
|
||||
|
||||
featureFlags = FeatureFlags.None;
|
||||
|
||||
return Array.Empty<Operation>();
|
||||
}
|
||||
|
||||
|
@ -192,6 +199,8 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
|
||||
size = (int)maxEndAddress + (((flags & TranslationFlags.Compute) != 0) ? 0 : HeaderSize);
|
||||
|
||||
featureFlags = context.UsedFeatures;
|
||||
|
||||
return context.GetOperations();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
"version": 10,
|
||||
"version": 11,
|
||||
"res_scale": 2,
|
||||
"res_scale_custom": 1,
|
||||
"max_anisotropy": -1,
|
||||
"graphics_shaders_dump_path": "",
|
||||
"logging_enable_debug": false,
|
||||
|
|
|
@ -328,6 +328,11 @@ namespace Ryujinx.Ui
|
|||
}
|
||||
|
||||
string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? "Docked" : "Handheld";
|
||||
float scale = Graphics.Gpu.GraphicsConfig.ResScale;
|
||||
if (scale != 1)
|
||||
{
|
||||
dockedMode += $" ({scale}x)";
|
||||
}
|
||||
|
||||
if (_ticks >= _ticksPerFrame)
|
||||
{
|
||||
|
|
|
@ -390,9 +390,7 @@ namespace Ryujinx.Ui
|
|||
|
||||
HLE.Switch device = InitializeSwitchInstance();
|
||||
|
||||
// TODO: Move this somewhere else + reloadable?
|
||||
Graphics.Gpu.GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy;
|
||||
Graphics.Gpu.GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath;
|
||||
UpdateGraphicsConfig();
|
||||
|
||||
Logger.PrintInfo(LogClass.Application, $"Using Firmware Version: {_contentManager.GetCurrentFirmwareVersion()?.VersionString}");
|
||||
|
||||
|
@ -605,6 +603,15 @@ namespace Ryujinx.Ui
|
|||
}
|
||||
}
|
||||
|
||||
public static void UpdateGraphicsConfig()
|
||||
{
|
||||
int resScale = ConfigurationState.Instance.Graphics.ResScale;
|
||||
float resScaleCustom = ConfigurationState.Instance.Graphics.ResScaleCustom;
|
||||
Graphics.Gpu.GraphicsConfig.ResScale = (resScale == -1) ? resScaleCustom : resScale;
|
||||
Graphics.Gpu.GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy;
|
||||
Graphics.Gpu.GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath;
|
||||
}
|
||||
|
||||
public static void SaveConfig()
|
||||
{
|
||||
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||
|
|
|
@ -63,6 +63,8 @@ namespace Ryujinx.Ui
|
|||
[GUI] Entry _addGameDirBox;
|
||||
[GUI] Entry _graphicsShadersDumpPath;
|
||||
[GUI] ComboBoxText _anisotropy;
|
||||
[GUI] ComboBoxText _resScaleCombo;
|
||||
[GUI] Entry _resScaleText;
|
||||
[GUI] ToggleButton _configureController1;
|
||||
[GUI] ToggleButton _configureController2;
|
||||
[GUI] ToggleButton _configureController3;
|
||||
|
@ -95,6 +97,8 @@ namespace Ryujinx.Ui
|
|||
_configureController8.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player8);
|
||||
_configureControllerH.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Handheld);
|
||||
|
||||
_resScaleCombo.Changed += (sender, args) => _resScaleText.Visible = _resScaleCombo.ActiveId == "-1";
|
||||
|
||||
//Setup Currents
|
||||
if (ConfigurationState.Instance.Logger.EnableFileLog)
|
||||
{
|
||||
|
@ -204,9 +208,12 @@ namespace Ryujinx.Ui
|
|||
_systemRegionSelect.SetActiveId(ConfigurationState.Instance.System.Region.Value.ToString());
|
||||
_audioBackendSelect.SetActiveId(ConfigurationState.Instance.System.AudioBackend.Value.ToString());
|
||||
_systemTimeZoneSelect.SetActiveId(timeZoneContentManager.SanityCheckDeviceLocationName());
|
||||
_resScaleCombo.SetActiveId(ConfigurationState.Instance.Graphics.ResScale.Value.ToString());
|
||||
_anisotropy.SetActiveId(ConfigurationState.Instance.Graphics.MaxAnisotropy.Value.ToString());
|
||||
|
||||
_custThemePath.Buffer.Text = ConfigurationState.Instance.Ui.CustomThemePath;
|
||||
_resScaleText.Buffer.Text = ConfigurationState.Instance.Graphics.ResScaleCustom.Value.ToString();
|
||||
_resScaleText.Visible = _resScaleCombo.ActiveId == "-1";
|
||||
_graphicsShadersDumpPath.Buffer.Text = ConfigurationState.Instance.Graphics.ShadersDumpPath;
|
||||
_fsLogSpinAdjustment.Value = ConfigurationState.Instance.System.FsGlobalAccessLogMode;
|
||||
_systemTimeOffset = ConfigurationState.Instance.System.SystemTimeOffset;
|
||||
|
@ -408,6 +415,12 @@ namespace Ryujinx.Ui
|
|||
_gameDirsBoxStore.IterNext(ref treeIter);
|
||||
}
|
||||
|
||||
float resScaleCustom;
|
||||
if (!float.TryParse(_resScaleText.Buffer.Text, out resScaleCustom) || resScaleCustom <= 0.0f)
|
||||
{
|
||||
resScaleCustom = 1.0f;
|
||||
}
|
||||
|
||||
ConfigurationState.Instance.Logger.EnableError.Value = _errorLogToggle.Active;
|
||||
ConfigurationState.Instance.Logger.EnableWarn.Value = _warningLogToggle.Active;
|
||||
ConfigurationState.Instance.Logger.EnableInfo.Value = _infoLogToggle.Active;
|
||||
|
@ -435,8 +448,11 @@ namespace Ryujinx.Ui
|
|||
ConfigurationState.Instance.Ui.GameDirs.Value = gameDirs;
|
||||
ConfigurationState.Instance.System.FsGlobalAccessLogMode.Value = (int)_fsLogSpinAdjustment.Value;
|
||||
ConfigurationState.Instance.Graphics.MaxAnisotropy.Value = float.Parse(_anisotropy.ActiveId);
|
||||
ConfigurationState.Instance.Graphics.ResScale.Value = int.Parse(_resScaleCombo.ActiveId);
|
||||
ConfigurationState.Instance.Graphics.ResScaleCustom.Value = resScaleCustom;
|
||||
|
||||
MainWindow.SaveConfig();
|
||||
MainWindow.UpdateGraphicsConfig();
|
||||
MainWindow.ApplyTheme();
|
||||
Dispose();
|
||||
}
|
||||
|
|
|
@ -1677,6 +1677,70 @@
|
|||
<property name="margin_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Resolution Scale applied to applicable render targets.</property>
|
||||
<property name="label" translatable="yes">Resolution Scale:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">5</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBoxText" id="_resScaleCombo">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Resolution Scale applied to applicable render targets.</property>
|
||||
<property name="active_id">1</property>
|
||||
<items>
|
||||
<item id="1" translatable="yes">Native (720p/1080p)</item>
|
||||
<item id="2" translatable="yes">2x (1440p/2160p)</item>
|
||||
<item id="3" translatable="yes">3x (2160p/3240p)</item>
|
||||
<item id="4" translatable="yes">4x (2880p/4320p)</item>
|
||||
<item id="-1" translatable="yes">Custom (not recommended)</item>
|
||||
</items>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="_resScaleText">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Floating point resolution scale, such as 1.5. Non-integral scales are more likely to cause issues or crash.</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="caps_lock_warning">False</property>
|
||||
<property name="placeholder-text">1.0</property>
|
||||
<property name="input-purpose">GTK_INPUT_PURPOSE_NUMBER</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">5</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
|
@ -1722,7 +1786,7 @@
|
|||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">5</property>
|
||||
<property name="position">0</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
|
|
|
@ -700,6 +700,27 @@
|
|||
}
|
||||
},
|
||||
"properties": {
|
||||
"res_scale": {
|
||||
"$id": "#/properties/res_scale",
|
||||
"type": "integer",
|
||||
"title": "Resolution Scale",
|
||||
"description": "An integer scale applied to applicable render targets. Values 1-4, or -1 to use a custom floating point scale instead.",
|
||||
"default": -1,
|
||||
"examples": [
|
||||
-1,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4
|
||||
]
|
||||
},
|
||||
"res_scale_custom": {
|
||||
"$id": "#/properties/res_scale_custom",
|
||||
"type": "number",
|
||||
"title": "Custom Resolution Scale",
|
||||
"description": "A custom floating point scale applied to applicable render targets. Only active when Resolution Scale is -1.",
|
||||
"default": 1.0,
|
||||
},
|
||||
"max_anisotropy": {
|
||||
"$id": "#/properties/max_anisotropy",
|
||||
"type": "integer",
|
||||
|
@ -1211,7 +1232,7 @@
|
|||
"button_sr": "Unbound"
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"controller_config": {
|
||||
"$id": "#/properties/controller_config",
|
||||
|
|
Loading…
Reference in a new issue