@@ -4,11 +4,13 @@

#pragma once

#include <array>
#include "DolphinQt2/Config/Graphics/GraphicsWidget.h"

class GraphicsWindow;
class QCheckBox;
class QComboBox;
class QRadioButton;
class QGridLayout;

namespace X11Utils
@@ -52,6 +54,7 @@ class GeneralWidget final : public GraphicsWidget
QCheckBox* m_keep_window_top;
QCheckBox* m_hide_cursor;
QCheckBox* m_render_main_window;
std::array<QRadioButton*, 4> m_shader_compilation_mode{};
QCheckBox* m_wait_for_shaders;

X11Utils::XRRConfiguration* m_xrr_config;
@@ -0,0 +1,24 @@
// Copyright 2018 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include "DolphinQt2/Config/Graphics/GraphicsRadio.h"

#include "Common/Config/Config.h"
#include "DolphinQt2/Settings.h"

GraphicsRadioInt::GraphicsRadioInt(const QString& label, const Config::ConfigInfo<int>& setting,
int value)
: QRadioButton(label), m_setting(setting), m_value(value)
{
setChecked(Config::Get(m_setting) == m_value);
connect(this, &QRadioButton::toggled, this, &GraphicsRadioInt::Update);
}

void GraphicsRadioInt::Update()
{
if (!isChecked())
return;

Config::SetBaseOrCurrent(m_setting, m_value);
}
@@ -0,0 +1,26 @@
// Copyright 2018 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#pragma once

#include <QRadioButton>

namespace Config
{
template <typename T>
struct ConfigInfo;
}

class GraphicsRadioInt : public QRadioButton
{
Q_OBJECT
public:
GraphicsRadioInt(const QString& label, const Config::ConfigInfo<int>& setting, int value);

private:
void Update();

const Config::ConfigInfo<int>& m_setting;
int m_value;
};
@@ -79,6 +79,7 @@
<QtMoc Include="Config\Graphics\GeneralWidget.h" />
<QtMoc Include="Config\Graphics\GraphicsBool.h" />
<QtMoc Include="Config\Graphics\GraphicsChoice.h" />
<QtMoc Include="Config\Graphics\GraphicsRadio.h" />
<QtMoc Include="Config\Graphics\GraphicsSlider.h" />
<QtMoc Include="Config\Graphics\GraphicsWidget.h" />
<QtMoc Include="Config\Graphics\GraphicsWindow.h" />
@@ -161,6 +162,7 @@
<ClCompile Include="$(QtMocOutPrefix)GeneralWidget.cpp" />
<ClCompile Include="$(QtMocOutPrefix)GraphicsBool.cpp" />
<ClCompile Include="$(QtMocOutPrefix)GraphicsChoice.cpp" />
<ClCompile Include="$(QtMocOutPrefix)GraphicsRadio.cpp" />
<ClCompile Include="$(QtMocOutPrefix)GraphicsSlider.cpp" />
<ClCompile Include="$(QtMocOutPrefix)GraphicsWidget.cpp" />
<ClCompile Include="$(QtMocOutPrefix)GraphicsWindow.cpp" />
@@ -212,6 +214,7 @@
<ClCompile Include="Config\Graphics\GeneralWidget.cpp" />
<ClCompile Include="Config\Graphics\GraphicsBool.cpp" />
<ClCompile Include="Config\Graphics\GraphicsChoice.cpp" />
<ClCompile Include="Config\Graphics\GraphicsRadio.cpp" />
<ClCompile Include="Config\Graphics\GraphicsSlider.cpp" />
<ClCompile Include="Config\Graphics\GraphicsWidget.cpp" />
<ClCompile Include="Config\Graphics\GraphicsWindow.cpp" />
@@ -44,7 +44,6 @@

// template instantiation
template class BoolSetting<wxCheckBox>;
template class BoolSetting<wxRadioButton>;

