Merge branch 'a32_tester_fpscr_followup' of https://github.com/LDj3SNuD/Ryujinx into a32_tester_fpscr_followup

This commit is contained in:
LDj3SNuD 2020-07-27 16:57:10 +02:00
commit 5a91efb525
59 changed files with 1720 additions and 1006 deletions

View file

@ -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;
size = IntSize;
}
else if (reg.Type == RegisterType.Vector)
{
offset = RegisterConsts.IntRegsCount * IntSize + reg.Index * VecSize;
size = VecSize;
}
else /* if (reg.Type == RegisterType.Flag) */
{
offset = RegisterConsts.IntRegsCount * IntSize +
RegisterConsts.VecRegsCount * VecSize + reg.Index * FlagSize;
size = FlagSize;
}
if ((uint)(offset + size) > (uint)TotalSize)
if ((uint)reg.Index >= RegisterConsts.IntRegsCount)
{
throw new ArgumentException("Invalid register.");
}
return offset;
return StorageOffset(ref _dummyStorage, ref _dummyStorage.X[reg.Index]);
}
else if (reg.Type == RegisterType.Vector)
{
if ((uint)reg.Index >= RegisterConsts.VecRegsCount)
{
throw new ArgumentException("Invalid register.");
}
return StorageOffset(ref _dummyStorage, ref _dummyStorage.V[reg.Index * 2]);
}
else if (reg.Type == RegisterType.Flag)
{
if ((uint)reg.Index >= RegisterConsts.FlagsCount)
{
throw new ArgumentException("Invalid register.");
}
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Flags[reg.Index]);
}
else /* if (reg.Type == RegisterType.FpFlag) */
{
if ((uint)reg.Index >= RegisterConsts.FpFlagsCount)
{
throw new ArgumentException("Invalid register.");
}
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();
}
}

View file

@ -37,6 +37,7 @@ namespace Ryujinx.Common.Logging
ServiceMm,
ServiceNfp,
ServiceNifm,
ServiceNim,
ServiceNs,
ServiceNsd,
ServiceNv,

View file

@ -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;
}
}
}

View file

@ -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>

View file

@ -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));
}
}
}

View file

@ -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);

View 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);
}
}

View 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);
}
}

View 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);
}
}
}

View 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;
}
}

View 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();
}
}

View 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();
}
}
}
}
}

View 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);
}
}
}

View file

@ -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;
}

View file

@ -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);
}
}
}
}

View file

@ -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);
}
};

View file

@ -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();
}
}
}

View file

@ -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>

View file

@ -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();
}
}
}

View file

@ -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);
}
}
}

View file

@ -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>

View file

@ -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();
}
}
}
}
}

View file

@ -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" />

View file

@ -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>

View file

@ -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);

View file

@ -1,11 +0,0 @@
namespace Ryujinx.Graphics.Gpu.State
{
/// <summary>
/// Fence action operations.
/// </summary>
enum FenceActionOperation
{
Acquire = 0,
Increment = 1
}
}

View file

@ -1,9 +0,0 @@
namespace Ryujinx.Graphics.Gpu.State
{
enum FifoSemaphoreOperation
{
Counter = 0,
Acquire = 1,
Release = 2
}
}

View file

@ -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,

View file

@ -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)

View file

@ -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;

View file

@ -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; }

View file

@ -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;
}
}
}
}

View file

@ -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();

View file

@ -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;
}
}
}
}
}

View file

@ -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");
}

View file

@ -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()

View file

@ -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();

View file

@ -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";
}
}

View file

@ -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;

View file

@ -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;

View file

@ -115,26 +115,50 @@ 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);
continue;
}
int fragmentOutputColorAttr = AttributeConsts.FragmentOutputColorBase + rtIndex * 16;
Operand src = Register(regIndex, RegisterType.Gpr);
this.Copy(dest, src);
// 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++;
}
}
}
}
}
public Operation[] GetOperations()
{

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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)

View file

@ -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
}
}

View file

@ -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;
}
}
}

View 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;
}
}
}

View file

@ -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;
}
}
}

View 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;
}
}
}

View file

@ -0,0 +1,7 @@
namespace Ryujinx.HLE.HOS.Services.Nim.ShopServiceAccessServerInterface.ShopServiceAccessServer.ShopServiceAccessor
{
class IShopServiceAsync : IpcService
{
public IShopServiceAsync() { }
}
}

View file

@ -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);

View file

@ -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;
}
}
}

View file

@ -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;
}

View file

@ -8,6 +8,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi
Success = 0,
InvalidArguments = (1 << ErrorCodeShift) | ModuleId,
InvalidLayerSize = (4 << ErrorCodeShift) | ModuleId,
InvalidScalingMode = (6 << ErrorCodeShift) | ModuleId
}
}

View file

@ -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)

View file

@ -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>

View file

@ -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)

View file

@ -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)