mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2025-07-26 10:27:11 +02:00

Metal sounded like a good idea to get in the emulator but frankly I underestimated just how experimental and not ready it was. From my write up in the Discord: ``` As is, Metal supports only a few games. The games it does support freeze on first use of not playing them via Vulkan, because shader translation is broken. So you need to use a dirty hack to not delete all your shaders. Not to mention it breaks many games via MoltenVK because of changes to the shared GPU code. Merging Metal seemed like a great idea, because of the few games it does support. But I don't think it's worth it. Many of the games it breaks via MoltenVK *don't work via Metal*. Which effectively makes current Ryubing worse for Mac users than Ryujinx 1.1.1403. I think what I'm gonna do is revert Metal, and reopen it as a PR. That way, you can still take advantage of the Metal backend as is, but without making other games worse with no solution. ``` For what it's worth, the shader translation part could at least be "fixed" by always applying a 30ms delay for shader translation to Metal. That being said, that solution sucks ass. The MoltenVK regressions are even worse. I hope this is not a let down to the Mac users. I hope you realize I'm reverting this because you're actively getting a worse experience with it in the emulator.
159 lines
5.2 KiB
C#
159 lines
5.2 KiB
C#
using CommandLine;
|
|
using Ryujinx.Graphics.Shader;
|
|
using Ryujinx.Graphics.Shader.Translation;
|
|
using System;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace Ryujinx.ShaderTools
|
|
{
|
|
class Program
|
|
{
|
|
private class GpuAccessor : IGpuAccessor
|
|
{
|
|
private const int DefaultArrayLength = 32;
|
|
|
|
private readonly byte[] _data;
|
|
|
|
private int _texturesCount;
|
|
private int _imagesCount;
|
|
|
|
public GpuAccessor(byte[] data)
|
|
{
|
|
_data = data;
|
|
_texturesCount = 0;
|
|
_imagesCount = 0;
|
|
}
|
|
|
|
public SetBindingPair CreateConstantBufferBinding(int index)
|
|
{
|
|
return new SetBindingPair(0, index + 1);
|
|
}
|
|
|
|
public SetBindingPair CreateImageBinding(int count, bool isBuffer)
|
|
{
|
|
int binding = _imagesCount;
|
|
|
|
_imagesCount += count;
|
|
|
|
return new SetBindingPair(3, binding);
|
|
}
|
|
|
|
public SetBindingPair CreateStorageBufferBinding(int index)
|
|
{
|
|
return new SetBindingPair(1, index);
|
|
}
|
|
|
|
public SetBindingPair CreateTextureBinding(int count, bool isBuffer)
|
|
{
|
|
int binding = _texturesCount;
|
|
|
|
_texturesCount += count;
|
|
|
|
return new SetBindingPair(2, binding);
|
|
}
|
|
|
|
public ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize)
|
|
{
|
|
return MemoryMarshal.Cast<byte, ulong>(new ReadOnlySpan<byte>(_data)[(int)address..]);
|
|
}
|
|
|
|
public int QuerySamplerArrayLengthFromPool()
|
|
{
|
|
return DefaultArrayLength;
|
|
}
|
|
|
|
public int QueryTextureArrayLengthFromBuffer(int slot)
|
|
{
|
|
return DefaultArrayLength;
|
|
}
|
|
|
|
public int QueryTextureArrayLengthFromPool()
|
|
{
|
|
return DefaultArrayLength;
|
|
}
|
|
}
|
|
|
|
private class Options
|
|
{
|
|
[Option("compute", Required = false, Default = false, HelpText = "Indicate that the shader is a compute shader.")]
|
|
public bool Compute { get; set; }
|
|
|
|
[Option("vertex-as-compute", Required = false, Default = false, HelpText = "Indicate that the shader is a vertex shader and should be converted to compute.")]
|
|
public bool VertexAsCompute { get; set; }
|
|
|
|
[Option("vertex-passthrough", Required = false, Default = false, HelpText = "Indicate that the shader is a vertex passthrough shader for compute output.")]
|
|
public bool VertexPassthrough { get; set; }
|
|
|
|
[Option("target-language", Required = false, Default = TargetLanguage.Glsl, HelpText = "Indicate the target shader language to use.")]
|
|
public TargetLanguage TargetLanguage { get; set; }
|
|
|
|
[Option("target-api", Required = false, Default = TargetApi.OpenGL, HelpText = "Indicate the target graphics api to use.")]
|
|
public TargetApi TargetApi { get; set; }
|
|
|
|
[Value(0, MetaName = "input", HelpText = "Binary Maxwell shader input path.", Required = true)]
|
|
public string InputPath { get; set; }
|
|
|
|
[Value(1, MetaName = "output", HelpText = "Decompiled shader output path.", Required = false)]
|
|
public string OutputPath { get; set; }
|
|
}
|
|
|
|
static void HandleArguments(Options options)
|
|
{
|
|
TranslationFlags flags = TranslationFlags.DebugMode;
|
|
|
|
if (options.Compute)
|
|
{
|
|
flags |= TranslationFlags.Compute;
|
|
}
|
|
|
|
byte[] data = File.ReadAllBytes(options.InputPath);
|
|
|
|
TranslationOptions translationOptions = new(options.TargetLanguage, options.TargetApi, flags);
|
|
TranslatorContext translatorContext = Translator.CreateContext(0, new GpuAccessor(data), translationOptions);
|
|
|
|
ShaderProgram program;
|
|
|
|
if (options.VertexPassthrough)
|
|
{
|
|
program = translatorContext.GenerateVertexPassthroughForCompute();
|
|
}
|
|
else
|
|
{
|
|
program = translatorContext.Translate(options.VertexAsCompute);
|
|
}
|
|
|
|
if (options.OutputPath == null)
|
|
{
|
|
if (program.BinaryCode != null)
|
|
{
|
|
using Stream outputStream = Console.OpenStandardOutput();
|
|
|
|
outputStream.Write(program.BinaryCode);
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine(program.Code);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (program.BinaryCode != null)
|
|
{
|
|
File.WriteAllBytes(options.OutputPath, program.BinaryCode);
|
|
}
|
|
else
|
|
{
|
|
File.WriteAllText(options.OutputPath, program.Code);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void Main(string[] args)
|
|
{
|
|
Parser.Default.ParseArguments<Options>(args)
|
|
.WithParsed(options => HandleArguments(options))
|
|
.WithNotParsed(errors => errors.Output());
|
|
}
|
|
}
|
|
}
|