template <>
SettingCheckBox::BoolSetting(wxWindow* parent, const wxString& label, const wxString& tooltip,
@@ -59,19 +58,6 @@ SettingCheckBox::BoolSetting(wxWindow* parent, const wxString& label, const wxSt
Bind(wxEVT_CHECKBOX, &SettingCheckBox::UpdateValue, this);
}

template <>
SettingRadioButton::BoolSetting(wxWindow* parent, const wxString& label, const wxString& tooltip,
const Config::ConfigInfo<bool>& setting, bool reverse, long style)
: wxRadioButton(parent, wxID_ANY, label, wxDefaultPosition, wxDefaultSize, style),
m_setting(setting), m_reverse(reverse)
{
SetToolTip(tooltip);
SetValue(Config::Get(m_setting) ^ m_reverse);
if (Config::GetActiveLayerForConfig(m_setting) != Config::LayerType::Base)
SetFont(GetFont().MakeBold());
Bind(wxEVT_RADIOBUTTON, &SettingRadioButton::UpdateValue, this);
}

template <>
RefBoolSetting<wxCheckBox>::RefBoolSetting(wxWindow* parent, const wxString& label,
const wxString& tooltip, bool& setting, bool reverse,
@@ -311,18 +297,35 @@ static wxString gpu_texture_decoding_desc =
"bottleneck.\n\nIf unsure, leave this unchecked.");
static wxString ubershader_desc =
wxTRANSLATE("Disabled: Ubershaders are never used. Stuttering will occur during shader "
"compilation, but GPU demands are low. Recommended for low-end hardware.\n\n"
"compilation, but GPU demands are low. Recommended for low-end hardware.\n"
"Hybrid: Ubershaders will be used to prevent stuttering during shader "
"compilation, but traditional shaders will be used when they will not cause "
"stuttering. Balances performance and smoothness.\n\n"
"stuttering. Balances performance and smoothness.\n"
"Exclusive: Ubershaders will always be used. Only recommended for high-end "
"systems.");
static wxString wait_for_shaders_desc =
"systems.\n"
"Skip Drawing: Does not draw objects during shader compilation. Reduces "
"stuttering at the cost of missing objects, or broken effects.");
static wxString shader_compile_sync_desc =
wxTRANSLATE("Ubershaders are never used. Stuttering will occur during shader "
"compilation, but GPU demands are low. Recommended for low-end hardware.\n\nIf "
"unsure, select this mode.");
static wxString shader_compile_uber_only_desc =
wxTRANSLATE("Ubershaders will always be used. Provides a near stutter-free experience at the "
"cost of high GPU requirements. Only recommended for high-end systems.");
static wxString shader_compile_async_uber_desc =
wxTRANSLATE("Ubershaders will be used to prevent stuttering during shader compilation, but "
"specialized shaders will be used when they will not cause stuttering.");
static wxString shader_compile_async_skip_desc =
wxTRANSLATE("Instead of using ubershaders during shader compilation, objects which use these "
"shaders will be not be rendered. This can further reduce stuttering and "
"performance requirements, compared to ubershaders, at the cost of introducing "
"visual glitches and broken effects. Not recommended.");
static wxString shader_compile_before_start_desc =
wxTRANSLATE("Waits for all shaders to finish compiling before starting a game. Enabling this "
"option may reduce stuttering or hitching for a short time after the game is "
"started, at the cost of a longer delay before the game starts.\n\nFor systems "
"with two or fewer cores, it is recommended to enable this option, as a large "
"shader queue may reduce frame rates. Otherwise, if unsure, leave this unchecked.");
"started, at the cost of a longer delay before the game starts. For systems with "
"two or fewer cores, it is recommended to enable this option, as a large shader "
"queue may reduce frame rates. Otherwise, if unsure, leave this unchecked.");

VideoConfigDiag::VideoConfigDiag(wxWindow* parent, const std::string& title)
: wxDialog(parent, wxID_ANY, wxString::Format(_("Dolphin %s Graphics Configuration"),
@@ -448,10 +451,29 @@ VideoConfigDiag::VideoConfigDiag(wxWindow* parent, const std::string& title)
wxGetTranslation(backend_multithreading_desc),
Config::GFX_BACKEND_MULTITHREADING));
}
}

