aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Graphics.Gpu/Memory/MultiRangeBuffer.cs
blob: d92b0836e93e663b7ca5403b7c7596b5b0b13b99 (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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
using Ryujinx.Graphics.GAL;
using Ryujinx.Memory.Range;
using System;
using System.Collections.Generic;

namespace Ryujinx.Graphics.Gpu.Memory
{
    /// <summary>
    /// Buffer, used to store vertex and index data, uniform and storage buffers, and others.
    /// </summary>
    class MultiRangeBuffer : IMultiRangeItem, IDisposable
    {
        private readonly GpuContext _context;

        /// <summary>
        /// Host buffer handle.
        /// </summary>
        public BufferHandle Handle { get; }

        /// <summary>
        /// Range of memory where the data is located.
        /// </summary>
        public MultiRange Range { get; }

        /// <summary>
        /// Ever increasing counter value indicating when the buffer was modified relative to other buffers.
        /// </summary>
        public int ModificationSequenceNumber { get; private set; }

        /// <summary>
        /// Physical buffer dependency entry.
        /// </summary>
        private readonly struct PhysicalDependency
        {
            /// <summary>
            /// Physical buffer.
            /// </summary>
            public readonly Buffer PhysicalBuffer;

            /// <summary>
            /// Offset of the range on the physical buffer.
            /// </summary>
            public readonly ulong PhysicalOffset;

            /// <summary>
            /// Offset of the range on the virtual buffer.
            /// </summary>
            public readonly ulong VirtualOffset;

            /// <summary>
            /// Size of the range.
            /// </summary>
            public readonly ulong Size;

            /// <summary>
            /// Creates a new physical dependency.
            /// </summary>
            /// <param name="physicalBuffer">Physical buffer</param>
            /// <param name="physicalOffset">Offset of the range on the physical buffer</param>
            /// <param name="virtualOffset">Offset of the range on the virtual buffer</param>
            /// <param name="size">Size of the range</param>
            public PhysicalDependency(Buffer physicalBuffer, ulong physicalOffset, ulong virtualOffset, ulong size)
            {
                PhysicalBuffer = physicalBuffer;
                PhysicalOffset = physicalOffset;
                VirtualOffset = virtualOffset;
                Size = size;
            }
        }

        private List<PhysicalDependency> _dependencies;
        private BufferModifiedRangeList _modifiedRanges = null;

        /// <summary>
        /// Creates a new instance of the buffer.
        /// </summary>
        /// <param name="context">GPU context that the buffer belongs to</param>
        /// <param name="range">Range of memory where the data is mapped</param>
        public MultiRangeBuffer(GpuContext context, MultiRange range)
        {
            _context = context;
            Range = range;
            Handle = context.Renderer.CreateBuffer((int)range.GetSize());
        }

        /// <summary>
        /// Creates a new instance of the buffer.
        /// </summary>
        /// <param name="context">GPU context that the buffer belongs to</param>
        /// <param name="range">Range of memory where the data is mapped</param>
        /// <param name="storages">Backing memory for the buffer</param>
        public MultiRangeBuffer(GpuContext context, MultiRange range, ReadOnlySpan<BufferRange> storages)
        {
            _context = context;
            Range = range;
            Handle = context.Renderer.CreateBufferSparse(storages);
        }

        /// <summary>
        /// Gets a sub-range from the buffer.
        /// </summary>
        /// <remarks>
        /// This can be used to bind and use sub-ranges of the buffer on the host API.
        /// </remarks>
        /// <param name="range">Range of memory where the data is mapped</param>
        /// <returns>The buffer sub-range</returns>
        public BufferRange GetRange(MultiRange range)
        {
            int offset = Range.FindOffset(range);

            return new BufferRange(Handle, offset, (int)range.GetSize());
        }

        /// <summary>
        /// Removes all physical buffer dependencies.
        /// </summary>
        public void ClearPhysicalDependencies()
        {
            _dependencies?.Clear();
        }

        /// <summary>
        /// Adds a physical buffer dependency.
        /// </summary>
        /// <param name="buffer">Physical buffer to be added</param>
        /// <param name="rangeAddress">Address inside the physical buffer where the virtual buffer range is located</param>
        /// <param name="dstOffset">Offset inside the virtual buffer where the physical range is located</param>
        /// <param name="rangeSize">Size of the range in bytes</param>
        public void AddPhysicalDependency(Buffer buffer, ulong rangeAddress, ulong dstOffset, ulong rangeSize)
        {
            (_dependencies ??= new()).Add(new(buffer, rangeAddress - buffer.Address, dstOffset, rangeSize));
            buffer.AddVirtualDependency(this);
        }

        /// <summary>
        /// Tries to get the physical range corresponding to the given physical buffer.
        /// </summary>
        /// <param name="buffer">Physical buffer</param>
        /// <param name="minimumVirtOffset">Minimum virtual offset that a range match can have</param>
        /// <param name="physicalOffset">Physical offset of the match</param>
        /// <param name="virtualOffset">Virtual offset of the match, always greater than or equal <paramref name="minimumVirtOffset"/></param>
        /// <param name="size">Size of the range match</param>
        /// <returns>True if a match was found for the given parameters, false otherwise</returns>
        public bool TryGetPhysicalOffset(Buffer buffer, ulong minimumVirtOffset, out ulong physicalOffset, out ulong virtualOffset, out ulong size)
        {
            physicalOffset = 0;
            virtualOffset = 0;
            size = 0;

            if (_dependencies != null)
            {
                foreach (var dependency in _dependencies)
                {
                    if (dependency.PhysicalBuffer == buffer && dependency.VirtualOffset >= minimumVirtOffset)
                    {
                        physicalOffset = dependency.PhysicalOffset;
                        virtualOffset = dependency.VirtualOffset;
                        size = dependency.Size;

                        return true;
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// Adds a modified virtual memory range.
        /// </summary>
        /// <remarks>
        /// This is only required when the host does not support sparse buffers, otherwise only physical buffers need to track modification.
        /// </remarks>
        /// <param name="range">Modified range</param>
        /// <param name="modifiedSequenceNumber">ModificationSequenceNumber</param>
        public void AddModifiedRegion(MultiRange range, int modifiedSequenceNumber)
        {
            _modifiedRanges ??= new(_context, null, null);

            for (int i = 0; i < range.Count; i++)
            {
                MemoryRange subRange = range.GetSubRange(i);

                _modifiedRanges.SignalModified(subRange.Address, subRange.Size);
            }

            ModificationSequenceNumber = modifiedSequenceNumber;
        }

        /// <summary>
        /// Calls the specified <paramref name="rangeAction"/> for all modified ranges that overlaps with <paramref name="buffer"/>.
        /// </summary>
        /// <param name="buffer">Buffer to have its range checked</param>
        /// <param name="rangeAction">Action to perform for modified ranges</param>
        public void ConsumeModifiedRegion(Buffer buffer, Action<ulong, ulong> rangeAction)
        {
            ConsumeModifiedRegion(buffer.Address, buffer.Size, rangeAction);
        }

        /// <summary>
        /// Calls the specified <paramref name="rangeAction"/> for all modified ranges that overlaps with <paramref name="address"/> and <paramref name="size"/>.
        /// </summary>
        /// <param name="address">Address of the region to consume</param>
        /// <param name="size">Size of the region to consume</param>
        /// <param name="rangeAction">Action to perform for modified ranges</param>
        public void ConsumeModifiedRegion(ulong address, ulong size, Action<ulong, ulong> rangeAction)
        {
            if (_modifiedRanges != null)
            {
                _modifiedRanges.GetRanges(address, size, rangeAction);
                _modifiedRanges.Clear(address, size);
            }
        }

        /// <summary>
        /// Gets data from the specified region of the buffer, and places it on <paramref name="output"/>.
        /// </summary>
        /// <param name="output">Span to put the data into</param>
        /// <param name="offset">Offset of the buffer to get the data from</param>
        /// <param name="size">Size of the data in bytes</param>
        public void GetData(Span<byte> output, int offset, int size)
        {
            using PinnedSpan<byte> data = _context.Renderer.GetBufferData(Handle, offset, size);
            data.Get().CopyTo(output);
        }

        /// <summary>
        /// Disposes the host buffer.
        /// </summary>
        public void Dispose()
        {
            if (_dependencies != null)
            {
                foreach (var dependency in _dependencies)
                {
                    dependency.PhysicalBuffer.RemoveVirtualDependency(this);
                }

                _dependencies = null;
            }

            _context.Renderer.DeleteBuffer(Handle);
        }
    }
}