aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Graphics.Gpu/Engine/Threed/ConstantBufferUpdater.cs
blob: 2095fcd7a0e45cb659faacb7992530866486d73c (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
using System;
using System.Runtime.InteropServices;

namespace Ryujinx.Graphics.Gpu.Engine.Threed
{
    /// <summary>
    /// Constant buffer updater.
    /// </summary>
    class ConstantBufferUpdater
    {
        private const int UniformDataCacheSize = 512;

        private readonly GpuChannel _channel;
        private readonly DeviceStateWithShadow<ThreedClassState> _state;

        // State associated with direct uniform buffer updates.
        // This state is used to attempt to batch together consecutive updates.
        private ulong _ubBeginCpuAddress = 0;
        private ulong _ubFollowUpAddress = 0;
        private ulong _ubByteCount = 0;
        private int _ubIndex = 0;
        private readonly int[] _ubData = new int[UniformDataCacheSize];

        /// <summary>
        /// Creates a new instance of the constant buffer updater.
        /// </summary>
        /// <param name="channel">GPU channel</param>
        /// <param name="state">Channel state</param>
        public ConstantBufferUpdater(GpuChannel channel, DeviceStateWithShadow<ThreedClassState> state)
        {
            _channel = channel;
            _state = state;
        }

        /// <summary>
        /// Binds a uniform buffer for the vertex shader stage.
        /// </summary>
        /// <param name="argument">Method call argument</param>
        public void BindVertex(int argument)
        {
            Bind(argument, ShaderType.Vertex);
        }

        /// <summary>
        /// Binds a uniform buffer for the tessellation control shader stage.
        /// </summary>
        /// <param name="argument">Method call argument</param>
        public void BindTessControl(int argument)
        {
            Bind(argument, ShaderType.TessellationControl);
        }

        /// <summary>
        /// Binds a uniform buffer for the tessellation evaluation shader stage.
        /// </summary>
        /// <param name="argument">Method call argument</param>
        public void BindTessEvaluation(int argument)
        {
            Bind(argument, ShaderType.TessellationEvaluation);
        }

        /// <summary>
        /// Binds a uniform buffer for the geometry shader stage.
        /// </summary>
        /// <param name="argument">Method call argument</param>
        public void BindGeometry(int argument)
        {
            Bind(argument, ShaderType.Geometry);
        }

        /// <summary>
        /// Binds a uniform buffer for the fragment shader stage.
        /// </summary>
        /// <param name="argument">Method call argument</param>
        public void BindFragment(int argument)
        {
            Bind(argument, ShaderType.Fragment);
        }

        /// <summary>
        /// Binds a uniform buffer for the specified shader stage.
        /// </summary>
        /// <param name="argument">Method call argument</param>
        /// <param name="type">Shader stage that will access the uniform buffer</param>
        private void Bind(int argument, ShaderType type)
        {
            bool enable = (argument & 1) != 0;

            int index = (argument >> 4) & 0x1f;

            FlushUboDirty();

            if (enable)
            {
                var uniformBuffer = _state.State.UniformBufferState;

                ulong address = uniformBuffer.Address.Pack();

                _channel.BufferManager.SetGraphicsUniformBuffer((int)type, index, address, (uint)uniformBuffer.Size);
            }
            else
            {
                _channel.BufferManager.SetGraphicsUniformBuffer((int)type, index, 0, 0);
            }
        }

        /// <summary>
        /// Flushes any queued UBO updates.
        /// </summary>
        public void FlushUboDirty()
        {
            if (_ubFollowUpAddress != 0)
            {
                var memoryManager = _channel.MemoryManager;

                Span<byte> data = MemoryMarshal.Cast<int, byte>(_ubData.AsSpan(0, (int)(_ubByteCount / 4)));

                if (memoryManager.Physical.WriteWithRedundancyCheck(_ubBeginCpuAddress, data))
                {
                    memoryManager.Physical.BufferCache.ForceDirty(memoryManager, _ubFollowUpAddress - _ubByteCount, _ubByteCount);
                }

                _ubFollowUpAddress = 0;
                _ubIndex = 0;
            }
        }

        /// <summary>
        /// Updates the uniform buffer data with inline data.
        /// </summary>
        /// <param name="argument">New uniform buffer data word</param>
        public void Update(int argument)
        {
            var uniformBuffer = _state.State.UniformBufferState;

            ulong address = uniformBuffer.Address.Pack() + (uint)uniformBuffer.Offset;

            if (_ubFollowUpAddress != address || _ubIndex == _ubData.Length)
            {
                FlushUboDirty();

                _ubByteCount = 0;
                _ubBeginCpuAddress = _channel.MemoryManager.Translate(address);
            }

            _ubData[_ubIndex++] = argument;

            _ubFollowUpAddress = address + 4;
            _ubByteCount += 4;

            _state.State.UniformBufferState.Offset += 4;
        }

        /// <summary>
        /// Updates the uniform buffer data with inline data.
        /// </summary>
        /// <param name="data">Data to be written to the uniform buffer</param>
        public void Update(ReadOnlySpan<int> data)
        {
            var uniformBuffer = _state.State.UniformBufferState;

            ulong address = uniformBuffer.Address.Pack() + (uint)uniformBuffer.Offset;

            ulong size = (ulong)data.Length * 4;

            if (_ubFollowUpAddress != address || _ubIndex + data.Length > _ubData.Length)
            {
                FlushUboDirty();

                _ubByteCount = 0;
                _ubBeginCpuAddress = _channel.MemoryManager.Translate(address);
            }

            data.CopyTo(_ubData.AsSpan(_ubIndex));
            _ubIndex += data.Length;

            _ubFollowUpAddress = address + size;
            _ubByteCount += size;

            _state.State.UniformBufferState.Offset += data.Length * 4;
        }
    }
}