diff --git a/Ryujinx.Graphics.GAL/IRenderer.cs b/Ryujinx.Graphics.GAL/IRenderer.cs index 4f9e5bc9ac..4f3ef6d1ae 100644 --- a/Ryujinx.Graphics.GAL/IRenderer.cs +++ b/Ryujinx.Graphics.GAL/IRenderer.cs @@ -1,8 +1,9 @@ using Ryujinx.Graphics.Shader; +using System; namespace Ryujinx.Graphics.GAL { - public interface IRenderer + public interface IRenderer : IDisposable { IPipeline Pipeline { get; } diff --git a/Ryujinx.Graphics.Gpu/Engine/Compute.cs b/Ryujinx.Graphics.Gpu/Engine/Compute.cs index 50aac1d074..1f52467126 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Compute.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Compute.cs @@ -28,7 +28,7 @@ namespace Ryujinx.Graphics.Gpu.Engine // Note: A size of 0 is also invalid, the size must be at least 1. int sharedMemorySize = Math.Clamp(dispatchParams.SharedMemorySize & 0xffff, 1, _context.Capabilities.MaximumComputeSharedMemorySize); - ComputeShader cs = _shaderCache.GetComputeShader( + ComputeShader cs = ShaderCache.GetComputeShader( shaderGpuVa, sharedMemorySize, dispatchParams.UnpackBlockSizeX(), diff --git a/Ryujinx.Graphics.Gpu/Engine/Methods.cs b/Ryujinx.Graphics.Gpu/Engine/Methods.cs index 0a52bee9ac..12a9744cb2 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Methods.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Methods.cs @@ -18,11 +18,13 @@ namespace Ryujinx.Graphics.Gpu.Engine partial class Methods { private readonly GpuContext _context; - - private readonly ShaderCache _shaderCache; - private readonly ShaderProgramInfo[] _currentProgramInfo; + /// + /// In-memory shader cache. + /// + public ShaderCache ShaderCache { get; } + /// /// GPU buffer manager. /// @@ -44,7 +46,7 @@ namespace Ryujinx.Graphics.Gpu.Engine { _context = context; - _shaderCache = new ShaderCache(_context); + ShaderCache = new ShaderCache(_context); _currentProgramInfo = new ShaderProgramInfo[Constants.TotalShaderStages]; @@ -757,13 +759,13 @@ namespace Ryujinx.Graphics.Gpu.Engine addressesArray[index] = baseAddress + shader.Offset; } - GraphicsShader gs = _shaderCache.GetGraphicsShader(state, addresses); + GraphicsShader gs = ShaderCache.GetGraphicsShader(state, addresses); - _vsUsesInstanceId = gs.Shader[0].Program.Info.UsesInstanceId; + _vsUsesInstanceId = gs.Shaders[0].Program.Info.UsesInstanceId; for (int stage = 0; stage < Constants.TotalShaderStages; stage++) { - ShaderProgramInfo info = gs.Shader[stage].Program?.Info; + ShaderProgramInfo info = gs.Shaders[stage].Program?.Info; _currentProgramInfo[stage] = info; diff --git a/Ryujinx.Graphics.Gpu/GpuContext.cs b/Ryujinx.Graphics.Gpu/GpuContext.cs index d68dd2c0a3..034cc065b6 100644 --- a/Ryujinx.Graphics.Gpu/GpuContext.cs +++ b/Ryujinx.Graphics.Gpu/GpuContext.cs @@ -8,7 +8,7 @@ namespace Ryujinx.Graphics.Gpu /// /// GPU emulation context. /// - public class GpuContext + public sealed class GpuContext : IDisposable { /// /// Host renderer. @@ -104,5 +104,18 @@ namespace Ryujinx.Graphics.Gpu { PhysicalMemory = new PhysicalMemory(cpuMemory); } + + /// + /// Disposes all GPU resources currently cached. + /// It's an error to push any GPU commands after disposal. + /// Additionally, the GPU commands FIFO must be empty for disposal, + /// and processing of all commands must have finished. + /// + public void Dispose() + { + Methods.ShaderCache.Dispose(); + Methods.BufferManager.Dispose(); + Methods.TextureManager.Dispose(); + } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs index 9b5c19f3aa..076718e524 100644 --- a/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -13,7 +13,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// Represents a cached GPU texture. /// - class Texture : IRange + class Texture : IRange, IDisposable { private GpuContext _context; @@ -1011,5 +1011,13 @@ namespace Ryujinx.Graphics.Gpu.Image _arrayViewTexture?.Dispose(); _arrayViewTexture = null; } + + /// + /// Performs texture disposal, deleting the texture. + /// + public void Dispose() + { + DisposeTextures(); + } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs index ecc5dc5115..e0a8908aa0 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs @@ -11,7 +11,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// Texture manager. /// - class TextureManager + class TextureManager : IDisposable { private const int OverlapsBufferInitialCapacity = 10; private const int OverlapsBufferMaxCapacity = 10000; @@ -761,5 +761,17 @@ namespace Ryujinx.Graphics.Gpu.Image { _textures.Remove(texture); } + + /// + /// Disposes all textures in the cache. + /// It's an error to use the texture manager after disposal. + /// + public void Dispose() + { + foreach (Texture texture in _textures) + { + texture.Dispose(); + } + } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs index 1d27bc7e3c..44542349d9 100644 --- a/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs +++ b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs @@ -683,5 +683,17 @@ namespace Ryujinx.Graphics.Gpu.Memory buffer.SynchronizeMemory(address, size); } } + + /// + /// Disposes all buffers in the cache. + /// It's an error to use the buffer manager after disposal. + /// + public void Dispose() + { + foreach (Buffer buffer in _buffers) + { + buffer.Dispose(); + } + } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Memory/RangeList.cs b/Ryujinx.Graphics.Gpu/Memory/RangeList.cs index 1d185e2165..75be1cf2c5 100644 --- a/Ryujinx.Graphics.Gpu/Memory/RangeList.cs +++ b/Ryujinx.Graphics.Gpu/Memory/RangeList.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; namespace Ryujinx.Graphics.Gpu.Memory @@ -7,11 +8,11 @@ namespace Ryujinx.Graphics.Gpu.Memory /// List of GPU resources with data on guest memory. /// /// Type of the GPU resource - class RangeList where T : IRange + class RangeList : IEnumerable where T : IRange { private const int ArrayGrowthSize = 32; - private List _items; + private readonly List _items; /// /// Creates a new GPU resources list. @@ -320,5 +321,15 @@ namespace Ryujinx.Graphics.Gpu.Memory return ~left; } + + public IEnumerator GetEnumerator() + { + return _items.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _items.GetEnumerator(); + } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/NvGpuFifo.cs b/Ryujinx.Graphics.Gpu/NvGpuFifo.cs index 6f51527fdd..9e15453340 100644 --- a/Ryujinx.Graphics.Gpu/NvGpuFifo.cs +++ b/Ryujinx.Graphics.Gpu/NvGpuFifo.cs @@ -21,6 +21,9 @@ namespace Ryujinx.Graphics.Gpu /// private struct CachedMacro { + /// + /// Word offset of the code on the code memory. + /// public int Position { get; } private bool _executionPending; diff --git a/Ryujinx.Graphics.Gpu/Shader/CachedShader.cs b/Ryujinx.Graphics.Gpu/Shader/CachedShader.cs index 362d149a64..418a4f4027 100644 --- a/Ryujinx.Graphics.Gpu/Shader/CachedShader.cs +++ b/Ryujinx.Graphics.Gpu/Shader/CachedShader.cs @@ -16,7 +16,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// /// Host shader object. /// - public IShader Shader { get; set; } + public IShader HostShader { get; set; } /// /// Maxwell binary shader code. diff --git a/Ryujinx.Graphics.Gpu/Shader/GraphicsShader.cs b/Ryujinx.Graphics.Gpu/Shader/GraphicsShader.cs index 14c8e5c21a..a149323649 100644 --- a/Ryujinx.Graphics.Gpu/Shader/GraphicsShader.cs +++ b/Ryujinx.Graphics.Gpu/Shader/GraphicsShader.cs @@ -15,14 +15,14 @@ namespace Ryujinx.Graphics.Gpu.Shader /// /// Compiled shader for each shader stage. /// - public CachedShader[] Shader { get; } + public CachedShader[] Shaders { get; } /// /// Creates a new instance of cached graphics shader. /// public GraphicsShader() { - Shader = new CachedShader[5]; + Shaders = new CachedShader[5]; } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs index b299da1a7d..ad2a603700 100644 --- a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs +++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs @@ -14,7 +14,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// /// Memory cache of shader code. /// - class ShaderCache + class ShaderCache : IDisposable { private const int MaxProgramSize = 0x100000; @@ -117,25 +117,25 @@ namespace Ryujinx.Graphics.Gpu.Shader if (addresses.VertexA != 0) { - gpShaders.Shader[0] = TranslateGraphicsShader(state, ShaderStage.Vertex, addresses.Vertex, addresses.VertexA); + gpShaders.Shaders[0] = TranslateGraphicsShader(state, ShaderStage.Vertex, addresses.Vertex, addresses.VertexA); } else { - gpShaders.Shader[0] = TranslateGraphicsShader(state, ShaderStage.Vertex, addresses.Vertex); + gpShaders.Shaders[0] = TranslateGraphicsShader(state, ShaderStage.Vertex, addresses.Vertex); } - gpShaders.Shader[1] = TranslateGraphicsShader(state, ShaderStage.TessellationControl, addresses.TessControl); - gpShaders.Shader[2] = TranslateGraphicsShader(state, ShaderStage.TessellationEvaluation, addresses.TessEvaluation); - gpShaders.Shader[3] = TranslateGraphicsShader(state, ShaderStage.Geometry, addresses.Geometry); - gpShaders.Shader[4] = TranslateGraphicsShader(state, ShaderStage.Fragment, addresses.Fragment); + gpShaders.Shaders[1] = TranslateGraphicsShader(state, ShaderStage.TessellationControl, addresses.TessControl); + gpShaders.Shaders[2] = TranslateGraphicsShader(state, ShaderStage.TessellationEvaluation, addresses.TessEvaluation); + gpShaders.Shaders[3] = TranslateGraphicsShader(state, ShaderStage.Geometry, addresses.Geometry); + gpShaders.Shaders[4] = TranslateGraphicsShader(state, ShaderStage.Fragment, addresses.Fragment); BackpropQualifiers(gpShaders); List hostShaders = new List(); - for (int stage = 0; stage < gpShaders.Shader.Length; stage++) + for (int stage = 0; stage < gpShaders.Shaders.Length; stage++) { - ShaderProgram program = gpShaders.Shader[stage].Program; + ShaderProgram program = gpShaders.Shaders[stage].Program; if (program == null) { @@ -144,7 +144,7 @@ namespace Ryujinx.Graphics.Gpu.Shader IShader hostShader = _context.Renderer.CompileShader(program); - gpShaders.Shader[stage].Shader = hostShader; + gpShaders.Shaders[stage].HostShader = hostShader; hostShaders.Add(hostShader); } @@ -182,9 +182,9 @@ namespace Ryujinx.Graphics.Gpu.Shader /// True if the code is different, false otherwise private bool IsShaderDifferent(GraphicsShader gpShaders, ShaderAddresses addresses) { - for (int stage = 0; stage < gpShaders.Shader.Length; stage++) + for (int stage = 0; stage < gpShaders.Shaders.Length; stage++) { - CachedShader shader = gpShaders.Shader[stage]; + CachedShader shader = gpShaders.Shaders[stage]; if (shader.Code == null) { @@ -370,13 +370,13 @@ namespace Ryujinx.Graphics.Gpu.Shader /// Graphics shader cached code private void BackpropQualifiers(GraphicsShader program) { - ShaderProgram fragmentShader = program.Shader[4].Program; + ShaderProgram fragmentShader = program.Shaders[4].Program; bool isFirst = true; for (int stage = 3; stage >= 0; stage--) { - if (program.Shader[stage].Program == null) + if (program.Shaders[stage].Program == null) { continue; } @@ -389,11 +389,11 @@ namespace Ryujinx.Graphics.Gpu.Shader if (isFirst && iq != string.Empty) { - program.Shader[stage].Program.Replace($"{DefineNames.OutQualifierPrefixName}{attr}", iq); + program.Shaders[stage].Program.Replace($"{DefineNames.OutQualifierPrefixName}{attr}", iq); } else { - program.Shader[stage].Program.Replace($"{DefineNames.OutQualifierPrefixName}{attr} ", string.Empty); + program.Shaders[stage].Program.Replace($"{DefineNames.OutQualifierPrefixName}{attr} ", string.Empty); } } @@ -497,5 +497,34 @@ namespace Ryujinx.Graphics.Gpu.Shader return 0; } + + /// + /// Disposes the shader cache, deleting all the cached shaders. + /// It's an error to use the shader cache after disposal. + /// + public void Dispose() + { + foreach (List list in _cpPrograms.Values) + { + foreach (ComputeShader shader in list) + { + shader.HostProgram.Dispose(); + shader.Shader.HostShader.Dispose(); + } + } + + foreach (List list in _gpPrograms.Values) + { + foreach (GraphicsShader shader in list) + { + shader.HostProgram.Dispose(); + + foreach (CachedShader cachedShader in shader.Shaders) + { + cachedShader.HostShader?.Dispose(); + } + } + } + } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.OpenGL/Pipeline.cs b/Ryujinx.Graphics.OpenGL/Pipeline.cs index 7ca19c9d57..0ef21d7db7 100644 --- a/Ryujinx.Graphics.OpenGL/Pipeline.cs +++ b/Ryujinx.Graphics.OpenGL/Pipeline.cs @@ -6,7 +6,7 @@ using System; namespace Ryujinx.Graphics.OpenGL { - class Pipeline : IPipeline + class Pipeline : IPipeline, IDisposable { private Program _program; @@ -863,5 +863,11 @@ namespace Ryujinx.Graphics.OpenGL (_componentMasks[index] & 8u) != 0); } } + + public void Dispose() + { + _framebuffer?.Dispose(); + _vertexArray?.Dispose(); + } } } diff --git a/Ryujinx.Graphics.OpenGL/Program.cs b/Ryujinx.Graphics.OpenGL/Program.cs index 48ec69d13a..a8ee7ae895 100644 --- a/Ryujinx.Graphics.OpenGL/Program.cs +++ b/Ryujinx.Graphics.OpenGL/Program.cs @@ -66,6 +66,13 @@ namespace Ryujinx.Graphics.OpenGL GL.LinkProgram(Handle); + for (int index = 0; index < shaders.Length; index++) + { + int shaderHandle = ((Shader)shaders[index]).Handle; + + GL.DetachShader(Handle, shaderHandle); + } + CheckProgramLink(); Bind(); diff --git a/Ryujinx.Graphics.OpenGL/Renderer.cs b/Ryujinx.Graphics.OpenGL/Renderer.cs index 7cb69a78c3..e6021f51f7 100644 --- a/Ryujinx.Graphics.OpenGL/Renderer.cs +++ b/Ryujinx.Graphics.OpenGL/Renderer.cs @@ -4,9 +4,11 @@ using Ryujinx.Graphics.Shader; namespace Ryujinx.Graphics.OpenGL { - public class Renderer : IRenderer + public sealed class Renderer : IRenderer { - public IPipeline Pipeline { get; } + private Pipeline _pipeline; + + public IPipeline Pipeline => _pipeline; private readonly Counters _counters; @@ -18,7 +20,7 @@ namespace Ryujinx.Graphics.OpenGL public Renderer() { - Pipeline = new Pipeline(); + _pipeline = new Pipeline(); _counters = new Counters(); @@ -81,5 +83,12 @@ namespace Ryujinx.Graphics.OpenGL { _counters.ResetCounter(type); } + + public void Dispose() + { + TextureCopy.Dispose(); + _pipeline.Dispose(); + _window.Dispose(); + } } } diff --git a/Ryujinx.Graphics.OpenGL/TextureCopy.cs b/Ryujinx.Graphics.OpenGL/TextureCopy.cs index 75ef07c158..244ace8a2e 100644 --- a/Ryujinx.Graphics.OpenGL/TextureCopy.cs +++ b/Ryujinx.Graphics.OpenGL/TextureCopy.cs @@ -1,9 +1,10 @@ using Ryujinx.Graphics.GAL; using OpenTK.Graphics.OpenGL; +using System; namespace Ryujinx.Graphics.OpenGL { - class TextureCopy + class TextureCopy : IDisposable { private int _srcFramebuffer; private int _dstFramebuffer; @@ -53,11 +54,6 @@ namespace Ryujinx.Graphics.OpenGL GL.Enable(EnableCap.FramebufferSrgb); } - private static void Detach(FramebufferTarget target, Format format) - { - Attach(target, format, 0); - } - private static void Attach(FramebufferTarget target, Format format, int handle) { if (format == Format.D24UnormS8Uint || format == Format.D32FloatS8Uint) @@ -124,5 +120,22 @@ namespace Ryujinx.Graphics.OpenGL return _dstFramebuffer; } + + public void Dispose() + { + if (_srcFramebuffer != 0) + { + GL.DeleteFramebuffer(_srcFramebuffer); + + _srcFramebuffer = 0; + } + + if (_dstFramebuffer != 0) + { + GL.DeleteFramebuffer(_dstFramebuffer); + + _dstFramebuffer = 0; + } + } } } diff --git a/Ryujinx.Graphics.OpenGL/VertexArray.cs b/Ryujinx.Graphics.OpenGL/VertexArray.cs index 26e6336478..721a90f0c7 100644 --- a/Ryujinx.Graphics.OpenGL/VertexArray.cs +++ b/Ryujinx.Graphics.OpenGL/VertexArray.cs @@ -6,7 +6,7 @@ namespace Ryujinx.Graphics.OpenGL { class VertexArray : IDisposable { - public int Handle { get; } + public int Handle { get; private set; } private bool _needsAttribsUpdate; @@ -128,7 +128,12 @@ namespace Ryujinx.Graphics.OpenGL public void Dispose() { - GL.DeleteVertexArray(Handle); + if (Handle != 0) + { + GL.DeleteVertexArray(Handle); + + Handle = 0; + } } } } diff --git a/Ryujinx.Graphics.OpenGL/Window.cs b/Ryujinx.Graphics.OpenGL/Window.cs index cf520ed441..26fc6a64b8 100644 --- a/Ryujinx.Graphics.OpenGL/Window.cs +++ b/Ryujinx.Graphics.OpenGL/Window.cs @@ -4,7 +4,7 @@ using System; namespace Ryujinx.Graphics.OpenGL { - class Window : IWindow + class Window : IWindow, IDisposable { private const int NativeWidth = 1280; private const int NativeHeight = 720; @@ -118,5 +118,15 @@ namespace Ryujinx.Graphics.OpenGL return handle; } + + public void Dispose() + { + if (_copyFramebufferHandle != 0) + { + GL.DeleteFramebuffer(_copyFramebufferHandle); + + _copyFramebufferHandle = 0; + } + } } } diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs index 9df1d7571a..90723c4caf 100644 --- a/Ryujinx.HLE/Switch.cs +++ b/Ryujinx.HLE/Switch.cs @@ -134,6 +134,11 @@ namespace Ryujinx.HLE Memory.Dispose(); } + public void DisposeGpu() + { + Gpu.Dispose(); + } + public void Dispose() { Dispose(true); diff --git a/Ryujinx/Ui/GLScreen.cs b/Ryujinx/Ui/GLScreen.cs index 1cce6a1f76..f1249d642c 100644 --- a/Ryujinx/Ui/GLScreen.cs +++ b/Ryujinx/Ui/GLScreen.cs @@ -106,6 +106,9 @@ namespace Ryujinx.Ui ticks = Math.Min(ticks - ticksPerFrame, ticksPerFrame); } } + + _device.DisposeGpu(); + _renderer.Dispose(); } public void MainLoop()