szr_other->Add(CreateCheckBox(page_general, _("Immediately Compile Shaders"),
wxGetTranslation(wait_for_shaders_desc),
Config::GFX_WAIT_FOR_SHADERS_BEFORE_STARTING));
// - shader compilation
wxGridBagSizer* const szr_shader_compilation = new wxGridBagSizer(space5, space5);
{
const std::array<std::pair<wxString, wxString>, 4> modes = {
{{_("Synchronous"), wxGetTranslation(shader_compile_sync_desc)},
{_("Synchronous (Ubershaders)"), wxGetTranslation(shader_compile_uber_only_desc)},
{_("Asynchronous (Ubershaders)"), wxGetTranslation(shader_compile_async_uber_desc)},
{_("Asynchronous (Skip Drawing)"), wxGetTranslation(shader_compile_async_skip_desc)}}};
for (size_t i = 0; i < modes.size(); i++)
{
szr_shader_compilation->Add(
CreateRadioButton(page_general, modes[i].first, modes[i].second,
Config::GFX_SHADER_COMPILATION_MODE, static_cast<int>(i)),
wxGBPosition(static_cast<int>(i / 2), static_cast<int>(i % 2)), wxDefaultSpan,
wxALIGN_CENTER_VERTICAL);
}
szr_shader_compilation->Add(
CreateCheckBox(page_general, _("Compile Shaders Before Starting"),
wxGetTranslation(shader_compile_before_start_desc),
Config::GFX_WAIT_FOR_SHADERS_BEFORE_STARTING),
wxGBPosition(2, 0), wxGBSpan(1, 2));
}

wxStaticBoxSizer* const group_basic =
@@ -469,12 +491,19 @@ VideoConfigDiag::VideoConfigDiag(wxWindow* parent, const std::string& title)
group_other->Add(szr_other, 1, wxEXPAND | wxLEFT | wxRIGHT, space5);
group_other->AddSpacer(space5);

wxStaticBoxSizer* const group_shader_compilation =
new wxStaticBoxSizer(wxVERTICAL, page_general, _("Shader Compilation"));
group_shader_compilation->Add(szr_shader_compilation, 1, wxEXPAND | wxLEFT | wxRIGHT, space5);
group_shader_compilation->AddSpacer(space5);

szr_general->AddSpacer(space5);
szr_general->Add(group_basic, 0, wxEXPAND | wxLEFT | wxRIGHT, space5);
szr_general->AddSpacer(space5);
szr_general->Add(group_display, 0, wxEXPAND | wxLEFT | wxRIGHT, space5);
szr_general->AddSpacer(space5);
szr_general->Add(group_other, 0, wxEXPAND | wxLEFT | wxRIGHT, space5);
szr_general->AddSpacer(space5);
szr_general->Add(group_shader_compilation, 0, wxEXPAND | wxLEFT | wxRIGHT, space5);
}

szr_general->AddSpacer(space5);
@@ -541,18 +570,6 @@ VideoConfigDiag::VideoConfigDiag(wxWindow* parent, const std::string& title)
row += 1;
}

