Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
167f50bbcd | |||
ba91f5d401 | |||
79f6c18a9b | |||
4f63782bac | |||
6f5fcb7970 | |||
6ef8946169 | |||
42340fc743 | |||
103e7cb021 | |||
31ed061bea | |||
4218311e6a | |||
e37735ed26 | |||
74a18b7c18 | |||
74fe814329 |
@ -155,7 +155,7 @@
|
||||
"SettingsTabGraphicsResolutionScaleNative": "Native (720p/1080p)",
|
||||
"SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)",
|
||||
"SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)",
|
||||
"SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)",
|
||||
"SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Not recommended)",
|
||||
"SettingsTabGraphicsAspectRatio": "Aspect Ratio:",
|
||||
"SettingsTabGraphicsAspectRatio4x3": "4:3",
|
||||
"SettingsTabGraphicsAspectRatio16x9": "16:9",
|
||||
@ -452,13 +452,13 @@
|
||||
"CustomThemePathTooltip": "Path to custom GUI theme",
|
||||
"CustomThemeBrowseTooltip": "Browse for a custom GUI theme",
|
||||
"DockModeToggleTooltip": "Docked mode makes the emulated system behave as a docked Nintendo Switch. This improves graphical fidelity in most games. Conversely, disabling this will make the emulated system behave as a handheld Nintendo Switch, reducing graphics quality.\n\nConfigure player 1 controls if planning to use docked mode; configure handheld controls if planning to use handheld mode.\n\nLeave ON if unsure.",
|
||||
"DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.",
|
||||
"DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.",
|
||||
"DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.",
|
||||
"DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.",
|
||||
"RegionTooltip": "Change System Region",
|
||||
"LanguageTooltip": "Change System Language",
|
||||
"TimezoneTooltip": "Change System TimeZone",
|
||||
"TimeTooltip": "Change System Time",
|
||||
"VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference. We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.",
|
||||
"VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.",
|
||||
"PptcToggleTooltip": "Saves translated JIT functions so that they do not need to be translated every time the game loads.\n\nReduces stuttering and significantly speeds up boot times after the first boot of a game.\n\nLeave ON if unsure.",
|
||||
"FsIntegrityToggleTooltip": "Checks for corrupt files when booting a game, and if corrupt files are detected, displays a hash error in the log.\n\nHas no impact on performance and is meant to help troubleshooting.\n\nLeave ON if unsure.",
|
||||
"AudioBackendTooltip": "Changes the backend used to render audio.\n\nSDL2 is the preferred one, while OpenAL and SoundIO are used as fallbacks. Dummy will have no sound.\n\nSet to SDL2 if unsure.",
|
||||
@ -472,10 +472,10 @@
|
||||
"GraphicsBackendThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.",
|
||||
"GalThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.",
|
||||
"ShaderCacheToggleTooltip": "Saves a disk shader cache which reduces stuttering in subsequent runs.\n\nLeave ON if unsure.",
|
||||
"ResolutionScaleTooltip": "Resolution Scale applied to applicable render targets",
|
||||
"ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.",
|
||||
"ResolutionScaleEntryTooltip": "Floating point resolution scale, such as 1.5. Non-integral scales are more likely to cause issues or crash.",
|
||||
"AnisotropyTooltip": "Level of Anisotropic Filtering (set to Auto to use the value requested by the game)",
|
||||
"AspectRatioTooltip": "Aspect Ratio applied to the renderer window.",
|
||||
"AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.",
|
||||
"AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.",
|
||||
"ShaderDumpPathTooltip": "Graphics Shaders Dump Path",
|
||||
"FileLogTooltip": "Saves console logging to a log file on disk. Does not affect performance.",
|
||||
"StubLogTooltip": "Prints stub log messages in the console. Does not affect performance.",
|
||||
@ -616,9 +616,9 @@
|
||||
"UserProfilesName": "Name:",
|
||||
"UserProfilesUserId": "User ID:",
|
||||
"SettingsTabGraphicsBackend": "Graphics Backend",
|
||||
"SettingsTabGraphicsBackendTooltip": "Graphics Backend to use",
|
||||
"SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.",
|
||||
"SettingsEnableTextureRecompression": "Enable Texture Recompression",
|
||||
"SettingsEnableTextureRecompressionTooltip": "Compresses certain textures in order to reduce VRAM usage.\n\nRecommended for use with GPUs that have less than 4GiB VRAM.\n\nLeave OFF if unsure.",
|
||||
"SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.",
|
||||
"SettingsTabGraphicsPreferredGpu": "Preferred GPU",
|
||||
"SettingsTabGraphicsPreferredGpuTooltip": "Select the graphics card that will be used with the Vulkan graphics backend.\n\nDoes not affect the GPU that OpenGL will use.\n\nSet to the GPU flagged as \"dGPU\" if unsure. If there isn't one, leave untouched.",
|
||||
"SettingsAppRequiredRestartMessage": "Ryujinx Restart Required",
|
||||
@ -644,12 +644,12 @@
|
||||
"Recover": "Recover",
|
||||
"UserProfilesRecoverHeading": "Saves were found for the following accounts",
|
||||
"UserProfilesRecoverEmptyList": "No profiles to recover",
|
||||
"GraphicsAATooltip": "Applies anti-aliasing to the game render",
|
||||
"GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.",
|
||||
"GraphicsAALabel": "Anti-Aliasing:",
|
||||
"GraphicsScalingFilterLabel": "Scaling Filter:",
|
||||
"GraphicsScalingFilterTooltip": "Enables Framebuffer Scaling",
|
||||
"GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.",
|
||||
"GraphicsScalingFilterLevelLabel": "Level",
|
||||
"GraphicsScalingFilterLevelTooltip": "Set Scaling Filter Level",
|
||||
"GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.",
|
||||
"SmaaLow": "SMAA Low",
|
||||
"SmaaMedium": "SMAA Medium",
|
||||
"SmaaHigh": "SMAA High",
|
||||
@ -657,12 +657,12 @@
|
||||
"UserEditorTitle": "Edit User",
|
||||
"UserEditorTitleCreate": "Create User",
|
||||
"SettingsTabNetworkInterface": "Network Interface:",
|
||||
"NetworkInterfaceTooltip": "The network interface used for LAN/LDN features",
|
||||
"NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.",
|
||||
"NetworkInterfaceDefault": "Default",
|
||||
"PackagingShaders": "Packaging Shaders",
|
||||
"AboutChangelogButton": "View Changelog on GitHub",
|
||||
"AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser.",
|
||||
"SettingsTabNetworkMultiplayer": "Multiplayer",
|
||||
"MultiplayerMode": "Mode:",
|
||||
"MultiplayerModeTooltip": "Change multiplayer mode"
|
||||
"MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure."
|
||||
}
|
||||
|
@ -59,6 +59,7 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
EnableMultiTouch = true,
|
||||
EnableIme = true,
|
||||
EnableInputFocusProxy = true,
|
||||
RenderingMode = new[] { X11RenderingMode.Glx, X11RenderingMode.Software },
|
||||
})
|
||||
.With(new Win32PlatformOptions
|
||||
|
@ -8,7 +8,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
||||
{
|
||||
static class Decoder
|
||||
{
|
||||
private const int MaxInstructionsPerBlock = 1000;
|
||||
private const int MaxInstructionsPerFunction = 10000;
|
||||
|
||||
private const uint NzcvFlags = 0xfu << 28;
|
||||
private const uint CFlag = 0x1u << 29;
|
||||
@ -22,10 +22,11 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
||||
|
||||
bool hasHostCall = false;
|
||||
bool hasMemoryInstruction = false;
|
||||
int totalInsts = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
Block block = Decode(cpuPreset, memoryManager, address, ref useMask, ref hasHostCall, ref hasMemoryInstruction);
|
||||
Block block = Decode(cpuPreset, memoryManager, address, ref totalInsts, ref useMask, ref hasHostCall, ref hasMemoryInstruction);
|
||||
|
||||
if (!block.IsTruncated && TryGetBranchTarget(block, out ulong targetAddress))
|
||||
{
|
||||
@ -230,6 +231,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
||||
CpuPreset cpuPreset,
|
||||
IMemoryManager memoryManager,
|
||||
ulong address,
|
||||
ref int totalInsts,
|
||||
ref RegisterMask useMask,
|
||||
ref bool hasHostCall,
|
||||
ref bool hasMemoryInstruction)
|
||||
@ -272,7 +274,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
||||
|
||||
uint tempGprUseMask = gprUseMask | instGprReadMask | instGprWriteMask;
|
||||
|
||||
if (CalculateAvailableTemps(tempGprUseMask) < CalculateRequiredGprTemps(tempGprUseMask) || insts.Count >= MaxInstructionsPerBlock)
|
||||
if (CalculateAvailableTemps(tempGprUseMask) < CalculateRequiredGprTemps(tempGprUseMask) || totalInsts++ >= MaxInstructionsPerFunction)
|
||||
{
|
||||
isTruncated = true;
|
||||
address -= 4UL;
|
||||
|
@ -148,6 +148,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
B8G8R8A8Unorm,
|
||||
B8G8R8A8Srgb,
|
||||
B10G10R10A2Unorm,
|
||||
X8UintD24Unorm,
|
||||
}
|
||||
|
||||
public static class FormatExtensions
|
||||
@ -269,6 +270,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
case Format.D16Unorm:
|
||||
return 2;
|
||||
case Format.S8UintD24Unorm:
|
||||
case Format.X8UintD24Unorm:
|
||||
case Format.D32Float:
|
||||
case Format.D24UnormS8Uint:
|
||||
return 4;
|
||||
@ -349,6 +351,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
case Format.D16Unorm:
|
||||
case Format.D24UnormS8Uint:
|
||||
case Format.S8UintD24Unorm:
|
||||
case Format.X8UintD24Unorm:
|
||||
case Format.D32Float:
|
||||
case Format.D32FloatS8Uint:
|
||||
return true;
|
||||
@ -633,6 +636,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
case Format.D16Unorm:
|
||||
case Format.D24UnormS8Uint:
|
||||
case Format.S8UintD24Unorm:
|
||||
case Format.X8UintD24Unorm:
|
||||
case Format.D32Float:
|
||||
case Format.D32FloatS8Uint:
|
||||
case Format.S8Uint:
|
||||
|
@ -58,7 +58,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
|
||||
void SetIndexBuffer(BufferRange buffer, IndexType type);
|
||||
|
||||
void SetImage(int binding, ITexture texture, Format imageFormat);
|
||||
void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat);
|
||||
|
||||
void SetLineParameters(float width, bool smooth);
|
||||
|
||||
|
@ -1,17 +1,20 @@
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
{
|
||||
struct SetImageCommand : IGALCommand, IGALCommand<SetImageCommand>
|
||||
{
|
||||
public readonly CommandType CommandType => CommandType.SetImage;
|
||||
private ShaderStage _stage;
|
||||
private int _binding;
|
||||
private TableRef<ITexture> _texture;
|
||||
private Format _imageFormat;
|
||||
|
||||
public void Set(int binding, TableRef<ITexture> texture, Format imageFormat)
|
||||
public void Set(ShaderStage stage, int binding, TableRef<ITexture> texture, Format imageFormat)
|
||||
{
|
||||
_stage = stage;
|
||||
_binding = binding;
|
||||
_texture = texture;
|
||||
_imageFormat = imageFormat;
|
||||
@ -19,7 +22,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
|
||||
public static void Run(ref SetImageCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
renderer.Pipeline.SetImage(command._binding, command._texture.GetAs<ThreadedTexture>(threaded)?.Base, command._imageFormat);
|
||||
renderer.Pipeline.SetImage(command._stage, command._binding, command._texture.GetAs<ThreadedTexture>(threaded)?.Base, command._imageFormat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -177,9 +177,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public void SetImage(int binding, ITexture texture, Format imageFormat)
|
||||
public void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat)
|
||||
{
|
||||
_renderer.New<SetImageCommand>().Set(binding, Ref(texture), imageFormat);
|
||||
_renderer.New<SetImageCommand>().Set(stage, binding, Ref(texture), imageFormat);
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
|
@ -8,13 +8,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Types
|
||||
/// </summary>
|
||||
enum ZetaFormat
|
||||
{
|
||||
D32Float = 0xa,
|
||||
D16Unorm = 0x13,
|
||||
D24UnormS8Uint = 0x14,
|
||||
D24Unorm = 0x15,
|
||||
S8UintD24Unorm = 0x16,
|
||||
Zf32 = 0xa,
|
||||
Z16 = 0x13,
|
||||
Z24S8 = 0x14,
|
||||
X8Z24 = 0x15,
|
||||
S8Z24 = 0x16,
|
||||
S8Uint = 0x17,
|
||||
D32FloatS8Uint = 0x19,
|
||||
Zf32X24S8 = 0x19,
|
||||
}
|
||||
|
||||
static class ZetaFormatConverter
|
||||
@ -29,14 +29,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.Types
|
||||
return format switch
|
||||
{
|
||||
#pragma warning disable IDE0055 // Disable formatting
|
||||
ZetaFormat.D32Float => new FormatInfo(Format.D32Float, 1, 1, 4, 1),
|
||||
ZetaFormat.D16Unorm => new FormatInfo(Format.D16Unorm, 1, 1, 2, 1),
|
||||
ZetaFormat.D24UnormS8Uint => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2),
|
||||
ZetaFormat.D24Unorm => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 1),
|
||||
ZetaFormat.S8UintD24Unorm => new FormatInfo(Format.S8UintD24Unorm, 1, 1, 4, 2),
|
||||
ZetaFormat.S8Uint => new FormatInfo(Format.S8Uint, 1, 1, 1, 1),
|
||||
ZetaFormat.D32FloatS8Uint => new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2),
|
||||
_ => FormatInfo.Default,
|
||||
ZetaFormat.Zf32 => new FormatInfo(Format.D32Float, 1, 1, 4, 1),
|
||||
ZetaFormat.Z16 => new FormatInfo(Format.D16Unorm, 1, 1, 2, 1),
|
||||
ZetaFormat.Z24S8 => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2),
|
||||
ZetaFormat.X8Z24 => new FormatInfo(Format.X8UintD24Unorm, 1, 1, 4, 1),
|
||||
ZetaFormat.S8Z24 => new FormatInfo(Format.S8UintD24Unorm, 1, 1, 4, 2),
|
||||
ZetaFormat.S8Uint => new FormatInfo(Format.S8Uint, 1, 1, 1, 1),
|
||||
ZetaFormat.Zf32X24S8 => new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2),
|
||||
_ => FormatInfo.Default,
|
||||
#pragma warning restore IDE0055
|
||||
};
|
||||
}
|
||||
|
@ -185,6 +185,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
G24R8RUintGUnormBUnormAUnorm = G24R8 | RUint | GUnorm | BUnorm | AUnorm, // 0x24a0e
|
||||
Z24S8RUintGUnormBUnormAUnorm = Z24S8 | RUint | GUnorm | BUnorm | AUnorm, // 0x24a29
|
||||
Z24S8RUintGUnormBUintAUint = Z24S8 | RUint | GUnorm | BUint | AUint, // 0x48a29
|
||||
X8Z24RUnormGUintBUintAUint = X8Z24 | RUnorm | GUint | BUint | AUint, // 0x4912a
|
||||
S8Z24RUnormGUintBUintAUint = S8Z24 | RUnorm | GUint | BUint | AUint, // 0x4912b
|
||||
R32B24G8RFloatGUintBUnormAUnorm = R32B24G8 | RFloat | GUint | BUnorm | AUnorm, // 0x25385
|
||||
Zf32X24S8RFloatGUintBUnormAUnorm = Zf32X24S8 | RFloat | GUint | BUnorm | AUnorm, // 0x253b0
|
||||
@ -410,6 +411,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{ TextureFormat.G24R8RUintGUnormBUnormAUnorm, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) },
|
||||
{ TextureFormat.Z24S8RUintGUnormBUnormAUnorm, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) },
|
||||
{ TextureFormat.Z24S8RUintGUnormBUintAUint, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) },
|
||||
{ TextureFormat.X8Z24RUnormGUintBUintAUint, new FormatInfo(Format.X8UintD24Unorm, 1, 1, 4, 2) },
|
||||
{ TextureFormat.S8Z24RUnormGUintBUintAUint, new FormatInfo(Format.S8UintD24Unorm, 1, 1, 4, 2) },
|
||||
{ TextureFormat.R32B24G8RFloatGUintBUnormAUnorm, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) },
|
||||
{ TextureFormat.Zf32X24S8RFloatGUintBUnormAUnorm, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) },
|
||||
|
@ -634,7 +634,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
state.Texture = hostTextureRebind;
|
||||
state.ImageFormat = format;
|
||||
|
||||
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTextureRebind, format);
|
||||
_context.Renderer.Pipeline.SetImage(stage, bindingInfo.Binding, hostTextureRebind, format);
|
||||
}
|
||||
|
||||
continue;
|
||||
@ -692,7 +692,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
state.ImageFormat = format;
|
||||
|
||||
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTexture, format);
|
||||
_context.Renderer.Pipeline.SetImage(stage, bindingInfo.Binding, hostTexture, format);
|
||||
}
|
||||
|
||||
state.CachedTexture = texture;
|
||||
|
@ -242,7 +242,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return TextureMatchQuality.FormatAlias;
|
||||
}
|
||||
else if ((lhs.FormatInfo.Format == Format.D24UnormS8Uint ||
|
||||
lhs.FormatInfo.Format == Format.S8UintD24Unorm) && rhs.FormatInfo.Format == Format.B8G8R8A8Unorm)
|
||||
lhs.FormatInfo.Format == Format.S8UintD24Unorm ||
|
||||
lhs.FormatInfo.Format == Format.X8UintD24Unorm) && rhs.FormatInfo.Format == Format.B8G8R8A8Unorm)
|
||||
{
|
||||
return TextureMatchQuality.FormatAlias;
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
@ -65,6 +67,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
private readonly Action<ulong, ulong> _loadDelegate;
|
||||
private readonly Action<ulong, ulong> _modifiedDelegate;
|
||||
|
||||
private HashSet<MultiRangeBuffer> _virtualDependencies;
|
||||
private readonly ReaderWriterLockSlim _virtualDependenciesLock;
|
||||
|
||||
private int _sequenceNumber;
|
||||
|
||||
private readonly bool _useGranular;
|
||||
@ -152,6 +157,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
_externalFlushDelegate = new RegionSignal(ExternalFlush);
|
||||
_loadDelegate = new Action<ulong, ulong>(LoadRegion);
|
||||
_modifiedDelegate = new Action<ulong, ulong>(RegionModified);
|
||||
|
||||
_virtualDependenciesLock = new ReaderWriterLockSlim();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -220,6 +227,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </remarks>
|
||||
/// <param name="address">Start address of the range to synchronize</param>
|
||||
/// <param name="size">Size in bytes of the range to synchronize</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SynchronizeMemory(ulong address, ulong size)
|
||||
{
|
||||
if (_useGranular)
|
||||
@ -239,6 +247,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
else
|
||||
{
|
||||
_context.Renderer.SetBufferData(Handle, 0, _physicalMemory.GetSpan(Address, (int)Size));
|
||||
CopyToDependantVirtualBuffers();
|
||||
}
|
||||
|
||||
_sequenceNumber = _context.SequenceNumber;
|
||||
@ -460,6 +469,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
int offset = (int)(mAddress - Address);
|
||||
|
||||
_context.Renderer.SetBufferData(Handle, offset, _physicalMemory.GetSpan(mAddress, (int)mSize));
|
||||
|
||||
CopyToDependantVirtualBuffers(mAddress, mSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -520,6 +531,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="dstOffset">The offset of the destination buffer to copy into</param>
|
||||
public void CopyTo(Buffer destination, int dstOffset)
|
||||
{
|
||||
CopyFromDependantVirtualBuffers();
|
||||
_context.Renderer.Pipeline.CopyBuffer(Handle, destination.Handle, 0, dstOffset, (int)Size);
|
||||
}
|
||||
|
||||
@ -536,7 +548,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
using PinnedSpan<byte> data = _context.Renderer.GetBufferData(Handle, offset, (int)size);
|
||||
|
||||
// TODO: When write tracking shaders, they will need to be aware of changes in overlapping buffers.
|
||||
_physicalMemory.WriteUntracked(address, data.Get());
|
||||
_physicalMemory.WriteUntracked(address, CopyFromDependantVirtualBuffers(data.Get(), address, size));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -617,6 +629,207 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
UnmappedSequence++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a virtual buffer dependency, indicating that a virtual buffer depends on data from this buffer.
|
||||
/// </summary>
|
||||
/// <param name="virtualBuffer">Dependant virtual buffer</param>
|
||||
public void AddVirtualDependency(MultiRangeBuffer virtualBuffer)
|
||||
{
|
||||
_virtualDependenciesLock.EnterWriteLock();
|
||||
|
||||
try
|
||||
{
|
||||
(_virtualDependencies ??= new()).Add(virtualBuffer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_virtualDependenciesLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a virtual buffer dependency, indicating that a virtual buffer no longer depends on data from this buffer.
|
||||
/// </summary>
|
||||
/// <param name="virtualBuffer">Dependant virtual buffer</param>
|
||||
public void RemoveVirtualDependency(MultiRangeBuffer virtualBuffer)
|
||||
{
|
||||
_virtualDependenciesLock.EnterWriteLock();
|
||||
|
||||
try
|
||||
{
|
||||
if (_virtualDependencies != null)
|
||||
{
|
||||
_virtualDependencies.Remove(virtualBuffer);
|
||||
|
||||
if (_virtualDependencies.Count == 0)
|
||||
{
|
||||
_virtualDependencies = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_virtualDependenciesLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the buffer data to all virtual buffers that depends on it.
|
||||
/// </summary>
|
||||
public void CopyToDependantVirtualBuffers()
|
||||
{
|
||||
CopyToDependantVirtualBuffers(Address, Size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the buffer data inside the specifide range to all virtual buffers that depends on it.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the range</param>
|
||||
/// <param name="size">Size of the range in bytes</param>
|
||||
public void CopyToDependantVirtualBuffers(ulong address, ulong size)
|
||||
{
|
||||
if (_virtualDependencies != null)
|
||||
{
|
||||
foreach (var virtualBuffer in _virtualDependencies)
|
||||
{
|
||||
CopyToDependantVirtualBuffer(virtualBuffer, address, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies all modified ranges from all virtual buffers back into this buffer.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void CopyFromDependantVirtualBuffers()
|
||||
{
|
||||
if (_virtualDependencies != null)
|
||||
{
|
||||
CopyFromDependantVirtualBuffersImpl();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies all modified ranges from all virtual buffers back into this buffer.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void CopyFromDependantVirtualBuffersImpl()
|
||||
{
|
||||
foreach (var virtualBuffer in _virtualDependencies.OrderBy(x => x.ModificationSequenceNumber))
|
||||
{
|
||||
virtualBuffer.ConsumeModifiedRegion(this, (mAddress, mSize) =>
|
||||
{
|
||||
// Get offset inside both this and the virtual buffer.
|
||||
// Note that sometimes there is no right answer for the virtual offset,
|
||||
// as the same physical range might be mapped multiple times inside a virtual buffer.
|
||||
// We just assume it does not happen in practice as it can only be implemented correctly
|
||||
// when the host has support for proper sparse mapping.
|
||||
|
||||
ulong mEndAddress = mAddress + mSize;
|
||||
mAddress = Math.Max(mAddress, Address);
|
||||
mSize = Math.Min(mEndAddress, EndAddress) - mAddress;
|
||||
|
||||
int physicalOffset = (int)(mAddress - Address);
|
||||
int virtualOffset = virtualBuffer.Range.FindOffset(new(mAddress, mSize));
|
||||
|
||||
_context.Renderer.Pipeline.CopyBuffer(virtualBuffer.Handle, Handle, virtualOffset, physicalOffset, (int)mSize);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies all overlapping modified ranges from all virtual buffers back into this buffer, and returns an updated span with the data.
|
||||
/// </summary>
|
||||
/// <param name="dataSpan">Span where the unmodified data will be taken from for the output</param>
|
||||
/// <param name="address">Address of the region to copy</param>
|
||||
/// <param name="size">Size of the region to copy in bytes</param>
|
||||
/// <returns>A span with <paramref name="dataSpan"/>, and the data for all modified ranges if any</returns>
|
||||
private ReadOnlySpan<byte> CopyFromDependantVirtualBuffers(ReadOnlySpan<byte> dataSpan, ulong address, ulong size)
|
||||
{
|
||||
_virtualDependenciesLock.EnterReadLock();
|
||||
|
||||
try
|
||||
{
|
||||
if (_virtualDependencies != null)
|
||||
{
|
||||
byte[] storage = dataSpan.ToArray();
|
||||
|
||||
foreach (var virtualBuffer in _virtualDependencies.OrderBy(x => x.ModificationSequenceNumber))
|
||||
{
|
||||
virtualBuffer.ConsumeModifiedRegion(address, size, (mAddress, mSize) =>
|
||||
{
|
||||
// Get offset inside both this and the virtual buffer.
|
||||
// Note that sometimes there is no right answer for the virtual offset,
|
||||
// as the same physical range might be mapped multiple times inside a virtual buffer.
|
||||
// We just assume it does not happen in practice as it can only be implemented correctly
|
||||
// when the host has support for proper sparse mapping.
|
||||
|
||||
ulong mEndAddress = mAddress + mSize;
|
||||
mAddress = Math.Max(mAddress, address);
|
||||
mSize = Math.Min(mEndAddress, address + size) - mAddress;
|
||||
|
||||
int physicalOffset = (int)(mAddress - Address);
|
||||
int virtualOffset = virtualBuffer.Range.FindOffset(new(mAddress, mSize));
|
||||
|
||||
_context.Renderer.Pipeline.CopyBuffer(virtualBuffer.Handle, Handle, virtualOffset, physicalOffset, (int)size);
|
||||
virtualBuffer.GetData(storage.AsSpan().Slice((int)(mAddress - address), (int)mSize), virtualOffset, (int)mSize);
|
||||
});
|
||||
}
|
||||
|
||||
dataSpan = storage;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_virtualDependenciesLock.ExitReadLock();
|
||||
}
|
||||
|
||||
return dataSpan;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the buffer data to the specified virtual buffer.
|
||||
/// </summary>
|
||||
/// <param name="virtualBuffer">Virtual buffer to copy the data into</param>
|
||||
public void CopyToDependantVirtualBuffer(MultiRangeBuffer virtualBuffer)
|
||||
{
|
||||
CopyToDependantVirtualBuffer(virtualBuffer, Address, Size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the buffer data inside the given range to the specified virtual buffer.
|
||||
/// </summary>
|
||||
/// <param name="virtualBuffer">Virtual buffer to copy the data into</param>
|
||||
/// <param name="address">Address of the range</param>
|
||||
/// <param name="size">Size of the range in bytes</param>
|
||||
public void CopyToDependantVirtualBuffer(MultiRangeBuffer virtualBuffer, ulong address, ulong size)
|
||||
{
|
||||
// Broadcast data to all ranges of the virtual buffer that are contained inside this buffer.
|
||||
|
||||
ulong lastOffset = 0;
|
||||
|
||||
while (virtualBuffer.TryGetPhysicalOffset(this, lastOffset, out ulong srcOffset, out ulong dstOffset, out ulong copySize))
|
||||
{
|
||||
ulong innerOffset = address - Address;
|
||||
ulong innerEndOffset = (address + size) - Address;
|
||||
|
||||
lastOffset = dstOffset + copySize;
|
||||
|
||||
// Clamp range to the specified range.
|
||||
ulong copySrcOffset = Math.Max(srcOffset, innerOffset);
|
||||
ulong copySrcEndOffset = Math.Min(innerEndOffset, srcOffset + copySize);
|
||||
|
||||
if (copySrcEndOffset > copySrcOffset)
|
||||
{
|
||||
copySize = copySrcEndOffset - copySrcOffset;
|
||||
dstOffset += copySrcOffset - srcOffset;
|
||||
srcOffset = copySrcOffset;
|
||||
|
||||
_context.Renderer.Pipeline.CopyBuffer(Handle, virtualBuffer.Handle, (int)srcOffset, (int)dstOffset, (int)copySize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increments the buffer reference count.
|
||||
/// </summary>
|
||||
|
@ -3,6 +3,7 @@ using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
@ -46,6 +47,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
private readonly Dictionary<ulong, BufferCacheEntry> _dirtyCache;
|
||||
private readonly Dictionary<ulong, BufferCacheEntry> _modifiedCache;
|
||||
private bool _pruneCaches;
|
||||
private int _virtualModifiedSequenceNumber;
|
||||
|
||||
public event Action NotifyBuffersModified;
|
||||
|
||||
@ -125,7 +127,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
/// <summary>
|
||||
/// Performs address translation of the GPU virtual address, and creates
|
||||
/// new buffers, if needed, for the specified range.
|
||||
/// new physical and virtual buffers, if needed, for the specified range.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
|
||||
/// <param name="gpuVa">Start GPU virtual address of the buffer</param>
|
||||
@ -138,12 +140,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
return new MultiRange(MemoryManager.PteUnmapped, size);
|
||||
}
|
||||
|
||||
bool supportsSparse = _context.Capabilities.SupportsSparseBuffer;
|
||||
|
||||
// Fast path not taken for non-contiguous ranges,
|
||||
// since multi-range buffers are not coalesced, so a buffer that covers
|
||||
// the entire cached range might not actually exist.
|
||||
if (memoryManager.VirtualBufferCache.TryGetOrAddRange(gpuVa, size, supportsSparse, out MultiRange range) &&
|
||||
if (memoryManager.VirtualRangeCache.TryGetOrAddRange(gpuVa, size, out MultiRange range) &&
|
||||
range.Count == 1)
|
||||
{
|
||||
return range;
|
||||
@ -154,6 +154,50 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
return range;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs address translation of the GPU virtual address, and creates
|
||||
/// new physical buffers, if needed, for the specified range.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
|
||||
/// <param name="gpuVa">Start GPU virtual address of the buffer</param>
|
||||
/// <param name="size">Size in bytes of the buffer</param>
|
||||
/// <returns>Physical ranges of the buffer, after address translation</returns>
|
||||
public MultiRange TranslateAndCreateMultiBuffersPhysicalOnly(MemoryManager memoryManager, ulong gpuVa, ulong size)
|
||||
{
|
||||
if (gpuVa == 0)
|
||||
{
|
||||
return new MultiRange(MemoryManager.PteUnmapped, size);
|
||||
}
|
||||
|
||||
// Fast path not taken for non-contiguous ranges,
|
||||
// since multi-range buffers are not coalesced, so a buffer that covers
|
||||
// the entire cached range might not actually exist.
|
||||
if (memoryManager.VirtualRangeCache.TryGetOrAddRange(gpuVa, size, out MultiRange range) &&
|
||||
range.Count == 1)
|
||||
{
|
||||
return range;
|
||||
}
|
||||
|
||||
for (int i = 0; i < range.Count; i++)
|
||||
{
|
||||
MemoryRange subRange = range.GetSubRange(i);
|
||||
|
||||
if (subRange.Address != MemoryManager.PteUnmapped)
|
||||
{
|
||||
if (range.Count > 1)
|
||||
{
|
||||
CreateBuffer(subRange.Address, subRange.Size, SparseBufferAlignmentSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
CreateBuffer(subRange.Address, subRange.Size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return range;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new buffer for the specified range, if it does not yet exist.
|
||||
/// This can be used to ensure the existance of a buffer.
|
||||
@ -263,41 +307,108 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
}
|
||||
|
||||
BufferRange[] storages = new BufferRange[range.Count];
|
||||
MultiRangeBuffer multiRangeBuffer;
|
||||
|
||||
MemoryRange[] alignedSubRanges = new MemoryRange[range.Count];
|
||||
|
||||
ulong alignmentMask = SparseBufferAlignmentSize - 1;
|
||||
|
||||
for (int i = 0; i < range.Count; i++)
|
||||
if (_context.Capabilities.SupportsSparseBuffer)
|
||||
{
|
||||
MemoryRange subRange = range.GetSubRange(i);
|
||||
BufferRange[] storages = new BufferRange[range.Count];
|
||||
|
||||
for (int i = 0; i < range.Count; i++)
|
||||
{
|
||||
MemoryRange subRange = range.GetSubRange(i);
|
||||
|
||||
if (subRange.Address != MemoryManager.PteUnmapped)
|
||||
{
|
||||
ulong endAddress = subRange.Address + subRange.Size;
|
||||
|
||||
ulong alignedAddress = subRange.Address & ~alignmentMask;
|
||||
ulong alignedEndAddress = (endAddress + alignmentMask) & ~alignmentMask;
|
||||
ulong alignedSize = alignedEndAddress - alignedAddress;
|
||||
|
||||
Buffer buffer = _buffers.FindFirstOverlap(alignedAddress, alignedSize);
|
||||
BufferRange bufferRange = buffer.GetRange(alignedAddress, alignedSize, false);
|
||||
|
||||
alignedSubRanges[i] = new MemoryRange(alignedAddress, alignedSize);
|
||||
storages[i] = bufferRange;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong alignedSize = (subRange.Size + alignmentMask) & ~alignmentMask;
|
||||
|
||||
alignedSubRanges[i] = new MemoryRange(MemoryManager.PteUnmapped, alignedSize);
|
||||
storages[i] = new BufferRange(BufferHandle.Null, 0, (int)alignedSize);
|
||||
}
|
||||
}
|
||||
|
||||
multiRangeBuffer = new(_context, new MultiRange(alignedSubRanges), storages);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < range.Count; i++)
|
||||
{
|
||||
MemoryRange subRange = range.GetSubRange(i);
|
||||
|
||||
if (subRange.Address != MemoryManager.PteUnmapped)
|
||||
{
|
||||
ulong endAddress = subRange.Address + subRange.Size;
|
||||
|
||||
ulong alignedAddress = subRange.Address & ~alignmentMask;
|
||||
ulong alignedEndAddress = (endAddress + alignmentMask) & ~alignmentMask;
|
||||
ulong alignedSize = alignedEndAddress - alignedAddress;
|
||||
|
||||
alignedSubRanges[i] = new MemoryRange(alignedAddress, alignedSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong alignedSize = (subRange.Size + alignmentMask) & ~alignmentMask;
|
||||
|
||||
alignedSubRanges[i] = new MemoryRange(MemoryManager.PteUnmapped, alignedSize);
|
||||
}
|
||||
}
|
||||
|
||||
multiRangeBuffer = new(_context, new MultiRange(alignedSubRanges));
|
||||
|
||||
UpdateVirtualBufferDependencies(multiRangeBuffer);
|
||||
}
|
||||
|
||||
_multiRangeBuffers.Add(multiRangeBuffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds two-way dependencies to all physical buffers contained within a given virtual buffer.
|
||||
/// </summary>
|
||||
/// <param name="virtualBuffer">Virtual buffer to have dependencies added</param>
|
||||
private void UpdateVirtualBufferDependencies(MultiRangeBuffer virtualBuffer)
|
||||
{
|
||||
virtualBuffer.ClearPhysicalDependencies();
|
||||
|
||||
ulong dstOffset = 0;
|
||||
|
||||
HashSet<Buffer> physicalBuffers = new();
|
||||
|
||||
for (int i = 0; i < virtualBuffer.Range.Count; i++)
|
||||
{
|
||||
MemoryRange subRange = virtualBuffer.Range.GetSubRange(i);
|
||||
|
||||
if (subRange.Address != MemoryManager.PteUnmapped)
|
||||
{
|
||||
ulong endAddress = subRange.Address + subRange.Size;
|
||||
Buffer buffer = _buffers.FindFirstOverlap(subRange.Address, subRange.Size);
|
||||
|
||||
ulong alignedAddress = subRange.Address & ~alignmentMask;
|
||||
ulong alignedEndAddress = (endAddress + alignmentMask) & ~alignmentMask;
|
||||
ulong alignedSize = alignedEndAddress - alignedAddress;
|
||||
|
||||
Buffer buffer = _buffers.FindFirstOverlap(alignedAddress, alignedSize);
|
||||
BufferRange bufferRange = buffer.GetRange(alignedAddress, alignedSize, false);
|
||||
|
||||
storages[i] = bufferRange;
|
||||
alignedSubRanges[i] = new MemoryRange(alignedAddress, alignedSize);
|
||||
virtualBuffer.AddPhysicalDependency(buffer, subRange.Address, dstOffset, subRange.Size);
|
||||
physicalBuffers.Add(buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong alignedSize = (subRange.Size + alignmentMask) & ~alignmentMask;
|
||||
|
||||
storages[i] = new BufferRange(BufferHandle.Null, 0, (int)alignedSize);
|
||||
alignedSubRanges[i] = new MemoryRange(MemoryManager.PteUnmapped, alignedSize);
|
||||
}
|
||||
dstOffset += subRange.Size;
|
||||
}
|
||||
|
||||
MultiRangeBuffer multiRangeBuffer = new(_context, new MultiRange(alignedSubRanges), storages);
|
||||
|
||||
_multiRangeBuffers.Add(multiRangeBuffer);
|
||||
foreach (var buffer in physicalBuffers)
|
||||
{
|
||||
buffer.CopyToDependantVirtualBuffer(virtualBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -620,8 +731,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="size">Size in bytes of the copy</param>
|
||||
public void CopyBuffer(MemoryManager memoryManager, ulong srcVa, ulong dstVa, ulong size)
|
||||
{
|
||||
MultiRange srcRange = TranslateAndCreateMultiBuffers(memoryManager, srcVa, size);
|
||||
MultiRange dstRange = TranslateAndCreateMultiBuffers(memoryManager, dstVa, size);
|
||||
MultiRange srcRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, srcVa, size);
|
||||
MultiRange dstRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, dstVa, size);
|
||||
|
||||
if (srcRange.Count == 1 && dstRange.Count == 1)
|
||||
{
|
||||
@ -701,6 +812,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
dstBuffer.ClearModified(dstAddress, size);
|
||||
memoryManager.Physical.WriteTrackedResource(dstAddress, memoryManager.Physical.GetSpan(srcAddress, (int)size), ResourceKind.Buffer);
|
||||
}
|
||||
|
||||
dstBuffer.CopyToDependantVirtualBuffers(dstAddress, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -715,7 +828,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="value">Value to be written into the buffer</param>
|
||||
public void ClearBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size, uint value)
|
||||
{
|
||||
MultiRange range = TranslateAndCreateMultiBuffers(memoryManager, gpuVa, size);
|
||||
MultiRange range = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, gpuVa, size);
|
||||
|
||||
for (int index = 0; index < range.Count; index++)
|
||||
{
|
||||
@ -727,6 +840,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
_context.Renderer.Pipeline.ClearBuffer(buffer.Handle, offset, (int)subRange.Size, value);
|
||||
|
||||
memoryManager.Physical.FillTrackedResource(subRange.Address, subRange.Size, value, ResourceKind.Buffer);
|
||||
|
||||
buffer.CopyToDependantVirtualBuffers(subRange.Address, subRange.Size);
|
||||
}
|
||||
}
|
||||
|
||||
@ -806,6 +921,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
}
|
||||
|
||||
if (write && buffer != null && !_context.Capabilities.SupportsSparseBuffer)
|
||||
{
|
||||
buffer.AddModifiedRegion(range, ++_virtualModifiedSequenceNumber);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@ -825,6 +945,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
buffer = _buffers.FindFirstOverlap(address, size);
|
||||
|
||||
buffer.CopyFromDependantVirtualBuffers();
|
||||
buffer.SynchronizeMemory(address, size);
|
||||
|
||||
if (write)
|
||||
@ -849,14 +970,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
if (range.Count == 1)
|
||||
{
|
||||
MemoryRange subRange = range.GetSubRange(0);
|
||||
SynchronizeBufferRange(subRange.Address, subRange.Size);
|
||||
SynchronizeBufferRange(subRange.Address, subRange.Size, copyBackVirtual: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int index = 0; index < range.Count; index++)
|
||||
{
|
||||
MemoryRange subRange = range.GetSubRange(index);
|
||||
SynchronizeBufferRange(subRange.Address, subRange.Size);
|
||||
SynchronizeBufferRange(subRange.Address, subRange.Size, copyBackVirtual: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -866,12 +987,19 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the memory range</param>
|
||||
/// <param name="size">Size in bytes of the memory range</param>
|
||||
private void SynchronizeBufferRange(ulong address, ulong size)
|
||||
/// <param name="copyBackVirtual">Whether virtual buffers that uses this buffer as backing memory should have its data copied back if modified</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SynchronizeBufferRange(ulong address, ulong size, bool copyBackVirtual)
|
||||
{
|
||||
if (size != 0)
|
||||
{
|
||||
Buffer buffer = _buffers.FindFirstOverlap(address, size);
|
||||
|
||||
if (copyBackVirtual)
|
||||
{
|
||||
buffer.CopyFromDependantVirtualBuffers();
|
||||
}
|
||||
|
||||
buffer.SynchronizeMemory(address, size);
|
||||
}
|
||||
}
|
||||
|
@ -484,7 +484,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
if (binding.IsImage)
|
||||
{
|
||||
_context.Renderer.Pipeline.SetImage(binding.BindingInfo.Binding, binding.Texture, binding.Format);
|
||||
_context.Renderer.Pipeline.SetImage(binding.Stage, binding.BindingInfo.Binding, binding.Texture, binding.Format);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -40,9 +40,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
internal PhysicalMemory Physical { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Virtual buffer cache.
|
||||
/// Virtual range cache.
|
||||
/// </summary>
|
||||
internal VirtualBufferCache VirtualBufferCache { get; }
|
||||
internal VirtualRangeCache VirtualRangeCache { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Cache of GPU counters.
|
||||
@ -56,12 +56,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
internal MemoryManager(PhysicalMemory physicalMemory)
|
||||
{
|
||||
Physical = physicalMemory;
|
||||
VirtualBufferCache = new VirtualBufferCache(this);
|
||||
VirtualRangeCache = new VirtualRangeCache(this);
|
||||
CounterCache = new CounterCache();
|
||||
_pageTable = new ulong[PtLvl0Size][];
|
||||
MemoryUnmapped += Physical.TextureCache.MemoryUnmappedHandler;
|
||||
MemoryUnmapped += Physical.BufferCache.MemoryUnmappedHandler;
|
||||
MemoryUnmapped += VirtualBufferCache.MemoryUnmappedHandler;
|
||||
MemoryUnmapped += VirtualRangeCache.MemoryUnmappedHandler;
|
||||
MemoryUnmapped += CounterCache.MemoryUnmappedHandler;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
@ -21,12 +22,73 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
public MultiRange Range { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Ever increasing counter value indicating when the buffer was modified relative to other buffers.
|
||||
/// </summary>
|
||||
public int ModificationSequenceNumber { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Physical buffer dependency entry.
|
||||
/// </summary>
|
||||
private readonly struct PhysicalDependency
|
||||
{
|
||||
/// <summary>
|
||||
/// Physical buffer.
|
||||
/// </summary>
|
||||
public readonly Buffer PhysicalBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// Offset of the range on the physical buffer.
|
||||
/// </summary>
|
||||
public readonly ulong PhysicalOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Offset of the range on the virtual buffer.
|
||||
/// </summary>
|
||||
public readonly ulong VirtualOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Size of the range.
|
||||
/// </summary>
|
||||
public readonly ulong Size;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new physical dependency.
|
||||
/// </summary>
|
||||
/// <param name="physicalBuffer">Physical buffer</param>
|
||||
/// <param name="physicalOffset">Offset of the range on the physical buffer</param>
|
||||
/// <param name="virtualOffset">Offset of the range on the virtual buffer</param>
|
||||
/// <param name="size">Size of the range</param>
|
||||
public PhysicalDependency(Buffer physicalBuffer, ulong physicalOffset, ulong virtualOffset, ulong size)
|
||||
{
|
||||
PhysicalBuffer = physicalBuffer;
|
||||
PhysicalOffset = physicalOffset;
|
||||
VirtualOffset = virtualOffset;
|
||||
Size = size;
|
||||
}
|
||||
}
|
||||
|
||||
private List<PhysicalDependency> _dependencies;
|
||||
private BufferModifiedRangeList _modifiedRanges = null;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the buffer.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context that the buffer belongs to</param>
|
||||
/// <param name="range">Range of memory where the data is mapped</param>
|
||||
/// <param name="storages">Backing memory for the buffers</param>
|
||||
public MultiRangeBuffer(GpuContext context, MultiRange range)
|
||||
{
|
||||
_context = context;
|
||||
Range = range;
|
||||
Handle = context.Renderer.CreateBuffer((int)range.GetSize());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the buffer.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context that the buffer belongs to</param>
|
||||
/// <param name="range">Range of memory where the data is mapped</param>
|
||||
/// <param name="storages">Backing memory for the buffer</param>
|
||||
public MultiRangeBuffer(GpuContext context, MultiRange range, ReadOnlySpan<BufferRange> storages)
|
||||
{
|
||||
_context = context;
|
||||
@ -49,11 +111,134 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
return new BufferRange(Handle, offset, (int)range.GetSize());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all physical buffer dependencies.
|
||||
/// </summary>
|
||||
public void ClearPhysicalDependencies()
|
||||
{
|
||||
_dependencies?.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a physical buffer dependency.
|
||||
/// </summary>
|
||||
/// <param name="buffer">Physical buffer to be added</param>
|
||||
/// <param name="rangeAddress">Address inside the physical buffer where the virtual buffer range is located</param>
|
||||
/// <param name="dstOffset">Offset inside the virtual buffer where the physical range is located</param>
|
||||
/// <param name="rangeSize">Size of the range in bytes</param>
|
||||
public void AddPhysicalDependency(Buffer buffer, ulong rangeAddress, ulong dstOffset, ulong rangeSize)
|
||||
{
|
||||
(_dependencies ??= new()).Add(new(buffer, rangeAddress - buffer.Address, dstOffset, rangeSize));
|
||||
buffer.AddVirtualDependency(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the physical range corresponding to the given physical buffer.
|
||||
/// </summary>
|
||||
/// <param name="buffer">Physical buffer</param>
|
||||
/// <param name="minimumVirtOffset">Minimum virtual offset that a range match can have</param>
|
||||
/// <param name="physicalOffset">Physical offset of the match</param>
|
||||
/// <param name="virtualOffset">Virtual offset of the match, always greater than or equal <paramref name="minimumVirtOffset"/></param>
|
||||
/// <param name="size">Size of the range match</param>
|
||||
/// <returns>True if a match was found for the given parameters, false otherwise</returns>
|
||||
public bool TryGetPhysicalOffset(Buffer buffer, ulong minimumVirtOffset, out ulong physicalOffset, out ulong virtualOffset, out ulong size)
|
||||
{
|
||||
physicalOffset = 0;
|
||||
virtualOffset = 0;
|
||||
size = 0;
|
||||
|
||||
if (_dependencies != null)
|
||||
{
|
||||
foreach (var dependency in _dependencies)
|
||||
{
|
||||
if (dependency.PhysicalBuffer == buffer && dependency.VirtualOffset >= minimumVirtOffset)
|
||||
{
|
||||
physicalOffset = dependency.PhysicalOffset;
|
||||
virtualOffset = dependency.VirtualOffset;
|
||||
size = dependency.Size;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a modified virtual memory range.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is only required when the host does not support sparse buffers, otherwise only physical buffers need to track modification.
|
||||
/// </remarks>
|
||||
/// <param name="range">Modified range</param>
|
||||
/// <param name="modifiedSequenceNumber">ModificationSequenceNumber</param>
|
||||
public void AddModifiedRegion(MultiRange range, int modifiedSequenceNumber)
|
||||
{
|
||||
_modifiedRanges ??= new(_context, null, null);
|
||||
|
||||
for (int i = 0; i < range.Count; i++)
|
||||
{
|
||||
MemoryRange subRange = range.GetSubRange(i);
|
||||
|
||||
_modifiedRanges.SignalModified(subRange.Address, subRange.Size);
|
||||
}
|
||||
|
||||
ModificationSequenceNumber = modifiedSequenceNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the specified <paramref name="rangeAction"/> for all modified ranges that overlaps with <paramref name="buffer"/>.
|
||||
/// </summary>
|
||||
/// <param name="buffer">Buffer to have its range checked</param>
|
||||
/// <param name="rangeAction">Action to perform for modified ranges</param>
|
||||
public void ConsumeModifiedRegion(Buffer buffer, Action<ulong, ulong> rangeAction)
|
||||
{
|
||||
ConsumeModifiedRegion(buffer.Address, buffer.Size, rangeAction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the specified <paramref name="rangeAction"/> for all modified ranges that overlaps with <paramref name="address"/> and <paramref name="size"/>.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the region to consume</param>
|
||||
/// <param name="size">Size of the region to consume</param>
|
||||
/// <param name="rangeAction">Action to perform for modified ranges</param>
|
||||
public void ConsumeModifiedRegion(ulong address, ulong size, Action<ulong, ulong> rangeAction)
|
||||
{
|
||||
if (_modifiedRanges != null)
|
||||
{
|
||||
_modifiedRanges.GetRanges(address, size, rangeAction);
|
||||
_modifiedRanges.Clear(address, size);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets data from the specified region of the buffer, and places it on <paramref name="output"/>.
|
||||
/// </summary>
|
||||
/// <param name="output">Span to put the data into</param>
|
||||
/// <param name="offset">Offset of the buffer to get the data from</param>
|
||||
/// <param name="size">Size of the data in bytes</param>
|
||||
public void GetData(Span<byte> output, int offset, int size)
|
||||
{
|
||||
using PinnedSpan<byte> data = _context.Renderer.GetBufferData(Handle, offset, size);
|
||||
data.Get().CopyTo(output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the host buffer.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_dependencies != null)
|
||||
{
|
||||
foreach (var dependency in _dependencies)
|
||||
{
|
||||
dependency.PhysicalBuffer.RemoveVirtualDependency(this);
|
||||
}
|
||||
|
||||
_dependencies = null;
|
||||
}
|
||||
|
||||
_context.Renderer.DeleteBuffer(Handle);
|
||||
}
|
||||
}
|
||||
|
@ -6,9 +6,9 @@ using System.Threading;
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
/// <summary>
|
||||
/// Virtual buffer cache.
|
||||
/// Virtual range cache.
|
||||
/// </summary>
|
||||
class VirtualBufferCache
|
||||
class VirtualRangeCache
|
||||
{
|
||||
private readonly MemoryManager _memoryManager;
|
||||
|
||||
@ -68,10 +68,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
private int _hasDeferredUnmaps;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the virtual buffer cache.
|
||||
/// Creates a new instance of the virtual range cache.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">Memory manager that the virtual buffer cache belongs to</param>
|
||||
public VirtualBufferCache(MemoryManager memoryManager)
|
||||
/// <param name="memoryManager">Memory manager that the virtual range cache belongs to</param>
|
||||
public VirtualRangeCache(MemoryManager memoryManager)
|
||||
{
|
||||
_memoryManager = memoryManager;
|
||||
_virtualRanges = new RangeList<VirtualRange>();
|
||||
@ -102,10 +102,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
/// <param name="gpuVa">GPU virtual address to get the physical range from</param>
|
||||
/// <param name="size">Size in bytes of the region</param>
|
||||
/// <param name="supportsSparse">Indicates host support for sparse buffer mapping of non-contiguous ranges</param>
|
||||
/// <param name="range">Physical range for the specified GPU virtual region</param>
|
||||
/// <returns>True if the range already existed, false if a new one was created and added</returns>
|
||||
public bool TryGetOrAddRange(ulong gpuVa, ulong size, bool supportsSparse, out MultiRange range)
|
||||
public bool TryGetOrAddRange(ulong gpuVa, ulong size, out MultiRange range)
|
||||
{
|
||||
VirtualRange[] overlaps = _virtualRangeOverlaps;
|
||||
int overlapsCount;
|
||||
@ -158,7 +157,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
else
|
||||
{
|
||||
found = true;
|
||||
found = overlap0.Range.Count == 1 || IsSparseAligned(overlap0.Range);
|
||||
range = overlap0.Range.Slice(gpuVa - overlap0.Address, size);
|
||||
}
|
||||
}
|
||||
@ -175,11 +174,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
ShrinkOverlapsBufferIfNeeded();
|
||||
|
||||
// If the the range is not properly aligned for sparse mapping,
|
||||
// or if the host does not support sparse mapping, let's just
|
||||
// force it to a single range.
|
||||
// let's just force it to a single range.
|
||||
// This might cause issues in some applications that uses sparse
|
||||
// mappings.
|
||||
if (!IsSparseAligned(range) || !supportsSparse)
|
||||
if (!IsSparseAligned(range))
|
||||
{
|
||||
range = new MultiRange(range.GetSubRange(0).Address, size);
|
||||
}
|
@ -68,6 +68,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
Add(Format.S8Uint, new FormatInfo(1, false, false, All.StencilIndex8, PixelFormat.StencilIndex, PixelType.UnsignedByte));
|
||||
Add(Format.D16Unorm, new FormatInfo(1, false, false, All.DepthComponent16, PixelFormat.DepthComponent, PixelType.UnsignedShort));
|
||||
Add(Format.S8UintD24Unorm, new FormatInfo(1, false, false, All.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248));
|
||||
Add(Format.X8UintD24Unorm, new FormatInfo(1, false, false, All.DepthComponent24, PixelFormat.DepthComponent, PixelType.UnsignedInt));
|
||||
Add(Format.D32Float, new FormatInfo(1, false, false, All.DepthComponent32f, PixelFormat.DepthComponent, PixelType.Float));
|
||||
Add(Format.D24UnormS8Uint, new FormatInfo(1, false, false, All.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248));
|
||||
Add(Format.D32FloatS8Uint, new FormatInfo(1, false, false, All.Depth32fStencil8, PixelFormat.DepthStencil, PixelType.Float32UnsignedInt248Rev));
|
||||
@ -224,5 +225,17 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
{
|
||||
return _tableImage[(int)format];
|
||||
}
|
||||
|
||||
public static bool IsPackedDepthStencil(Format format)
|
||||
{
|
||||
return format == Format.D24UnormS8Uint ||
|
||||
format == Format.D32FloatS8Uint ||
|
||||
format == Format.S8UintD24Unorm;
|
||||
}
|
||||
|
||||
public static bool IsDepthOnly(Format format)
|
||||
{
|
||||
return format == Format.D16Unorm || format == Format.D32Float || format == Format.X8UintD24Unorm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -119,11 +119,11 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
|
||||
private static FramebufferAttachment GetAttachment(Format format)
|
||||
{
|
||||
if (IsPackedDepthStencilFormat(format))
|
||||
if (FormatTable.IsPackedDepthStencil(format))
|
||||
{
|
||||
return FramebufferAttachment.DepthStencilAttachment;
|
||||
}
|
||||
else if (IsDepthOnlyFormat(format))
|
||||
else if (FormatTable.IsDepthOnly(format))
|
||||
{
|
||||
return FramebufferAttachment.DepthAttachment;
|
||||
}
|
||||
@ -133,18 +133,6 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsPackedDepthStencilFormat(Format format)
|
||||
{
|
||||
return format == Format.D24UnormS8Uint ||
|
||||
format == Format.D32FloatS8Uint ||
|
||||
format == Format.S8UintD24Unorm;
|
||||
}
|
||||
|
||||
private static bool IsDepthOnlyFormat(Format format)
|
||||
{
|
||||
return format == Format.D16Unorm || format == Format.D32Float;
|
||||
}
|
||||
|
||||
public int GetColorLayerCount(int index)
|
||||
{
|
||||
return _colors[index]?.Info.GetDepthOrLayers() ?? 0;
|
||||
|
@ -294,7 +294,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
{
|
||||
return FramebufferAttachment.DepthStencilAttachment;
|
||||
}
|
||||
else if (IsDepthOnly(format))
|
||||
else if (FormatTable.IsDepthOnly(format))
|
||||
{
|
||||
return FramebufferAttachment.DepthAttachment;
|
||||
}
|
||||
@ -324,11 +324,11 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
|
||||
private static ClearBufferMask GetMask(Format format)
|
||||
{
|
||||
if (format == Format.D24UnormS8Uint || format == Format.D32FloatS8Uint || format == Format.S8UintD24Unorm)
|
||||
if (FormatTable.IsPackedDepthStencil(format))
|
||||
{
|
||||
return ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit;
|
||||
}
|
||||
else if (IsDepthOnly(format))
|
||||
else if (FormatTable.IsDepthOnly(format))
|
||||
{
|
||||
return ClearBufferMask.DepthBufferBit;
|
||||
}
|
||||
@ -342,11 +342,6 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsDepthOnly(Format format)
|
||||
{
|
||||
return format == Format.D16Unorm || format == Format.D32Float;
|
||||
}
|
||||
|
||||
public TextureView BgraSwap(TextureView from)
|
||||
{
|
||||
TextureView to = (TextureView)_renderer.CreateTexture(from.Info);
|
||||
|
@ -935,7 +935,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
SetFrontFace(_frontFace = frontFace.Convert());
|
||||
}
|
||||
|
||||
public void SetImage(int binding, ITexture texture, Format imageFormat)
|
||||
public void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat)
|
||||
{
|
||||
if ((uint)binding < SavedImages)
|
||||
{
|
||||
|
229
src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs
Normal file
229
src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs
Normal file
@ -0,0 +1,229 @@
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
internal class BarrierBatch : IDisposable
|
||||
{
|
||||
private const int MaxBarriersPerCall = 16;
|
||||
|
||||
private readonly VulkanRenderer _gd;
|
||||
|
||||
private readonly NativeArray<MemoryBarrier> _memoryBarrierBatch = new(MaxBarriersPerCall);
|
||||
private readonly NativeArray<BufferMemoryBarrier> _bufferBarrierBatch = new(MaxBarriersPerCall);
|
||||
private readonly NativeArray<ImageMemoryBarrier> _imageBarrierBatch = new(MaxBarriersPerCall);
|
||||
|
||||
private readonly List<BarrierWithStageFlags<MemoryBarrier>> _memoryBarriers = new();
|
||||
private readonly List<BarrierWithStageFlags<BufferMemoryBarrier>> _bufferBarriers = new();
|
||||
private readonly List<BarrierWithStageFlags<ImageMemoryBarrier>> _imageBarriers = new();
|
||||
private int _queuedBarrierCount;
|
||||
|
||||
public BarrierBatch(VulkanRenderer gd)
|
||||
{
|
||||
_gd = gd;
|
||||
}
|
||||
|
||||
private readonly record struct StageFlags : IEquatable<StageFlags>
|
||||
{
|
||||
public readonly PipelineStageFlags Source;
|
||||
public readonly PipelineStageFlags Dest;
|
||||
|
||||
public StageFlags(PipelineStageFlags source, PipelineStageFlags dest)
|
||||
{
|
||||
Source = source;
|
||||
Dest = dest;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct BarrierWithStageFlags<T> where T : unmanaged
|
||||
{
|
||||
public readonly StageFlags Flags;
|
||||
public readonly T Barrier;
|
||||
|
||||
public BarrierWithStageFlags(StageFlags flags, T barrier)
|
||||
{
|
||||
Flags = flags;
|
||||
Barrier = barrier;
|
||||
}
|
||||
|
||||
public BarrierWithStageFlags(PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags, T barrier)
|
||||
{
|
||||
Flags = new StageFlags(srcStageFlags, dstStageFlags);
|
||||
Barrier = barrier;
|
||||
}
|
||||
}
|
||||
|
||||
private void QueueBarrier<T>(List<BarrierWithStageFlags<T>> list, T barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags) where T : unmanaged
|
||||
{
|
||||
list.Add(new BarrierWithStageFlags<T>(srcStageFlags, dstStageFlags, barrier));
|
||||
_queuedBarrierCount++;
|
||||
}
|
||||
|
||||
public void QueueBarrier(MemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
|
||||
{
|
||||
QueueBarrier(_memoryBarriers, barrier, srcStageFlags, dstStageFlags);
|
||||
}
|
||||
|
||||
public void QueueBarrier(BufferMemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
|
||||
{
|
||||
QueueBarrier(_bufferBarriers, barrier, srcStageFlags, dstStageFlags);
|
||||
}
|
||||
|
||||
public void QueueBarrier(ImageMemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
|
||||
{
|
||||
QueueBarrier(_imageBarriers, barrier, srcStageFlags, dstStageFlags);
|
||||
}
|
||||
|
||||
public unsafe void Flush(CommandBuffer cb, bool insideRenderPass, Action endRenderPass)
|
||||
{
|
||||
while (_queuedBarrierCount > 0)
|
||||
{
|
||||
int memoryCount = 0;
|
||||
int bufferCount = 0;
|
||||
int imageCount = 0;
|
||||
|
||||
bool hasBarrier = false;
|
||||
StageFlags flags = default;
|
||||
|
||||
static void AddBarriers<T>(
|
||||
Span<T> target,
|
||||
ref int queuedBarrierCount,
|
||||
ref bool hasBarrier,
|
||||
ref StageFlags flags,
|
||||
ref int count,
|
||||
List<BarrierWithStageFlags<T>> list) where T : unmanaged
|
||||
{
|
||||
int firstMatch = -1;
|
||||
int end = list.Count;
|
||||
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
BarrierWithStageFlags<T> barrier = list[i];
|
||||
|
||||
if (!hasBarrier)
|
||||
{
|
||||
flags = barrier.Flags;
|
||||
hasBarrier = true;
|
||||
|
||||
target[count++] = barrier.Barrier;
|
||||
queuedBarrierCount--;
|
||||
firstMatch = i;
|
||||
|
||||
if (count >= target.Length)
|
||||
{
|
||||
end = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (flags.Equals(barrier.Flags))
|
||||
{
|
||||
target[count++] = barrier.Barrier;
|
||||
queuedBarrierCount--;
|
||||
|
||||
if (firstMatch == -1)
|
||||
{
|
||||
firstMatch = i;
|
||||
}
|
||||
|
||||
if (count >= target.Length)
|
||||
{
|
||||
end = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Delete consumed barriers from the first match to the current non-match.
|
||||
if (firstMatch != -1)
|
||||
{
|
||||
int deleteCount = i - firstMatch;
|
||||
list.RemoveRange(firstMatch, deleteCount);
|
||||
i -= deleteCount;
|
||||
|
||||
firstMatch = -1;
|
||||
end = list.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (firstMatch == 0 && end == list.Count)
|
||||
{
|
||||
list.Clear();
|
||||
}
|
||||
else if (firstMatch != -1)
|
||||
{
|
||||
int deleteCount = end - firstMatch;
|
||||
|
||||
list.RemoveRange(firstMatch, deleteCount);
|
||||
}
|
||||
}
|
||||
|
||||
if (insideRenderPass)
|
||||
{
|
||||
// Image barriers queued in the batch are meant to be globally scoped,
|
||||
// but inside a render pass they're scoped to just the range of the render pass.
|
||||
|
||||
// On MoltenVK, we just break the rules and always use image barrier.
|
||||
// On desktop GPUs, all barriers are globally scoped, so we just replace it with a generic memory barrier.
|
||||
// TODO: On certain GPUs, we need to split render pass so the barrier scope is global. When this is done,
|
||||
// notify the resource that it should add a barrier as soon as a render pass ends to avoid this in future.
|
||||
|
||||
if (!_gd.IsMoltenVk)
|
||||
{
|
||||
foreach (var barrier in _imageBarriers)
|
||||
{
|
||||
_memoryBarriers.Add(new BarrierWithStageFlags<MemoryBarrier>(
|
||||
barrier.Flags,
|
||||
new MemoryBarrier()
|
||||
{
|
||||
SType = StructureType.MemoryBarrier,
|
||||
SrcAccessMask = barrier.Barrier.SrcAccessMask,
|
||||
DstAccessMask = barrier.Barrier.DstAccessMask
|
||||
}));
|
||||
}
|
||||
|
||||
_imageBarriers.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
AddBarriers(_memoryBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref memoryCount, _memoryBarriers);
|
||||
AddBarriers(_bufferBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref bufferCount, _bufferBarriers);
|
||||
AddBarriers(_imageBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref imageCount, _imageBarriers);
|
||||
|
||||
if (hasBarrier)
|
||||
{
|
||||
PipelineStageFlags srcStageFlags = flags.Source;
|
||||
|
||||
if (insideRenderPass)
|
||||
{
|
||||
// Inside a render pass, barrier stages can only be from rasterization.
|
||||
srcStageFlags &= ~PipelineStageFlags.ComputeShaderBit;
|
||||
}
|
||||
|
||||
_gd.Api.CmdPipelineBarrier(
|
||||
cb,
|
||||
srcStageFlags,
|
||||
flags.Dest,
|
||||
0,
|
||||
(uint)memoryCount,
|
||||
_memoryBarrierBatch.Pointer,
|
||||
(uint)bufferCount,
|
||||
_bufferBarrierBatch.Pointer,
|
||||
(uint)imageCount,
|
||||
_imageBarrierBatch.Pointer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_memoryBarrierBatch.Dispose();
|
||||
_bufferBarrierBatch.Dispose();
|
||||
_imageBarrierBatch.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public const int MaxStorageBufferBindings = MaxStorageBuffersPerStage * MaxShaderStages;
|
||||
public const int MaxTextureBindings = MaxTexturesPerStage * MaxShaderStages;
|
||||
public const int MaxImageBindings = MaxImagesPerStage * MaxShaderStages;
|
||||
public const int MaxPushDescriptorBinding = 64;
|
||||
|
||||
public const ulong SparseBufferAlignment = 0x10000;
|
||||
}
|
||||
|
@ -1,19 +1,32 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
class DescriptorSetTemplate : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Renderdoc seems to crash when doing a templated uniform update with count > 1 on a push descriptor.
|
||||
/// When this is true, consecutive buffers are always updated individually.
|
||||
/// </summary>
|
||||
private const bool RenderdocPushCountBug = true;
|
||||
|
||||
private readonly VulkanRenderer _gd;
|
||||
private readonly Device _device;
|
||||
|
||||
public readonly DescriptorUpdateTemplate Template;
|
||||
public readonly int Size;
|
||||
|
||||
public unsafe DescriptorSetTemplate(VulkanRenderer gd, Device device, ResourceBindingSegment[] segments, PipelineLayoutCacheEntry plce, PipelineBindPoint pbp, int setIndex)
|
||||
public unsafe DescriptorSetTemplate(
|
||||
VulkanRenderer gd,
|
||||
Device device,
|
||||
ResourceBindingSegment[] segments,
|
||||
PipelineLayoutCacheEntry plce,
|
||||
PipelineBindPoint pbp,
|
||||
int setIndex)
|
||||
{
|
||||
_gd = gd;
|
||||
_device = device;
|
||||
@ -137,6 +150,93 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Template = result;
|
||||
}
|
||||
|
||||
public unsafe DescriptorSetTemplate(
|
||||
VulkanRenderer gd,
|
||||
Device device,
|
||||
ResourceDescriptorCollection descriptors,
|
||||
long updateMask,
|
||||
PipelineLayoutCacheEntry plce,
|
||||
PipelineBindPoint pbp,
|
||||
int setIndex)
|
||||
{
|
||||
_gd = gd;
|
||||
_device = device;
|
||||
|
||||
// Create a template from the set usages. Assumes the descriptor set is updated in segment order then binding order.
|
||||
int segmentCount = BitOperations.PopCount((ulong)updateMask);
|
||||
|
||||
DescriptorUpdateTemplateEntry* entries = stackalloc DescriptorUpdateTemplateEntry[segmentCount];
|
||||
int entry = 0;
|
||||
nuint structureOffset = 0;
|
||||
|
||||
void AddBinding(int binding, int count)
|
||||
{
|
||||
entries[entry++] = new DescriptorUpdateTemplateEntry()
|
||||
{
|
||||
DescriptorType = DescriptorType.UniformBuffer,
|
||||
DstBinding = (uint)binding,
|
||||
DescriptorCount = (uint)count,
|
||||
Offset = structureOffset,
|
||||
Stride = (nuint)Unsafe.SizeOf<DescriptorBufferInfo>()
|
||||
};
|
||||
|
||||
structureOffset += (nuint)(Unsafe.SizeOf<DescriptorBufferInfo>() * count);
|
||||
}
|
||||
|
||||
int startBinding = 0;
|
||||
int bindingCount = 0;
|
||||
|
||||
foreach (ResourceDescriptor descriptor in descriptors.Descriptors)
|
||||
{
|
||||
for (int i = 0; i < descriptor.Count; i++)
|
||||
{
|
||||
int binding = descriptor.Binding + i;
|
||||
|
||||
if ((updateMask & (1L << binding)) != 0)
|
||||
{
|
||||
if (bindingCount > 0 && (RenderdocPushCountBug || startBinding + bindingCount != binding))
|
||||
{
|
||||
AddBinding(startBinding, bindingCount);
|
||||
|
||||
bindingCount = 0;
|
||||
}
|
||||
|
||||
if (bindingCount == 0)
|
||||
{
|
||||
startBinding = binding;
|
||||
}
|
||||
|
||||
bindingCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bindingCount > 0)
|
||||
{
|
||||
AddBinding(startBinding, bindingCount);
|
||||
}
|
||||
|
||||
Size = (int)structureOffset;
|
||||
|
||||
var info = new DescriptorUpdateTemplateCreateInfo()
|
||||
{
|
||||
SType = StructureType.DescriptorUpdateTemplateCreateInfo,
|
||||
DescriptorUpdateEntryCount = (uint)entry,
|
||||
PDescriptorUpdateEntries = entries,
|
||||
|
||||
TemplateType = DescriptorUpdateTemplateType.PushDescriptorsKhr,
|
||||
DescriptorSetLayout = plce.DescriptorSetLayouts[setIndex],
|
||||
PipelineBindPoint = pbp,
|
||||
PipelineLayout = plce.PipelineLayout,
|
||||
Set = (uint)setIndex,
|
||||
};
|
||||
|
||||
DescriptorUpdateTemplate result;
|
||||
gd.Api.CreateDescriptorUpdateTemplate(device, &info, null, &result).ThrowOnError();
|
||||
|
||||
Template = result;
|
||||
}
|
||||
|
||||
public unsafe void Dispose()
|
||||
{
|
||||
_gd.Api.DestroyDescriptorUpdateTemplate(_device, Template, null);
|
||||
|
@ -52,11 +52,23 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return new DescriptorSetTemplateWriter(new Span<byte>(_data.Pointer, template.Size));
|
||||
}
|
||||
|
||||
public DescriptorSetTemplateWriter Begin(int maxSize)
|
||||
{
|
||||
EnsureSize(maxSize);
|
||||
|
||||
return new DescriptorSetTemplateWriter(new Span<byte>(_data.Pointer, maxSize));
|
||||
}
|
||||
|
||||
public void Commit(VulkanRenderer gd, Device device, DescriptorSet set)
|
||||
{
|
||||
gd.Api.UpdateDescriptorSetWithTemplate(device, set, _activeTemplate.Template, _data.Pointer);
|
||||
}
|
||||
|
||||
public void CommitPushDescriptor(VulkanRenderer gd, CommandBufferScoped cbs, DescriptorSetTemplate template, PipelineLayout layout)
|
||||
{
|
||||
gd.PushDescriptorApi.CmdPushDescriptorSetWithTemplate(cbs.CommandBuffer, template.Template, layout, 0, _data.Pointer);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_data?.Dispose();
|
||||
|
@ -4,6 +4,7 @@ using Ryujinx.Graphics.Shader;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using CompareOp = Ryujinx.Graphics.GAL.CompareOp;
|
||||
using Format = Ryujinx.Graphics.GAL.Format;
|
||||
using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo;
|
||||
@ -34,6 +35,36 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
private record struct TextureRef
|
||||
{
|
||||
public ShaderStage Stage;
|
||||
public TextureStorage Storage;
|
||||
public Auto<DisposableImageView> View;
|
||||
public Auto<DisposableSampler> Sampler;
|
||||
|
||||
public TextureRef(ShaderStage stage, TextureStorage storage, Auto<DisposableImageView> view, Auto<DisposableSampler> sampler)
|
||||
{
|
||||
Stage = stage;
|
||||
Storage = storage;
|
||||
View = view;
|
||||
Sampler = sampler;
|
||||
}
|
||||
}
|
||||
|
||||
private record struct ImageRef
|
||||
{
|
||||
public ShaderStage Stage;
|
||||
public TextureStorage Storage;
|
||||
public Auto<DisposableImageView> View;
|
||||
|
||||
public ImageRef(ShaderStage stage, TextureStorage storage, Auto<DisposableImageView> view)
|
||||
{
|
||||
Stage = stage;
|
||||
Storage = storage;
|
||||
View = view;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly VulkanRenderer _gd;
|
||||
private readonly Device _device;
|
||||
private readonly PipelineBase _pipeline;
|
||||
@ -41,9 +72,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private readonly BufferRef[] _uniformBufferRefs;
|
||||
private readonly BufferRef[] _storageBufferRefs;
|
||||
private readonly Auto<DisposableImageView>[] _textureRefs;
|
||||
private readonly Auto<DisposableSampler>[] _samplerRefs;
|
||||
private readonly Auto<DisposableImageView>[] _imageRefs;
|
||||
private readonly TextureRef[] _textureRefs;
|
||||
private readonly ImageRef[] _imageRefs;
|
||||
private readonly TextureBuffer[] _bufferTextureRefs;
|
||||
private readonly TextureBuffer[] _bufferImageRefs;
|
||||
private readonly Format[] _bufferImageFormats;
|
||||
@ -61,6 +91,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private BitMapStruct<Array2<long>> _storageSet;
|
||||
private BitMapStruct<Array2<long>> _uniformMirrored;
|
||||
private BitMapStruct<Array2<long>> _storageMirrored;
|
||||
private readonly int[] _uniformSetPd;
|
||||
private int _pdSequence = 1;
|
||||
|
||||
private bool _updateDescriptorCacheCbIndex;
|
||||
|
||||
@ -92,9 +124,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
_uniformBufferRefs = new BufferRef[Constants.MaxUniformBufferBindings];
|
||||
_storageBufferRefs = new BufferRef[Constants.MaxStorageBufferBindings];
|
||||
_textureRefs = new Auto<DisposableImageView>[Constants.MaxTextureBindings * 2];
|
||||
_samplerRefs = new Auto<DisposableSampler>[Constants.MaxTextureBindings * 2];
|
||||
_imageRefs = new Auto<DisposableImageView>[Constants.MaxImageBindings * 2];
|
||||
_textureRefs = new TextureRef[Constants.MaxTextureBindings * 2];
|
||||
_imageRefs = new ImageRef[Constants.MaxImageBindings * 2];
|
||||
_bufferTextureRefs = new TextureBuffer[Constants.MaxTextureBindings * 2];
|
||||
_bufferImageRefs = new TextureBuffer[Constants.MaxImageBindings * 2];
|
||||
_bufferImageFormats = new Format[Constants.MaxImageBindings * 2];
|
||||
@ -106,6 +137,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_bufferTextures = new BufferView[Constants.MaxTexturesPerStage];
|
||||
_bufferImages = new BufferView[Constants.MaxImagesPerStage];
|
||||
|
||||
_uniformSetPd = new int[Constants.MaxUniformBufferBindings];
|
||||
|
||||
var initialImageInfo = new DescriptorImageInfo
|
||||
{
|
||||
ImageLayout = ImageLayout.General,
|
||||
@ -193,6 +226,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
if (BindingOverlaps(ref info, bindingOffset, offset, size))
|
||||
{
|
||||
_uniformSet.Clear(binding);
|
||||
_uniformSetPd[binding] = 0;
|
||||
SignalDirty(DirtyFlags.Uniform);
|
||||
}
|
||||
}
|
||||
@ -223,14 +257,61 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
});
|
||||
}
|
||||
|
||||
public void SetProgram(ShaderCollection program)
|
||||
public void InsertBindingBarriers(CommandBufferScoped cbs)
|
||||
{
|
||||
foreach (ResourceBindingSegment segment in _program.BindingSegments[PipelineBase.TextureSetIndex])
|
||||
{
|
||||
if (segment.Type == ResourceType.TextureAndSampler)
|
||||
{
|
||||
for (int i = 0; i < segment.Count; i++)
|
||||
{
|
||||
ref var texture = ref _textureRefs[segment.Binding + i];
|
||||
texture.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, texture.Stage.ConvertToPipelineStageFlags());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (ResourceBindingSegment segment in _program.BindingSegments[PipelineBase.ImageSetIndex])
|
||||
{
|
||||
if (segment.Type == ResourceType.Image)
|
||||
{
|
||||
for (int i = 0; i < segment.Count; i++)
|
||||
{
|
||||
ref var image = ref _imageRefs[segment.Binding + i];
|
||||
image.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, image.Stage.ConvertToPipelineStageFlags());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AdvancePdSequence()
|
||||
{
|
||||
if (++_pdSequence == 0)
|
||||
{
|
||||
_pdSequence = 1;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetProgram(CommandBufferScoped cbs, ShaderCollection program, bool isBound)
|
||||
{
|
||||
if (!program.HasSameLayout(_program))
|
||||
{
|
||||
// When the pipeline layout changes, push descriptor bindings are invalidated.
|
||||
|
||||
AdvancePdSequence();
|
||||
}
|
||||
|
||||
_program = program;
|
||||
_updateDescriptorCacheCbIndex = true;
|
||||
_dirty = DirtyFlags.All;
|
||||
}
|
||||
|
||||
public void SetImage(int binding, ITexture image, Format imageFormat)
|
||||
public void SetImage(
|
||||
CommandBufferScoped cbs,
|
||||
ShaderStage stage,
|
||||
int binding,
|
||||
ITexture image,
|
||||
Format imageFormat)
|
||||
{
|
||||
if (image is TextureBuffer imageBuffer)
|
||||
{
|
||||
@ -239,11 +320,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
else if (image is TextureView view)
|
||||
{
|
||||
_imageRefs[binding] = view.GetView(imageFormat).GetIdentityImageView();
|
||||
view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
|
||||
|
||||
_imageRefs[binding] = new(stage, view.Storage, view.GetView(imageFormat).GetIdentityImageView());
|
||||
}
|
||||
else
|
||||
{
|
||||
_imageRefs[binding] = null;
|
||||
_imageRefs[binding] = default;
|
||||
_bufferImageRefs[binding] = null;
|
||||
_bufferImageFormats[binding] = default;
|
||||
}
|
||||
@ -253,7 +336,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public void SetImage(int binding, Auto<DisposableImageView> image)
|
||||
{
|
||||
_imageRefs[binding] = image;
|
||||
_imageRefs[binding] = new(ShaderStage.Compute, null, image);
|
||||
|
||||
SignalDirty(DirtyFlags.Image);
|
||||
}
|
||||
@ -338,15 +421,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
else if (texture is TextureView view)
|
||||
{
|
||||
view.Storage.InsertWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
|
||||
view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
|
||||
|
||||
_textureRefs[binding] = view.GetImageView();
|
||||
_samplerRefs[binding] = ((SamplerHolder)sampler)?.GetSampler();
|
||||
_textureRefs[binding] = new(stage, view.Storage, view.GetImageView(), ((SamplerHolder)sampler)?.GetSampler());
|
||||
}
|
||||
else
|
||||
{
|
||||
_textureRefs[binding] = null;
|
||||
_samplerRefs[binding] = null;
|
||||
_textureRefs[binding] = default;
|
||||
_bufferTextureRefs[binding] = null;
|
||||
}
|
||||
|
||||
@ -362,10 +443,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
if (texture is TextureView view)
|
||||
{
|
||||
view.Storage.InsertWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
|
||||
view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
|
||||
|
||||
_textureRefs[binding] = view.GetIdentityImageView();
|
||||
_samplerRefs[binding] = ((SamplerHolder)sampler)?.GetSampler();
|
||||
_textureRefs[binding] = new(stage, view.Storage, view.GetIdentityImageView(), ((SamplerHolder)sampler)?.GetSampler());
|
||||
|
||||
SignalDirty(DirtyFlags.Texture);
|
||||
}
|
||||
@ -402,6 +482,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
if (!currentBufferRef.Equals(newRef) || currentInfo.Range != info.Range)
|
||||
{
|
||||
_uniformSet.Clear(index);
|
||||
_uniformSetPd[index] = 0;
|
||||
|
||||
currentInfo = info;
|
||||
currentBufferRef = newRef;
|
||||
@ -579,9 +660,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ref var texture = ref textures[i];
|
||||
ref var refs = ref _textureRefs[binding + i];
|
||||
|
||||
texture.ImageView = _textureRefs[binding + i]?.Get(cbs).Value ?? default;
|
||||
texture.Sampler = _samplerRefs[binding + i]?.Get(cbs).Value ?? default;
|
||||
texture.ImageView = refs.View?.Get(cbs).Value ?? default;
|
||||
texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default;
|
||||
|
||||
if (texture.ImageView.Handle == 0)
|
||||
{
|
||||
@ -616,7 +698,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
images[i].ImageView = _imageRefs[binding + i]?.Get(cbs).Value ?? default;
|
||||
images[i].ImageView = _imageRefs[binding + i].View?.Get(cbs).Value ?? default;
|
||||
}
|
||||
|
||||
tu.Push<DescriptorImageInfo>(images[..count]);
|
||||
@ -671,15 +753,19 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void UpdateAndBindUniformBufferPd(CommandBufferScoped cbs, PipelineBindPoint pbp)
|
||||
{
|
||||
int sequence = _pdSequence;
|
||||
var bindingSegments = _program.BindingSegments[PipelineBase.UniformSetIndex];
|
||||
var dummyBuffer = _dummyBuffer?.GetBuffer();
|
||||
|
||||
long updatedBindings = 0;
|
||||
DescriptorSetTemplateWriter writer = _templateUpdater.Begin(32 * Unsafe.SizeOf<DescriptorBufferInfo>());
|
||||
|
||||
foreach (ResourceBindingSegment segment in bindingSegments)
|
||||
{
|
||||
int binding = segment.Binding;
|
||||
int count = segment.Count;
|
||||
|
||||
bool doUpdate = false;
|
||||
ReadOnlySpan<DescriptorBufferInfo> uniformBuffers = _uniformBuffers;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
@ -688,16 +774,28 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
if (_uniformSet.Set(index))
|
||||
{
|
||||
ref BufferRef buffer = ref _uniformBufferRefs[index];
|
||||
UpdateBuffer(cbs, ref _uniformBuffers[index], ref buffer, dummyBuffer, true);
|
||||
doUpdate = true;
|
||||
|
||||
bool mirrored = UpdateBuffer(cbs, ref _uniformBuffers[index], ref buffer, dummyBuffer, true);
|
||||
|
||||
_uniformMirrored.Set(index, mirrored);
|
||||
}
|
||||
|
||||
if (_uniformSetPd[index] != sequence)
|
||||
{
|
||||
// Need to set this push descriptor (even if the buffer binding has not changed)
|
||||
|
||||
_uniformSetPd[index] = sequence;
|
||||
updatedBindings |= 1L << index;
|
||||
|
||||
writer.Push(MemoryMarshal.CreateReadOnlySpan(ref _uniformBuffers[index], 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (doUpdate)
|
||||
{
|
||||
ReadOnlySpan<DescriptorBufferInfo> uniformBuffers = _uniformBuffers;
|
||||
UpdateBuffers(cbs, pbp, binding, uniformBuffers.Slice(binding, count), DescriptorType.UniformBuffer);
|
||||
}
|
||||
if (updatedBindings > 0)
|
||||
{
|
||||
DescriptorSetTemplate template = _program.GetPushDescriptorTemplate(updatedBindings);
|
||||
_templateUpdater.CommitPushDescriptor(_gd, cbs, template, _program.PipelineLayout);
|
||||
}
|
||||
}
|
||||
|
||||
@ -724,6 +822,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
_uniformSet.Clear();
|
||||
_storageSet.Clear();
|
||||
AdvancePdSequence();
|
||||
}
|
||||
|
||||
private static void SwapBuffer(BufferRef[] list, Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
|
||||
|
@ -154,7 +154,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
int dispatchY = (height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
|
||||
|
||||
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) });
|
||||
_pipeline.SetImage(0, _intermediaryTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
|
||||
_pipeline.SetImage(ShaderStage.Compute, 0, _intermediaryTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
|
||||
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
||||
_pipeline.ComputeBarrier();
|
||||
|
||||
|
@ -75,7 +75,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize);
|
||||
var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize);
|
||||
|
||||
_pipeline.SetImage(0, _texture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
|
||||
_pipeline.SetImage(ShaderStage.Compute, 0, _texture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
|
||||
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
||||
|
||||
_pipeline.ComputeBarrier();
|
||||
|
@ -219,7 +219,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
|
||||
buffer.Holder.SetDataUnchecked(buffer.Offset, resolutionBuffer);
|
||||
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) });
|
||||
_pipeline.SetImage(0, _edgeOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
|
||||
_pipeline.SetImage(ShaderStage.Compute, 0, _edgeOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
|
||||
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
||||
_pipeline.ComputeBarrier();
|
||||
|
||||
@ -229,7 +229,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _edgeOutputTexture, _samplerLinear);
|
||||
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _areaTexture, _samplerLinear);
|
||||
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 4, _searchTexture, _samplerLinear);
|
||||
_pipeline.SetImage(0, _blendOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
|
||||
_pipeline.SetImage(ShaderStage.Compute, 0, _blendOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
|
||||
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
||||
_pipeline.ComputeBarrier();
|
||||
|
||||
@ -238,7 +238,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
_pipeline.Specialize(_specConstants);
|
||||
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _blendOutputTexture, _samplerLinear);
|
||||
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _samplerLinear);
|
||||
_pipeline.SetImage(0, _outputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
|
||||
_pipeline.SetImage(ShaderStage.Compute, 0, _outputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
|
||||
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
||||
_pipeline.ComputeBarrier();
|
||||
|
||||
|
@ -376,7 +376,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
Format.D16Unorm or Format.D32Float => ImageAspectFlags.DepthBit,
|
||||
Format.D16Unorm or Format.D32Float or Format.X8UintD24Unorm => ImageAspectFlags.DepthBit,
|
||||
Format.S8Uint => ImageAspectFlags.StencilBit,
|
||||
Format.D24UnormS8Uint or
|
||||
Format.D32FloatS8Uint or
|
||||
@ -389,7 +389,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
Format.D16Unorm or Format.D32Float => ImageAspectFlags.DepthBit,
|
||||
Format.D16Unorm or Format.D32Float or Format.X8UintD24Unorm => ImageAspectFlags.DepthBit,
|
||||
Format.S8Uint => ImageAspectFlags.StencilBit,
|
||||
Format.D24UnormS8Uint or
|
||||
Format.D32FloatS8Uint or
|
||||
|
@ -220,7 +220,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public static bool IsD24S8(Format format)
|
||||
{
|
||||
return format == Format.D24UnormS8Uint || format == Format.S8UintD24Unorm;
|
||||
return format == Format.D24UnormS8Uint || format == Format.S8UintD24Unorm || format == Format.X8UintD24Unorm;
|
||||
}
|
||||
|
||||
private static bool IsRGB16IntFloat(Format format)
|
||||
|
@ -67,6 +67,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Add(Format.S8Uint, VkFormat.S8Uint);
|
||||
Add(Format.D16Unorm, VkFormat.D16Unorm);
|
||||
Add(Format.S8UintD24Unorm, VkFormat.D24UnormS8Uint);
|
||||
Add(Format.X8UintD24Unorm, VkFormat.X8D24UnormPack32);
|
||||
Add(Format.D32Float, VkFormat.D32Sfloat);
|
||||
Add(Format.D24UnormS8Uint, VkFormat.D24UnormS8Uint);
|
||||
Add(Format.D32FloatS8Uint, VkFormat.D32SfloatS8Uint);
|
||||
|
@ -243,41 +243,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return new Auto<DisposableFramebuffer>(new DisposableFramebuffer(api, _device, framebuffer), null, _attachments);
|
||||
}
|
||||
|
||||
public void UpdateModifications()
|
||||
{
|
||||
if (_colors != null)
|
||||
{
|
||||
for (int index = 0; index < _colors.Length; index++)
|
||||
{
|
||||
_colors[index].Storage.SetModification(
|
||||
AccessFlags.ColorAttachmentWriteBit,
|
||||
PipelineStageFlags.ColorAttachmentOutputBit);
|
||||
}
|
||||
}
|
||||
|
||||
_depthStencil?.Storage.SetModification(
|
||||
AccessFlags.DepthStencilAttachmentWriteBit,
|
||||
PipelineStageFlags.LateFragmentTestsBit);
|
||||
}
|
||||
|
||||
public void InsertClearBarrier(CommandBufferScoped cbs, int index)
|
||||
{
|
||||
_colorsCanonical?[index]?.Storage?.InsertReadToWriteBarrier(
|
||||
cbs,
|
||||
AccessFlags.ColorAttachmentWriteBit,
|
||||
PipelineStageFlags.ColorAttachmentOutputBit,
|
||||
insideRenderPass: true);
|
||||
}
|
||||
|
||||
public void InsertClearBarrierDS(CommandBufferScoped cbs)
|
||||
{
|
||||
_depthStencil?.Storage?.InsertReadToWriteBarrier(
|
||||
cbs,
|
||||
AccessFlags.DepthStencilAttachmentWriteBit,
|
||||
PipelineStageFlags.LateFragmentTestsBit,
|
||||
insideRenderPass: true);
|
||||
}
|
||||
|
||||
public TextureView[] GetAttachmentViews()
|
||||
{
|
||||
var result = new TextureView[_attachments.Length];
|
||||
@ -297,23 +262,20 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return new RenderPassCacheKey(_depthStencil, _colorsCanonical);
|
||||
}
|
||||
|
||||
public void InsertLoadOpBarriers(CommandBufferScoped cbs)
|
||||
public void InsertLoadOpBarriers(VulkanRenderer gd, CommandBufferScoped cbs)
|
||||
{
|
||||
if (_colors != null)
|
||||
{
|
||||
foreach (var color in _colors)
|
||||
{
|
||||
// If Clear or DontCare were used, this would need to be write bit.
|
||||
color.Storage?.InsertWriteToReadBarrier(cbs, AccessFlags.ColorAttachmentReadBit, PipelineStageFlags.ColorAttachmentOutputBit);
|
||||
color.Storage?.SetModification(AccessFlags.ColorAttachmentWriteBit, PipelineStageFlags.ColorAttachmentOutputBit);
|
||||
color.Storage?.QueueLoadOpBarrier(cbs, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (_depthStencil != null)
|
||||
{
|
||||
_depthStencil.Storage?.InsertWriteToReadBarrier(cbs, AccessFlags.DepthStencilAttachmentReadBit, PipelineStageFlags.EarlyFragmentTestsBit);
|
||||
_depthStencil.Storage?.SetModification(AccessFlags.DepthStencilAttachmentWriteBit, PipelineStageFlags.LateFragmentTestsBit);
|
||||
}
|
||||
_depthStencil?.Storage?.QueueLoadOpBarrier(cbs, true);
|
||||
|
||||
gd.Barriers.Flush(cbs.CommandBuffer, false, null);
|
||||
}
|
||||
|
||||
public (Auto<DisposableRenderPass> renderPass, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
|
||||
|
@ -34,6 +34,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public readonly bool SupportsMultiView;
|
||||
public readonly bool SupportsNullDescriptors;
|
||||
public readonly bool SupportsPushDescriptors;
|
||||
public readonly uint MaxPushDescriptors;
|
||||
public readonly bool SupportsPrimitiveTopologyListRestart;
|
||||
public readonly bool SupportsPrimitiveTopologyPatchListRestart;
|
||||
public readonly bool SupportsTransformFeedback;
|
||||
@ -71,6 +72,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
bool supportsMultiView,
|
||||
bool supportsNullDescriptors,
|
||||
bool supportsPushDescriptors,
|
||||
uint maxPushDescriptors,
|
||||
bool supportsPrimitiveTopologyListRestart,
|
||||
bool supportsPrimitiveTopologyPatchListRestart,
|
||||
bool supportsTransformFeedback,
|
||||
@ -107,6 +109,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
SupportsMultiView = supportsMultiView;
|
||||
SupportsNullDescriptors = supportsNullDescriptors;
|
||||
SupportsPushDescriptors = supportsPushDescriptors;
|
||||
MaxPushDescriptors = maxPushDescriptors;
|
||||
SupportsPrimitiveTopologyListRestart = supportsPrimitiveTopologyListRestart;
|
||||
SupportsPrimitiveTopologyPatchListRestart = supportsPrimitiveTopologyPatchListRestart;
|
||||
SupportsTransformFeedback = supportsTransformFeedback;
|
||||
|
@ -1039,7 +1039,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
var dstView = Create2DLayerView(dst, dstLayer + z, dstLevel + l);
|
||||
|
||||
_pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Compute, 0, srcView, null);
|
||||
_pipeline.SetImage(0, dstView, dstFormat);
|
||||
_pipeline.SetImage(ShaderStage.Compute, 0, dstView, dstFormat);
|
||||
|
||||
int dispatchX = (Math.Min(srcView.Info.Width, dstView.Info.Width) + 31) / 32;
|
||||
int dispatchY = (Math.Min(srcView.Info.Height, dstView.Info.Height) + 31) / 32;
|
||||
@ -1168,7 +1168,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
var dstView = Create2DLayerView(dst, dstLayer + z, 0);
|
||||
|
||||
_pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Compute, 0, srcView, null);
|
||||
_pipeline.SetImage(0, dstView, format);
|
||||
_pipeline.SetImage(ShaderStage.Compute, 0, dstView, format);
|
||||
|
||||
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
||||
|
||||
|
@ -36,6 +36,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private PipelineState _newState;
|
||||
private bool _graphicsStateDirty;
|
||||
private bool _computeStateDirty;
|
||||
private bool _bindingBarriersDirty;
|
||||
private PrimitiveTopology _topology;
|
||||
|
||||
private ulong _currentPipelineHandle;
|
||||
@ -248,14 +249,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
CreateRenderPass();
|
||||
}
|
||||
|
||||
Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
|
||||
|
||||
BeginRenderPass();
|
||||
|
||||
var clearValue = new ClearValue(new ClearColorValue(color.Red, color.Green, color.Blue, color.Alpha));
|
||||
var attachment = new ClearAttachment(ImageAspectFlags.ColorBit, (uint)index, clearValue);
|
||||
var clearRect = FramebufferParams.GetClearRect(ClearScissor, layer, layerCount);
|
||||
|
||||
FramebufferParams.InsertClearBarrier(Cbs, index);
|
||||
|
||||
Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect);
|
||||
}
|
||||
|
||||
@ -286,13 +287,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
CreateRenderPass();
|
||||
}
|
||||
|
||||
Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
|
||||
|
||||
BeginRenderPass();
|
||||
|
||||
var attachment = new ClearAttachment(flags, 0, clearValue);
|
||||
var clearRect = FramebufferParams.GetClearRect(ClearScissor, layer, layerCount);
|
||||
|
||||
FramebufferParams.InsertClearBarrierDS(Cbs);
|
||||
|
||||
Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect);
|
||||
}
|
||||
|
||||
@ -887,9 +888,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
SignalStateChange();
|
||||
}
|
||||
|
||||
public void SetImage(int binding, ITexture image, Format imageFormat)
|
||||
public void SetImage(ShaderStage stage, int binding, ITexture image, Format imageFormat)
|
||||
{
|
||||
_descriptorSetUpdater.SetImage(binding, image, imageFormat);
|
||||
_descriptorSetUpdater.SetImage(Cbs, stage, binding, image, imageFormat);
|
||||
}
|
||||
|
||||
public void SetImage(int binding, Auto<DisposableImageView> image)
|
||||
@ -976,7 +977,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
_program = internalProgram;
|
||||
|
||||
_descriptorSetUpdater.SetProgram(internalProgram);
|
||||
_descriptorSetUpdater.SetProgram(Cbs, internalProgram, _currentPipelineHandle != 0);
|
||||
_bindingBarriersDirty = true;
|
||||
|
||||
_newState.PipelineLayout = internalProgram.PipelineLayout;
|
||||
_newState.StagesCount = (uint)stages.Length;
|
||||
@ -1066,7 +1068,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private void SetRenderTargetsInternal(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked)
|
||||
{
|
||||
CreateFramebuffer(colors, depthStencil, filterWriteMasked);
|
||||
FramebufferParams?.UpdateModifications();
|
||||
CreateRenderPass();
|
||||
SignalStateChange();
|
||||
SignalAttachmentChange();
|
||||
@ -1520,8 +1521,18 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
CreatePipeline(PipelineBindPoint.Compute);
|
||||
_computeStateDirty = false;
|
||||
Pbp = PipelineBindPoint.Compute;
|
||||
|
||||
if (_bindingBarriersDirty)
|
||||
{
|
||||
// Stale barriers may have been activated by switching program. Emit any that are relevant.
|
||||
_descriptorSetUpdater.InsertBindingBarriers(Cbs);
|
||||
|
||||
_bindingBarriersDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
|
||||
|
||||
_descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Compute);
|
||||
}
|
||||
|
||||
@ -1575,8 +1586,18 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
_graphicsStateDirty = false;
|
||||
Pbp = PipelineBindPoint.Graphics;
|
||||
|
||||
if (_bindingBarriersDirty)
|
||||
{
|
||||
// Stale barriers may have been activated by switching program. Emit any that are relevant.
|
||||
_descriptorSetUpdater.InsertBindingBarriers(Cbs);
|
||||
|
||||
_bindingBarriersDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
|
||||
|
||||
_descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Graphics);
|
||||
|
||||
return true;
|
||||
@ -1630,6 +1651,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
if (!RenderPassActive)
|
||||
{
|
||||
FramebufferParams.InsertLoadOpBarriers(Gd, Cbs);
|
||||
|
||||
var renderArea = new Rect2D(null, new Extent2D(FramebufferParams.Width, FramebufferParams.Height));
|
||||
var clearValue = new ClearValue();
|
||||
|
||||
|
@ -269,6 +269,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
PreloadCbs = null;
|
||||
}
|
||||
|
||||
Gd.Barriers.Flush(Cbs.CommandBuffer, false, null);
|
||||
CommandBuffer = (Cbs = Gd.CommandBufferPool.ReturnAndRent(Cbs)).CommandBuffer;
|
||||
Gd.RegisterFlush();
|
||||
|
||||
|
@ -31,6 +31,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private int _dsLastCbIndex;
|
||||
private int _dsLastSubmissionCount;
|
||||
|
||||
private readonly Dictionary<long, DescriptorSetTemplate> _pdTemplates;
|
||||
private readonly ResourceDescriptorCollection _pdDescriptors;
|
||||
private long _lastPdUsage;
|
||||
private DescriptorSetTemplate _lastPdTemplate;
|
||||
|
||||
private PipelineLayoutCacheEntry(VulkanRenderer gd, Device device, int setsCount)
|
||||
{
|
||||
_gd = gd;
|
||||
@ -72,6 +77,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
_consumedDescriptorsPerSet[setIndex] = count;
|
||||
}
|
||||
|
||||
if (usePushDescriptors)
|
||||
{
|
||||
_pdDescriptors = setDescriptors[0];
|
||||
_pdTemplates = new();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateCommandBufferIndex(int commandBufferIndex)
|
||||
@ -143,10 +154,39 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return output[..count];
|
||||
}
|
||||
|
||||
public DescriptorSetTemplate GetPushDescriptorTemplate(PipelineBindPoint pbp, long updateMask)
|
||||
{
|
||||
if (_lastPdUsage == updateMask && _lastPdTemplate != null)
|
||||
{
|
||||
// Most likely result is that it asks to update the same buffers.
|
||||
return _lastPdTemplate;
|
||||
}
|
||||
|
||||
if (!_pdTemplates.TryGetValue(updateMask, out DescriptorSetTemplate template))
|
||||
{
|
||||
template = new DescriptorSetTemplate(_gd, _device, _pdDescriptors, updateMask, this, pbp, 0);
|
||||
|
||||
_pdTemplates.Add(updateMask, template);
|
||||
}
|
||||
|
||||
_lastPdUsage = updateMask;
|
||||
_lastPdTemplate = template;
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
protected virtual unsafe void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (_pdTemplates != null)
|
||||
{
|
||||
foreach (DescriptorSetTemplate template in _pdTemplates.Values)
|
||||
{
|
||||
template.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < _dsCache.Length; i++)
|
||||
{
|
||||
for (int j = 0; j < _dsCache[i].Length; j++)
|
||||
|
@ -108,18 +108,26 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
_shaders = internalShaders;
|
||||
|
||||
bool usePushDescriptors = !isMinimal && VulkanConfiguration.UsePushDescriptors && _gd.Capabilities.SupportsPushDescriptors;
|
||||
bool usePushDescriptors = !isMinimal &&
|
||||
VulkanConfiguration.UsePushDescriptors &&
|
||||
_gd.Capabilities.SupportsPushDescriptors &&
|
||||
!_gd.IsNvidiaPreTuring &&
|
||||
!IsCompute &&
|
||||
CanUsePushDescriptors(gd, resourceLayout, IsCompute);
|
||||
|
||||
_plce = gd.PipelineLayoutCache.GetOrCreate(gd, device, resourceLayout.Sets, usePushDescriptors);
|
||||
ReadOnlyCollection<ResourceDescriptorCollection> sets = usePushDescriptors ?
|
||||
BuildPushDescriptorSets(gd, resourceLayout.Sets) : resourceLayout.Sets;
|
||||
|
||||
_plce = gd.PipelineLayoutCache.GetOrCreate(gd, device, sets, usePushDescriptors);
|
||||
|
||||
HasMinimalLayout = isMinimal;
|
||||
UsePushDescriptors = usePushDescriptors;
|
||||
|
||||
Stages = stages;
|
||||
|
||||
ClearSegments = BuildClearSegments(resourceLayout.Sets);
|
||||
ClearSegments = BuildClearSegments(sets);
|
||||
BindingSegments = BuildBindingSegments(resourceLayout.SetUsages);
|
||||
Templates = BuildTemplates();
|
||||
Templates = BuildTemplates(usePushDescriptors);
|
||||
|
||||
_compileTask = Task.CompletedTask;
|
||||
_firstBackgroundUse = false;
|
||||
@ -139,6 +147,76 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_firstBackgroundUse = !fromCache;
|
||||
}
|
||||
|
||||
private static bool CanUsePushDescriptors(VulkanRenderer gd, ResourceLayout layout, bool isCompute)
|
||||
{
|
||||
// If binding 3 is immediately used, use an alternate set of reserved bindings.
|
||||
ReadOnlyCollection<ResourceUsage> uniformUsage = layout.SetUsages[0].Usages;
|
||||
bool hasBinding3 = uniformUsage.Any(x => x.Binding == 3);
|
||||
int[] reserved = isCompute ? Array.Empty<int>() : gd.GetPushDescriptorReservedBindings(hasBinding3);
|
||||
|
||||
// Can't use any of the reserved usages.
|
||||
for (int i = 0; i < uniformUsage.Count; i++)
|
||||
{
|
||||
var binding = uniformUsage[i].Binding;
|
||||
|
||||
if (reserved.Contains(binding) ||
|
||||
binding >= Constants.MaxPushDescriptorBinding ||
|
||||
binding >= gd.Capabilities.MaxPushDescriptors + reserved.Count(id => id < binding))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static ReadOnlyCollection<ResourceDescriptorCollection> BuildPushDescriptorSets(
|
||||
VulkanRenderer gd,
|
||||
ReadOnlyCollection<ResourceDescriptorCollection> sets)
|
||||
{
|
||||
// The reserved bindings were selected when determining if push descriptors could be used.
|
||||
int[] reserved = gd.GetPushDescriptorReservedBindings(false);
|
||||
|
||||
var result = new ResourceDescriptorCollection[sets.Count];
|
||||
|
||||
for (int i = 0; i < sets.Count; i++)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
// Push descriptors apply here. Remove reserved bindings.
|
||||
ResourceDescriptorCollection original = sets[i];
|
||||
|
||||
var pdUniforms = new ResourceDescriptor[original.Descriptors.Count];
|
||||
int j = 0;
|
||||
|
||||
foreach (ResourceDescriptor descriptor in original.Descriptors)
|
||||
{
|
||||
if (reserved.Contains(descriptor.Binding))
|
||||
{
|
||||
// If the binding is reserved, set its descriptor count to 0.
|
||||
pdUniforms[j++] = new ResourceDescriptor(
|
||||
descriptor.Binding,
|
||||
0,
|
||||
descriptor.Type,
|
||||
descriptor.Stages);
|
||||
}
|
||||
else
|
||||
{
|
||||
pdUniforms[j++] = descriptor;
|
||||
}
|
||||
}
|
||||
|
||||
result[i] = new ResourceDescriptorCollection(new(pdUniforms));
|
||||
}
|
||||
else
|
||||
{
|
||||
result[i] = sets[i];
|
||||
}
|
||||
}
|
||||
|
||||
return new(result);
|
||||
}
|
||||
|
||||
private static ResourceBindingSegment[][] BuildClearSegments(ReadOnlyCollection<ResourceDescriptorCollection> sets)
|
||||
{
|
||||
ResourceBindingSegment[][] segments = new ResourceBindingSegment[sets.Count][];
|
||||
@ -243,12 +321,18 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return segments;
|
||||
}
|
||||
|
||||
private DescriptorSetTemplate[] BuildTemplates()
|
||||
private DescriptorSetTemplate[] BuildTemplates(bool usePushDescriptors)
|
||||
{
|
||||
var templates = new DescriptorSetTemplate[BindingSegments.Length];
|
||||
|
||||
for (int setIndex = 0; setIndex < BindingSegments.Length; setIndex++)
|
||||
{
|
||||
if (usePushDescriptors && setIndex == 0)
|
||||
{
|
||||
// Push descriptors get updated using templates owned by the pipeline layout.
|
||||
continue;
|
||||
}
|
||||
|
||||
ResourceBindingSegment[] segments = BindingSegments[setIndex];
|
||||
|
||||
if (segments != null && segments.Length > 0)
|
||||
@ -433,6 +517,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return null;
|
||||
}
|
||||
|
||||
public DescriptorSetTemplate GetPushDescriptorTemplate(long updateMask)
|
||||
{
|
||||
return _plce.GetPushDescriptorTemplate(IsCompute ? PipelineBindPoint.Compute : PipelineBindPoint.Graphics, updateMask);
|
||||
}
|
||||
|
||||
public void AddComputePipeline(ref SpecData key, Auto<DisposablePipeline> pipeline)
|
||||
{
|
||||
(_computePipelineCache ??= new()).Add(ref key, pipeline);
|
||||
@ -493,6 +582,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return _plce.GetNewDescriptorSetCollection(setIndex, out isNew);
|
||||
}
|
||||
|
||||
public bool HasSameLayout(ShaderCollection other)
|
||||
{
|
||||
return other != null && _plce == other._plce;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
|
@ -154,9 +154,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
Format.S8Uint => Format.R8Unorm,
|
||||
Format.D16Unorm => Format.R16Unorm,
|
||||
Format.S8UintD24Unorm => Format.R8G8B8A8Unorm,
|
||||
Format.D24UnormS8Uint or Format.S8UintD24Unorm or Format.X8UintD24Unorm => Format.R8G8B8A8Unorm,
|
||||
Format.D32Float => Format.R32Float,
|
||||
Format.D24UnormS8Uint => Format.R8G8B8A8Unorm,
|
||||
Format.D32FloatS8Uint => Format.R32G32Float,
|
||||
_ => throw new ArgumentException($"\"{format}\" is not a supported depth or stencil format."),
|
||||
};
|
||||
@ -434,99 +433,65 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return FormatCapabilities.IsD24S8(Info.Format) && VkFormat == VkFormat.D32SfloatS8Uint;
|
||||
}
|
||||
|
||||
public void SetModification(AccessFlags accessFlags, PipelineStageFlags stage)
|
||||
public void QueueLoadOpBarrier(CommandBufferScoped cbs, bool depthStencil)
|
||||
{
|
||||
_lastModificationAccess = accessFlags;
|
||||
_lastModificationStage = stage;
|
||||
}
|
||||
PipelineStageFlags srcStageFlags = _lastReadStage | _lastModificationStage;
|
||||
PipelineStageFlags dstStageFlags = depthStencil ?
|
||||
PipelineStageFlags.EarlyFragmentTestsBit | PipelineStageFlags.LateFragmentTestsBit :
|
||||
PipelineStageFlags.ColorAttachmentOutputBit;
|
||||
|
||||
public void InsertReadToWriteBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags, bool insideRenderPass)
|
||||
{
|
||||
var lastReadStage = _lastReadStage;
|
||||
AccessFlags srcAccessFlags = _lastModificationAccess | _lastReadAccess;
|
||||
AccessFlags dstAccessFlags = depthStencil ?
|
||||
AccessFlags.DepthStencilAttachmentWriteBit | AccessFlags.DepthStencilAttachmentReadBit :
|
||||
AccessFlags.ColorAttachmentWriteBit | AccessFlags.ColorAttachmentReadBit;
|
||||
|
||||
if (insideRenderPass)
|
||||
if (srcAccessFlags != AccessFlags.None)
|
||||
{
|
||||
// We can't have barrier from compute inside a render pass,
|
||||
// as it is invalid to specify compute in the subpass dependency stage mask.
|
||||
ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags();
|
||||
ImageMemoryBarrier barrier = TextureView.GetImageBarrier(
|
||||
_imageAuto.Get(cbs).Value,
|
||||
srcAccessFlags,
|
||||
dstAccessFlags,
|
||||
aspectFlags,
|
||||
0,
|
||||
0,
|
||||
_info.GetLayers(),
|
||||
_info.Levels);
|
||||
|
||||
lastReadStage &= ~PipelineStageFlags.ComputeShaderBit;
|
||||
}
|
||||
_gd.Barriers.QueueBarrier(barrier, srcStageFlags, dstStageFlags);
|
||||
|
||||
if (lastReadStage != PipelineStageFlags.None)
|
||||
{
|
||||
// This would result in a validation error, but is
|
||||
// required on MoltenVK as the generic barrier results in
|
||||
// severe texture flickering in some scenarios.
|
||||
if (_gd.IsMoltenVk)
|
||||
{
|
||||
ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags();
|
||||
TextureView.InsertImageBarrier(
|
||||
_gd.Api,
|
||||
cbs.CommandBuffer,
|
||||
_imageAuto.Get(cbs).Value,
|
||||
_lastReadAccess,
|
||||
dstAccessFlags,
|
||||
_lastReadStage,
|
||||
dstStageFlags,
|
||||
aspectFlags,
|
||||
0,
|
||||
0,
|
||||
_info.GetLayers(),
|
||||
_info.Levels);
|
||||
}
|
||||
else
|
||||
{
|
||||
TextureView.InsertMemoryBarrier(
|
||||
_gd.Api,
|
||||
cbs.CommandBuffer,
|
||||
_lastReadAccess,
|
||||
dstAccessFlags,
|
||||
lastReadStage,
|
||||
dstStageFlags);
|
||||
}
|
||||
|
||||
_lastReadAccess = AccessFlags.None;
|
||||
_lastReadStage = PipelineStageFlags.None;
|
||||
_lastReadAccess = AccessFlags.None;
|
||||
}
|
||||
|
||||
_lastModificationStage = depthStencil ?
|
||||
PipelineStageFlags.LateFragmentTestsBit :
|
||||
PipelineStageFlags.ColorAttachmentOutputBit;
|
||||
|
||||
_lastModificationAccess = depthStencil ?
|
||||
AccessFlags.DepthStencilAttachmentWriteBit :
|
||||
AccessFlags.ColorAttachmentWriteBit;
|
||||
}
|
||||
|
||||
public void InsertWriteToReadBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags)
|
||||
public void QueueWriteToReadBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags)
|
||||
{
|
||||
_lastReadAccess |= dstAccessFlags;
|
||||
_lastReadStage |= dstStageFlags;
|
||||
|
||||
if (_lastModificationAccess != AccessFlags.None)
|
||||
{
|
||||
// This would result in a validation error, but is
|
||||
// required on MoltenVK as the generic barrier results in
|
||||
// severe texture flickering in some scenarios.
|
||||
if (_gd.IsMoltenVk)
|
||||
{
|
||||
ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags();
|
||||
TextureView.InsertImageBarrier(
|
||||
_gd.Api,
|
||||
cbs.CommandBuffer,
|
||||
_imageAuto.Get(cbs).Value,
|
||||
_lastModificationAccess,
|
||||
dstAccessFlags,
|
||||
_lastModificationStage,
|
||||
dstStageFlags,
|
||||
aspectFlags,
|
||||
0,
|
||||
0,
|
||||
_info.GetLayers(),
|
||||
_info.Levels);
|
||||
}
|
||||
else
|
||||
{
|
||||
TextureView.InsertMemoryBarrier(
|
||||
_gd.Api,
|
||||
cbs.CommandBuffer,
|
||||
_lastModificationAccess,
|
||||
dstAccessFlags,
|
||||
_lastModificationStage,
|
||||
dstStageFlags);
|
||||
}
|
||||
ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags();
|
||||
ImageMemoryBarrier barrier = TextureView.GetImageBarrier(
|
||||
_imageAuto.Get(cbs).Value,
|
||||
_lastModificationAccess,
|
||||
dstAccessFlags,
|
||||
aspectFlags,
|
||||
0,
|
||||
0,
|
||||
_info.GetLayers(),
|
||||
_info.Levels);
|
||||
|
||||
_gd.Barriers.QueueBarrier(barrier, _lastModificationStage, dstStageFlags);
|
||||
|
||||
_lastModificationAccess = AccessFlags.None;
|
||||
}
|
||||
|
@ -497,6 +497,30 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
null);
|
||||
}
|
||||
|
||||
public static ImageMemoryBarrier GetImageBarrier(
|
||||
Image image,
|
||||
AccessFlags srcAccessMask,
|
||||
AccessFlags dstAccessMask,
|
||||
ImageAspectFlags aspectFlags,
|
||||
int firstLayer,
|
||||
int firstLevel,
|
||||
int layers,
|
||||
int levels)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
SType = StructureType.ImageMemoryBarrier,
|
||||
SrcAccessMask = srcAccessMask,
|
||||
DstAccessMask = dstAccessMask,
|
||||
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||
Image = image,
|
||||
OldLayout = ImageLayout.General,
|
||||
NewLayout = ImageLayout.General,
|
||||
SubresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, (uint)levels, (uint)firstLayer, (uint)layers),
|
||||
};
|
||||
}
|
||||
|
||||
public static unsafe void InsertImageBarrier(
|
||||
Vk api,
|
||||
CommandBuffer commandBuffer,
|
||||
@ -511,18 +535,15 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
int layers,
|
||||
int levels)
|
||||
{
|
||||
ImageMemoryBarrier memoryBarrier = new()
|
||||
{
|
||||
SType = StructureType.ImageMemoryBarrier,
|
||||
SrcAccessMask = srcAccessMask,
|
||||
DstAccessMask = dstAccessMask,
|
||||
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||
Image = image,
|
||||
OldLayout = ImageLayout.General,
|
||||
NewLayout = ImageLayout.General,
|
||||
SubresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, (uint)levels, (uint)firstLayer, (uint)layers),
|
||||
};
|
||||
ImageMemoryBarrier memoryBarrier = GetImageBarrier(
|
||||
image,
|
||||
srcAccessMask,
|
||||
dstAccessMask,
|
||||
aspectFlags,
|
||||
firstLayer,
|
||||
firstLevel,
|
||||
layers,
|
||||
levels);
|
||||
|
||||
api.CmdPipelineBarrier(
|
||||
commandBuffer,
|
||||
|
@ -20,6 +20,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
[GeneratedRegex("Radeon (((HD|R(5|7|9|X)) )?((M?[2-6]\\d{2}(\\D|$))|([7-8]\\d{3}(\\D|$))|Fury|Nano))|(Pro Duo)")]
|
||||
public static partial Regex AmdGcnRegex();
|
||||
|
||||
[GeneratedRegex("NVIDIA GeForce (R|G)?TX? (\\d{3}\\d?)M?")]
|
||||
public static partial Regex NvidiaConsumerClassRegex();
|
||||
|
||||
public static Vendor FromId(uint id)
|
||||
{
|
||||
return id switch
|
||||
|
@ -4,7 +4,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
public const bool UseFastBufferUpdates = true;
|
||||
public const bool UseUnsafeBlit = true;
|
||||
public const bool UsePushDescriptors = false;
|
||||
public const bool UsePushDescriptors = true;
|
||||
|
||||
public const bool ForceD24S8Unsupported = false;
|
||||
public const bool ForceRGB16IntFloatUnsupported = false;
|
||||
|
@ -486,20 +486,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
pExtendedFeatures = &featuresFragmentShaderInterlock;
|
||||
}
|
||||
|
||||
PhysicalDeviceSubgroupSizeControlFeaturesEXT featuresSubgroupSizeControl;
|
||||
|
||||
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_subgroup_size_control"))
|
||||
{
|
||||
featuresSubgroupSizeControl = new PhysicalDeviceSubgroupSizeControlFeaturesEXT
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceSubgroupSizeControlFeaturesExt,
|
||||
PNext = pExtendedFeatures,
|
||||
SubgroupSizeControl = true,
|
||||
};
|
||||
|
||||
pExtendedFeatures = &featuresSubgroupSizeControl;
|
||||
}
|
||||
|
||||
PhysicalDeviceCustomBorderColorFeaturesEXT featuresCustomBorderColor;
|
||||
|
||||
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_custom_border_color") &&
|
||||
|
@ -68,6 +68,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
internal HelperShader HelperShader { get; private set; }
|
||||
internal PipelineFull PipelineInternal => _pipeline;
|
||||
|
||||
internal BarrierBatch Barriers { get; private set; }
|
||||
|
||||
public IPipeline Pipeline => _pipeline;
|
||||
|
||||
public IWindow Window => _window;
|
||||
@ -76,10 +78,15 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private readonly Func<string[]> _getRequiredExtensions;
|
||||
private readonly string _preferredGpuId;
|
||||
|
||||
private int[] _pdReservedBindings;
|
||||
private readonly static int[] _pdReservedBindingsNvn = { 3, 18, 21, 36, 30 };
|
||||
private readonly static int[] _pdReservedBindingsOgl = { 17, 18, 34, 35, 36 };
|
||||
|
||||
internal Vendor Vendor { get; private set; }
|
||||
internal bool IsAmdWindows { get; private set; }
|
||||
internal bool IsIntelWindows { get; private set; }
|
||||
internal bool IsAmdGcn { get; private set; }
|
||||
internal bool IsNvidiaPreTuring { get; private set; }
|
||||
internal bool IsMoltenVk { get; private set; }
|
||||
internal bool IsTBDR { get; private set; }
|
||||
internal bool IsSharedMemory { get; private set; }
|
||||
@ -191,6 +198,19 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
SType = StructureType.PhysicalDevicePortabilitySubsetPropertiesKhr,
|
||||
};
|
||||
|
||||
bool supportsPushDescriptors = _physicalDevice.IsDeviceExtensionPresent(KhrPushDescriptor.ExtensionName);
|
||||
|
||||
PhysicalDevicePushDescriptorPropertiesKHR propertiesPushDescriptor = new PhysicalDevicePushDescriptorPropertiesKHR()
|
||||
{
|
||||
SType = StructureType.PhysicalDevicePushDescriptorPropertiesKhr
|
||||
};
|
||||
|
||||
if (supportsPushDescriptors)
|
||||
{
|
||||
propertiesPushDescriptor.PNext = properties2.PNext;
|
||||
properties2.PNext = &propertiesPushDescriptor;
|
||||
}
|
||||
|
||||
PhysicalDeviceFeatures2 features2 = new()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceFeatures2,
|
||||
@ -320,7 +340,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName),
|
||||
features2.Features.MultiViewport && !(IsMoltenVk && Vendor == Vendor.Amd), // Workaround for AMD on MoltenVK issue
|
||||
featuresRobustness2.NullDescriptor || IsMoltenVk,
|
||||
_physicalDevice.IsDeviceExtensionPresent(KhrPushDescriptor.ExtensionName),
|
||||
supportsPushDescriptors && !IsMoltenVk,
|
||||
propertiesPushDescriptor.MaxPushDescriptors,
|
||||
featuresPrimitiveTopologyListRestart.PrimitiveTopologyListRestart,
|
||||
featuresPrimitiveTopologyListRestart.PrimitiveTopologyPatchListRestart,
|
||||
supportsTransformFeedback,
|
||||
@ -362,6 +383,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
HelperShader = new HelperShader(this, _device);
|
||||
|
||||
Barriers = new BarrierBatch(this);
|
||||
|
||||
_counters = new Counters(this, _device, _pipeline);
|
||||
}
|
||||
|
||||
@ -400,6 +423,25 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
internal int[] GetPushDescriptorReservedBindings(bool isOgl)
|
||||
{
|
||||
// The first call of this method determines what push descriptor layout is used for all shaders on this renderer.
|
||||
// This is chosen to minimize shaders that can't fit their uniforms on the device's max number of push descriptors.
|
||||
if (_pdReservedBindings == null)
|
||||
{
|
||||
if (Capabilities.MaxPushDescriptors <= Constants.MaxUniformBuffersPerStage * 2)
|
||||
{
|
||||
_pdReservedBindings = isOgl ? _pdReservedBindingsOgl : _pdReservedBindingsNvn;
|
||||
}
|
||||
else
|
||||
{
|
||||
_pdReservedBindings = Array.Empty<int>();
|
||||
}
|
||||
}
|
||||
|
||||
return _pdReservedBindings;
|
||||
}
|
||||
|
||||
public BufferHandle CreateBuffer(int size, BufferAccess access)
|
||||
{
|
||||
return BufferManager.CreateWithHandle(this, size, access.HasFlag(BufferAccess.SparseCompatible), access.Convert(), default, access == BufferAccess.Stream);
|
||||
@ -716,6 +758,20 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
IsAmdGcn = !IsMoltenVk && Vendor == Vendor.Amd && VendorUtils.AmdGcnRegex().IsMatch(GpuRenderer);
|
||||
|
||||
if (Vendor == Vendor.Nvidia)
|
||||
{
|
||||
var match = VendorUtils.NvidiaConsumerClassRegex().Match(GpuRenderer);
|
||||
|
||||
if (match != null && int.TryParse(match.Groups[2].Value, out int gpuNumber))
|
||||
{
|
||||
IsNvidiaPreTuring = gpuNumber < 2000;
|
||||
}
|
||||
else if (GpuDriver.Contains("TITAN") && !GpuDriver.Contains("RTX"))
|
||||
{
|
||||
IsNvidiaPreTuring = true;
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})");
|
||||
}
|
||||
|
||||
@ -862,6 +918,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
BufferManager.Dispose();
|
||||
DescriptorSetManager.Dispose();
|
||||
PipelineLayoutCache.Dispose();
|
||||
Barriers.Dispose();
|
||||
|
||||
MemoryAllocator.Dispose();
|
||||
|
||||
|
@ -673,9 +673,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
MemoryState.UnmapProcessCodeMemoryAllowed,
|
||||
KMemoryPermission.None,
|
||||
KMemoryPermission.None,
|
||||
MemoryAttribute.Mask,
|
||||
MemoryAttribute.Mask & ~MemoryAttribute.PermissionLocked,
|
||||
MemoryAttribute.None,
|
||||
MemoryAttribute.IpcAndDeviceMapped | MemoryAttribute.PermissionLocked,
|
||||
MemoryAttribute.IpcAndDeviceMapped,
|
||||
out MemoryState state,
|
||||
out _,
|
||||
out _);
|
||||
|
@ -1821,5 +1821,18 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(1004)] // 17.0.0+
|
||||
// SetTouchScreenResolution(int width, int height, nn::applet::AppletResourceUserId)
|
||||
public ResultCode SetTouchScreenResolution(ServiceCtx context)
|
||||
{
|
||||
int width = context.RequestData.ReadInt32();
|
||||
int height = context.RequestData.ReadInt32();
|
||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { width, height, appletResourceUserId });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user