/ src / Ryujinx.Graphics.OpenGL / OpenGLRenderer.cs
OpenGLRenderer.cs
  1  using OpenTK.Graphics.OpenGL;
  2  using Ryujinx.Common.Configuration;
  3  using Ryujinx.Common.Logging;
  4  using Ryujinx.Graphics.GAL;
  5  using Ryujinx.Graphics.OpenGL.Image;
  6  using Ryujinx.Graphics.OpenGL.Queries;
  7  using Ryujinx.Graphics.Shader.Translation;
  8  using System;
  9  
 10  namespace Ryujinx.Graphics.OpenGL
 11  {
 12      public sealed class OpenGLRenderer : IRenderer
 13      {
 14          private readonly Pipeline _pipeline;
 15  
 16          public IPipeline Pipeline => _pipeline;
 17  
 18          private readonly Counters _counters;
 19  
 20          private readonly Window _window;
 21  
 22          public IWindow Window => _window;
 23  
 24          private readonly TextureCopy _textureCopy;
 25          private readonly TextureCopy _backgroundTextureCopy;
 26          internal TextureCopy TextureCopy => BackgroundContextWorker.InBackground ? _backgroundTextureCopy : _textureCopy;
 27          internal TextureCopyIncompatible TextureCopyIncompatible { get; }
 28          internal TextureCopyMS TextureCopyMS { get; }
 29  
 30          private readonly Sync _sync;
 31  
 32          public event EventHandler<ScreenCaptureImageInfo> ScreenCaptured;
 33  
 34          internal PersistentBuffers PersistentBuffers { get; }
 35  
 36          internal ResourcePool ResourcePool { get; }
 37  
 38          internal int BufferCount { get; private set; }
 39  
 40          public string GpuVendor { get; private set; }
 41          public string GpuRenderer { get; private set; }
 42          public string GpuVersion { get; private set; }
 43  
 44          public bool PreferThreading => true;
 45  
 46          public OpenGLRenderer()
 47          {
 48              _pipeline = new Pipeline();
 49              _counters = new Counters();
 50              _window = new Window(this);
 51              _textureCopy = new TextureCopy(this);
 52              _backgroundTextureCopy = new TextureCopy(this);
 53              TextureCopyIncompatible = new TextureCopyIncompatible(this);
 54              TextureCopyMS = new TextureCopyMS(this);
 55              _sync = new Sync();
 56              PersistentBuffers = new PersistentBuffers();
 57              ResourcePool = new ResourcePool();
 58          }
 59  
 60          public BufferHandle CreateBuffer(int size, GAL.BufferAccess access)
 61          {
 62              BufferCount++;
 63  
 64              var memType = access & GAL.BufferAccess.MemoryTypeMask;
 65  
 66              if (memType == GAL.BufferAccess.HostMemory)
 67              {
 68                  BufferHandle handle = Buffer.CreatePersistent(size);
 69  
 70                  PersistentBuffers.Map(handle, size);
 71  
 72                  return handle;
 73              }
 74              else
 75              {
 76                  return Buffer.Create(size);
 77              }
 78          }
 79  
 80          public BufferHandle CreateBuffer(nint pointer, int size)
 81          {
 82              throw new NotSupportedException();
 83          }
 84  
 85          public BufferHandle CreateBufferSparse(ReadOnlySpan<BufferRange> storageBuffers)
 86          {
 87              throw new NotSupportedException();
 88          }
 89  
 90          public IImageArray CreateImageArray(int size, bool isBuffer)
 91          {
 92              return new ImageArray(size);
 93          }
 94  
 95          public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
 96          {
 97              return new Program(shaders, info.FragmentOutputMap);
 98          }
 99  
100          public ISampler CreateSampler(SamplerCreateInfo info)
101          {
102              return new Sampler(info);
103          }
104  
105          public ITexture CreateTexture(TextureCreateInfo info)
106          {
107              if (info.Target == Target.TextureBuffer)
108              {
109                  return new TextureBuffer(this, info);
110              }
111              else
112              {
113                  return ResourcePool.GetTextureOrNull(info) ?? new TextureStorage(this, info).CreateDefaultView();
114              }
115          }
116  
117          public ITextureArray CreateTextureArray(int size, bool isBuffer)
118          {
119              return new TextureArray(size);
120          }
121  
122          public void DeleteBuffer(BufferHandle buffer)
123          {
124              PersistentBuffers.Unmap(buffer);
125  
126              Buffer.Delete(buffer);
127          }
128  
129          public HardwareInfo GetHardwareInfo()
130          {
131              return new HardwareInfo(GpuVendor, GpuRenderer, GpuVendor); // OpenGL does not provide a driver name, vendor name is closest analogue.
132          }
133  
134          public PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
135          {
136              return Buffer.GetData(this, buffer, offset, size);
137          }
138  
139          public Capabilities GetCapabilities()
140          {
141              bool intelWindows = HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows;
142              bool intelUnix = HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelUnix;
143              bool amdWindows = HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows;
144  
145              return new Capabilities(
146                  api: TargetApi.OpenGL,
147                  vendorName: GpuVendor,
148                  memoryType: SystemMemoryType.BackendManaged,
149                  hasFrontFacingBug: intelWindows,
150                  hasVectorIndexingBug: amdWindows,
151                  needsFragmentOutputSpecialization: false,
152                  reduceShaderPrecision: false,
153                  supportsAstcCompression: HwCapabilities.SupportsAstcCompression,
154                  supportsBc123Compression: HwCapabilities.SupportsTextureCompressionS3tc,
155                  supportsBc45Compression: HwCapabilities.SupportsTextureCompressionRgtc,
156                  supportsBc67Compression: true, // Should check BPTC extension, but for some reason NVIDIA is not exposing the extension.
157                  supportsEtc2Compression: true,
158                  supports3DTextureCompression: false,
159                  supportsBgraFormat: false,
160                  supportsR4G4Format: false,
161                  supportsR4G4B4A4Format: true,
162                  supportsScaledVertexFormats: true,
163                  supportsSnormBufferTextureFormat: false,
164                  supports5BitComponentFormat: true,
165                  supportsSparseBuffer: false,
166                  supportsBlendEquationAdvanced: HwCapabilities.SupportsBlendEquationAdvanced,
167                  supportsFragmentShaderInterlock: HwCapabilities.SupportsFragmentShaderInterlock,
168                  supportsFragmentShaderOrderingIntel: HwCapabilities.SupportsFragmentShaderOrdering,
169                  supportsGeometryShader: true,
170                  supportsGeometryShaderPassthrough: HwCapabilities.SupportsGeometryShaderPassthrough,
171                  supportsTransformFeedback: true,
172                  supportsImageLoadFormatted: HwCapabilities.SupportsImageLoadFormatted,
173                  supportsLayerVertexTessellation: HwCapabilities.SupportsShaderViewportLayerArray,
174                  supportsMismatchingViewFormat: HwCapabilities.SupportsMismatchingViewFormat,
175                  supportsCubemapView: true,
176                  supportsNonConstantTextureOffset: HwCapabilities.SupportsNonConstantTextureOffset,
177                  supportsQuads: HwCapabilities.SupportsQuads,
178                  supportsSeparateSampler: false,
179                  supportsShaderBallot: HwCapabilities.SupportsShaderBallot,
180                  supportsShaderBarrierDivergence: !(intelWindows || intelUnix),
181                  supportsShaderFloat64: true,
182                  supportsTextureGatherOffsets: true,
183                  supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod,
184                  supportsVertexStoreAndAtomics: true,
185                  supportsViewportIndexVertexTessellation: HwCapabilities.SupportsShaderViewportLayerArray,
186                  supportsViewportMask: HwCapabilities.SupportsViewportArray2,
187                  supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle,
188                  supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters,
189                  supportsDepthClipControl: true,
190                  uniformBufferSetIndex: 0,
191                  storageBufferSetIndex: 1,
192                  textureSetIndex: 2,
193                  imageSetIndex: 3,
194                  extraSetBaseIndex: 0,
195                  maximumExtraSets: 0,
196                  maximumUniformBuffersPerStage: 13, // TODO: Avoid hardcoding those limits here and get from driver?
197                  maximumStorageBuffersPerStage: 16,
198                  maximumTexturesPerStage: 32,
199                  maximumImagesPerStage: 8,
200                  maximumComputeSharedMemorySize: HwCapabilities.MaximumComputeSharedMemorySize,
201                  maximumSupportedAnisotropy: HwCapabilities.MaximumSupportedAnisotropy,
202                  shaderSubgroupSize: Constants.MaxSubgroupSize,
203                  storageBufferOffsetAlignment: HwCapabilities.StorageBufferOffsetAlignment,
204                  textureBufferOffsetAlignment: HwCapabilities.TextureBufferOffsetAlignment,
205                  gatherBiasPrecision: intelWindows || amdWindows ? 8 : 0, // Precision is 8 for these vendors on Vulkan.
206                  maximumGpuMemory: 0);
207          }
208  
209          public void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan<byte> data)
210          {
211              Buffer.SetData(buffer, offset, data);
212          }
213  
214          public void UpdateCounters()
215          {
216              _counters.Update();
217          }
218  
219          public void PreFrame()
220          {
221              _sync.Cleanup();
222              ResourcePool.Tick();
223          }
224  
225          public ICounterEvent ReportCounter(CounterType type, EventHandler<ulong> resultHandler, float divisor, bool hostReserved)
226          {
227              return _counters.QueueReport(type, resultHandler, divisor, _pipeline.DrawCount, hostReserved);
228          }
229  
230          public void Initialize(GraphicsDebugLevel glLogLevel)
231          {
232              Debugger.Initialize(glLogLevel);
233  
234              PrintGpuInformation();
235  
236              if (HwCapabilities.SupportsParallelShaderCompile)
237              {
238                  GL.Arb.MaxShaderCompilerThreads(Math.Min(Environment.ProcessorCount, 8));
239              }
240  
241              _counters.Initialize();
242  
243              // This is required to disable [0, 1] clamping for SNorm outputs on compatibility profiles.
244              // This call is expected to fail if we're running with a core profile,
245              // as this clamp target was deprecated, but that's fine as a core profile
246              // should already have the desired behaviour were outputs are not clamped.
247              GL.ClampColor(ClampColorTarget.ClampFragmentColor, ClampColorMode.False);
248          }
249  
250          private void PrintGpuInformation()
251          {
252              GpuVendor = GL.GetString(StringName.Vendor);
253              GpuRenderer = GL.GetString(StringName.Renderer);
254              GpuVersion = GL.GetString(StringName.Version);
255  
256              Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})");
257          }
258  
259          public void ResetCounter(CounterType type)
260          {
261              _counters.QueueReset(type);
262          }
263  
264          public void BackgroundContextAction(Action action, bool alwaysBackground = false)
265          {
266              // alwaysBackground is ignored, since we cannot switch from the current context.
267  
268              if (_window.BackgroundContext.HasContext())
269              {
270                  action(); // We have a context already - use that (assuming it is the main one).
271              }
272              else
273              {
274                  _window.BackgroundContext.Invoke(action);
275              }
276          }
277  
278          public void InitializeBackgroundContext(IOpenGLContext baseContext)
279          {
280              _window.InitializeBackgroundContext(baseContext);
281          }
282  
283          public void Dispose()
284          {
285              _textureCopy.Dispose();
286              _backgroundTextureCopy.Dispose();
287              TextureCopyMS.Dispose();
288              PersistentBuffers.Dispose();
289              ResourcePool.Dispose();
290              _pipeline.Dispose();
291              _window.Dispose();
292              _counters.Dispose();
293              _sync.Dispose();
294          }
295  
296          public IProgram LoadProgramBinary(byte[] programBinary, bool hasFragmentShader, ShaderInfo info)
297          {
298              return new Program(programBinary, hasFragmentShader, info.FragmentOutputMap);
299          }
300  
301          public void CreateSync(ulong id, bool strict)
302          {
303              _sync.Create(id);
304          }
305  
306          public void WaitSync(ulong id)
307          {
308              _sync.Wait(id);
309          }
310  
311          public ulong GetCurrentSync()
312          {
313              return _sync.GetCurrent();
314          }
315  
316          public void SetInterruptAction(Action<Action> interruptAction)
317          {
318              // Currently no need for an interrupt action.
319          }
320  
321          public void Screenshot()
322          {
323              _window.ScreenCaptureRequested = true;
324          }
325  
326          public void OnScreenCaptured(ScreenCaptureImageInfo bitmap)
327          {
328              ScreenCaptured?.Invoke(this, bitmap);
329          }
330  
331          public bool PrepareHostMapping(nint address, ulong size)
332          {
333              return false;
334          }
335      }
336  }