aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Graphics.Gpu/Synchronization/Syncpoint.cs
blob: 39fb83c05baaabf200dfe7c9df046f19624bf8d4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
using System;
using System.Collections.Generic;
using System.Threading;

namespace Ryujinx.Graphics.Gpu.Synchronization
{
    /// <summary>
    /// Represents GPU hardware syncpoint.
    /// </summary>
    class Syncpoint
    {
        private int _storedValue;

        public readonly uint Id;

        /// <summary>
        /// The value of the syncpoint.
        /// </summary>
        public uint Value => (uint)_storedValue;

        // TODO: switch to something handling concurrency?
        private readonly List<SyncpointWaiterHandle> _waiters;

        public Syncpoint(uint id)
        {
            Id       = id;
            _waiters = new List<SyncpointWaiterHandle>();
        }

        /// <summary>
        /// Register a new callback for a target threshold.
        /// The callback will be called once the threshold is reached and will automatically be unregistered.
        /// </summary>
        /// <param name="threshold">The target threshold</param>
        /// <param name="callback">The callback to call when the threshold is reached</param>
        /// <returns>The created SyncpointWaiterHandle object or null if already past threshold</returns>
        public SyncpointWaiterHandle RegisterCallback(uint threshold, Action<SyncpointWaiterHandle> callback)
        {
            lock (_waiters)
            {
                if (Value >= threshold)
                {
                    callback(null);

                    return null;
                }
                else
                {
                    SyncpointWaiterHandle waiterInformation = new SyncpointWaiterHandle
                    {
                        Threshold = threshold,
                        Callback  = callback
                    };

                    _waiters.Add(waiterInformation);

                    return waiterInformation;
                }
            }
        }

        public void UnregisterCallback(SyncpointWaiterHandle waiterInformation)
        {
            lock (_waiters)
            {
                _waiters.Remove(waiterInformation);
            }
        }

        /// <summary>
        /// Increment the syncpoint
        /// </summary>
        /// <returns>The incremented value of the syncpoint</returns>
        public uint Increment()
        {
            uint currentValue = (uint)Interlocked.Increment(ref _storedValue);

            SyncpointWaiterHandle expired = null;
            List<SyncpointWaiterHandle> expiredList = null;

            lock (_waiters)
            {
                _waiters.RemoveAll(item =>
                {
                    bool isPastThreshold = currentValue >= item.Threshold;

                    if (isPastThreshold)
                    {
                        if (expired == null)
                        {
                            expired = item;
                        }
                        else
                        {
                            if (expiredList == null)
                            {
                                expiredList = new List<SyncpointWaiterHandle>();
                            }

                            expiredList.Add(item);
                        }
                    }

                    return isPastThreshold;
                });
            }

            // Call the callbacks as a separate step.
            // As we don't know what the callback will be doing,
            // and it could block execution for a indefinite amount of time,
            // we can't call it inside the lock.
            if (expired != null)
            {
                expired.Callback(expired);

                if (expiredList != null)
                {
                    for (int i = 0; i < expiredList.Count; i++)
                    {
                        expiredList[i].Callback(expiredList[i]);
                    }
                }
            }

            return currentValue;
        }
    }
}