Properly support MSAA and SSAA as separate features(+GLES)

SSAA relies on MSAA being active to work. We only supports 4x SSAA while in fact you can enable SSAA at any MSAA level.
I even managed to run 64xMSAA + SSAA on my Quadro which made some pretty sleek looking games. They were very cinematic though.

With this, it properly fixes up SSAA and MSAA support in GLES as well. Before they were broken when stereo rendering was enabled.
Now in GLES they can properly support MSAA and also stereo rendering with MSAA enabled(with proper extensions).
This commit is contained in:
Ryan Houdek 2015-09-01 00:17:24 -05:00
parent 0f3263ac63
commit 7650117c26
13 changed files with 144 additions and 43 deletions

View File

@ -418,6 +418,16 @@ VideoConfigDiag::VideoConfigDiag(wxWindow* parent, const std::string &title, con
szr_enh->Add(CreateCheckBox(page_enh, _("Widescreen Hack"), wxGetTranslation(ws_hack_desc), vconfig.bWidescreenHack));
szr_enh->Add(CreateCheckBox(page_enh, _("Disable Fog"), wxGetTranslation(disable_fog_desc), vconfig.bDisableFog));
if (vconfig.backend_info.bSupportsSSAA)
{
ssaa_checkbox = CreateCheckBox(page_enh, _("SSAA"), wxGetTranslation(aa_desc), vconfig.bSSAA);
szr_enh->Add(ssaa_checkbox);
}
else
{
ssaa_checkbox = nullptr;
}
wxStaticBoxSizer* const group_enh = new wxStaticBoxSizer(wxVERTICAL, page_enh, _("Enhancements"));
group_enh->Add(szr_enh, 1, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 5);
szr_enh_main->Add(group_enh, 0, wxEXPAND | wxALL, 5);

View File

@ -193,6 +193,8 @@ protected:
// Anti-aliasing
choice_aamode->Enable(vconfig.backend_info.AAModes.size() > 1);
text_aamode->Enable(vconfig.backend_info.AAModes.size() > 1);
if (vconfig.backend_info.bSupportsSSAA && ssaa_checkbox)
ssaa_checkbox->Enable(vconfig.iMultisampleMode > 0);
// XFB
virtual_xfb->Enable(vconfig.bUseXFB);
@ -253,6 +255,7 @@ protected:
wxStaticText* text_aamode;
SettingChoice* choice_aamode;
wxCheckBox* ssaa_checkbox;
wxStaticText* label_display_resolution;

View File

@ -84,6 +84,7 @@ void InitBackendInfo()
g_Config.backend_info.bSupportsPostProcessing = false;
g_Config.backend_info.bSupportsPaletteConversion = true;
g_Config.backend_info.bSupportsClipControl = true;
g_Config.backend_info.bSupportsSSAA = false;
IDXGIFactory* factory;
IDXGIAdapter* ad;

View File

@ -107,6 +107,21 @@ FramebufferManager::FramebufferManager(int targetWidth, int targetHeight, int ms
{
m_textureType = GL_TEXTURE_2D_MULTISAMPLE_ARRAY;
if (g_ogl_config.bSupports3DTextureStorage)
{
glBindTexture(m_textureType, m_efbColor);
glTexStorage3DMultisample(m_textureType, m_msaaSamples, GL_RGBA8, m_targetWidth, m_targetHeight, m_EFBLayers, false);
glBindTexture(m_textureType, m_efbDepth);
glTexStorage3DMultisample(m_textureType, m_msaaSamples, GL_DEPTH_COMPONENT32F, m_targetWidth, m_targetHeight, m_EFBLayers, false);
glBindTexture(m_textureType, m_efbColorSwap);
glTexStorage3DMultisample(m_textureType, m_msaaSamples, GL_RGBA8, m_targetWidth, m_targetHeight, m_EFBLayers, false);
glBindTexture(m_textureType, 0);
}
else
{
glBindTexture(m_textureType, m_efbColor);
glTexImage3DMultisample(m_textureType, m_msaaSamples, GL_RGBA, m_targetWidth, m_targetHeight, m_EFBLayers, false);
@ -117,10 +132,25 @@ FramebufferManager::FramebufferManager(int targetWidth, int targetHeight, int ms
glTexImage3DMultisample(m_textureType, m_msaaSamples, GL_RGBA, m_targetWidth, m_targetHeight, m_EFBLayers, false);
glBindTexture(m_textureType, 0);
}
}
else
{
m_textureType = GL_TEXTURE_2D_MULTISAMPLE;
if (g_ogl_config.bSupports2DTextureStorage)
{
glBindTexture(m_textureType, m_efbColor);
glTexStorage2DMultisample(m_textureType, m_msaaSamples, GL_RGBA8, m_targetWidth, m_targetHeight, false);
glBindTexture(m_textureType, m_efbDepth);
glTexStorage2DMultisample(m_textureType, m_msaaSamples, GL_DEPTH_COMPONENT32F, m_targetWidth, m_targetHeight, false);
glBindTexture(m_textureType, m_efbColorSwap);
glTexStorage2DMultisample(m_textureType, m_msaaSamples, GL_RGBA8, m_targetWidth, m_targetHeight, false);
glBindTexture(m_textureType, 0);
}
else
{
glBindTexture(m_textureType, m_efbColor);
glTexImage2DMultisample(m_textureType, m_msaaSamples, GL_RGBA, m_targetWidth, m_targetHeight, false);
@ -131,6 +161,7 @@ FramebufferManager::FramebufferManager(int targetWidth, int targetHeight, int ms
glTexImage2DMultisample(m_textureType, m_msaaSamples, GL_RGBA, m_targetWidth, m_targetHeight, false);
glBindTexture(m_textureType, 0);
}
}
// Although we are able to access the multisampled texture directly, we don't do it everywhere.
// The old way is to "resolve" this multisampled texture by copying it into a non-sampled texture.
@ -213,7 +244,7 @@ FramebufferManager::FramebufferManager(int targetWidth, int targetHeight, int ms
" return texelFetch(samp9, pos, 0);\n"
"}\n";
}
else if (g_ogl_config.bSupportSampleShading)
else if (g_ActiveConfig.backend_info.bSupportsSSAA)
{
// msaa + sample shading available, so just fetch the sample
// This will lead to sample shading, but it's the only way to not loose

View File

@ -0,0 +1,11 @@
// Copyright 2013 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "VideoBackends/OGL/GLExtensions/gl_common.h"
typedef void (*PFNGLTEXSTORAGE2DMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations);
typedef void (*PFNGLTEXSTORAGE3DMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations);
extern PFNGLTEXSTORAGE2DMULTISAMPLEPROC glTexStorage2DMultisample;
extern PFNGLTEXSTORAGE3DMULTISAMPLEPROC glTexStorage3DMultisample;

View File

@ -718,6 +718,10 @@ PFNGLTEXIMAGE3DMULTISAMPLEPROC glTexImage3DMultisample;
PFNGLGETMULTISAMPLEFVPROC glGetMultisamplefv;
PFNGLSAMPLEMASKIPROC glSampleMaski;
// ARB_texture_storage_multisample
PFNGLTEXSTORAGE2DMULTISAMPLEPROC glTexStorage2DMultisample;
PFNGLTEXSTORAGE3DMULTISAMPLEPROC glTexStorage3DMultisample;
// ARB_ES2_compatibility
PFNGLCLEARDEPTHFPROC glClearDepthf;
PFNGLDEPTHRANGEFPROC glDepthRangef;
@ -1183,6 +1187,11 @@ const GLFunc gl_function_array[] =
GLFUNC_REQUIRES(glGetMultisamplefv, "GL_ARB_texture_multisample"),
GLFUNC_REQUIRES(glSampleMaski, "GL_ARB_texture_multisample"),
// ARB_texture_storage_multisample
GLFUNC_REQUIRES(glTexStorage2DMultisample, "GL_ARB_texture_storage_multisample"),
GLFUNC_REQUIRES(glTexStorage3DMultisample, "GL_ARB_texture_storage_multisample"),
GLFUNC_SUFFIX(glTexStorage3DMultisample, OES, "GL_OES_texture_storage_multisample_2d_array !VERSION_GLES_3_2"),
// ARB_ES2_compatibility
GLFUNC_REQUIRES(glClearDepthf, "GL_ARB_ES2_compatibility"),
GLFUNC_REQUIRES(glDepthRangef, "GL_ARB_ES2_compatibility"),
@ -1231,6 +1240,9 @@ const GLFunc gl_function_array[] =
// ARB_sample_shading
GLFUNC_SUFFIX(glMinSampleShading, ARB, "GL_ARB_sample_shading"),
// OES_sample_shading
GLFUNC_SUFFIX(glMinSampleShading, OES, "GL_OES_sample_shading !VERSION_GLES_3_2"),
// ARB_debug_output
GLFUNC_REQUIRES(glDebugMessageCallbackARB, "GL_ARB_debug_output"),
GLFUNC_REQUIRES(glDebugMessageControlARB, "GL_ARB_debug_output"),
@ -1298,6 +1310,8 @@ const GLFunc gl_function_array[] =
// EXT_texture_buffer
GLFUNC_SUFFIX(glTexBuffer, EXT, "GL_EXT_texture_buffer !GL_OES_texture_buffer !VERSION_GLES_3_2"),
// GLES 3.1
GLFUNC_REQUIRES(glTexStorage2DMultisample, "VERSION_GLES_3_1"),
// EXT_blend_func_extended
GLFUNC_SUFFIX(glBindFragDataLocationIndexed, EXT, "GL_EXT_blend_func_extended"),
@ -1317,6 +1331,7 @@ const GLFunc gl_function_array[] =
GLFUNC_REQUIRES(glPushDebugGroup, "VERSION_GLES_3_2"),
GLFUNC_REQUIRES(glCopyImageSubData, "VERSION_GLES_3_2"),
GLFUNC_REQUIRES(glTexBuffer, "VERSION_GLES_3_2"),
GLFUNC_REQUIRES(glTexStorage3DMultisample, "VERSION_GLES_3_2"),
// gl_1_1
// OpenGL 1.1 is at the end due to a bug in Android's EGL stack.
@ -1702,6 +1717,14 @@ namespace GLExtensions
m_extension_list[it] = true;
}
case 310:
{
std::string gles310exts[] = {
"GL_ARB_texture_storage_multisample",
"VERSION_GLES_3_1",
};
for (auto it : gles310exts)
m_extension_list[it] = true;
}
case 300:
{
std::string gles3exts[] = {

View File

@ -20,6 +20,7 @@
#include "VideoBackends/OGL/GLExtensions/ARB_sampler_objects.h"
#include "VideoBackends/OGL/GLExtensions/ARB_sync.h"
#include "VideoBackends/OGL/GLExtensions/ARB_texture_multisample.h"
#include "VideoBackends/OGL/GLExtensions/ARB_texture_storage_multisample.h"
#include "VideoBackends/OGL/GLExtensions/ARB_uniform_buffer_object.h"
#include "VideoBackends/OGL/GLExtensions/ARB_vertex_array_object.h"
#include "VideoBackends/OGL/GLExtensions/ARB_viewport_array.h"

View File

@ -567,6 +567,7 @@ void ProgramShaderCache::CreateHeader()
"%s\n"
"%s\n"
"%s\n"
"%s\n"
// Silly differences
"#define float2 vec2\n"
@ -588,7 +589,7 @@ void ProgramShaderCache::CreateHeader()
, !is_glsles && g_ActiveConfig.backend_info.bSupportsEarlyZ ? "#extension GL_ARB_shader_image_load_store : enable" : ""
, (g_ActiveConfig.backend_info.bSupportsBindingLayout && v < GLSLES_310) ? "#extension GL_ARB_shading_language_420pack : enable" : ""
, (g_ogl_config.bSupportsMSAA && v < GLSL_150) ? "#extension GL_ARB_texture_multisample : enable" : ""
, (g_ogl_config.bSupportSampleShading) ? "#extension GL_ARB_sample_shading : enable" : ""
, (v < GLSLES_300 && g_ActiveConfig.backend_info.bSupportsSSAA) ? "#extension GL_ARB_sample_shading : enable" : ""
, g_ActiveConfig.backend_info.bSupportsBindingLayout ? "#define SAMPLER_BINDING(x) layout(binding = x)" : "#define SAMPLER_BINDING(x)"
, g_ActiveConfig.backend_info.bSupportsBBox ? "#extension GL_ARB_shader_storage_buffer_object : enable" : ""
, !is_glsles && g_ActiveConfig.backend_info.bSupportsGSInstancing ? "#extension GL_ARB_gpu_shader5 : enable" : ""
@ -602,6 +603,7 @@ void ProgramShaderCache::CreateHeader()
, is_glsles ? "precision highp int;" : ""
, is_glsles ? "precision highp sampler2DArray;" : ""
, (is_glsles && g_ActiveConfig.backend_info.bSupportsPaletteConversion) ? "precision highp usamplerBuffer;" : ""
, v > GLSLES_300 ? "precision highp sampler2DMS;" : ""
);
}

View File

@ -73,10 +73,8 @@ enum MultisampleMode
MULTISAMPLE_2X,
MULTISAMPLE_4X,
MULTISAMPLE_8X,
MULTISAMPLE_SSAA_4X,
};
VideoConfig g_ogl_config;
// Declarations and definitions
@ -90,6 +88,7 @@ static RasterFont* s_pfont = nullptr;
// 1 for no MSAA. Use s_MSAASamples > 1 to check for MSAA.
static int s_MSAASamples = 1;
static int s_last_multisample_mode = 0;
static bool s_last_ssaa_mode = false;
static bool s_last_stereo_mode = false;
static bool s_last_xfb_mode = false;
@ -119,14 +118,12 @@ static int GetNumMSAASamples(int MSAAMode)
break;
case MULTISAMPLE_4X:
case MULTISAMPLE_SSAA_4X:
samples = 4;
break;
case MULTISAMPLE_8X:
samples = 8;
break;
default:
samples = 1;
}
@ -141,16 +138,12 @@ static int GetNumMSAASamples(int MSAAMode)
static void ApplySSAASettings()
{
// GLES3 doesn't support SSAA
if (GLInterface->GetMode() == GLInterfaceMode::MODE_OPENGL)
if (g_ActiveConfig.bSSAA)
{
if (g_ActiveConfig.iMultisampleMode == MULTISAMPLE_SSAA_4X)
{
if (g_ogl_config.bSupportSampleShading)
if (g_ActiveConfig.backend_info.bSupportsSSAA)
{
glEnable(GL_SAMPLE_SHADING_ARB);
GLfloat min_sample_shading_value = static_cast<GLfloat>(s_MSAASamples);
glMinSampleShading(min_sample_shading_value);
glMinSampleShading(1.0f);
}
else
{
@ -158,12 +151,11 @@ static void ApplySSAASettings()
OSD::AddMessage("SSAA Anti Aliasing isn't supported by your GPU.", 10000);
}
}
else if (g_ogl_config.bSupportSampleShading)
else if (g_ActiveConfig.backend_info.bSupportsSSAA)
{
glDisable(GL_SAMPLE_SHADING_ARB);
}
}
}
static void GLAPIENTRY ErrorCallback( GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const char* message, const void* userParam)
{
@ -491,11 +483,15 @@ Renderer::Renderer()
g_ogl_config.bSupportsGLBufferStorage = GLExtensions::Supports("GL_ARB_buffer_storage") ||
GLExtensions::Supports("GL_EXT_buffer_storage");
g_ogl_config.bSupportsMSAA = GLExtensions::Supports("GL_ARB_texture_multisample");
g_ogl_config.bSupportSampleShading = GLExtensions::Supports("GL_ARB_sample_shading");
g_ActiveConfig.backend_info.bSupportsSSAA = GLExtensions::Supports("GL_ARB_sample_shading") ||
GLExtensions::Supports("GL_OES_sample_shading");
g_ogl_config.bSupportOGL31 = GLExtensions::Version() >= 310;
g_ogl_config.bSupportViewportFloat = GLExtensions::Supports("GL_ARB_viewport_array");
g_ogl_config.bSupportsDebug = GLExtensions::Supports("GL_KHR_debug") ||
GLExtensions::Supports("GL_ARB_debug_output");
g_ogl_config.bSupports3DTextureStorage = GLExtensions::Supports("GL_ARB_texture_storage_multisample") ||
GLExtensions::Supports("GL_OES_texture_storage_multisample_2d_array");
g_ogl_config.bSupports2DTextureStorage = GLExtensions::Supports("GL_ARB_texture_storage_multisample");
if (GLInterface->GetMode() == GLInterfaceMode::MODE_OPENGLES3)
{
@ -518,6 +514,14 @@ Renderer::Renderer()
g_Config.backend_info.bSupportsEarlyZ = true;
g_Config.backend_info.bSupportsGeometryShaders = g_ogl_config.bSupportsAEP;
g_Config.backend_info.bSupportsGSInstancing = g_Config.backend_info.bSupportsGeometryShaders && g_ogl_config.SupportedESPointSize > 0;
g_ogl_config.bSupportsMSAA = true;
g_ogl_config.bSupports2DTextureStorage = true;
if (g_ActiveConfig.iStereoMode > 0 && g_ActiveConfig.iMultisampleMode > 1 && !g_ogl_config.bSupports3DTextureStorage)
{
// GLES 3.1 can't support stereo rendering and MSAA
OSD::AddMessage("MSAA Stereo rendering isn't supported by your GPU.", 10000);
g_ActiveConfig.iMultisampleMode = 1;
}
}
else
{
@ -528,10 +532,13 @@ Renderer::Renderer()
g_Config.backend_info.bSupportsGeometryShaders = true;
g_Config.backend_info.bSupportsGSInstancing = g_ogl_config.SupportedESPointSize > 0;
g_Config.backend_info.bSupportsPaletteConversion = true;
g_Config.backend_info.bSupportsSSAA = true;
g_ogl_config.bSupportsCopySubImage = true;
g_ogl_config.bSupportsGLBaseVertex = true;
g_ogl_config.bSupportSampleShading = true;
g_ogl_config.bSupportsDebug = true;
g_ogl_config.bSupportsMSAA = true;
g_ogl_config.bSupports2DTextureStorage = true;
g_ogl_config.bSupports3DTextureStorage = true;
}
}
else
@ -624,13 +631,14 @@ Renderer::Renderer()
g_ogl_config.bSupportsGLBufferStorage ? "" : "BufferStorage ",
g_ogl_config.bSupportsGLSync ? "" : "Sync ",
g_ogl_config.bSupportsMSAA ? "" : "MSAA ",
g_ogl_config.bSupportSampleShading ? "" : "SSAA ",
g_ActiveConfig.backend_info.bSupportsSSAA ? "" : "SSAA ",
g_ActiveConfig.backend_info.bSupportsGSInstancing ? "" : "GSInstancing ",
g_ActiveConfig.backend_info.bSupportsClipControl ? "" : "ClipControl ",
g_ogl_config.bSupportsCopySubImage ? "" : "CopyImageSubData "
);
s_last_multisample_mode = g_ActiveConfig.iMultisampleMode;
s_last_ssaa_mode = g_ActiveConfig.bSSAA;
s_MSAASamples = GetNumMSAASamples(s_last_multisample_mode);
ApplySSAASettings();
@ -1683,16 +1691,19 @@ void Renderer::SwapImpl(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, co
{
TargetSizeChanged = true;
}
if (TargetSizeChanged || xfbchanged || WindowResized || (s_last_multisample_mode != g_ActiveConfig.iMultisampleMode) || (s_last_stereo_mode != (g_ActiveConfig.iStereoMode > 0)))
if (TargetSizeChanged || xfbchanged || WindowResized || s_last_ssaa_mode != g_ActiveConfig.bSSAA ||
(s_last_multisample_mode != g_ActiveConfig.iMultisampleMode) || (s_last_stereo_mode != (g_ActiveConfig.iStereoMode > 0)))
{
s_last_xfb_mode = g_ActiveConfig.bUseRealXFB;
UpdateDrawRectangle(s_backbuffer_width, s_backbuffer_height);
if (TargetSizeChanged || s_last_multisample_mode != g_ActiveConfig.iMultisampleMode || s_last_stereo_mode != (g_ActiveConfig.iStereoMode > 0))
if (TargetSizeChanged || s_last_ssaa_mode != g_ActiveConfig.bSSAA ||
s_last_multisample_mode != g_ActiveConfig.iMultisampleMode || s_last_stereo_mode != (g_ActiveConfig.iStereoMode > 0))
{
s_last_stereo_mode = g_ActiveConfig.iStereoMode > 0;
s_last_multisample_mode = g_ActiveConfig.iMultisampleMode;
s_last_ssaa_mode = g_ActiveConfig.bSSAA;
s_MSAASamples = GetNumMSAASamples(s_last_multisample_mode);
ApplySSAASettings();

View File

@ -38,7 +38,6 @@ struct VideoConfig
bool bSupportsGLBaseVertex;
bool bSupportsGLBufferStorage;
bool bSupportsMSAA;
bool bSupportSampleShading;
GLSL_VERSION eSupportedGLSLVersion;
bool bSupportOGL31;
bool bSupportViewportFloat;
@ -47,6 +46,8 @@ struct VideoConfig
bool bSupportsCopySubImage;
u8 SupportedESPointSize;
ES_TEXBUF_TYPE SupportedESTextureBuffer;
bool bSupports2DTextureStorage;
bool bSupports3DTextureStorage;
const char* gl_vendor;
const char* gl_renderer;

View File

@ -117,11 +117,12 @@ static void InitBackendInfo()
g_Config.backend_info.bSupportsGeometryShaders = true;
g_Config.backend_info.bSupports3DVision = false;
g_Config.backend_info.bSupportsPostProcessing = true;
g_Config.backend_info.bSupportsSSAA = true;
g_Config.backend_info.Adapters.clear();
// aamodes
const char* caamodes[] = {_trans("None"), "2x MSAA", "4x MSAA", "8x MSAA", "4x SSAA"};
const char* caamodes[] = {_trans("None"), "2x MSAA", "4x MSAA", "8x MSAA"};
g_Config.backend_info.AAModes.assign(caamodes, caamodes + sizeof(caamodes)/sizeof(*caamodes));
// pp shaders

View File

@ -78,6 +78,7 @@ void VideoConfig::Load(const std::string& ini_file)
settings->Get("EnablePixelLighting", &bEnablePixelLighting, 0);
settings->Get("FastDepthCalc", &bFastDepthCalc, true);
settings->Get("MSAA", &iMultisampleMode, 0);
settings->Get("SSAA", &bSSAA, false);
settings->Get("EFBScale", &iEFBScale, (int)SCALE_1X); // native
settings->Get("DstAlphaPass", &bDstAlphaPass, false);
settings->Get("TexFmtOverlayEnable", &bTexFmtOverlayEnable, 0);
@ -161,6 +162,8 @@ void VideoConfig::GameIniLoad()
CHECK_SETTING("Video_Settings", "EnablePixelLighting", bEnablePixelLighting);
CHECK_SETTING("Video_Settings", "FastDepthCalc", bFastDepthCalc);
CHECK_SETTING("Video_Settings", "MSAA", iMultisampleMode);
CHECK_SETTING("Video_Settings", "SSAA", bSSAA);
int tmp = -9000;
CHECK_SETTING("Video_Settings", "EFBScale", tmp); // integral
if (tmp != -9000)
@ -274,6 +277,7 @@ void VideoConfig::Save(const std::string& ini_file)
settings->Set("FastDepthCalc", bFastDepthCalc);
settings->Set("ShowEFBCopyRegions", bShowEFBCopyRegions);
settings->Set("MSAA", iMultisampleMode);
settings->Set("SSAA", bSSAA);
settings->Set("EFBScale", iEFBScale);
settings->Set("TexFmtOverlayEnable", bTexFmtOverlayEnable);
settings->Set("TexFmtOverlayCenter", bTexFmtOverlayCenter);

View File

@ -75,6 +75,7 @@ struct VideoConfig final
// Enhancements
int iMultisampleMode;
bool bSSAA;
int iEFBScale;
bool bForceFiltering;
int iMaxAnisotropy;
@ -161,6 +162,7 @@ struct VideoConfig final
bool bSupportsPostProcessing;
bool bSupportsPaletteConversion;
bool bSupportsClipControl; // Needed by VertexShaderGen, so must stay in VideoCommon
bool bSupportsSSAA;
} backend_info;
// Utility