diff --git a/Data/Sys/GameSettings/GLME01.ini b/Data/Sys/GameSettings/GLME01.ini index 5920388eddfa..ccabdf2a1031 100644 --- a/Data/Sys/GameSettings/GLME01.ini +++ b/Data/Sys/GameSettings/GLME01.ini @@ -32,3 +32,5 @@ $99 of some treasures 040AE530 3F000063 $End Boss Has No HP +[Video_Stereoscopy] +StereoMonoEFBDepth = True diff --git a/Data/Sys/Shaders/stereoscopic.glsl b/Data/Sys/Shaders/stereoscopic.glsl deleted file mode 100644 index 0c49b55f3282..000000000000 --- a/Data/Sys/Shaders/stereoscopic.glsl +++ /dev/null @@ -1,24 +0,0 @@ -// Omega's 3D Stereoscopic filtering -// TODO: Need depth info! - -void main() -{ - // Source Color - float4 c0 = Sample(); - const int sep = 5; - float red = c0.r; - float green = c0.g; - float blue = c0.b; - - // Left Eye (Red) - float4 c1 = SampleOffset(int2(sep, 0)); - red = max(c0.r, c1.r); - - // Right Eye (Cyan) - float4 c2 = SampleOffset(int2(-sep, 0)); - float cyan = (c2.g + c2.b) / 2.0; - green = max(c0.g, cyan); - blue = max(c0.b, cyan); - - SetOutput(float4(red, green, blue, c0.a)); -} diff --git a/Data/Sys/Shaders/stereoscopic2.glsl b/Data/Sys/Shaders/stereoscopic2.glsl deleted file mode 100644 index 1784a988bf95..000000000000 --- a/Data/Sys/Shaders/stereoscopic2.glsl +++ /dev/null @@ -1,24 +0,0 @@ -// Omega's 3D Stereoscopic filtering (Amber/Blue) -// TODO: Need depth info! - -void main() -{ - // Source Color - float4 c0 = Sample(); - float sep = 5.0; - float red = c0.r; - float green = c0.g; - float blue = c0.b; - - // Left Eye (Amber) - float4 c2 = SampleLocation(GetCoordinates() + float2(sep,0.0)*GetInvResolution()).rgba; - float amber = (c2.r + c2.g) / 2.0; - red = max(c0.r, amber); - green = max(c0.g, amber); - - // Right Eye (Blue) - float4 c1 = SampleLocation(GetCoordinates() + float2(-sep,0.0)*GetInvResolution()).rgba; - blue = max(c0.b, c1.b); - - SetOutput(float4(red, green, blue, c0.a)); -} diff --git a/Source/Core/Common/MathUtil.cpp b/Source/Core/Common/MathUtil.cpp index e409e3fe24ba..05822dac77d0 100644 --- a/Source/Core/Common/MathUtil.cpp +++ b/Source/Core/Common/MathUtil.cpp @@ -351,6 +351,13 @@ void Matrix44::Translate(Matrix44 &mtx, const float vec[3]) mtx.data[11] = vec[2]; } +void Matrix44::Shear(Matrix44 &mtx, const float a, const float b) +{ + LoadIdentity(mtx); + mtx.data[2] = a; + mtx.data[6] = b; +} + void Matrix44::Multiply(const Matrix44 &a, const Matrix44 &b, Matrix44 &result) { MatrixMul(4, a.data, b.data, result.data); diff --git a/Source/Core/Common/MathUtil.h b/Source/Core/Common/MathUtil.h index 5af6ce40f961..13ef25e64762 100644 --- a/Source/Core/Common/MathUtil.h +++ b/Source/Core/Common/MathUtil.h @@ -232,6 +232,7 @@ class Matrix44 static void Set(Matrix44 &mtx, const float mtxArray[16]); static void Translate(Matrix44 &mtx, const float vec[3]); + static void Shear(Matrix44 &mtx, const float a, const float b = 0); static void Multiply(const Matrix44 &a, const Matrix44 &b, Matrix44 &result); diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index 2c74b423b401..c1f0cf4288d7 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -81,6 +81,11 @@ static const struct { "FreelookZoomOut", 83 /* 'S' */, 4 /* wxMOD_SHIFT */ }, { "FreelookReset", 82 /* 'R' */, 4 /* wxMOD_SHIFT */ }, + { "IncreaseSeparation", 0, 0 /* wxMOD_NONE */ }, + { "DecreaseSeparation", 0, 0 /* wxMOD_NONE */ }, + { "IncreaseConvergence", 0, 0 /* wxMOD_NONE */ }, + { "DecreaseConvergence", 0, 0 /* wxMOD_NONE */ }, + { "LoadStateSlot1", 340 /* WXK_F1 */, 0 /* wxMOD_NONE */ }, { "LoadStateSlot2", 341 /* WXK_F2 */, 0 /* wxMOD_NONE */ }, { "LoadStateSlot3", 342 /* WXK_F3 */, 0 /* wxMOD_NONE */ }, diff --git a/Source/Core/Core/CoreParameter.h b/Source/Core/Core/CoreParameter.h index 28686a55bcfd..94087f77e7e6 100644 --- a/Source/Core/Core/CoreParameter.h +++ b/Source/Core/Core/CoreParameter.h @@ -54,6 +54,11 @@ enum Hotkey HK_FREELOOK_ZOOM_OUT, HK_FREELOOK_RESET, + HK_INCREASE_SEPARATION, + HK_DECREASE_SEPARATION, + HK_INCREASE_CONVERGENCE, + HK_DECREASE_CONVERGENCE, + HK_LOAD_STATE_SLOT_1, HK_LOAD_STATE_SLOT_2, HK_LOAD_STATE_SLOT_3, diff --git a/Source/Core/DolphinWX/Frame.cpp b/Source/Core/DolphinWX/Frame.cpp index 3784ae4bc9f3..d4b7ba65f74f 100644 --- a/Source/Core/DolphinWX/Frame.cpp +++ b/Source/Core/DolphinWX/Frame.cpp @@ -1123,6 +1123,26 @@ void CFrame::OnKeyDown(wxKeyEvent& event) { State::Load(g_saveSlot); } + else if (IsHotkey(event, HK_INCREASE_SEPARATION)) + { + if (++g_Config.iStereoSeparation > 100) + g_Config.iStereoSeparation = 100; + } + else if (IsHotkey(event, HK_DECREASE_SEPARATION)) + { + if (--g_Config.iStereoSeparation < 0) + g_Config.iStereoSeparation = 0; + } + else if (IsHotkey(event, HK_INCREASE_CONVERGENCE)) + { + if (++g_Config.iStereoConvergence > 500) + g_Config.iStereoConvergence = 500; + } + else if (IsHotkey(event, HK_DECREASE_CONVERGENCE)) + { + if (--g_Config.iStereoConvergence < 0) + g_Config.iStereoConvergence = 0; + } else { diff --git a/Source/Core/DolphinWX/HotkeyDlg.cpp b/Source/Core/DolphinWX/HotkeyDlg.cpp index 2be49b904df7..91fc6b90a2bf 100644 --- a/Source/Core/DolphinWX/HotkeyDlg.cpp +++ b/Source/Core/DolphinWX/HotkeyDlg.cpp @@ -238,6 +238,11 @@ void HotkeyConfigDialog::CreateHotkeyGUIControls() _("Freelook Zoom Out"), _("Freelook Reset"), + _("Increase Stereocopy Separation"), + _("Decrease Stereocopy Separation"), + _("Increase Stereocopy Convergence"), + _("Decrease Stereocopy Convergence"), + _("Load State Slot 1"), _("Load State Slot 2"), _("Load State Slot 3"), diff --git a/Source/Core/DolphinWX/VideoConfigDiag.cpp b/Source/Core/DolphinWX/VideoConfigDiag.cpp index 454fbb5cd5ca..ab01c4339a35 100644 --- a/Source/Core/DolphinWX/VideoConfigDiag.cpp +++ b/Source/Core/DolphinWX/VideoConfigDiag.cpp @@ -149,6 +149,10 @@ static wxString crop_desc = wxTRANSLATE("Crop the picture from 4:3 to 5:4 or fro static wxString ppshader_desc = wxTRANSLATE("Apply a post-processing effect after finishing a frame.\n\nIf unsure, select (off)."); static wxString cache_efb_copies_desc = wxTRANSLATE("Slightly speeds up EFB to RAM copies by sacrificing emulation accuracy.\nSometimes also increases visual quality.\nIf you're experiencing any issues, try raising texture cache accuracy or disable this option.\n\nIf unsure, leave this unchecked."); static wxString shader_errors_desc = wxTRANSLATE("Usually if shader compilation fails, an error message is displayed.\nHowever, one may skip the popups to allow interruption free gameplay by checking this option.\n\nIf unsure, leave this unchecked."); +static wxString stereo_3d_desc = wxTRANSLATE("Select the stereoscopic 3D mode, stereoscopy allows you to get a better feeling of depth if you have the necessary hardware.\nSide-by-Side and Top-and-Bottom are used by most 3D TVs.\nAnaglyph is used for Red-Cyan colored glasses.\nHeavily decreases emulation speed and sometimes causes issues.\n\nIf unsure, select Off."); +static wxString stereo_separation_desc = wxTRANSLATE("Control the separation distance, this is the distance between the virtual cameras.\nA higher value creates a stronger feeling of depth while a lower value is more comfortable."); +static wxString stereo_convergence_desc = wxTRANSLATE("Control the convergence distance, this controls the apparant distance of virtual objects.\nA higher value creates stronger out-of-screen effects while a lower value is more comfortable."); +static wxString stereo_swap_desc = wxTRANSLATE("Swap the left and right eye, mostly useful if you want to view side-by-side cross-eyed.\n\nIf unsure, leave this unchecked."); #if !defined(__APPLE__) @@ -396,7 +400,7 @@ VideoConfigDiag::VideoConfigDiag(wxWindow* parent, const std::string &title, con if (vconfig.backend_info.PPShaders.size()) { wxFlexGridSizer* const szr_pp = new wxFlexGridSizer(3, 5, 5); - wxChoice *const choice_ppshader = new wxChoice(page_enh, -1); + choice_ppshader = new wxChoice(page_enh, -1); RegisterControl(choice_ppshader, wxGetTranslation(ppshader_desc)); choice_ppshader->AppendString(_("(off)")); @@ -425,6 +429,11 @@ VideoConfigDiag::VideoConfigDiag(wxWindow* parent, const std::string &title, con szr_pp->Add(button_config_pp); szr_enh->Add(szr_pp); } + else + { + choice_ppshader = nullptr; + button_config_pp = nullptr; + } // Scaled copy, PL, Bilinear filter szr_enh->Add(CreateCheckBox(page_enh, _("Scaled EFB Copy"), wxGetTranslation(scaled_efb_copy_desc), vconfig.bCopyEFBScaled)); @@ -438,6 +447,36 @@ VideoConfigDiag::VideoConfigDiag(wxWindow* parent, const std::string &title, con group_enh->Add(szr_enh, 1, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 5); szr_enh_main->Add(group_enh, 0, wxEXPAND | wxALL, 5); + // - stereoscopy + + if (vconfig.backend_info.bSupportsStereoscopy && vconfig.iStereoMode > 0) + { + wxFlexGridSizer* const szr_stereo = new wxFlexGridSizer(2, 5, 5); + + const wxString stereo_choices[] = { "Off", "Side-by-Side", "Top-and-Bottom", "Anaglyph" }; + szr_stereo->Add(new wxStaticText(page_enh, -1, _("Stereoscopic 3D Mode:")), 1, wxALIGN_CENTER_VERTICAL, 0); + szr_stereo->Add(CreateChoice(page_enh, vconfig.iStereoMode, wxGetTranslation(stereo_3d_desc), 4, stereo_choices)); + + wxSlider* const sep_slider = new wxSlider(page_enh, wxID_ANY, vconfig.iStereoSeparation, 0, 100, wxDefaultPosition, wxDefaultSize); + sep_slider->Bind(wxEVT_SLIDER, &VideoConfigDiag::Event_StereoSep, this); + RegisterControl(sep_slider, wxGetTranslation(stereo_separation_desc)); + + szr_stereo->Add(new wxStaticText(page_enh, wxID_ANY, _("Separation:")), 1, wxALIGN_CENTER_VERTICAL, 0); + szr_stereo->Add(sep_slider, 0, wxEXPAND | wxRIGHT); + + wxSlider* const conv_slider = new wxSlider(page_enh, wxID_ANY, vconfig.iStereoConvergence, 0, 500, wxDefaultPosition, wxDefaultSize); + conv_slider->Bind(wxEVT_SLIDER, &VideoConfigDiag::Event_StereoFoc, this); + RegisterControl(conv_slider, wxGetTranslation(stereo_convergence_desc)); + + szr_stereo->Add(new wxStaticText(page_enh, wxID_ANY, _("Convergence:")), 1, wxALIGN_CENTER_VERTICAL, 0); + szr_stereo->Add(conv_slider, 0, wxEXPAND | wxRIGHT); + + szr_stereo->Add(CreateCheckBox(page_enh, _("Swap Eyes"), wxGetTranslation(stereo_swap_desc), vconfig.bStereoSwapEyes)); + + wxStaticBoxSizer* const group_stereo = new wxStaticBoxSizer(wxVERTICAL, page_enh, _("Stereoscopy")); + group_stereo->Add(szr_stereo, 1, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 5); + szr_enh_main->Add(group_stereo, 0, wxEXPAND | wxALL, 5); + } szr_enh_main->AddStretchSpacer(); CreateDescriptionArea(page_enh, szr_enh_main); diff --git a/Source/Core/DolphinWX/VideoConfigDiag.h b/Source/Core/DolphinWX/VideoConfigDiag.h index bd242d3a99b2..10dc808bb9a1 100644 --- a/Source/Core/DolphinWX/VideoConfigDiag.h +++ b/Source/Core/DolphinWX/VideoConfigDiag.h @@ -165,6 +165,20 @@ class VideoConfigDiag : public wxDialog ev.Skip(); } + void Event_StereoSep(wxCommandEvent &ev) + { + vconfig.iStereoSeparation = ev.GetInt(); + + ev.Skip(); + } + + void Event_StereoFoc(wxCommandEvent &ev) + { + vconfig.iStereoConvergence = ev.GetInt(); + + ev.Skip(); + } + void Event_ClickClose(wxCommandEvent&); void Event_Close(wxCloseEvent&); @@ -184,6 +198,12 @@ class VideoConfigDiag : public wxDialog virtual_xfb->Enable(vconfig.bUseXFB); real_xfb->Enable(vconfig.bUseXFB); + // PP Shaders + if (choice_ppshader) + choice_ppshader->Enable(vconfig.iStereoMode != STEREO_ANAGLYPH); + if (button_config_pp) + button_config_pp->Enable(vconfig.iStereoMode != STEREO_ANAGLYPH); + // Things which shouldn't be changed during emulation if (Core::IsRunning()) { @@ -248,6 +268,8 @@ class VideoConfigDiag : public wxDialog wxCheckBox* progressive_scan_checkbox; + wxChoice* choice_ppshader; + std::map ctrl_descs; // maps setting controls to their descriptions std::map desc_texts; // maps dialog tabs (which are the parents of the setting controls) to their description text objects diff --git a/Source/Core/VideoBackends/D3D/LineGeometryShader.cpp b/Source/Core/VideoBackends/D3D/LineGeometryShader.cpp index fabe6ed91aa8..9cd3ba6b5a39 100644 --- a/Source/Core/VideoBackends/D3D/LineGeometryShader.cpp +++ b/Source/Core/VideoBackends/D3D/LineGeometryShader.cpp @@ -172,7 +172,7 @@ bool LineGeometryShader::SetShader(u32 components, float lineWidth, static char buffer[16384]; ShaderCode code; code.SetBuffer(buffer); - GenerateVSOutputStructForGS(code, API_D3D); + GenerateVSOutputStruct(code, API_D3D); code.Write("\n%s", LINE_GS_COMMON); std::stringstream numTexCoordsStream; diff --git a/Source/Core/VideoBackends/D3D/PointGeometryShader.cpp b/Source/Core/VideoBackends/D3D/PointGeometryShader.cpp index d15fe3eb0e8e..732ef9574991 100644 --- a/Source/Core/VideoBackends/D3D/PointGeometryShader.cpp +++ b/Source/Core/VideoBackends/D3D/PointGeometryShader.cpp @@ -166,7 +166,7 @@ bool PointGeometryShader::SetShader(u32 components, float pointSize, static char buffer[16384]; ShaderCode code; code.SetBuffer(buffer); - GenerateVSOutputStructForGS(code, API_D3D); + GenerateVSOutputStruct(code, API_D3D); code.Write("\n%s", POINT_GS_COMMON); std::stringstream numTexCoordsStream; diff --git a/Source/Core/VideoBackends/D3D/TextureCache.h b/Source/Core/VideoBackends/D3D/TextureCache.h index 6045d3f7dbd8..79cd8145a8fe 100644 --- a/Source/Core/VideoBackends/D3D/TextureCache.h +++ b/Source/Core/VideoBackends/D3D/TextureCache.h @@ -43,6 +43,9 @@ class TextureCache : public ::TextureCache TCacheEntryBase* CreateRenderTargetTexture(unsigned int scaled_tex_w, unsigned int scaled_tex_h) override; u64 EncodeToRamFromTexture(u32 address, void* source_texture, u32 SourceW, u32 SourceH, bool bFromZBuffer, bool bIsIntensityFmt, u32 copyfmt, int bScaleByHalf, const EFBRectangle& source) {return 0;}; + + void CompileShaders() override { } + void DeleteShaders() override { } }; } diff --git a/Source/Core/VideoBackends/D3D/main.cpp b/Source/Core/VideoBackends/D3D/main.cpp index 296936d8a72a..ed5868c91b72 100644 --- a/Source/Core/VideoBackends/D3D/main.cpp +++ b/Source/Core/VideoBackends/D3D/main.cpp @@ -78,6 +78,7 @@ void InitBackendInfo() g_Config.backend_info.bSupportsPrimitiveRestart = true; g_Config.backend_info.bSupportsOversizedViewports = false; g_Config.backend_info.bSupportsBBox = false; // TODO: not implemented + g_Config.backend_info.bSupportsStereoscopy = false; // TODO: not implemented IDXGIFactory* factory; IDXGIAdapter* ad; diff --git a/Source/Core/VideoBackends/OGL/FramebufferManager.cpp b/Source/Core/VideoBackends/OGL/FramebufferManager.cpp index a8441952e98a..f24172e7e00f 100644 --- a/Source/Core/VideoBackends/OGL/FramebufferManager.cpp +++ b/Source/Core/VideoBackends/OGL/FramebufferManager.cpp @@ -21,7 +21,6 @@ int FramebufferManager::m_targetHeight; int FramebufferManager::m_msaaSamples; GLenum FramebufferManager::m_textureType; - GLuint FramebufferManager::m_efbFramebuffer; GLuint FramebufferManager::m_xfbFramebuffer; GLuint FramebufferManager::m_efbColor; @@ -72,42 +71,45 @@ FramebufferManager::FramebufferManager(int targetWidth, int targetHeight, int ms m_efbDepth = glObj[1]; m_efbColorSwap = glObj[2]; + m_EFBLayers = (g_ActiveConfig.iStereoMode > 0) ? 2 : 1; + // OpenGL MSAA textures are a different kind of texture type and must be allocated // with a different function, so we create them separately. if (m_msaaSamples <= 1) { - m_textureType = GL_TEXTURE_2D; + m_textureType = GL_TEXTURE_2D_ARRAY; glBindTexture(m_textureType, m_efbColor); glTexParameteri(m_textureType, GL_TEXTURE_MAX_LEVEL, 0); glTexParameteri(m_textureType, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(m_textureType, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexImage2D(m_textureType, 0, GL_RGBA, m_targetWidth, m_targetHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + glTexImage3D(m_textureType, 0, GL_RGBA, m_targetWidth, m_targetHeight, m_EFBLayers, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); glBindTexture(m_textureType, m_efbDepth); glTexParameteri(m_textureType, GL_TEXTURE_MAX_LEVEL, 0); glTexParameteri(m_textureType, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(m_textureType, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexImage2D(m_textureType, 0, GL_DEPTH_COMPONENT24, m_targetWidth, m_targetHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr); + glTexImage3D(m_textureType, 0, GL_DEPTH_COMPONENT24, m_targetWidth, m_targetHeight, m_EFBLayers, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr); glBindTexture(m_textureType, m_efbColorSwap); glTexParameteri(m_textureType, GL_TEXTURE_MAX_LEVEL, 0); glTexParameteri(m_textureType, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(m_textureType, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexImage2D(m_textureType, 0, GL_RGBA, m_targetWidth, m_targetHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + glTexImage3D(m_textureType, 0, GL_RGBA, m_targetWidth, m_targetHeight, m_EFBLayers, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); } else { - m_textureType = GL_TEXTURE_2D_MULTISAMPLE; + m_textureType = GL_TEXTURE_2D_MULTISAMPLE_ARRAY; + GLenum resolvedType = GL_TEXTURE_2D_ARRAY; glBindTexture(m_textureType, m_efbColor); - glTexImage2DMultisample(m_textureType, m_msaaSamples, GL_RGBA, m_targetWidth, m_targetHeight, false); + glTexImage3DMultisample(m_textureType, m_msaaSamples, GL_RGBA, m_targetWidth, m_targetHeight, m_EFBLayers, false); glBindTexture(m_textureType, m_efbDepth); - glTexImage2DMultisample(m_textureType, m_msaaSamples, GL_DEPTH_COMPONENT24, m_targetWidth, m_targetHeight, false); + glTexImage3DMultisample(m_textureType, m_msaaSamples, GL_DEPTH_COMPONENT24, m_targetWidth, m_targetHeight, m_EFBLayers, false); glBindTexture(m_textureType, m_efbColorSwap); - glTexImage2DMultisample(m_textureType, m_msaaSamples, GL_RGBA, m_targetWidth, m_targetHeight, false); + glTexImage3DMultisample(m_textureType, m_msaaSamples, GL_RGBA, m_targetWidth, m_targetHeight, m_EFBLayers, false); glBindTexture(m_textureType, 0); // Although we are able to access the multisampled texture directly, we don't do it everywhere. @@ -118,23 +120,23 @@ FramebufferManager::FramebufferManager(int targetWidth, int targetHeight, int ms m_resolvedColorTexture = glObj[0]; m_resolvedDepthTexture = glObj[1]; - glBindTexture(GL_TEXTURE_2D, m_resolvedColorTexture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_targetWidth, m_targetHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + glBindTexture(resolvedType, m_resolvedColorTexture); + glTexParameteri(resolvedType, GL_TEXTURE_MAX_LEVEL, 0); + glTexParameteri(resolvedType, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(resolvedType, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage3D(resolvedType, 0, GL_RGBA, m_targetWidth, m_targetHeight, m_EFBLayers, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); - glBindTexture(GL_TEXTURE_2D, m_resolvedDepthTexture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, m_targetWidth, m_targetHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr); + glBindTexture(resolvedType, m_resolvedDepthTexture); + glTexParameteri(resolvedType, GL_TEXTURE_MAX_LEVEL, 0); + glTexParameteri(resolvedType, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(resolvedType, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage3D(resolvedType, 0, GL_DEPTH_COMPONENT24, m_targetWidth, m_targetHeight, m_EFBLayers, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr); // Bind resolved textures to resolved framebuffer. glGenFramebuffers(1, &m_resolvedFramebuffer); glBindFramebuffer(GL_FRAMEBUFFER, m_resolvedFramebuffer); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_resolvedColorTexture, 0); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_resolvedDepthTexture, 0); + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_resolvedColorTexture, 0); + glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, m_resolvedDepthTexture, 0); } // Create XFB framebuffer; targets will be created elsewhere. @@ -143,8 +145,8 @@ FramebufferManager::FramebufferManager(int targetWidth, int targetHeight, int ms // Bind target textures to EFB framebuffer. glGenFramebuffers(1, &m_efbFramebuffer); glBindFramebuffer(GL_FRAMEBUFFER, m_efbFramebuffer); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_textureType, m_efbColor, 0); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, m_textureType, m_efbDepth, 0); + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_efbColor, 0); + glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, m_efbDepth, 0); // EFB framebuffer is currently bound, make sure to clear its alpha value to 1.f glViewport(0, 0, m_targetWidth, m_targetHeight); @@ -168,9 +170,9 @@ FramebufferManager::FramebufferManager(int targetWidth, int targetHeight, int ms { // non-msaa, so just fetch the pixel sampler = - "SAMPLER_BINDING(9) uniform sampler2D samp9;\n" + "SAMPLER_BINDING(9) uniform sampler2DArray samp9;\n" "vec4 sampleEFB(ivec2 pos) {\n" - " return texelFetch(samp9, pos, 0);\n" + " return texelFetch(samp9, ivec3(pos, 0), 0);\n" "}\n"; } else if (g_ogl_config.bSupportSampleShading) @@ -179,9 +181,9 @@ FramebufferManager::FramebufferManager(int targetWidth, int targetHeight, int ms // This will lead to sample shading, but it's the only way to not loose // the values of each sample. sampler = - "SAMPLER_BINDING(9) uniform sampler2DMS samp9;\n" + "SAMPLER_BINDING(9) uniform sampler2DMSArray samp9;\n" "vec4 sampleEFB(ivec2 pos) {\n" - " return texelFetch(samp9, pos, gl_SampleID);\n" + " return texelFetch(samp9, ivec3(pos, 0), gl_SampleID);\n" "}\n"; } else @@ -190,11 +192,11 @@ FramebufferManager::FramebufferManager(int targetWidth, int targetHeight, int ms std::stringstream samples; samples << m_msaaSamples; sampler = - "SAMPLER_BINDING(9) uniform sampler2DMS samp9;\n" + "SAMPLER_BINDING(9) uniform sampler2DMSArray samp9;\n" "vec4 sampleEFB(ivec2 pos) {\n" " vec4 color = vec4(0.0, 0.0, 0.0, 0.0);\n" " for(int i=0; i<" + samples.str() + "; i++)\n" - " color += texelFetch(samp9, pos, i);\n" + " color += texelFetch(samp9, ivec3(pos, 0), i);\n" " return color / " + samples.str() + ";\n" "}\n"; } @@ -365,7 +367,7 @@ void FramebufferManager::ReinterpretPixelData(unsigned int convtype) src_texture = m_efbColor; m_efbColor = m_efbColorSwap; m_efbColorSwap = src_texture; - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_textureType, m_efbColor, 0); + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_efbColor, 0); glViewport(0,0, m_targetWidth, m_targetHeight); glActiveTexture(GL_TEXTURE0 + 9); @@ -397,7 +399,7 @@ void XFBSource::CopyEFB(float Gamma) glBindFramebuffer(GL_DRAW_FRAMEBUFFER, FramebufferManager::GetXFBFramebuffer()); // Bind texture. - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); + glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0); glBlitFramebuffer( 0, 0, texWidth, texHeight, @@ -419,11 +421,11 @@ XFBSourceBase* FramebufferManager::CreateXFBSource(unsigned int target_width, un glGenTextures(1, &texture); glActiveTexture(GL_TEXTURE0 + 9); - glBindTexture(GL_TEXTURE_2D, texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, target_width, target_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + glBindTexture(GL_TEXTURE_2D_ARRAY, texture); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 0); + glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, target_width, target_height, m_EFBLayers, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); - return new XFBSource(texture); + return new XFBSource(texture, m_EFBLayers); } void FramebufferManager::GetTargetSize(unsigned int *width, unsigned int *height, const EFBRectangle& sourceRc) diff --git a/Source/Core/VideoBackends/OGL/FramebufferManager.h b/Source/Core/VideoBackends/OGL/FramebufferManager.h index 1d0670408ec5..ed129c5a6e9f 100644 --- a/Source/Core/VideoBackends/OGL/FramebufferManager.h +++ b/Source/Core/VideoBackends/OGL/FramebufferManager.h @@ -47,13 +47,14 @@ namespace OGL struct XFBSource : public XFBSourceBase { - XFBSource(GLuint tex) : texture(tex) {} + XFBSource(GLuint tex, int layers) : texture(tex), m_layers(layers) {} ~XFBSource(); void CopyEFB(float Gamma) override; void DecodeToTexture(u32 xfbAddr, u32 fbWidth, u32 fbHeight) override; const GLuint texture; + const int m_layers; }; class FramebufferManager : public FramebufferManagerBase @@ -100,7 +101,6 @@ class FramebufferManager : public FramebufferManagerBase static int m_msaaSamples; static GLenum m_textureType; - static GLuint m_efbFramebuffer; static GLuint m_xfbFramebuffer; static GLuint m_efbColor; diff --git a/Source/Core/VideoBackends/OGL/PostProcessing.cpp b/Source/Core/VideoBackends/OGL/PostProcessing.cpp index 434a1d94a36b..80bdf8eef477 100644 --- a/Source/Core/VideoBackends/OGL/PostProcessing.cpp +++ b/Source/Core/VideoBackends/OGL/PostProcessing.cpp @@ -37,6 +37,8 @@ static char s_vertex_shader[] = "}\n"; OpenGLPostProcessing::OpenGLPostProcessing() + : m_initialized(false) + , m_anaglyph(false) { CreateHeader(); @@ -46,8 +48,6 @@ OpenGLPostProcessing::OpenGLPostProcessing() glGenBuffers(1, &m_attribute_vbo); glGenVertexArrays(1, &m_attribute_vao); } - - m_initialized = false; } OpenGLPostProcessing::~OpenGLPostProcessing() @@ -62,7 +62,7 @@ OpenGLPostProcessing::~OpenGLPostProcessing() } void OpenGLPostProcessing::BlitFromTexture(TargetRectangle src, TargetRectangle dst, - int src_texture, int src_width, int src_height) + int src_texture, int src_width, int src_height, int layer) { ApplyShader(); @@ -79,6 +79,7 @@ void OpenGLPostProcessing::BlitFromTexture(TargetRectangle src, TargetRectangle glUniform4f(m_uniform_src_rect, src.left / (float) src_width, src.bottom / (float) src_height, src.GetWidth() / (float) src_width, src.GetHeight() / (float) src_height); glUniform1ui(m_uniform_time, (GLuint)m_timer.GetTimeElapsed()); + glUniform1i(m_uniform_layer, layer); if (m_config.IsDirty()) { @@ -151,25 +152,29 @@ void OpenGLPostProcessing::BlitFromTexture(TargetRectangle src, TargetRectangle } glActiveTexture(GL_TEXTURE0+9); - glBindTexture(GL_TEXTURE_2D, src_texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glBindTexture(GL_TEXTURE_2D_ARRAY, src_texture); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } void OpenGLPostProcessing::ApplyShader() { // shader didn't changed - if (m_initialized && m_config.GetShader() == g_ActiveConfig.sPostProcessingShader) + if (m_initialized && m_config.GetShader() == g_ActiveConfig.sPostProcessingShader && + ((g_ActiveConfig.iStereoMode == STEREO_ANAGLYPH) == m_anaglyph)) return; m_shader.Destroy(); m_uniform_bindings.clear(); - // load shader from disk - std::string default_shader = "void main() { SetOutput(Sample()); }"; + // load shader code std::string code = ""; - if (g_ActiveConfig.sPostProcessingShader != "") + std::string default_shader = "void main() { SetOutput(Sample()); }\n"; + + if (g_ActiveConfig.iStereoMode == STEREO_ANAGLYPH) + code = "void main() { SetOutput(float4(pow(0.7 * SampleLayer(0).g + 0.3 * SampleLayer(0).b, 1.5), SampleLayer(1).gba)); }\n"; + else if (g_ActiveConfig.sPostProcessingShader != "") code = m_config.LoadShader(); if (code == "") @@ -195,6 +200,7 @@ void OpenGLPostProcessing::ApplyShader() m_uniform_resolution = glGetUniformLocation(m_shader.glprogid, "resolution"); m_uniform_time = glGetUniformLocation(m_shader.glprogid, "time"); m_uniform_src_rect = glGetUniformLocation(m_shader.glprogid, "src_rect"); + m_uniform_layer = glGetUniformLocation(m_shader.glprogid, "layer"); if (m_attribute_workaround) { @@ -218,6 +224,7 @@ void OpenGLPostProcessing::ApplyShader() std::string glsl_name = "option_" + it.first; m_uniform_bindings[it.first] = glGetUniformLocation(m_shader.glprogid, glsl_name.c_str()); } + m_anaglyph = g_ActiveConfig.iStereoMode == STEREO_ANAGLYPH; m_initialized = true; } @@ -228,7 +235,7 @@ void OpenGLPostProcessing::CreateHeader() // Shouldn't be accessed directly by the PP shader // Texture sampler "SAMPLER_BINDING(8) uniform sampler2D samp8;\n" - "SAMPLER_BINDING(9) uniform sampler2D samp9;\n" + "SAMPLER_BINDING(9) uniform sampler2DArray samp9;\n" // Output variable "out float4 ocol0;\n" @@ -238,19 +245,26 @@ void OpenGLPostProcessing::CreateHeader() "uniform float4 resolution;\n" // Time "uniform uint time;\n" + // Layer + "uniform int layer;\n" // Interfacing functions "float4 Sample()\n" "{\n" - "\treturn texture(samp9, uv0);\n" + "\treturn texture(samp9, float3(uv0, layer));\n" "}\n" "float4 SampleLocation(float2 location)\n" "{\n" - "\treturn texture(samp9, location);\n" + "\treturn texture(samp9, float3(location, layer));\n" + "}\n" + + "float4 SampleLayer(int layer)\n" + "{\n" + "\treturn texture(samp9, float3(uv0, layer));\n" "}\n" - "#define SampleOffset(offset) textureOffset(samp9, uv0, offset)\n" + "#define SampleOffset(offset) textureOffset(samp9, float3(uv0, layer), offset)\n" "float4 SampleFontLocation(float2 location)\n" "{\n" diff --git a/Source/Core/VideoBackends/OGL/PostProcessing.h b/Source/Core/VideoBackends/OGL/PostProcessing.h index 9f4f6ba5bc40..e2be0bdc7de8 100644 --- a/Source/Core/VideoBackends/OGL/PostProcessing.h +++ b/Source/Core/VideoBackends/OGL/PostProcessing.h @@ -23,15 +23,17 @@ class OpenGLPostProcessing : public PostProcessingShaderImplementation ~OpenGLPostProcessing(); void BlitFromTexture(TargetRectangle src, TargetRectangle dst, - int src_texture, int src_width, int src_height) override; + int src_texture, int src_width, int src_height, int layer) override; void ApplyShader() override; private: bool m_initialized; + bool m_anaglyph; SHADER m_shader; GLuint m_uniform_resolution; GLuint m_uniform_src_rect; GLuint m_uniform_time; + GLuint m_uniform_layer; std::string m_glsl_header; // These are only used when working around Qualcomm's broken attributeless rendering diff --git a/Source/Core/VideoBackends/OGL/ProgramShaderCache.cpp b/Source/Core/VideoBackends/OGL/ProgramShaderCache.cpp index f25f9c90cfc1..bf789f46a679 100644 --- a/Source/Core/VideoBackends/OGL/ProgramShaderCache.cpp +++ b/Source/Core/VideoBackends/OGL/ProgramShaderCache.cpp @@ -36,6 +36,7 @@ ProgramShaderCache::PCacheEntry* ProgramShaderCache::last_entry; SHADERUID ProgramShaderCache::last_uid; UidChecker ProgramShaderCache::pixel_uid_checker; UidChecker ProgramShaderCache::vertex_uid_checker; +UidChecker ProgramShaderCache::geometry_uid_checker; static char s_glsl_header[1024] = ""; @@ -196,13 +197,17 @@ SHADER* ProgramShaderCache::SetShader(DSTALPHA_MODE dstAlphaMode, u32 components VertexShaderCode vcode; PixelShaderCode pcode; + ShaderCode gcode; GenerateVertexShaderCode(vcode, components, API_OPENGL); GeneratePixelShaderCode(pcode, dstAlphaMode, API_OPENGL, components); + if (g_ActiveConfig.iStereoMode > 0) + GenerateGeometryShaderCode(gcode, components, API_OPENGL); if (g_ActiveConfig.bEnableShaderDebugging) { newentry.shader.strvprog = vcode.GetBuffer(); newentry.shader.strpprog = pcode.GetBuffer(); + newentry.shader.strgprog = gcode.GetBuffer(); } #if defined(_DEBUG) || defined(DEBUGFAST) @@ -214,10 +219,16 @@ SHADER* ProgramShaderCache::SetShader(DSTALPHA_MODE dstAlphaMode, u32 components filename = StringFromFormat("%sps_%04i.txt", File::GetUserPath(D_DUMP_IDX).c_str(), counter++); SaveData(filename, pcode.GetBuffer()); + + if (gcode.GetBuffer() != nullptr) + { + filename = StringFromFormat("%sgs_%04i.txt", File::GetUserPath(D_DUMP_IDX).c_str(), counter++); + SaveData(filename, gcode.GetBuffer()); + } } #endif - if (!CompileShader(newentry.shader, vcode.GetBuffer(), pcode.GetBuffer())) + if (!CompileShader(newentry.shader, vcode.GetBuffer(), pcode.GetBuffer(), gcode.GetBuffer())) { GFX_DEBUGGER_PAUSE_AT(NEXT_ERROR, true); return nullptr; @@ -231,15 +242,21 @@ SHADER* ProgramShaderCache::SetShader(DSTALPHA_MODE dstAlphaMode, u32 components return &last_entry->shader; } -bool ProgramShaderCache::CompileShader(SHADER& shader, const char* vcode, const char* pcode) +bool ProgramShaderCache::CompileShader(SHADER& shader, const char* vcode, const char* pcode, const char* gcode) { GLuint vsid = CompileSingleShader(GL_VERTEX_SHADER, vcode); GLuint psid = CompileSingleShader(GL_FRAGMENT_SHADER, pcode); - if (!vsid || !psid) + // Optional geometry shader + GLuint gsid = 0; + if (gcode) + gsid = CompileSingleShader(GL_GEOMETRY_SHADER, gcode); + + if (!vsid || !psid || (gcode && !gsid)) { glDeleteShader(vsid); glDeleteShader(psid); + glDeleteShader(gsid); return false; } @@ -247,6 +264,8 @@ bool ProgramShaderCache::CompileShader(SHADER& shader, const char* vcode, const glAttachShader(pid, vsid); glAttachShader(pid, psid); + if (gsid) + glAttachShader(pid, gsid); if (g_ogl_config.bSupportsGLSLCache) glProgramParameteri(pid, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE); @@ -258,6 +277,7 @@ bool ProgramShaderCache::CompileShader(SHADER& shader, const char* vcode, const // original shaders aren't needed any more glDeleteShader(vsid); glDeleteShader(psid); + glDeleteShader(gsid); GLint linkStatus; glGetProgramiv(pid, GL_LINK_STATUS, &linkStatus); @@ -273,7 +293,10 @@ bool ProgramShaderCache::CompileShader(SHADER& shader, const char* vcode, const std::string filename = StringFromFormat("%sbad_p_%d.txt", File::GetUserPath(D_DUMP_IDX).c_str(), num_failures++); std::ofstream file; OpenFStream(file, filename, std::ios_base::out); - file << s_glsl_header << vcode << s_glsl_header << pcode << infoLog; + file << s_glsl_header << vcode << s_glsl_header << pcode; + if (gcode) + file << s_glsl_header << gcode; + file << infoLog; file.close(); if (linkStatus != GL_TRUE) @@ -324,11 +347,11 @@ GLuint ProgramShaderCache::CompileSingleShader(GLuint type, const char* code) GLsizei charsWritten; GLchar* infoLog = new GLchar[length]; glGetShaderInfoLog(result, length, &charsWritten, infoLog); - ERROR_LOG(VIDEO, "%s Shader info log:\n%s", type==GL_VERTEX_SHADER ? "VS" : "PS", infoLog); + ERROR_LOG(VIDEO, "%s Shader info log:\n%s", type==GL_VERTEX_SHADER ? "VS" : type==GL_FRAGMENT_SHADER ? "PS" : "GS", infoLog); std::string filename = StringFromFormat("%sbad_%s_%04i.txt", File::GetUserPath(D_DUMP_IDX).c_str(), - type==GL_VERTEX_SHADER ? "vs" : "ps", + type==GL_VERTEX_SHADER ? "vs" : type==GL_FRAGMENT_SHADER ? "ps" : "gs", num_failures++); std::ofstream file; OpenFStream(file, filename, std::ios_base::out); @@ -338,7 +361,7 @@ GLuint ProgramShaderCache::CompileSingleShader(GLuint type, const char* code) if (compileStatus != GL_TRUE) { PanicAlert("Failed to compile %s shader!\nThis usually happens when trying to use Dolphin with an outdated GPU or integrated GPU like the Intel GMA series.\n\nIf you're sure this is Dolphin's error anyway, post the contents of %s along with this error message at the forums.\n\nDebug info (%s, %s, %s):\n%s", - type == GL_VERTEX_SHADER ? "vertex" : "pixel", + type == GL_VERTEX_SHADER ? "vertex" : type==GL_FRAGMENT_SHADER ? "pixel" : "geometry", filename.c_str(), g_ogl_config.gl_vendor, g_ogl_config.gl_renderer, @@ -365,6 +388,7 @@ void ProgramShaderCache::GetShaderId(SHADERUID* uid, DSTALPHA_MODE dstAlphaMode, { GetPixelShaderUid(uid->puid, dstAlphaMode, API_OPENGL, components); GetVertexShaderUid(uid->vuid, components, API_OPENGL); + GetGeometryShaderUid(uid->guid, components, API_OPENGL); if (g_ActiveConfig.bEnableShaderDebugging) { @@ -375,6 +399,10 @@ void ProgramShaderCache::GetShaderId(SHADERUID* uid, DSTALPHA_MODE dstAlphaMode, VertexShaderCode vcode; GenerateVertexShaderCode(vcode, components, API_OPENGL); vertex_uid_checker.AddToIndexAndCheck(vcode, uid->vuid, "Vertex", "v"); + + ShaderCode gcode; + GenerateGeometryShaderCode(gcode, components, API_OPENGL); + geometry_uid_checker.AddToIndexAndCheck(gcode, uid->guid, "Geometry", "g"); } } @@ -486,6 +514,7 @@ void ProgramShaderCache::CreateHeader() "%s\n" // sample shading "%s\n" // Sampler binding "%s\n" // storage buffer + "%s\n" // shader5 // Precision defines for GLSL ES "%s\n" @@ -518,6 +547,7 @@ void ProgramShaderCache::CreateHeader() , (g_ogl_config.bSupportSampleShading) ? "#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" : "" + , g_ActiveConfig.backend_info.bSupportsGSInstancing ? "#extension GL_ARB_gpu_shader5 : enable" : "" , v>=GLSLES_300 ? "precision highp float;" : "" , v>=GLSLES_300 ? "precision highp int;" : "" diff --git a/Source/Core/VideoBackends/OGL/ProgramShaderCache.h b/Source/Core/VideoBackends/OGL/ProgramShaderCache.h index cab5107eea49..d1e3a5ee5436 100644 --- a/Source/Core/VideoBackends/OGL/ProgramShaderCache.h +++ b/Source/Core/VideoBackends/OGL/ProgramShaderCache.h @@ -7,6 +7,7 @@ #include "Common/LinearDiskCache.h" #include "Core/ConfigManager.h" #include "VideoBackends/OGL/GLUtil.h" +#include "VideoCommon/GeometryShaderGen.h" #include "VideoCommon/PixelShaderGen.h" #include "VideoCommon/VertexShaderGen.h" @@ -18,10 +19,11 @@ class SHADERUID public: VertexShaderUid vuid; PixelShaderUid puid; + GeometryShaderUid guid; SHADERUID() {} - SHADERUID(const SHADERUID& r) : vuid(r.vuid), puid(r.puid) {} + SHADERUID(const SHADERUID& r) : vuid(r.vuid), puid(r.puid), guid(r.guid) {} bool operator <(const SHADERUID& r) const { @@ -34,12 +36,18 @@ class SHADERUID if (vuid < r.vuid) return true; + if (r.vuid < vuid) + return false; + + if (guid < r.guid) + return true; + return false; } bool operator ==(const SHADERUID& r) const { - return puid == r.puid && vuid == r.vuid; + return puid == r.puid && vuid == r.vuid && guid == r.guid; } }; @@ -54,7 +62,7 @@ struct SHADER } GLuint glprogid; // OpenGL program id - std::string strvprog, strpprog; + std::string strvprog, strpprog, strgprog; void SetProgramVariables(); void SetProgramBindings(); @@ -83,7 +91,7 @@ class ProgramShaderCache static SHADER* SetShader(DSTALPHA_MODE dstAlphaMode, u32 components); static void GetShaderId(SHADERUID *uid, DSTALPHA_MODE dstAlphaMode, u32 components); - static bool CompileShader(SHADER &shader, const char* vcode, const char* pcode); + static bool CompileShader(SHADER &shader, const char* vcode, const char* pcode, const char* gcode = nullptr); static GLuint CompileSingleShader(GLuint type, const char *code); static void UploadConstants(); @@ -104,6 +112,7 @@ class ProgramShaderCache static UidChecker pixel_uid_checker; static UidChecker vertex_uid_checker; + static UidChecker geometry_uid_checker; static u32 s_ubo_buffer_size; static s32 s_ubo_align; diff --git a/Source/Core/VideoBackends/OGL/Render.cpp b/Source/Core/VideoBackends/OGL/Render.cpp index b66d24b0fd4e..79c42519c0c6 100644 --- a/Source/Core/VideoBackends/OGL/Render.cpp +++ b/Source/Core/VideoBackends/OGL/Render.cpp @@ -89,6 +89,8 @@ static RasterFont* s_pfont = nullptr; static int s_MSAASamples = 1; static int s_LastMultisampleMode = 0; +static bool s_LastStereo = false; + static u32 s_blendMode; static bool s_vsync; @@ -204,6 +206,10 @@ static void GLAPIENTRY ClearDepthf(GLfloat depthval) { glClearDepth(depthval); } +static void GLAPIENTRY FramebufferTexture(GLenum target, GLenum attachment, GLuint texture, GLint level) +{ + glFramebufferTextureLayer(target, attachment, texture, level, 0); +} static void InitDriverInfo() { @@ -460,11 +466,17 @@ Renderer::Renderer() glClearDepthf = ClearDepthf; } + if (GLExtensions::Version() < 320 || GLInterface->GetMode() != GLInterfaceMode::MODE_OPENGL) + { + glFramebufferTexture = FramebufferTexture; + } + g_Config.backend_info.bSupportsDualSourceBlend = GLExtensions::Supports("GL_ARB_blend_func_extended"); g_Config.backend_info.bSupportsPrimitiveRestart = !DriverDetails::HasBug(DriverDetails::BUG_PRIMITIVERESTART) && ((GLExtensions::Version() >= 310) || GLExtensions::Supports("GL_NV_primitive_restart")); g_Config.backend_info.bSupportsEarlyZ = GLExtensions::Supports("GL_ARB_shader_image_load_store"); g_Config.backend_info.bSupportsBBox = GLExtensions::Supports("GL_ARB_shader_storage_buffer_object"); + g_Config.backend_info.bSupportsGSInstancing = GLExtensions::Supports("GL_ARB_gpu_shader5"); // Desktop OpenGL supports the binding layout if it supports 420pack // OpenGL ES 3.1 supports it implicitly without an extension @@ -493,6 +505,8 @@ Renderer::Renderer() g_Config.backend_info.bSupportsBindingLayout = true; g_Config.backend_info.bSupportsEarlyZ = true; } + // TODO: OpenGL ES 3.1 provides the necessary features as extensions. + g_Config.backend_info.bSupportsStereoscopy = false; } else { @@ -507,11 +521,13 @@ Renderer::Renderer() { g_ogl_config.eSupportedGLSLVersion = GLSL_130; g_Config.backend_info.bSupportsEarlyZ = false; // layout keyword is only supported on glsl150+ + g_Config.backend_info.bSupportsStereoscopy = false; // geometry shaders are only supported on glsl150+ } else if (strstr(g_ogl_config.glsl_version, "1.40")) { g_ogl_config.eSupportedGLSLVersion = GLSL_140; g_Config.backend_info.bSupportsEarlyZ = false; // layout keyword is only supported on glsl150+ + g_Config.backend_info.bSupportsStereoscopy = false; // geometry shaders are only supported on glsl150+ } else { @@ -545,6 +561,9 @@ Renderer::Renderer() bSuccess = false; } + if (g_Config.iStereoMode > 0 && !g_Config.backend_info.bSupportsStereoscopy) + OSD::AddMessage("Stereoscopic 3D isn't supported by your GPU, support for OpenGL 3.2 is required.", 10000); + if (!bSuccess) { // Not all needed extensions are supported, so we have to stop here. @@ -556,6 +575,7 @@ Renderer::Renderer() if (g_ogl_config.max_samples < 1 || !g_ogl_config.bSupportsMSAA) g_ogl_config.max_samples = 1; + g_Config.VerifyValidity(); UpdateActiveConfig(); OSD::AddMessage(StringFromFormat("Video Info: %s, %s, %s", @@ -563,7 +583,7 @@ Renderer::Renderer() g_ogl_config.gl_renderer, g_ogl_config.gl_version), 5000); - WARN_LOG(VIDEO,"Missing OGL Extensions: %s%s%s%s%s%s%s%s%s%s", + WARN_LOG(VIDEO,"Missing OGL Extensions: %s%s%s%s%s%s%s%s%s%s%s", g_ActiveConfig.backend_info.bSupportsDualSourceBlend ? "" : "DualSourceBlend ", g_ActiveConfig.backend_info.bSupportsPrimitiveRestart ? "" : "PrimitiveRestart ", g_ActiveConfig.backend_info.bSupportsEarlyZ ? "" : "EarlyZ ", @@ -573,12 +593,14 @@ Renderer::Renderer() g_ogl_config.bSupportsGLBufferStorage ? "" : "BufferStorage ", g_ogl_config.bSupportsGLSync ? "" : "Sync ", g_ogl_config.bSupportsMSAA ? "" : "MSAA ", - g_ogl_config.bSupportSampleShading ? "" : "SSAA " + g_ogl_config.bSupportSampleShading ? "" : "SSAA ", + g_ActiveConfig.backend_info.bSupportsGSInstancing ? "" : "GSInstancing " ); s_LastMultisampleMode = g_ActiveConfig.iMultisampleMode; s_MSAASamples = GetNumMSAASamples(s_LastMultisampleMode); ApplySSAASettings(); + s_LastStereo = g_ActiveConfig.iStereoMode > 0; // Decide framebuffer size s_backbuffer_width = (int)GLInterface->GetBackBufferWidth(); @@ -1493,7 +1515,8 @@ void Renderer::SwapImpl(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, co sourceRc.right -= fbStride - fbWidth; - m_post_processor->BlitFromTexture(sourceRc, drawRc, xfbSource->texture, xfbSource->texWidth, xfbSource->texHeight); + // TODO: Virtual XFB stereoscopic 3D support. + m_post_processor->BlitFromTexture(sourceRc, drawRc, xfbSource->texture, xfbSource->texWidth, xfbSource->texHeight, 1); } } else @@ -1503,7 +1526,18 @@ void Renderer::SwapImpl(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, co // for msaa mode, we must resolve the efb content to non-msaa GLuint tex = FramebufferManager::ResolveAndGetRenderTarget(rc); - m_post_processor->BlitFromTexture(targetRc, flipped_trc, tex, s_target_width, s_target_height); + if (g_ActiveConfig.iStereoMode == STEREO_SBS || g_ActiveConfig.iStereoMode == STEREO_TAB) + { + TargetRectangle leftRc, rightRc; + ConvertStereoRectangle(flipped_trc, leftRc, rightRc); + + m_post_processor->BlitFromTexture(targetRc, leftRc, tex, s_target_width, s_target_height, 0); + m_post_processor->BlitFromTexture(targetRc, rightRc, tex, s_target_width, s_target_height, 1); + } + else + { + m_post_processor->BlitFromTexture(targetRc, flipped_trc, tex, s_target_width, s_target_height); + } } glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); @@ -1652,15 +1686,16 @@ void Renderer::SwapImpl(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, co s_LastEFBScale = g_ActiveConfig.iEFBScale; } - if (xfbchanged || WindowResized || (s_LastMultisampleMode != g_ActiveConfig.iMultisampleMode)) + if (xfbchanged || WindowResized || (s_LastMultisampleMode != g_ActiveConfig.iMultisampleMode) || (s_LastStereo != (g_ActiveConfig.iStereoMode > 0))) { UpdateDrawRectangle(s_backbuffer_width, s_backbuffer_height); - if (CalculateTargetSize(s_backbuffer_width, s_backbuffer_height) || s_LastMultisampleMode != g_ActiveConfig.iMultisampleMode) + if (CalculateTargetSize(s_backbuffer_width, s_backbuffer_height) || s_LastMultisampleMode != g_ActiveConfig.iMultisampleMode || s_LastStereo != (g_ActiveConfig.iStereoMode > 0)) { s_LastMultisampleMode = g_ActiveConfig.iMultisampleMode; s_MSAASamples = GetNumMSAASamples(s_LastMultisampleMode); ApplySSAASettings(); + s_LastStereo = g_ActiveConfig.iStereoMode > 0; delete g_framebuffer_manager; g_framebuffer_manager = new FramebufferManager(s_target_width, s_target_height, diff --git a/Source/Core/VideoBackends/OGL/TextureCache.cpp b/Source/Core/VideoBackends/OGL/TextureCache.cpp index 5f14704cae61..66f633c6e3a8 100644 --- a/Source/Core/VideoBackends/OGL/TextureCache.cpp +++ b/Source/Core/VideoBackends/OGL/TextureCache.cpp @@ -98,14 +98,14 @@ void TextureCache::TCacheEntry::Bind(unsigned int stage) s_ActiveTexture = stage; } - glBindTexture(GL_TEXTURE_2D, texture); + glBindTexture(GL_TEXTURE_2D_ARRAY, texture); s_Textures[stage] = texture; } } bool TextureCache::TCacheEntry::Save(const std::string& filename, unsigned int level) { - return SaveTexture(filename, GL_TEXTURE_2D, texture, virtual_width, virtual_height, level); + return SaveTexture(filename, GL_TEXTURE_2D_ARRAY, texture, virtual_width, virtual_height, level); } TextureCache::TCacheEntryBase* TextureCache::CreateTexture(unsigned int width, @@ -172,8 +172,8 @@ TextureCache::TCacheEntryBase* TextureCache::CreateTexture(unsigned int width, entry.pcfmt = pcfmt; glActiveTexture(GL_TEXTURE0+9); - glBindTexture(GL_TEXTURE_2D, entry.texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, tex_levels - 1); + glBindTexture(GL_TEXTURE_2D_ARRAY, entry.texture); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, tex_levels - 1); entry.Load(width, height, expanded_width, 0); @@ -189,12 +189,12 @@ void TextureCache::TCacheEntry::Load(unsigned int width, unsigned int height, if (pcfmt != PC_TEX_FMT_DXT1) { glActiveTexture(GL_TEXTURE0+9); - glBindTexture(GL_TEXTURE_2D, texture); + glBindTexture(GL_TEXTURE_2D_ARRAY, texture); if (expanded_width != width) glPixelStorei(GL_UNPACK_ROW_LENGTH, expanded_width); - glTexImage2D(GL_TEXTURE_2D, level, gl_iformat, width, height, 0, gl_format, gl_type, temp); + glTexImage3D(GL_TEXTURE_2D_ARRAY, level, gl_iformat, width, height, 1, 0, gl_format, gl_type, temp); if (expanded_width != width) glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); @@ -213,21 +213,20 @@ TextureCache::TCacheEntryBase* TextureCache::CreateRenderTargetTexture( { TCacheEntry *const entry = new TCacheEntry; glActiveTexture(GL_TEXTURE0+9); - glBindTexture(GL_TEXTURE_2D, entry->texture); + glBindTexture(GL_TEXTURE_2D_ARRAY, entry->texture); const GLenum gl_format = GL_RGBA, gl_iformat = GL_RGBA, gl_type = GL_UNSIGNED_BYTE; - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); - - glTexImage2D(GL_TEXTURE_2D, 0, gl_iformat, scaled_tex_w, scaled_tex_h, 0, gl_format, gl_type, nullptr); - glBindTexture(GL_TEXTURE_2D, 0); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 0); + glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, gl_iformat, scaled_tex_w, scaled_tex_h, FramebufferManager::GetEFBLayers(), 0, gl_format, gl_type, nullptr); + glBindTexture(GL_TEXTURE_2D_ARRAY, 0); glGenFramebuffers(1, &entry->framebuffer); FramebufferManager::SetFramebuffer(entry->framebuffer); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, entry->texture, 0); + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, entry->texture, 0); SetStage(); @@ -251,7 +250,7 @@ void TextureCache::TCacheEntry::FromRenderTarget(u32 dstAddr, unsigned int dstFo FramebufferManager::SetFramebuffer(framebuffer); glActiveTexture(GL_TEXTURE0+9); - glBindTexture(GL_TEXTURE_2D, read_texture); + glBindTexture(GL_TEXTURE_2D_ARRAY, read_texture); glViewport(0, 0, virtual_width, virtual_height); @@ -309,34 +308,60 @@ void TextureCache::TCacheEntry::FromRenderTarget(u32 dstAddr, unsigned int dstFo { static int count = 0; SaveTexture(StringFromFormat("%sefb_frame_%i.png", File::GetUserPath(D_DUMPTEXTURES_IDX).c_str(), - count++), GL_TEXTURE_2D, texture, virtual_width, virtual_height, 0); + count++), GL_TEXTURE_2D_ARRAY, texture, virtual_width, virtual_height, 0); } g_renderer->RestoreAPIState(); } TextureCache::TextureCache() +{ + CompileShaders(); + + s_ActiveTexture = -1; + for (auto& gtex : s_Textures) + gtex = -1; +} + + +TextureCache::~TextureCache() +{ + DeleteShaders(); +} + +void TextureCache::DisableStage(unsigned int stage) +{ +} + +void TextureCache::SetStage() +{ + // -1 is the initial value as we don't know which texture should be bound + if (s_ActiveTexture != (u32)-1) + glActiveTexture(GL_TEXTURE0 + s_ActiveTexture); +} + +void TextureCache::CompileShaders() { const char *pColorMatrixProg = - "SAMPLER_BINDING(9) uniform sampler2D samp9;\n" + "SAMPLER_BINDING(9) uniform sampler2DArray samp9;\n" "uniform vec4 colmat[7];\n" - "in vec2 uv0;\n" + "in vec3 f_uv0;\n" "out vec4 ocol0;\n" "\n" "void main(){\n" - " vec4 texcol = texture(samp9, uv0);\n" + " vec4 texcol = texture(samp9, f_uv0);\n" " texcol = round(texcol * colmat[5]) * colmat[6];\n" " ocol0 = texcol * mat4(colmat[0], colmat[1], colmat[2], colmat[3]) + colmat[4];\n" "}\n"; const char *pDepthMatrixProg = - "SAMPLER_BINDING(9) uniform sampler2D samp9;\n" + "SAMPLER_BINDING(9) uniform sampler2DArray samp9;\n" "uniform vec4 colmat[5];\n" - "in vec2 uv0;\n" + "in vec3 f_uv0;\n" "out vec4 ocol0;\n" "\n" "void main(){\n" - " vec4 texcol = texture(samp9, uv0);\n" + " vec4 texcol = texture(samp9, vec3(f_uv0.xy, %s));\n" // 255.99998474121 = 16777215/16777216*256 " float workspace = texcol.x * 255.99998474121;\n" @@ -363,18 +388,41 @@ TextureCache::TextureCache() "}\n"; const char *VProgram = - "out vec2 uv0;\n" - "SAMPLER_BINDING(9) uniform sampler2D samp9;\n" + "out vec3 %s_uv0;\n" + "SAMPLER_BINDING(9) uniform sampler2DArray samp9;\n" "uniform vec4 copy_position;\n" // left, top, right, bottom "void main()\n" "{\n" " vec2 rawpos = vec2(gl_VertexID&1, gl_VertexID&2);\n" - " uv0 = mix(copy_position.xy, copy_position.zw, rawpos) / vec2(textureSize(samp9, 0));\n" + " %s_uv0 = vec3(mix(copy_position.xy, copy_position.zw, rawpos) / vec2(textureSize(samp9, 0).xy), 0.0);\n" " gl_Position = vec4(rawpos*2.0-1.0, 0.0, 1.0);\n" "}\n"; - ProgramShaderCache::CompileShader(s_ColorMatrixProgram, VProgram, pColorMatrixProg); - ProgramShaderCache::CompileShader(s_DepthMatrixProgram, VProgram, pDepthMatrixProg); + const char *GProgram = (g_ActiveConfig.iStereoMode > 0) ? + "layout(triangles) in;\n" + "layout(triangle_strip, max_vertices = 6) out;\n" + "in vec3 v_uv0[3];\n" + "out vec3 f_uv0;\n" + "SAMPLER_BINDING(9) uniform sampler2DArray samp9;\n" + "void main()\n" + "{\n" + " int layers = textureSize(samp9, 0).z;\n" + " for (int layer = 0; layer < layers; ++layer) {\n" + " for (int i = 0; i < 3; ++i) {\n" + " f_uv0 = vec3(v_uv0[i].xy, layer);\n" + " gl_Position = gl_in[i].gl_Position;\n" + " gl_Layer = layer;\n" + " EmitVertex();\n" + " }\n" + " EndPrimitive();\n" + " }\n" + "}\n" : nullptr; + + const char* prefix = (GProgram == nullptr) ? "f" : "v"; + const char* depth_layer = (g_ActiveConfig.bStereoMonoEFBDepth) ? "0" : "f_uv0.z"; + + ProgramShaderCache::CompileShader(s_ColorMatrixProgram, StringFromFormat(VProgram, prefix, prefix).c_str(), pColorMatrixProg, GProgram); + ProgramShaderCache::CompileShader(s_DepthMatrixProgram, StringFromFormat(VProgram, prefix, prefix).c_str(), StringFromFormat(pDepthMatrixProg, depth_layer).c_str(), GProgram); s_ColorMatrixUniform = glGetUniformLocation(s_ColorMatrixProgram.glprogid, "colmat"); s_DepthMatrixUniform = glGetUniformLocation(s_DepthMatrixProgram.glprogid, "colmat"); @@ -383,28 +431,12 @@ TextureCache::TextureCache() s_ColorCopyPositionUniform = glGetUniformLocation(s_ColorMatrixProgram.glprogid, "copy_position"); s_DepthCopyPositionUniform = glGetUniformLocation(s_DepthMatrixProgram.glprogid, "copy_position"); - - s_ActiveTexture = -1; - for (auto& gtex : s_Textures) - gtex = -1; } - -TextureCache::~TextureCache() +void TextureCache::DeleteShaders() { s_ColorMatrixProgram.Destroy(); s_DepthMatrixProgram.Destroy(); } -void TextureCache::DisableStage(unsigned int stage) -{ -} - -void TextureCache::SetStage () -{ - // -1 is the initial value as we don't know which texture should be bound - if (s_ActiveTexture != (u32)-1) - glActiveTexture(GL_TEXTURE0 + s_ActiveTexture); -} - } diff --git a/Source/Core/VideoBackends/OGL/TextureCache.h b/Source/Core/VideoBackends/OGL/TextureCache.h index e1e2ec694ca8..f8d181e8e196 100644 --- a/Source/Core/VideoBackends/OGL/TextureCache.h +++ b/Source/Core/VideoBackends/OGL/TextureCache.h @@ -57,6 +57,9 @@ class TextureCache : public ::TextureCache unsigned int expanded_width, unsigned int tex_levels, PC_TexFormat pcfmt) override; TCacheEntryBase* CreateRenderTargetTexture(unsigned int scaled_tex_w, unsigned int scaled_tex_h) override; + + void CompileShaders() override; + void DeleteShaders() override; }; bool SaveTexture(const std::string& filename, u32 textarget, u32 tex, int virtual_width, int virtual_height, unsigned int level); diff --git a/Source/Core/VideoBackends/OGL/TextureConverter.cpp b/Source/Core/VideoBackends/OGL/TextureConverter.cpp index 1404ec92eb71..ece33797a64d 100644 --- a/Source/Core/VideoBackends/OGL/TextureConverter.cpp +++ b/Source/Core/VideoBackends/OGL/TextureConverter.cpp @@ -72,21 +72,21 @@ static void CreatePrograms() const char *VProgramRgbToYuyv = "out vec2 uv0;\n" "uniform vec4 copy_position;\n" // left, top, right, bottom - "SAMPLER_BINDING(9) uniform sampler2D samp9;\n" + "SAMPLER_BINDING(9) uniform sampler2DArray samp9;\n" "void main()\n" "{\n" " vec2 rawpos = vec2(gl_VertexID&1, gl_VertexID&2);\n" " gl_Position = vec4(rawpos*2.0-1.0, 0.0, 1.0);\n" - " uv0 = mix(copy_position.xy, copy_position.zw, rawpos) / vec2(textureSize(samp9, 0));\n" + " uv0 = mix(copy_position.xy, copy_position.zw, rawpos) / vec2(textureSize(samp9, 0).xy);\n" "}\n"; const char *FProgramRgbToYuyv = - "SAMPLER_BINDING(9) uniform sampler2D samp9;\n" + "SAMPLER_BINDING(9) uniform sampler2DArray samp9;\n" "in vec2 uv0;\n" "out vec4 ocol0;\n" "void main()\n" "{\n" - " vec3 c0 = texture(samp9, (uv0 - dFdx(uv0) * 0.25)).rgb;\n" - " vec3 c1 = texture(samp9, (uv0 + dFdx(uv0) * 0.25)).rgb;\n" + " vec3 c0 = texture(samp9, vec3(uv0 - dFdx(uv0) * 0.25, 0.0)).rgb;\n" + " vec3 c1 = texture(samp9, vec3(uv0 + dFdx(uv0) * 0.25, 0.0)).rgb;\n" " vec3 c01 = (c0 + c1) * 0.5;\n" " vec3 y_const = vec3(0.257,0.504,0.098);\n" " vec3 u_const = vec3(-0.148,-0.291,0.439);\n" @@ -224,17 +224,17 @@ static void EncodeToRamUsingShader(GLuint srcTexture, // set source texture glActiveTexture(GL_TEXTURE0+9); - glBindTexture(GL_TEXTURE_2D, srcTexture); + glBindTexture(GL_TEXTURE_2D_ARRAY, srcTexture); if (linearFilter) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); } else { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST); } glViewport(0, 0, (GLsizei)dstWidth, (GLsizei)dstHeight); @@ -365,7 +365,7 @@ void DecodeToTexture(u32 xfbAddr, int srcWidth, int srcHeight, GLuint destTextur // switch to texture converter frame buffer // attach destTexture as color destination FramebufferManager::SetFramebuffer(s_texConvFrameBuffer[1]); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, destTexture, 0); + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, destTexture, 0); // activate source texture // set srcAddr as data for source texture diff --git a/Source/Core/VideoBackends/OGL/main.cpp b/Source/Core/VideoBackends/OGL/main.cpp index eb01d3355ad7..0fb36ab8d152 100644 --- a/Source/Core/VideoBackends/OGL/main.cpp +++ b/Source/Core/VideoBackends/OGL/main.cpp @@ -138,6 +138,7 @@ static void InitBackendInfo() //g_Config.backend_info.bSupportsDualSourceBlend = true; // is gpu dependent and must be set in renderer //g_Config.backend_info.bSupportsEarlyZ = true; // is gpu dependent and must be set in renderer g_Config.backend_info.bSupportsOversizedViewports = true; + g_Config.backend_info.bSupportsStereoscopy = true; g_Config.backend_info.Adapters.clear(); diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt index 87b23a25ff4d..0dfd12b73285 100644 --- a/Source/Core/VideoCommon/CMakeLists.txt +++ b/Source/Core/VideoCommon/CMakeLists.txt @@ -9,6 +9,7 @@ set(SRCS BoundingBox.cpp Fifo.cpp FPSCounter.cpp FramebufferManagerBase.cpp + GeometryShaderGen.cpp HiresTextures.cpp ImageWrite.cpp IndexGenerator.cpp diff --git a/Source/Core/VideoCommon/ConstantManager.h b/Source/Core/VideoCommon/ConstantManager.h index 3e52640ca9e0..116088d76ec7 100644 --- a/Source/Core/VideoCommon/ConstantManager.h +++ b/Source/Core/VideoCommon/ConstantManager.h @@ -43,5 +43,6 @@ struct VertexShaderConstants float4 normalmatrices[32]; float4 posttransformmatrices[64]; float4 depthparams; + float4 stereoparams; }; diff --git a/Source/Core/VideoCommon/FramebufferManagerBase.cpp b/Source/Core/VideoCommon/FramebufferManagerBase.cpp index ea865cd275b2..e2a09c652738 100644 --- a/Source/Core/VideoCommon/FramebufferManagerBase.cpp +++ b/Source/Core/VideoCommon/FramebufferManagerBase.cpp @@ -12,6 +12,8 @@ const XFBSourceBase* FramebufferManagerBase::m_overlappingXFBArray[MAX_VIRTUAL_X unsigned int FramebufferManagerBase::s_last_xfb_width = 1; unsigned int FramebufferManagerBase::s_last_xfb_height = 1; +unsigned int FramebufferManagerBase::m_EFBLayers = 1; + FramebufferManagerBase::FramebufferManagerBase() { m_realXFBSource = nullptr; diff --git a/Source/Core/VideoCommon/FramebufferManagerBase.h b/Source/Core/VideoCommon/FramebufferManagerBase.h index 50813539ab6b..b01e820243c4 100644 --- a/Source/Core/VideoCommon/FramebufferManagerBase.h +++ b/Source/Core/VideoCommon/FramebufferManagerBase.h @@ -55,6 +55,8 @@ class FramebufferManagerBase static int ScaleToVirtualXfbWidth(int x, unsigned int backbuffer_width); static int ScaleToVirtualXfbHeight(int y, unsigned int backbuffer_height); + static int GetEFBLayers() { return m_EFBLayers; } + protected: struct VirtualXFB { @@ -70,6 +72,8 @@ class FramebufferManagerBase typedef std::list VirtualXFBListType; + static unsigned int m_EFBLayers; + private: virtual XFBSourceBase* CreateXFBSource(unsigned int target_width, unsigned int target_height) = 0; // TODO: figure out why OGL is different for this guy diff --git a/Source/Core/VideoCommon/GeometryShaderGen.cpp b/Source/Core/VideoCommon/GeometryShaderGen.cpp new file mode 100644 index 000000000000..83a590a7ed72 --- /dev/null +++ b/Source/Core/VideoCommon/GeometryShaderGen.cpp @@ -0,0 +1,134 @@ +// Copyright 2013 Dolphin Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#include +#include +#ifdef __APPLE__ + #include +#endif + +#include "VideoCommon/GeometryShaderGen.h" +#include "VideoCommon/LightingShaderGen.h" +#include "VideoCommon/VertexShaderGen.h" +#include "VideoCommon/VideoConfig.h" + +static char text[16384]; + +template +static inline void GenerateGeometryShader(T& out, u32 components, API_TYPE ApiType) +{ + // Non-uid template parameters will write to the dummy data (=> gets optimized out) + geometry_shader_uid_data dummy_data; + geometry_shader_uid_data* uid_data = out.template GetUidData(); + if (uid_data == nullptr) + uid_data = &dummy_data; + + out.SetBuffer(text); + const bool is_writing_shadercode = (out.GetBuffer() != nullptr); +#ifndef ANDROID + locale_t locale; + locale_t old_locale; + if (is_writing_shadercode) + { + locale = newlocale(LC_NUMERIC_MASK, "C", nullptr); // New locale for compilation + old_locale = uselocale(locale); // Apply the locale for this thread + } +#endif + + if (is_writing_shadercode) + text[sizeof(text) - 1] = 0x7C; // canary + + out.Write("//Geometry Shader for 3D stereoscopy\n"); + + uid_data->stereo = g_ActiveConfig.iStereoMode > 0; + if (ApiType == API_OPENGL) + { + // Insert layout parameters + if (g_ActiveConfig.backend_info.bSupportsGSInstancing) + out.Write("layout(triangles, invocations = %d) in;\n", g_ActiveConfig.iStereoMode > 0 ? 2 : 1); + else + out.Write("layout(triangles) in;\n"); + out.Write("layout(triangle_strip, max_vertices = %d) out;\n", g_ActiveConfig.backend_info.bSupportsGSInstancing ? 3 : 6); + } + + out.Write("%s", s_lighting_struct); + + // uniforms + if (ApiType == API_OPENGL) + out.Write("layout(std140%s) uniform VSBlock {\n", g_ActiveConfig.backend_info.bSupportsBindingLayout ? ", binding = 2" : ""); + else + out.Write("cbuffer VSBlock {\n"); + out.Write(s_shader_uniforms); + out.Write("};\n"); + + uid_data->numTexGens = xfmem.numTexGen.numTexGens; + uid_data->pixel_lighting = g_ActiveConfig.bEnablePixelLighting; + + GenerateVSOutputStruct(out, ApiType); + + out.Write("centroid in VS_OUTPUT o[3];\n"); + out.Write("centroid out VS_OUTPUT f;\n"); + + out.Write("flat out int layer;\n"); + + out.Write("void main()\n{\n"); + + // If the GPU supports invocation we don't need a for loop and can simply use the + // invocation identifier to determine which layer we're rendering. + if (g_ActiveConfig.backend_info.bSupportsGSInstancing) + out.Write("\tint l = gl_InvocationID;\n"); + else + out.Write("\tfor (int l = 0; l < %d; ++l) {\n", g_ActiveConfig.iStereoMode > 0 ? 2 : 1); + + out.Write("\tfor (int i = 0; i < 3; ++i) {\n"); + out.Write("\t\tlayer = l;\n"); + out.Write("\t\tgl_Layer = l;\n"); + out.Write("\t\tf = o[i];\n"); + out.Write("\t\tfloat4 pos = o[i].pos;\n"); + + if (g_ActiveConfig.iStereoMode > 0) + { + // For stereoscopy add a small horizontal offset in Normalized Device Coordinates proportional + // to the depth of the vertex. We retrieve the depth value from the w-component of the projected + // vertex which contains the negated z-component of the original vertex. + // For negative parallax (out-of-screen effects) we subtract a convergence value from + // the depth value. This results in objects at a distance smaller than the convergence + // distance to seemingly appear in front of the screen. + // This formula is based on page 13 of the "Nvidia 3D Vision Automatic, Best Practices Guide" + out.Write("\t\tf.clipPos.x = o[i].clipPos.x + " I_STEREOPARAMS"[l] * (o[i].clipPos.w - " I_STEREOPARAMS"[2]);\n"); + out.Write("\t\tpos.x = o[i].pos.x + " I_STEREOPARAMS"[l] * (o[i].pos.w - " I_STEREOPARAMS"[2]);\n"); + } + + out.Write("\t\tf.pos.x = pos.x;\n"); + out.Write("\t\tgl_Position = pos;\n"); + out.Write("\t\tEmitVertex();\n"); + out.Write("\t}\n"); + out.Write("\tEndPrimitive();\n"); + + if (!g_ActiveConfig.backend_info.bSupportsGSInstancing) + out.Write("\t}\n"); + + out.Write("}\n"); + + if (is_writing_shadercode) + { + if (text[sizeof(text) - 1] != 0x7C) + PanicAlert("GeometryShader generator - buffer too small, canary has been eaten!"); + +#ifndef ANDROID + uselocale(old_locale); // restore locale + freelocale(locale); +#endif + } +} + +void GetGeometryShaderUid(GeometryShaderUid& object, u32 components, API_TYPE ApiType) +{ + GenerateGeometryShader(object, components, ApiType); +} + +void GenerateGeometryShaderCode(ShaderCode& object, u32 components, API_TYPE ApiType) +{ + GenerateGeometryShader(object, components, ApiType); +} diff --git a/Source/Core/VideoCommon/GeometryShaderGen.h b/Source/Core/VideoCommon/GeometryShaderGen.h new file mode 100644 index 000000000000..3f7dc5198b18 --- /dev/null +++ b/Source/Core/VideoCommon/GeometryShaderGen.h @@ -0,0 +1,26 @@ +// Copyright 2013 Dolphin Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#pragma once + +#include "VideoCommon/ShaderGenCommon.h" +#include "VideoCommon/VideoCommon.h" + +#pragma pack(1) + +struct geometry_shader_uid_data +{ + u32 NumValues() const { return sizeof(geometry_shader_uid_data); } + + u32 stereo : 1; + u32 numTexGens : 4; + u32 pixel_lighting : 1; +}; + +#pragma pack() + +typedef ShaderUid GeometryShaderUid; + +void GenerateGeometryShaderCode(ShaderCode& object, u32 components, API_TYPE ApiType); +void GetGeometryShaderUid(GeometryShaderUid& object, u32 components, API_TYPE ApiType); diff --git a/Source/Core/VideoCommon/PixelShaderGen.cpp b/Source/Core/VideoCommon/PixelShaderGen.cpp index c0b45fdfdab7..71e534df0a74 100644 --- a/Source/Core/VideoCommon/PixelShaderGen.cpp +++ b/Source/Core/VideoCommon/PixelShaderGen.cpp @@ -16,6 +16,7 @@ #include "VideoCommon/LightingShaderGen.h" #include "VideoCommon/NativeVertexFormat.h" #include "VideoCommon/PixelShaderGen.h" +#include "VideoCommon/VertexShaderGen.h" #include "VideoCommon/VideoConfig.h" #include "VideoCommon/XFMemory.h" // for texture projection mode @@ -206,7 +207,7 @@ static inline void GeneratePixelShader(T& out, DSTALPHA_MODE dstAlphaMode, API_T { // Declare samplers for (int i = 0; i < 8; ++i) - out.Write("SAMPLER_BINDING(%d) uniform sampler2D samp%d;\n", i, i); + out.Write("SAMPLER_BINDING(%d) uniform sampler2DArray samp%d;\n", i, i); } else // D3D { @@ -253,17 +254,8 @@ static inline void GeneratePixelShader(T& out, DSTALPHA_MODE dstAlphaMode, API_T { out.Write("cbuffer VSBlock : register(b1) {\n"); } - out.Write( - "\tfloat4 " I_POSNORMALMATRIX"[6];\n" - "\tfloat4 " I_PROJECTION"[4];\n" - "\tint4 " I_MATERIALS"[4];\n" - "\tLight " I_LIGHTS"[8];\n" - "\tfloat4 " I_TEXMATRICES"[24];\n" - "\tfloat4 " I_TRANSFORMMATRICES"[64];\n" - "\tfloat4 " I_NORMALMATRICES"[32];\n" - "\tfloat4 " I_POSTTRANSFORMMATRICES"[64];\n" - "\tfloat4 " I_DEPTHPARAMS";\n" - "};\n"); + out.Write(s_shader_uniforms); + out.Write("};\n"); } if (g_ActiveConfig.backend_info.bSupportsBBox) @@ -275,6 +267,8 @@ static inline void GeneratePixelShader(T& out, DSTALPHA_MODE dstAlphaMode, API_T ); } + GenerateVSOutputStruct(out, ApiType); + const bool forced_early_z = g_ActiveConfig.backend_info.bSupportsEarlyZ && bpmem.UseEarlyDepthTest() && (g_ActiveConfig.bFastDepthCalc || bpmem.alpha_test.TestResult() == AlphaTest::UNDETERMINED); const bool per_pixel_depth = (bpmem.ztex2.op != ZTEXTURE_DISABLE && bpmem.UseLateDepthTest()) || (!g_ActiveConfig.bFastDepthCalc && bpmem.zmode.testenable && !forced_early_z); @@ -325,22 +319,52 @@ static inline void GeneratePixelShader(T& out, DSTALPHA_MODE dstAlphaMode, API_T // As a workaround, we interpolate at the centroid of the coveraged pixel, which // is always inside the primitive. // Without MSAA, this flag is defined to have no effect. - out.Write("centroid in float4 colors_02;\n"); - out.Write("centroid in float4 colors_12;\n"); - - // compute window position if needed because binding semantic WPOS is not widely supported - // Let's set up attributes - for (unsigned int i = 0; i < numTexgen; ++i) + uid_data->stereo = g_ActiveConfig.iStereoMode > 0; + if (g_ActiveConfig.iStereoMode > 0) { - out.Write("centroid in float3 uv%d;\n", i); + out.Write("centroid in VS_OUTPUT f;\n"); + out.Write("flat in int layer;\n"); } - out.Write("centroid in float4 clipPos;\n"); - if (g_ActiveConfig.bEnablePixelLighting) + else { - out.Write("centroid in float4 Normal;\n"); + out.Write("centroid in float4 colors_02;\n"); + out.Write("centroid in float4 colors_12;\n"); + + // compute window position if needed because binding semantic WPOS is not widely supported + // Let's set up attributes + for (unsigned int i = 0; i < numTexgen; ++i) + { + out.Write("centroid in float3 uv%d;\n", i); + } + out.Write("centroid in float4 clipPos;\n"); + if (g_ActiveConfig.bEnablePixelLighting) + { + out.Write("centroid in float4 Normal;\n"); + } } out.Write("void main()\n{\n"); + + if (g_ActiveConfig.iStereoMode > 0) + { + // compute window position if needed because binding semantic WPOS is not widely supported + // Let's set up attributes + for (unsigned int i = 0; i < numTexgen; ++i) + { + out.Write("\tfloat3 uv%d = f.tex%d;\n", i, i); + } + out.Write("\tfloat4 clipPos = f.clipPos;\n"); + if (g_ActiveConfig.bEnablePixelLighting) + { + out.Write("\tfloat4 Normal = f.Normal;\n"); + } + } + + // On Mali, global variables must be initialized as constants. + // This is why we initialize these variables locally instead. + out.Write("\tfloat4 colors_0 = %s;\n", (g_ActiveConfig.iStereoMode > 0) ? "f.colors_0" : "colors_02"); + out.Write("\tfloat4 colors_1 = %s;\n", (g_ActiveConfig.iStereoMode > 0) ? "f.colors_1" : "colors_12"); + out.Write("\tfloat4 rawpos = gl_FragCoord;\n"); } else // D3D @@ -370,14 +394,6 @@ static inline void GeneratePixelShader(T& out, DSTALPHA_MODE dstAlphaMode, API_T "\tint2 wrappedcoord=int2(0,0), tempcoord=int2(0,0);\n" "\tint4 tevin_a=int4(0,0,0,0),tevin_b=int4(0,0,0,0),tevin_c=int4(0,0,0,0),tevin_d=int4(0,0,0,0);\n\n"); // tev combiner inputs - if (ApiType == API_OPENGL) - { - // On Mali, global variables must be initialized as constants. - // This is why we initialize these variables locally instead. - out.Write("\tfloat4 colors_0 = colors_02;\n"); - out.Write("\tfloat4 colors_1 = colors_12;\n"); - } - if (g_ActiveConfig.bEnablePixelLighting) { out.Write("\tfloat3 _norm0 = normalize(Normal.xyz);\n\n"); @@ -931,7 +947,7 @@ static inline void SampleTexture(T& out, const char *texcoords, const char *texs if (ApiType == API_D3D) out.Write("iround(255.0 * Tex%d.Sample(samp%d,%s.xy * " I_TEXDIMS"[%d].xy)).%s;\n", texmap,texmap, texcoords, texmap, texswap); else - out.Write("iround(255.0 * texture(samp%d,%s.xy * " I_TEXDIMS"[%d].xy)).%s;\n", texmap, texcoords, texmap, texswap); + out.Write("iround(255.0 * texture(samp%d, float3(%s.xy * " I_TEXDIMS"[%d].xy, %s))).%s;\n", texmap, texcoords, texmap, g_ActiveConfig.iStereoMode > 0 ? "layer" : "0.0", texswap); } static const char *tevAlphaFuncsTable[] = diff --git a/Source/Core/VideoCommon/PixelShaderGen.h b/Source/Core/VideoCommon/PixelShaderGen.h index 369f94061901..784523087aa5 100644 --- a/Source/Core/VideoCommon/PixelShaderGen.h +++ b/Source/Core/VideoCommon/PixelShaderGen.h @@ -44,7 +44,7 @@ struct pixel_shader_uid_data u32 dstAlphaMode : 2; u32 Pretest : 2; u32 nIndirectStagesUsed : 4; - u32 pad0 : 1; + u32 stereo : 1; u32 genMode_numtexgens : 4; u32 genMode_numtevstages : 4; diff --git a/Source/Core/VideoCommon/PostProcessing.h b/Source/Core/VideoCommon/PostProcessing.h index 68ab47cff6cf..7c4f94221462 100644 --- a/Source/Core/VideoCommon/PostProcessing.h +++ b/Source/Core/VideoCommon/PostProcessing.h @@ -91,7 +91,7 @@ class PostProcessingShaderImplementation // Should be implemented by the backends for backend specific code virtual void BlitFromTexture(TargetRectangle src, TargetRectangle dst, - int src_texture, int src_width, int src_height) = 0; + int src_texture, int src_width, int src_height, int layer = 0) = 0; virtual void ApplyShader() = 0; protected: diff --git a/Source/Core/VideoCommon/RenderBase.cpp b/Source/Core/VideoCommon/RenderBase.cpp index d93d43f4905e..84c037c75336 100644 --- a/Source/Core/VideoCommon/RenderBase.cpp +++ b/Source/Core/VideoCommon/RenderBase.cpp @@ -240,6 +240,42 @@ bool Renderer::CalculateTargetSize(unsigned int framebuffer_width, unsigned int return false; } +void Renderer::ConvertStereoRectangle(const TargetRectangle& rc, TargetRectangle& leftRc, TargetRectangle& rightRc) +{ + // Resize target to half its original size + TargetRectangle drawRc = rc; + if (g_ActiveConfig.iStereoMode == STEREO_TAB) + { + // The height may be negative due to flipped rectangles + int height = rc.bottom - rc.top; + drawRc.top += height / 4; + drawRc.bottom -= height / 4; + } + else + { + int width = rc.right - rc.left; + drawRc.left += width / 4; + drawRc.right -= width / 4; + } + + // Create two target rectangle offset to the sides of the backbuffer + leftRc = drawRc, rightRc = drawRc; + if (g_ActiveConfig.iStereoMode == STEREO_TAB) + { + leftRc.top -= s_backbuffer_height / 4; + leftRc.bottom -= s_backbuffer_height / 4; + rightRc.top += s_backbuffer_height / 4; + rightRc.bottom += s_backbuffer_height / 4; + } + else + { + leftRc.left -= s_backbuffer_width / 4; + leftRc.right -= s_backbuffer_width / 4; + rightRc.left += s_backbuffer_width / 4; + rightRc.right += s_backbuffer_width / 4; + } +} + void Renderer::SetScreenshot(const std::string& filename) { std::lock_guard lk(s_criticalScreenshot); diff --git a/Source/Core/VideoCommon/RenderBase.h b/Source/Core/VideoCommon/RenderBase.h index 746f75366cd0..f59458e5239e 100644 --- a/Source/Core/VideoCommon/RenderBase.h +++ b/Source/Core/VideoCommon/RenderBase.h @@ -83,6 +83,8 @@ class Renderer static const TargetRectangle& GetTargetRectangle() { return target_rc; } static void UpdateDrawRectangle(int backbuffer_width, int backbuffer_height); + // Use this to convert a single target rectangle to two stereo rectangles + static void ConvertStereoRectangle(const TargetRectangle& rc, TargetRectangle& leftRc, TargetRectangle& rightRc); // Use this to upscale native EFB coordinates to IDEAL internal resolution static int EFBToScaledX(int x); diff --git a/Source/Core/VideoCommon/ShaderGenCommon.h b/Source/Core/VideoCommon/ShaderGenCommon.h index 54adb1ba17fc..330b9f8cedd5 100644 --- a/Source/Core/VideoCommon/ShaderGenCommon.h +++ b/Source/Core/VideoCommon/ShaderGenCommon.h @@ -239,3 +239,16 @@ class UidChecker #define I_NORMALMATRICES "cnmtx" #define I_POSTTRANSFORMMATRICES "cpostmtx" #define I_DEPTHPARAMS "cDepth" // farZ, zRange +#define I_STEREOPARAMS "cstereo" + +static const char s_shader_uniforms[] = + "\tfloat4 " I_POSNORMALMATRIX"[6];\n" + "\tfloat4 " I_PROJECTION"[4];\n" + "\tint4 " I_MATERIALS"[4];\n" + "\tLight " I_LIGHTS"[8];\n" + "\tfloat4 " I_TEXMATRICES"[24];\n" + "\tfloat4 " I_TRANSFORMMATRICES"[64];\n" + "\tfloat4 " I_NORMALMATRICES"[32];\n" + "\tfloat4 " I_POSTTRANSFORMMATRICES"[64];\n" + "\tfloat4 " I_DEPTHPARAMS";\n" + "\tfloat4 " I_STEREOPARAMS";\n"; diff --git a/Source/Core/VideoCommon/TextureCacheBase.cpp b/Source/Core/VideoCommon/TextureCacheBase.cpp index 0618b3f1b9f4..7fa540022de9 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.cpp +++ b/Source/Core/VideoCommon/TextureCacheBase.cpp @@ -115,6 +115,13 @@ void TextureCache::OnConfigChanged(VideoConfig& config) { g_texture_cache->ClearRenderTargets(); } + + if ((config.iStereoMode > 0) != backup_config.s_stereo_3d || + config.bStereoMonoEFBDepth != backup_config.s_mono_efb_depth) + { + g_texture_cache->DeleteShaders(); + g_texture_cache->CompileShaders(); + } } backup_config.s_colorsamples = config.iSafeTextureCache_ColorSamples; @@ -126,6 +133,8 @@ void TextureCache::OnConfigChanged(VideoConfig& config) backup_config.s_texfmt_overlay_center = config.bTexFmtOverlayCenter; backup_config.s_hires_textures = config.bHiresTextures; backup_config.s_copy_cache_enable = config.bEFBCopyCacheEnable; + backup_config.s_stereo_3d = config.iStereoMode > 0; + backup_config.s_mono_efb_depth = config.bStereoMonoEFBDepth; } void TextureCache::Cleanup() @@ -444,14 +453,15 @@ TextureCache::TCacheEntryBase* TextureCache::Load(unsigned int const stage, // // TODO: Don't we need to force texture decoding to RGBA8 for dynamic EFB copies? // TODO: Actually, it should be enough if the internal texture format matches... - if ((entry->type == TCET_NORMAL && + if (((entry->type == TCET_NORMAL && width == entry->virtual_width && height == entry->virtual_height && full_format == entry->format && entry->num_mipmaps > maxlevel) || (entry->type == TCET_EC_DYNAMIC && entry->native_width == width && - entry->native_height == height)) + entry->native_height == height)) && + entry->num_layers == 1) { // reuse the texture } @@ -519,6 +529,7 @@ TextureCache::TCacheEntryBase* TextureCache::Load(unsigned int const stage, // But that will currently make the above "existing entry" tests fail as "texLevels" is not calculated until after. // Currently, we might try to reuse a texture which appears to have more levels than actual, maybe.. entry->num_mipmaps = maxlevel + 1; + entry->num_layers = 1; entry->type = TCET_NORMAL; GFX_DEBUGGER_PAUSE_AT(NEXT_NEW_TEXTURE, true); @@ -529,7 +540,7 @@ TextureCache::TCacheEntryBase* TextureCache::Load(unsigned int const stage, entry->Load(width, height, expandedWidth, 0); } - entry->SetGeneralParameters(address, texture_size, full_format, entry->num_mipmaps); + entry->SetGeneralParameters(address, texture_size, full_format, entry->num_mipmaps, entry->num_layers); entry->SetDimensions(nativeW, nativeH, width, height); entry->hash = tex_hash; @@ -886,12 +897,12 @@ void TextureCache::CopyRenderTargetToTexture(u32 dstAddr, unsigned int dstFormat TCacheEntryBase *entry = textures[dstAddr]; if (entry) { - if (entry->type == TCET_EC_DYNAMIC && entry->native_width == tex_w && entry->native_height == tex_h) + if (entry->type == TCET_EC_DYNAMIC && entry->native_width == tex_w && entry->native_height == tex_h && entry->num_layers == FramebufferManagerBase::GetEFBLayers()) { scaled_tex_w = tex_w; scaled_tex_h = tex_h; } - else if (!(entry->type == TCET_EC_VRAM && entry->virtual_width == scaled_tex_w && entry->virtual_height == scaled_tex_h)) + else if (!(entry->type == TCET_EC_VRAM && entry->virtual_width == scaled_tex_w && entry->virtual_height == scaled_tex_h && entry->num_layers == FramebufferManagerBase::GetEFBLayers())) { if (entry->type == TCET_EC_VRAM) { @@ -914,7 +925,7 @@ void TextureCache::CopyRenderTargetToTexture(u32 dstAddr, unsigned int dstFormat textures[dstAddr] = entry = AllocateRenderTarget(scaled_tex_w, scaled_tex_h); // TODO: Using the wrong dstFormat, dumb... - entry->SetGeneralParameters(dstAddr, 0, dstFormat, 1); + entry->SetGeneralParameters(dstAddr, 0, dstFormat, 1, FramebufferManagerBase::GetEFBLayers()); entry->SetDimensions(tex_w, tex_h, scaled_tex_w, scaled_tex_h); entry->SetHashes(TEXHASH_INVALID); entry->type = TCET_EC_VRAM; diff --git a/Source/Core/VideoCommon/TextureCacheBase.h b/Source/Core/VideoCommon/TextureCacheBase.h index fbe13915fafe..d9cd2b8c16b1 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.h +++ b/Source/Core/VideoCommon/TextureCacheBase.h @@ -39,6 +39,7 @@ class TextureCache enum TexCacheEntryType type; unsigned int num_mipmaps; + unsigned int num_layers; unsigned int native_width, native_height; // Texture dimensions from the GameCube's point of view unsigned int virtual_width, virtual_height; // Texture dimensions from OUR point of view - for hires textures or scaled EFB copies @@ -46,12 +47,13 @@ class TextureCache int frameCount; - void SetGeneralParameters(u32 _addr, u32 _size, u32 _format, unsigned int _num_mipmaps) + void SetGeneralParameters(u32 _addr, u32 _size, u32 _format, unsigned int _num_mipmaps, unsigned int _num_layers) { addr = _addr; size_in_bytes = _size; format = _format; num_mipmaps = _num_mipmaps; + num_layers = _num_layers; } void SetDimensions(unsigned int _native_width, unsigned int _native_height, unsigned int _virtual_width, unsigned int _virtual_height) @@ -101,6 +103,9 @@ class TextureCache unsigned int expanded_width, unsigned int tex_levels, PC_TexFormat pcfmt) = 0; virtual TCacheEntryBase* CreateRenderTargetTexture(unsigned int scaled_tex_w, unsigned int scaled_tex_h) = 0; + virtual void CompileShaders() = 0; // currently only implemented by OGL + virtual void DeleteShaders() = 0; // currently only implemented by OGL + static TCacheEntryBase* Load(unsigned int stage, u32 address, unsigned int width, unsigned int height, int format, unsigned int tlutaddr, int tlutfmt, bool use_mipmaps, unsigned int maxlevel, bool from_tmem); static void CopyRenderTargetToTexture(u32 dstAddr, unsigned int dstFormat, PEControl::PixelFormat srcFormat, @@ -140,6 +145,8 @@ class TextureCache bool s_texfmt_overlay_center; bool s_hires_textures; bool s_copy_cache_enable; + bool s_stereo_3d; + bool s_mono_efb_depth; } backup_config; }; diff --git a/Source/Core/VideoCommon/TextureConversionShader.cpp b/Source/Core/VideoCommon/TextureConversionShader.cpp index b0215e4c08d6..1ab973d11b61 100644 --- a/Source/Core/VideoCommon/TextureConversionShader.cpp +++ b/Source/Core/VideoCommon/TextureConversionShader.cpp @@ -70,7 +70,7 @@ static void WriteSwizzler(char*& p, u32 format, API_TYPE ApiType) if (ApiType == API_OPENGL) { WRITE(p, "#define samp0 samp9\n"); - WRITE(p, "SAMPLER_BINDING(9) uniform sampler2D samp0;\n"); + WRITE(p, "SAMPLER_BINDING(9) uniform sampler2DArray samp0;\n"); WRITE(p, " out vec4 ocol0;\n"); WRITE(p, "void main()\n"); @@ -120,7 +120,7 @@ static void WriteSwizzler(char*& p, u32 format, API_TYPE ApiType) static void WriteSampleColor(char*& p, const char* colorComp, const char* dest, int xoffset, API_TYPE ApiType) { - WRITE(p, " %s = texture(samp0, uv0 + float2(%d, 0) * sample_offset).%s;\n", + WRITE(p, " %s = texture(samp0, float3(uv0 + float2(%d, 0) * sample_offset, 0.0)).%s;\n", dest, xoffset, colorComp ); } diff --git a/Source/Core/VideoCommon/VertexShaderGen.cpp b/Source/Core/VideoCommon/VertexShaderGen.cpp index 894a18a0e242..24c6f39f467b 100644 --- a/Source/Core/VideoCommon/VertexShaderGen.cpp +++ b/Source/Core/VideoCommon/VertexShaderGen.cpp @@ -89,17 +89,8 @@ static inline void GenerateVertexShader(T& out, u32 components, API_TYPE api_typ out.Write("layout(std140%s) uniform VSBlock {\n", g_ActiveConfig.backend_info.bSupportsBindingLayout ? ", binding = 2" : ""); else out.Write("cbuffer VSBlock {\n"); - out.Write( - "\tfloat4 " I_POSNORMALMATRIX"[6];\n" - "\tfloat4 " I_PROJECTION"[4];\n" - "\tint4 " I_MATERIALS"[4];\n" - "\tLight " I_LIGHTS"[8];\n" - "\tfloat4 " I_TEXMATRICES"[24];\n" - "\tfloat4 " I_TRANSFORMMATRICES"[64];\n" - "\tfloat4 " I_NORMALMATRICES"[32];\n" - "\tfloat4 " I_POSTTRANSFORMMATRICES"[64];\n" - "\tfloat4 " I_DEPTHPARAMS";\n" - "};\n"); + out.Write(s_shader_uniforms); + out.Write("};\n"); GenerateVSOutputStruct(out, api_type); @@ -131,22 +122,33 @@ static inline void GenerateVertexShader(T& out, u32 components, API_TYPE api_typ out.Write("in float%d tex%d; // ATTR%d,\n", hastexmtx ? 3 : 2, i, SHADER_TEXTURE0_ATTRIB + i); } - // Let's set up attributes - for (size_t i = 0; i < 8; ++i) + uid_data->stereo = g_ActiveConfig.iStereoMode > 0; + if (g_ActiveConfig.iStereoMode > 0) { - if (i < xfmem.numTexGen.numTexGens) + out.Write("centroid out VS_OUTPUT o;\n"); + } + else + { + // Let's set up attributes + for (size_t i = 0; i < 8; ++i) { - out.Write("centroid out float3 uv%d;\n", i); + if (i < xfmem.numTexGen.numTexGens) + { + out.Write("centroid out float3 uv%d;\n", i); + } } - } - out.Write("centroid out float4 clipPos;\n"); - if (g_ActiveConfig.bEnablePixelLighting) - out.Write("centroid out float4 Normal;\n"); - out.Write("centroid out float4 colors_02;\n"); - out.Write("centroid out float4 colors_12;\n"); + out.Write("centroid out float4 clipPos;\n"); + if (g_ActiveConfig.bEnablePixelLighting) + out.Write("centroid out float4 Normal;\n"); + out.Write("centroid out float4 colors_02;\n"); + out.Write("centroid out float4 colors_12;\n"); + } out.Write("void main()\n{\n"); + + if (g_ActiveConfig.iStereoMode <= 0) + out.Write("VS_OUTPUT o;\n"); } else // D3D { @@ -172,8 +174,9 @@ static inline void GenerateVertexShader(T& out, u32 components, API_TYPE api_typ if (components & VB_HAS_POSMTXIDX) out.Write(" int posmtx : BLENDINDICES,\n"); out.Write(" float4 rawpos : POSITION) {\n"); + + out.Write("VS_OUTPUT o;\n"); } - out.Write("VS_OUTPUT o;\n"); // transforms if (components & VB_HAS_POSMTXIDX) @@ -431,27 +434,32 @@ static inline void GenerateVertexShader(T& out, u32 components, API_TYPE api_typ if (api_type == API_OPENGL) { - // Bit ugly here - // TODO: Make pretty - // Will look better when we bind uniforms in GLSL 1.3 - // clipPos/w needs to be done in pixel shader, not here + if (g_ActiveConfig.iStereoMode <= 0) + { + // Bit ugly here + // TODO: Make pretty + // Will look better when we bind uniforms in GLSL 1.3 + // clipPos/w needs to be done in pixel shader, not here + + for (unsigned int i = 0; i < xfmem.numTexGen.numTexGens; ++i) + out.Write("uv%d.xyz = o.tex%d;\n", i, i); - for (unsigned int i = 0; i < xfmem.numTexGen.numTexGens; ++i) - out.Write(" uv%d.xyz = o.tex%d;\n", i, i); - out.Write(" clipPos = o.clipPos;\n"); + out.Write("clipPos = o.clipPos;\n"); - if (g_ActiveConfig.bEnablePixelLighting) - out.Write(" Normal = o.Normal;\n"); + if (g_ActiveConfig.bEnablePixelLighting) + out.Write("Normal = o.Normal;\n"); + + out.Write("colors_02 = o.colors_0;\n"); + out.Write("colors_12 = o.colors_1;\n"); + } - out.Write("colors_02 = o.colors_0;\n"); - out.Write("colors_12 = o.colors_1;\n"); out.Write("gl_Position = o.pos;\n"); - out.Write("}\n"); } else // D3D { - out.Write("return o;\n}\n"); + out.Write("return o;\n"); } + out.Write("}\n"); if (is_writing_shadercode) { @@ -475,7 +483,12 @@ void GenerateVertexShaderCode(VertexShaderCode& object, u32 components, API_TYPE GenerateVertexShader(object, components, api_type); } -void GenerateVSOutputStructForGS(ShaderCode& object, API_TYPE api_type) +void GenerateVSOutputStruct(ShaderCode& object, API_TYPE api_type) { GenerateVSOutputStruct(object, api_type); } + +void GenerateVSOutputStruct(ShaderGeneratorInterface& object, API_TYPE api_type) +{ + // Ignore unknown types +} diff --git a/Source/Core/VideoCommon/VertexShaderGen.h b/Source/Core/VideoCommon/VertexShaderGen.h index 998f8fe6bccd..b5e9e33f29b4 100644 --- a/Source/Core/VideoCommon/VertexShaderGen.h +++ b/Source/Core/VideoCommon/VertexShaderGen.h @@ -38,7 +38,7 @@ struct vertex_shader_uid_data u32 numColorChans : 2; u32 dualTexTrans_enabled : 1; u32 pixel_lighting : 1; - u32 pad0 : 1; + u32 stereo : 1; u32 texMtxInfo_n_projection : 16; // Stored separately to guarantee that the texMtxInfo struct is 8 bits wide struct { @@ -64,4 +64,5 @@ typedef ShaderCode VertexShaderCode; // TODO: Obsolete.. void GetVertexShaderUid(VertexShaderUid& object, u32 components, API_TYPE api_type); void GenerateVertexShaderCode(VertexShaderCode& object, u32 components, API_TYPE api_type); -void GenerateVSOutputStructForGS(ShaderCode& object, API_TYPE api_type); +void GenerateVSOutputStruct(ShaderCode& object, API_TYPE api_type); +void GenerateVSOutputStruct(ShaderGeneratorInterface& object, API_TYPE api_type); diff --git a/Source/Core/VideoCommon/VertexShaderManager.cpp b/Source/Core/VideoCommon/VertexShaderManager.cpp index 93f969b0b584..8de50c543673 100644 --- a/Source/Core/VideoCommon/VertexShaderManager.cpp +++ b/Source/Core/VideoCommon/VertexShaderManager.cpp @@ -489,7 +489,7 @@ void VertexShaderManager::SetConstants() PRIM_LOG("Projection: %f %f %f %f %f %f\n", rawProjection[0], rawProjection[1], rawProjection[2], rawProjection[3], rawProjection[4], rawProjection[5]); - if ((g_ActiveConfig.bFreeLook || g_ActiveConfig.bAnaglyphStereo ) && xfmem.projection.type == GX_PERSPECTIVE) + if (g_ActiveConfig.bFreeLook && xfmem.projection.type == GX_PERSPECTIVE) { Matrix44 mtxA; Matrix44 mtxB; @@ -512,6 +512,19 @@ void VertexShaderManager::SetConstants() Matrix44::Multiply(s_viewportCorrection, projMtx, correctedMtx); memcpy(constants.projection, correctedMtx.data, 4*16); } + + if (g_ActiveConfig.iStereoMode > 0 && xfmem.projection.type == GX_PERSPECTIVE) + { + float offset = (g_ActiveConfig.iStereoSeparation / 1000.0f) * (g_ActiveConfig.iStereoSeparationPercent / 100.0f); + constants.stereoparams[0] = (g_ActiveConfig.bStereoSwapEyes) ? offset : -offset; + constants.stereoparams[1] = (g_ActiveConfig.bStereoSwapEyes) ? -offset : offset; + constants.stereoparams[2] = (g_ActiveConfig.iStereoConvergence / 10.0f) * (g_ActiveConfig.iStereoConvergencePercent / 100.0f); + } + else + { + constants.stereoparams[0] = constants.stereoparams[1] = 0; + } + dirty = true; } } diff --git a/Source/Core/VideoCommon/VideoCommon.vcxproj b/Source/Core/VideoCommon/VideoCommon.vcxproj index bda73f396ca2..31dc0897367e 100644 --- a/Source/Core/VideoCommon/VideoCommon.vcxproj +++ b/Source/Core/VideoCommon/VideoCommon.vcxproj @@ -60,6 +60,7 @@ + @@ -110,6 +111,7 @@ + diff --git a/Source/Core/VideoCommon/VideoCommon.vcxproj.filters b/Source/Core/VideoCommon/VideoCommon.vcxproj.filters index c662422200ce..f285ce35d4e3 100644 --- a/Source/Core/VideoCommon/VideoCommon.vcxproj.filters +++ b/Source/Core/VideoCommon/VideoCommon.vcxproj.filters @@ -143,6 +143,9 @@ Util + + Shader Generators + @@ -275,6 +278,9 @@ Util + + Shader Generators + diff --git a/Source/Core/VideoCommon/VideoConfig.cpp b/Source/Core/VideoCommon/VideoConfig.cpp index df72df9be267..2b6025f13e66 100644 --- a/Source/Core/VideoCommon/VideoConfig.cpp +++ b/Source/Core/VideoCommon/VideoConfig.cpp @@ -38,6 +38,11 @@ VideoConfig::VideoConfig() backend_info.APIType = API_NONE; backend_info.bUseMinimalMipCount = false; backend_info.bSupportsExclusiveFullscreen = false; + + // Game-specific stereoscopy settings + bStereoMonoEFBDepth = false; + iStereoSeparationPercent = 100; + iStereoConvergencePercent = 100; } void VideoConfig::Load(const std::string& ini_file) @@ -66,9 +71,6 @@ void VideoConfig::Load(const std::string& ini_file) settings->Get("DumpEFBTarget", &bDumpEFBTarget, 0); settings->Get("FreeLook", &bFreeLook, 0); settings->Get("UseFFV1", &bUseFFV1, 0); - settings->Get("AnaglyphStereo", &bAnaglyphStereo, false); - settings->Get("AnaglyphStereoSeparation", &iAnaglyphStereoSeparation, 200); - settings->Get("AnaglyphFocalAngle", &iAnaglyphFocalAngle, 0); settings->Get("EnablePixelLighting", &bEnablePixelLighting, 0); settings->Get("FastDepthCalc", &bFastDepthCalc, true); settings->Get("MSAA", &iMultisampleMode, 0); @@ -85,6 +87,10 @@ void VideoConfig::Load(const std::string& ini_file) enhancements->Get("ForceFiltering", &bForceFiltering, 0); enhancements->Get("MaxAnisotropy", &iMaxAnisotropy, 0); // NOTE - this is x in (1 << x) enhancements->Get("PostProcessingShader", &sPostProcessingShader, ""); + enhancements->Get("StereoMode", &iStereoMode, 0); + enhancements->Get("StereoSeparation", &iStereoSeparation, 20); + enhancements->Get("StereoConvergence", &iStereoConvergence, 20); + enhancements->Get("StereoSwapEyes", &bStereoSwapEyes, false); IniFile::Section* hacks = iniFile.GetOrCreateSection("Hacks"); hacks->Get("EFBAccessEnable", &bEFBAccessEnable, true); @@ -140,9 +146,6 @@ void VideoConfig::GameIniLoad() CHECK_SETTING("Video_Settings", "UseRealXFB", bUseRealXFB); CHECK_SETTING("Video_Settings", "SafeTextureCacheColorSamples", iSafeTextureCache_ColorSamples); CHECK_SETTING("Video_Settings", "HiresTextures", bHiresTextures); - CHECK_SETTING("Video_Settings", "AnaglyphStereo", bAnaglyphStereo); - CHECK_SETTING("Video_Settings", "AnaglyphStereoSeparation", iAnaglyphStereoSeparation); - CHECK_SETTING("Video_Settings", "AnaglyphFocalAngle", iAnaglyphFocalAngle); CHECK_SETTING("Video_Settings", "EnablePixelLighting", bEnablePixelLighting); CHECK_SETTING("Video_Settings", "FastDepthCalc", bFastDepthCalc); CHECK_SETTING("Video_Settings", "MSAA", iMultisampleMode); @@ -179,6 +182,14 @@ void VideoConfig::GameIniLoad() CHECK_SETTING("Video_Enhancements", "ForceFiltering", bForceFiltering); CHECK_SETTING("Video_Enhancements", "MaxAnisotropy", iMaxAnisotropy); // NOTE - this is x in (1 << x) CHECK_SETTING("Video_Enhancements", "PostProcessingShader", sPostProcessingShader); + CHECK_SETTING("Video_Enhancements", "StereoMode", iStereoMode); + CHECK_SETTING("Video_Enhancements", "StereoSeparation", iStereoSeparation); + CHECK_SETTING("Video_Enhancements", "StereoConvergence", iStereoConvergence); + CHECK_SETTING("Video_Enhancements", "StereoSwapEyes", bStereoSwapEyes); + + CHECK_SETTING("Video_Stereoscopy", "StereoMonoEFBDepth", bStereoMonoEFBDepth); + CHECK_SETTING("Video_Stereoscopy", "StereoSeparationPercent", iStereoSeparationPercent); + CHECK_SETTING("Video_Stereoscopy", "StereoConvergencePercent", iStereoConvergencePercent); CHECK_SETTING("Video_Hacks", "EFBAccessEnable", bEFBAccessEnable); CHECK_SETTING("Video_Hacks", "EFBCopyEnable", bEFBCopyEnable); @@ -203,6 +214,7 @@ void VideoConfig::VerifyValidity() // TODO: Check iMaxAnisotropy value if (iAdapter < 0 || iAdapter > ((int)backend_info.Adapters.size() - 1)) iAdapter = 0; if (iMultisampleMode < 0 || iMultisampleMode >= (int)backend_info.AAModes.size()) iMultisampleMode = 0; + if (!backend_info.bSupportsStereoscopy) iStereoMode = 0; } void VideoConfig::Save(const std::string& ini_file) @@ -230,9 +242,6 @@ void VideoConfig::Save(const std::string& ini_file) settings->Set("DumpEFBTarget", bDumpEFBTarget); settings->Set("FreeLook", bFreeLook); settings->Set("UseFFV1", bUseFFV1); - settings->Set("AnaglyphStereo", bAnaglyphStereo); - settings->Set("AnaglyphStereoSeparation", iAnaglyphStereoSeparation); - settings->Set("AnaglyphFocalAngle", iAnaglyphFocalAngle); settings->Set("EnablePixelLighting", bEnablePixelLighting); settings->Set("FastDepthCalc", bFastDepthCalc); settings->Set("ShowEFBCopyRegions", bShowEFBCopyRegions); @@ -250,6 +259,10 @@ void VideoConfig::Save(const std::string& ini_file) enhancements->Set("ForceFiltering", bForceFiltering); enhancements->Set("MaxAnisotropy", iMaxAnisotropy); enhancements->Set("PostProcessingShader", sPostProcessingShader); + enhancements->Set("StereoMode", iStereoMode); + enhancements->Set("StereoSeparation", iStereoSeparation); + enhancements->Set("StereoConvergence", iStereoConvergence); + enhancements->Set("StereoSwapEyes", bStereoSwapEyes); IniFile::Section* hacks = iniFile.GetOrCreateSection("Hacks"); hacks->Set("EFBAccessEnable", bEFBAccessEnable); diff --git a/Source/Core/VideoCommon/VideoConfig.h b/Source/Core/VideoCommon/VideoConfig.h index 44b074072833..c825f8ec53e6 100644 --- a/Source/Core/VideoCommon/VideoConfig.h +++ b/Source/Core/VideoCommon/VideoConfig.h @@ -44,6 +44,14 @@ enum EFBScale SCALE_4X, }; +enum StereoMode +{ + STEREO_OFF = 0, + STEREO_SBS, + STEREO_TAB, + STEREO_ANAGLYPH +}; + // NEVER inherit from this class. struct VideoConfig final { @@ -71,6 +79,10 @@ struct VideoConfig final bool bForceFiltering; int iMaxAnisotropy; std::string sPostProcessingShader; + int iStereoMode; + int iStereoSeparation; + int iStereoConvergence; + bool bStereoSwapEyes; // Information bool bShowFPS; @@ -92,9 +104,6 @@ struct VideoConfig final bool bDumpEFBTarget; bool bUseFFV1; bool bFreeLook; - bool bAnaglyphStereo; - int iAnaglyphStereoSeparation; - int iAnaglyphFocalAngle; bool bBorderlessFullscreen; // Hacks @@ -115,6 +124,11 @@ struct VideoConfig final int iLog; // CONF_ bits int iSaveTargetId; // TODO: Should be dropped + // Stereoscopy + bool bStereoMonoEFBDepth; + int iStereoSeparationPercent; + int iStereoConvergencePercent; + // D3D only config, mostly to be merged into the above int iAdapter; @@ -136,9 +150,11 @@ struct VideoConfig final bool bSupportsDualSourceBlend; bool bSupportsPrimitiveRestart; bool bSupportsOversizedViewports; + bool bSupportsStereoscopy; bool bSupportsEarlyZ; // needed by PixelShaderGen, so must stay in VideoCommon bool bSupportsBindingLayout; // Needed by ShaderGen, so must stay in VideoCommon bool bSupportsBBox; + bool bSupportsGSInstancing; // Needed by GeometryShaderGen, so must stay in VideoCommon } backend_info; // Utility