Ver código fonte

Initial conditional rendering support (#1012)

* Initial conditional rendering support

* Properly reset state

* Support conditional modes and skeleton a counter cache for future host conditional rendering

* Address PR feedback
gdkchan 6 anos atrás
pai
commit
6bfe4715f0

+ 5 - 0
Ryujinx.Graphics.Gpu/Engine/MethodClear.cs

@@ -13,6 +13,11 @@ namespace Ryujinx.Graphics.Gpu.Engine
         /// <param name="argument">Method call argument</param>
         private void Clear(GpuState state, int argument)
         {
+            if (!GetRenderEnable(state))
+            {
+                return;
+            }
+
             // Scissor affects clears aswell.
             if (state.QueryModified(MethodOffset.ScissorState))
             {

+ 82 - 0
Ryujinx.Graphics.Gpu/Engine/MethodConditionalRendering.cs

@@ -0,0 +1,82 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.Gpu.State;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+    partial class Methods
+    {
+        /// <summary>
+        /// Checks if draws and clears should be performed, according
+        /// to currently set conditional rendering conditions.
+        /// </summary>
+        /// <param name="state">GPU state</param>
+        /// <returns>True if rendering is enabled, false otherwise</returns>
+        private bool GetRenderEnable(GpuState state)
+        {
+            ConditionState condState = state.Get<ConditionState>(MethodOffset.ConditionState);
+
+            switch (condState.Condition)
+            {
+                case Condition.Always:
+                    return true;
+                case Condition.Never:
+                    return false;
+                case Condition.ResultNonZero:
+                    return CounterNonZero(condState.Address.Pack());
+                case Condition.Equal:
+                    return CounterCompare(condState.Address.Pack(), true);
+                case Condition.NotEqual:
+                    return CounterCompare(condState.Address.Pack(), false);
+            }
+
+            Logger.PrintWarning(LogClass.Gpu, $"Invalid conditional render condition \"{condState.Condition}\".");
+
+            return true;
+        }
+
+        /// <summary>
+        /// Checks if the counter value at a given GPU memory address is non-zero.
+        /// </summary>
+        /// <param name="gpuVa">GPU virtual address of the counter value</param>
+        /// <returns>True if the value is not zero, false otherwise</returns>
+        private bool CounterNonZero(ulong gpuVa)
+        {
+            if (!FindAndFlush(gpuVa))
+            {
+                return false;
+            }
+
+            return _context.MemoryAccessor.ReadUInt64(gpuVa) != 0;
+        }
+
+        /// <summary>
+        /// Checks if the counter at a given GPU memory address passes a specified equality comparison.
+        /// </summary>
+        /// <param name="gpuVa">GPU virtual address</param>
+        /// <param name="isEqual">True to check if the values are equal, false to check if they are not equal</param>
+        /// <returns>True if the condition is met, false otherwise</returns>
+        private bool CounterCompare(ulong gpuVa, bool isEqual)
+        {
+            if (!FindAndFlush(gpuVa) && !FindAndFlush(gpuVa + 16))
+            {
+                return false;
+            }
+
+            ulong x = _context.MemoryAccessor.ReadUInt64(gpuVa);
+            ulong y = _context.MemoryAccessor.ReadUInt64(gpuVa + 16);
+
+            return isEqual ? x == y : x != y;
+        }
+
+        /// <summary>
+        /// Tries to find a counter that is supposed to be written at the specified address,
+        /// flushing if necessary.
+        /// </summary>
+        /// <param name="gpuVa">GPU virtual address where the counter is supposed to be written</param>
+        /// <returns>True if a counter value is found at the specified address, false otherwise</returns>
+        private bool FindAndFlush(ulong gpuVa)
+        {
+            return _counterCache.Contains(gpuVa);
+        }
+    }
+}

+ 8 - 1
Ryujinx.Graphics.Gpu/Engine/MethodDraw.cs

@@ -35,8 +35,15 @@ namespace Ryujinx.Graphics.Gpu.Engine
         /// <param name="argument">Method call argument</param>
         private void DrawEnd(GpuState state, int argument)
         {
-            if (_instancedDrawPending)
+            bool renderEnable = GetRenderEnable(state);
+
+            if (!renderEnable || _instancedDrawPending)
             {
+                if (!renderEnable)
+                {
+                    PerformDeferredDraws();
+                }
+
                 _drawIndexed = false;
 
                 return;

+ 7 - 0
Ryujinx.Graphics.Gpu/Engine/MethodReport.cs

@@ -1,5 +1,6 @@
 using Ryujinx.Common;
 using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Memory;
 using Ryujinx.Graphics.Gpu.State;
 using System;
 using System.Runtime.InteropServices;
@@ -11,6 +12,10 @@ namespace Ryujinx.Graphics.Gpu.Engine
         private const int NsToTicksFractionNumerator   = 384;
         private const int NsToTicksFractionDenominator = 625;
 
+        private ulong _runningCounter;
+
+        private readonly CounterCache _counterCache = new CounterCache();
+
         /// <summary>
         /// Writes a GPU counter to guest memory.
         /// </summary>
@@ -98,6 +103,8 @@ namespace Ryujinx.Graphics.Gpu.Engine
             var rs = state.Get<ReportState>(MethodOffset.ReportState);
 
             _context.MemoryAccessor.Write(rs.Address.Pack(), data);
+
+            _counterCache.AddOrUpdate(rs.Address.Pack());
         }
 
         /// <summary>

+ 2 - 0
Ryujinx.Graphics.Gpu/Engine/Methods.cs

@@ -52,6 +52,8 @@ namespace Ryujinx.Graphics.Gpu.Engine
 
             BufferManager  = new BufferManager(context);
             TextureManager = new TextureManager(context);
+
+            context.MemoryManager.MemoryUnmapped += _counterCache.MemoryUnmappedHandler;
         }
 
         /// <summary>

+ 140 - 0
Ryujinx.Graphics.Gpu/Memory/CounterCache.cs

@@ -0,0 +1,140 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+    /// <summary>
+    /// Represents the GPU counter cache.
+    /// </summary>
+    class CounterCache
+    {
+        private struct CounterEntry
+        {
+            public ulong Address { get; }
+
+            public CounterEntry(ulong address)
+            {
+                Address = address;
+            }
+        }
+
+        private readonly List<CounterEntry> _items;
+
+        /// <summary>
+        /// Creates a new instance of the GPU counter cache.
+        /// </summary>
+        public CounterCache()
+        {
+            _items = new List<CounterEntry>();
+        }
+
+        /// <summary>
+        /// Adds a new counter to the counter cache, or updates a existing one.
+        /// </summary>
+        /// <param name="gpuVa">GPU virtual address where the counter will be written in memory</param>
+        public void AddOrUpdate(ulong gpuVa)
+        {
+            int index = BinarySearch(gpuVa);
+
+            CounterEntry entry = new CounterEntry(gpuVa);
+
+            if (index < 0)
+            {
+                _items.Insert(~index, entry);
+            }
+            else
+            {
+                _items[index] = entry;
+            }
+        }
+
+        /// <summary>
+        /// Handles removal of counters written to a memory region being unmapped.
+        /// </summary>
+        /// <param name="sender">Sender object</param>
+        /// <param name="e">Event arguments</param>
+        public void MemoryUnmappedHandler(object sender, UnmapEventArgs e) => RemoveRange(e.Address, e.Size);
+
+        private void RemoveRange(ulong gpuVa, ulong size)
+        {
+            int index = BinarySearch(gpuVa + size - 1);
+
+            if (index < 0)
+            {
+                index = ~index;
+            }
+
+            if (index >= _items.Count || !InRange(gpuVa, size, _items[index].Address))
+            {
+                return;
+            }
+
+            int count = 1;
+
+            while (index > 0 && InRange(gpuVa, size, _items[index - 1].Address))
+            {
+                index--;
+                count++;
+            }
+
+            _items.RemoveRange(index, count);
+        }
+
+        /// <summary>
+        /// Checks whenever an address falls inside a given range.
+        /// </summary>
+        /// <param name="startVa">Range start address</param>
+        /// <param name="size">Range size</param>
+        /// <param name="gpuVa">Address being checked</param>
+        /// <returns>True if the address falls inside the range, false otherwise</returns>
+        private static bool InRange(ulong startVa, ulong size, ulong gpuVa)
+        {
+            return gpuVa >= startVa && gpuVa < startVa + size;
+        }
+
+        /// <summary>
+        /// Check if any counter value was written to the specified GPU virtual memory address.
+        /// </summary>
+        /// <param name="gpuVa">GPU virtual address</param>
+        /// <returns>True if any counter value was written on the specified address, false otherwise</returns>
+        public bool Contains(ulong gpuVa)
+        {
+            return BinarySearch(gpuVa) >= 0;
+        }
+
+        /// <summary>
+        /// Performs binary search of an address on the list.
+        /// </summary>
+        /// <param name="address">Address to search</param>
+        /// <returns>Index of the item, or complement of the index of the nearest item with lower value</returns>
+        private int BinarySearch(ulong address)
+        {
+            int left = 0;
+            int right = _items.Count - 1;
+
+            while (left <= right)
+            {
+                int range = right - left;
+
+                int middle = left + (range >> 1);
+
+               CounterEntry item = _items[middle];
+
+                if (item.Address == address)
+                {
+                    return middle;
+                }
+
+                if (address < item.Address)
+                {
+                    right = middle - 1;
+                }
+                else
+                {
+                    left = middle + 1;
+                }
+            }
+
+            return ~left;
+        }
+    }
+}

+ 8 - 1
Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs

@@ -1,3 +1,5 @@
+using System;
+
 namespace Ryujinx.Graphics.Gpu.Memory
 {
     /// <summary>
@@ -27,7 +29,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
         private const ulong PteUnmapped = 0xffffffff_ffffffff;
         private const ulong PteReserved = 0xffffffff_fffffffe;
 
-        private ulong[][] _pageTable;
+        private readonly ulong[][] _pageTable;
+
+        public event EventHandler<UnmapEventArgs> MemoryUnmapped;
 
         /// <summary>
         /// Creates a new instance of the GPU memory manager.
@@ -181,6 +185,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
                 {
                     SetPte(va + offset, PteUnmapped);
                 }
+
+                // Event handlers are not expected to be thread safe.
+                MemoryUnmapped?.Invoke(this, new UnmapEventArgs(va, size));
             }
         }
 

+ 14 - 0
Ryujinx.Graphics.Gpu/Memory/UnmapEventArgs.cs

@@ -0,0 +1,14 @@
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+    public class UnmapEventArgs
+    {
+        public ulong Address { get; }
+        public ulong Size { get; }
+
+        public UnmapEventArgs(ulong address, ulong size)
+        {
+            Address = address;
+            Size = size;
+        }
+    }
+}

+ 3 - 0
Ryujinx.Graphics.Gpu/State/GpuState.cs

@@ -133,6 +133,9 @@ namespace Ryujinx.Graphics.Gpu.State
             // Default front stencil mask.
             _backingMemory[0x4e7] = 0xff;
 
+            // Conditional rendering condition.
+            _backingMemory[0x556] = (int)Condition.Always;
+
             // Default color mask.
             for (int index = 0; index < Constants.TotalRenderTargets; index++)
             {