// ubershaders
{
const std::array<wxString, 3> mode_choices = {{_("Disabled"), _("Hybrid"), _("Exclusive")}};
szr_enh->Add(new wxStaticText(page_enh, wxID_ANY, _("Ubershaders:")), wxGBPosition(row, 0),
wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
szr_enh->Add(CreateChoice(page_enh, Config::GFX_UBERSHADER_MODE,
wxGetTranslation(ubershader_desc), mode_choices.size(),
mode_choices.data()),
wxGBPosition(row, 1), span2, wxALIGN_CENTER_VERTICAL);
row += 1;
}

// postproc shader
if (vconfig.backend_info.bSupportsPostProcessing)
{
@@ -1111,17 +1128,6 @@ SettingChoice* VideoConfigDiag::CreateChoice(wxWindow* parent,
return ch;
}

SettingRadioButton* VideoConfigDiag::CreateRadioButton(wxWindow* parent, const wxString& label,
const wxString& description,
const Config::ConfigInfo<bool>& setting,
bool reverse, long style)
{
SettingRadioButton* const rb =
new SettingRadioButton(parent, label, wxString(), setting, reverse, style);
RegisterControl(rb, description);
return rb;
}

/* Use this to register descriptions for controls which have NOT been created using the Create*
* functions from above */
wxControl* VideoConfigDiag::RegisterControl(wxControl* const control, const wxString& description)
@@ -64,7 +64,6 @@ class RefBoolSetting : public W
};

typedef BoolSetting<wxCheckBox> SettingCheckBox;
typedef BoolSetting<wxRadioButton> SettingRadioButton;

class IntegerSetting : public wxSpinCtrl
{
@@ -93,6 +92,33 @@ class SettingChoice : public wxChoice
Config::ConfigInfo<int> m_setting;
};

template <typename ValueType>
class SettingRadioButton : public wxRadioButton
{
public:
SettingRadioButton(wxWindow* parent, const wxString& label, const wxString& tooltip,
const Config::ConfigInfo<ValueType>& setting, const ValueType& value,
long style = 0)
: wxRadioButton(parent, wxID_ANY, label, wxDefaultPosition, wxDefaultSize, style),
m_setting(setting), m_value(value)
{
SetToolTip(tooltip);
SetValue(Config::Get(m_setting) == m_value);
Bind(wxEVT_RADIOBUTTON, &SettingRadioButton::UpdateValue, this);
}

void UpdateValue(wxCommandEvent& ev)
{
if (ev.IsChecked())
Config::SetBaseOrCurrent(m_setting, m_value);
ev.Skip();
}

private:
Config::ConfigInfo<ValueType> m_setting;
ValueType m_value;
};

class VideoConfigDiag : public wxDialog
{
public:
@@ -125,10 +151,17 @@ class VideoConfigDiag : public wxDialog
SettingChoice* CreateChoice(wxWindow* parent, const Config::ConfigInfo<int>& setting,
const wxString& description, int num = 0,
const wxString choices[] = nullptr, long style = 0);
SettingRadioButton* CreateRadioButton(wxWindow* parent, const wxString& label,
const wxString& description,
const Config::ConfigInfo<bool>& setting,
bool reverse = false, long style = 0);
template <typename ValueType>
SettingRadioButton<ValueType>* CreateRadioButton(wxWindow* parent, const wxString& label,
const wxString& description,
const Config::ConfigInfo<ValueType>& setting,
const ValueType& value, long style = 0)
{
auto* const rb =
new SettingRadioButton<ValueType>(parent, label, wxString(), setting, value, style);
RegisterControl(rb, description);
return rb;
}

// Same as above but only connects enter/leave window events
wxControl* RegisterControl(wxControl* const control, const wxString& description);
@@ -157,9 +190,6 @@ class VideoConfigDiag : public wxDialog
SettingCheckBox* borderless_fullscreen;
RefBoolSetting<wxCheckBox>* render_to_main_checkbox;

SettingRadioButton* virtual_xfb;
SettingRadioButton* real_xfb;

SettingCheckBox* cache_hires_textures;

wxCheckBox* progressive_scan_checkbox;
@@ -166,16 +166,19 @@ void VertexManager::vFlush()
}

// Bind all pending state to the command buffer
g_renderer->SetPipeline(m_current_pipeline_object);
if (!StateTracker::GetInstance()->Bind())
if (m_current_pipeline_object)
{
WARN_LOG(VIDEO, "Skipped draw of %u indices", index_count);
return;
}
g_renderer->SetPipeline(m_current_pipeline_object);
if (!StateTracker::GetInstance()->Bind())
{
WARN_LOG(VIDEO, "Skipped draw of %u indices", index_count);
return;
}

// Execute the draw
vkCmdDrawIndexed(g_command_buffer_mgr->GetCurrentCommandBuffer(), index_count, 1,
m_current_draw_base_index, m_current_draw_base_vertex, 0);
// Execute the draw
vkCmdDrawIndexed(g_command_buffer_mgr->GetCurrentCommandBuffer(), index_count, 1,
m_current_draw_base_index, m_current_draw_base_vertex, 0);
}

StateTracker::GetInstance()->OnDraw();
}
@@ -566,13 +566,25 @@ void VertexManagerBase::UpdatePipelineObject()
m_current_pipeline_object = nullptr;
m_pipeline_config_changed = false;

