Improved GPU command lists decoding (#499)
* Better implementation of the DMA pusher, misc fixes * Remove some debug code * Correct RGBX8 format * Add support for linked Texture Sampler Control * Attempt to fix upside down screen issue
This commit is contained in:
parent
b833183ef6
commit
d2bb458b51
@ -409,9 +409,31 @@ namespace ChocolArm64.Memory
|
||||
|
||||
public void WriteBytes(long position, byte[] data)
|
||||
{
|
||||
EnsureRangeIsValid(position, (uint)data.Length);
|
||||
long endAddr = position + data.Length;
|
||||
|
||||
Marshal.Copy(data, 0, (IntPtr)TranslateWrite(position), data.Length);
|
||||
if ((ulong)endAddr < (ulong)position)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(position));
|
||||
}
|
||||
|
||||
int offset = 0;
|
||||
|
||||
while ((ulong)position < (ulong)endAddr)
|
||||
{
|
||||
long pageLimit = (position + PageSize) & ~(long)PageMask;
|
||||
|
||||
if ((ulong)pageLimit > (ulong)endAddr)
|
||||
{
|
||||
pageLimit = endAddr;
|
||||
}
|
||||
|
||||
int copySize = (int)(pageLimit - position);
|
||||
|
||||
Marshal.Copy(data, offset, (IntPtr)TranslateWrite(position), copySize);
|
||||
|
||||
position += copySize;
|
||||
offset += copySize;
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteBytes(long position, byte[] data, int startIndex, int size)
|
||||
|
190
Ryujinx.Graphics/DmaPusher.cs
Normal file
190
Ryujinx.Graphics/DmaPusher.cs
Normal file
@ -0,0 +1,190 @@
|
||||
using Ryujinx.Graphics.Memory;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics
|
||||
{
|
||||
public class DmaPusher
|
||||
{
|
||||
private ConcurrentQueue<(NvGpuVmm, long)> IbBuffer;
|
||||
|
||||
private long DmaPut;
|
||||
private long DmaGet;
|
||||
|
||||
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 SliEnable;
|
||||
private bool SliActive;
|
||||
|
||||
private bool IbEnable;
|
||||
private bool NonMain;
|
||||
|
||||
private long DmaMGet;
|
||||
|
||||
private NvGpuVmm Vmm;
|
||||
|
||||
private NvGpu Gpu;
|
||||
|
||||
private AutoResetEvent Event;
|
||||
|
||||
public DmaPusher(NvGpu Gpu)
|
||||
{
|
||||
this.Gpu = Gpu;
|
||||
|
||||
IbBuffer = new ConcurrentQueue<(NvGpuVmm, long)>();
|
||||
|
||||
IbEnable = true;
|
||||
|
||||
Event = new AutoResetEvent(false);
|
||||
}
|
||||
|
||||
public void Push(NvGpuVmm Vmm, long Entry)
|
||||
{
|
||||
IbBuffer.Enqueue((Vmm, Entry));
|
||||
|
||||
Event.Set();
|
||||
}
|
||||
|
||||
public bool WaitForCommands()
|
||||
{
|
||||
return Event.WaitOne(8);
|
||||
}
|
||||
|
||||
public void DispatchCalls()
|
||||
{
|
||||
while (Step());
|
||||
}
|
||||
|
||||
private bool Step()
|
||||
{
|
||||
if (DmaGet != DmaPut)
|
||||
{
|
||||
int Word = Vmm.ReadInt32(DmaGet);
|
||||
|
||||
DmaGet += 4;
|
||||
|
||||
if (!NonMain)
|
||||
{
|
||||
DmaMGet = DmaGet;
|
||||
}
|
||||
|
||||
if (State.LengthPending != 0)
|
||||
{
|
||||
State.LengthPending = 0;
|
||||
State.MethodCount = Word & 0xffffff;
|
||||
}
|
||||
else if (State.MethodCount != 0)
|
||||
{
|
||||
if (!SliEnable || SliActive)
|
||||
{
|
||||
CallMethod(Word);
|
||||
}
|
||||
|
||||
if (!State.NonIncrementing)
|
||||
{
|
||||
State.Method++;
|
||||
}
|
||||
|
||||
if (State.IncrementOnce)
|
||||
{
|
||||
State.NonIncrementing = true;
|
||||
}
|
||||
|
||||
State.MethodCount--;
|
||||
}
|
||||
else
|
||||
{
|
||||
int SumissionMode = (Word >> 29) & 7;
|
||||
|
||||
switch (SumissionMode)
|
||||
{
|
||||
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 && IbBuffer.TryDequeue(out (NvGpuVmm Vmm, long Entry) Tuple))
|
||||
{
|
||||
this.Vmm = Tuple.Vmm;
|
||||
|
||||
long Entry = Tuple.Entry;
|
||||
|
||||
int Length = (int)(Entry >> 42) & 0x1fffff;
|
||||
|
||||
DmaGet = Entry & 0xfffffffffc;
|
||||
DmaPut = DmaGet + Length * 4;
|
||||
|
||||
NonMain = (Entry & (1L << 41)) != 0;
|
||||
|
||||
Gpu.ResourceManager.ClearPbCache();
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SetNonImmediateState(int Word)
|
||||
{
|
||||
State.Method = (Word >> 0) & 0x1fff;
|
||||
State.SubChannel = (Word >> 13) & 7;
|
||||
State.MethodCount = (Word >> 16) & 0x1fff;
|
||||
}
|
||||
|
||||
private void CallMethod(int Argument)
|
||||
{
|
||||
Gpu.Fifo.CallMethod(Vmm, new GpuMethodCall(
|
||||
State.Method,
|
||||
Argument,
|
||||
State.SubChannel,
|
||||
State.MethodCount));
|
||||
}
|
||||
}
|
||||
}
|
@ -28,6 +28,7 @@ namespace Ryujinx.Graphics.Gal
|
||||
RGB5A1,
|
||||
R8,
|
||||
RG8,
|
||||
RGBX8,
|
||||
RGBA8,
|
||||
BGRA8,
|
||||
RGB10A2,
|
||||
@ -39,6 +40,7 @@ namespace Ryujinx.Graphics.Gal
|
||||
RGBA32,
|
||||
R11G11B10,
|
||||
D16,
|
||||
D24,
|
||||
D32,
|
||||
D24S8,
|
||||
D32S8,
|
||||
|
@ -139,6 +139,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL
|
||||
case GalImageFormat.RG32 | GalImageFormat.Float: return (PixelInternalFormat.Rg32f, PixelFormat.Rg, PixelType.Float);
|
||||
case GalImageFormat.RG32 | GalImageFormat.Sint: return (PixelInternalFormat.Rg32i, PixelFormat.RgInteger, PixelType.Int);
|
||||
case GalImageFormat.RG32 | GalImageFormat.Uint: return (PixelInternalFormat.Rg32ui, PixelFormat.RgInteger, PixelType.UnsignedInt);
|
||||
case GalImageFormat.RGBX8 | GalImageFormat.Unorm: return (PixelInternalFormat.Rgb8, PixelFormat.Rgba, PixelType.UnsignedByte);
|
||||
case GalImageFormat.RGBA8 | GalImageFormat.Snorm: return (PixelInternalFormat.Rgba8Snorm, PixelFormat.Rgba, PixelType.Byte);
|
||||
case GalImageFormat.RGBA8 | GalImageFormat.Unorm: return (PixelInternalFormat.Rgba8, PixelFormat.Rgba, PixelType.UnsignedByte);
|
||||
case GalImageFormat.RGBA8 | GalImageFormat.Sint: return (PixelInternalFormat.Rgba8i, PixelFormat.RgbaInteger, PixelType.Byte);
|
||||
@ -174,10 +175,11 @@ namespace Ryujinx.Graphics.Gal.OpenGL
|
||||
case GalImageFormat.R8 | GalImageFormat.Unorm: return (PixelInternalFormat.R8, PixelFormat.Red, PixelType.UnsignedByte);
|
||||
case GalImageFormat.R11G11B10 | GalImageFormat.Float: return (PixelInternalFormat.R11fG11fB10f, PixelFormat.Rgb, PixelType.UnsignedInt10F11F11FRev);
|
||||
|
||||
case GalImageFormat.D16 | GalImageFormat.Unorm: return (PixelInternalFormat.DepthComponent16, PixelFormat.DepthComponent, PixelType.UnsignedShort);
|
||||
case GalImageFormat.D24 | GalImageFormat.Unorm: return (PixelInternalFormat.DepthComponent24, PixelFormat.DepthComponent, PixelType.UnsignedInt);
|
||||
case GalImageFormat.D24S8 | GalImageFormat.Uint: return (PixelInternalFormat.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248);
|
||||
case GalImageFormat.D24S8 | GalImageFormat.Unorm: return (PixelInternalFormat.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248);
|
||||
case GalImageFormat.D32 | GalImageFormat.Float: return (PixelInternalFormat.DepthComponent32f, PixelFormat.DepthComponent, PixelType.Float);
|
||||
case GalImageFormat.D16 | GalImageFormat.Unorm: return (PixelInternalFormat.DepthComponent16, PixelFormat.DepthComponent, PixelType.UnsignedShort);
|
||||
case GalImageFormat.D32S8 | GalImageFormat.Float: return (PixelInternalFormat.Depth32fStencil8, PixelFormat.DepthStencil, PixelType.Float32UnsignedInt248Rev);
|
||||
}
|
||||
|
||||
|
@ -421,8 +421,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL
|
||||
|
||||
ClearBufferMask Mask = GetClearMask(SrcTex);
|
||||
|
||||
GL.Clear(Mask);
|
||||
|
||||
GL.BlitFramebuffer(SrcX0, SrcY0, SrcX1, SrcY1, DstX0, DstY0, DstX1, DstY1, Mask, Filter);
|
||||
}
|
||||
}
|
||||
|
24
Ryujinx.Graphics/GpuMethodCall.cs
Normal file
24
Ryujinx.Graphics/GpuMethodCall.cs
Normal file
@ -0,0 +1,24 @@
|
||||
namespace Ryujinx.Graphics
|
||||
{
|
||||
struct GpuMethodCall
|
||||
{
|
||||
public int Method { get; private set; }
|
||||
public int Argument { get; private set; }
|
||||
public int SubChannel { get; private set; }
|
||||
public int MethodCount { get; private set; }
|
||||
|
||||
public bool IsLastCall => MethodCount <= 1;
|
||||
|
||||
public GpuMethodCall(
|
||||
int Method,
|
||||
int Argument,
|
||||
int SubChannel = 0,
|
||||
int MethodCount = 0)
|
||||
{
|
||||
this.Method = Method;
|
||||
this.Argument = Argument;
|
||||
this.SubChannel = SubChannel;
|
||||
this.MethodCount = MethodCount;
|
||||
}
|
||||
}
|
||||
}
|
@ -117,7 +117,7 @@ namespace Ryujinx.Graphics
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool MemoryRegionModified(NvGpuVmm Vmm, long Position, long Size, NvGpuBufferType Type)
|
||||
public bool MemoryRegionModified(NvGpuVmm Vmm, long Position, long Size, NvGpuBufferType Type)
|
||||
{
|
||||
HashSet<long> Uploaded = UploadedKeys[(int)Type];
|
||||
|
||||
@ -136,5 +136,10 @@ namespace Ryujinx.Graphics
|
||||
UploadedKeys[Index].Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearPbCache(NvGpuBufferType Type)
|
||||
{
|
||||
UploadedKeys[(int)Type].Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,6 @@ namespace Ryujinx.Graphics
|
||||
{
|
||||
int[] Registers { get; }
|
||||
|
||||
void CallMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry);
|
||||
void CallMethod(NvGpuVmm Vmm, GpuMethodCall MethCall);
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -388,15 +389,12 @@ namespace Ryujinx.Graphics
|
||||
{
|
||||
int Value;
|
||||
|
||||
//If we don't have any parameters in the FIFO,
|
||||
//keep running the PFIFO engine until it writes the parameters.
|
||||
while (!Fifo.TryDequeue(out Value))
|
||||
{
|
||||
if (!PFifo.Step())
|
||||
if (!Fifo.TryDequeue(out Value))
|
||||
{
|
||||
Logger.PrintWarning(LogClass.Gpu, "Macro attempted to fetch an inexistent argument.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return Value;
|
||||
}
|
||||
@ -408,9 +406,9 @@ namespace Ryujinx.Graphics
|
||||
|
||||
private void Send(NvGpuVmm Vmm, int Value)
|
||||
{
|
||||
NvGpuPBEntry PBEntry = new NvGpuPBEntry(MethAddr, 0, Value);
|
||||
GpuMethodCall MethCall = new GpuMethodCall(MethAddr, Value);
|
||||
|
||||
Engine.CallMethod(Vmm, PBEntry);
|
||||
Engine.CallMethod(Vmm, MethCall);
|
||||
|
||||
MethAddr += MethIncr;
|
||||
}
|
||||
|
@ -1,23 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Ryujinx.Graphics.Memory
|
||||
{
|
||||
public struct NvGpuPBEntry
|
||||
{
|
||||
public int Method { get; private set; }
|
||||
|
||||
public int SubChannel { get; private set; }
|
||||
|
||||
private int[] m_Arguments;
|
||||
|
||||
public ReadOnlyCollection<int> Arguments => Array.AsReadOnly(m_Arguments);
|
||||
|
||||
public NvGpuPBEntry(int Method, int SubChannel, params int[] Arguments)
|
||||
{
|
||||
this.Method = Method;
|
||||
this.SubChannel = SubChannel;
|
||||
this.m_Arguments = Arguments;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.Graphics.Memory
|
||||
{
|
||||
public static class NvGpuPushBuffer
|
||||
{
|
||||
private enum SubmissionMode
|
||||
{
|
||||
Incrementing = 1,
|
||||
NonIncrementing = 3,
|
||||
Immediate = 4,
|
||||
IncrementOnce = 5
|
||||
}
|
||||
|
||||
public static NvGpuPBEntry[] Decode(byte[] Data)
|
||||
{
|
||||
using (MemoryStream MS = new MemoryStream(Data))
|
||||
{
|
||||
BinaryReader Reader = new BinaryReader(MS);
|
||||
|
||||
List<NvGpuPBEntry> PushBuffer = new List<NvGpuPBEntry>();
|
||||
|
||||
bool CanRead() => MS.Position + 4 <= MS.Length;
|
||||
|
||||
while (CanRead())
|
||||
{
|
||||
int Packed = Reader.ReadInt32();
|
||||
|
||||
int Meth = (Packed >> 0) & 0x1fff;
|
||||
int SubC = (Packed >> 13) & 7;
|
||||
int Args = (Packed >> 16) & 0x1fff;
|
||||
int Mode = (Packed >> 29) & 7;
|
||||
|
||||
switch ((SubmissionMode)Mode)
|
||||
{
|
||||
case SubmissionMode.Incrementing:
|
||||
{
|
||||
for (int Index = 0; Index < Args && CanRead(); Index++, Meth++)
|
||||
{
|
||||
PushBuffer.Add(new NvGpuPBEntry(Meth, SubC, Reader.ReadInt32()));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SubmissionMode.NonIncrementing:
|
||||
{
|
||||
int[] Arguments = new int[Args];
|
||||
|
||||
for (int Index = 0; Index < Arguments.Length; Index++)
|
||||
{
|
||||
if (!CanRead())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Arguments[Index] = Reader.ReadInt32();
|
||||
}
|
||||
|
||||
PushBuffer.Add(new NvGpuPBEntry(Meth, SubC, Arguments));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SubmissionMode.Immediate:
|
||||
{
|
||||
PushBuffer.Add(new NvGpuPBEntry(Meth, SubC, Args));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SubmissionMode.IncrementOnce:
|
||||
{
|
||||
if (CanRead())
|
||||
{
|
||||
PushBuffer.Add(new NvGpuPBEntry(Meth, SubC, Reader.ReadInt32()));
|
||||
}
|
||||
|
||||
if (CanRead() && Args > 1)
|
||||
{
|
||||
int[] Arguments = new int[Args - 1];
|
||||
|
||||
for (int Index = 0; Index < Arguments.Length && CanRead(); Index++)
|
||||
{
|
||||
Arguments[Index] = Reader.ReadInt32();
|
||||
}
|
||||
|
||||
PushBuffer.Add(new NvGpuPBEntry(Meth + 1, SubC, Arguments));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PushBuffer.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -5,27 +5,54 @@ namespace Ryujinx.Graphics.Memory
|
||||
{
|
||||
class NvGpuVmmCache
|
||||
{
|
||||
private ValueRangeSet<int> CachedRanges;
|
||||
private struct CachedResource
|
||||
{
|
||||
public long Key;
|
||||
public int Mask;
|
||||
|
||||
public CachedResource(long Key, int Mask)
|
||||
{
|
||||
this.Key = Key;
|
||||
this.Mask = Mask;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return (int)(Key * 23 + Mask);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is CachedResource Cached && Equals(Cached);
|
||||
}
|
||||
|
||||
public bool Equals(CachedResource other)
|
||||
{
|
||||
return Key == other.Key && Mask == other.Mask;
|
||||
}
|
||||
}
|
||||
|
||||
private ValueRangeSet<CachedResource> CachedRanges;
|
||||
|
||||
public NvGpuVmmCache()
|
||||
{
|
||||
CachedRanges = new ValueRangeSet<int>();
|
||||
CachedRanges = new ValueRangeSet<CachedResource>();
|
||||
}
|
||||
|
||||
public bool IsRegionModified(MemoryManager Memory, NvGpuBufferType BufferType, long PA, long Size)
|
||||
public bool IsRegionModified(MemoryManager Memory, NvGpuBufferType BufferType, long Start, long Size)
|
||||
{
|
||||
(bool[] Modified, long ModifiedCount) = Memory.IsRegionModified(PA, Size);
|
||||
(bool[] Modified, long ModifiedCount) = Memory.IsRegionModified(Start, Size);
|
||||
|
||||
//Remove all modified ranges.
|
||||
int Index = 0;
|
||||
|
||||
long Position = PA & ~NvGpuVmm.PageMask;
|
||||
long Position = Start & ~NvGpuVmm.PageMask;
|
||||
|
||||
while (ModifiedCount > 0)
|
||||
{
|
||||
if (Modified[Index++])
|
||||
{
|
||||
CachedRanges.Remove(new ValueRange<int>(Position, Position + NvGpuVmm.PageSize));
|
||||
CachedRanges.Remove(new ValueRange<CachedResource>(Position, Position + NvGpuVmm.PageSize));
|
||||
|
||||
ModifiedCount--;
|
||||
}
|
||||
@ -37,11 +64,19 @@ namespace Ryujinx.Graphics.Memory
|
||||
//If the region is not yet present on the list, then a new ValueRange
|
||||
//is directly added with the current resource type as the only bit set.
|
||||
//Otherwise, it just sets the bit for this new resource type on the current mask.
|
||||
//The physical address of the resource is used as key, those keys are used to keep
|
||||
//track of resources that are already on the cache. A resource may be inside another
|
||||
//resource, and in this case we should return true if the "sub-resource" was not
|
||||
//yet cached.
|
||||
int Mask = 1 << (int)BufferType;
|
||||
|
||||
ValueRange<int> NewCached = new ValueRange<int>(PA, PA + Size);
|
||||
CachedResource NewCachedValue = new CachedResource(Start, Mask);
|
||||
|
||||
ValueRange<int>[] Ranges = CachedRanges.GetAllIntersections(NewCached);
|
||||
ValueRange<CachedResource> NewCached = new ValueRange<CachedResource>(Start, Start + Size);
|
||||
|
||||
ValueRange<CachedResource>[] Ranges = CachedRanges.GetAllIntersections(NewCached);
|
||||
|
||||
bool IsKeyCached = Ranges.Length > 0 && Ranges[0].Value.Key == Start;
|
||||
|
||||
long LastEnd = NewCached.Start;
|
||||
|
||||
@ -49,23 +84,36 @@ namespace Ryujinx.Graphics.Memory
|
||||
|
||||
for (Index = 0; Index < Ranges.Length; Index++)
|
||||
{
|
||||
ValueRange<int> Current = Ranges[Index];
|
||||
ValueRange<CachedResource> Current = Ranges[Index];
|
||||
|
||||
CachedResource Cached = Current.Value;
|
||||
|
||||
long RgStart = Math.Max(Current.Start, NewCached.Start);
|
||||
long RgEnd = Math.Min(Current.End, NewCached.End);
|
||||
|
||||
if ((Current.Value & Mask) == 0)
|
||||
{
|
||||
CachedRanges.Add(new ValueRange<int>(RgStart, RgEnd, Current.Value | Mask));
|
||||
}
|
||||
else
|
||||
if ((Cached.Mask & Mask) != 0)
|
||||
{
|
||||
Coverage += RgEnd - RgStart;
|
||||
}
|
||||
|
||||
//Highest key value has priority, this prevents larger resources
|
||||
//for completely invalidating smaller ones on the cache. For example,
|
||||
//consider that a resource in the range [100, 200) was added, and then
|
||||
//another one in the range [50, 200). We prevent the new resource from
|
||||
//completely replacing the old one by spliting it like this:
|
||||
//New resource key is added at [50, 100), old key is still present at [100, 200).
|
||||
if (Cached.Key < Start)
|
||||
{
|
||||
Cached.Key = Start;
|
||||
}
|
||||
|
||||
Cached.Mask |= Mask;
|
||||
|
||||
CachedRanges.Add(new ValueRange<CachedResource>(RgStart, RgEnd, Cached));
|
||||
|
||||
if (RgStart > LastEnd)
|
||||
{
|
||||
CachedRanges.Add(new ValueRange<int>(LastEnd, RgStart, Mask));
|
||||
CachedRanges.Add(new ValueRange<CachedResource>(LastEnd, RgStart, NewCachedValue));
|
||||
}
|
||||
|
||||
LastEnd = RgEnd;
|
||||
@ -73,10 +121,10 @@ namespace Ryujinx.Graphics.Memory
|
||||
|
||||
if (LastEnd < NewCached.End)
|
||||
{
|
||||
CachedRanges.Add(new ValueRange<int>(LastEnd, NewCached.End, Mask));
|
||||
CachedRanges.Add(new ValueRange<CachedResource>(LastEnd, NewCached.End, NewCachedValue));
|
||||
}
|
||||
|
||||
return Coverage != Size;
|
||||
return !IsKeyCached || Coverage != Size;
|
||||
}
|
||||
}
|
||||
}
|
@ -8,8 +8,9 @@ namespace Ryujinx.Graphics
|
||||
|
||||
public GpuResourceManager ResourceManager { get; private set; }
|
||||
|
||||
public NvGpuFifo Fifo { get; private set; }
|
||||
public DmaPusher Pusher { get; private set; }
|
||||
|
||||
internal NvGpuFifo Fifo { get; private set; }
|
||||
internal NvGpuEngine2d Engine2d { get; private set; }
|
||||
internal NvGpuEngine3d Engine3d { get; private set; }
|
||||
internal NvGpuEngineM2mf EngineM2mf { get; private set; }
|
||||
@ -21,8 +22,9 @@ namespace Ryujinx.Graphics
|
||||
|
||||
ResourceManager = new GpuResourceManager(this);
|
||||
|
||||
Fifo = new NvGpuFifo(this);
|
||||
Pusher = new DmaPusher(this);
|
||||
|
||||
Fifo = new NvGpuFifo(this);
|
||||
Engine2d = new NvGpuEngine2d(this);
|
||||
Engine3d = new NvGpuEngine3d(this);
|
||||
EngineM2mf = new NvGpuEngineM2mf(this);
|
||||
|
@ -1,11 +1,10 @@
|
||||
using Ryujinx.Graphics.Gal;
|
||||
using Ryujinx.Graphics.Memory;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics
|
||||
{
|
||||
public class NvGpuEngine2d : INvGpuEngine
|
||||
class NvGpuEngine2d : INvGpuEngine
|
||||
{
|
||||
private enum CopyOperation
|
||||
{
|
||||
@ -29,11 +28,11 @@ namespace Ryujinx.Graphics
|
||||
Registers = new int[0x238];
|
||||
}
|
||||
|
||||
public void CallMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
|
||||
public void CallMethod(NvGpuVmm Vmm, GpuMethodCall MethCall)
|
||||
{
|
||||
WriteRegister(PBEntry);
|
||||
WriteRegister(MethCall);
|
||||
|
||||
if ((NvGpuEngine2dReg)PBEntry.Method == NvGpuEngine2dReg.BlitSrcYInt)
|
||||
if ((NvGpuEngine2dReg)MethCall.Method == NvGpuEngine2dReg.BlitSrcYInt)
|
||||
{
|
||||
TextureCopy(Vmm);
|
||||
}
|
||||
@ -43,6 +42,13 @@ namespace Ryujinx.Graphics
|
||||
{
|
||||
CopyOperation Operation = (CopyOperation)ReadRegister(NvGpuEngine2dReg.CopyOperation);
|
||||
|
||||
int DstFormat = ReadRegister(NvGpuEngine2dReg.DstFormat);
|
||||
bool DstLinear = ReadRegister(NvGpuEngine2dReg.DstLinear) != 0;
|
||||
int DstWidth = ReadRegister(NvGpuEngine2dReg.DstWidth);
|
||||
int DstHeight = ReadRegister(NvGpuEngine2dReg.DstHeight);
|
||||
int DstPitch = ReadRegister(NvGpuEngine2dReg.DstPitch);
|
||||
int DstBlkDim = ReadRegister(NvGpuEngine2dReg.DstBlockDimensions);
|
||||
|
||||
int SrcFormat = ReadRegister(NvGpuEngine2dReg.SrcFormat);
|
||||
bool SrcLinear = ReadRegister(NvGpuEngine2dReg.SrcLinear) != 0;
|
||||
int SrcWidth = ReadRegister(NvGpuEngine2dReg.SrcWidth);
|
||||
@ -50,12 +56,13 @@ namespace Ryujinx.Graphics
|
||||
int SrcPitch = ReadRegister(NvGpuEngine2dReg.SrcPitch);
|
||||
int SrcBlkDim = ReadRegister(NvGpuEngine2dReg.SrcBlockDimensions);
|
||||
|
||||
int DstFormat = ReadRegister(NvGpuEngine2dReg.DstFormat);
|
||||
bool DstLinear = ReadRegister(NvGpuEngine2dReg.DstLinear) != 0;
|
||||
int DstWidth = ReadRegister(NvGpuEngine2dReg.DstWidth);
|
||||
int DstHeight = ReadRegister(NvGpuEngine2dReg.DstHeight);
|
||||
int DstPitch = ReadRegister(NvGpuEngine2dReg.DstPitch);
|
||||
int DstBlkDim = ReadRegister(NvGpuEngine2dReg.DstBlockDimensions);
|
||||
int DstBlitX = ReadRegister(NvGpuEngine2dReg.BlitDstX);
|
||||
int DstBlitY = ReadRegister(NvGpuEngine2dReg.BlitDstY);
|
||||
int DstBlitW = ReadRegister(NvGpuEngine2dReg.BlitDstW);
|
||||
int DstBlitH = ReadRegister(NvGpuEngine2dReg.BlitDstH);
|
||||
|
||||
int SrcBlitX = ReadRegister(NvGpuEngine2dReg.BlitSrcXInt);
|
||||
int SrcBlitY = ReadRegister(NvGpuEngine2dReg.BlitSrcYInt);
|
||||
|
||||
GalImageFormat SrcImgFormat = ImageUtils.ConvertSurface((GalSurfaceFormat)SrcFormat);
|
||||
GalImageFormat DstImgFormat = ImageUtils.ConvertSurface((GalSurfaceFormat)DstFormat);
|
||||
@ -86,23 +93,42 @@ namespace Ryujinx.Graphics
|
||||
DstLayout,
|
||||
DstImgFormat);
|
||||
|
||||
SrcTexture.Pitch = SrcPitch;
|
||||
DstTexture.Pitch = DstPitch;
|
||||
|
||||
Gpu.ResourceManager.SendTexture(Vmm, SrcKey, SrcTexture);
|
||||
Gpu.ResourceManager.SendTexture(Vmm, DstKey, DstTexture);
|
||||
|
||||
int Width = Math.Min(SrcWidth, DstWidth);
|
||||
int Height = Math.Min(SrcHeight, DstHeight);
|
||||
|
||||
Gpu.Renderer.RenderTarget.Copy(
|
||||
SrcKey,
|
||||
DstKey,
|
||||
0,
|
||||
0,
|
||||
Width,
|
||||
Height,
|
||||
0,
|
||||
0,
|
||||
Width,
|
||||
Height);
|
||||
SrcBlitX,
|
||||
SrcBlitY,
|
||||
SrcBlitX + DstBlitW,
|
||||
SrcBlitY + DstBlitH,
|
||||
DstBlitX,
|
||||
DstBlitY,
|
||||
DstBlitX + DstBlitW,
|
||||
DstBlitY + DstBlitH);
|
||||
|
||||
//Do a guest side copy aswell. This is necessary when
|
||||
//the texture is modified by the guest, however it doesn't
|
||||
//work when resources that the gpu can write to are copied,
|
||||
//like framebuffers.
|
||||
ImageUtils.CopyTexture(
|
||||
Vmm,
|
||||
SrcTexture,
|
||||
DstTexture,
|
||||
SrcAddress,
|
||||
DstAddress,
|
||||
SrcBlitX,
|
||||
SrcBlitY,
|
||||
DstBlitX,
|
||||
DstBlitY,
|
||||
DstBlitW,
|
||||
DstBlitH);
|
||||
|
||||
Vmm.IsRegionModified(DstKey, ImageUtils.GetSize(DstTexture), NvGpuBufferType.Texture);
|
||||
}
|
||||
|
||||
private static GalMemoryLayout GetLayout(bool Linear)
|
||||
@ -119,14 +145,9 @@ namespace Ryujinx.Graphics
|
||||
(uint)Registers[(int)Reg + 1];
|
||||
}
|
||||
|
||||
private void WriteRegister(NvGpuPBEntry PBEntry)
|
||||
private void WriteRegister(GpuMethodCall MethCall)
|
||||
{
|
||||
int ArgsCount = PBEntry.Arguments.Count;
|
||||
|
||||
if (ArgsCount > 0)
|
||||
{
|
||||
Registers[PBEntry.Method] = PBEntry.Arguments[ArgsCount - 1];
|
||||
}
|
||||
Registers[MethCall.Method] = MethCall.Argument;
|
||||
}
|
||||
|
||||
private int ReadRegister(NvGpuEngine2dReg Reg)
|
||||
|
@ -7,7 +7,7 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics
|
||||
{
|
||||
public class NvGpuEngine3d : INvGpuEngine
|
||||
class NvGpuEngine3d : INvGpuEngine
|
||||
{
|
||||
public int[] Registers { get; private set; }
|
||||
|
||||
@ -24,8 +24,6 @@ namespace Ryujinx.Graphics
|
||||
|
||||
private ConstBuffer[][] ConstBuffers;
|
||||
|
||||
private List<long>[] UploadedKeys;
|
||||
|
||||
private int CurrentInstance = 0;
|
||||
|
||||
public NvGpuEngine3d(NvGpu Gpu)
|
||||
@ -59,13 +57,6 @@ namespace Ryujinx.Graphics
|
||||
ConstBuffers[Index] = new ConstBuffer[18];
|
||||
}
|
||||
|
||||
UploadedKeys = new List<long>[(int)NvGpuBufferType.Count];
|
||||
|
||||
for (int i = 0; i < UploadedKeys.Length; i++)
|
||||
{
|
||||
UploadedKeys[i] = new List<long>();
|
||||
}
|
||||
|
||||
//Ensure that all components are enabled by default.
|
||||
//FIXME: Is this correct?
|
||||
WriteRegister(NvGpuEngine3dReg.ColorMaskN, 0x1111);
|
||||
@ -81,27 +72,19 @@ namespace Ryujinx.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
public void CallMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
|
||||
public void CallMethod(NvGpuVmm Vmm, GpuMethodCall MethCall)
|
||||
{
|
||||
if (Methods.TryGetValue(PBEntry.Method, out NvGpuMethod Method))
|
||||
if (Methods.TryGetValue(MethCall.Method, out NvGpuMethod Method))
|
||||
{
|
||||
Method(Vmm, PBEntry);
|
||||
Method(Vmm, MethCall);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteRegister(PBEntry);
|
||||
WriteRegister(MethCall);
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetCache()
|
||||
{
|
||||
foreach (List<long> Uploaded in UploadedKeys)
|
||||
{
|
||||
Uploaded.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void VertexEndGl(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
|
||||
private void VertexEndGl(NvGpuVmm Vmm, GpuMethodCall MethCall)
|
||||
{
|
||||
LockCaches();
|
||||
|
||||
@ -152,13 +135,11 @@ namespace Ryujinx.Graphics
|
||||
Gpu.Renderer.Texture.UnlockCache();
|
||||
}
|
||||
|
||||
private void ClearBuffers(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
|
||||
private void ClearBuffers(NvGpuVmm Vmm, GpuMethodCall MethCall)
|
||||
{
|
||||
int Arg0 = PBEntry.Arguments[0];
|
||||
int Attachment = (MethCall.Argument >> 6) & 0xf;
|
||||
|
||||
int Attachment = (Arg0 >> 6) & 0xf;
|
||||
|
||||
GalClearBufferFlags Flags = (GalClearBufferFlags)(Arg0 & 0x3f);
|
||||
GalClearBufferFlags Flags = (GalClearBufferFlags)(MethCall.Argument & 0x3f);
|
||||
|
||||
float Red = ReadRegisterFloat(NvGpuEngine3dReg.ClearNColor + 0);
|
||||
float Green = ReadRegisterFloat(NvGpuEngine3dReg.ClearNColor + 1);
|
||||
@ -234,6 +215,15 @@ namespace Ryujinx.Graphics
|
||||
|
||||
State.FlipX = GetFlipSign(NvGpuEngine3dReg.ViewportNScaleX);
|
||||
State.FlipY = GetFlipSign(NvGpuEngine3dReg.ViewportNScaleY);
|
||||
|
||||
int ScreenYControl = ReadRegister(NvGpuEngine3dReg.ScreenYControl);
|
||||
|
||||
bool NegateY = (ScreenYControl & 1) != 0;
|
||||
|
||||
if (NegateY)
|
||||
{
|
||||
State.FlipY = -State.FlipY;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetZeta(NvGpuVmm Vmm)
|
||||
@ -566,8 +556,11 @@ namespace Ryujinx.Graphics
|
||||
return;
|
||||
}
|
||||
|
||||
bool LinkedTsc = ReadRegisterBool(NvGpuEngine3dReg.LinkedTsc);
|
||||
|
||||
int TicIndex = (TextureHandle >> 0) & 0xfffff;
|
||||
int TscIndex = (TextureHandle >> 20) & 0xfff;
|
||||
|
||||
int TscIndex = LinkedTsc ? TicIndex : (TextureHandle >> 20) & 0xfff;
|
||||
|
||||
long TicPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.TexHeaderPoolOffset);
|
||||
long TscPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.TexSamplerPoolOffset);
|
||||
@ -618,7 +611,7 @@ namespace Ryujinx.Graphics
|
||||
|
||||
long Key = Vmm.GetPhysicalAddress(Cb.Position);
|
||||
|
||||
if (QueryKeyUpload(Vmm, Key, Cb.Size, NvGpuBufferType.ConstBuffer))
|
||||
if (Gpu.ResourceManager.MemoryRegionModified(Vmm, Key, Cb.Size, NvGpuBufferType.ConstBuffer))
|
||||
{
|
||||
IntPtr Source = Vmm.GetHostAddress(Cb.Position, Cb.Size);
|
||||
|
||||
@ -661,7 +654,7 @@ namespace Ryujinx.Graphics
|
||||
PrimType == GalPrimitiveType.Quads ||
|
||||
PrimType == GalPrimitiveType.QuadStrip;
|
||||
|
||||
if (!IboCached || QueryKeyUpload(Vmm, IboKey, (uint)IbSize, NvGpuBufferType.Index))
|
||||
if (!IboCached || Gpu.ResourceManager.MemoryRegionModified(Vmm, IboKey, (uint)IbSize, NvGpuBufferType.Index))
|
||||
{
|
||||
if (!UsesLegacyQuads)
|
||||
{
|
||||
@ -778,7 +771,7 @@ namespace Ryujinx.Graphics
|
||||
|
||||
bool VboCached = Gpu.Renderer.Rasterizer.IsVboCached(VboKey, VbSize);
|
||||
|
||||
if (!VboCached || QueryKeyUpload(Vmm, VboKey, VbSize, NvGpuBufferType.Vertex))
|
||||
if (!VboCached || Gpu.ResourceManager.MemoryRegionModified(Vmm, VboKey, VbSize, NvGpuBufferType.Vertex))
|
||||
{
|
||||
IntPtr DataAddress = Vmm.GetHostAddress(VertexPosition, VbSize);
|
||||
|
||||
@ -877,9 +870,9 @@ namespace Ryujinx.Graphics
|
||||
WriteCounterAndTimestamp
|
||||
}
|
||||
|
||||
private void QueryControl(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
|
||||
private void QueryControl(NvGpuVmm Vmm, GpuMethodCall MethCall)
|
||||
{
|
||||
WriteRegister(PBEntry);
|
||||
WriteRegister(MethCall);
|
||||
|
||||
long Position = MakeInt64From2xInt32(NvGpuEngine3dReg.QueryAddress);
|
||||
|
||||
@ -909,29 +902,24 @@ namespace Ryujinx.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
private void CbData(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
|
||||
private void CbData(NvGpuVmm Vmm, GpuMethodCall MethCall)
|
||||
{
|
||||
long Position = MakeInt64From2xInt32(NvGpuEngine3dReg.ConstBufferAddress);
|
||||
|
||||
int Offset = ReadRegister(NvGpuEngine3dReg.ConstBufferOffset);
|
||||
|
||||
foreach (int Arg in PBEntry.Arguments)
|
||||
{
|
||||
Vmm.WriteInt32(Position + Offset, Arg);
|
||||
Vmm.WriteInt32(Position + Offset, MethCall.Argument);
|
||||
|
||||
Offset += 4;
|
||||
WriteRegister(NvGpuEngine3dReg.ConstBufferOffset, Offset + 4);
|
||||
|
||||
Gpu.ResourceManager.ClearPbCache(NvGpuBufferType.ConstBuffer);
|
||||
}
|
||||
|
||||
WriteRegister(NvGpuEngine3dReg.ConstBufferOffset, Offset);
|
||||
|
||||
UploadedKeys[(int)NvGpuBufferType.ConstBuffer].Clear();
|
||||
}
|
||||
|
||||
private void CbBind(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
|
||||
private void CbBind(NvGpuVmm Vmm, GpuMethodCall MethCall)
|
||||
{
|
||||
int Stage = (PBEntry.Method - 0x904) >> 3;
|
||||
int Stage = (MethCall.Method - 0x904) >> 3;
|
||||
|
||||
int Index = PBEntry.Arguments[0];
|
||||
int Index = MethCall.Argument;
|
||||
|
||||
bool Enabled = (Index & 1) != 0;
|
||||
|
||||
@ -970,14 +958,9 @@ namespace Ryujinx.Graphics
|
||||
(uint)Registers[(int)Reg + 1];
|
||||
}
|
||||
|
||||
private void WriteRegister(NvGpuPBEntry PBEntry)
|
||||
private void WriteRegister(GpuMethodCall MethCall)
|
||||
{
|
||||
int ArgsCount = PBEntry.Arguments.Count;
|
||||
|
||||
if (ArgsCount > 0)
|
||||
{
|
||||
Registers[PBEntry.Method] = PBEntry.Arguments[ArgsCount - 1];
|
||||
}
|
||||
Registers[MethCall.Method] = MethCall.Argument;
|
||||
}
|
||||
|
||||
private int ReadRegister(NvGpuEngine3dReg Reg)
|
||||
@ -999,19 +982,5 @@ namespace Ryujinx.Graphics
|
||||
{
|
||||
Registers[(int)Reg] = Value;
|
||||
}
|
||||
|
||||
private bool QueryKeyUpload(NvGpuVmm Vmm, long Key, long Size, NvGpuBufferType Type)
|
||||
{
|
||||
List<long> Uploaded = UploadedKeys[(int)Type];
|
||||
|
||||
if (Uploaded.Contains(Key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Uploaded.Add(Key);
|
||||
|
||||
return Vmm.IsRegionModified(Key, Size, Type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ namespace Ryujinx.Graphics
|
||||
ZetaHoriz = 0x48a,
|
||||
ZetaVert = 0x48b,
|
||||
ZetaArrayMode = 0x48c,
|
||||
LinkedTsc = 0x48d,
|
||||
DepthTestEnable = 0x4b3,
|
||||
BlendIndependent = 0x4b9,
|
||||
DepthWriteEnable = 0x4ba,
|
||||
@ -57,6 +58,7 @@ namespace Ryujinx.Graphics
|
||||
StencilFrontFuncRef = 0x4e5,
|
||||
StencilFrontFuncMask = 0x4e6,
|
||||
StencilFrontMask = 0x4e7,
|
||||
ScreenYControl = 0x4eb,
|
||||
VertexArrayElemBase = 0x50d,
|
||||
VertexArrayInstBase = 0x50e,
|
||||
ZetaEnable = 0x54e,
|
||||
|
@ -4,7 +4,7 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics
|
||||
{
|
||||
public class NvGpuEngineM2mf : INvGpuEngine
|
||||
class NvGpuEngineM2mf : INvGpuEngine
|
||||
{
|
||||
public int[] Registers { get; private set; }
|
||||
|
||||
@ -33,22 +33,22 @@ namespace Ryujinx.Graphics
|
||||
AddMethod(0xc0, 1, 1, Execute);
|
||||
}
|
||||
|
||||
public void CallMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
|
||||
public void CallMethod(NvGpuVmm Vmm, GpuMethodCall MethCall)
|
||||
{
|
||||
if (Methods.TryGetValue(PBEntry.Method, out NvGpuMethod Method))
|
||||
if (Methods.TryGetValue(MethCall.Method, out NvGpuMethod Method))
|
||||
{
|
||||
Method(Vmm, PBEntry);
|
||||
Method(Vmm, MethCall);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteRegister(PBEntry);
|
||||
WriteRegister(MethCall);
|
||||
}
|
||||
}
|
||||
|
||||
private void Execute(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
|
||||
private void Execute(NvGpuVmm Vmm, GpuMethodCall MethCall)
|
||||
{
|
||||
//TODO: Some registers and copy modes are still not implemented.
|
||||
int Control = PBEntry.Arguments[0];
|
||||
int Control = MethCall.Argument;
|
||||
|
||||
bool SrcLinear = ((Control >> 7) & 1) != 0;
|
||||
bool DstLinear = ((Control >> 8) & 1) != 0;
|
||||
@ -169,14 +169,9 @@ namespace Ryujinx.Graphics
|
||||
(uint)Registers[(int)Reg + 1];
|
||||
}
|
||||
|
||||
private void WriteRegister(NvGpuPBEntry PBEntry)
|
||||
private void WriteRegister(GpuMethodCall MethCall)
|
||||
{
|
||||
int ArgsCount = PBEntry.Arguments.Count;
|
||||
|
||||
if (ArgsCount > 0)
|
||||
{
|
||||
Registers[PBEntry.Method] = PBEntry.Arguments[ArgsCount - 1];
|
||||
}
|
||||
Registers[MethCall.Method] = MethCall.Argument;
|
||||
}
|
||||
|
||||
private int ReadRegister(NvGpuEngineM2mfReg Reg)
|
||||
|
@ -1,10 +1,10 @@
|
||||
using Ryujinx.Graphics.Memory;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Ryujinx.Graphics
|
||||
{
|
||||
public class NvGpuEngineP2mf : INvGpuEngine
|
||||
class NvGpuEngineP2mf : INvGpuEngine
|
||||
{
|
||||
public int[] Registers { get; private set; }
|
||||
|
||||
@ -12,7 +12,21 @@ namespace Ryujinx.Graphics
|
||||
|
||||
private Dictionary<int, NvGpuMethod> Methods;
|
||||
|
||||
private ReadOnlyCollection<int> DataBuffer;
|
||||
private int CopyStartX;
|
||||
private int CopyStartY;
|
||||
|
||||
private int CopyWidth;
|
||||
private int CopyHeight;
|
||||
private int CopyGobBlockHeight;
|
||||
|
||||
private long CopyAddress;
|
||||
|
||||
private int CopyOffset;
|
||||
private int CopySize;
|
||||
|
||||
private bool CopyLinear;
|
||||
|
||||
private byte[] Buffer;
|
||||
|
||||
public NvGpuEngineP2mf(NvGpu Gpu)
|
||||
{
|
||||
@ -36,40 +50,90 @@ namespace Ryujinx.Graphics
|
||||
AddMethod(0x6d, 1, 1, PushData);
|
||||
}
|
||||
|
||||
public void CallMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
|
||||
public void CallMethod(NvGpuVmm Vmm, GpuMethodCall MethCall)
|
||||
{
|
||||
if (Methods.TryGetValue(PBEntry.Method, out NvGpuMethod Method))
|
||||
if (Methods.TryGetValue(MethCall.Method, out NvGpuMethod Method))
|
||||
{
|
||||
Method(Vmm, PBEntry);
|
||||
Method(Vmm, MethCall);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteRegister(PBEntry);
|
||||
WriteRegister(MethCall);
|
||||
}
|
||||
}
|
||||
|
||||
private void Execute(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
|
||||
private void Execute(NvGpuVmm Vmm, GpuMethodCall MethCall)
|
||||
{
|
||||
//TODO: Some registers and copy modes are still not implemented.
|
||||
int Control = PBEntry.Arguments[0];
|
||||
int Control = MethCall.Argument;
|
||||
|
||||
long DstAddress = MakeInt64From2xInt32(NvGpuEngineP2mfReg.DstAddress);
|
||||
|
||||
int DstPitch = ReadRegister(NvGpuEngineP2mfReg.DstPitch);
|
||||
int DstBlkDim = ReadRegister(NvGpuEngineP2mfReg.DstBlockDim);
|
||||
|
||||
int DstX = ReadRegister(NvGpuEngineP2mfReg.DstX);
|
||||
int DstY = ReadRegister(NvGpuEngineP2mfReg.DstY);
|
||||
|
||||
int DstWidth = ReadRegister(NvGpuEngineP2mfReg.DstWidth);
|
||||
int DstHeight = ReadRegister(NvGpuEngineP2mfReg.DstHeight);
|
||||
|
||||
int LineLengthIn = ReadRegister(NvGpuEngineP2mfReg.LineLengthIn);
|
||||
int LineCount = ReadRegister(NvGpuEngineP2mfReg.LineCount);
|
||||
|
||||
DataBuffer = null;
|
||||
CopyLinear = (Control & 1) != 0;
|
||||
|
||||
Gpu.Fifo.Step();
|
||||
CopyGobBlockHeight = 1 << ((DstBlkDim >> 4) & 0xf);
|
||||
|
||||
for (int Offset = 0; Offset < LineLengthIn; Offset += 4)
|
||||
CopyStartX = DstX;
|
||||
CopyStartY = DstY;
|
||||
|
||||
CopyWidth = DstWidth;
|
||||
CopyHeight = DstHeight;
|
||||
|
||||
CopyAddress = DstAddress;
|
||||
|
||||
CopyOffset = 0;
|
||||
CopySize = LineLengthIn * LineCount;
|
||||
|
||||
Buffer = new byte[CopySize];
|
||||
}
|
||||
|
||||
private void PushData(NvGpuVmm Vmm, GpuMethodCall MethCall)
|
||||
{
|
||||
Vmm.WriteInt32(DstAddress + Offset, DataBuffer[Offset >> 2]);
|
||||
if (Buffer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int Shift = 0; Shift < 32 && CopyOffset < CopySize; Shift += 8, CopyOffset++)
|
||||
{
|
||||
Buffer[CopyOffset] = (byte)(MethCall.Argument >> Shift);
|
||||
}
|
||||
|
||||
if (MethCall.IsLastCall)
|
||||
{
|
||||
if (CopyLinear)
|
||||
{
|
||||
Vmm.WriteBytes(CopyAddress, Buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
BlockLinearSwizzle Swizzle = new BlockLinearSwizzle(CopyWidth, 1, CopyGobBlockHeight);
|
||||
|
||||
int SrcOffset = 0;
|
||||
|
||||
for (int Y = CopyStartY; Y < CopyHeight && SrcOffset < CopySize; Y++)
|
||||
for (int X = CopyStartX; X < CopyWidth && SrcOffset < CopySize; X++)
|
||||
{
|
||||
int DstOffset = Swizzle.GetSwizzleOffset(X, Y);
|
||||
|
||||
Vmm.WriteByte(CopyAddress + DstOffset, Buffer[SrcOffset++]);
|
||||
}
|
||||
}
|
||||
|
||||
private void PushData(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
|
||||
{
|
||||
DataBuffer = PBEntry.Arguments;
|
||||
Buffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private long MakeInt64From2xInt32(NvGpuEngineP2mfReg Reg)
|
||||
@ -79,14 +143,9 @@ namespace Ryujinx.Graphics
|
||||
(uint)Registers[(int)Reg + 1];
|
||||
}
|
||||
|
||||
private void WriteRegister(NvGpuPBEntry PBEntry)
|
||||
private void WriteRegister(GpuMethodCall MethCall)
|
||||
{
|
||||
int ArgsCount = PBEntry.Arguments.Count;
|
||||
|
||||
if (ArgsCount > 0)
|
||||
{
|
||||
Registers[PBEntry.Method] = PBEntry.Arguments[ArgsCount - 1];
|
||||
}
|
||||
Registers[MethCall.Method] = MethCall.Argument;
|
||||
}
|
||||
|
||||
private int ReadRegister(NvGpuEngineP2mfReg Reg)
|
||||
|
@ -1,10 +1,8 @@
|
||||
using Ryujinx.Graphics.Memory;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics
|
||||
{
|
||||
public class NvGpuFifo
|
||||
class NvGpuFifo
|
||||
{
|
||||
private const int MacrosCount = 0x80;
|
||||
private const int MacroIndexMask = MacrosCount - 1;
|
||||
@ -15,33 +13,47 @@ namespace Ryujinx.Graphics
|
||||
|
||||
private NvGpu Gpu;
|
||||
|
||||
private ConcurrentQueue<(NvGpuVmm, NvGpuPBEntry[])> BufferQueue;
|
||||
|
||||
private NvGpuEngine[] SubChannels;
|
||||
|
||||
public AutoResetEvent Event { get; private set; }
|
||||
|
||||
private struct CachedMacro
|
||||
{
|
||||
public int Position { get; private set; }
|
||||
|
||||
private bool ExecutionPending;
|
||||
private int Argument;
|
||||
|
||||
private MacroInterpreter Interpreter;
|
||||
|
||||
public CachedMacro(NvGpuFifo PFifo, INvGpuEngine Engine, int Position)
|
||||
{
|
||||
this.Position = Position;
|
||||
|
||||
ExecutionPending = false;
|
||||
Argument = 0;
|
||||
|
||||
Interpreter = new MacroInterpreter(PFifo, Engine);
|
||||
}
|
||||
|
||||
public void PushParam(int Param)
|
||||
public void StartExecution(int Argument)
|
||||
{
|
||||
Interpreter?.Fifo.Enqueue(Param);
|
||||
this.Argument = Argument;
|
||||
|
||||
ExecutionPending = true;
|
||||
}
|
||||
|
||||
public void Execute(NvGpuVmm Vmm, int[] Mme, int Param)
|
||||
public void Execute(NvGpuVmm Vmm, int[] Mme)
|
||||
{
|
||||
Interpreter?.Execute(Vmm, Mme, Position, Param);
|
||||
if (ExecutionPending)
|
||||
{
|
||||
ExecutionPending = false;
|
||||
|
||||
Interpreter?.Execute(Vmm, Mme, Position, Argument);
|
||||
}
|
||||
}
|
||||
|
||||
public void PushArgument(int Argument)
|
||||
{
|
||||
Interpreter?.Fifo.Enqueue(Argument);
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,148 +68,109 @@ namespace Ryujinx.Graphics
|
||||
{
|
||||
this.Gpu = Gpu;
|
||||
|
||||
BufferQueue = new ConcurrentQueue<(NvGpuVmm, NvGpuPBEntry[])>();
|
||||
|
||||
SubChannels = new NvGpuEngine[8];
|
||||
|
||||
Macros = new CachedMacro[MacrosCount];
|
||||
|
||||
Mme = new int[MmeWords];
|
||||
|
||||
Event = new AutoResetEvent(false);
|
||||
}
|
||||
|
||||
public void PushBuffer(NvGpuVmm Vmm, NvGpuPBEntry[] Buffer)
|
||||
public void CallMethod(NvGpuVmm Vmm, GpuMethodCall MethCall)
|
||||
{
|
||||
BufferQueue.Enqueue((Vmm, Buffer));
|
||||
|
||||
Event.Set();
|
||||
}
|
||||
|
||||
public void DispatchCalls()
|
||||
if ((NvGpuFifoMeth)MethCall.Method == NvGpuFifoMeth.BindChannel)
|
||||
{
|
||||
while (Step());
|
||||
}
|
||||
NvGpuEngine Engine = (NvGpuEngine)MethCall.Argument;
|
||||
|
||||
private (NvGpuVmm Vmm, NvGpuPBEntry[] Pb) Curr;
|
||||
|
||||
private int CurrPbEntryIndex;
|
||||
|
||||
public bool Step()
|
||||
{
|
||||
while (Curr.Pb == null || Curr.Pb.Length <= CurrPbEntryIndex)
|
||||
{
|
||||
if (!BufferQueue.TryDequeue(out Curr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Gpu.Engine3d.ResetCache();
|
||||
|
||||
Gpu.ResourceManager.ClearPbCache();
|
||||
|
||||
CurrPbEntryIndex = 0;
|
||||
}
|
||||
|
||||
CallMethod(Curr.Vmm, Curr.Pb[CurrPbEntryIndex++]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CallMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
|
||||
{
|
||||
if ((NvGpuFifoMeth)PBEntry.Method == NvGpuFifoMeth.BindChannel)
|
||||
{
|
||||
NvGpuEngine Engine = (NvGpuEngine)PBEntry.Arguments[0];
|
||||
|
||||
SubChannels[PBEntry.SubChannel] = Engine;
|
||||
SubChannels[MethCall.SubChannel] = Engine;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (SubChannels[PBEntry.SubChannel])
|
||||
switch (SubChannels[MethCall.SubChannel])
|
||||
{
|
||||
case NvGpuEngine._2d: Call2dMethod (Vmm, PBEntry); break;
|
||||
case NvGpuEngine._3d: Call3dMethod (Vmm, PBEntry); break;
|
||||
case NvGpuEngine.P2mf: CallP2mfMethod(Vmm, PBEntry); break;
|
||||
case NvGpuEngine.M2mf: CallM2mfMethod(Vmm, PBEntry); break;
|
||||
case NvGpuEngine._2d: Call2dMethod (Vmm, MethCall); break;
|
||||
case NvGpuEngine._3d: Call3dMethod (Vmm, MethCall); break;
|
||||
case NvGpuEngine.P2mf: CallP2mfMethod(Vmm, MethCall); break;
|
||||
case NvGpuEngine.M2mf: CallM2mfMethod(Vmm, MethCall); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Call2dMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
|
||||
private void Call2dMethod(NvGpuVmm Vmm, GpuMethodCall MethCall)
|
||||
{
|
||||
Gpu.Engine2d.CallMethod(Vmm, PBEntry);
|
||||
Gpu.Engine2d.CallMethod(Vmm, MethCall);
|
||||
}
|
||||
|
||||
private void Call3dMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
|
||||
private void Call3dMethod(NvGpuVmm Vmm, GpuMethodCall MethCall)
|
||||
{
|
||||
if (PBEntry.Method < 0x80)
|
||||
if (MethCall.Method < 0x80)
|
||||
{
|
||||
switch ((NvGpuFifoMeth)PBEntry.Method)
|
||||
switch ((NvGpuFifoMeth)MethCall.Method)
|
||||
{
|
||||
case NvGpuFifoMeth.SetMacroUploadAddress:
|
||||
{
|
||||
CurrMacroPosition = PBEntry.Arguments[0];
|
||||
CurrMacroPosition = MethCall.Argument;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case NvGpuFifoMeth.SendMacroCodeData:
|
||||
{
|
||||
foreach (int Arg in PBEntry.Arguments)
|
||||
{
|
||||
Mme[CurrMacroPosition++] = Arg;
|
||||
}
|
||||
Mme[CurrMacroPosition++] = MethCall.Argument;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case NvGpuFifoMeth.SetMacroBindingIndex:
|
||||
{
|
||||
CurrMacroBindIndex = PBEntry.Arguments[0];
|
||||
CurrMacroBindIndex = MethCall.Argument;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case NvGpuFifoMeth.BindMacro:
|
||||
{
|
||||
int Position = PBEntry.Arguments[0];
|
||||
int Position = MethCall.Argument;
|
||||
|
||||
Macros[CurrMacroBindIndex] = new CachedMacro(this, Gpu.Engine3d, Position);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default: CallP2mfMethod(Vmm, MethCall); break;
|
||||
}
|
||||
}
|
||||
else if (PBEntry.Method < 0xe00)
|
||||
else if (MethCall.Method < 0xe00)
|
||||
{
|
||||
Gpu.Engine3d.CallMethod(Vmm, PBEntry);
|
||||
Gpu.Engine3d.CallMethod(Vmm, MethCall);
|
||||
}
|
||||
else
|
||||
{
|
||||
int MacroIndex = (PBEntry.Method >> 1) & MacroIndexMask;
|
||||
int MacroIndex = (MethCall.Method >> 1) & MacroIndexMask;
|
||||
|
||||
if ((PBEntry.Method & 1) != 0)
|
||||
if ((MethCall.Method & 1) != 0)
|
||||
{
|
||||
foreach (int Arg in PBEntry.Arguments)
|
||||
{
|
||||
Macros[MacroIndex].PushParam(Arg);
|
||||
}
|
||||
Macros[MacroIndex].PushArgument(MethCall.Argument);
|
||||
}
|
||||
else
|
||||
{
|
||||
Macros[MacroIndex].Execute(Vmm, Mme, PBEntry.Arguments[0]);
|
||||
Macros[MacroIndex].StartExecution(MethCall.Argument);
|
||||
}
|
||||
|
||||
if (MethCall.IsLastCall)
|
||||
{
|
||||
Macros[MacroIndex].Execute(Vmm, Mme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CallP2mfMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
|
||||
private void CallP2mfMethod(NvGpuVmm Vmm, GpuMethodCall MethCall)
|
||||
{
|
||||
Gpu.EngineP2mf.CallMethod(Vmm, PBEntry);
|
||||
Gpu.EngineP2mf.CallMethod(Vmm, MethCall);
|
||||
}
|
||||
|
||||
private void CallM2mfMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
|
||||
private void CallM2mfMethod(NvGpuVmm Vmm, GpuMethodCall MethCall)
|
||||
{
|
||||
Gpu.EngineM2mf.CallMethod(Vmm, PBEntry);
|
||||
Gpu.EngineM2mf.CallMethod(Vmm, MethCall);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,5 +2,5 @@ using Ryujinx.Graphics.Memory;
|
||||
|
||||
namespace Ryujinx.Graphics
|
||||
{
|
||||
delegate void NvGpuMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry);
|
||||
delegate void NvGpuMethod(NvGpuVmm Vmm, GpuMethodCall MethCall);
|
||||
}
|
@ -95,6 +95,7 @@ namespace Ryujinx.Graphics.Texture
|
||||
{ GalImageFormat.RGBA32, new ImageDescriptor(16, 1, 1, TargetBuffer.Color) },
|
||||
{ GalImageFormat.RGBA16, new ImageDescriptor(8, 1, 1, TargetBuffer.Color) },
|
||||
{ GalImageFormat.RG32, new ImageDescriptor(8, 1, 1, TargetBuffer.Color) },
|
||||
{ GalImageFormat.RGBX8, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) },
|
||||
{ GalImageFormat.RGBA8, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) },
|
||||
{ GalImageFormat.BGRA8, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) },
|
||||
{ GalImageFormat.RGB10A2, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) },
|
||||
@ -131,9 +132,10 @@ namespace Ryujinx.Graphics.Texture
|
||||
{ GalImageFormat.Astc2D10x5, new ImageDescriptor(16, 10, 5, TargetBuffer.Color) },
|
||||
{ GalImageFormat.Astc2D10x6, new ImageDescriptor(16, 10, 6, TargetBuffer.Color) },
|
||||
|
||||
{ GalImageFormat.D16, new ImageDescriptor(2, 1, 1, TargetBuffer.Depth) },
|
||||
{ GalImageFormat.D24, new ImageDescriptor(4, 1, 1, TargetBuffer.Depth) },
|
||||
{ GalImageFormat.D24S8, new ImageDescriptor(4, 1, 1, TargetBuffer.DepthStencil) },
|
||||
{ GalImageFormat.D32, new ImageDescriptor(4, 1, 1, TargetBuffer.Depth) },
|
||||
{ GalImageFormat.D16, new ImageDescriptor(2, 1, 1, TargetBuffer.Depth) },
|
||||
{ GalImageFormat.D32S8, new ImageDescriptor(8, 1, 1, TargetBuffer.DepthStencil) }
|
||||
};
|
||||
|
||||
@ -198,6 +200,7 @@ namespace Ryujinx.Graphics.Texture
|
||||
case GalSurfaceFormat.R8Uint: return GalImageFormat.R8 | Uint;
|
||||
case GalSurfaceFormat.B5G6R5Unorm: return GalImageFormat.RGB565 | Unorm;
|
||||
case GalSurfaceFormat.BGR5A1Unorm: return GalImageFormat.BGR5A1 | Unorm;
|
||||
case GalSurfaceFormat.RGBX8Unorm: return GalImageFormat.RGBX8 | Unorm;
|
||||
}
|
||||
|
||||
throw new NotImplementedException(Format.ToString());
|
||||
@ -210,6 +213,7 @@ namespace Ryujinx.Graphics.Texture
|
||||
case GalZetaFormat.D32Float: return GalImageFormat.D32 | Float;
|
||||
case GalZetaFormat.S8D24Unorm: return GalImageFormat.D24S8 | Unorm;
|
||||
case GalZetaFormat.D16Unorm: return GalImageFormat.D16 | Unorm;
|
||||
case GalZetaFormat.D24X8Unorm: return GalImageFormat.D24 | Unorm;
|
||||
case GalZetaFormat.D24S8Unorm: return GalImageFormat.D24S8 | Unorm;
|
||||
case GalZetaFormat.D32S8X24Float: return GalImageFormat.D32S8 | Float;
|
||||
}
|
||||
@ -283,6 +287,45 @@ namespace Ryujinx.Graphics.Texture
|
||||
}
|
||||
}
|
||||
|
||||
public static bool CopyTexture(
|
||||
NvGpuVmm Vmm,
|
||||
GalImage SrcImage,
|
||||
GalImage DstImage,
|
||||
long SrcAddress,
|
||||
long DstAddress,
|
||||
int SrcX,
|
||||
int SrcY,
|
||||
int DstX,
|
||||
int DstY,
|
||||
int Width,
|
||||
int Height)
|
||||
{
|
||||
ISwizzle SrcSwizzle = TextureHelper.GetSwizzle(SrcImage);
|
||||
ISwizzle DstSwizzle = TextureHelper.GetSwizzle(DstImage);
|
||||
|
||||
ImageDescriptor Desc = GetImageDescriptor(SrcImage.Format);
|
||||
|
||||
if (GetImageDescriptor(DstImage.Format).BytesPerPixel != Desc.BytesPerPixel)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int BytesPerPixel = Desc.BytesPerPixel;
|
||||
|
||||
for (int Y = 0; Y < Height; Y++)
|
||||
for (int X = 0; X < Width; X++)
|
||||
{
|
||||
long SrcOffset = (uint)SrcSwizzle.GetSwizzleOffset(SrcX + X, SrcY + Y);
|
||||
long DstOffset = (uint)DstSwizzle.GetSwizzleOffset(DstX + X, DstY + Y);
|
||||
|
||||
byte[] Texel = Vmm.ReadBytes(SrcAddress + SrcOffset, BytesPerPixel);
|
||||
|
||||
Vmm.WriteBytes(DstAddress + DstOffset, Texel);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static int GetSize(GalImage Image)
|
||||
{
|
||||
ImageDescriptor Desc = GetImageDescriptor(Image.Format);
|
||||
|
@ -76,7 +76,7 @@ namespace Ryujinx.Graphics
|
||||
{
|
||||
Ranges.RemoveAt(NewIndex + 1);
|
||||
|
||||
Ranges[NewIndex] = new ValueRange<T>(Range.Start, Next.End, Range.Value);
|
||||
Ranges[NewIndex] = new ValueRange<T>(Ranges[NewIndex].Start, Next.End, Range.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -181,15 +181,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvHostChannel
|
||||
|
||||
private static void PushGpfifo(ServiceCtx Context, NvGpuVmm Vmm, long Gpfifo)
|
||||
{
|
||||
long VA = Gpfifo & 0xff_ffff_ffff;
|
||||
|
||||
int Size = (int)(Gpfifo >> 40) & 0x7ffffc;
|
||||
|
||||
byte[] Data = Vmm.ReadBytes(VA, Size);
|
||||
|
||||
NvGpuPBEntry[] PushBuffer = NvGpuPushBuffer.Decode(Data);
|
||||
|
||||
Context.Device.Gpu.Fifo.PushBuffer(Vmm, PushBuffer);
|
||||
Context.Device.Gpu.Pusher.Push(Vmm, Gpfifo);
|
||||
}
|
||||
|
||||
public static NvChannel GetChannel(ServiceCtx Context, NvChannelName Channel)
|
||||
|
@ -88,12 +88,12 @@ namespace Ryujinx.HLE
|
||||
|
||||
public bool WaitFifo()
|
||||
{
|
||||
return Gpu.Fifo.Event.WaitOne(8);
|
||||
return Gpu.Pusher.WaitForCommands();
|
||||
}
|
||||
|
||||
public void ProcessFrame()
|
||||
{
|
||||
Gpu.Fifo.DispatchCalls();
|
||||
Gpu.Pusher.DispatchCalls();
|
||||
}
|
||||
|
||||
internal void Unload()
|
||||
|
Loading…
Reference in New Issue
Block a user