Merge branch 'a32_tester_fpscr_followup' of https://github.com/LDj3SNuD/Ryujinx into a32_tester_fpscr_followup
This commit is contained in:
commit
5a91efb525
59 changed files with 1720 additions and 1006 deletions
|
@ -1,21 +1,23 @@
|
||||||
using ARMeilleure.IntermediateRepresentation;
|
using ARMeilleure.IntermediateRepresentation;
|
||||||
using ARMeilleure.Memory;
|
using ARMeilleure.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace ARMeilleure.State
|
namespace ARMeilleure.State
|
||||||
{
|
{
|
||||||
class NativeContext : IDisposable
|
class NativeContext : IDisposable
|
||||||
{
|
{
|
||||||
private const int IntSize = 8;
|
private unsafe struct NativeCtxStorage
|
||||||
private const int VecSize = 16;
|
{
|
||||||
private const int FlagSize = 4;
|
public fixed ulong X[RegisterConsts.IntRegsCount];
|
||||||
private const int ExtraSize = 8;
|
public fixed ulong V[RegisterConsts.VecRegsCount * 2];
|
||||||
|
public fixed uint Flags[RegisterConsts.FlagsCount];
|
||||||
|
public fixed uint FpFlags[RegisterConsts.FpFlagsCount];
|
||||||
|
public int Counter;
|
||||||
|
public ulong CallAddress;
|
||||||
|
}
|
||||||
|
|
||||||
private const int TotalSize = RegisterConsts.IntRegsCount * IntSize +
|
private static NativeCtxStorage _dummyStorage = new NativeCtxStorage();
|
||||||
RegisterConsts.VecRegsCount * VecSize +
|
|
||||||
RegisterConsts.FlagsCount * FlagSize +
|
|
||||||
RegisterConsts.FpFlagsCount * FlagSize + ExtraSize;
|
|
||||||
|
|
||||||
private readonly IJitMemoryBlock _block;
|
private readonly IJitMemoryBlock _block;
|
||||||
|
|
||||||
|
@ -23,179 +25,150 @@ namespace ARMeilleure.State
|
||||||
|
|
||||||
public NativeContext(IJitMemoryAllocator allocator)
|
public NativeContext(IJitMemoryAllocator allocator)
|
||||||
{
|
{
|
||||||
_block = allocator.Allocate(TotalSize);
|
_block = allocator.Allocate((ulong)Unsafe.SizeOf<NativeCtxStorage>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ulong GetX(int index)
|
public unsafe ulong GetX(int index)
|
||||||
{
|
{
|
||||||
if ((uint)index >= RegisterConsts.IntRegsCount)
|
if ((uint)index >= RegisterConsts.IntRegsCount)
|
||||||
{
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(index));
|
throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
return (ulong)Marshal.ReadInt64(BasePtr, index * IntSize);
|
return GetStorage().X[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetX(int index, ulong value)
|
public unsafe void SetX(int index, ulong value)
|
||||||
{
|
{
|
||||||
if ((uint)index >= RegisterConsts.IntRegsCount)
|
if ((uint)index >= RegisterConsts.IntRegsCount)
|
||||||
{
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(index));
|
throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
Marshal.WriteInt64(BasePtr, index * IntSize, (long)value);
|
GetStorage().X[index] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public V128 GetV(int index)
|
public unsafe V128 GetV(int index)
|
||||||
{
|
{
|
||||||
if ((uint)index >= RegisterConsts.IntRegsCount)
|
if ((uint)index >= RegisterConsts.VecRegsCount)
|
||||||
{
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(index));
|
throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
int offset = RegisterConsts.IntRegsCount * IntSize + index * VecSize;
|
return new V128(GetStorage().V[index * 2 + 0], GetStorage().V[index * 2 + 1]);
|
||||||
|
|
||||||
return new V128(
|
|
||||||
Marshal.ReadInt64(BasePtr, offset + 0),
|
|
||||||
Marshal.ReadInt64(BasePtr, offset + 8));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetV(int index, V128 value)
|
public unsafe void SetV(int index, V128 value)
|
||||||
{
|
{
|
||||||
if ((uint)index >= RegisterConsts.IntRegsCount)
|
if ((uint)index >= RegisterConsts.VecRegsCount)
|
||||||
{
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(index));
|
throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
int offset = RegisterConsts.IntRegsCount * IntSize + index * VecSize;
|
GetStorage().V[index * 2 + 0] = value.Extract<ulong>(0);
|
||||||
|
GetStorage().V[index * 2 + 1] = value.Extract<ulong>(1);
|
||||||
Marshal.WriteInt64(BasePtr, offset + 0, value.Extract<long>(0));
|
|
||||||
Marshal.WriteInt64(BasePtr, offset + 8, value.Extract<long>(1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool GetPstateFlag(PState flag)
|
public unsafe bool GetPstateFlag(PState flag)
|
||||||
{
|
{
|
||||||
if ((uint)flag >= RegisterConsts.FlagsCount)
|
if ((uint)flag >= RegisterConsts.FlagsCount)
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"Invalid flag \"{flag}\" specified.");
|
throw new ArgumentException($"Invalid flag \"{flag}\" specified.");
|
||||||
}
|
}
|
||||||
|
|
||||||
int offset =
|
return GetStorage().Flags[(int)flag] != 0;
|
||||||
RegisterConsts.IntRegsCount * IntSize +
|
|
||||||
RegisterConsts.VecRegsCount * VecSize + (int)flag * FlagSize;
|
|
||||||
|
|
||||||
int value = Marshal.ReadInt32(BasePtr, offset);
|
|
||||||
|
|
||||||
return value != 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetPstateFlag(PState flag, bool value)
|
public unsafe void SetPstateFlag(PState flag, bool value)
|
||||||
{
|
{
|
||||||
if ((uint)flag >= RegisterConsts.FlagsCount)
|
if ((uint)flag >= RegisterConsts.FlagsCount)
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"Invalid flag \"{flag}\" specified.");
|
throw new ArgumentException($"Invalid flag \"{flag}\" specified.");
|
||||||
}
|
}
|
||||||
|
|
||||||
int offset =
|
GetStorage().Flags[(int)flag] = value ? 1u : 0u;
|
||||||
RegisterConsts.IntRegsCount * IntSize +
|
|
||||||
RegisterConsts.VecRegsCount * VecSize + (int)flag * FlagSize;
|
|
||||||
|
|
||||||
Marshal.WriteInt32(BasePtr, offset, value ? 1 : 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool GetFPStateFlag(FPState flag)
|
public unsafe bool GetFPStateFlag(FPState flag)
|
||||||
{
|
{
|
||||||
if ((uint)flag >= RegisterConsts.FlagsCount)
|
if ((uint)flag >= RegisterConsts.FpFlagsCount)
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"Invalid flag \"{flag}\" specified.");
|
throw new ArgumentException($"Invalid flag \"{flag}\" specified.");
|
||||||
}
|
}
|
||||||
|
|
||||||
int offset =
|
return GetStorage().FpFlags[(int)flag] != 0;
|
||||||
RegisterConsts.IntRegsCount * IntSize +
|
|
||||||
RegisterConsts.VecRegsCount * VecSize +
|
|
||||||
RegisterConsts.FlagsCount * FlagSize + (int)flag * FlagSize;
|
|
||||||
|
|
||||||
int value = Marshal.ReadInt32(BasePtr, offset);
|
|
||||||
|
|
||||||
return value != 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetFPStateFlag(FPState flag, bool value)
|
public unsafe void SetFPStateFlag(FPState flag, bool value)
|
||||||
{
|
{
|
||||||
if ((uint)flag >= RegisterConsts.FlagsCount)
|
if ((uint)flag >= RegisterConsts.FpFlagsCount)
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"Invalid flag \"{flag}\" specified.");
|
throw new ArgumentException($"Invalid flag \"{flag}\" specified.");
|
||||||
}
|
}
|
||||||
|
|
||||||
int offset =
|
GetStorage().FpFlags[(int)flag] = value ? 1u : 0u;
|
||||||
RegisterConsts.IntRegsCount * IntSize +
|
|
||||||
RegisterConsts.VecRegsCount * VecSize +
|
|
||||||
RegisterConsts.FlagsCount * FlagSize + (int)flag * FlagSize;
|
|
||||||
|
|
||||||
Marshal.WriteInt32(BasePtr, offset, value ? 1 : 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetCounter()
|
public int GetCounter() => GetStorage().Counter;
|
||||||
{
|
public void SetCounter(int value) => GetStorage().Counter = value;
|
||||||
return Marshal.ReadInt32(BasePtr, GetCounterOffset());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetCounter(int value)
|
public unsafe static int GetRegisterOffset(Register reg)
|
||||||
{
|
{
|
||||||
Marshal.WriteInt32(BasePtr, GetCounterOffset(), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int GetRegisterOffset(Register reg)
|
|
||||||
{
|
|
||||||
int offset, size;
|
|
||||||
|
|
||||||
if (reg.Type == RegisterType.Integer)
|
if (reg.Type == RegisterType.Integer)
|
||||||
{
|
{
|
||||||
offset = reg.Index * IntSize;
|
if ((uint)reg.Index >= RegisterConsts.IntRegsCount)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid register.");
|
||||||
|
}
|
||||||
|
|
||||||
size = IntSize;
|
return StorageOffset(ref _dummyStorage, ref _dummyStorage.X[reg.Index]);
|
||||||
}
|
}
|
||||||
else if (reg.Type == RegisterType.Vector)
|
else if (reg.Type == RegisterType.Vector)
|
||||||
{
|
{
|
||||||
offset = RegisterConsts.IntRegsCount * IntSize + reg.Index * VecSize;
|
if ((uint)reg.Index >= RegisterConsts.VecRegsCount)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid register.");
|
||||||
|
}
|
||||||
|
|
||||||
size = VecSize;
|
return StorageOffset(ref _dummyStorage, ref _dummyStorage.V[reg.Index * 2]);
|
||||||
}
|
}
|
||||||
else /* if (reg.Type == RegisterType.Flag) */
|
else if (reg.Type == RegisterType.Flag)
|
||||||
{
|
{
|
||||||
offset = RegisterConsts.IntRegsCount * IntSize +
|
if ((uint)reg.Index >= RegisterConsts.FlagsCount)
|
||||||
RegisterConsts.VecRegsCount * VecSize + reg.Index * FlagSize;
|
{
|
||||||
|
throw new ArgumentException("Invalid register.");
|
||||||
|
}
|
||||||
|
|
||||||
size = FlagSize;
|
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Flags[reg.Index]);
|
||||||
}
|
}
|
||||||
|
else /* if (reg.Type == RegisterType.FpFlag) */
|
||||||
if ((uint)(offset + size) > (uint)TotalSize)
|
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Invalid register.");
|
if ((uint)reg.Index >= RegisterConsts.FpFlagsCount)
|
||||||
}
|
{
|
||||||
|
throw new ArgumentException("Invalid register.");
|
||||||
|
}
|
||||||
|
|
||||||
return offset;
|
return StorageOffset(ref _dummyStorage, ref _dummyStorage.FpFlags[reg.Index]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int GetCounterOffset()
|
public static int GetCounterOffset()
|
||||||
{
|
{
|
||||||
return RegisterConsts.IntRegsCount * IntSize +
|
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Counter);
|
||||||
RegisterConsts.VecRegsCount * VecSize +
|
|
||||||
RegisterConsts.FlagsCount * FlagSize +
|
|
||||||
RegisterConsts.FpFlagsCount * FlagSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int GetCallAddressOffset()
|
public static int GetCallAddressOffset()
|
||||||
{
|
{
|
||||||
return RegisterConsts.IntRegsCount * IntSize +
|
return StorageOffset(ref _dummyStorage, ref _dummyStorage.CallAddress);
|
||||||
RegisterConsts.VecRegsCount * VecSize +
|
|
||||||
RegisterConsts.FlagsCount * FlagSize +
|
|
||||||
RegisterConsts.FpFlagsCount * FlagSize + 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
private static int StorageOffset<T>(ref NativeCtxStorage storage, ref T target)
|
||||||
{
|
{
|
||||||
_block.Dispose();
|
return (int)Unsafe.ByteOffset(ref Unsafe.As<NativeCtxStorage, T>(ref storage), ref target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private unsafe ref NativeCtxStorage GetStorage() => ref Unsafe.AsRef<NativeCtxStorage>((void*)_block.Pointer);
|
||||||
|
|
||||||
|
public void Dispose() => _block.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -37,6 +37,7 @@ namespace Ryujinx.Common.Logging
|
||||||
ServiceMm,
|
ServiceMm,
|
||||||
ServiceNfp,
|
ServiceNfp,
|
||||||
ServiceNifm,
|
ServiceNifm,
|
||||||
|
ServiceNim,
|
||||||
ServiceNs,
|
ServiceNs,
|
||||||
ServiceNsd,
|
ServiceNsd,
|
||||||
ServiceNv,
|
ServiceNv,
|
||||||
|
|
|
@ -90,14 +90,12 @@ namespace Ryujinx.Graphics.Device
|
||||||
{
|
{
|
||||||
int alignedOffset = Align(offset);
|
int alignedOffset = Align(offset);
|
||||||
|
|
||||||
|
GetRef<int>(alignedOffset) = data;
|
||||||
|
|
||||||
if (_writeCallbacks.TryGetValue(alignedOffset, out Action<int> write))
|
if (_writeCallbacks.TryGetValue(alignedOffset, out Action<int> write))
|
||||||
{
|
{
|
||||||
write(data);
|
write(data);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
GetRef<int>(alignedOffset) = data;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -230,6 +230,25 @@ namespace Ryujinx.Graphics.GAL
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the texture format is a BGRA format with 8-bit components.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">Texture format</param>
|
||||||
|
/// <returns>True if the texture format is a BGRA format with 8-bit components, false otherwise</returns>
|
||||||
|
public static bool IsBgra8(this Format format)
|
||||||
|
{
|
||||||
|
switch (format)
|
||||||
|
{
|
||||||
|
case Format.B8G8R8X8Unorm:
|
||||||
|
case Format.B8G8R8A8Unorm:
|
||||||
|
case Format.B8G8R8X8Srgb:
|
||||||
|
case Format.B8G8R8A8Srgb:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if the texture format is a depth, stencil or depth-stencil format.
|
/// Checks if the texture format is a depth, stencil or depth-stencil format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -1,316 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// GPU DMA pusher, used to push commands to the GPU.
|
|
||||||
/// </summary>
|
|
||||||
public class DmaPusher
|
|
||||||
{
|
|
||||||
private ConcurrentQueue<CommandBuffer> _commandBufferQueue;
|
|
||||||
|
|
||||||
private enum CommandBufferType
|
|
||||||
{
|
|
||||||
Prefetch,
|
|
||||||
NoPrefetch,
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct CommandBuffer
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The type of the command buffer.
|
|
||||||
/// </summary>
|
|
||||||
public CommandBufferType Type;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fetched data.
|
|
||||||
/// </summary>
|
|
||||||
public int[] Words;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The GPFIFO entry address. (used in NoPrefetch mode)
|
|
||||||
/// </summary>
|
|
||||||
public ulong EntryAddress;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The count of entries inside this GPFIFO entry.
|
|
||||||
/// </summary>
|
|
||||||
public uint EntryCount;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fetch the command buffer.
|
|
||||||
/// </summary>
|
|
||||||
public void Fetch(GpuContext context)
|
|
||||||
{
|
|
||||||
if (Words == null)
|
|
||||||
{
|
|
||||||
Words = MemoryMarshal.Cast<byte, int>(context.MemoryAccessor.GetSpan(EntryAddress, (int)EntryCount * 4)).ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Read inside the command buffer.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context">The GPU context</param>
|
|
||||||
/// <param name="index">The index inside the command buffer</param>
|
|
||||||
/// <returns>The value read</returns>
|
|
||||||
public int ReadAt(GpuContext context, int index)
|
|
||||||
{
|
|
||||||
return Words[index];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private CommandBuffer _currentCommandBuffer;
|
|
||||||
private int _wordsPosition;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Internal GPFIFO state.
|
|
||||||
/// </summary>
|
|
||||||
private struct DmaState
|
|
||||||
{
|
|
||||||
public int Method;
|
|
||||||
public int SubChannel;
|
|
||||||
public int MethodCount;
|
|
||||||
public bool NonIncrementing;
|
|
||||||
public bool IncrementOnce;
|
|
||||||
public int LengthPending;
|
|
||||||
}
|
|
||||||
|
|
||||||
private DmaState _state;
|
|
||||||
|
|
||||||
private bool _ibEnable;
|
|
||||||
|
|
||||||
private GpuContext _context;
|
|
||||||
|
|
||||||
private AutoResetEvent _event;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the GPU DMA pusher.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context">GPU context that the pusher belongs to</param>
|
|
||||||
internal DmaPusher(GpuContext context)
|
|
||||||
{
|
|
||||||
_context = context;
|
|
||||||
|
|
||||||
_ibEnable = true;
|
|
||||||
|
|
||||||
_commandBufferQueue = new ConcurrentQueue<CommandBuffer>();
|
|
||||||
|
|
||||||
_event = new AutoResetEvent(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Signal the pusher that there are new entries to process.
|
|
||||||
/// </summary>
|
|
||||||
public void SignalNewEntries()
|
|
||||||
{
|
|
||||||
_event.Set();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Push a GPFIFO entry in the form of a prefetched command buffer.
|
|
||||||
/// It is intended to be used by nvservices to handle special cases.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="commandBuffer">The command buffer containing the prefetched commands</param>
|
|
||||||
public void PushHostCommandBuffer(int[] commandBuffer)
|
|
||||||
{
|
|
||||||
_commandBufferQueue.Enqueue(new CommandBuffer
|
|
||||||
{
|
|
||||||
Type = CommandBufferType.Prefetch,
|
|
||||||
Words = commandBuffer,
|
|
||||||
EntryAddress = ulong.MaxValue,
|
|
||||||
EntryCount = (uint)commandBuffer.Length
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a CommandBuffer from a GPFIFO entry.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="entry">The GPFIFO entry</param>
|
|
||||||
/// <returns>A new CommandBuffer based on the GPFIFO entry</returns>
|
|
||||||
private CommandBuffer CreateCommandBuffer(ulong entry)
|
|
||||||
{
|
|
||||||
ulong length = (entry >> 42) & 0x1fffff;
|
|
||||||
ulong startAddress = entry & 0xfffffffffc;
|
|
||||||
|
|
||||||
bool noPrefetch = (entry & (1UL << 63)) != 0;
|
|
||||||
|
|
||||||
CommandBufferType type = CommandBufferType.Prefetch;
|
|
||||||
|
|
||||||
if (noPrefetch)
|
|
||||||
{
|
|
||||||
type = CommandBufferType.NoPrefetch;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new CommandBuffer
|
|
||||||
{
|
|
||||||
Type = type,
|
|
||||||
Words = null,
|
|
||||||
EntryAddress = startAddress,
|
|
||||||
EntryCount = (uint)length
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Pushes GPFIFO entries.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="entries">GPFIFO entries</param>
|
|
||||||
public void PushEntries(ReadOnlySpan<ulong> entries)
|
|
||||||
{
|
|
||||||
bool beforeBarrier = true;
|
|
||||||
|
|
||||||
foreach (ulong entry in entries)
|
|
||||||
{
|
|
||||||
CommandBuffer commandBuffer = CreateCommandBuffer(entry);
|
|
||||||
|
|
||||||
if (beforeBarrier && commandBuffer.Type == CommandBufferType.Prefetch)
|
|
||||||
{
|
|
||||||
commandBuffer.Fetch(_context);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (commandBuffer.Type == CommandBufferType.NoPrefetch)
|
|
||||||
{
|
|
||||||
beforeBarrier = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_commandBufferQueue.Enqueue(commandBuffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Waits until commands are pushed to the FIFO.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if commands were received, false if wait timed out</returns>
|
|
||||||
public bool WaitForCommands()
|
|
||||||
{
|
|
||||||
return _event.WaitOne(8);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Processes commands pushed to the FIFO.
|
|
||||||
/// </summary>
|
|
||||||
public void DispatchCalls()
|
|
||||||
{
|
|
||||||
while (Step());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Processes a single command on the FIFO.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if the FIFO still has commands to be processed, false otherwise</returns>
|
|
||||||
private bool Step()
|
|
||||||
{
|
|
||||||
if (_wordsPosition != _currentCommandBuffer.EntryCount)
|
|
||||||
{
|
|
||||||
int word = _currentCommandBuffer.ReadAt(_context, _wordsPosition++);
|
|
||||||
|
|
||||||
if (_state.LengthPending != 0)
|
|
||||||
{
|
|
||||||
_state.LengthPending = 0;
|
|
||||||
_state.MethodCount = word & 0xffffff;
|
|
||||||
}
|
|
||||||
else if (_state.MethodCount != 0)
|
|
||||||
{
|
|
||||||
CallMethod(word);
|
|
||||||
|
|
||||||
if (!_state.NonIncrementing)
|
|
||||||
{
|
|
||||||
_state.Method++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_state.IncrementOnce)
|
|
||||||
{
|
|
||||||
_state.NonIncrementing = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
_state.MethodCount--;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int submissionMode = (word >> 29) & 7;
|
|
||||||
|
|
||||||
switch (submissionMode)
|
|
||||||
{
|
|
||||||
case 1:
|
|
||||||
// Incrementing.
|
|
||||||
SetNonImmediateState(word);
|
|
||||||
|
|
||||||
_state.NonIncrementing = false;
|
|
||||||
_state.IncrementOnce = false;
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
// Non-incrementing.
|
|
||||||
SetNonImmediateState(word);
|
|
||||||
|
|
||||||
_state.NonIncrementing = true;
|
|
||||||
_state.IncrementOnce = false;
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 4:
|
|
||||||
// Immediate.
|
|
||||||
_state.Method = (word >> 0) & 0x1fff;
|
|
||||||
_state.SubChannel = (word >> 13) & 7;
|
|
||||||
_state.NonIncrementing = true;
|
|
||||||
_state.IncrementOnce = false;
|
|
||||||
|
|
||||||
CallMethod((word >> 16) & 0x1fff);
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 5:
|
|
||||||
// Increment-once.
|
|
||||||
SetNonImmediateState(word);
|
|
||||||
|
|
||||||
_state.NonIncrementing = false;
|
|
||||||
_state.IncrementOnce = true;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (_ibEnable && _commandBufferQueue.TryDequeue(out CommandBuffer entry))
|
|
||||||
{
|
|
||||||
_currentCommandBuffer = entry;
|
|
||||||
_wordsPosition = 0;
|
|
||||||
|
|
||||||
_currentCommandBuffer.Fetch(_context);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets current non-immediate method call state.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="word">Compressed method word</param>
|
|
||||||
private void SetNonImmediateState(int word)
|
|
||||||
{
|
|
||||||
_state.Method = (word >> 0) & 0x1fff;
|
|
||||||
_state.SubChannel = (word >> 13) & 7;
|
|
||||||
_state.MethodCount = (word >> 16) & 0x1fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Forwards the method call to GPU engines.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="argument">Call argument</param>
|
|
||||||
private void CallMethod(int argument)
|
|
||||||
{
|
|
||||||
_context.Fifo.CallMethod(new MethodParams(
|
|
||||||
_state.Method,
|
|
||||||
argument,
|
|
||||||
_state.SubChannel,
|
|
||||||
_state.MethodCount));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,7 +18,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
||||||
{
|
{
|
||||||
uint qmdAddress = (uint)state.Get<int>(MethodOffset.DispatchParamsAddress);
|
uint qmdAddress = (uint)state.Get<int>(MethodOffset.DispatchParamsAddress);
|
||||||
|
|
||||||
var qmd = _context.MemoryAccessor.Read<ComputeQmd>((ulong)qmdAddress << 8);
|
var qmd = _context.MemoryManager.Read<ComputeQmd>((ulong)qmdAddress << 8);
|
||||||
|
|
||||||
GpuVa shaderBaseAddress = state.Get<GpuVa>(MethodOffset.ShaderBaseAddress);
|
GpuVa shaderBaseAddress = state.Get<GpuVa>(MethodOffset.ShaderBaseAddress);
|
||||||
|
|
||||||
|
|
39
Ryujinx.Graphics.Gpu/Engine/GPFifo/CompressedMethod.cs
Normal file
39
Ryujinx.Graphics.Gpu/Engine/GPFifo/CompressedMethod.cs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
// This file was auto-generated from NVIDIA official Maxwell definitions.
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
|
||||||
|
{
|
||||||
|
enum TertOp
|
||||||
|
{
|
||||||
|
Grp0IncMethod = 0,
|
||||||
|
Grp0SetSubDevMask = 1,
|
||||||
|
Grp0StoreSubDevMask = 2,
|
||||||
|
Grp0UseSubDevMask = 3,
|
||||||
|
Grp2NonIncMethod = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SecOp
|
||||||
|
{
|
||||||
|
Grp0UseTert = 0,
|
||||||
|
IncMethod = 1,
|
||||||
|
Grp2UseTert = 2,
|
||||||
|
NonIncMethod = 3,
|
||||||
|
ImmdDataMethod = 4,
|
||||||
|
OneInc = 5,
|
||||||
|
Reserved6 = 6,
|
||||||
|
EndPbSegment = 7
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CompressedMethod
|
||||||
|
{
|
||||||
|
public uint Method;
|
||||||
|
public int MethodAddressOld => (int)((Method >> 2) & 0x7FF);
|
||||||
|
public int MethodAddress => (int)((Method >> 0) & 0xFFF);
|
||||||
|
public int SubdeviceMask => (int)((Method >> 4) & 0xFFF);
|
||||||
|
public int MethodSubchannel => (int)((Method >> 13) & 0x7);
|
||||||
|
public TertOp TertOp => (TertOp)((Method >> 16) & 0x3);
|
||||||
|
public int MethodCountOld => (int)((Method >> 18) & 0x7FF);
|
||||||
|
public int MethodCount => (int)((Method >> 16) & 0x1FFF);
|
||||||
|
public int ImmdData => (int)((Method >> 16) & 0x1FFF);
|
||||||
|
public SecOp SecOp => (SecOp)((Method >> 29) & 0x7);
|
||||||
|
}
|
||||||
|
}
|
51
Ryujinx.Graphics.Gpu/Engine/GPFifo/GPEntry.cs
Normal file
51
Ryujinx.Graphics.Gpu/Engine/GPFifo/GPEntry.cs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
// This file was auto-generated from NVIDIA official Maxwell definitions.
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
|
||||||
|
{
|
||||||
|
enum Entry0Fetch
|
||||||
|
{
|
||||||
|
Unconditional = 0,
|
||||||
|
Conditional = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Entry1Priv
|
||||||
|
{
|
||||||
|
User = 0,
|
||||||
|
Kernel = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Entry1Level
|
||||||
|
{
|
||||||
|
Main = 0,
|
||||||
|
Subroutine = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Entry1Sync
|
||||||
|
{
|
||||||
|
Proceed = 0,
|
||||||
|
Wait = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Entry1Opcode
|
||||||
|
{
|
||||||
|
Nop = 0,
|
||||||
|
Illegal = 1,
|
||||||
|
Crc = 2,
|
||||||
|
PbCrc = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GPEntry
|
||||||
|
{
|
||||||
|
public uint Entry0;
|
||||||
|
public Entry0Fetch Entry0Fetch => (Entry0Fetch)((Entry0 >> 0) & 0x1);
|
||||||
|
public int Entry0Get => (int)((Entry0 >> 2) & 0x3FFFFFFF);
|
||||||
|
public int Entry0Operand => (int)(Entry0);
|
||||||
|
public uint Entry1;
|
||||||
|
public int Entry1GetHi => (int)((Entry1 >> 0) & 0xFF);
|
||||||
|
public Entry1Priv Entry1Priv => (Entry1Priv)((Entry1 >> 8) & 0x1);
|
||||||
|
public Entry1Level Entry1Level => (Entry1Level)((Entry1 >> 9) & 0x1);
|
||||||
|
public int Entry1Length => (int)((Entry1 >> 10) & 0x1FFFFF);
|
||||||
|
public Entry1Sync Entry1Sync => (Entry1Sync)((Entry1 >> 31) & 0x1);
|
||||||
|
public Entry1Opcode Entry1Opcode => (Entry1Opcode)((Entry1 >> 0) & 0xFF);
|
||||||
|
}
|
||||||
|
}
|
214
Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs
Normal file
214
Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
using Ryujinx.Graphics.Device;
|
||||||
|
using Ryujinx.Graphics.Gpu.Engine.MME;
|
||||||
|
using Ryujinx.Graphics.Gpu.State;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a GPU General Purpose FIFO class.
|
||||||
|
/// </summary>
|
||||||
|
class GPFifoClass : IDeviceState
|
||||||
|
{
|
||||||
|
private readonly GpuContext _context;
|
||||||
|
private readonly DeviceState<GPFifoClassState> _state;
|
||||||
|
|
||||||
|
private const int MacrosCount = 0x80;
|
||||||
|
|
||||||
|
// Note: The size of the macro memory is unknown, we just make
|
||||||
|
// a guess here and use 256kb as the size. Increase if needed.
|
||||||
|
private const int MacroCodeSize = 256 * 256;
|
||||||
|
|
||||||
|
private readonly Macro[] _macros;
|
||||||
|
private readonly int[] _macroCode;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MME Shadow RAM Control.
|
||||||
|
/// </summary>
|
||||||
|
public ShadowRamControl ShadowCtrl { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the GPU General Purpose FIFO class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">GPU context</param>
|
||||||
|
public GPFifoClass(GpuContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_state = new DeviceState<GPFifoClassState>(new Dictionary<string, RwCallback>
|
||||||
|
{
|
||||||
|
{ nameof(GPFifoClassState.Semaphored), new RwCallback(Semaphored, null) },
|
||||||
|
{ nameof(GPFifoClassState.Syncpointb), new RwCallback(Syncpointb, null) },
|
||||||
|
{ nameof(GPFifoClassState.WaitForIdle), new RwCallback(WaitForIdle, null) },
|
||||||
|
{ nameof(GPFifoClassState.LoadMmeInstructionRam), new RwCallback(LoadMmeInstructionRam, null) },
|
||||||
|
{ nameof(GPFifoClassState.LoadMmeStartAddressRam), new RwCallback(LoadMmeStartAddressRam, null) },
|
||||||
|
{ nameof(GPFifoClassState.SetMmeShadowRamControl), new RwCallback(SetMmeShadowRamControl, null) }
|
||||||
|
});
|
||||||
|
|
||||||
|
_macros = new Macro[MacrosCount];
|
||||||
|
_macroCode = new int[MacroCodeSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads data from the class registers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">Register byte offset</param>
|
||||||
|
/// <returns>Data at the specified offset</returns>
|
||||||
|
public int Read(int offset) => _state.Read(offset);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes data to the class registers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">Register byte offset</param>
|
||||||
|
/// <param name="data">Data to be written</param>
|
||||||
|
public void Write(int offset, int data) => _state.Write(offset, data);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a GPU counter to guest memory.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argument">Method call argument</param>
|
||||||
|
public void Semaphored(int argument)
|
||||||
|
{
|
||||||
|
ulong address = ((ulong)_state.State.SemaphorebOffsetLower << 2) |
|
||||||
|
((ulong)_state.State.SemaphoreaOffsetUpper << 32);
|
||||||
|
|
||||||
|
int value = _state.State.SemaphorecPayload;
|
||||||
|
|
||||||
|
SemaphoredOperation operation = _state.State.SemaphoredOperation;
|
||||||
|
|
||||||
|
// TODO: Acquire operations (Wait), interrupts for invalid combinations.
|
||||||
|
if (operation == SemaphoredOperation.Release)
|
||||||
|
{
|
||||||
|
_context.MemoryManager.Write(address, value);
|
||||||
|
}
|
||||||
|
else if (operation == SemaphoredOperation.Reduction)
|
||||||
|
{
|
||||||
|
bool signed = _state.State.SemaphoredFormat == SemaphoredFormat.Signed;
|
||||||
|
|
||||||
|
int mem = _context.MemoryManager.Read<int>(address);
|
||||||
|
|
||||||
|
switch (_state.State.SemaphoredReduction)
|
||||||
|
{
|
||||||
|
case SemaphoredReduction.Min:
|
||||||
|
value = signed ? Math.Min(mem, value) : (int)Math.Min((uint)mem, (uint)value);
|
||||||
|
break;
|
||||||
|
case SemaphoredReduction.Max:
|
||||||
|
value = signed ? Math.Max(mem, value) : (int)Math.Max((uint)mem, (uint)value);
|
||||||
|
break;
|
||||||
|
case SemaphoredReduction.Xor:
|
||||||
|
value ^= mem;
|
||||||
|
break;
|
||||||
|
case SemaphoredReduction.And:
|
||||||
|
value &= mem;
|
||||||
|
break;
|
||||||
|
case SemaphoredReduction.Or:
|
||||||
|
value |= mem;
|
||||||
|
break;
|
||||||
|
case SemaphoredReduction.Add:
|
||||||
|
value += mem;
|
||||||
|
break;
|
||||||
|
case SemaphoredReduction.Inc:
|
||||||
|
value = (uint)mem < (uint)value ? mem + 1 : 0;
|
||||||
|
break;
|
||||||
|
case SemaphoredReduction.Dec:
|
||||||
|
value = (uint)mem > 0 && (uint)mem <= (uint)value ? mem - 1 : value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.MemoryManager.Write(address, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Apply a fence operation on a syncpoint.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argument">Method call argument</param>
|
||||||
|
public void Syncpointb(int argument)
|
||||||
|
{
|
||||||
|
SyncpointbOperation operation = _state.State.SyncpointbOperation;
|
||||||
|
|
||||||
|
uint syncpointId = (uint)_state.State.SyncpointbSyncptIndex;
|
||||||
|
|
||||||
|
if (operation == SyncpointbOperation.Wait)
|
||||||
|
{
|
||||||
|
uint threshold = (uint)_state.State.SyncpointaPayload;
|
||||||
|
|
||||||
|
_context.Synchronization.WaitOnSyncpoint(syncpointId, threshold, Timeout.InfiniteTimeSpan);
|
||||||
|
}
|
||||||
|
else if (operation == SyncpointbOperation.Incr)
|
||||||
|
{
|
||||||
|
_context.Synchronization.IncrementSyncpoint(syncpointId);
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.AdvanceSequence();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Waits for the GPU to be idle.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argument">Method call argument</param>
|
||||||
|
public void WaitForIdle(int argument)
|
||||||
|
{
|
||||||
|
_context.Methods.PerformDeferredDraws();
|
||||||
|
_context.Renderer.Pipeline.Barrier();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send macro code/data to the MME
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argument">Method call argument</param>
|
||||||
|
public void LoadMmeInstructionRam(int argument)
|
||||||
|
{
|
||||||
|
_macroCode[_state.State.LoadMmeInstructionRamPointer++] = argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bind a macro index to a position for the MME
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argument">Method call argument</param>
|
||||||
|
public void LoadMmeStartAddressRam(int argument)
|
||||||
|
{
|
||||||
|
_macros[_state.State.LoadMmeStartAddressRamPointer++] = new Macro(argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Change the shadow RAM setting
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argument">Method call argument</param>
|
||||||
|
public void SetMmeShadowRamControl(int argument)
|
||||||
|
{
|
||||||
|
ShadowCtrl = (ShadowRamControl)argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pushes an argument to a macro.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">Index of the macro</param>
|
||||||
|
/// <param name="argument">Argument to be pushed to the macro</param>
|
||||||
|
public void MmePushArgument(int index, int argument)
|
||||||
|
{
|
||||||
|
_macros[index].PushArgument(argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Prepares a macro for execution.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">Index of the macro</param>
|
||||||
|
/// <param name="argument">Initial argument passed to the macro</param>
|
||||||
|
public void MmeStart(int index, int argument)
|
||||||
|
{
|
||||||
|
_macros[index].StartExecution(argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes a macro.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">Index of the macro</param>
|
||||||
|
/// <param name="state">Current GPU state</param>
|
||||||
|
public void CallMme(int index, GpuState state)
|
||||||
|
{
|
||||||
|
_macros[index].Execute(_macroCode, ShadowCtrl, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
186
Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClassState.cs
Normal file
186
Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClassState.cs
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
// This file was auto-generated from NVIDIA official Maxwell definitions.
|
||||||
|
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
|
||||||
|
{
|
||||||
|
enum SemaphoredOperation
|
||||||
|
{
|
||||||
|
Acquire = 1,
|
||||||
|
Release = 2,
|
||||||
|
AcqGeq = 4,
|
||||||
|
AcqAnd = 8,
|
||||||
|
Reduction = 16
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SemaphoredAcquireSwitch
|
||||||
|
{
|
||||||
|
Disabled = 0,
|
||||||
|
Enabled = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SemaphoredReleaseWfi
|
||||||
|
{
|
||||||
|
En = 0,
|
||||||
|
Dis = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SemaphoredReleaseSize
|
||||||
|
{
|
||||||
|
SixteenBytes = 0,
|
||||||
|
FourBytes = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SemaphoredReduction
|
||||||
|
{
|
||||||
|
Min = 0,
|
||||||
|
Max = 1,
|
||||||
|
Xor = 2,
|
||||||
|
And = 3,
|
||||||
|
Or = 4,
|
||||||
|
Add = 5,
|
||||||
|
Inc = 6,
|
||||||
|
Dec = 7
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SemaphoredFormat
|
||||||
|
{
|
||||||
|
Signed = 0,
|
||||||
|
Unsigned = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MemOpCTlbInvalidatePdb
|
||||||
|
{
|
||||||
|
One = 0,
|
||||||
|
All = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MemOpCTlbInvalidateGpc
|
||||||
|
{
|
||||||
|
Enable = 0,
|
||||||
|
Disable = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MemOpCTlbInvalidateTarget
|
||||||
|
{
|
||||||
|
VidMem = 0,
|
||||||
|
SysMemCoherent = 2,
|
||||||
|
SysMemNoncoherent = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MemOpDOperation
|
||||||
|
{
|
||||||
|
Membar = 5,
|
||||||
|
MmuTlbInvalidate = 9,
|
||||||
|
L2PeermemInvalidate = 13,
|
||||||
|
L2SysmemInvalidate = 14,
|
||||||
|
L2CleanComptags = 15,
|
||||||
|
L2FlushDirty = 16
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SyncpointbOperation
|
||||||
|
{
|
||||||
|
Wait = 0,
|
||||||
|
Incr = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SyncpointbWaitSwitch
|
||||||
|
{
|
||||||
|
Dis = 0,
|
||||||
|
En = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
enum WfiScope
|
||||||
|
{
|
||||||
|
CurrentScgType = 0,
|
||||||
|
All = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
enum YieldOp
|
||||||
|
{
|
||||||
|
Nop = 0,
|
||||||
|
PbdmaTimeslice = 1,
|
||||||
|
RunlistTimeslice = 2,
|
||||||
|
Tsg = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GPFifoClassState
|
||||||
|
{
|
||||||
|
public uint SetObject;
|
||||||
|
public int SetObjectNvclass => (int)((SetObject >> 0) & 0xFFFF);
|
||||||
|
public int SetObjectEngine => (int)((SetObject >> 16) & 0x1F);
|
||||||
|
public uint Illegal;
|
||||||
|
public int IllegalHandle => (int)(Illegal);
|
||||||
|
public uint Nop;
|
||||||
|
public int NopHandle => (int)(Nop);
|
||||||
|
public uint Reserved0C;
|
||||||
|
public uint Semaphorea;
|
||||||
|
public int SemaphoreaOffsetUpper => (int)((Semaphorea >> 0) & 0xFF);
|
||||||
|
public uint Semaphoreb;
|
||||||
|
public int SemaphorebOffsetLower => (int)((Semaphoreb >> 2) & 0x3FFFFFFF);
|
||||||
|
public uint Semaphorec;
|
||||||
|
public int SemaphorecPayload => (int)(Semaphorec);
|
||||||
|
public uint Semaphored;
|
||||||
|
public SemaphoredOperation SemaphoredOperation => (SemaphoredOperation)((Semaphored >> 0) & 0x1F);
|
||||||
|
public SemaphoredAcquireSwitch SemaphoredAcquireSwitch => (SemaphoredAcquireSwitch)((Semaphored >> 12) & 0x1);
|
||||||
|
public SemaphoredReleaseWfi SemaphoredReleaseWfi => (SemaphoredReleaseWfi)((Semaphored >> 20) & 0x1);
|
||||||
|
public SemaphoredReleaseSize SemaphoredReleaseSize => (SemaphoredReleaseSize)((Semaphored >> 24) & 0x1);
|
||||||
|
public SemaphoredReduction SemaphoredReduction => (SemaphoredReduction)((Semaphored >> 27) & 0xF);
|
||||||
|
public SemaphoredFormat SemaphoredFormat => (SemaphoredFormat)((Semaphored >> 31) & 0x1);
|
||||||
|
public uint NonStallInterrupt;
|
||||||
|
public int NonStallInterruptHandle => (int)(NonStallInterrupt);
|
||||||
|
public uint FbFlush;
|
||||||
|
public int FbFlushHandle => (int)(FbFlush);
|
||||||
|
public uint Reserved28;
|
||||||
|
public uint Reserved2C;
|
||||||
|
public uint MemOpC;
|
||||||
|
public int MemOpCOperandLow => (int)((MemOpC >> 2) & 0x3FFFFFFF);
|
||||||
|
public MemOpCTlbInvalidatePdb MemOpCTlbInvalidatePdb => (MemOpCTlbInvalidatePdb)((MemOpC >> 0) & 0x1);
|
||||||
|
public MemOpCTlbInvalidateGpc MemOpCTlbInvalidateGpc => (MemOpCTlbInvalidateGpc)((MemOpC >> 1) & 0x1);
|
||||||
|
public MemOpCTlbInvalidateTarget MemOpCTlbInvalidateTarget => (MemOpCTlbInvalidateTarget)((MemOpC >> 10) & 0x3);
|
||||||
|
public int MemOpCTlbInvalidateAddrLo => (int)((MemOpC >> 12) & 0xFFFFF);
|
||||||
|
public uint MemOpD;
|
||||||
|
public int MemOpDOperandHigh => (int)((MemOpD >> 0) & 0xFF);
|
||||||
|
public MemOpDOperation MemOpDOperation => (MemOpDOperation)((MemOpD >> 27) & 0x1F);
|
||||||
|
public int MemOpDTlbInvalidateAddrHi => (int)((MemOpD >> 0) & 0xFF);
|
||||||
|
public uint Reserved38;
|
||||||
|
public uint Reserved3C;
|
||||||
|
public uint Reserved40;
|
||||||
|
public uint Reserved44;
|
||||||
|
public uint Reserved48;
|
||||||
|
public uint Reserved4C;
|
||||||
|
public uint SetReference;
|
||||||
|
public int SetReferenceCount => (int)(SetReference);
|
||||||
|
public uint Reserved54;
|
||||||
|
public uint Reserved58;
|
||||||
|
public uint Reserved5C;
|
||||||
|
public uint Reserved60;
|
||||||
|
public uint Reserved64;
|
||||||
|
public uint Reserved68;
|
||||||
|
public uint Reserved6C;
|
||||||
|
public uint Syncpointa;
|
||||||
|
public int SyncpointaPayload => (int)(Syncpointa);
|
||||||
|
public uint Syncpointb;
|
||||||
|
public SyncpointbOperation SyncpointbOperation => (SyncpointbOperation)((Syncpointb >> 0) & 0x1);
|
||||||
|
public SyncpointbWaitSwitch SyncpointbWaitSwitch => (SyncpointbWaitSwitch)((Syncpointb >> 4) & 0x1);
|
||||||
|
public int SyncpointbSyncptIndex => (int)((Syncpointb >> 8) & 0xFFF);
|
||||||
|
public uint Wfi;
|
||||||
|
public WfiScope WfiScope => (WfiScope)((Wfi >> 0) & 0x1);
|
||||||
|
public uint CrcCheck;
|
||||||
|
public int CrcCheckValue => (int)(CrcCheck);
|
||||||
|
public uint Yield;
|
||||||
|
public YieldOp YieldOp => (YieldOp)((Yield >> 0) & 0x3);
|
||||||
|
// TODO: Eventually move this to per-engine state.
|
||||||
|
public Array31<uint> Reserved84;
|
||||||
|
public uint NoOperation;
|
||||||
|
public uint SetNotifyA;
|
||||||
|
public uint SetNotifyB;
|
||||||
|
public uint Notify;
|
||||||
|
public uint WaitForIdle;
|
||||||
|
public uint LoadMmeInstructionRamPointer;
|
||||||
|
public uint LoadMmeInstructionRam;
|
||||||
|
public uint LoadMmeStartAddressRamPointer;
|
||||||
|
public uint LoadMmeStartAddressRam;
|
||||||
|
public uint SetMmeShadowRamControl;
|
||||||
|
}
|
||||||
|
}
|
188
Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoDevice.cs
Normal file
188
Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoDevice.cs
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a GPU General Purpose FIFO device.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GPFifoDevice : IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates if the command buffer has pre-fetch enabled.
|
||||||
|
/// </summary>
|
||||||
|
private enum CommandBufferType
|
||||||
|
{
|
||||||
|
Prefetch,
|
||||||
|
NoPrefetch
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Command buffer data.
|
||||||
|
/// </summary>
|
||||||
|
private struct CommandBuffer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The type of the command buffer.
|
||||||
|
/// </summary>
|
||||||
|
public CommandBufferType Type;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fetched data.
|
||||||
|
/// </summary>
|
||||||
|
public int[] Words;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The GPFIFO entry address (used in <see cref="CommandBufferType.NoPrefetch"/> mode).
|
||||||
|
/// </summary>
|
||||||
|
public ulong EntryAddress;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The count of entries inside this GPFIFO entry.
|
||||||
|
/// </summary>
|
||||||
|
public uint EntryCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fetch the command buffer.
|
||||||
|
/// </summary>
|
||||||
|
public void Fetch(GpuContext context)
|
||||||
|
{
|
||||||
|
if (Words == null)
|
||||||
|
{
|
||||||
|
Words = MemoryMarshal.Cast<byte, int>(context.MemoryManager.GetSpan(EntryAddress, (int)EntryCount * 4)).ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly ConcurrentQueue<CommandBuffer> _commandBufferQueue;
|
||||||
|
|
||||||
|
private CommandBuffer _currentCommandBuffer;
|
||||||
|
|
||||||
|
private readonly bool _ibEnable;
|
||||||
|
private readonly GpuContext _context;
|
||||||
|
private readonly AutoResetEvent _event;
|
||||||
|
private readonly GPFifoProcessor _processor;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the GPU General Purpose FIFO device.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">GPU context that the GPFIFO belongs to</param>
|
||||||
|
internal GPFifoDevice(GpuContext context)
|
||||||
|
{
|
||||||
|
_commandBufferQueue = new ConcurrentQueue<CommandBuffer>();
|
||||||
|
_ibEnable = true;
|
||||||
|
_context = context;
|
||||||
|
_event = new AutoResetEvent(false);
|
||||||
|
|
||||||
|
_processor = new GPFifoProcessor(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signal the FIFO that there are new entries to process.
|
||||||
|
/// </summary>
|
||||||
|
public void SignalNewEntries()
|
||||||
|
{
|
||||||
|
_event.Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Push a GPFIFO entry in the form of a prefetched command buffer.
|
||||||
|
/// It is intended to be used by nvservices to handle special cases.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="commandBuffer">The command buffer containing the prefetched commands</param>
|
||||||
|
public void PushHostCommandBuffer(int[] commandBuffer)
|
||||||
|
{
|
||||||
|
_commandBufferQueue.Enqueue(new CommandBuffer
|
||||||
|
{
|
||||||
|
Type = CommandBufferType.Prefetch,
|
||||||
|
Words = commandBuffer,
|
||||||
|
EntryAddress = ulong.MaxValue,
|
||||||
|
EntryCount = (uint)commandBuffer.Length
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a CommandBuffer from a GPFIFO entry.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entry">The GPFIFO entry</param>
|
||||||
|
/// <returns>A new CommandBuffer based on the GPFIFO entry</returns>
|
||||||
|
private CommandBuffer CreateCommandBuffer(GPEntry entry)
|
||||||
|
{
|
||||||
|
CommandBufferType type = CommandBufferType.Prefetch;
|
||||||
|
|
||||||
|
if (entry.Entry1Sync == Entry1Sync.Wait)
|
||||||
|
{
|
||||||
|
type = CommandBufferType.NoPrefetch;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong startAddress = ((ulong)entry.Entry0Get << 2) | ((ulong)entry.Entry1GetHi << 32);
|
||||||
|
|
||||||
|
return new CommandBuffer
|
||||||
|
{
|
||||||
|
Type = type,
|
||||||
|
Words = null,
|
||||||
|
EntryAddress = startAddress,
|
||||||
|
EntryCount = (uint)entry.Entry1Length
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pushes GPFIFO entries.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entries">GPFIFO entries</param>
|
||||||
|
public void PushEntries(ReadOnlySpan<ulong> entries)
|
||||||
|
{
|
||||||
|
bool beforeBarrier = true;
|
||||||
|
|
||||||
|
for (int index = 0; index < entries.Length; index++)
|
||||||
|
{
|
||||||
|
ulong entry = entries[index];
|
||||||
|
|
||||||
|
CommandBuffer commandBuffer = CreateCommandBuffer(Unsafe.As<ulong, GPEntry>(ref entry));
|
||||||
|
|
||||||
|
if (beforeBarrier && commandBuffer.Type == CommandBufferType.Prefetch)
|
||||||
|
{
|
||||||
|
commandBuffer.Fetch(_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (commandBuffer.Type == CommandBufferType.NoPrefetch)
|
||||||
|
{
|
||||||
|
beforeBarrier = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_commandBufferQueue.Enqueue(commandBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Waits until commands are pushed to the FIFO.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if commands were received, false if wait timed out</returns>
|
||||||
|
public bool WaitForCommands()
|
||||||
|
{
|
||||||
|
return _event.WaitOne(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes commands pushed to the FIFO.
|
||||||
|
/// </summary>
|
||||||
|
public void DispatchCalls()
|
||||||
|
{
|
||||||
|
while (_ibEnable && _commandBufferQueue.TryDequeue(out CommandBuffer entry))
|
||||||
|
{
|
||||||
|
_currentCommandBuffer = entry;
|
||||||
|
_currentCommandBuffer.Fetch(_context);
|
||||||
|
|
||||||
|
_processor.Process(_currentCommandBuffer.Words);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disposes of resources used for GPFifo command processing.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose() => _event.Dispose();
|
||||||
|
}
|
||||||
|
}
|
179
Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs
Normal file
179
Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
using Ryujinx.Graphics.Gpu.State;
|
||||||
|
using System;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a GPU General Purpose FIFO command processor.
|
||||||
|
/// </summary>
|
||||||
|
class GPFifoProcessor
|
||||||
|
{
|
||||||
|
private const int MacrosCount = 0x80;
|
||||||
|
private const int MacroIndexMask = MacrosCount - 1;
|
||||||
|
|
||||||
|
private readonly GpuContext _context;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal GPFIFO state.
|
||||||
|
/// </summary>
|
||||||
|
private struct DmaState
|
||||||
|
{
|
||||||
|
public int Method;
|
||||||
|
public int SubChannel;
|
||||||
|
public int MethodCount;
|
||||||
|
public bool NonIncrementing;
|
||||||
|
public bool IncrementOnce;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DmaState _state;
|
||||||
|
|
||||||
|
private readonly GpuState[] _subChannels;
|
||||||
|
private readonly GPFifoClass _fifoClass;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the GPU General Purpose FIFO command processor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">GPU context</param>
|
||||||
|
public GPFifoProcessor(GpuContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
|
||||||
|
_fifoClass = new GPFifoClass(context);
|
||||||
|
|
||||||
|
_subChannels = new GpuState[8];
|
||||||
|
|
||||||
|
for (int index = 0; index < _subChannels.Length; index++)
|
||||||
|
{
|
||||||
|
_subChannels[index] = new GpuState();
|
||||||
|
|
||||||
|
_context.Methods.RegisterCallbacks(_subChannels[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes a command buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="commandBuffer">Command buffer</param>
|
||||||
|
public void Process(ReadOnlySpan<int> commandBuffer)
|
||||||
|
{
|
||||||
|
for (int index = 0; index < commandBuffer.Length; index++)
|
||||||
|
{
|
||||||
|
int command = commandBuffer[index];
|
||||||
|
|
||||||
|
if (_state.MethodCount != 0)
|
||||||
|
{
|
||||||
|
Send(new MethodParams(_state.Method, command, _state.SubChannel, _state.MethodCount));
|
||||||
|
|
||||||
|
if (!_state.NonIncrementing)
|
||||||
|
{
|
||||||
|
_state.Method++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_state.IncrementOnce)
|
||||||
|
{
|
||||||
|
_state.NonIncrementing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_state.MethodCount--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CompressedMethod meth = Unsafe.As<int, CompressedMethod>(ref command);
|
||||||
|
|
||||||
|
if (TryFastUniformBufferUpdate(meth, commandBuffer, index))
|
||||||
|
{
|
||||||
|
index += meth.MethodCount;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (meth.SecOp)
|
||||||
|
{
|
||||||
|
case SecOp.IncMethod:
|
||||||
|
case SecOp.NonIncMethod:
|
||||||
|
case SecOp.OneInc:
|
||||||
|
_state.Method = meth.MethodAddress;
|
||||||
|
_state.SubChannel = meth.MethodSubchannel;
|
||||||
|
_state.MethodCount = meth.MethodCount;
|
||||||
|
_state.IncrementOnce = meth.SecOp == SecOp.OneInc;
|
||||||
|
_state.NonIncrementing = meth.SecOp == SecOp.NonIncMethod;
|
||||||
|
break;
|
||||||
|
case SecOp.ImmdDataMethod:
|
||||||
|
Send(new MethodParams(meth.MethodAddress, meth.ImmdData, meth.MethodSubchannel, 1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to perform a fast constant buffer data update.
|
||||||
|
/// If successful, all data will be copied at once, and <see cref="CompressedMethod.MethodCount"/> + 1
|
||||||
|
/// command buffer entries will be consumed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="meth">Compressed method to be checked</param>
|
||||||
|
/// <param name="commandBuffer">Command buffer where <paramref name="meth"/> is contained</param>
|
||||||
|
/// <param name="offset">Offset at <paramref name="commandBuffer"/> where <paramref name="meth"/> is located</param>
|
||||||
|
/// <returns>True if the fast copy was successful, false otherwise</returns>
|
||||||
|
private bool TryFastUniformBufferUpdate(CompressedMethod meth, ReadOnlySpan<int> commandBuffer, int offset)
|
||||||
|
{
|
||||||
|
int availableCount = commandBuffer.Length - offset;
|
||||||
|
|
||||||
|
if (meth.MethodCount < availableCount &&
|
||||||
|
meth.SecOp == SecOp.NonIncMethod &&
|
||||||
|
meth.MethodAddress == (int)MethodOffset.UniformBufferUpdateData)
|
||||||
|
{
|
||||||
|
GpuState state = _subChannels[meth.MethodSubchannel];
|
||||||
|
|
||||||
|
_context.Methods.UniformBufferUpdate(state, commandBuffer.Slice(offset + 1, meth.MethodCount));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a uncompressed method for processing by the graphics pipeline.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="meth">Method to be processed</param>
|
||||||
|
private void Send(MethodParams meth)
|
||||||
|
{
|
||||||
|
if ((MethodOffset)meth.Method == MethodOffset.BindChannel)
|
||||||
|
{
|
||||||
|
_subChannels[meth.SubChannel] = new GpuState();
|
||||||
|
|
||||||
|
_context.Methods.RegisterCallbacks(_subChannels[meth.SubChannel]);
|
||||||
|
}
|
||||||
|
else if (meth.Method < 0x60)
|
||||||
|
{
|
||||||
|
// TODO: check if macros are shared between subchannels or not. For now let's assume they are.
|
||||||
|
_fifoClass.Write(meth.Method * 4, meth.Argument);
|
||||||
|
}
|
||||||
|
else if (meth.Method < 0xe00)
|
||||||
|
{
|
||||||
|
_subChannels[meth.SubChannel].CallMethod(meth, _fifoClass.ShadowCtrl);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int macroIndex = (meth.Method >> 1) & MacroIndexMask;
|
||||||
|
|
||||||
|
if ((meth.Method & 1) != 0)
|
||||||
|
{
|
||||||
|
_fifoClass.MmePushArgument(macroIndex, meth.Argument);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_fifoClass.MmeStart(macroIndex, meth.Argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (meth.IsLastCall)
|
||||||
|
{
|
||||||
|
_fifoClass.CallMme(macroIndex, _subChannels[meth.SubChannel]);
|
||||||
|
|
||||||
|
_context.Methods.PerformDeferredDraws();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
69
Ryujinx.Graphics.Gpu/Engine/MME/Macro.cs
Normal file
69
Ryujinx.Graphics.Gpu/Engine/MME/Macro.cs
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
using Ryujinx.Graphics.Gpu.State;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// GPU macro program.
|
||||||
|
/// </summary>
|
||||||
|
struct Macro
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Word offset of the code on the code memory.
|
||||||
|
/// </summary>
|
||||||
|
public int Position { get; }
|
||||||
|
|
||||||
|
private bool _executionPending;
|
||||||
|
private int _argument;
|
||||||
|
|
||||||
|
private readonly MacroInterpreter _interpreter;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the GPU cached macro program.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="position">Macro code start position</param>
|
||||||
|
public Macro(int position)
|
||||||
|
{
|
||||||
|
Position = position;
|
||||||
|
|
||||||
|
_executionPending = false;
|
||||||
|
_argument = 0;
|
||||||
|
|
||||||
|
_interpreter = new MacroInterpreter();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the first argument for the macro call.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argument">First argument</param>
|
||||||
|
public void StartExecution(int argument)
|
||||||
|
{
|
||||||
|
_argument = argument;
|
||||||
|
|
||||||
|
_executionPending = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts executing the macro program code.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mme">Program code</param>
|
||||||
|
/// <param name="state">Current GPU state</param>
|
||||||
|
public void Execute(int[] mme, ShadowRamControl shadowCtrl, GpuState state)
|
||||||
|
{
|
||||||
|
if (_executionPending)
|
||||||
|
{
|
||||||
|
_executionPending = false;
|
||||||
|
|
||||||
|
_interpreter?.Execute(mme, Position, _argument, shadowCtrl, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pushes an argument to the macro call argument FIFO.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argument">Argument to be pushed</param>
|
||||||
|
public void PushArgument(int argument)
|
||||||
|
{
|
||||||
|
_interpreter?.Fifo.Enqueue(argument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -63,7 +63,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
evt.Flush();
|
evt.Flush();
|
||||||
return (_context.MemoryAccessor.Read<ulong>(gpuVa) != 0) ? ConditionalRenderEnabled.True : ConditionalRenderEnabled.False;
|
return (_context.MemoryManager.Read<ulong>(gpuVa) != 0) ? ConditionalRenderEnabled.True : ConditionalRenderEnabled.False;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,11 +87,11 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
||||||
|
|
||||||
if (evt != null && evt2 == null)
|
if (evt != null && evt2 == null)
|
||||||
{
|
{
|
||||||
useHost = _context.Renderer.Pipeline.TryHostConditionalRendering(evt, _context.MemoryAccessor.Read<ulong>(gpuVa + 16), isEqual);
|
useHost = _context.Renderer.Pipeline.TryHostConditionalRendering(evt, _context.MemoryManager.Read<ulong>(gpuVa + 16), isEqual);
|
||||||
}
|
}
|
||||||
else if (evt == null && evt2 != null)
|
else if (evt == null && evt2 != null)
|
||||||
{
|
{
|
||||||
useHost = _context.Renderer.Pipeline.TryHostConditionalRendering(evt2, _context.MemoryAccessor.Read<ulong>(gpuVa), isEqual);
|
useHost = _context.Renderer.Pipeline.TryHostConditionalRendering(evt2, _context.MemoryManager.Read<ulong>(gpuVa), isEqual);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -107,8 +107,8 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
||||||
evt?.Flush();
|
evt?.Flush();
|
||||||
evt2?.Flush();
|
evt2?.Flush();
|
||||||
|
|
||||||
ulong x = _context.MemoryAccessor.Read<ulong>(gpuVa);
|
ulong x = _context.MemoryManager.Read<ulong>(gpuVa);
|
||||||
ulong y = _context.MemoryAccessor.Read<ulong>(gpuVa + 16);
|
ulong y = _context.MemoryManager.Read<ulong>(gpuVa + 16);
|
||||||
|
|
||||||
return (isEqual ? x == y : x != y) ? ConditionalRenderEnabled.True : ConditionalRenderEnabled.False;
|
return (isEqual ? x == y : x != y) ? ConditionalRenderEnabled.True : ConditionalRenderEnabled.False;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,103 +0,0 @@
|
||||||
using Ryujinx.Graphics.Gpu.State;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Engine
|
|
||||||
{
|
|
||||||
partial class Methods
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Writes a GPU counter to guest memory.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="state">Current GPU state</param>
|
|
||||||
/// <param name="argument">Method call argument</param>
|
|
||||||
public void Semaphore(GpuState state, int argument)
|
|
||||||
{
|
|
||||||
FifoSemaphoreOperation op = (FifoSemaphoreOperation)(argument & 3);
|
|
||||||
|
|
||||||
var semaphore = state.Get<SemaphoreState>(MethodOffset.Semaphore);
|
|
||||||
|
|
||||||
int value = semaphore.Payload;
|
|
||||||
|
|
||||||
if (op == FifoSemaphoreOperation.Counter)
|
|
||||||
{
|
|
||||||
// TODO: There's much more that should be done here.
|
|
||||||
// NVN only supports the "Accumulate" mode, so we
|
|
||||||
// can't currently guess which bits specify the
|
|
||||||
// reduction operation.
|
|
||||||
value += _context.MemoryAccessor.Read<int>(semaphore.Address.Pack());
|
|
||||||
}
|
|
||||||
|
|
||||||
_context.MemoryAccessor.Write(semaphore.Address.Pack(), value);
|
|
||||||
|
|
||||||
_context.AdvanceSequence();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Waits for the GPU to be idle.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="state">Current GPU state</param>
|
|
||||||
/// <param name="argument">Method call argument</param>
|
|
||||||
public void WaitForIdle(GpuState state, int argument)
|
|
||||||
{
|
|
||||||
PerformDeferredDraws();
|
|
||||||
|
|
||||||
_context.Renderer.Pipeline.Barrier();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Send macro code/data to the MME.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="state">Current GPU state</param>
|
|
||||||
/// <param name="argument">Method call argument</param>
|
|
||||||
public void SendMacroCodeData(GpuState state, int argument)
|
|
||||||
{
|
|
||||||
int macroUploadAddress = state.Get<int>(MethodOffset.MacroUploadAddress);
|
|
||||||
|
|
||||||
_context.Fifo.SendMacroCodeData(macroUploadAddress++, argument);
|
|
||||||
|
|
||||||
state.Write((int)MethodOffset.MacroUploadAddress, macroUploadAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bind a macro index to a position for the MME.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="state">Current GPU state</param>
|
|
||||||
/// <param name="argument">Method call argument</param>
|
|
||||||
public void BindMacro(GpuState state, int argument)
|
|
||||||
{
|
|
||||||
int macroBindingIndex = state.Get<int>(MethodOffset.MacroBindingIndex);
|
|
||||||
|
|
||||||
_context.Fifo.BindMacro(macroBindingIndex++, argument);
|
|
||||||
|
|
||||||
state.Write((int)MethodOffset.MacroBindingIndex, macroBindingIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetMmeShadowRamControl(GpuState state, int argument)
|
|
||||||
{
|
|
||||||
_context.Fifo.SetMmeShadowRamControl((ShadowRamControl)argument);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Apply a fence operation on a syncpoint.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="state">Current GPU state</param>
|
|
||||||
/// <param name="argument">Method call argument</param>
|
|
||||||
public void FenceAction(GpuState state, int argument)
|
|
||||||
{
|
|
||||||
uint threshold = state.Get<uint>(MethodOffset.FenceValue);
|
|
||||||
|
|
||||||
FenceActionOperation operation = (FenceActionOperation)(argument & 1);
|
|
||||||
|
|
||||||
uint syncpointId = (uint)(argument >> 8) & 0xFF;
|
|
||||||
|
|
||||||
if (operation == FenceActionOperation.Acquire)
|
|
||||||
{
|
|
||||||
_context.Synchronization.WaitOnSyncpoint(syncpointId, threshold, Timeout.InfiniteTimeSpan);
|
|
||||||
}
|
|
||||||
else if (operation == FenceActionOperation.Increment)
|
|
||||||
{
|
|
||||||
_context.Synchronization.IncrementSyncpoint(syncpointId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -39,7 +39,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
||||||
{
|
{
|
||||||
var rs = state.Get<SemaphoreState>(MethodOffset.ReportState);
|
var rs = state.Get<SemaphoreState>(MethodOffset.ReportState);
|
||||||
|
|
||||||
_context.MemoryAccessor.Write(rs.Address.Pack(), rs.Payload);
|
_context.MemoryManager.Write(rs.Address.Pack(), rs.Payload);
|
||||||
|
|
||||||
_context.AdvanceSequence();
|
_context.AdvanceSequence();
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
||||||
|
|
||||||
if (counter?.Invalid != true)
|
if (counter?.Invalid != true)
|
||||||
{
|
{
|
||||||
_context.MemoryAccessor.Write(gpuVa, counterData);
|
_context.MemoryManager.Write(gpuVa, counterData);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
using Ryujinx.Graphics.Gpu.State;
|
using Ryujinx.Graphics.Gpu.State;
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Engine
|
namespace Ryujinx.Graphics.Gpu.Engine
|
||||||
{
|
{
|
||||||
|
@ -13,11 +15,27 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
||||||
{
|
{
|
||||||
var uniformBuffer = state.Get<UniformBufferState>(MethodOffset.UniformBufferState);
|
var uniformBuffer = state.Get<UniformBufferState>(MethodOffset.UniformBufferState);
|
||||||
|
|
||||||
_context.MemoryAccessor.Write(uniformBuffer.Address.Pack() + (uint)uniformBuffer.Offset, argument);
|
_context.MemoryManager.Write(uniformBuffer.Address.Pack() + (uint)uniformBuffer.Offset, argument);
|
||||||
|
|
||||||
state.SetUniformBufferOffset(uniformBuffer.Offset + 4);
|
state.SetUniformBufferOffset(uniformBuffer.Offset + 4);
|
||||||
|
|
||||||
_context.AdvanceSequence();
|
_context.AdvanceSequence();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the uniform buffer data with inline data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state">Current GPU state</param>
|
||||||
|
/// <param name="data">Data to be written to the uniform buffer</param>
|
||||||
|
public void UniformBufferUpdate(GpuState state, ReadOnlySpan<int> data)
|
||||||
|
{
|
||||||
|
var uniformBuffer = state.Get<UniformBufferState>(MethodOffset.UniformBufferState);
|
||||||
|
|
||||||
|
_context.MemoryManager.Write(uniformBuffer.Address.Pack() + (uint)uniformBuffer.Offset, MemoryMarshal.Cast<int, byte>(data));
|
||||||
|
|
||||||
|
state.SetUniformBufferOffset(uniformBuffer.Offset + data.Length * 4);
|
||||||
|
|
||||||
|
_context.AdvanceSequence();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -106,20 +106,6 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
||||||
state.RegisterCallback(MethodOffset.UniformBufferBindFragment, UniformBufferBindFragment);
|
state.RegisterCallback(MethodOffset.UniformBufferBindFragment, UniformBufferBindFragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Register callback for Fifo method calls that triggers an action on the GPFIFO.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="state">GPU state where the triggers will be registered</param>
|
|
||||||
public void RegisterCallbacksForFifo(GpuState state)
|
|
||||||
{
|
|
||||||
state.RegisterCallback(MethodOffset.Semaphore, Semaphore);
|
|
||||||
state.RegisterCallback(MethodOffset.FenceAction, FenceAction);
|
|
||||||
state.RegisterCallback(MethodOffset.WaitForIdle, WaitForIdle);
|
|
||||||
state.RegisterCallback(MethodOffset.SendMacroCodeData, SendMacroCodeData);
|
|
||||||
state.RegisterCallback(MethodOffset.BindMacro, BindMacro);
|
|
||||||
state.RegisterCallback(MethodOffset.SetMmeShadowRamControl, SetMmeShadowRamControl);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates host state based on the current guest GPU state.
|
/// Updates host state based on the current guest GPU state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -582,7 +568,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
||||||
enables |= (depthBias.LineEnable ? PolygonModeMask.Line : 0);
|
enables |= (depthBias.LineEnable ? PolygonModeMask.Line : 0);
|
||||||
enables |= (depthBias.FillEnable ? PolygonModeMask.Fill : 0);
|
enables |= (depthBias.FillEnable ? PolygonModeMask.Fill : 0);
|
||||||
|
|
||||||
_context.Renderer.Pipeline.SetDepthBias(enables, factor, units, clamp);
|
_context.Renderer.Pipeline.SetDepthBias(enables, factor, units / 2f, clamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Gpu.Engine;
|
using Ryujinx.Graphics.Gpu.Engine;
|
||||||
|
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
||||||
using Ryujinx.Graphics.Gpu.Memory;
|
using Ryujinx.Graphics.Gpu.Memory;
|
||||||
using Ryujinx.Graphics.Gpu.Synchronization;
|
using Ryujinx.Graphics.Gpu.Synchronization;
|
||||||
using System;
|
using System;
|
||||||
|
@ -26,25 +27,15 @@ namespace Ryujinx.Graphics.Gpu
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MemoryManager MemoryManager { get; }
|
public MemoryManager MemoryManager { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// GPU memory accessor.
|
|
||||||
/// </summary>
|
|
||||||
public MemoryAccessor MemoryAccessor { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// GPU engine methods processing.
|
/// GPU engine methods processing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal Methods Methods { get; }
|
internal Methods Methods { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// GPU commands FIFO.
|
/// GPU General Purpose FIFO queue.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal NvGpuFifo Fifo { get; }
|
public GPFifoDevice GPFifo { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// DMA pusher.
|
|
||||||
/// </summary>
|
|
||||||
public DmaPusher DmaPusher { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// GPU synchronization manager.
|
/// GPU synchronization manager.
|
||||||
|
@ -79,13 +70,9 @@ namespace Ryujinx.Graphics.Gpu
|
||||||
|
|
||||||
MemoryManager = new MemoryManager(this);
|
MemoryManager = new MemoryManager(this);
|
||||||
|
|
||||||
MemoryAccessor = new MemoryAccessor(this);
|
|
||||||
|
|
||||||
Methods = new Methods(this);
|
Methods = new Methods(this);
|
||||||
|
|
||||||
Fifo = new NvGpuFifo(this);
|
GPFifo = new GPFifoDevice(this);
|
||||||
|
|
||||||
DmaPusher = new DmaPusher(this);
|
|
||||||
|
|
||||||
Synchronization = new SynchronizationManager();
|
Synchronization = new SynchronizationManager();
|
||||||
|
|
||||||
|
@ -125,6 +112,7 @@ namespace Ryujinx.Graphics.Gpu
|
||||||
Methods.BufferManager.Dispose();
|
Methods.BufferManager.Dispose();
|
||||||
Methods.TextureManager.Dispose();
|
Methods.TextureManager.Dispose();
|
||||||
Renderer.Dispose();
|
Renderer.Dispose();
|
||||||
|
GPFifo.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,85 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Memory
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// GPU mapped memory accessor.
|
|
||||||
/// </summary>
|
|
||||||
public class MemoryAccessor
|
|
||||||
{
|
|
||||||
private GpuContext _context;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the GPU memory accessor.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context">GPU context that the memory accessor belongs to</param>
|
|
||||||
public MemoryAccessor(GpuContext context)
|
|
||||||
{
|
|
||||||
_context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reads a byte array from GPU mapped memory.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="gpuVa">GPU virtual address where the data is located</param>
|
|
||||||
/// <param name="size">Size of the data in bytes</param>
|
|
||||||
/// <returns>Byte array with the data</returns>
|
|
||||||
public byte[] ReadBytes(ulong gpuVa, int size)
|
|
||||||
{
|
|
||||||
return GetSpan(gpuVa, size).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a read-only span of data from GPU mapped memory.
|
|
||||||
/// This reads as much data as possible, up to the specified maximum size.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="gpuVa">GPU virtual address where the data is located</param>
|
|
||||||
/// <param name="size">Size of the data</param>
|
|
||||||
/// <returns>The span of the data at the specified memory location</returns>
|
|
||||||
public ReadOnlySpan<byte> GetSpan(ulong gpuVa, int size)
|
|
||||||
{
|
|
||||||
ulong processVa = _context.MemoryManager.Translate(gpuVa);
|
|
||||||
|
|
||||||
return _context.PhysicalMemory.GetSpan(processVa, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reads data from GPU mapped memory.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">Type of the data</typeparam>
|
|
||||||
/// <param name="gpuVa">GPU virtual address where the data is located</param>
|
|
||||||
/// <returns>The data at the specified memory location</returns>
|
|
||||||
public T Read<T>(ulong gpuVa) where T : unmanaged
|
|
||||||
{
|
|
||||||
ulong processVa = _context.MemoryManager.Translate(gpuVa);
|
|
||||||
|
|
||||||
return MemoryMarshal.Cast<byte, T>(_context.PhysicalMemory.GetSpan(processVa, Unsafe.SizeOf<T>()))[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Writes a 32-bits signed integer to GPU mapped memory.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="gpuVa">GPU virtual address to write the value into</param>
|
|
||||||
/// <param name="value">The value to be written</param>
|
|
||||||
public void Write<T>(ulong gpuVa, T value) where T : unmanaged
|
|
||||||
{
|
|
||||||
ulong processVa = _context.MemoryManager.Translate(gpuVa);
|
|
||||||
|
|
||||||
_context.PhysicalMemory.Write(processVa, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Writes data to GPU mapped memory.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="gpuVa">GPU virtual address to write the data into</param>
|
|
||||||
/// <param name="data">The data to be written</param>
|
|
||||||
public void Write(ulong gpuVa, ReadOnlySpan<byte> data)
|
|
||||||
{
|
|
||||||
ulong processVa = _context.MemoryManager.Translate(gpuVa);
|
|
||||||
|
|
||||||
_context.PhysicalMemory.Write(processVa, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -62,7 +62,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a read-only span of data from GPU mapped memory.
|
/// Gets a read-only span of data from GPU mapped memory.
|
||||||
/// This reads as much data as possible, up to the specified maximum size.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="gpuVa">GPU virtual address where the data is located</param>
|
/// <param name="gpuVa">GPU virtual address where the data is located</param>
|
||||||
/// <param name="size">Size of the data</param>
|
/// <param name="size">Size of the data</param>
|
||||||
|
@ -87,6 +86,19 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
return _context.PhysicalMemory.GetWritableRegion(processVa, size);
|
return _context.PhysicalMemory.GetWritableRegion(processVa, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes data to GPU mapped memory.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Type of the data</typeparam>
|
||||||
|
/// <param name="gpuVa">GPU virtual address to write the value into</param>
|
||||||
|
/// <param name="value">The value to be written</param>
|
||||||
|
public void Write<T>(ulong gpuVa, T value) where T : unmanaged
|
||||||
|
{
|
||||||
|
ulong processVa = Translate(gpuVa);
|
||||||
|
|
||||||
|
_context.PhysicalMemory.Write(processVa, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1)));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes data to GPU mapped memory.
|
/// Writes data to GPU mapped memory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -1,220 +0,0 @@
|
||||||
using Ryujinx.Graphics.Gpu.State;
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// GPU commands FIFO.
|
|
||||||
/// </summary>
|
|
||||||
class NvGpuFifo
|
|
||||||
{
|
|
||||||
private const int MacrosCount = 0x80;
|
|
||||||
private const int MacroIndexMask = MacrosCount - 1;
|
|
||||||
|
|
||||||
// Note: The size of the macro memory is unknown, we just make
|
|
||||||
// a guess here and use 256kb as the size. Increase if needed.
|
|
||||||
private const int MmeWords = 256 * 256;
|
|
||||||
|
|
||||||
private GpuContext _context;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Cached GPU macro program.
|
|
||||||
/// </summary>
|
|
||||||
private struct CachedMacro
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Word offset of the code on the code memory.
|
|
||||||
/// </summary>
|
|
||||||
public int Position { get; }
|
|
||||||
|
|
||||||
private bool _executionPending;
|
|
||||||
private int _argument;
|
|
||||||
|
|
||||||
private MacroInterpreter _interpreter;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the GPU cached macro program.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="position">Macro code start position</param>
|
|
||||||
public CachedMacro(int position)
|
|
||||||
{
|
|
||||||
Position = position;
|
|
||||||
|
|
||||||
_executionPending = false;
|
|
||||||
_argument = 0;
|
|
||||||
|
|
||||||
_interpreter = new MacroInterpreter();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the first argument for the macro call.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="argument">First argument</param>
|
|
||||||
public void StartExecution(int argument)
|
|
||||||
{
|
|
||||||
_argument = argument;
|
|
||||||
|
|
||||||
_executionPending = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Starts executing the macro program code.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="mme">Program code</param>
|
|
||||||
/// <param name="state">Current GPU state</param>
|
|
||||||
public void Execute(int[] mme, ShadowRamControl shadowCtrl, GpuState state)
|
|
||||||
{
|
|
||||||
if (_executionPending)
|
|
||||||
{
|
|
||||||
_executionPending = false;
|
|
||||||
|
|
||||||
_interpreter?.Execute(mme, Position, _argument, shadowCtrl, state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Pushes an argument to the macro call argument FIFO.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="argument">Argument to be pushed</param>
|
|
||||||
public void PushArgument(int argument)
|
|
||||||
{
|
|
||||||
_interpreter?.Fifo.Enqueue(argument);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ShadowRamControl _shadowCtrl;
|
|
||||||
|
|
||||||
private CachedMacro[] _macros;
|
|
||||||
|
|
||||||
private int[] _mme;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// GPU sub-channel information.
|
|
||||||
/// </summary>
|
|
||||||
private class SubChannel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Sub-channel GPU state.
|
|
||||||
/// </summary>
|
|
||||||
public GpuState State { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Engine bound to the sub-channel.
|
|
||||||
/// </summary>
|
|
||||||
public ClassId Class { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the GPU sub-channel.
|
|
||||||
/// </summary>
|
|
||||||
public SubChannel()
|
|
||||||
{
|
|
||||||
State = new GpuState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private SubChannel[] _subChannels;
|
|
||||||
|
|
||||||
private SubChannel _fifoChannel;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the GPU commands FIFO.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context">GPU emulation context</param>
|
|
||||||
public NvGpuFifo(GpuContext context)
|
|
||||||
{
|
|
||||||
_context = context;
|
|
||||||
|
|
||||||
_macros = new CachedMacro[MacrosCount];
|
|
||||||
|
|
||||||
_mme = new int[MmeWords];
|
|
||||||
|
|
||||||
_fifoChannel = new SubChannel();
|
|
||||||
|
|
||||||
_context.Methods.RegisterCallbacksForFifo(_fifoChannel.State);
|
|
||||||
|
|
||||||
_subChannels = new SubChannel[8];
|
|
||||||
|
|
||||||
for (int index = 0; index < _subChannels.Length; index++)
|
|
||||||
{
|
|
||||||
_subChannels[index] = new SubChannel();
|
|
||||||
|
|
||||||
_context.Methods.RegisterCallbacks(_subChannels[index].State);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Send macro code/data to the MME
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">The index in the MME</param>
|
|
||||||
/// <param name="data">The data to use</param>
|
|
||||||
public void SendMacroCodeData(int index, int data)
|
|
||||||
{
|
|
||||||
_mme[index] = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bind a macro index to a position for the MME
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">The macro index</param>
|
|
||||||
/// <param name="position">The position of the macro</param>
|
|
||||||
public void BindMacro(int index, int position)
|
|
||||||
{
|
|
||||||
_macros[index] = new CachedMacro(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Change the shadow RAM setting
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="shadowCtrl">The new Shadow RAM setting</param>
|
|
||||||
public void SetMmeShadowRamControl(ShadowRamControl shadowCtrl)
|
|
||||||
{
|
|
||||||
_shadowCtrl = shadowCtrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calls a GPU method.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="meth">GPU method call parameters</param>
|
|
||||||
public void CallMethod(MethodParams meth)
|
|
||||||
{
|
|
||||||
if ((MethodOffset)meth.Method == MethodOffset.BindChannel)
|
|
||||||
{
|
|
||||||
_subChannels[meth.SubChannel] = new SubChannel
|
|
||||||
{
|
|
||||||
Class = (ClassId)meth.Argument
|
|
||||||
};
|
|
||||||
|
|
||||||
_context.Methods.RegisterCallbacks(_subChannels[meth.SubChannel].State);
|
|
||||||
}
|
|
||||||
else if (meth.Method < 0x60)
|
|
||||||
{
|
|
||||||
// TODO: check if macros are shared between subchannels or not. For now let's assume they are.
|
|
||||||
_fifoChannel.State.CallMethod(meth, _shadowCtrl);
|
|
||||||
}
|
|
||||||
else if (meth.Method < 0xe00)
|
|
||||||
{
|
|
||||||
_subChannels[meth.SubChannel].State.CallMethod(meth, _shadowCtrl);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int macroIndex = (meth.Method >> 1) & MacroIndexMask;
|
|
||||||
|
|
||||||
if ((meth.Method & 1) != 0)
|
|
||||||
{
|
|
||||||
_macros[macroIndex].PushArgument(meth.Argument);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_macros[macroIndex].StartExecution(meth.Argument);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (meth.IsLastCall)
|
|
||||||
{
|
|
||||||
_macros[macroIndex].Execute(_mme, _shadowCtrl, _subChannels[meth.SubChannel].State);
|
|
||||||
|
|
||||||
_context.Methods.PerformDeferredDraws();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Ryujinx.Cpu\Ryujinx.Cpu.csproj" />
|
<ProjectReference Include="..\Ryujinx.Cpu\Ryujinx.Cpu.csproj" />
|
||||||
|
<ProjectReference Include="..\Ryujinx.Graphics.Device\Ryujinx.Graphics.Device.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Graphics.GAL\Ryujinx.Graphics.GAL.csproj" />
|
<ProjectReference Include="..\Ryujinx.Graphics.GAL\Ryujinx.Graphics.GAL.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Graphics.Texture\Ryujinx.Graphics.Texture.csproj" />
|
<ProjectReference Include="..\Ryujinx.Graphics.Texture\Ryujinx.Graphics.Texture.csproj" />
|
||||||
|
|
|
@ -81,7 +81,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// <returns>Data at the memory location</returns>
|
/// <returns>Data at the memory location</returns>
|
||||||
public T MemoryRead<T>(ulong address) where T : unmanaged
|
public T MemoryRead<T>(ulong address) where T : unmanaged
|
||||||
{
|
{
|
||||||
return _context.MemoryAccessor.Read<T>(address);
|
return _context.MemoryManager.Read<T>(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -262,13 +262,13 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ReadOnlySpan<byte> memoryCode = _context.MemoryAccessor.GetSpan(gpuVa, shader.Code.Length);
|
ReadOnlySpan<byte> memoryCode = _context.MemoryManager.GetSpan(gpuVa, shader.Code.Length);
|
||||||
|
|
||||||
bool equals = memoryCode.SequenceEqual(shader.Code);
|
bool equals = memoryCode.SequenceEqual(shader.Code);
|
||||||
|
|
||||||
if (equals && shader.Code2 != null)
|
if (equals && shader.Code2 != null)
|
||||||
{
|
{
|
||||||
memoryCode = _context.MemoryAccessor.GetSpan(gpuVaA, shader.Code2.Length);
|
memoryCode = _context.MemoryManager.GetSpan(gpuVaA, shader.Code2.Length);
|
||||||
|
|
||||||
equals = memoryCode.SequenceEqual(shader.Code2);
|
equals = memoryCode.SequenceEqual(shader.Code2);
|
||||||
}
|
}
|
||||||
|
@ -307,7 +307,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
|
|
||||||
program = Translator.Translate(gpuVa, gpuAccessor, DefaultFlags | TranslationFlags.Compute);
|
program = Translator.Translate(gpuVa, gpuAccessor, DefaultFlags | TranslationFlags.Compute);
|
||||||
|
|
||||||
byte[] code = _context.MemoryAccessor.ReadBytes(gpuVa, program.Size);
|
byte[] code = _context.MemoryManager.GetSpan(gpuVa, program.Size).ToArray();
|
||||||
|
|
||||||
_dumper.Dump(code, compute: true, out string fullPath, out string codePath);
|
_dumper.Dump(code, compute: true, out string fullPath, out string codePath);
|
||||||
|
|
||||||
|
@ -344,8 +344,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
{
|
{
|
||||||
ShaderProgram program = Translator.Translate(gpuVaA, gpuVa, gpuAccessor, DefaultFlags);
|
ShaderProgram program = Translator.Translate(gpuVaA, gpuVa, gpuAccessor, DefaultFlags);
|
||||||
|
|
||||||
byte[] codeA = _context.MemoryAccessor.ReadBytes(gpuVaA, program.SizeA);
|
byte[] codeA = _context.MemoryManager.GetSpan(gpuVaA, program.SizeA).ToArray();
|
||||||
byte[] codeB = _context.MemoryAccessor.ReadBytes(gpuVa, program.Size);
|
byte[] codeB = _context.MemoryManager.GetSpan(gpuVa, program.Size).ToArray();
|
||||||
|
|
||||||
_dumper.Dump(codeA, compute: false, out string fullPathA, out string codePathA);
|
_dumper.Dump(codeA, compute: false, out string fullPathA, out string codePathA);
|
||||||
_dumper.Dump(codeB, compute: false, out string fullPathB, out string codePathB);
|
_dumper.Dump(codeB, compute: false, out string fullPathB, out string codePathB);
|
||||||
|
@ -364,7 +364,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
{
|
{
|
||||||
ShaderProgram program = Translator.Translate(gpuVa, gpuAccessor, DefaultFlags);
|
ShaderProgram program = Translator.Translate(gpuVa, gpuAccessor, DefaultFlags);
|
||||||
|
|
||||||
byte[] code = _context.MemoryAccessor.ReadBytes(gpuVa, program.Size);
|
byte[] code = _context.MemoryManager.GetSpan(gpuVa, program.Size).ToArray();
|
||||||
|
|
||||||
_dumper.Dump(code, compute: false, out string fullPath, out string codePath);
|
_dumper.Dump(code, compute: false, out string fullPath, out string codePath);
|
||||||
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
namespace Ryujinx.Graphics.Gpu.State
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Fence action operations.
|
|
||||||
/// </summary>
|
|
||||||
enum FenceActionOperation
|
|
||||||
{
|
|
||||||
Acquire = 0,
|
|
||||||
Increment = 1
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
namespace Ryujinx.Graphics.Gpu.State
|
|
||||||
{
|
|
||||||
enum FifoSemaphoreOperation
|
|
||||||
{
|
|
||||||
Counter = 0,
|
|
||||||
Acquire = 1,
|
|
||||||
Release = 2
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,15 +9,6 @@ namespace Ryujinx.Graphics.Gpu.State
|
||||||
enum MethodOffset
|
enum MethodOffset
|
||||||
{
|
{
|
||||||
BindChannel = 0x0,
|
BindChannel = 0x0,
|
||||||
Semaphore = 0x4,
|
|
||||||
FenceValue = 0x1c,
|
|
||||||
FenceAction = 0x1d,
|
|
||||||
WaitForIdle = 0x44,
|
|
||||||
MacroUploadAddress = 0x45,
|
|
||||||
SendMacroCodeData = 0x46,
|
|
||||||
MacroBindingIndex = 0x47,
|
|
||||||
BindMacro = 0x48,
|
|
||||||
SetMmeShadowRamControl = 0x49,
|
|
||||||
I2mParams = 0x60,
|
I2mParams = 0x60,
|
||||||
LaunchDma = 0x6c,
|
LaunchDma = 0x6c,
|
||||||
LoadInlineData = 0x6d,
|
LoadInlineData = 0x6d,
|
||||||
|
|
|
@ -164,10 +164,10 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
Add(Format.B5G5R5X1Unorm, new FormatInfo(4, true, false, All.Rgb5, PixelFormat.Bgra, PixelType.UnsignedShort5551));
|
Add(Format.B5G5R5X1Unorm, new FormatInfo(4, true, false, All.Rgb5, PixelFormat.Bgra, PixelType.UnsignedShort5551));
|
||||||
Add(Format.B5G5R5A1Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Bgra, PixelType.UnsignedShort5551));
|
Add(Format.B5G5R5A1Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Bgra, PixelType.UnsignedShort5551));
|
||||||
Add(Format.A1B5G5R5Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Bgra, PixelType.UnsignedShort1555Reversed));
|
Add(Format.A1B5G5R5Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Bgra, PixelType.UnsignedShort1555Reversed));
|
||||||
Add(Format.B8G8R8X8Unorm, new FormatInfo(4, true, false, All.Rgba8, PixelFormat.Bgra, PixelType.UnsignedByte));
|
Add(Format.B8G8R8X8Unorm, new FormatInfo(4, true, false, All.Rgba8, PixelFormat.Rgba, PixelType.UnsignedByte));
|
||||||
Add(Format.B8G8R8A8Unorm, new FormatInfo(4, true, false, All.Rgba8, PixelFormat.Bgra, PixelType.UnsignedByte));
|
Add(Format.B8G8R8A8Unorm, new FormatInfo(4, true, false, All.Rgba8, PixelFormat.Rgba, PixelType.UnsignedByte));
|
||||||
Add(Format.B8G8R8X8Srgb, new FormatInfo(4, false, false, All.Srgb8, PixelFormat.BgraInteger, PixelType.UnsignedByte));
|
Add(Format.B8G8R8X8Srgb, new FormatInfo(4, false, false, All.Srgb8, PixelFormat.Rgba, PixelType.UnsignedByte));
|
||||||
Add(Format.B8G8R8A8Srgb, new FormatInfo(4, false, false, All.Srgb8Alpha8, PixelFormat.BgraInteger, PixelType.UnsignedByte));
|
Add(Format.B8G8R8A8Srgb, new FormatInfo(4, false, false, All.Srgb8Alpha8, PixelFormat.Rgba, PixelType.UnsignedByte));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Add(Format format, FormatInfo info)
|
private static void Add(Format format, FormatInfo info)
|
||||||
|
|
|
@ -7,6 +7,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
{
|
{
|
||||||
private static readonly Lazy<bool> _supportsAstcCompression = new Lazy<bool>(() => HasExtension("GL_KHR_texture_compression_astc_ldr"));
|
private static readonly Lazy<bool> _supportsAstcCompression = new Lazy<bool>(() => HasExtension("GL_KHR_texture_compression_astc_ldr"));
|
||||||
private static readonly Lazy<bool> _supportsImageLoadFormatted = new Lazy<bool>(() => HasExtension("GL_EXT_shader_image_load_formatted"));
|
private static readonly Lazy<bool> _supportsImageLoadFormatted = new Lazy<bool>(() => HasExtension("GL_EXT_shader_image_load_formatted"));
|
||||||
|
private static readonly Lazy<bool> _supportsPolygonOffsetClamp = new Lazy<bool>(() => HasExtension("GL_EXT_polygon_offset_clamp"));
|
||||||
private static readonly Lazy<bool> _supportsViewportSwizzle = new Lazy<bool>(() => HasExtension("GL_NV_viewport_swizzle"));
|
private static readonly Lazy<bool> _supportsViewportSwizzle = new Lazy<bool>(() => HasExtension("GL_NV_viewport_swizzle"));
|
||||||
|
|
||||||
private static readonly Lazy<int> _maximumComputeSharedMemorySize = new Lazy<int>(() => GetLimit(All.MaxComputeSharedMemorySize));
|
private static readonly Lazy<int> _maximumComputeSharedMemorySize = new Lazy<int>(() => GetLimit(All.MaxComputeSharedMemorySize));
|
||||||
|
@ -28,6 +29,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
|
|
||||||
public static bool SupportsAstcCompression => _supportsAstcCompression.Value;
|
public static bool SupportsAstcCompression => _supportsAstcCompression.Value;
|
||||||
public static bool SupportsImageLoadFormatted => _supportsImageLoadFormatted.Value;
|
public static bool SupportsImageLoadFormatted => _supportsImageLoadFormatted.Value;
|
||||||
|
public static bool SupportsPolygonOffsetClamp => _supportsPolygonOffsetClamp.Value;
|
||||||
public static bool SupportsViewportSwizzle => _supportsViewportSwizzle.Value;
|
public static bool SupportsViewportSwizzle => _supportsViewportSwizzle.Value;
|
||||||
public static bool SupportsNonConstantTextureOffset => _gpuVendor.Value == GpuVendor.Nvidia;
|
public static bool SupportsNonConstantTextureOffset => _gpuVendor.Value == GpuVendor.Nvidia;
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||||
{
|
{
|
||||||
public int Handle { get; protected set; }
|
public int Handle { get; protected set; }
|
||||||
|
|
||||||
protected TextureCreateInfo Info { get; }
|
public TextureCreateInfo Info { get; }
|
||||||
|
|
||||||
public int Width { get; }
|
public int Width { get; }
|
||||||
public int Height { get; }
|
public int Height { get; }
|
||||||
|
|
|
@ -11,6 +11,9 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||||
private int _srcFramebuffer;
|
private int _srcFramebuffer;
|
||||||
private int _dstFramebuffer;
|
private int _dstFramebuffer;
|
||||||
|
|
||||||
|
private int _copyPboHandle;
|
||||||
|
private int _copyPboSize;
|
||||||
|
|
||||||
public TextureCopy(Renderer renderer)
|
public TextureCopy(Renderer renderer)
|
||||||
{
|
{
|
||||||
_renderer = renderer;
|
_renderer = renderer;
|
||||||
|
@ -23,12 +26,14 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||||
Extents2D dstRegion,
|
Extents2D dstRegion,
|
||||||
bool linearFilter)
|
bool linearFilter)
|
||||||
{
|
{
|
||||||
|
TextureView srcConverted = src.Format.IsBgra8() != dst.Format.IsBgra8() ? BgraSwap(src) : src;
|
||||||
|
|
||||||
(int oldDrawFramebufferHandle, int oldReadFramebufferHandle) = ((Pipeline)_renderer.Pipeline).GetBoundFramebuffers();
|
(int oldDrawFramebufferHandle, int oldReadFramebufferHandle) = ((Pipeline)_renderer.Pipeline).GetBoundFramebuffers();
|
||||||
|
|
||||||
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, GetSrcFramebufferLazy());
|
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, GetSrcFramebufferLazy());
|
||||||
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, GetDstFramebufferLazy());
|
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, GetDstFramebufferLazy());
|
||||||
|
|
||||||
Attach(FramebufferTarget.ReadFramebuffer, src.Format, src.Handle);
|
Attach(FramebufferTarget.ReadFramebuffer, src.Format, srcConverted.Handle);
|
||||||
Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle);
|
Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle);
|
||||||
|
|
||||||
ClearBufferMask mask = GetMask(src.Format);
|
ClearBufferMask mask = GetMask(src.Format);
|
||||||
|
@ -68,6 +73,11 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||||
|
|
||||||
((Pipeline)_renderer.Pipeline).RestoreScissor0Enable();
|
((Pipeline)_renderer.Pipeline).RestoreScissor0Enable();
|
||||||
((Pipeline)_renderer.Pipeline).RestoreRasterizerDiscard();
|
((Pipeline)_renderer.Pipeline).RestoreRasterizerDiscard();
|
||||||
|
|
||||||
|
if (srcConverted != src)
|
||||||
|
{
|
||||||
|
srcConverted.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Attach(FramebufferTarget target, Format format, int handle)
|
private static void Attach(FramebufferTarget target, Format format, int handle)
|
||||||
|
@ -117,6 +127,52 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||||
format == Format.D32Float;
|
format == Format.D32Float;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TextureView BgraSwap(TextureView from)
|
||||||
|
{
|
||||||
|
TextureView to = (TextureView)_renderer.CreateTexture(from.Info, 1f);
|
||||||
|
|
||||||
|
EnsurePbo(from);
|
||||||
|
|
||||||
|
GL.BindBuffer(BufferTarget.PixelPackBuffer, _copyPboHandle);
|
||||||
|
|
||||||
|
from.WriteToPbo(0, forceBgra: true);
|
||||||
|
|
||||||
|
GL.BindBuffer(BufferTarget.PixelPackBuffer, 0);
|
||||||
|
GL.BindBuffer(BufferTarget.PixelUnpackBuffer, _copyPboHandle);
|
||||||
|
|
||||||
|
to.ReadFromPbo(0, _copyPboSize);
|
||||||
|
|
||||||
|
GL.BindBuffer(BufferTarget.PixelUnpackBuffer, 0);
|
||||||
|
|
||||||
|
return to;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsurePbo(TextureView view)
|
||||||
|
{
|
||||||
|
int requiredSize = 0;
|
||||||
|
|
||||||
|
for (int level = 0; level < view.Info.Levels; level++)
|
||||||
|
{
|
||||||
|
requiredSize += view.Info.GetMipSize(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_copyPboSize < requiredSize && _copyPboHandle != 0)
|
||||||
|
{
|
||||||
|
GL.DeleteBuffer(_copyPboHandle);
|
||||||
|
|
||||||
|
_copyPboHandle = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_copyPboHandle == 0)
|
||||||
|
{
|
||||||
|
_copyPboHandle = GL.GenBuffer();
|
||||||
|
_copyPboSize = requiredSize;
|
||||||
|
|
||||||
|
GL.BindBuffer(BufferTarget.PixelPackBuffer, _copyPboHandle);
|
||||||
|
GL.BufferData(BufferTarget.PixelPackBuffer, requiredSize, IntPtr.Zero, BufferUsageHint.DynamicCopy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private int GetSrcFramebufferLazy()
|
private int GetSrcFramebufferLazy()
|
||||||
{
|
{
|
||||||
if (_srcFramebuffer == 0)
|
if (_srcFramebuffer == 0)
|
||||||
|
@ -152,6 +208,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||||
|
|
||||||
_dstFramebuffer = 0;
|
_dstFramebuffer = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_copyPboHandle != 0)
|
||||||
|
{
|
||||||
|
GL.DeleteBuffer(_copyPboHandle);
|
||||||
|
|
||||||
|
_copyPboHandle = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,15 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||||
(int)Info.SwizzleA.Convert()
|
(int)Info.SwizzleA.Convert()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (Info.Format.IsBgra8())
|
||||||
|
{
|
||||||
|
// Swap B <-> R for BGRA formats, as OpenGL has no support for them
|
||||||
|
// and we need to manually swap the components on read/write on the GPU.
|
||||||
|
int temp = swizzleRgba[0];
|
||||||
|
swizzleRgba[0] = swizzleRgba[2];
|
||||||
|
swizzleRgba[2] = temp;
|
||||||
|
}
|
||||||
|
|
||||||
GL.TexParameter(target, TextureParameterName.TextureSwizzleRgba, swizzleRgba);
|
GL.TexParameter(target, TextureParameterName.TextureSwizzleRgba, swizzleRgba);
|
||||||
|
|
||||||
int maxLevel = Info.Levels - 1;
|
int maxLevel = Info.Levels - 1;
|
||||||
|
@ -189,7 +198,12 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WriteTo(IntPtr ptr)
|
public void WriteToPbo(int offset, bool forceBgra)
|
||||||
|
{
|
||||||
|
WriteTo(IntPtr.Zero + offset, forceBgra);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteTo(IntPtr data, bool forceBgra = false)
|
||||||
{
|
{
|
||||||
TextureTarget target = Target.Convert();
|
TextureTarget target = Target.Convert();
|
||||||
|
|
||||||
|
@ -197,6 +211,14 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||||
|
|
||||||
FormatInfo format = FormatTable.GetFormatInfo(Info.Format);
|
FormatInfo format = FormatTable.GetFormatInfo(Info.Format);
|
||||||
|
|
||||||
|
PixelFormat pixelFormat = format.PixelFormat;
|
||||||
|
PixelType pixelType = format.PixelType;
|
||||||
|
|
||||||
|
if (forceBgra)
|
||||||
|
{
|
||||||
|
pixelFormat = PixelFormat.Bgra;
|
||||||
|
}
|
||||||
|
|
||||||
int faces = 1;
|
int faces = 1;
|
||||||
|
|
||||||
if (target == TextureTarget.TextureCubeMap)
|
if (target == TextureTarget.TextureCubeMap)
|
||||||
|
@ -214,20 +236,15 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||||
|
|
||||||
if (format.IsCompressed)
|
if (format.IsCompressed)
|
||||||
{
|
{
|
||||||
GL.GetCompressedTexImage(target + face, level, ptr + faceOffset);
|
GL.GetCompressedTexImage(target + face, level, data + faceOffset);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
GL.GetTexImage(
|
GL.GetTexImage(target + face, level, pixelFormat, pixelType, data + faceOffset);
|
||||||
target + face,
|
|
||||||
level,
|
|
||||||
format.PixelFormat,
|
|
||||||
format.PixelType,
|
|
||||||
ptr + faceOffset);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ptr += Info.GetMipSize(level);
|
data += Info.GetMipSize(level);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,12 +254,17 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||||
{
|
{
|
||||||
fixed (byte* ptr = data)
|
fixed (byte* ptr = data)
|
||||||
{
|
{
|
||||||
SetData((IntPtr)ptr, data.Length);
|
ReadFrom((IntPtr)ptr, data.Length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetData(IntPtr data, int size)
|
public void ReadFromPbo(int offset, int size)
|
||||||
|
{
|
||||||
|
ReadFrom(IntPtr.Zero + offset, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReadFrom(IntPtr data, int size)
|
||||||
{
|
{
|
||||||
TextureTarget target = Target.Convert();
|
TextureTarget target = Target.Convert();
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
private int _boundDrawFramebuffer;
|
private int _boundDrawFramebuffer;
|
||||||
private int _boundReadFramebuffer;
|
private int _boundReadFramebuffer;
|
||||||
|
|
||||||
|
private int[] _fpIsBgra = new int[8];
|
||||||
private float[] _fpRenderScale = new float[33];
|
private float[] _fpRenderScale = new float[33];
|
||||||
private float[] _cpRenderScale = new float[32];
|
private float[] _cpRenderScale = new float[32];
|
||||||
|
|
||||||
|
@ -609,9 +610,14 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
GL.PolygonOffset(factor, units / 2f);
|
if (HwCapabilities.SupportsPolygonOffsetClamp)
|
||||||
// TODO: Enable when GL_EXT_polygon_offset_clamp is supported.
|
{
|
||||||
// GL.PolygonOffsetClamp(factor, units, clamp);
|
GL.PolygonOffsetClamp(factor, units, clamp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GL.PolygonOffset(factor, units);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetDepthClamp(bool clamp)
|
public void SetDepthClamp(bool clamp)
|
||||||
|
@ -765,6 +771,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
_program.Bind();
|
_program.Bind();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpdateFpIsBgra();
|
||||||
SetRenderTargetScale(_fpRenderScale[0]);
|
SetRenderTargetScale(_fpRenderScale[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -814,12 +821,15 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
TextureView color = (TextureView)colors[index];
|
TextureView color = (TextureView)colors[index];
|
||||||
|
|
||||||
_framebuffer.AttachColor(index, color);
|
_framebuffer.AttachColor(index, color);
|
||||||
|
|
||||||
|
_fpIsBgra[index] = color != null && color.Format.IsBgra8() ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpdateFpIsBgra();
|
||||||
|
|
||||||
TextureView depthStencilView = (TextureView)depthStencil;
|
TextureView depthStencilView = (TextureView)depthStencil;
|
||||||
|
|
||||||
_framebuffer.AttachDepthStencil(depthStencilView);
|
_framebuffer.AttachDepthStencil(depthStencilView);
|
||||||
|
|
||||||
_framebuffer.SetDrawBuffers(colors.Length);
|
_framebuffer.SetDrawBuffers(colors.Length);
|
||||||
|
|
||||||
_hasDepthBuffer = depthStencil != null && depthStencilView.Format != Format.S8Uint;
|
_hasDepthBuffer = depthStencil != null && depthStencilView.Format != Format.S8Uint;
|
||||||
|
@ -938,7 +948,9 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
|
|
||||||
if (activeTarget != null && activeTarget.Width / (float)texture.Width == activeTarget.Height / (float)texture.Height)
|
if (activeTarget != null && activeTarget.Width / (float)texture.Width == activeTarget.Height / (float)texture.Height)
|
||||||
{
|
{
|
||||||
// If the texture's size is a multiple of the sampler size, enable interpolation using gl_FragCoord. (helps "invent" new integer values between scaled pixels)
|
// If the texture's size is a multiple of the sampler size,
|
||||||
|
// enable interpolation using gl_FragCoord.
|
||||||
|
// (helps "invent" new integer values between scaled pixels)
|
||||||
interpolate = true;
|
interpolate = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1112,6 +1124,14 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
return (_boundDrawFramebuffer, _boundReadFramebuffer);
|
return (_boundDrawFramebuffer, _boundReadFramebuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateFpIsBgra()
|
||||||
|
{
|
||||||
|
if (_program != null)
|
||||||
|
{
|
||||||
|
GL.Uniform1(_program.FragmentIsBgraUniform, 8, _fpIsBgra);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateDepthTest()
|
private void UpdateDepthTest()
|
||||||
{
|
{
|
||||||
// Enabling depth operations is only valid when we have
|
// Enabling depth operations is only valid when we have
|
||||||
|
@ -1137,6 +1157,29 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UpdateRenderScale(ShaderStage stage, int textureCount)
|
||||||
|
{
|
||||||
|
if (_program != null)
|
||||||
|
{
|
||||||
|
switch (stage)
|
||||||
|
{
|
||||||
|
case ShaderStage.Fragment:
|
||||||
|
if (_program.FragmentRenderScaleUniform != -1)
|
||||||
|
{
|
||||||
|
GL.Uniform1(_program.FragmentRenderScaleUniform, textureCount + 1, _fpRenderScale);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ShaderStage.Compute:
|
||||||
|
if (_program.ComputeRenderScaleUniform != -1)
|
||||||
|
{
|
||||||
|
GL.Uniform1(_program.ComputeRenderScaleUniform, textureCount, _cpRenderScale);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void PrepareForDispatch()
|
private void PrepareForDispatch()
|
||||||
{
|
{
|
||||||
if (_unit0Texture != null)
|
if (_unit0Texture != null)
|
||||||
|
@ -1230,28 +1273,5 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
_framebuffer?.Dispose();
|
_framebuffer?.Dispose();
|
||||||
_vertexArray?.Dispose();
|
_vertexArray?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateRenderScale(ShaderStage stage, int textureCount)
|
|
||||||
{
|
|
||||||
if (_program != null)
|
|
||||||
{
|
|
||||||
switch (stage)
|
|
||||||
{
|
|
||||||
case ShaderStage.Fragment:
|
|
||||||
if (_program.FragmentRenderScaleUniform != -1)
|
|
||||||
{
|
|
||||||
GL.Uniform1(_program.FragmentRenderScaleUniform, textureCount + 1, _fpRenderScale);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ShaderStage.Compute:
|
|
||||||
if (_program.ComputeRenderScaleUniform != -1)
|
|
||||||
{
|
|
||||||
GL.Uniform1(_program.ComputeRenderScaleUniform, textureCount, _cpRenderScale);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
|
|
||||||
public int Handle { get; private set; }
|
public int Handle { get; private set; }
|
||||||
|
|
||||||
|
public int FragmentIsBgraUniform { get; }
|
||||||
public int FragmentRenderScaleUniform { get; }
|
public int FragmentRenderScaleUniform { get; }
|
||||||
public int ComputeRenderScaleUniform { get; }
|
public int ComputeRenderScaleUniform { get; }
|
||||||
|
|
||||||
|
@ -218,6 +219,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FragmentIsBgraUniform = GL.GetUniformLocation(Handle, "is_bgra");
|
||||||
FragmentRenderScaleUniform = GL.GetUniformLocation(Handle, "fp_renderScale");
|
FragmentRenderScaleUniform = GL.GetUniformLocation(Handle, "fp_renderScale");
|
||||||
ComputeRenderScaleUniform = GL.GetUniformLocation(Handle, "cp_renderScale");
|
ComputeRenderScaleUniform = GL.GetUniformLocation(Handle, "cp_renderScale");
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,10 +51,12 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, drawFramebuffer);
|
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, drawFramebuffer);
|
||||||
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, readFramebuffer);
|
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, readFramebuffer);
|
||||||
|
|
||||||
|
TextureView viewConverted = view.Format.IsBgra8() ? _renderer.TextureCopy.BgraSwap(view) : view;
|
||||||
|
|
||||||
GL.FramebufferTexture(
|
GL.FramebufferTexture(
|
||||||
FramebufferTarget.ReadFramebuffer,
|
FramebufferTarget.ReadFramebuffer,
|
||||||
FramebufferAttachment.ColorAttachment0,
|
FramebufferAttachment.ColorAttachment0,
|
||||||
view.Handle,
|
viewConverted.Handle,
|
||||||
0);
|
0);
|
||||||
|
|
||||||
GL.ReadBuffer(ReadBufferMode.ColorAttachment0);
|
GL.ReadBuffer(ReadBufferMode.ColorAttachment0);
|
||||||
|
@ -138,6 +140,11 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
|
|
||||||
((Pipeline)_renderer.Pipeline).RestoreScissor0Enable();
|
((Pipeline)_renderer.Pipeline).RestoreScissor0Enable();
|
||||||
((Pipeline)_renderer.Pipeline).RestoreRasterizerDiscard();
|
((Pipeline)_renderer.Pipeline).RestoreRasterizerDiscard();
|
||||||
|
|
||||||
|
if (viewConverted != view)
|
||||||
|
{
|
||||||
|
viewConverted.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GetCopyFramebufferHandleLazy()
|
private int GetCopyFramebufferHandleLazy()
|
||||||
|
|
|
@ -139,6 +139,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||||
|
|
||||||
if (context.Config.Stage == ShaderStage.Fragment || context.Config.Stage == ShaderStage.Compute)
|
if (context.Config.Stage == ShaderStage.Fragment || context.Config.Stage == ShaderStage.Compute)
|
||||||
{
|
{
|
||||||
|
if (context.Config.Stage == ShaderStage.Fragment)
|
||||||
|
{
|
||||||
|
context.AppendLine($"uniform bool {DefaultNames.IsBgraName}[8];");
|
||||||
|
context.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
if (DeclareRenderScale(context))
|
if (DeclareRenderScale(context))
|
||||||
{
|
{
|
||||||
context.AppendLine();
|
context.AppendLine();
|
||||||
|
|
|
@ -23,5 +23,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||||
public const string SharedMemoryName = "shared_mem";
|
public const string SharedMemoryName = "shared_mem";
|
||||||
|
|
||||||
public const string UndefinedName = "undef";
|
public const string UndefinedName = "undef";
|
||||||
|
|
||||||
|
public const string IsBgraName = "is_bgra";
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -64,6 +64,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||||
{ AttributeConsts.GtMask, new BuiltInAttribute("unpackUint2x32(gl_SubGroupGtMaskARB).x", VariableType.U32) },
|
{ AttributeConsts.GtMask, new BuiltInAttribute("unpackUint2x32(gl_SubGroupGtMaskARB).x", VariableType.U32) },
|
||||||
{ AttributeConsts.LeMask, new BuiltInAttribute("unpackUint2x32(gl_SubGroupLeMaskARB).x", VariableType.U32) },
|
{ AttributeConsts.LeMask, new BuiltInAttribute("unpackUint2x32(gl_SubGroupLeMaskARB).x", VariableType.U32) },
|
||||||
{ AttributeConsts.LtMask, new BuiltInAttribute("unpackUint2x32(gl_SubGroupLtMaskARB).x", VariableType.U32) },
|
{ AttributeConsts.LtMask, new BuiltInAttribute("unpackUint2x32(gl_SubGroupLtMaskARB).x", VariableType.U32) },
|
||||||
|
|
||||||
|
// Support uniforms.
|
||||||
|
{ AttributeConsts.FragmentOutputIsBgraBase + 0, new BuiltInAttribute($"{DefaultNames.IsBgraName}[0]", VariableType.Bool) },
|
||||||
|
{ AttributeConsts.FragmentOutputIsBgraBase + 4, new BuiltInAttribute($"{DefaultNames.IsBgraName}[1]", VariableType.Bool) },
|
||||||
|
{ AttributeConsts.FragmentOutputIsBgraBase + 8, new BuiltInAttribute($"{DefaultNames.IsBgraName}[2]", VariableType.Bool) },
|
||||||
|
{ AttributeConsts.FragmentOutputIsBgraBase + 12, new BuiltInAttribute($"{DefaultNames.IsBgraName}[3]", VariableType.Bool) },
|
||||||
|
{ AttributeConsts.FragmentOutputIsBgraBase + 16, new BuiltInAttribute($"{DefaultNames.IsBgraName}[4]", VariableType.Bool) },
|
||||||
|
{ AttributeConsts.FragmentOutputIsBgraBase + 20, new BuiltInAttribute($"{DefaultNames.IsBgraName}[5]", VariableType.Bool) },
|
||||||
|
{ AttributeConsts.FragmentOutputIsBgraBase + 24, new BuiltInAttribute($"{DefaultNames.IsBgraName}[6]", VariableType.Bool) },
|
||||||
|
{ AttributeConsts.FragmentOutputIsBgraBase + 28, new BuiltInAttribute($"{DefaultNames.IsBgraName}[7]", VariableType.Bool) }
|
||||||
};
|
};
|
||||||
|
|
||||||
private Dictionary<AstOperand, string> _locals;
|
private Dictionary<AstOperand, string> _locals;
|
||||||
|
@ -149,8 +159,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||||
|
|
||||||
char swzMask = GetSwizzleMask((value >> 2) & 3);
|
char swzMask = GetSwizzleMask((value >> 2) & 3);
|
||||||
|
|
||||||
if (value >= AttributeConsts.UserAttributeBase &&
|
if (value >= AttributeConsts.UserAttributeBase && value < AttributeConsts.UserAttributeEnd)
|
||||||
value < AttributeConsts.UserAttributeEnd)
|
|
||||||
{
|
{
|
||||||
value -= AttributeConsts.UserAttributeBase;
|
value -= AttributeConsts.UserAttributeBase;
|
||||||
|
|
||||||
|
@ -169,8 +178,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (value >= AttributeConsts.FragmentOutputColorBase &&
|
if (value >= AttributeConsts.FragmentOutputColorBase && value < AttributeConsts.FragmentOutputColorEnd)
|
||||||
value < AttributeConsts.FragmentOutputColorEnd)
|
|
||||||
{
|
{
|
||||||
value -= AttributeConsts.FragmentOutputColorBase;
|
value -= AttributeConsts.FragmentOutputColorBase;
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,9 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
public const int FragmentOutputColorBase = 0x1000010;
|
public const int FragmentOutputColorBase = 0x1000010;
|
||||||
public const int FragmentOutputColorEnd = FragmentOutputColorBase + 8 * 16;
|
public const int FragmentOutputColorEnd = FragmentOutputColorBase + 8 * 16;
|
||||||
|
|
||||||
|
public const int FragmentOutputIsBgraBase = 0x1000100;
|
||||||
|
public const int FragmentOutputIsBgraEnd = FragmentOutputIsBgraBase + 8 * 4;
|
||||||
|
|
||||||
public const int ThreadIdX = 0x2000000;
|
public const int ThreadIdX = 0x2000000;
|
||||||
public const int ThreadIdY = 0x2000004;
|
public const int ThreadIdY = 0x2000004;
|
||||||
public const int ThreadIdZ = 0x2000008;
|
public const int ThreadIdZ = 0x2000008;
|
||||||
|
|
|
@ -115,22 +115,46 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
|
|
||||||
int regIndex = 0;
|
int regIndex = 0;
|
||||||
|
|
||||||
for (int attachment = 0; attachment < 8; attachment++)
|
for (int rtIndex = 0; rtIndex < 8; rtIndex++)
|
||||||
{
|
{
|
||||||
OmapTarget target = Config.OmapTargets[attachment];
|
OmapTarget target = Config.OmapTargets[rtIndex];
|
||||||
|
|
||||||
for (int component = 0; component < 4; component++)
|
for (int component = 0; component < 4; component++)
|
||||||
{
|
{
|
||||||
if (target.ComponentEnabled(component))
|
if (!target.ComponentEnabled(component))
|
||||||
{
|
{
|
||||||
Operand dest = Attribute(AttributeConsts.FragmentOutputColorBase + attachment * 16 + component * 4);
|
continue;
|
||||||
|
|
||||||
Operand src = Register(regIndex, RegisterType.Gpr);
|
|
||||||
|
|
||||||
this.Copy(dest, src);
|
|
||||||
|
|
||||||
regIndex++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int fragmentOutputColorAttr = AttributeConsts.FragmentOutputColorBase + rtIndex * 16;
|
||||||
|
|
||||||
|
Operand src = Register(regIndex, RegisterType.Gpr);
|
||||||
|
|
||||||
|
// Perform B <-> R swap if needed, for BGRA formats (not supported on OpenGL).
|
||||||
|
if (component == 0 || component == 2)
|
||||||
|
{
|
||||||
|
Operand isBgra = Attribute(AttributeConsts.FragmentOutputIsBgraBase + rtIndex * 4);
|
||||||
|
|
||||||
|
Operand lblIsBgra = Label();
|
||||||
|
Operand lblEnd = Label();
|
||||||
|
|
||||||
|
this.BranchIfTrue(lblIsBgra, isBgra);
|
||||||
|
|
||||||
|
this.Copy(Attribute(fragmentOutputColorAttr + component * 4), src);
|
||||||
|
this.Branch(lblEnd);
|
||||||
|
|
||||||
|
MarkLabel(lblIsBgra);
|
||||||
|
|
||||||
|
this.Copy(Attribute(fragmentOutputColorAttr + (2 - component) * 4), src);
|
||||||
|
|
||||||
|
MarkLabel(lblEnd);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.Copy(Attribute(fragmentOutputColorAttr + component * 4), src);
|
||||||
|
}
|
||||||
|
|
||||||
|
regIndex++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,5 +161,42 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Command(110)]
|
||||||
|
// NeedsToExitProcess()
|
||||||
|
public ResultCode NeedsToExitProcess(ServiceCtx context)
|
||||||
|
{
|
||||||
|
return ResultCode.Stubbed;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command(150)]
|
||||||
|
// RequestForAppletToGetForeground()
|
||||||
|
public ResultCode RequestForAppletToGetForeground(ServiceCtx context)
|
||||||
|
{
|
||||||
|
return ResultCode.Stubbed;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command(160)] // 2.0.0+
|
||||||
|
// GetIndirectLayerConsumerHandle() -> u64 indirect_layer_consumer_handle
|
||||||
|
public ResultCode GetIndirectLayerConsumerHandle(ServiceCtx context)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
if (indirectLayerConsumer == null)
|
||||||
|
{
|
||||||
|
return ResultCode.ObjectInvalid;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO: Official sw uses this during LibraryApplet creation when LibraryAppletMode is 0x3.
|
||||||
|
// Since we don't support IndirectLayer and the handle couldn't be 0, it's fine to return 1.
|
||||||
|
|
||||||
|
ulong indirectLayerConsumerHandle = 1;
|
||||||
|
|
||||||
|
context.ResponseData.Write(indirectLayerConsumerHandle);
|
||||||
|
|
||||||
|
Logger.PrintStub(LogClass.ServiceAm, new { indirectLayerConsumerHandle });
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,18 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||||
// TODO: Set this when the game goes in suspension (go back to home menu ect), we currently don't support that so we can keep it set to 0.
|
// TODO: Set this when the game goes in suspension (go back to home menu ect), we currently don't support that so we can keep it set to 0.
|
||||||
private ulong _accumulatedSuspendedTickValue = 0;
|
private ulong _accumulatedSuspendedTickValue = 0;
|
||||||
|
|
||||||
private int _idleTimeDetectionExtension;
|
// TODO: Determine where those fields are used.
|
||||||
|
private bool _screenShotPermission = false;
|
||||||
|
private bool _operationModeChangedNotification = false;
|
||||||
|
private bool _performanceModeChangedNotification = false;
|
||||||
|
private bool _restartMessageEnabled = false;
|
||||||
|
private bool _outOfFocusSuspendingEnabled = false;
|
||||||
|
private bool _handlesRequestToDisplay = false;
|
||||||
|
private bool _autoSleepDisabled = false;
|
||||||
|
private bool _albumImageTakenNotificationEnabled = false;
|
||||||
|
|
||||||
|
private uint _screenShotImageOrientation = 0;
|
||||||
|
private uint _idleTimeDetectionExtension = 0;
|
||||||
|
|
||||||
public ISelfController(Horizon system)
|
public ISelfController(Horizon system)
|
||||||
{
|
{
|
||||||
|
@ -108,9 +119,11 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||||
// SetScreenShotPermission(u32)
|
// SetScreenShotPermission(u32)
|
||||||
public ResultCode SetScreenShotPermission(ServiceCtx context)
|
public ResultCode SetScreenShotPermission(ServiceCtx context)
|
||||||
{
|
{
|
||||||
bool enable = context.RequestData.ReadByte() != 0;
|
bool screenShotPermission = context.RequestData.ReadBoolean();
|
||||||
|
|
||||||
Logger.PrintStub(LogClass.ServiceAm);
|
Logger.PrintStub(LogClass.ServiceAm, new { screenShotPermission });
|
||||||
|
|
||||||
|
_screenShotPermission = screenShotPermission;
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
@ -119,9 +132,11 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||||
// SetOperationModeChangedNotification(b8)
|
// SetOperationModeChangedNotification(b8)
|
||||||
public ResultCode SetOperationModeChangedNotification(ServiceCtx context)
|
public ResultCode SetOperationModeChangedNotification(ServiceCtx context)
|
||||||
{
|
{
|
||||||
bool enable = context.RequestData.ReadByte() != 0;
|
bool operationModeChangedNotification = context.RequestData.ReadBoolean();
|
||||||
|
|
||||||
Logger.PrintStub(LogClass.ServiceAm);
|
Logger.PrintStub(LogClass.ServiceAm, new { operationModeChangedNotification });
|
||||||
|
|
||||||
|
_operationModeChangedNotification = operationModeChangedNotification;
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
@ -130,9 +145,11 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||||
// SetPerformanceModeChangedNotification(b8)
|
// SetPerformanceModeChangedNotification(b8)
|
||||||
public ResultCode SetPerformanceModeChangedNotification(ServiceCtx context)
|
public ResultCode SetPerformanceModeChangedNotification(ServiceCtx context)
|
||||||
{
|
{
|
||||||
bool enable = context.RequestData.ReadByte() != 0;
|
bool performanceModeChangedNotification = context.RequestData.ReadBoolean();
|
||||||
|
|
||||||
Logger.PrintStub(LogClass.ServiceAm);
|
Logger.PrintStub(LogClass.ServiceAm, new { performanceModeChangedNotification });
|
||||||
|
|
||||||
|
_performanceModeChangedNotification = performanceModeChangedNotification;
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
@ -141,11 +158,11 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||||
// SetFocusHandlingMode(b8, b8, b8)
|
// SetFocusHandlingMode(b8, b8, b8)
|
||||||
public ResultCode SetFocusHandlingMode(ServiceCtx context)
|
public ResultCode SetFocusHandlingMode(ServiceCtx context)
|
||||||
{
|
{
|
||||||
bool flag1 = context.RequestData.ReadByte() != 0;
|
bool unknownFlag1 = context.RequestData.ReadBoolean();
|
||||||
bool flag2 = context.RequestData.ReadByte() != 0;
|
bool unknownFlag2 = context.RequestData.ReadBoolean();
|
||||||
bool flag3 = context.RequestData.ReadByte() != 0;
|
bool unknownFlag3 = context.RequestData.ReadBoolean();
|
||||||
|
|
||||||
Logger.PrintStub(LogClass.ServiceAm);
|
Logger.PrintStub(LogClass.ServiceAm, new { unknownFlag1, unknownFlag2, unknownFlag3 });
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
@ -154,9 +171,11 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||||
// SetRestartMessageEnabled(b8)
|
// SetRestartMessageEnabled(b8)
|
||||||
public ResultCode SetRestartMessageEnabled(ServiceCtx context)
|
public ResultCode SetRestartMessageEnabled(ServiceCtx context)
|
||||||
{
|
{
|
||||||
bool enable = context.RequestData.ReadByte() != 0;
|
bool restartMessageEnabled = context.RequestData.ReadBoolean();
|
||||||
|
|
||||||
Logger.PrintStub(LogClass.ServiceAm);
|
Logger.PrintStub(LogClass.ServiceAm, new { restartMessageEnabled });
|
||||||
|
|
||||||
|
_restartMessageEnabled = restartMessageEnabled;
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
@ -165,19 +184,24 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||||
// SetOutOfFocusSuspendingEnabled(b8)
|
// SetOutOfFocusSuspendingEnabled(b8)
|
||||||
public ResultCode SetOutOfFocusSuspendingEnabled(ServiceCtx context)
|
public ResultCode SetOutOfFocusSuspendingEnabled(ServiceCtx context)
|
||||||
{
|
{
|
||||||
bool enable = context.RequestData.ReadByte() != 0;
|
bool outOfFocusSuspendingEnabled = context.RequestData.ReadBoolean();
|
||||||
|
|
||||||
Logger.PrintStub(LogClass.ServiceAm);
|
Logger.PrintStub(LogClass.ServiceAm, new { outOfFocusSuspendingEnabled });
|
||||||
|
|
||||||
|
_outOfFocusSuspendingEnabled = outOfFocusSuspendingEnabled;
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Command(19)] // 3.0.0+
|
[Command(19)] // 3.0.0+
|
||||||
|
// SetScreenShotImageOrientation(u32)
|
||||||
public ResultCode SetScreenShotImageOrientation(ServiceCtx context)
|
public ResultCode SetScreenShotImageOrientation(ServiceCtx context)
|
||||||
{
|
{
|
||||||
int orientation = context.RequestData.ReadInt32();
|
uint screenShotImageOrientation = context.RequestData.ReadUInt32();
|
||||||
|
|
||||||
Logger.PrintStub(LogClass.ServiceAm);
|
Logger.PrintStub(LogClass.ServiceAm, new { screenShotImageOrientation });
|
||||||
|
|
||||||
|
_screenShotImageOrientation = screenShotImageOrientation;
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
@ -186,9 +210,11 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||||
// SetHandlesRequestToDisplay(b8)
|
// SetHandlesRequestToDisplay(b8)
|
||||||
public ResultCode SetHandlesRequestToDisplay(ServiceCtx context)
|
public ResultCode SetHandlesRequestToDisplay(ServiceCtx context)
|
||||||
{
|
{
|
||||||
bool enable = context.RequestData.ReadByte() != 0;
|
bool handlesRequestToDisplay = context.RequestData.ReadBoolean();
|
||||||
|
|
||||||
Logger.PrintStub(LogClass.ServiceAm);
|
Logger.PrintStub(LogClass.ServiceAm, new { handlesRequestToDisplay });
|
||||||
|
|
||||||
|
_handlesRequestToDisplay = handlesRequestToDisplay;
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
@ -197,9 +223,11 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||||
// SetIdleTimeDetectionExtension(u32)
|
// SetIdleTimeDetectionExtension(u32)
|
||||||
public ResultCode SetIdleTimeDetectionExtension(ServiceCtx context)
|
public ResultCode SetIdleTimeDetectionExtension(ServiceCtx context)
|
||||||
{
|
{
|
||||||
_idleTimeDetectionExtension = context.RequestData.ReadInt32();
|
uint idleTimeDetectionExtension = context.RequestData.ReadUInt32();
|
||||||
|
|
||||||
Logger.PrintStub(LogClass.ServiceAm, new { _idleTimeDetectionExtension });
|
Logger.PrintStub(LogClass.ServiceAm, new { idleTimeDetectionExtension });
|
||||||
|
|
||||||
|
_idleTimeDetectionExtension = idleTimeDetectionExtension;
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
@ -215,6 +243,26 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Command(68)]
|
||||||
|
// SetAutoSleepDisabled(u8)
|
||||||
|
public ResultCode SetAutoSleepDisabled(ServiceCtx context)
|
||||||
|
{
|
||||||
|
bool autoSleepDisabled = context.RequestData.ReadBoolean();
|
||||||
|
|
||||||
|
_autoSleepDisabled = autoSleepDisabled;
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command(69)]
|
||||||
|
// IsAutoSleepDisabled() -> u8
|
||||||
|
public ResultCode IsAutoSleepDisabled(ServiceCtx context)
|
||||||
|
{
|
||||||
|
context.ResponseData.Write(_autoSleepDisabled);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
[Command(90)] // 6.0.0+
|
[Command(90)] // 6.0.0+
|
||||||
// GetAccumulatedSuspendedTickValue() -> u64
|
// GetAccumulatedSuspendedTickValue() -> u64
|
||||||
public ResultCode GetAccumulatedSuspendedTickValue(ServiceCtx context)
|
public ResultCode GetAccumulatedSuspendedTickValue(ServiceCtx context)
|
||||||
|
@ -244,5 +292,16 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Command(100)] // 7.0.0+
|
||||||
|
// SetAlbumImageTakenNotificationEnabled(u8)
|
||||||
|
public ResultCode SetAlbumImageTakenNotificationEnabled(ServiceCtx context)
|
||||||
|
{
|
||||||
|
bool albumImageTakenNotificationEnabled = context.RequestData.ReadBoolean();
|
||||||
|
|
||||||
|
_albumImageTakenNotificationEnabled = albumImageTakenNotificationEnabled;
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -158,7 +158,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
||||||
// NotifyRunning() -> b8
|
// NotifyRunning() -> b8
|
||||||
public ResultCode NotifyRunning(ServiceCtx context)
|
public ResultCode NotifyRunning(ServiceCtx context)
|
||||||
{
|
{
|
||||||
context.ResponseData.Write(1);
|
context.ResponseData.Write(true);
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
@ -195,6 +195,17 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Command(90)] // 4.0.0+
|
||||||
|
// EnableApplicationCrashReport(u8)
|
||||||
|
public ResultCode EnableApplicationCrashReport(ServiceCtx context)
|
||||||
|
{
|
||||||
|
bool applicationCrashReportEnabled = context.RequestData.ReadBoolean();
|
||||||
|
|
||||||
|
Logger.PrintStub(LogClass.ServiceAm, new { applicationCrashReportEnabled });
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
[Command(100)] // 5.0.0+
|
[Command(100)] // 5.0.0+
|
||||||
// InitializeApplicationCopyrightFrameBuffer(s32 width, s32 height, handle<copy, transfer_memory> transfer_memory, u64 transfer_memory_size)
|
// InitializeApplicationCopyrightFrameBuffer(s32 width, s32 height, handle<copy, transfer_memory> transfer_memory, u64 transfer_memory_size)
|
||||||
public ResultCode InitializeApplicationCopyrightFrameBuffer(ServiceCtx context)
|
public ResultCode InitializeApplicationCopyrightFrameBuffer(ServiceCtx context)
|
||||||
|
@ -319,6 +330,22 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
||||||
return (ResultCode)QueryPlayStatisticsManager.GetPlayStatistics(context, true);
|
return (ResultCode)QueryPlayStatisticsManager.GetPlayStatistics(context, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Command(123)] // 5.0.0+
|
||||||
|
// GetPreviousProgramIndex() -> s32 program_index
|
||||||
|
public ResultCode GetPreviousProgramIndex(ServiceCtx context)
|
||||||
|
{
|
||||||
|
// TODO: The output PreviousProgramIndex is -1 when there was no previous title.
|
||||||
|
// When multi-process will be supported, return the last program index.
|
||||||
|
|
||||||
|
int previousProgramIndex = -1;
|
||||||
|
|
||||||
|
context.ResponseData.Write(previousProgramIndex);
|
||||||
|
|
||||||
|
Logger.PrintStub(LogClass.ServiceAm, new { previousProgramIndex });
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
[Command(130)] // 8.0.0+
|
[Command(130)] // 8.0.0+
|
||||||
// GetGpuErrorDetectedSystemEvent() -> handle<copy>
|
// GetGpuErrorDetectedSystemEvent() -> handle<copy>
|
||||||
public ResultCode GetGpuErrorDetectedSystemEvent(ServiceCtx context)
|
public ResultCode GetGpuErrorDetectedSystemEvent(ServiceCtx context)
|
||||||
|
|
|
@ -22,6 +22,7 @@ namespace Ryujinx.HLE.HOS.Services.Am
|
||||||
StackPoolExhausted = (712 << ErrorCodeShift) | ModuleId,
|
StackPoolExhausted = (712 << ErrorCodeShift) | ModuleId,
|
||||||
DebugModeNotEnabled = (974 << ErrorCodeShift) | ModuleId,
|
DebugModeNotEnabled = (974 << ErrorCodeShift) | ModuleId,
|
||||||
DevFunctionNotEnabled = (980 << ErrorCodeShift) | ModuleId,
|
DevFunctionNotEnabled = (980 << ErrorCodeShift) | ModuleId,
|
||||||
NotImplemented = (998 << ErrorCodeShift) | ModuleId
|
NotImplemented = (998 << ErrorCodeShift) | ModuleId,
|
||||||
|
Stubbed = (999 << ErrorCodeShift) | ModuleId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,86 @@
|
||||||
namespace Ryujinx.HLE.HOS.Services.Audio
|
using Ryujinx.Cpu;
|
||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Audio
|
||||||
{
|
{
|
||||||
[Service("audin:u")]
|
[Service("audin:u")]
|
||||||
class IAudioInManager : IpcService
|
class IAudioInManager : IpcService
|
||||||
{
|
{
|
||||||
|
private const string DefaultAudioInsName = "BuiltInHeadset";
|
||||||
|
|
||||||
public IAudioInManager(ServiceCtx context) { }
|
public IAudioInManager(ServiceCtx context) { }
|
||||||
|
|
||||||
|
[Command(0)]
|
||||||
|
// ListAudioIns() -> (u32 count, buffer<bytes, 6> names)
|
||||||
|
public ResultCode ListAudioIns(ServiceCtx context)
|
||||||
|
{
|
||||||
|
long bufferPosition = context.Request.ReceiveBuff[0].Position;
|
||||||
|
long bufferSize = context.Request.ReceiveBuff[0].Size;
|
||||||
|
|
||||||
|
// NOTE: The service check if AudioInManager thread is started, if not it starts it.
|
||||||
|
|
||||||
|
uint count = ListAudioInsImpl(context.Memory, bufferPosition, bufferSize, false);
|
||||||
|
|
||||||
|
context.ResponseData.Write(count);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command(3)] // 3.0.0+
|
||||||
|
// ListAudioInsAuto() -> (u32 count, buffer<bytes, 0x22> names)
|
||||||
|
public ResultCode ListAudioInsAuto(ServiceCtx context)
|
||||||
|
{
|
||||||
|
(long bufferPosition, long bufferSize) = context.Request.GetBufferType0x22();
|
||||||
|
|
||||||
|
// NOTE: The service check if AudioInManager thread is started, if not it starts it.
|
||||||
|
|
||||||
|
uint count = ListAudioInsImpl(context.Memory, bufferPosition, bufferSize, false);
|
||||||
|
|
||||||
|
context.ResponseData.Write(count);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command(4)] // 3.0.0+
|
||||||
|
// ListAudioInsAutoFiltered() -> (u32 count, buffer<bytes, 0x22> names)
|
||||||
|
public ResultCode ListAudioInsAutoFiltered(ServiceCtx context)
|
||||||
|
{
|
||||||
|
(long bufferPosition, long bufferSize) = context.Request.GetBufferType0x22();
|
||||||
|
|
||||||
|
// NOTE: The service check if AudioInManager thread is started, if not it starts it.
|
||||||
|
|
||||||
|
uint count = ListAudioInsImpl(context.Memory, bufferPosition, bufferSize, true);
|
||||||
|
|
||||||
|
context.ResponseData.Write(count);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint ListAudioInsImpl(MemoryManager memory, long bufferPosition, long bufferSize, bool filtered = false)
|
||||||
|
{
|
||||||
|
uint count = 0;
|
||||||
|
|
||||||
|
MemoryHelper.FillWithZeros(memory, bufferPosition, (int)bufferSize);
|
||||||
|
|
||||||
|
if (bufferSize > 0)
|
||||||
|
{
|
||||||
|
// NOTE: The service also check that the input target is enabled when in filtering mode, as audctl and most of the audin logic isn't supported, we don't support it.
|
||||||
|
if (!filtered)
|
||||||
|
{
|
||||||
|
byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(DefaultAudioInsName + "\0");
|
||||||
|
|
||||||
|
memory.Write((ulong)bufferPosition, deviceNameBuffer);
|
||||||
|
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: The service adds other input devices names available in the buffer,
|
||||||
|
// every name is aligned to 0x100 bytes.
|
||||||
|
// Since we don't support it for now, it's fine to do nothing here.
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
21
Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServer.cs
Normal file
21
Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServer.cs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Nim.ShopServiceAccessServerInterface.ShopServiceAccessServer;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Nim.ShopServiceAccessServerInterface
|
||||||
|
{
|
||||||
|
class IShopServiceAccessServer : IpcService
|
||||||
|
{
|
||||||
|
public IShopServiceAccessServer() { }
|
||||||
|
|
||||||
|
[Command(0)]
|
||||||
|
// CreateAccessorInterface(u8) -> object<nn::ec::IShopServiceAccessor>
|
||||||
|
public ResultCode CreateAccessorInterface(ServiceCtx context)
|
||||||
|
{
|
||||||
|
MakeObject(context, new IShopServiceAccessor(context.Device.System));
|
||||||
|
|
||||||
|
Logger.PrintStub(LogClass.ServiceNim);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,22 @@
|
||||||
namespace Ryujinx.HLE.HOS.Services.Nim
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Nim.ShopServiceAccessServerInterface;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Nim
|
||||||
{
|
{
|
||||||
[Service("nim:eca")] // 5.0.0+
|
[Service("nim:eca")] // 5.0.0+
|
||||||
class IShopServiceAccessServerInterface : IpcService
|
class IShopServiceAccessServerInterface : IpcService
|
||||||
{
|
{
|
||||||
public IShopServiceAccessServerInterface(ServiceCtx context) { }
|
public IShopServiceAccessServerInterface(ServiceCtx context) { }
|
||||||
|
|
||||||
|
[Command(0)]
|
||||||
|
// CreateServerInterface(pid, handle<unknown>, u64) -> object<nn::ec::IShopServiceAccessServer>
|
||||||
|
public ResultCode CreateServerInterface(ServiceCtx context)
|
||||||
|
{
|
||||||
|
MakeObject(context, new IShopServiceAccessServer());
|
||||||
|
|
||||||
|
Logger.PrintStub(LogClass.ServiceNim);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
37
Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessor.cs
Normal file
37
Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessor.cs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.HLE.HOS.Ipc;
|
||||||
|
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||||
|
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Nim.ShopServiceAccessServerInterface.ShopServiceAccessServer.ShopServiceAccessor;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Nim.ShopServiceAccessServerInterface.ShopServiceAccessServer
|
||||||
|
{
|
||||||
|
class IShopServiceAccessor : IpcService
|
||||||
|
{
|
||||||
|
private readonly KEvent _event;
|
||||||
|
|
||||||
|
public IShopServiceAccessor(Horizon system)
|
||||||
|
{
|
||||||
|
_event = new KEvent(system.KernelContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command(0)]
|
||||||
|
// CreateAsyncInterface(u64) -> (handle<copy>, object<nn::ec::IShopServiceAsync>)
|
||||||
|
public ResultCode CreateAsyncInterface(ServiceCtx context)
|
||||||
|
{
|
||||||
|
MakeObject(context, new IShopServiceAsync());
|
||||||
|
|
||||||
|
if (context.Process.HandleTable.GenerateHandle(_event.ReadableEvent, out int handle) != KernelResult.Success)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Out of handles!");
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
|
||||||
|
|
||||||
|
Logger.PrintStub(LogClass.ServiceNim);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
Ryujinx.HLE/HOS/Services/Nim/IShopServiceAsync.cs
Normal file
7
Ryujinx.HLE/HOS/Services/Nim/IShopServiceAsync.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Nim.ShopServiceAccessServerInterface.ShopServiceAccessServer.ShopServiceAccessor
|
||||||
|
{
|
||||||
|
class IShopServiceAsync : IpcService
|
||||||
|
{
|
||||||
|
public IShopServiceAsync() { }
|
||||||
|
}
|
||||||
|
}
|
|
@ -165,7 +165,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns
|
||||||
// CreateEcPurchasedEventManager() -> object<nn::ec::IPurchaseEventManager>
|
// CreateEcPurchasedEventManager() -> object<nn::ec::IPurchaseEventManager>
|
||||||
public ResultCode CreateEcPurchasedEventManager(ServiceCtx context)
|
public ResultCode CreateEcPurchasedEventManager(ServiceCtx context)
|
||||||
{
|
{
|
||||||
MakeObject(context, new IPurchaseEventManager());
|
MakeObject(context, new IPurchaseEventManager(context.Device.System));
|
||||||
|
|
||||||
Logger.PrintStub(LogClass.ServiceNs);
|
Logger.PrintStub(LogClass.ServiceNs);
|
||||||
|
|
||||||
|
@ -178,7 +178,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns
|
||||||
{
|
{
|
||||||
// Very similar to CreateEcPurchasedEventManager but with some extra code
|
// Very similar to CreateEcPurchasedEventManager but with some extra code
|
||||||
|
|
||||||
MakeObject(context, new IPurchaseEventManager());
|
MakeObject(context, new IPurchaseEventManager(context.Device.System));
|
||||||
|
|
||||||
Logger.PrintStub(LogClass.ServiceNs);
|
Logger.PrintStub(LogClass.ServiceNs);
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,52 @@
|
||||||
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.HLE.HOS.Ipc;
|
||||||
|
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||||
|
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ns
|
namespace Ryujinx.HLE.HOS.Services.Ns
|
||||||
{
|
{
|
||||||
class IPurchaseEventManager : IpcService
|
class IPurchaseEventManager : IpcService
|
||||||
{
|
{
|
||||||
// TODO: Implement this
|
private readonly KEvent _purchasedEvent;
|
||||||
// Size seems to be atleast 0x7a8
|
|
||||||
|
public IPurchaseEventManager(Horizon system)
|
||||||
|
{
|
||||||
|
_purchasedEvent = new KEvent(system.KernelContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command(0)]
|
||||||
|
// SetDefaultDeliveryTarget(pid, buffer<bytes, 5> unknown)
|
||||||
|
public ResultCode SetDefaultDeliveryTarget(ServiceCtx context)
|
||||||
|
{
|
||||||
|
long inBufferPosition = context.Request.SendBuff[0].Position;
|
||||||
|
long inBufferSize = context.Request.SendBuff[0].Size;
|
||||||
|
byte[] buffer = new byte[inBufferSize];
|
||||||
|
|
||||||
|
context.Memory.Read((ulong)inBufferPosition, buffer);
|
||||||
|
|
||||||
|
// NOTE: Service use the pid to call arp:r GetApplicationLaunchProperty and store it in internal field.
|
||||||
|
// Then it seems to use the buffer content and compare it with a stored linked instrusive list.
|
||||||
|
// Since we don't support purchase from eShop, we can stub it.
|
||||||
|
|
||||||
|
Logger.PrintStub(LogClass.ServiceNs);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command(2)]
|
||||||
|
// GetPurchasedEventReadableHandle() -> handle<copy, event>
|
||||||
|
public ResultCode GetPurchasedEventReadableHandle(ServiceCtx context)
|
||||||
|
{
|
||||||
|
if (context.Process.HandleTable.GenerateHandle(_purchasedEvent.ReadableEvent, out int handle) != KernelResult.Success)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Out of handles!");
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -414,10 +414,10 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
|
||||||
|
|
||||||
if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceWait) && !_device.System.HostSyncpoint.IsSyncpointExpired(header.Fence.Id, header.Fence.Value))
|
if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceWait) && !_device.System.HostSyncpoint.IsSyncpointExpired(header.Fence.Id, header.Fence.Value))
|
||||||
{
|
{
|
||||||
_device.Gpu.DmaPusher.PushHostCommandBuffer(CreateWaitCommandBuffer(header.Fence));
|
_device.Gpu.GPFifo.PushHostCommandBuffer(CreateWaitCommandBuffer(header.Fence));
|
||||||
}
|
}
|
||||||
|
|
||||||
_device.Gpu.DmaPusher.PushEntries(entries);
|
_device.Gpu.GPFifo.PushEntries(entries);
|
||||||
|
|
||||||
header.Fence.Id = _channelSyncpoint.Id;
|
header.Fence.Id = _channelSyncpoint.Id;
|
||||||
|
|
||||||
|
@ -439,12 +439,12 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
|
||||||
|
|
||||||
if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceIncrement))
|
if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceIncrement))
|
||||||
{
|
{
|
||||||
_device.Gpu.DmaPusher.PushHostCommandBuffer(CreateIncrementCommandBuffer(ref header.Fence, header.Flags));
|
_device.Gpu.GPFifo.PushHostCommandBuffer(CreateIncrementCommandBuffer(ref header.Fence, header.Flags));
|
||||||
}
|
}
|
||||||
|
|
||||||
header.Flags = SubmitGpfifoFlags.None;
|
header.Flags = SubmitGpfifoFlags.None;
|
||||||
|
|
||||||
_device.Gpu.DmaPusher.SignalNewEntries();
|
_device.Gpu.GPFifo.SignalNewEntries();
|
||||||
|
|
||||||
return NvInternalResult.Success;
|
return NvInternalResult.Success;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi
|
||||||
Success = 0,
|
Success = 0,
|
||||||
|
|
||||||
InvalidArguments = (1 << ErrorCodeShift) | ModuleId,
|
InvalidArguments = (1 << ErrorCodeShift) | ModuleId,
|
||||||
|
InvalidLayerSize = (4 << ErrorCodeShift) | ModuleId,
|
||||||
InvalidScalingMode = (6 << ErrorCodeShift) | ModuleId
|
InvalidScalingMode = (6 << ErrorCodeShift) | ModuleId
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Cpu;
|
using Ryujinx.Cpu;
|
||||||
using Ryujinx.HLE.HOS.Ipc;
|
using Ryujinx.HLE.HOS.Ipc;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||||
|
@ -190,7 +191,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
|
||||||
public ResultCode SetLayerScalingMode(ServiceCtx context)
|
public ResultCode SetLayerScalingMode(ServiceCtx context)
|
||||||
{
|
{
|
||||||
int scalingMode = context.RequestData.ReadInt32();
|
int scalingMode = context.RequestData.ReadInt32();
|
||||||
long unknown = context.RequestData.ReadInt64();
|
long layerId = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
@ -235,6 +236,53 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Command(2460)]
|
||||||
|
// GetIndirectLayerImageRequiredMemoryInfo(u64 width, u64 height) -> (u64 size, u64 alignment)
|
||||||
|
public ResultCode GetIndirectLayerImageRequiredMemoryInfo(ServiceCtx context)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
// Doesn't occur in our case.
|
||||||
|
if (sizePtr == null || address_alignmentPtr == null)
|
||||||
|
{
|
||||||
|
return ResultCode.InvalidArguments;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
int width = (int)context.RequestData.ReadUInt64();
|
||||||
|
int height = (int)context.RequestData.ReadUInt64();
|
||||||
|
|
||||||
|
if (height < 0 || width < 0)
|
||||||
|
{
|
||||||
|
return ResultCode.InvalidLayerSize;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
// Doesn't occur in our case.
|
||||||
|
if (!service_initialized)
|
||||||
|
{
|
||||||
|
return ResultCode.InvalidArguments;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
const ulong defaultAlignment = 0x1000;
|
||||||
|
const ulong defaultSize = 0x20000;
|
||||||
|
|
||||||
|
// NOTE: The official service setup a A8B8G8R8 texture with a linear layout and then query its size.
|
||||||
|
// As we don't need this texture on the emulator, we can just simplify this logic and directly
|
||||||
|
// do a linear layout size calculation. (stride * height * bytePerPixel)
|
||||||
|
int pitch = BitUtils.AlignUp(BitUtils.DivRoundUp(width * 32, 8), 64);
|
||||||
|
int memorySize = pitch * BitUtils.AlignUp(height, 64);
|
||||||
|
ulong requiredMemorySize = (ulong)BitUtils.AlignUp(memorySize, (int)defaultAlignment);
|
||||||
|
ulong size = (requiredMemorySize + defaultSize - 1) / defaultSize * defaultSize;
|
||||||
|
|
||||||
|
context.ResponseData.Write(size);
|
||||||
|
context.ResponseData.Write(defaultAlignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
[Command(5202)]
|
[Command(5202)]
|
||||||
// GetDisplayVsyncEvent(u64) -> handle<copy>
|
// GetDisplayVsyncEvent(u64) -> handle<copy>
|
||||||
public ResultCode GetDisplayVSyncEvent(ServiceCtx context)
|
public ResultCode GetDisplayVSyncEvent(ServiceCtx context)
|
||||||
|
|
|
@ -58,7 +58,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Concentus" Version="1.1.7" />
|
<PackageReference Include="Concentus" Version="1.1.7" />
|
||||||
<PackageReference Include="LibHac" Version="0.11.2" />
|
<PackageReference Include="LibHac" Version="0.11.3" />
|
||||||
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
|
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -148,12 +148,12 @@ namespace Ryujinx.HLE
|
||||||
|
|
||||||
public bool WaitFifo()
|
public bool WaitFifo()
|
||||||
{
|
{
|
||||||
return Gpu.DmaPusher.WaitForCommands();
|
return Gpu.GPFifo.WaitForCommands();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ProcessFrame()
|
public void ProcessFrame()
|
||||||
{
|
{
|
||||||
Gpu.DmaPusher.DispatchCalls();
|
Gpu.GPFifo.DispatchCalls();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PresentFrame(Action swapBuffersCallback)
|
public void PresentFrame(Action swapBuffersCallback)
|
||||||
|
|
|
@ -94,6 +94,7 @@ namespace Ryujinx.Ui
|
||||||
this.DefaultWidth = monitorWidth < 1280 ? monitorWidth : 1280;
|
this.DefaultWidth = monitorWidth < 1280 ? monitorWidth : 1280;
|
||||||
this.DefaultHeight = monitorHeight < 760 ? monitorHeight : 760;
|
this.DefaultHeight = monitorHeight < 760 ? monitorHeight : 760;
|
||||||
|
|
||||||
|
this.WindowStateEvent += MainWindow_WindowStateEvent;
|
||||||
this.DeleteEvent += Window_Close;
|
this.DeleteEvent += Window_Close;
|
||||||
_fullScreen.Activated += FullScreen_Toggled;
|
_fullScreen.Activated += FullScreen_Toggled;
|
||||||
|
|
||||||
|
@ -192,6 +193,11 @@ namespace Ryujinx.Ui
|
||||||
_statusBar.Hide();
|
_statusBar.Hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void MainWindow_WindowStateEvent(object o, WindowStateEventArgs args)
|
||||||
|
{
|
||||||
|
_fullScreen.Label = args.Event.NewWindowState.HasFlag(Gdk.WindowState.Fullscreen) ? "Exit Fullscreen" : "Enter Fullscreen";
|
||||||
|
}
|
||||||
|
|
||||||
#if USE_DEBUGGING
|
#if USE_DEBUGGING
|
||||||
private void _openDebugger_Opened(object sender, EventArgs e)
|
private void _openDebugger_Opened(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
@ -505,6 +511,11 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
_glWidget.ShowAll();
|
_glWidget.ShowAll();
|
||||||
EditFooterForGameRender();
|
EditFooterForGameRender();
|
||||||
|
|
||||||
|
if (this.Window.State.HasFlag(Gdk.WindowState.Fullscreen))
|
||||||
|
{
|
||||||
|
ToggleExtraWidgets(false);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
_glWidget.WaitEvent.WaitOne();
|
_glWidget.WaitEvent.WaitOne();
|
||||||
|
@ -520,6 +531,11 @@ namespace Ryujinx.Ui
|
||||||
// NOTE: Everything that is here will not be executed when you close the UI.
|
// NOTE: Everything that is here will not be executed when you close the UI.
|
||||||
Application.Invoke(delegate
|
Application.Invoke(delegate
|
||||||
{
|
{
|
||||||
|
if (this.Window.State.HasFlag(Gdk.WindowState.Fullscreen))
|
||||||
|
{
|
||||||
|
ToggleExtraWidgets(true);
|
||||||
|
}
|
||||||
|
|
||||||
_viewBox.Remove(_glWidget);
|
_viewBox.Remove(_glWidget);
|
||||||
_glWidget.Exit();
|
_glWidget.Exit();
|
||||||
|
|
||||||
|
@ -583,10 +599,6 @@ namespace Ryujinx.Ui
|
||||||
_footerBox.Hide();
|
_footerBox.Hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fullScreenToggled = this.Window.State.HasFlag(Gdk.WindowState.Fullscreen);
|
|
||||||
|
|
||||||
_fullScreen.Label = fullScreenToggled ? "Exit Fullscreen" : "Enter Fullscreen";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void UpdateGameMetadata(string titleId)
|
private static void UpdateGameMetadata(string titleId)
|
||||||
|
|
Loading…
Reference in a new issue