if (g_ActiveConfig.iUberShaderMode == UberShaderMode::Disabled)
switch (g_ActiveConfig.iShaderCompilationMode)
{
case ShaderCompilationMode::Synchronous:
{
// Ubershaders disabled? Block and compile the specialized shader.
m_current_pipeline_object = g_shader_cache->GetPipelineForUid(m_current_pipeline_config);
return;
}
else if (g_ActiveConfig.iUberShaderMode == UberShaderMode::Hybrid)
break;

case ShaderCompilationMode::SynchronousUberShaders:
{
// Exclusive ubershader mode, always use ubershaders.
m_current_pipeline_object =
g_shader_cache->GetUberPipelineForUid(m_current_uber_pipeline_config);
}
break;

case ShaderCompilationMode::AsynchronousUberShaders:
case ShaderCompilationMode::AsynchronousSkipRendering:
{
// Can we background compile shaders? If so, get the pipeline asynchronously.
auto res = g_shader_cache->GetPipelineForUidAsync(m_current_pipeline_config);
@@ -582,8 +594,20 @@ void VertexManagerBase::UpdatePipelineObject()
m_current_pipeline_object = *res;
return;
}
}

// Exclusive ubershader mode, or hybrid and shaders are still compiling.
m_current_pipeline_object = g_shader_cache->GetUberPipelineForUid(m_current_uber_pipeline_config);
if (g_ActiveConfig.iShaderCompilationMode == ShaderCompilationMode::AsynchronousUberShaders)
{
// Specialized shaders not ready, use the ubershaders.
m_current_pipeline_object =
g_shader_cache->GetUberPipelineForUid(m_current_uber_pipeline_config);
}
else
{
// Ensure we try again next draw. Otherwise, if no registers change between frames, the
// object will never be drawn, even when the shader is ready.
m_pipeline_config_changed = true;
}
}
break;
}
}
@@ -103,7 +103,8 @@ void VideoConfig::Refresh()
iCommandBufferExecuteInterval = Config::Get(Config::GFX_COMMAND_BUFFER_EXECUTE_INTERVAL);
bShaderCache = Config::Get(Config::GFX_SHADER_CACHE);
bWaitForShadersBeforeStarting = Config::Get(Config::GFX_WAIT_FOR_SHADERS_BEFORE_STARTING);
iUberShaderMode = static_cast<UberShaderMode>(Config::Get(Config::GFX_UBERSHADER_MODE));
iShaderCompilationMode =
static_cast<ShaderCompilationMode>(Config::Get(Config::GFX_SHADER_COMPILATION_MODE));
iShaderCompilerThreads = Config::Get(Config::GFX_SHADER_COMPILER_THREADS);
iShaderPrecompilerThreads = Config::Get(Config::GFX_SHADER_PRECOMPILER_THREADS);

@@ -178,6 +179,12 @@ bool VideoConfig::IsVSync() const
return bVSync && !Core::GetIsThrottlerTempDisabled();
}

bool VideoConfig::UsingUberShaders() const
{
return iShaderCompilationMode == ShaderCompilationMode::SynchronousUberShaders ||
iShaderCompilationMode == ShaderCompilationMode::AsynchronousUberShaders;
}

static u32 GetNumAutoShaderCompilerThreads()
{
// Automatic number. We use clamp(cpus - 3, 1, 4).
@@ -42,11 +42,12 @@ enum class StereoMode : int
Nvidia3DVision
};

enum class UberShaderMode : int
enum class ShaderCompilationMode : int
{
Disabled,
Hybrid,
Exclusive
Synchronous,
SynchronousUberShaders,
AsynchronousUberShaders,
AsynchronousSkipRendering
};

struct ProjectionHackConfig final
@@ -170,7 +171,7 @@ struct VideoConfig final

// Shader compilation settings.
bool bWaitForShadersBeforeStarting;
UberShaderMode iUberShaderMode;
ShaderCompilationMode iShaderCompilationMode;

// Number of shader compiler threads.
// 0 disables background compilation.
@@ -238,7 +239,7 @@ struct VideoConfig final
return backend_info.bSupportsGPUTextureDecoding && bEnableGPUTextureDecoding;
}
bool UseVertexRounding() const { return bVertexRounding && iEFBScale != 1; }
bool UsingUberShaders() const { return iUberShaderMode != UberShaderMode::Disabled; }
bool UsingUberShaders() const;
u32 GetShaderCompilerThreads() const;
u32 GetShaderPrecompilerThreads() const;
};