diff --git a/src/Ryujinx.Graphics.OpenGL/Framebuffer.cs b/src/Ryujinx.Graphics.OpenGL/Framebuffer.cs index 394b8bc76..aad8735bb 100644 --- a/src/Ryujinx.Graphics.OpenGL/Framebuffer.cs +++ b/src/Ryujinx.Graphics.OpenGL/Framebuffer.cs @@ -20,6 +20,27 @@ namespace Ryujinx.Graphics.OpenGL private int _colorsCount; private bool _dualSourceBlend; + public bool HasAttachments + { + get + { + if (_depthStencil != null) + { + return true; + } + + for (int index = 0; index < _colors.Length; index++) + { + if (_colors[index] != null) + { + return true; + } + } + + return false; + } + } + public Framebuffer() { Handle = GL.GenFramebuffer(); @@ -34,6 +55,12 @@ namespace Ryujinx.Graphics.OpenGL return Handle; } + public void SetDefaultSize(int width, int height) + { + GL.FramebufferParameter(FramebufferTarget.Framebuffer, FramebufferDefaultParameter.FramebufferDefaultWidth, Math.Max(1, width)); + GL.FramebufferParameter(FramebufferTarget.Framebuffer, FramebufferDefaultParameter.FramebufferDefaultHeight, Math.Max(1, height)); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AttachColor(int index, TextureView color) { @@ -105,13 +132,20 @@ namespace Ryujinx.Graphics.OpenGL _colorsCount = colorsCount; } - private static void SetDrawBuffersImpl(int colorsCount) + private void SetDrawBuffersImpl(int colorsCount) { DrawBuffersEnum[] drawBuffers = new DrawBuffersEnum[colorsCount]; for (int index = 0; index < colorsCount; index++) { - drawBuffers[index] = DrawBuffersEnum.ColorAttachment0 + index; + if (_colors[index] != null) + { + drawBuffers[index] = DrawBuffersEnum.ColorAttachment0 + index; + } + else + { + drawBuffers[index] = DrawBuffersEnum.None; + } } GL.DrawBuffers(colorsCount, drawBuffers); diff --git a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs index 6774f0fba..0de923275 100644 --- a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs +++ b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs @@ -1537,6 +1537,11 @@ namespace Ryujinx.Graphics.OpenGL { DrawCount++; + if (!_framebuffer.HasAttachments && _viewportArray.Length >= 4) + { + _framebuffer.SetDefaultSize((int)_viewportArray[2], (int)_viewportArray[3]); + } + _unit0Texture?.Bind(0); } diff --git a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs index a0d1b50df..e5800ed7d 100644 --- a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs +++ b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs @@ -32,6 +32,29 @@ namespace Ryujinx.Graphics.Vulkan public bool HasDepthStencil { get; private set; } public int ColorAttachmentsCount => AttachmentsCount - (HasDepthStencil ? 1 : 0); + public bool SetVirtualSize(uint width, uint height, uint layers) + { + if (AttachmentsCount != 0) + { + return false; + } + + width = Math.Max(1u, width); + height = Math.Max(1u, height); + layers = Math.Max(1u, layers); + + if (Width == width && Height == height && Layers == layers) + { + return false; + } + + Width = width; + Height = height; + Layers = layers; + + return true; + } + public FramebufferParams(Device device, TextureView view, uint width, uint height) { Format format = view.Info.Format; diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs index aececee61..fcb8826c2 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs @@ -843,6 +843,11 @@ namespace Ryujinx.Graphics.Vulkan public void SetImage(ShaderStage stage, int binding, ITexture image) { _descriptorSetUpdater.SetImage(Cbs, stage, binding, image); + + if (stage == ShaderStage.Fragment && image is TextureView view) + { + FramebufferParams?.SetVirtualSize((uint)view.Width, (uint)view.Height, (uint)view.Layers); + } } public void SetImage(int binding, Auto image) @@ -1607,6 +1612,24 @@ namespace Ryujinx.Graphics.Vulkan private bool RecreateGraphicsPipelineIfNeeded() { + if (FramebufferParams != null && + FramebufferParams.AttachmentsCount == 0 && + FramebufferParams.Width == 1 && + FramebufferParams.Height == 1 && + DynamicState.ViewportsCount != 0) + { + // An attachmentless fragment pass can reach its first draw before the storage + // image descriptor is rebound. At that point the null framebuffer still has + // its constructor fallback of 1x1, even though the guest viewport already + // describes the real render area. Seed the virtual framebuffer from that + // viewport so the first storage-image draw is not clipped to one pixel. + Silk.NET.Vulkan.Viewport viewport = DynamicState.Viewports[0]; + uint width = (uint)Math.Max(1f, Math.Abs(viewport.Width)); + uint height = (uint)Math.Max(1f, Math.Abs(viewport.Height)); + + FramebufferParams.SetVirtualSize(width, height, 1); + } + if (AutoFlush.ShouldFlushDraw(DrawCount)) { Gd.FlushAllCommands();