mirror of
https://git.ryujinx.app/projects/Kenji-NX.git
synced 2026-09-29 20:57:53 +02:00
Some fragment passes used by FFT do not have any color or depth/stencil attachments. Instead, the fragment shader writes the results directly to storage images (on OpenGL: through imageStore). ### Vulkan When SetImage is called and FramebufferParams has no attachments, its virtual size must be updated using the width, height, and layer count of the fragment storage image. Otherwise, the attachmentless framebuffer may retain an incorrect 1x1 extent, causing backgrounds, logos, UI elements, and other rendered content to be missing. If the first draw occurs before the storage-image descriptor is rebound, SetImage cannot yet provide the correct storage-image dimensions. Therefore, when RecreateGraphicsPipelineIfNeeded finds that FramebufferParams has no attachments and still uses the default 1x1 extent, it initializes the framebuffer dimensions from the active viewport. Otherwise, the first Vulkan draw may be restricted to a 1x1 area and render incorrectly. The storage-image extent is therefore used as the authoritative size for FramebufferParams, while the active viewport is used as a fallback when the storage-image extent is not yet available before the first draw. ### OpenGL A storage image is not a framebuffer attachment, so OpenGL cannot derive the framebuffer width and height from it. When the framebuffer has no attachments but a viewport with valid dimensions has already been defined, Pipeline.PreDraw must set the default framebuffer width and height from the active viewport. Without non-zero default width and height values, the attachmentless framebuffer remains incomplete. Drawing with that framebuffer results in InvalidFramebufferOperation, so rasterization and fragment shader execution do not occur even when the storage images are bound correctly.
270 lines
7.5 KiB
C#
270 lines
7.5 KiB
C#
using OpenTK.Graphics.OpenGL;
|
|
using Ryujinx.Graphics.GAL;
|
|
using Ryujinx.Graphics.OpenGL.Image;
|
|
using System;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace Ryujinx.Graphics.OpenGL
|
|
{
|
|
class Framebuffer : IDisposable
|
|
{
|
|
public int Handle { get; private set; }
|
|
private int _clearFbHandle;
|
|
private bool _clearFbInitialized;
|
|
|
|
private FramebufferAttachment _lastDsAttachment;
|
|
|
|
private readonly TextureView[] _colors;
|
|
private TextureView _depthStencil;
|
|
|
|
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();
|
|
_clearFbHandle = GL.GenFramebuffer();
|
|
|
|
_colors = new TextureView[8];
|
|
}
|
|
|
|
public int Bind()
|
|
{
|
|
GL.BindFramebuffer(FramebufferTarget.Framebuffer, Handle);
|
|
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)
|
|
{
|
|
if (_colors[index] == color)
|
|
{
|
|
return;
|
|
}
|
|
|
|
FramebufferAttachment attachment = FramebufferAttachment.ColorAttachment0 + index;
|
|
|
|
GL.FramebufferTexture(FramebufferTarget.Framebuffer, attachment, color?.Handle ?? 0, 0);
|
|
|
|
_colors[index] = color;
|
|
}
|
|
|
|
public void AttachDepthStencil(TextureView depthStencil)
|
|
{
|
|
// Detach the last depth/stencil buffer if there is any.
|
|
if (_lastDsAttachment != 0)
|
|
{
|
|
GL.FramebufferTexture(FramebufferTarget.Framebuffer, _lastDsAttachment, 0, 0);
|
|
}
|
|
|
|
if (depthStencil != null)
|
|
{
|
|
FramebufferAttachment attachment = GetAttachment(depthStencil.Format);
|
|
|
|
GL.FramebufferTexture(
|
|
FramebufferTarget.Framebuffer,
|
|
attachment,
|
|
depthStencil.Handle,
|
|
0);
|
|
|
|
_lastDsAttachment = attachment;
|
|
}
|
|
else
|
|
{
|
|
_lastDsAttachment = 0;
|
|
}
|
|
|
|
_depthStencil = depthStencil;
|
|
}
|
|
|
|
public void SetDualSourceBlend(bool enable)
|
|
{
|
|
bool oldEnable = _dualSourceBlend;
|
|
|
|
_dualSourceBlend = enable;
|
|
|
|
// When dual source blend is used,
|
|
// we can only have one draw buffer.
|
|
if (enable)
|
|
{
|
|
GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
|
|
}
|
|
else if (oldEnable)
|
|
{
|
|
SetDrawBuffersImpl(_colorsCount);
|
|
}
|
|
}
|
|
|
|
public void SetDrawBuffers(int colorsCount)
|
|
{
|
|
if (_colorsCount != colorsCount && !_dualSourceBlend)
|
|
{
|
|
SetDrawBuffersImpl(colorsCount);
|
|
}
|
|
|
|
_colorsCount = colorsCount;
|
|
}
|
|
|
|
private void SetDrawBuffersImpl(int colorsCount)
|
|
{
|
|
DrawBuffersEnum[] drawBuffers = new DrawBuffersEnum[colorsCount];
|
|
|
|
for (int index = 0; index < colorsCount; index++)
|
|
{
|
|
if (_colors[index] != null)
|
|
{
|
|
drawBuffers[index] = DrawBuffersEnum.ColorAttachment0 + index;
|
|
}
|
|
else
|
|
{
|
|
drawBuffers[index] = DrawBuffersEnum.None;
|
|
}
|
|
}
|
|
|
|
GL.DrawBuffers(colorsCount, drawBuffers);
|
|
}
|
|
|
|
private static FramebufferAttachment GetAttachment(Format format)
|
|
{
|
|
if (FormatTable.IsPackedDepthStencil(format))
|
|
{
|
|
return FramebufferAttachment.DepthStencilAttachment;
|
|
}
|
|
else if (FormatTable.IsDepthOnly(format))
|
|
{
|
|
return FramebufferAttachment.DepthAttachment;
|
|
}
|
|
else
|
|
{
|
|
return FramebufferAttachment.StencilAttachment;
|
|
}
|
|
}
|
|
|
|
public int GetColorLayerCount(int index)
|
|
{
|
|
return _colors[index]?.Info.GetDepthOrLayers() ?? 0;
|
|
}
|
|
|
|
public int GetDepthStencilLayerCount()
|
|
{
|
|
return _depthStencil?.Info.GetDepthOrLayers() ?? 0;
|
|
}
|
|
|
|
public void AttachColorLayerForClear(int index, int layer)
|
|
{
|
|
TextureView color = _colors[index];
|
|
|
|
if (!IsLayered(color))
|
|
{
|
|
return;
|
|
}
|
|
|
|
BindClearFb();
|
|
GL.FramebufferTextureLayer(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0 + index, color.Handle, 0, layer);
|
|
}
|
|
|
|
public void DetachColorLayerForClear(int index)
|
|
{
|
|
TextureView color = _colors[index];
|
|
|
|
if (!IsLayered(color))
|
|
{
|
|
return;
|
|
}
|
|
|
|
GL.FramebufferTexture(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0 + index, 0, 0);
|
|
Bind();
|
|
}
|
|
|
|
public void AttachDepthStencilLayerForClear(int layer)
|
|
{
|
|
TextureView depthStencil = _depthStencil;
|
|
|
|
if (!IsLayered(depthStencil))
|
|
{
|
|
return;
|
|
}
|
|
|
|
BindClearFb();
|
|
GL.FramebufferTextureLayer(FramebufferTarget.Framebuffer, GetAttachment(depthStencil.Format), depthStencil.Handle, 0, layer);
|
|
}
|
|
|
|
public void DetachDepthStencilLayerForClear()
|
|
{
|
|
TextureView depthStencil = _depthStencil;
|
|
|
|
if (!IsLayered(depthStencil))
|
|
{
|
|
return;
|
|
}
|
|
|
|
GL.FramebufferTexture(FramebufferTarget.Framebuffer, GetAttachment(depthStencil.Format), 0, 0);
|
|
Bind();
|
|
}
|
|
|
|
private void BindClearFb()
|
|
{
|
|
GL.BindFramebuffer(FramebufferTarget.Framebuffer, _clearFbHandle);
|
|
|
|
if (!_clearFbInitialized)
|
|
{
|
|
SetDrawBuffersImpl(Constants.MaxRenderTargets);
|
|
_clearFbInitialized = true;
|
|
}
|
|
}
|
|
|
|
private static bool IsLayered(TextureView view)
|
|
{
|
|
return view != null &&
|
|
view.Target != Target.Texture1D &&
|
|
view.Target != Target.Texture2D &&
|
|
view.Target != Target.Texture2DMultisample &&
|
|
view.Target != Target.TextureBuffer;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (Handle != 0)
|
|
{
|
|
GL.DeleteFramebuffer(Handle);
|
|
|
|
Handle = 0;
|
|
}
|
|
|
|
if (_clearFbHandle != 0)
|
|
{
|
|
GL.DeleteFramebuffer(_clearFbHandle);
|
|
|
|
_clearFbHandle = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|