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.Memory;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace ARMeilleure.State
|
||||
{
|
||||
class NativeContext : IDisposable
|
||||
{
|
||||
private const int IntSize = 8;
|
||||
private const int VecSize = 16;
|
||||
private const int FlagSize = 4;
|
||||
private const int ExtraSize = 8;
|
||||
private unsafe struct NativeCtxStorage
|
||||
{
|
||||
public fixed ulong X[RegisterConsts.IntRegsCount];
|
||||
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 +
|
||||
RegisterConsts.VecRegsCount * VecSize +
|
||||
RegisterConsts.FlagsCount * FlagSize +
|
||||
RegisterConsts.FpFlagsCount * FlagSize + ExtraSize;
|
||||
private static NativeCtxStorage _dummyStorage = new NativeCtxStorage();
|
||||
|
||||
private readonly IJitMemoryBlock _block;
|
||||
|
||||
|
@ -23,179 +25,150 @@ namespace ARMeilleure.State
|
|||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
int offset = RegisterConsts.IntRegsCount * IntSize + index * VecSize;
|
||||
|
||||
return new V128(
|
||||
Marshal.ReadInt64(BasePtr, offset + 0),
|
||||
Marshal.ReadInt64(BasePtr, offset + 8));
|
||||
return new V128(GetStorage().V[index * 2 + 0], GetStorage().V[index * 2 + 1]);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
int offset = RegisterConsts.IntRegsCount * IntSize + index * VecSize;
|
||||
|
||||
Marshal.WriteInt64(BasePtr, offset + 0, value.Extract<long>(0));
|
||||
Marshal.WriteInt64(BasePtr, offset + 8, value.Extract<long>(1));
|
||||
GetStorage().V[index * 2 + 0] = value.Extract<ulong>(0);
|
||||
GetStorage().V[index * 2 + 1] = value.Extract<ulong>(1);
|
||||
}
|
||||
|
||||
public bool GetPstateFlag(PState flag)
|
||||
public unsafe bool GetPstateFlag(PState flag)
|
||||
{
|
||||
if ((uint)flag >= RegisterConsts.FlagsCount)
|
||||
{
|
||||
throw new ArgumentException($"Invalid flag \"{flag}\" specified.");
|
||||
}
|
||||
|
||||
int offset =
|
||||
RegisterConsts.IntRegsCount * IntSize +
|
||||
RegisterConsts.VecRegsCount * VecSize + (int)flag * FlagSize;
|
||||
|
||||
int value = Marshal.ReadInt32(BasePtr, offset);
|
||||
|
||||
return value != 0;
|
||||
return GetStorage().Flags[(int)flag] != 0;
|
||||
}
|
||||
|
||||
public void SetPstateFlag(PState flag, bool value)
|
||||
public unsafe void SetPstateFlag(PState flag, bool value)
|
||||
{
|
||||
if ((uint)flag >= RegisterConsts.FlagsCount)
|
||||
{
|
||||
throw new ArgumentException($"Invalid flag \"{flag}\" specified.");
|
||||
}
|
||||
|
||||
int offset =
|
||||
RegisterConsts.IntRegsCount * IntSize +
|
||||
RegisterConsts.VecRegsCount * VecSize + (int)flag * FlagSize;
|
||||
|
||||
Marshal.WriteInt32(BasePtr, offset, value ? 1 : 0);
|
||||
GetStorage().Flags[(int)flag] = value ? 1u : 0u;
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
int offset =
|
||||
RegisterConsts.IntRegsCount * IntSize +
|
||||
RegisterConsts.VecRegsCount * VecSize +
|
||||
RegisterConsts.FlagsCount * FlagSize + (int)flag * FlagSize;
|
||||
|
||||
int value = Marshal.ReadInt32(BasePtr, offset);
|
||||
|
||||
return value != 0;
|
||||
return GetStorage().FpFlags[(int)flag] != 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.");
|
||||
}
|
||||
|
||||
int offset =
|
||||
RegisterConsts.IntRegsCount * IntSize +
|
||||
RegisterConsts.VecRegsCount * VecSize +
|
||||
RegisterConsts.FlagsCount * FlagSize + (int)flag * FlagSize;
|
||||
|
||||
Marshal.WriteInt32(BasePtr, offset, value ? 1 : 0);
|
||||
GetStorage().FpFlags[(int)flag] = value ? 1u : 0u;
|
||||
}
|
||||
|
||||
public int GetCounter()
|
||||
{
|
||||
return Marshal.ReadInt32(BasePtr, GetCounterOffset());
|
||||
}
|
||||
public int GetCounter() => GetStorage().Counter;
|
||||
public void SetCounter(int value) => GetStorage().Counter = value;
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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 +
|
||||
RegisterConsts.VecRegsCount * VecSize + reg.Index * FlagSize;
|
||||
if ((uint)reg.Index >= RegisterConsts.FlagsCount)
|
||||
{
|
||||
throw new ArgumentException("Invalid register.");
|
||||
}
|
||||
|
||||
size = FlagSize;
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Flags[reg.Index]);
|
||||
}
|
||||
|
||||
if ((uint)(offset + size) > (uint)TotalSize)
|
||||
else /* if (reg.Type == RegisterType.FpFlag) */
|
||||
{
|
||||
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()
|
||||
{
|
||||
return RegisterConsts.IntRegsCount * IntSize +
|
||||
RegisterConsts.VecRegsCount * VecSize +
|
||||
RegisterConsts.FlagsCount * FlagSize +
|
||||
RegisterConsts.FpFlagsCount * FlagSize;
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Counter);
|
||||
}
|
||||
|
||||
public static int GetCallAddressOffset()
|
||||
{
|
||||
return RegisterConsts.IntRegsCount * IntSize +
|
||||
RegisterConsts.VecRegsCount * VecSize +
|
||||
RegisterConsts.FlagsCount * FlagSize +
|
||||
RegisterConsts.FpFlagsCount * FlagSize + 4;
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.CallAddress);
|
||||
}
|
||||
|
||||
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,
|
||||
ServiceNfp,
|
||||
ServiceNifm,
|
||||
ServiceNim,
|
||||
ServiceNs,
|
||||
ServiceNsd,
|
||||
ServiceNv,
|
||||
|
|
|
@ -90,14 +90,12 @@ namespace Ryujinx.Graphics.Device
|
|||
{
|
||||
int alignedOffset = Align(offset);
|
||||
|
||||
GetRef<int>(alignedOffset) = data;
|
||||
|
||||
if (_writeCallbacks.TryGetValue(alignedOffset, out Action<int> write))
|
||||
{
|
||||
write(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
GetRef<int>(alignedOffset) = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -230,6 +230,25 @@ namespace Ryujinx.Graphics.GAL
|
|||
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>
|
||||
/// Checks if the texture format is a depth, stencil or depth-stencil format.
|
||||
/// </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);
|
||||
|
||||
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);
|
||||
|
||||
|
|
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
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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
|
||||
{
|
||||
|
@ -107,8 +107,8 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
|||
evt?.Flush();
|
||||
evt2?.Flush();
|
||||
|
||||
ulong x = _context.MemoryAccessor.Read<ulong>(gpuVa);
|
||||
ulong y = _context.MemoryAccessor.Read<ulong>(gpuVa + 16);
|
||||
ulong x = _context.MemoryManager.Read<ulong>(gpuVa);
|
||||
ulong y = _context.MemoryManager.Read<ulong>(gpuVa + 16);
|
||||
|
||||
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);
|
||||
|
||||
_context.MemoryAccessor.Write(rs.Address.Pack(), rs.Payload);
|
||||
_context.MemoryManager.Write(rs.Address.Pack(), rs.Payload);
|
||||
|
||||
_context.AdvanceSequence();
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
|||
|
||||
if (counter?.Invalid != true)
|
||||
{
|
||||
_context.MemoryAccessor.Write(gpuVa, counterData);
|
||||
_context.MemoryManager.Write(gpuVa, counterData);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using Ryujinx.Graphics.Gpu.State;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine
|
||||
{
|
||||
|
@ -13,11 +15,27 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
|||
{
|
||||
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);
|
||||
|
||||
_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);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Updates host state based on the current guest GPU state.
|
||||
/// </summary>
|
||||
|
@ -582,7 +568,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
|||
enables |= (depthBias.LineEnable ? PolygonModeMask.Line : 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>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine;
|
||||
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Gpu.Synchronization;
|
||||
using System;
|
||||
|
@ -26,25 +27,15 @@ namespace Ryujinx.Graphics.Gpu
|
|||
/// </summary>
|
||||
public MemoryManager MemoryManager { get; }
|
||||
|
||||
/// <summary>
|
||||
/// GPU memory accessor.
|
||||
/// </summary>
|
||||
public MemoryAccessor MemoryAccessor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// GPU engine methods processing.
|
||||
/// </summary>
|
||||
internal Methods Methods { get; }
|
||||
|
||||
/// <summary>
|
||||
/// GPU commands FIFO.
|
||||
/// GPU General Purpose FIFO queue.
|
||||
/// </summary>
|
||||
internal NvGpuFifo Fifo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// DMA pusher.
|
||||
/// </summary>
|
||||
public DmaPusher DmaPusher { get; }
|
||||
public GPFifoDevice GPFifo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// GPU synchronization manager.
|
||||
|
@ -79,13 +70,9 @@ namespace Ryujinx.Graphics.Gpu
|
|||
|
||||
MemoryManager = new MemoryManager(this);
|
||||
|
||||
MemoryAccessor = new MemoryAccessor(this);
|
||||
|
||||
Methods = new Methods(this);
|
||||
|
||||
Fifo = new NvGpuFifo(this);
|
||||
|
||||
DmaPusher = new DmaPusher(this);
|
||||
GPFifo = new GPFifoDevice(this);
|
||||
|
||||
Synchronization = new SynchronizationManager();
|
||||
|
||||
|
@ -125,6 +112,7 @@ namespace Ryujinx.Graphics.Gpu
|
|||
Methods.BufferManager.Dispose();
|
||||
Methods.TextureManager.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>
|
||||
/// 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>
|
||||
|
@ -87,6 +86,19 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
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>
|
||||
/// Writes data to GPU mapped memory.
|
||||
/// </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>
|
||||
<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.Common\Ryujinx.Common.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>
|
||||
public T MemoryRead<T>(ulong address) where T : unmanaged
|
||||
{
|
||||
return _context.MemoryAccessor.Read<T>(address);
|
||||
return _context.MemoryManager.Read<T>(address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -262,13 +262,13 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -307,7 +307,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
|
||||
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);
|
||||
|
||||
|
@ -344,8 +344,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
{
|
||||
ShaderProgram program = Translator.Translate(gpuVaA, gpuVa, gpuAccessor, DefaultFlags);
|
||||
|
||||
byte[] codeA = _context.MemoryAccessor.ReadBytes(gpuVaA, program.SizeA);
|
||||
byte[] codeB = _context.MemoryAccessor.ReadBytes(gpuVa, program.Size);
|
||||
byte[] codeA = _context.MemoryManager.GetSpan(gpuVaA, program.SizeA).ToArray();
|
||||
byte[] codeB = _context.MemoryManager.GetSpan(gpuVa, program.Size).ToArray();
|
||||
|
||||
_dumper.Dump(codeA, compute: false, out string fullPathA, out string codePathA);
|
||||
_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);
|
||||
|
||||
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);
|
||||
|
||||
|
|
|
@ -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
|
||||
{
|
||||
BindChannel = 0x0,
|
||||
Semaphore = 0x4,
|
||||
FenceValue = 0x1c,
|
||||
FenceAction = 0x1d,
|
||||
WaitForIdle = 0x44,
|
||||
MacroUploadAddress = 0x45,
|
||||
SendMacroCodeData = 0x46,
|
||||
MacroBindingIndex = 0x47,
|
||||
BindMacro = 0x48,
|
||||
SetMmeShadowRamControl = 0x49,
|
||||
I2mParams = 0x60,
|
||||
LaunchDma = 0x6c,
|
||||
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.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.B8G8R8X8Unorm, new FormatInfo(4, true, false, All.Rgba8, PixelFormat.Bgra, PixelType.UnsignedByte));
|
||||
Add(Format.B8G8R8A8Unorm, new FormatInfo(4, true, false, All.Rgba8, PixelFormat.Bgra, PixelType.UnsignedByte));
|
||||
Add(Format.B8G8R8X8Srgb, new FormatInfo(4, false, false, All.Srgb8, PixelFormat.BgraInteger, PixelType.UnsignedByte));
|
||||
Add(Format.B8G8R8A8Srgb, new FormatInfo(4, false, false, All.Srgb8Alpha8, PixelFormat.BgraInteger, 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.Rgba, 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.Rgba, PixelType.UnsignedByte));
|
||||
}
|
||||
|
||||
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> _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<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 SupportsImageLoadFormatted => _supportsImageLoadFormatted.Value;
|
||||
public static bool SupportsPolygonOffsetClamp => _supportsPolygonOffsetClamp.Value;
|
||||
public static bool SupportsViewportSwizzle => _supportsViewportSwizzle.Value;
|
||||
public static bool SupportsNonConstantTextureOffset => _gpuVendor.Value == GpuVendor.Nvidia;
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
{
|
||||
public int Handle { get; protected set; }
|
||||
|
||||
protected TextureCreateInfo Info { get; }
|
||||
public TextureCreateInfo Info { get; }
|
||||
|
||||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
|
|
|
@ -11,6 +11,9 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
private int _srcFramebuffer;
|
||||
private int _dstFramebuffer;
|
||||
|
||||
private int _copyPboHandle;
|
||||
private int _copyPboSize;
|
||||
|
||||
public TextureCopy(Renderer renderer)
|
||||
{
|
||||
_renderer = renderer;
|
||||
|
@ -23,12 +26,14 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
Extents2D dstRegion,
|
||||
bool linearFilter)
|
||||
{
|
||||
TextureView srcConverted = src.Format.IsBgra8() != dst.Format.IsBgra8() ? BgraSwap(src) : src;
|
||||
|
||||
(int oldDrawFramebufferHandle, int oldReadFramebufferHandle) = ((Pipeline)_renderer.Pipeline).GetBoundFramebuffers();
|
||||
|
||||
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, GetSrcFramebufferLazy());
|
||||
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);
|
||||
|
||||
ClearBufferMask mask = GetMask(src.Format);
|
||||
|
@ -68,6 +73,11 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
|
||||
((Pipeline)_renderer.Pipeline).RestoreScissor0Enable();
|
||||
((Pipeline)_renderer.Pipeline).RestoreRasterizerDiscard();
|
||||
|
||||
if (srcConverted != src)
|
||||
{
|
||||
srcConverted.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static void Attach(FramebufferTarget target, Format format, int handle)
|
||||
|
@ -117,6 +127,52 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
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()
|
||||
{
|
||||
if (_srcFramebuffer == 0)
|
||||
|
@ -152,6 +208,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
|
||||
_dstFramebuffer = 0;
|
||||
}
|
||||
|
||||
if (_copyPboHandle != 0)
|
||||
{
|
||||
GL.DeleteBuffer(_copyPboHandle);
|
||||
|
||||
_copyPboHandle = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,6 +72,15 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
(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);
|
||||
|
||||
int maxLevel = Info.Levels - 1;
|
||||
|
@ -189,7 +198,12 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
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();
|
||||
|
||||
|
@ -197,6 +211,14 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
|
||||
FormatInfo format = FormatTable.GetFormatInfo(Info.Format);
|
||||
|
||||
PixelFormat pixelFormat = format.PixelFormat;
|
||||
PixelType pixelType = format.PixelType;
|
||||
|
||||
if (forceBgra)
|
||||
{
|
||||
pixelFormat = PixelFormat.Bgra;
|
||||
}
|
||||
|
||||
int faces = 1;
|
||||
|
||||
if (target == TextureTarget.TextureCubeMap)
|
||||
|
@ -214,20 +236,15 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
|
||||
if (format.IsCompressed)
|
||||
{
|
||||
GL.GetCompressedTexImage(target + face, level, ptr + faceOffset);
|
||||
GL.GetCompressedTexImage(target + face, level, data + faceOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
GL.GetTexImage(
|
||||
target + face,
|
||||
level,
|
||||
format.PixelFormat,
|
||||
format.PixelType,
|
||||
ptr + faceOffset);
|
||||
GL.GetTexImage(target + face, level, pixelFormat, pixelType, data + faceOffset);
|
||||
}
|
||||
}
|
||||
|
||||
ptr += Info.GetMipSize(level);
|
||||
data += Info.GetMipSize(level);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -237,12 +254,17 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
{
|
||||
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();
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
private int _boundDrawFramebuffer;
|
||||
private int _boundReadFramebuffer;
|
||||
|
||||
private int[] _fpIsBgra = new int[8];
|
||||
private float[] _fpRenderScale = new float[33];
|
||||
private float[] _cpRenderScale = new float[32];
|
||||
|
||||
|
@ -609,9 +610,14 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
return;
|
||||
}
|
||||
|
||||
GL.PolygonOffset(factor, units / 2f);
|
||||
// TODO: Enable when GL_EXT_polygon_offset_clamp is supported.
|
||||
// GL.PolygonOffsetClamp(factor, units, clamp);
|
||||
if (HwCapabilities.SupportsPolygonOffsetClamp)
|
||||
{
|
||||
GL.PolygonOffsetClamp(factor, units, clamp);
|
||||
}
|
||||
else
|
||||
{
|
||||
GL.PolygonOffset(factor, units);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDepthClamp(bool clamp)
|
||||
|
@ -765,6 +771,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
_program.Bind();
|
||||
}
|
||||
|
||||
UpdateFpIsBgra();
|
||||
SetRenderTargetScale(_fpRenderScale[0]);
|
||||
}
|
||||
|
||||
|
@ -814,12 +821,15 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
TextureView color = (TextureView)colors[index];
|
||||
|
||||
_framebuffer.AttachColor(index, color);
|
||||
|
||||
_fpIsBgra[index] = color != null && color.Format.IsBgra8() ? 1 : 0;
|
||||
}
|
||||
|
||||
UpdateFpIsBgra();
|
||||
|
||||
TextureView depthStencilView = (TextureView)depthStencil;
|
||||
|
||||
_framebuffer.AttachDepthStencil(depthStencilView);
|
||||
|
||||
_framebuffer.SetDrawBuffers(colors.Length);
|
||||
|
||||
_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 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;
|
||||
}
|
||||
}
|
||||
|
@ -1112,6 +1124,14 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
return (_boundDrawFramebuffer, _boundReadFramebuffer);
|
||||
}
|
||||
|
||||
private void UpdateFpIsBgra()
|
||||
{
|
||||
if (_program != null)
|
||||
{
|
||||
GL.Uniform1(_program.FragmentIsBgraUniform, 8, _fpIsBgra);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateDepthTest()
|
||||
{
|
||||
// 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()
|
||||
{
|
||||
if (_unit0Texture != null)
|
||||
|
@ -1230,28 +1273,5 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
_framebuffer?.Dispose();
|
||||
_vertexArray?.Dispose();
|
||||
}
|
||||
|
||||
public void UpdateRenderScale(ShaderStage stage, int textureCount)
|
||||
{
|
||||
if (_program != null)
|
||||
{
|
||||
switch (stage)
|
||||
{
|
||||
case ShaderStage.Fragment:
|
||||
if (_program.FragmentRenderScaleUniform != -1)
|
||||
{
|
||||
GL.Uniform1(_program.FragmentRenderScaleUniform, textureCount + 1, _fpRenderScale);
|
||||
}
|
||||
break;
|
||||
|
||||
case ShaderStage.Compute:
|
||||
if (_program.ComputeRenderScaleUniform != -1)
|
||||
{
|
||||
GL.Uniform1(_program.ComputeRenderScaleUniform, textureCount, _cpRenderScale);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
|
||||
public int Handle { get; private set; }
|
||||
|
||||
public int FragmentIsBgraUniform { get; }
|
||||
public int FragmentRenderScaleUniform { 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");
|
||||
ComputeRenderScaleUniform = GL.GetUniformLocation(Handle, "cp_renderScale");
|
||||
}
|
||||
|
|
|
@ -51,10 +51,12 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, drawFramebuffer);
|
||||
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, readFramebuffer);
|
||||
|
||||
TextureView viewConverted = view.Format.IsBgra8() ? _renderer.TextureCopy.BgraSwap(view) : view;
|
||||
|
||||
GL.FramebufferTexture(
|
||||
FramebufferTarget.ReadFramebuffer,
|
||||
FramebufferAttachment.ColorAttachment0,
|
||||
view.Handle,
|
||||
viewConverted.Handle,
|
||||
0);
|
||||
|
||||
GL.ReadBuffer(ReadBufferMode.ColorAttachment0);
|
||||
|
@ -138,6 +140,11 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
|
||||
((Pipeline)_renderer.Pipeline).RestoreScissor0Enable();
|
||||
((Pipeline)_renderer.Pipeline).RestoreRasterizerDiscard();
|
||||
|
||||
if (viewConverted != view)
|
||||
{
|
||||
viewConverted.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
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.AppendLine($"uniform bool {DefaultNames.IsBgraName}[8];");
|
||||
context.AppendLine();
|
||||
}
|
||||
|
||||
if (DeclareRenderScale(context))
|
||||
{
|
||||
context.AppendLine();
|
||||
|
|
|
@ -23,5 +23,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||
public const string SharedMemoryName = "shared_mem";
|
||||
|
||||
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.LeMask, new BuiltInAttribute("unpackUint2x32(gl_SubGroupLeMaskARB).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;
|
||||
|
@ -149,8 +159,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||
|
||||
char swzMask = GetSwizzleMask((value >> 2) & 3);
|
||||
|
||||
if (value >= AttributeConsts.UserAttributeBase &&
|
||||
value < AttributeConsts.UserAttributeEnd)
|
||||
if (value >= AttributeConsts.UserAttributeBase && value < AttributeConsts.UserAttributeEnd)
|
||||
{
|
||||
value -= AttributeConsts.UserAttributeBase;
|
||||
|
||||
|
@ -169,8 +178,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||
}
|
||||
else
|
||||
{
|
||||
if (value >= AttributeConsts.FragmentOutputColorBase &&
|
||||
value < AttributeConsts.FragmentOutputColorEnd)
|
||||
if (value >= AttributeConsts.FragmentOutputColorBase && value < AttributeConsts.FragmentOutputColorEnd)
|
||||
{
|
||||
value -= AttributeConsts.FragmentOutputColorBase;
|
||||
|
||||
|
|
|
@ -35,6 +35,9 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
public const int FragmentOutputColorBase = 0x1000010;
|
||||
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 ThreadIdY = 0x2000004;
|
||||
public const int ThreadIdZ = 0x2000008;
|
||||
|
|
|
@ -115,22 +115,46 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
|
||||
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++)
|
||||
{
|
||||
if (target.ComponentEnabled(component))
|
||||
if (!target.ComponentEnabled(component))
|
||||
{
|
||||
Operand dest = Attribute(AttributeConsts.FragmentOutputColorBase + attachment * 16 + component * 4);
|
||||
|
||||
Operand src = Register(regIndex, RegisterType.Gpr);
|
||||
|
||||
this.Copy(dest, src);
|
||||
|
||||
regIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
[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.
|
||||
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)
|
||||
{
|
||||
|
@ -108,9 +119,11 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
|||
// SetScreenShotPermission(u32)
|
||||
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;
|
||||
}
|
||||
|
@ -119,9 +132,11 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
|||
// SetOperationModeChangedNotification(b8)
|
||||
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;
|
||||
}
|
||||
|
@ -130,9 +145,11 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
|||
// SetPerformanceModeChangedNotification(b8)
|
||||
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;
|
||||
}
|
||||
|
@ -141,11 +158,11 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
|||
// SetFocusHandlingMode(b8, b8, b8)
|
||||
public ResultCode SetFocusHandlingMode(ServiceCtx context)
|
||||
{
|
||||
bool flag1 = context.RequestData.ReadByte() != 0;
|
||||
bool flag2 = context.RequestData.ReadByte() != 0;
|
||||
bool flag3 = context.RequestData.ReadByte() != 0;
|
||||
bool unknownFlag1 = context.RequestData.ReadBoolean();
|
||||
bool unknownFlag2 = context.RequestData.ReadBoolean();
|
||||
bool unknownFlag3 = context.RequestData.ReadBoolean();
|
||||
|
||||
Logger.PrintStub(LogClass.ServiceAm);
|
||||
Logger.PrintStub(LogClass.ServiceAm, new { unknownFlag1, unknownFlag2, unknownFlag3 });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
@ -154,9 +171,11 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
|||
// SetRestartMessageEnabled(b8)
|
||||
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;
|
||||
}
|
||||
|
@ -165,19 +184,24 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
|||
// SetOutOfFocusSuspendingEnabled(b8)
|
||||
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;
|
||||
}
|
||||
|
||||
[Command(19)] // 3.0.0+
|
||||
// SetScreenShotImageOrientation(u32)
|
||||
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;
|
||||
}
|
||||
|
@ -186,9 +210,11 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
|||
// SetHandlesRequestToDisplay(b8)
|
||||
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;
|
||||
}
|
||||
|
@ -197,9 +223,11 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
|||
// SetIdleTimeDetectionExtension(u32)
|
||||
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;
|
||||
}
|
||||
|
@ -215,6 +243,26 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
|||
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+
|
||||
// GetAccumulatedSuspendedTickValue() -> u64
|
||||
public ResultCode GetAccumulatedSuspendedTickValue(ServiceCtx context)
|
||||
|
@ -244,5 +292,16 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
|||
|
||||
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
|
||||
public ResultCode NotifyRunning(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write(1);
|
||||
context.ResponseData.Write(true);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
@ -195,6 +195,17 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
|||
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+
|
||||
// InitializeApplicationCopyrightFrameBuffer(s32 width, s32 height, handle<copy, transfer_memory> transfer_memory, u64 transfer_memory_size)
|
||||
public ResultCode InitializeApplicationCopyrightFrameBuffer(ServiceCtx context)
|
||||
|
@ -319,6 +330,22 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
|||
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+
|
||||
// GetGpuErrorDetectedSystemEvent() -> handle<copy>
|
||||
public ResultCode GetGpuErrorDetectedSystemEvent(ServiceCtx context)
|
||||
|
|
|
@ -22,6 +22,7 @@ namespace Ryujinx.HLE.HOS.Services.Am
|
|||
StackPoolExhausted = (712 << ErrorCodeShift) | ModuleId,
|
||||
DebugModeNotEnabled = (974 << 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")]
|
||||
class IAudioInManager : IpcService
|
||||
{
|
||||
private const string DefaultAudioInsName = "BuiltInHeadset";
|
||||
|
||||
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+
|
||||
class IShopServiceAccessServerInterface : IpcService
|
||||
{
|
||||
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>
|
||||
public ResultCode CreateEcPurchasedEventManager(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new IPurchaseEventManager());
|
||||
MakeObject(context, new IPurchaseEventManager(context.Device.System));
|
||||
|
||||
Logger.PrintStub(LogClass.ServiceNs);
|
||||
|
||||
|
@ -178,7 +178,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns
|
|||
{
|
||||
// Very similar to CreateEcPurchasedEventManager but with some extra code
|
||||
|
||||
MakeObject(context, new IPurchaseEventManager());
|
||||
MakeObject(context, new IPurchaseEventManager(context.Device.System));
|
||||
|
||||
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
|
||||
{
|
||||
class IPurchaseEventManager : IpcService
|
||||
{
|
||||
// TODO: Implement this
|
||||
// Size seems to be atleast 0x7a8
|
||||
private readonly KEvent _purchasedEvent;
|
||||
|
||||
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))
|
||||
{
|
||||
_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;
|
||||
|
||||
|
@ -439,12 +439,12 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
|
|||
|
||||
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;
|
||||
|
||||
_device.Gpu.DmaPusher.SignalNewEntries();
|
||||
_device.Gpu.GPFifo.SignalNewEntries();
|
||||
|
||||
return NvInternalResult.Success;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi
|
|||
Success = 0,
|
||||
|
||||
InvalidArguments = (1 << ErrorCodeShift) | ModuleId,
|
||||
InvalidLayerSize = (4 << ErrorCodeShift) | ModuleId,
|
||||
InvalidScalingMode = (6 << ErrorCodeShift) | ModuleId
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
|
@ -190,7 +191,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
|
|||
public ResultCode SetLayerScalingMode(ServiceCtx context)
|
||||
{
|
||||
int scalingMode = context.RequestData.ReadInt32();
|
||||
long unknown = context.RequestData.ReadInt64();
|
||||
long layerId = context.RequestData.ReadInt64();
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
@ -235,6 +236,53 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
|
|||
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)]
|
||||
// GetDisplayVsyncEvent(u64) -> handle<copy>
|
||||
public ResultCode GetDisplayVSyncEvent(ServiceCtx context)
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -148,12 +148,12 @@ namespace Ryujinx.HLE
|
|||
|
||||
public bool WaitFifo()
|
||||
{
|
||||
return Gpu.DmaPusher.WaitForCommands();
|
||||
return Gpu.GPFifo.WaitForCommands();
|
||||
}
|
||||
|
||||
public void ProcessFrame()
|
||||
{
|
||||
Gpu.DmaPusher.DispatchCalls();
|
||||
Gpu.GPFifo.DispatchCalls();
|
||||
}
|
||||
|
||||
public void PresentFrame(Action swapBuffersCallback)
|
||||
|
|
|
@ -94,6 +94,7 @@ namespace Ryujinx.Ui
|
|||
this.DefaultWidth = monitorWidth < 1280 ? monitorWidth : 1280;
|
||||
this.DefaultHeight = monitorHeight < 760 ? monitorHeight : 760;
|
||||
|
||||
this.WindowStateEvent += MainWindow_WindowStateEvent;
|
||||
this.DeleteEvent += Window_Close;
|
||||
_fullScreen.Activated += FullScreen_Toggled;
|
||||
|
||||
|
@ -192,6 +193,11 @@ namespace Ryujinx.Ui
|
|||
_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
|
||||
private void _openDebugger_Opened(object sender, EventArgs e)
|
||||
{
|
||||
|
@ -505,6 +511,11 @@ namespace Ryujinx.Ui
|
|||
|
||||
_glWidget.ShowAll();
|
||||
EditFooterForGameRender();
|
||||
|
||||
if (this.Window.State.HasFlag(Gdk.WindowState.Fullscreen))
|
||||
{
|
||||
ToggleExtraWidgets(false);
|
||||
}
|
||||
});
|
||||
|
||||
_glWidget.WaitEvent.WaitOne();
|
||||
|
@ -520,6 +531,11 @@ namespace Ryujinx.Ui
|
|||
// NOTE: Everything that is here will not be executed when you close the UI.
|
||||
Application.Invoke(delegate
|
||||
{
|
||||
if (this.Window.State.HasFlag(Gdk.WindowState.Fullscreen))
|
||||
{
|
||||
ToggleExtraWidgets(true);
|
||||
}
|
||||
|
||||
_viewBox.Remove(_glWidget);
|
||||
_glWidget.Exit();
|
||||
|
||||
|
@ -583,10 +599,6 @@ namespace Ryujinx.Ui
|
|||
_footerBox.Hide();
|
||||
}
|
||||
}
|
||||
|
||||
bool fullScreenToggled = this.Window.State.HasFlag(Gdk.WindowState.Fullscreen);
|
||||
|
||||
_fullScreen.Label = fullScreenToggled ? "Exit Fullscreen" : "Enter Fullscreen";
|
||||
}
|
||||
|
||||
private static void UpdateGameMetadata(string titleId)
|
||||
|
|
Loading…
Reference in a new issue