mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-22 13:55:38 +00:00
GPU: Add wireframe rendering/overlay
This commit is contained in:
parent
e804b5e701
commit
7ad1b8d093
|
@ -2872,8 +2872,8 @@ void FullscreenUI::DrawBIOSSettingsPage()
|
||||||
FSUI_CSTR("Patches the BIOS to skip the boot animation. Safe to enable."), "BIOS", "PatchFastBoot",
|
FSUI_CSTR("Patches the BIOS to skip the boot animation. Safe to enable."), "BIOS", "PatchFastBoot",
|
||||||
Settings::DEFAULT_FAST_BOOT_VALUE);
|
Settings::DEFAULT_FAST_BOOT_VALUE);
|
||||||
DrawToggleSetting(bsi, FSUI_CSTR("Enable TTY Logging"),
|
DrawToggleSetting(bsi, FSUI_CSTR("Enable TTY Logging"),
|
||||||
FSUI_CSTR("Logs BIOS calls to printf(). Not all games contain debugging messages."),
|
FSUI_CSTR("Logs BIOS calls to printf(). Not all games contain debugging messages."), "BIOS",
|
||||||
"BIOS", "TTYLogging", false);
|
"TTYLogging", false);
|
||||||
|
|
||||||
EndMenuButtons();
|
EndMenuButtons();
|
||||||
}
|
}
|
||||||
|
@ -4695,6 +4695,11 @@ void FullscreenUI::DrawAdvancedSettingsPage()
|
||||||
bsi, FSUI_CSTR("Stretch Display Vertically"),
|
bsi, FSUI_CSTR("Stretch Display Vertically"),
|
||||||
FSUI_CSTR("Stretches the display to match the aspect ratio by multiplying vertically instead of horizontally."),
|
FSUI_CSTR("Stretches the display to match the aspect ratio by multiplying vertically instead of horizontally."),
|
||||||
"Display", "StretchVertically", false);
|
"Display", "StretchVertically", false);
|
||||||
|
DrawEnumSetting(bsi, FSUI_CSTR("Wireframe Rendering"),
|
||||||
|
FSUI_CSTR("Overlays or replaces normal triangle drawing with a wireframe/line view."), "GPU",
|
||||||
|
"WireframeMode", GPUWireframeMode::Disabled, &Settings::ParseGPUWireframeMode,
|
||||||
|
&Settings::GetGPUWireframeModeName, &Settings::GetGPUWireframeModeDisplayName,
|
||||||
|
GPUWireframeMode::Count);
|
||||||
|
|
||||||
MenuHeading(FSUI_CSTR("PGXP Settings"));
|
MenuHeading(FSUI_CSTR("PGXP Settings"));
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,11 @@ ALWAYS_INLINE static constexpr std::tuple<T, T> MinMax(T v1, T v2)
|
||||||
return std::tie(v1, v2);
|
return std::tie(v1, v2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ALWAYS_INLINE static u32 GetMaxResolutionScale()
|
||||||
|
{
|
||||||
|
return g_gpu_device->GetMaxTextureSize() / VRAM_WIDTH;
|
||||||
|
}
|
||||||
|
|
||||||
ALWAYS_INLINE static bool ShouldUseUVLimits()
|
ALWAYS_INLINE static bool ShouldUseUVLimits()
|
||||||
{
|
{
|
||||||
// We only need UV limits if PGXP is enabled, or texture filtering is enabled.
|
// We only need UV limits if PGXP is enabled, or texture filtering is enabled.
|
||||||
|
@ -57,7 +62,7 @@ ALWAYS_INLINE static bool ShouldDisableColorPerspective()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the specified texture filtering mode requires dual-source blending.
|
/// Returns true if the specified texture filtering mode requires dual-source blending.
|
||||||
static bool TextureFilterRequiresDualSourceBlend(GPUTextureFilter filter)
|
ALWAYS_INLINE static bool TextureFilterRequiresDualSourceBlend(GPUTextureFilter filter)
|
||||||
{
|
{
|
||||||
return (filter == GPUTextureFilter::Bilinear || filter == GPUTextureFilter::JINC2 || filter == GPUTextureFilter::xBR);
|
return (filter == GPUTextureFilter::Bilinear || filter == GPUTextureFilter::JINC2 || filter == GPUTextureFilter::xBR);
|
||||||
}
|
}
|
||||||
|
@ -148,42 +153,50 @@ bool GPU_HW::Initialize()
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const GPUDevice::Features features = g_gpu_device->GetFeatures();
|
const GPUDevice::Features features = g_gpu_device->GetFeatures();
|
||||||
m_max_resolution_scale = g_gpu_device->GetMaxTextureSize() / VRAM_WIDTH;
|
|
||||||
m_supports_dual_source_blend = features.dual_source_blend;
|
|
||||||
m_supports_per_sample_shading = features.per_sample_shading;
|
|
||||||
m_supports_disable_color_perspective = features.noperspective_interpolation;
|
|
||||||
|
|
||||||
m_resolution_scale = CalculateResolutionScale();
|
m_resolution_scale = CalculateResolutionScale();
|
||||||
m_multisamples = std::min(g_settings.gpu_multisamples, g_gpu_device->GetMaxMultisamples());
|
m_multisamples = std::min(g_settings.gpu_multisamples, g_gpu_device->GetMaxMultisamples());
|
||||||
m_per_sample_shading = g_settings.gpu_per_sample_shading && m_supports_per_sample_shading;
|
m_per_sample_shading = g_settings.gpu_per_sample_shading && features.per_sample_shading;
|
||||||
m_true_color = g_settings.gpu_true_color;
|
m_true_color = g_settings.gpu_true_color;
|
||||||
m_scaled_dithering = g_settings.gpu_scaled_dithering;
|
m_scaled_dithering = g_settings.gpu_scaled_dithering;
|
||||||
m_texture_filtering = g_settings.gpu_texture_filter;
|
m_texture_filtering = g_settings.gpu_texture_filter;
|
||||||
m_using_uv_limits = ShouldUseUVLimits();
|
m_using_uv_limits = ShouldUseUVLimits();
|
||||||
m_chroma_smoothing = g_settings.gpu_24bit_chroma_smoothing;
|
m_chroma_smoothing = g_settings.gpu_24bit_chroma_smoothing;
|
||||||
m_downsample_mode = GetDownsampleMode(m_resolution_scale);
|
m_downsample_mode = GetDownsampleMode(m_resolution_scale);
|
||||||
m_disable_color_perspective = m_supports_disable_color_perspective && ShouldDisableColorPerspective();
|
m_wireframe_mode = g_settings.gpu_wireframe_mode;
|
||||||
|
m_disable_color_perspective = features.noperspective_interpolation && ShouldDisableColorPerspective();
|
||||||
|
|
||||||
if (m_multisamples != g_settings.gpu_multisamples)
|
if (m_multisamples != g_settings.gpu_multisamples)
|
||||||
{
|
{
|
||||||
Host::AddFormattedOSDMessage(20.0f, TRANSLATE("OSDMessage", "%ux MSAA is not supported, using %ux instead."),
|
Host::AddFormattedOSDMessage(Host::OSD_CRITICAL_ERROR_DURATION,
|
||||||
|
TRANSLATE("OSDMessage", "%ux MSAA is not supported, using %ux instead."),
|
||||||
g_settings.gpu_multisamples, m_multisamples);
|
g_settings.gpu_multisamples, m_multisamples);
|
||||||
}
|
}
|
||||||
if (!m_per_sample_shading && g_settings.gpu_per_sample_shading)
|
if (!m_per_sample_shading && g_settings.gpu_per_sample_shading)
|
||||||
{
|
{
|
||||||
Host::AddOSDMessage(TRANSLATE_STR("OSDMessage", "SSAA is not supported, using MSAA instead."), 20.0f);
|
Host::AddOSDMessage(TRANSLATE_STR("OSDMessage", "SSAA is not supported, using MSAA instead."), 20.0f);
|
||||||
}
|
}
|
||||||
if (!m_supports_dual_source_blend && TextureFilterRequiresDualSourceBlend(m_texture_filtering))
|
if (!features.dual_source_blend && TextureFilterRequiresDualSourceBlend(m_texture_filtering))
|
||||||
{
|
{
|
||||||
Host::AddFormattedOSDMessage(
|
Host::AddFormattedOSDMessage(
|
||||||
20.0f, TRANSLATE("OSDMessage", "Texture filter '%s' is not supported with the current renderer."),
|
Host::OSD_CRITICAL_ERROR_DURATION,
|
||||||
|
TRANSLATE("OSDMessage", "Texture filter '%s' is not supported with the current renderer."),
|
||||||
Settings::GetTextureFilterDisplayName(m_texture_filtering));
|
Settings::GetTextureFilterDisplayName(m_texture_filtering));
|
||||||
m_texture_filtering = GPUTextureFilter::Nearest;
|
m_texture_filtering = GPUTextureFilter::Nearest;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_supports_disable_color_perspective && !ShouldDisableColorPerspective())
|
if (!features.noperspective_interpolation && !ShouldDisableColorPerspective())
|
||||||
Log_WarningPrint("Disable color perspective not supported, but should be used.");
|
Log_WarningPrint("Disable color perspective not supported, but should be used.");
|
||||||
|
|
||||||
|
if (!features.geometry_shaders && m_wireframe_mode != GPUWireframeMode::Disabled)
|
||||||
|
{
|
||||||
|
Host::AddOSDMessage(
|
||||||
|
TRANSLATE("OSDMessage",
|
||||||
|
"Geometry shaders are not supported by your GPU, and are required for wireframe rendering."),
|
||||||
|
Host::OSD_CRITICAL_ERROR_DURATION);
|
||||||
|
m_wireframe_mode = GPUWireframeMode::Disabled;
|
||||||
|
}
|
||||||
|
|
||||||
m_pgxp_depth_buffer = g_settings.UsingPGXPDepthBuffer();
|
m_pgxp_depth_buffer = g_settings.UsingPGXPDepthBuffer();
|
||||||
|
|
||||||
UpdateSoftwareRenderer(false);
|
UpdateSoftwareRenderer(false);
|
||||||
|
@ -290,12 +303,16 @@ void GPU_HW::UpdateSettings(const Settings& old_settings)
|
||||||
{
|
{
|
||||||
GPU::UpdateSettings(old_settings);
|
GPU::UpdateSettings(old_settings);
|
||||||
|
|
||||||
|
const GPUDevice::Features features = g_gpu_device->GetFeatures();
|
||||||
|
|
||||||
const u32 resolution_scale = CalculateResolutionScale();
|
const u32 resolution_scale = CalculateResolutionScale();
|
||||||
const u32 multisamples = std::min(g_settings.gpu_multisamples, g_gpu_device->GetMaxMultisamples());
|
const u32 multisamples = std::min(g_settings.gpu_multisamples, g_gpu_device->GetMaxMultisamples());
|
||||||
const bool per_sample_shading = g_settings.gpu_per_sample_shading && m_supports_per_sample_shading;
|
const bool per_sample_shading = g_settings.gpu_per_sample_shading && features.noperspective_interpolation;
|
||||||
const GPUDownsampleMode downsample_mode = GetDownsampleMode(resolution_scale);
|
const GPUDownsampleMode downsample_mode = GetDownsampleMode(resolution_scale);
|
||||||
|
const GPUWireframeMode wireframe_mode =
|
||||||
|
features.geometry_shaders ? g_settings.gpu_wireframe_mode : GPUWireframeMode::Disabled;
|
||||||
const bool use_uv_limits = ShouldUseUVLimits();
|
const bool use_uv_limits = ShouldUseUVLimits();
|
||||||
const bool disable_color_perspective = m_supports_disable_color_perspective && ShouldDisableColorPerspective();
|
const bool disable_color_perspective = features.noperspective_interpolation && ShouldDisableColorPerspective();
|
||||||
|
|
||||||
// TODO: Use old_settings
|
// TODO: Use old_settings
|
||||||
const bool framebuffer_changed =
|
const bool framebuffer_changed =
|
||||||
|
@ -305,7 +322,8 @@ void GPU_HW::UpdateSettings(const Settings& old_settings)
|
||||||
m_true_color != g_settings.gpu_true_color || m_per_sample_shading != per_sample_shading ||
|
m_true_color != g_settings.gpu_true_color || m_per_sample_shading != per_sample_shading ||
|
||||||
m_scaled_dithering != g_settings.gpu_scaled_dithering || m_texture_filtering != g_settings.gpu_texture_filter ||
|
m_scaled_dithering != g_settings.gpu_scaled_dithering || m_texture_filtering != g_settings.gpu_texture_filter ||
|
||||||
m_using_uv_limits != use_uv_limits || m_chroma_smoothing != g_settings.gpu_24bit_chroma_smoothing ||
|
m_using_uv_limits != use_uv_limits || m_chroma_smoothing != g_settings.gpu_24bit_chroma_smoothing ||
|
||||||
m_downsample_mode != downsample_mode || m_pgxp_depth_buffer != g_settings.UsingPGXPDepthBuffer() ||
|
m_downsample_mode != downsample_mode || m_wireframe_mode != wireframe_mode ||
|
||||||
|
m_pgxp_depth_buffer != g_settings.UsingPGXPDepthBuffer() ||
|
||||||
m_disable_color_perspective != disable_color_perspective);
|
m_disable_color_perspective != disable_color_perspective);
|
||||||
|
|
||||||
if (m_resolution_scale != resolution_scale)
|
if (m_resolution_scale != resolution_scale)
|
||||||
|
@ -348,6 +366,7 @@ void GPU_HW::UpdateSettings(const Settings& old_settings)
|
||||||
m_using_uv_limits = use_uv_limits;
|
m_using_uv_limits = use_uv_limits;
|
||||||
m_chroma_smoothing = g_settings.gpu_24bit_chroma_smoothing;
|
m_chroma_smoothing = g_settings.gpu_24bit_chroma_smoothing;
|
||||||
m_downsample_mode = downsample_mode;
|
m_downsample_mode = downsample_mode;
|
||||||
|
m_wireframe_mode = wireframe_mode;
|
||||||
m_disable_color_perspective = disable_color_perspective;
|
m_disable_color_perspective = disable_color_perspective;
|
||||||
|
|
||||||
if (!m_supports_dual_source_blend && TextureFilterRequiresDualSourceBlend(m_texture_filtering))
|
if (!m_supports_dual_source_blend && TextureFilterRequiresDualSourceBlend(m_texture_filtering))
|
||||||
|
@ -387,10 +406,12 @@ void GPU_HW::UpdateSettings(const Settings& old_settings)
|
||||||
|
|
||||||
u32 GPU_HW::CalculateResolutionScale() const
|
u32 GPU_HW::CalculateResolutionScale() const
|
||||||
{
|
{
|
||||||
|
const u32 max_resolution_scale = GetMaxResolutionScale();
|
||||||
|
|
||||||
u32 scale;
|
u32 scale;
|
||||||
if (g_settings.gpu_resolution_scale != 0)
|
if (g_settings.gpu_resolution_scale != 0)
|
||||||
{
|
{
|
||||||
scale = std::clamp<u32>(g_settings.gpu_resolution_scale, 1, m_max_resolution_scale);
|
scale = std::clamp<u32>(g_settings.gpu_resolution_scale, 1, max_resolution_scale);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -404,7 +425,7 @@ u32 GPU_HW::CalculateResolutionScale() const
|
||||||
static_cast<s32>(std::ceil(static_cast<float>(g_gpu_device->GetWindowHeight()) / height));
|
static_cast<s32>(std::ceil(static_cast<float>(g_gpu_device->GetWindowHeight()) / height));
|
||||||
Log_InfoPrintf("Height = %d, preferred scale = %d", height, preferred_scale);
|
Log_InfoPrintf("Height = %d, preferred scale = %d", height, preferred_scale);
|
||||||
|
|
||||||
scale = static_cast<u32>(std::clamp<s32>(preferred_scale, 1, m_max_resolution_scale));
|
scale = static_cast<u32>(std::clamp<s32>(preferred_scale, 1, max_resolution_scale));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g_settings.gpu_downsample_mode == GPUDownsampleMode::Adaptive && scale > 1 && !Common::IsPow2(scale))
|
if (g_settings.gpu_downsample_mode == GPUDownsampleMode::Adaptive && scale > 1 && !Common::IsPow2(scale))
|
||||||
|
@ -474,7 +495,7 @@ std::tuple<u32, u32> GPU_HW::GetFullDisplayResolution(bool scaled /* = true */)
|
||||||
void GPU_HW::PrintSettingsToLog()
|
void GPU_HW::PrintSettingsToLog()
|
||||||
{
|
{
|
||||||
Log_InfoPrintf("Resolution Scale: %u (%ux%u), maximum %u", m_resolution_scale, VRAM_WIDTH * m_resolution_scale,
|
Log_InfoPrintf("Resolution Scale: %u (%ux%u), maximum %u", m_resolution_scale, VRAM_WIDTH * m_resolution_scale,
|
||||||
VRAM_HEIGHT * m_resolution_scale, m_max_resolution_scale);
|
VRAM_HEIGHT * m_resolution_scale, GetMaxResolutionScale());
|
||||||
Log_InfoPrintf("Multisampling: %ux%s", m_multisamples, m_per_sample_shading ? " (per sample shading)" : "");
|
Log_InfoPrintf("Multisampling: %ux%s", m_multisamples, m_per_sample_shading ? " (per sample shading)" : "");
|
||||||
Log_InfoPrintf("Dithering: %s%s", m_true_color ? "Disabled" : "Enabled",
|
Log_InfoPrintf("Dithering: %s%s", m_true_color ? "Disabled" : "Enabled",
|
||||||
(!m_true_color && m_scaled_dithering) ? " (Scaled)" : "");
|
(!m_true_color && m_scaled_dithering) ? " (Scaled)" : "");
|
||||||
|
@ -483,6 +504,7 @@ void GPU_HW::PrintSettingsToLog()
|
||||||
Log_InfoPrintf("Using UV limits: %s", m_using_uv_limits ? "YES" : "NO");
|
Log_InfoPrintf("Using UV limits: %s", m_using_uv_limits ? "YES" : "NO");
|
||||||
Log_InfoPrintf("Depth buffer: %s", m_pgxp_depth_buffer ? "YES" : "NO");
|
Log_InfoPrintf("Depth buffer: %s", m_pgxp_depth_buffer ? "YES" : "NO");
|
||||||
Log_InfoPrintf("Downsampling: %s", Settings::GetDownsampleModeDisplayName(m_downsample_mode));
|
Log_InfoPrintf("Downsampling: %s", Settings::GetDownsampleModeDisplayName(m_downsample_mode));
|
||||||
|
Log_InfoPrintf("Wireframe rendering: %s", Settings::GetGPUWireframeModeDisplayName(m_wireframe_mode));
|
||||||
Log_InfoPrintf("Using software renderer for readbacks: %s", m_sw_renderer ? "YES" : "NO");
|
Log_InfoPrintf("Using software renderer for readbacks: %s", m_sw_renderer ? "YES" : "NO");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -772,6 +794,39 @@ bool GPU_HW::CompilePipelines()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_wireframe_mode != GPUWireframeMode::Disabled)
|
||||||
|
{
|
||||||
|
std::unique_ptr<GPUShader> gs =
|
||||||
|
g_gpu_device->CreateShader(GPUShaderStage::Geometry, shadergen.GenerateWireframeGeometryShader());
|
||||||
|
std::unique_ptr<GPUShader> fs =
|
||||||
|
g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GenerateWireframeFragmentShader());
|
||||||
|
if (!gs || !fs)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
GL_OBJECT_NAME(gs, "Batch Wireframe Geometry Shader");
|
||||||
|
GL_OBJECT_NAME(fs, "Batch Wireframe Fragment Shader");
|
||||||
|
|
||||||
|
plconfig.input_layout.vertex_attributes =
|
||||||
|
gsl::span<const GPUPipeline::VertexAttribute>(vertex_attributes, NUM_BATCH_VERTEX_ATTRIBUTES);
|
||||||
|
plconfig.blend = (m_wireframe_mode == GPUWireframeMode::OverlayWireframe) ?
|
||||||
|
GPUPipeline::BlendState::GetAlphaBlendingState() :
|
||||||
|
GPUPipeline::BlendState::GetNoBlendingState();
|
||||||
|
plconfig.blend.write_mask = 0x7;
|
||||||
|
plconfig.depth = GPUPipeline::DepthState::GetNoTestsState();
|
||||||
|
plconfig.vertex_shader = batch_vertex_shaders[0].get();
|
||||||
|
plconfig.geometry_shader = gs.get();
|
||||||
|
plconfig.fragment_shader = fs.get();
|
||||||
|
|
||||||
|
if (!(m_wireframe_pipeline = g_gpu_device->CreatePipeline(plconfig)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
GL_OBJECT_NAME(m_wireframe_pipeline, "Batch Wireframe Pipeline");
|
||||||
|
|
||||||
|
plconfig.vertex_shader = nullptr;
|
||||||
|
plconfig.geometry_shader = nullptr;
|
||||||
|
plconfig.fragment_shader = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
batch_shader_guard.Run();
|
batch_shader_guard.Run();
|
||||||
|
|
||||||
std::unique_ptr<GPUShader> fullscreen_quad_vertex_shader =
|
std::unique_ptr<GPUShader> fullscreen_quad_vertex_shader =
|
||||||
|
@ -1026,6 +1081,8 @@ void GPU_HW::DestroyPipelines()
|
||||||
{
|
{
|
||||||
static constexpr auto destroy = [](std::unique_ptr<GPUPipeline>& p) { p.reset(); };
|
static constexpr auto destroy = [](std::unique_ptr<GPUPipeline>& p) { p.reset(); };
|
||||||
|
|
||||||
|
m_wireframe_pipeline.reset();
|
||||||
|
|
||||||
m_batch_pipelines.enumerate(destroy);
|
m_batch_pipelines.enumerate(destroy);
|
||||||
|
|
||||||
m_vram_fill_pipelines.enumerate(destroy);
|
m_vram_fill_pipelines.enumerate(destroy);
|
||||||
|
@ -1136,7 +1193,7 @@ void GPU_HW::UnmapBatchVertexPointer(u32 used_vertices)
|
||||||
m_batch_current_vertex_ptr = nullptr;
|
m_batch_current_vertex_ptr = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPU_HW::DrawBatchVertices(BatchRenderMode render_mode, u32 base_vertex, u32 num_vertices)
|
void GPU_HW::DrawBatchVertices(BatchRenderMode render_mode, u32 num_vertices, u32 base_vertex)
|
||||||
{
|
{
|
||||||
// [depth_test][render_mode][texture_mode][transparency_mode][dithering][interlacing]
|
// [depth_test][render_mode][texture_mode][transparency_mode][dithering][interlacing]
|
||||||
const u8 depth_test = m_batch.use_depth_buffer ? static_cast<u8>(2) : BoolToUInt8(m_batch.check_mask_before_draw);
|
const u8 depth_test = m_batch.use_depth_buffer ? static_cast<u8>(2) : BoolToUInt8(m_batch.check_mask_before_draw);
|
||||||
|
@ -2365,16 +2422,26 @@ void GPU_HW::FlushRender()
|
||||||
m_batch_ubo_dirty = false;
|
m_batch_ubo_dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (NeedsTwoPassRendering())
|
if (m_wireframe_mode != GPUWireframeMode::OnlyWireframe)
|
||||||
{
|
{
|
||||||
m_renderer_stats.num_batches += 2;
|
if (NeedsTwoPassRendering())
|
||||||
DrawBatchVertices(BatchRenderMode::OnlyOpaque, m_batch_base_vertex, vertex_count);
|
{
|
||||||
DrawBatchVertices(BatchRenderMode::OnlyTransparent, m_batch_base_vertex, vertex_count);
|
m_renderer_stats.num_batches += 2;
|
||||||
|
DrawBatchVertices(BatchRenderMode::OnlyOpaque, vertex_count, m_batch_base_vertex);
|
||||||
|
DrawBatchVertices(BatchRenderMode::OnlyTransparent, vertex_count, m_batch_base_vertex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_renderer_stats.num_batches++;
|
||||||
|
DrawBatchVertices(m_batch.GetRenderMode(), vertex_count, m_batch_base_vertex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if (m_wireframe_mode != GPUWireframeMode::Disabled)
|
||||||
{
|
{
|
||||||
m_renderer_stats.num_batches++;
|
m_renderer_stats.num_batches++;
|
||||||
DrawBatchVertices(m_batch.GetRenderMode(), m_batch_base_vertex, vertex_count);
|
g_gpu_device->SetPipeline(m_wireframe_pipeline.get());
|
||||||
|
g_gpu_device->Draw(vertex_count, m_batch_base_vertex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -157,7 +157,7 @@ private:
|
||||||
void SetScissor();
|
void SetScissor();
|
||||||
void MapBatchVertexPointer(u32 required_vertices);
|
void MapBatchVertexPointer(u32 required_vertices);
|
||||||
void UnmapBatchVertexPointer(u32 used_vertices);
|
void UnmapBatchVertexPointer(u32 used_vertices);
|
||||||
void DrawBatchVertices(BatchRenderMode render_mode, u32 base_vertex, u32 num_vertices);
|
void DrawBatchVertices(BatchRenderMode render_mode, u32 num_vertices, u32 base_vertex);
|
||||||
void ClearDisplay() override;
|
void ClearDisplay() override;
|
||||||
void UpdateDisplay() override;
|
void UpdateDisplay() override;
|
||||||
|
|
||||||
|
@ -266,24 +266,22 @@ private:
|
||||||
|
|
||||||
u32 m_resolution_scale = 1;
|
u32 m_resolution_scale = 1;
|
||||||
u32 m_multisamples = 1;
|
u32 m_multisamples = 1;
|
||||||
u32 m_max_resolution_scale = 1;
|
|
||||||
bool m_true_color = true;
|
|
||||||
|
|
||||||
union
|
union
|
||||||
{
|
{
|
||||||
BitField<u8, bool, 0, 1> m_supports_per_sample_shading;
|
BitField<u8, bool, 0, 1> m_supports_dual_source_blend;
|
||||||
BitField<u8, bool, 1, 1> m_supports_dual_source_blend;
|
BitField<u8, bool, 1, 1> m_per_sample_shading;
|
||||||
BitField<u8, bool, 2, 1> m_supports_disable_color_perspective;
|
BitField<u8, bool, 2, 1> m_scaled_dithering;
|
||||||
BitField<u8, bool, 3, 1> m_per_sample_shading;
|
BitField<u8, bool, 3, 1> m_chroma_smoothing;
|
||||||
BitField<u8, bool, 4, 1> m_scaled_dithering;
|
BitField<u8, bool, 4, 1> m_disable_color_perspective;
|
||||||
BitField<u8, bool, 5, 1> m_chroma_smoothing;
|
|
||||||
BitField<u8, bool, 6, 1> m_disable_color_perspective;
|
|
||||||
|
|
||||||
u8 bits = 0;
|
u8 bits = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
GPUTextureFilter m_texture_filtering = GPUTextureFilter::Nearest;
|
GPUTextureFilter m_texture_filtering = GPUTextureFilter::Nearest;
|
||||||
GPUDownsampleMode m_downsample_mode = GPUDownsampleMode::Disabled;
|
GPUDownsampleMode m_downsample_mode = GPUDownsampleMode::Disabled;
|
||||||
|
GPUWireframeMode m_wireframe_mode = GPUWireframeMode::Disabled;
|
||||||
|
bool m_true_color = true;
|
||||||
bool m_using_uv_limits = false;
|
bool m_using_uv_limits = false;
|
||||||
bool m_pgxp_depth_buffer = false;
|
bool m_pgxp_depth_buffer = false;
|
||||||
|
|
||||||
|
@ -298,6 +296,7 @@ private:
|
||||||
|
|
||||||
// [depth_test][render_mode][texture_mode][transparency_mode][dithering][interlacing]
|
// [depth_test][render_mode][texture_mode][transparency_mode][dithering][interlacing]
|
||||||
DimensionalArray<std::unique_ptr<GPUPipeline>, 2, 2, 5, 9, 4, 3> m_batch_pipelines{};
|
DimensionalArray<std::unique_ptr<GPUPipeline>, 2, 2, 5, 9, 4, 3> m_batch_pipelines{};
|
||||||
|
std::unique_ptr<GPUPipeline> m_wireframe_pipeline;
|
||||||
|
|
||||||
// [wrapped][interlaced]
|
// [wrapped][interlaced]
|
||||||
DimensionalArray<std::unique_ptr<GPUPipeline>, 2, 2> m_vram_fill_pipelines{};
|
DimensionalArray<std::unique_ptr<GPUPipeline>, 2, 2> m_vram_fill_pipelines{};
|
||||||
|
|
|
@ -1087,6 +1087,96 @@ float3 SampleVRAM24Smoothed(uint2 icoords)
|
||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string GPU_HW_ShaderGen::GenerateWireframeGeometryShader()
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
WriteHeader(ss);
|
||||||
|
WriteCommonFunctions(ss);
|
||||||
|
|
||||||
|
if (m_glsl)
|
||||||
|
{
|
||||||
|
ss << R"(
|
||||||
|
layout(triangles) in;
|
||||||
|
layout(line_strip, max_vertices = 6) out;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
gl_Position = gl_in[0].gl_Position;
|
||||||
|
EmitVertex();
|
||||||
|
gl_Position = gl_in[1].gl_Position;
|
||||||
|
EmitVertex();
|
||||||
|
EndPrimitive();
|
||||||
|
gl_Position = gl_in[1].gl_Position;
|
||||||
|
EmitVertex();
|
||||||
|
gl_Position = gl_in[2].gl_Position;
|
||||||
|
EmitVertex();
|
||||||
|
EndPrimitive();
|
||||||
|
gl_Position = gl_in[2].gl_Position;
|
||||||
|
EmitVertex();
|
||||||
|
gl_Position = gl_in[0].gl_Position;
|
||||||
|
EmitVertex();
|
||||||
|
EndPrimitive();
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ss << R"(
|
||||||
|
struct GSInput
|
||||||
|
{
|
||||||
|
float4 col0 : COLOR0;
|
||||||
|
float4 pos : SV_Position;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GSOutput
|
||||||
|
{
|
||||||
|
float4 pos : SV_Position;
|
||||||
|
};
|
||||||
|
|
||||||
|
GSOutput GetVertex(GSInput vi)
|
||||||
|
{
|
||||||
|
GSOutput vo;
|
||||||
|
vo.pos = vi.pos;
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
[maxvertexcount(6)]
|
||||||
|
void main(triangle GSInput input[3], inout LineStream<GSOutput> output)
|
||||||
|
{
|
||||||
|
output.Append(GetVertex(input[0]));
|
||||||
|
output.Append(GetVertex(input[1]));
|
||||||
|
output.RestartStrip();
|
||||||
|
|
||||||
|
output.Append(GetVertex(input[1]));
|
||||||
|
output.Append(GetVertex(input[2]));
|
||||||
|
output.RestartStrip();
|
||||||
|
|
||||||
|
output.Append(GetVertex(input[2]));
|
||||||
|
output.Append(GetVertex(input[0]));
|
||||||
|
output.RestartStrip();
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
}
|
||||||
|
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GPU_HW_ShaderGen::GenerateWireframeFragmentShader()
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
WriteHeader(ss);
|
||||||
|
WriteCommonFunctions(ss);
|
||||||
|
|
||||||
|
DeclareFragmentEntryPoint(ss, 0, 0, {}, false, 1);
|
||||||
|
ss << R"(
|
||||||
|
{
|
||||||
|
o_col0 = float4(1.0, 1.0, 1.0, 0.5);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
std::string GPU_HW_ShaderGen::GenerateVRAMReadFragmentShader()
|
std::string GPU_HW_ShaderGen::GenerateVRAMReadFragmentShader()
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
|
|
|
@ -18,6 +18,8 @@ public:
|
||||||
bool dithering, bool interlacing);
|
bool dithering, bool interlacing);
|
||||||
std::string GenerateDisplayFragmentShader(bool depth_24bit, GPU_HW::InterlacedRenderMode interlace_mode,
|
std::string GenerateDisplayFragmentShader(bool depth_24bit, GPU_HW::InterlacedRenderMode interlace_mode,
|
||||||
bool smooth_chroma);
|
bool smooth_chroma);
|
||||||
|
std::string GenerateWireframeGeometryShader();
|
||||||
|
std::string GenerateWireframeFragmentShader();
|
||||||
std::string GenerateVRAMReadFragmentShader();
|
std::string GenerateVRAMReadFragmentShader();
|
||||||
std::string GenerateVRAMWriteFragmentShader(bool use_ssbo);
|
std::string GenerateVRAMWriteFragmentShader(bool use_ssbo);
|
||||||
std::string GenerateVRAMCopyFragmentShader();
|
std::string GenerateVRAMCopyFragmentShader();
|
||||||
|
|
|
@ -161,7 +161,7 @@ void Settings::Load(SettingsInterface& si)
|
||||||
region =
|
region =
|
||||||
ParseConsoleRegionName(
|
ParseConsoleRegionName(
|
||||||
si.GetStringValue("Console", "Region", Settings::GetConsoleRegionName(Settings::DEFAULT_CONSOLE_REGION)).c_str())
|
si.GetStringValue("Console", "Region", Settings::GetConsoleRegionName(Settings::DEFAULT_CONSOLE_REGION)).c_str())
|
||||||
.value_or(DEFAULT_CONSOLE_REGION);
|
.value_or(DEFAULT_CONSOLE_REGION);
|
||||||
enable_8mb_ram = si.GetBoolValue("Console", "Enable8MBRAM", false);
|
enable_8mb_ram = si.GetBoolValue("Console", "Enable8MBRAM", false);
|
||||||
|
|
||||||
emulation_speed = si.GetFloatValue("Main", "EmulationSpeed", 1.0f);
|
emulation_speed = si.GetFloatValue("Main", "EmulationSpeed", 1.0f);
|
||||||
|
@ -192,7 +192,7 @@ void Settings::Load(SettingsInterface& si)
|
||||||
cpu_execution_mode =
|
cpu_execution_mode =
|
||||||
ParseCPUExecutionMode(
|
ParseCPUExecutionMode(
|
||||||
si.GetStringValue("CPU", "ExecutionMode", GetCPUExecutionModeName(DEFAULT_CPU_EXECUTION_MODE)).c_str())
|
si.GetStringValue("CPU", "ExecutionMode", GetCPUExecutionModeName(DEFAULT_CPU_EXECUTION_MODE)).c_str())
|
||||||
.value_or(DEFAULT_CPU_EXECUTION_MODE);
|
.value_or(DEFAULT_CPU_EXECUTION_MODE);
|
||||||
cpu_overclock_numerator = std::max(si.GetIntValue("CPU", "OverclockNumerator", 1), 1);
|
cpu_overclock_numerator = std::max(si.GetIntValue("CPU", "OverclockNumerator", 1), 1);
|
||||||
cpu_overclock_denominator = std::max(si.GetIntValue("CPU", "OverclockDenominator", 1), 1);
|
cpu_overclock_denominator = std::max(si.GetIntValue("CPU", "OverclockDenominator", 1), 1);
|
||||||
cpu_overclock_enable = si.GetBoolValue("CPU", "OverclockEnable", false);
|
cpu_overclock_enable = si.GetBoolValue("CPU", "OverclockEnable", false);
|
||||||
|
@ -201,11 +201,11 @@ void Settings::Load(SettingsInterface& si)
|
||||||
cpu_recompiler_block_linking = si.GetBoolValue("CPU", "RecompilerBlockLinking", true);
|
cpu_recompiler_block_linking = si.GetBoolValue("CPU", "RecompilerBlockLinking", true);
|
||||||
cpu_recompiler_icache = si.GetBoolValue("CPU", "RecompilerICache", false);
|
cpu_recompiler_icache = si.GetBoolValue("CPU", "RecompilerICache", false);
|
||||||
cpu_fastmem_mode = ParseCPUFastmemMode(
|
cpu_fastmem_mode = ParseCPUFastmemMode(
|
||||||
si.GetStringValue("CPU", "FastmemMode", GetCPUFastmemModeName(DEFAULT_CPU_FASTMEM_MODE)).c_str())
|
si.GetStringValue("CPU", "FastmemMode", GetCPUFastmemModeName(DEFAULT_CPU_FASTMEM_MODE)).c_str())
|
||||||
.value_or(DEFAULT_CPU_FASTMEM_MODE);
|
.value_or(DEFAULT_CPU_FASTMEM_MODE);
|
||||||
|
|
||||||
gpu_renderer = ParseRendererName(si.GetStringValue("GPU", "Renderer", GetRendererName(DEFAULT_GPU_RENDERER)).c_str())
|
gpu_renderer = ParseRendererName(si.GetStringValue("GPU", "Renderer", GetRendererName(DEFAULT_GPU_RENDERER)).c_str())
|
||||||
.value_or(DEFAULT_GPU_RENDERER);
|
.value_or(DEFAULT_GPU_RENDERER);
|
||||||
gpu_adapter = si.GetStringValue("GPU", "Adapter", "");
|
gpu_adapter = si.GetStringValue("GPU", "Adapter", "");
|
||||||
gpu_resolution_scale = static_cast<u32>(si.GetIntValue("GPU", "ResolutionScale", 1));
|
gpu_resolution_scale = static_cast<u32>(si.GetIntValue("GPU", "ResolutionScale", 1));
|
||||||
gpu_multisamples = static_cast<u32>(si.GetIntValue("GPU", "Multisamples", 1));
|
gpu_multisamples = static_cast<u32>(si.GetIntValue("GPU", "Multisamples", 1));
|
||||||
|
@ -220,11 +220,15 @@ void Settings::Load(SettingsInterface& si)
|
||||||
gpu_texture_filter =
|
gpu_texture_filter =
|
||||||
ParseTextureFilterName(
|
ParseTextureFilterName(
|
||||||
si.GetStringValue("GPU", "TextureFilter", GetTextureFilterName(DEFAULT_GPU_TEXTURE_FILTER)).c_str())
|
si.GetStringValue("GPU", "TextureFilter", GetTextureFilterName(DEFAULT_GPU_TEXTURE_FILTER)).c_str())
|
||||||
.value_or(DEFAULT_GPU_TEXTURE_FILTER);
|
.value_or(DEFAULT_GPU_TEXTURE_FILTER);
|
||||||
gpu_downsample_mode =
|
gpu_downsample_mode =
|
||||||
ParseDownsampleModeName(
|
ParseDownsampleModeName(
|
||||||
si.GetStringValue("GPU", "DownsampleMode", GetDownsampleModeName(DEFAULT_GPU_DOWNSAMPLE_MODE)).c_str())
|
si.GetStringValue("GPU", "DownsampleMode", GetDownsampleModeName(DEFAULT_GPU_DOWNSAMPLE_MODE)).c_str())
|
||||||
.value_or(DEFAULT_GPU_DOWNSAMPLE_MODE);
|
.value_or(DEFAULT_GPU_DOWNSAMPLE_MODE);
|
||||||
|
gpu_wireframe_mode =
|
||||||
|
ParseGPUWireframeMode(
|
||||||
|
si.GetStringValue("GPU", "WireframeMode", GetGPUWireframeModeName(DEFAULT_GPU_WIREFRAME_MODE)).c_str())
|
||||||
|
.value_or(DEFAULT_GPU_WIREFRAME_MODE);
|
||||||
gpu_disable_interlacing = si.GetBoolValue("GPU", "DisableInterlacing", true);
|
gpu_disable_interlacing = si.GetBoolValue("GPU", "DisableInterlacing", true);
|
||||||
gpu_force_ntsc_timings = si.GetBoolValue("GPU", "ForceNTSCTimings", false);
|
gpu_force_ntsc_timings = si.GetBoolValue("GPU", "ForceNTSCTimings", false);
|
||||||
gpu_widescreen_hack = si.GetBoolValue("GPU", "WidescreenHack", false);
|
gpu_widescreen_hack = si.GetBoolValue("GPU", "WidescreenHack", false);
|
||||||
|
@ -243,11 +247,11 @@ void Settings::Load(SettingsInterface& si)
|
||||||
display_crop_mode =
|
display_crop_mode =
|
||||||
ParseDisplayCropMode(
|
ParseDisplayCropMode(
|
||||||
si.GetStringValue("Display", "CropMode", GetDisplayCropModeName(DEFAULT_DISPLAY_CROP_MODE)).c_str())
|
si.GetStringValue("Display", "CropMode", GetDisplayCropModeName(DEFAULT_DISPLAY_CROP_MODE)).c_str())
|
||||||
.value_or(DEFAULT_DISPLAY_CROP_MODE);
|
.value_or(DEFAULT_DISPLAY_CROP_MODE);
|
||||||
display_aspect_ratio =
|
display_aspect_ratio =
|
||||||
ParseDisplayAspectRatio(
|
ParseDisplayAspectRatio(
|
||||||
si.GetStringValue("Display", "AspectRatio", GetDisplayAspectRatioName(DEFAULT_DISPLAY_ASPECT_RATIO)).c_str())
|
si.GetStringValue("Display", "AspectRatio", GetDisplayAspectRatioName(DEFAULT_DISPLAY_ASPECT_RATIO)).c_str())
|
||||||
.value_or(DEFAULT_DISPLAY_ASPECT_RATIO);
|
.value_or(DEFAULT_DISPLAY_ASPECT_RATIO);
|
||||||
display_aspect_ratio_custom_numerator = static_cast<u16>(
|
display_aspect_ratio_custom_numerator = static_cast<u16>(
|
||||||
std::clamp<int>(si.GetIntValue("Display", "CustomAspectRatioNumerator", 4), 1, std::numeric_limits<u16>::max()));
|
std::clamp<int>(si.GetIntValue("Display", "CustomAspectRatioNumerator", 4), 1, std::numeric_limits<u16>::max()));
|
||||||
display_aspect_ratio_custom_denominator = static_cast<u16>(
|
display_aspect_ratio_custom_denominator = static_cast<u16>(
|
||||||
|
@ -255,10 +259,10 @@ void Settings::Load(SettingsInterface& si)
|
||||||
display_alignment =
|
display_alignment =
|
||||||
ParseDisplayAlignment(
|
ParseDisplayAlignment(
|
||||||
si.GetStringValue("Display", "Alignment", GetDisplayAlignmentName(DEFAULT_DISPLAY_ALIGNMENT)).c_str())
|
si.GetStringValue("Display", "Alignment", GetDisplayAlignmentName(DEFAULT_DISPLAY_ALIGNMENT)).c_str())
|
||||||
.value_or(DEFAULT_DISPLAY_ALIGNMENT);
|
.value_or(DEFAULT_DISPLAY_ALIGNMENT);
|
||||||
display_scaling =
|
display_scaling =
|
||||||
ParseDisplayScaling(si.GetStringValue("Display", "Scaling", GetDisplayScalingName(DEFAULT_DISPLAY_SCALING)).c_str())
|
ParseDisplayScaling(si.GetStringValue("Display", "Scaling", GetDisplayScalingName(DEFAULT_DISPLAY_SCALING)).c_str())
|
||||||
.value_or(DEFAULT_DISPLAY_SCALING);
|
.value_or(DEFAULT_DISPLAY_SCALING);
|
||||||
display_force_4_3_for_24bit = si.GetBoolValue("Display", "Force4_3For24Bit", false);
|
display_force_4_3_for_24bit = si.GetBoolValue("Display", "Force4_3For24Bit", false);
|
||||||
display_active_start_offset = static_cast<s16>(si.GetIntValue("Display", "ActiveStartOffset", 0));
|
display_active_start_offset = static_cast<s16>(si.GetIntValue("Display", "ActiveStartOffset", 0));
|
||||||
display_active_end_offset = static_cast<s16>(si.GetIntValue("Display", "ActiveEndOffset", 0));
|
display_active_end_offset = static_cast<s16>(si.GetIntValue("Display", "ActiveEndOffset", 0));
|
||||||
|
@ -292,13 +296,13 @@ void Settings::Load(SettingsInterface& si)
|
||||||
|
|
||||||
audio_backend =
|
audio_backend =
|
||||||
ParseAudioBackend(si.GetStringValue("Audio", "Backend", GetAudioBackendName(DEFAULT_AUDIO_BACKEND)).c_str())
|
ParseAudioBackend(si.GetStringValue("Audio", "Backend", GetAudioBackendName(DEFAULT_AUDIO_BACKEND)).c_str())
|
||||||
.value_or(DEFAULT_AUDIO_BACKEND);
|
.value_or(DEFAULT_AUDIO_BACKEND);
|
||||||
audio_driver = si.GetStringValue("Audio", "Driver");
|
audio_driver = si.GetStringValue("Audio", "Driver");
|
||||||
audio_output_device = si.GetStringValue("Audio", "OutputDevice");
|
audio_output_device = si.GetStringValue("Audio", "OutputDevice");
|
||||||
audio_stretch_mode =
|
audio_stretch_mode =
|
||||||
AudioStream::ParseStretchMode(
|
AudioStream::ParseStretchMode(
|
||||||
si.GetStringValue("Audio", "StretchMode", AudioStream::GetStretchModeName(DEFAULT_AUDIO_STRETCH_MODE)).c_str())
|
si.GetStringValue("Audio", "StretchMode", AudioStream::GetStretchModeName(DEFAULT_AUDIO_STRETCH_MODE)).c_str())
|
||||||
.value_or(DEFAULT_AUDIO_STRETCH_MODE);
|
.value_or(DEFAULT_AUDIO_STRETCH_MODE);
|
||||||
audio_output_latency_ms = si.GetUIntValue("Audio", "OutputLatencyMS", DEFAULT_AUDIO_OUTPUT_LATENCY_MS);
|
audio_output_latency_ms = si.GetUIntValue("Audio", "OutputLatencyMS", DEFAULT_AUDIO_OUTPUT_LATENCY_MS);
|
||||||
audio_buffer_ms = si.GetUIntValue("Audio", "BufferMS", DEFAULT_AUDIO_BUFFER_MS);
|
audio_buffer_ms = si.GetUIntValue("Audio", "BufferMS", DEFAULT_AUDIO_BUFFER_MS);
|
||||||
audio_output_volume = si.GetUIntValue("Audio", "OutputVolume", 100);
|
audio_output_volume = si.GetUIntValue("Audio", "OutputVolume", 100);
|
||||||
|
@ -323,14 +327,14 @@ void Settings::Load(SettingsInterface& si)
|
||||||
multitap_mode =
|
multitap_mode =
|
||||||
ParseMultitapModeName(
|
ParseMultitapModeName(
|
||||||
si.GetStringValue("ControllerPorts", "MultitapMode", GetMultitapModeName(DEFAULT_MULTITAP_MODE)).c_str())
|
si.GetStringValue("ControllerPorts", "MultitapMode", GetMultitapModeName(DEFAULT_MULTITAP_MODE)).c_str())
|
||||||
.value_or(DEFAULT_MULTITAP_MODE);
|
.value_or(DEFAULT_MULTITAP_MODE);
|
||||||
|
|
||||||
controller_types[0] = ParseControllerTypeName(si.GetStringValue(Controller::GetSettingsSection(0).c_str(), "Type",
|
controller_types[0] = ParseControllerTypeName(si.GetStringValue(Controller::GetSettingsSection(0).c_str(), "Type",
|
||||||
GetControllerTypeName(DEFAULT_CONTROLLER_1_TYPE))
|
GetControllerTypeName(DEFAULT_CONTROLLER_1_TYPE))
|
||||||
.c_str())
|
.c_str())
|
||||||
.value_or(DEFAULT_CONTROLLER_1_TYPE);
|
.value_or(DEFAULT_CONTROLLER_1_TYPE);
|
||||||
|
|
||||||
const std::array<bool, 2> mtap_enabled = { {IsPort1MultitapEnabled(), IsPort2MultitapEnabled()} };
|
const std::array<bool, 2> mtap_enabled = {{IsPort1MultitapEnabled(), IsPort2MultitapEnabled()}};
|
||||||
for (u32 i = 1; i < NUM_CONTROLLER_AND_CARD_PORTS; i++)
|
for (u32 i = 1; i < NUM_CONTROLLER_AND_CARD_PORTS; i++)
|
||||||
{
|
{
|
||||||
// Ignore types when multitap not enabled
|
// Ignore types when multitap not enabled
|
||||||
|
@ -342,19 +346,19 @@ void Settings::Load(SettingsInterface& si)
|
||||||
}
|
}
|
||||||
|
|
||||||
controller_types[i] = ParseControllerTypeName(si.GetStringValue(Controller::GetSettingsSection(i).c_str(), "Type",
|
controller_types[i] = ParseControllerTypeName(si.GetStringValue(Controller::GetSettingsSection(i).c_str(), "Type",
|
||||||
GetControllerTypeName(DEFAULT_CONTROLLER_2_TYPE))
|
GetControllerTypeName(DEFAULT_CONTROLLER_2_TYPE))
|
||||||
.c_str())
|
.c_str())
|
||||||
.value_or(DEFAULT_CONTROLLER_2_TYPE);
|
.value_or(DEFAULT_CONTROLLER_2_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
memory_card_types[0] =
|
memory_card_types[0] =
|
||||||
ParseMemoryCardTypeName(
|
ParseMemoryCardTypeName(
|
||||||
si.GetStringValue("MemoryCards", "Card1Type", GetMemoryCardTypeName(DEFAULT_MEMORY_CARD_1_TYPE)).c_str())
|
si.GetStringValue("MemoryCards", "Card1Type", GetMemoryCardTypeName(DEFAULT_MEMORY_CARD_1_TYPE)).c_str())
|
||||||
.value_or(DEFAULT_MEMORY_CARD_1_TYPE);
|
.value_or(DEFAULT_MEMORY_CARD_1_TYPE);
|
||||||
memory_card_types[1] =
|
memory_card_types[1] =
|
||||||
ParseMemoryCardTypeName(
|
ParseMemoryCardTypeName(
|
||||||
si.GetStringValue("MemoryCards", "Card2Type", GetMemoryCardTypeName(DEFAULT_MEMORY_CARD_2_TYPE)).c_str())
|
si.GetStringValue("MemoryCards", "Card2Type", GetMemoryCardTypeName(DEFAULT_MEMORY_CARD_2_TYPE)).c_str())
|
||||||
.value_or(DEFAULT_MEMORY_CARD_2_TYPE);
|
.value_or(DEFAULT_MEMORY_CARD_2_TYPE);
|
||||||
memory_card_paths[0] = si.GetStringValue("MemoryCards", "Card1Path", "");
|
memory_card_paths[0] = si.GetStringValue("MemoryCards", "Card1Path", "");
|
||||||
memory_card_paths[1] = si.GetStringValue("MemoryCards", "Card2Path", "");
|
memory_card_paths[1] = si.GetStringValue("MemoryCards", "Card2Path", "");
|
||||||
memory_card_use_playlist_title = si.GetBoolValue("MemoryCards", "UsePlaylistTitle", true);
|
memory_card_use_playlist_title = si.GetBoolValue("MemoryCards", "UsePlaylistTitle", true);
|
||||||
|
@ -372,7 +376,7 @@ void Settings::Load(SettingsInterface& si)
|
||||||
achievements_use_raintegration = si.GetBoolValue("Cheevos", "UseRAIntegration", false);
|
achievements_use_raintegration = si.GetBoolValue("Cheevos", "UseRAIntegration", false);
|
||||||
|
|
||||||
log_level = ParseLogLevelName(si.GetStringValue("Logging", "LogLevel", GetLogLevelName(DEFAULT_LOG_LEVEL)).c_str())
|
log_level = ParseLogLevelName(si.GetStringValue("Logging", "LogLevel", GetLogLevelName(DEFAULT_LOG_LEVEL)).c_str())
|
||||||
.value_or(DEFAULT_LOG_LEVEL);
|
.value_or(DEFAULT_LOG_LEVEL);
|
||||||
log_filter = si.GetStringValue("Logging", "LogFilter", "");
|
log_filter = si.GetStringValue("Logging", "LogFilter", "");
|
||||||
log_to_console = si.GetBoolValue("Logging", "LogToConsole", DEFAULT_LOG_TO_CONSOLE);
|
log_to_console = si.GetBoolValue("Logging", "LogToConsole", DEFAULT_LOG_TO_CONSOLE);
|
||||||
log_to_debug = si.GetBoolValue("Logging", "LogToDebug", false);
|
log_to_debug = si.GetBoolValue("Logging", "LogToDebug", false);
|
||||||
|
@ -462,6 +466,7 @@ void Settings::Save(SettingsInterface& si) const
|
||||||
si.SetBoolValue("GPU", "ScaledDithering", gpu_scaled_dithering);
|
si.SetBoolValue("GPU", "ScaledDithering", gpu_scaled_dithering);
|
||||||
si.SetStringValue("GPU", "TextureFilter", GetTextureFilterName(gpu_texture_filter));
|
si.SetStringValue("GPU", "TextureFilter", GetTextureFilterName(gpu_texture_filter));
|
||||||
si.SetStringValue("GPU", "DownsampleMode", GetDownsampleModeName(gpu_downsample_mode));
|
si.SetStringValue("GPU", "DownsampleMode", GetDownsampleModeName(gpu_downsample_mode));
|
||||||
|
si.SetStringValue("GPU", "WireframeMode", GetGPUWireframeModeName(gpu_wireframe_mode));
|
||||||
si.SetBoolValue("GPU", "DisableInterlacing", gpu_disable_interlacing);
|
si.SetBoolValue("GPU", "DisableInterlacing", gpu_disable_interlacing);
|
||||||
si.SetBoolValue("GPU", "ForceNTSCTimings", gpu_force_ntsc_timings);
|
si.SetBoolValue("GPU", "ForceNTSCTimings", gpu_force_ntsc_timings);
|
||||||
si.SetBoolValue("GPU", "WidescreenHack", gpu_widescreen_hack);
|
si.SetBoolValue("GPU", "WidescreenHack", gpu_widescreen_hack);
|
||||||
|
@ -1052,6 +1057,35 @@ const char* Settings::GetDownsampleModeDisplayName(GPUDownsampleMode mode)
|
||||||
return Host::TranslateToCString("GPUDownsampleMode", s_downsample_mode_display_names[static_cast<int>(mode)]);
|
return Host::TranslateToCString("GPUDownsampleMode", s_downsample_mode_display_names[static_cast<int>(mode)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static constexpr auto s_wireframe_mode_names = make_array("Disabled", "OverlayWireframe", "OnlyWireframe");
|
||||||
|
static constexpr auto s_wireframe_mode_display_names =
|
||||||
|
make_array(TRANSLATE_NOOP("GPUWireframeMode", "Disabled"), TRANSLATE_NOOP("GPUWireframeMode", "Overlay Wireframe"),
|
||||||
|
TRANSLATE_NOOP("GPUWireframeMode", "Only Wireframe"));
|
||||||
|
|
||||||
|
std::optional<GPUWireframeMode> Settings::ParseGPUWireframeMode(const char* str)
|
||||||
|
{
|
||||||
|
int index = 0;
|
||||||
|
for (const char* name : s_wireframe_mode_names)
|
||||||
|
{
|
||||||
|
if (StringUtil::Strcasecmp(name, str) == 0)
|
||||||
|
return static_cast<GPUWireframeMode>(index);
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* Settings::GetGPUWireframeModeName(GPUWireframeMode mode)
|
||||||
|
{
|
||||||
|
return s_wireframe_mode_names[static_cast<int>(mode)];
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* Settings::GetGPUWireframeModeDisplayName(GPUWireframeMode mode)
|
||||||
|
{
|
||||||
|
return Host::TranslateToCString("GPUWireframeMode", s_wireframe_mode_display_names[static_cast<int>(mode)]);
|
||||||
|
}
|
||||||
|
|
||||||
static std::array<const char*, 3> s_display_crop_mode_names = {{"None", "Overscan", "Borders"}};
|
static std::array<const char*, 3> s_display_crop_mode_names = {{"None", "Overscan", "Borders"}};
|
||||||
static std::array<const char*, 3> s_display_crop_mode_display_names = {
|
static std::array<const char*, 3> s_display_crop_mode_display_names = {
|
||||||
{TRANSLATE_NOOP("DisplayCropMode", "None"), TRANSLATE_NOOP("DisplayCropMode", "Only Overscan Area"),
|
{TRANSLATE_NOOP("DisplayCropMode", "None"), TRANSLATE_NOOP("DisplayCropMode", "Only Overscan Area"),
|
||||||
|
@ -1082,9 +1116,8 @@ const char* Settings::GetDisplayCropModeDisplayName(DisplayCropMode crop_mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::array<const char*, static_cast<size_t>(DisplayAspectRatio::Count)> s_display_aspect_ratio_names = {
|
static std::array<const char*, static_cast<size_t>(DisplayAspectRatio::Count)> s_display_aspect_ratio_names = {
|
||||||
{TRANSLATE_NOOP("DisplayAspectRatio", "Auto (Game Native)"),
|
{TRANSLATE_NOOP("DisplayAspectRatio", "Auto (Game Native)"), TRANSLATE_NOOP("DisplayAspectRatio", "Stretch To Fill"),
|
||||||
TRANSLATE_NOOP("DisplayAspectRatio", "Stretch To Fill"), TRANSLATE_NOOP("DisplayAspectRatio", "Custom"), "4:3",
|
TRANSLATE_NOOP("DisplayAspectRatio", "Custom"), "4:3", "16:9", "19:9", "20:9", "PAR 1:1"}};
|
||||||
"16:9", "19:9", "20:9", "PAR 1:1"}};
|
|
||||||
static constexpr std::array<float, static_cast<size_t>(DisplayAspectRatio::Count)> s_display_aspect_ratio_values = {
|
static constexpr std::array<float, static_cast<size_t>(DisplayAspectRatio::Count)> s_display_aspect_ratio_values = {
|
||||||
{-1.0f, -1.0f, -1.0f, 4.0f / 3.0f, 16.0f / 9.0f, 19.0f / 9.0f, 20.0f / 9.0f, -1.0f}};
|
{-1.0f, -1.0f, -1.0f, 4.0f / 3.0f, 16.0f / 9.0f, 19.0f / 9.0f, 20.0f / 9.0f, -1.0f}};
|
||||||
|
|
||||||
|
|
|
@ -106,6 +106,7 @@ struct Settings
|
||||||
bool gpu_scaled_dithering = true;
|
bool gpu_scaled_dithering = true;
|
||||||
GPUTextureFilter gpu_texture_filter = DEFAULT_GPU_TEXTURE_FILTER;
|
GPUTextureFilter gpu_texture_filter = DEFAULT_GPU_TEXTURE_FILTER;
|
||||||
GPUDownsampleMode gpu_downsample_mode = DEFAULT_GPU_DOWNSAMPLE_MODE;
|
GPUDownsampleMode gpu_downsample_mode = DEFAULT_GPU_DOWNSAMPLE_MODE;
|
||||||
|
GPUWireframeMode gpu_wireframe_mode = DEFAULT_GPU_WIREFRAME_MODE;
|
||||||
bool gpu_disable_interlacing = true;
|
bool gpu_disable_interlacing = true;
|
||||||
bool gpu_force_ntsc_timings = false;
|
bool gpu_force_ntsc_timings = false;
|
||||||
bool gpu_widescreen_hack = false;
|
bool gpu_widescreen_hack = false;
|
||||||
|
@ -373,6 +374,10 @@ struct Settings
|
||||||
static const char* GetDownsampleModeName(GPUDownsampleMode mode);
|
static const char* GetDownsampleModeName(GPUDownsampleMode mode);
|
||||||
static const char* GetDownsampleModeDisplayName(GPUDownsampleMode mode);
|
static const char* GetDownsampleModeDisplayName(GPUDownsampleMode mode);
|
||||||
|
|
||||||
|
static std::optional<GPUWireframeMode> ParseGPUWireframeMode(const char* str);
|
||||||
|
static const char* GetGPUWireframeModeName(GPUWireframeMode mode);
|
||||||
|
static const char* GetGPUWireframeModeDisplayName(GPUWireframeMode mode);
|
||||||
|
|
||||||
static std::optional<DisplayCropMode> ParseDisplayCropMode(const char* str);
|
static std::optional<DisplayCropMode> ParseDisplayCropMode(const char* str);
|
||||||
static const char* GetDisplayCropModeName(DisplayCropMode crop_mode);
|
static const char* GetDisplayCropModeName(DisplayCropMode crop_mode);
|
||||||
static const char* GetDisplayCropModeDisplayName(DisplayCropMode crop_mode);
|
static const char* GetDisplayCropModeDisplayName(DisplayCropMode crop_mode);
|
||||||
|
@ -421,6 +426,7 @@ struct Settings
|
||||||
#endif
|
#endif
|
||||||
static constexpr GPUTextureFilter DEFAULT_GPU_TEXTURE_FILTER = GPUTextureFilter::Nearest;
|
static constexpr GPUTextureFilter DEFAULT_GPU_TEXTURE_FILTER = GPUTextureFilter::Nearest;
|
||||||
static constexpr GPUDownsampleMode DEFAULT_GPU_DOWNSAMPLE_MODE = GPUDownsampleMode::Disabled;
|
static constexpr GPUDownsampleMode DEFAULT_GPU_DOWNSAMPLE_MODE = GPUDownsampleMode::Disabled;
|
||||||
|
static constexpr GPUWireframeMode DEFAULT_GPU_WIREFRAME_MODE = GPUWireframeMode::Disabled;
|
||||||
static constexpr ConsoleRegion DEFAULT_CONSOLE_REGION = ConsoleRegion::Auto;
|
static constexpr ConsoleRegion DEFAULT_CONSOLE_REGION = ConsoleRegion::Auto;
|
||||||
static constexpr float DEFAULT_GPU_PGXP_DEPTH_THRESHOLD = 300.0f;
|
static constexpr float DEFAULT_GPU_PGXP_DEPTH_THRESHOLD = 300.0f;
|
||||||
static constexpr float GPU_PGXP_DEPTH_THRESHOLD_SCALE = 4096.0f;
|
static constexpr float GPU_PGXP_DEPTH_THRESHOLD_SCALE = 4096.0f;
|
||||||
|
|
|
@ -3589,6 +3589,7 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
|
||||||
g_settings.gpu_force_ntsc_timings != old_settings.gpu_force_ntsc_timings ||
|
g_settings.gpu_force_ntsc_timings != old_settings.gpu_force_ntsc_timings ||
|
||||||
g_settings.gpu_24bit_chroma_smoothing != old_settings.gpu_24bit_chroma_smoothing ||
|
g_settings.gpu_24bit_chroma_smoothing != old_settings.gpu_24bit_chroma_smoothing ||
|
||||||
g_settings.gpu_downsample_mode != old_settings.gpu_downsample_mode ||
|
g_settings.gpu_downsample_mode != old_settings.gpu_downsample_mode ||
|
||||||
|
g_settings.gpu_wireframe_mode != old_settings.gpu_wireframe_mode ||
|
||||||
g_settings.display_crop_mode != old_settings.display_crop_mode ||
|
g_settings.display_crop_mode != old_settings.display_crop_mode ||
|
||||||
g_settings.display_aspect_ratio != old_settings.display_aspect_ratio ||
|
g_settings.display_aspect_ratio != old_settings.display_aspect_ratio ||
|
||||||
g_settings.display_alignment != old_settings.display_alignment ||
|
g_settings.display_alignment != old_settings.display_alignment ||
|
||||||
|
|
|
@ -95,6 +95,14 @@ enum class GPUDownsampleMode : u8
|
||||||
Count
|
Count
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class GPUWireframeMode : u8
|
||||||
|
{
|
||||||
|
Disabled,
|
||||||
|
OverlayWireframe,
|
||||||
|
OnlyWireframe,
|
||||||
|
Count,
|
||||||
|
};
|
||||||
|
|
||||||
enum class DisplayCropMode : u8
|
enum class DisplayCropMode : u8
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
|
|
|
@ -279,6 +279,11 @@ void AdvancedSettingsWidget::addTweakOptions()
|
||||||
|
|
||||||
addMSAATweakOption(m_dialog, m_ui.tweakOptionTable, tr("Multisample Antialiasing"));
|
addMSAATweakOption(m_dialog, m_ui.tweakOptionTable, tr("Multisample Antialiasing"));
|
||||||
|
|
||||||
|
addChoiceTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Wireframe Mode"), "GPU", "WireframeMode",
|
||||||
|
Settings::ParseGPUWireframeMode, Settings::GetGPUWireframeModeName,
|
||||||
|
Settings::GetGPUWireframeModeDisplayName, static_cast<u32>(GPUWireframeMode::Count),
|
||||||
|
GPUWireframeMode::Disabled);
|
||||||
|
|
||||||
if (m_dialog->isPerGameSettings())
|
if (m_dialog->isPerGameSettings())
|
||||||
{
|
{
|
||||||
addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Display Active Start Offset"), "Display",
|
addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Display Active Start Offset"), "Display",
|
||||||
|
@ -365,6 +370,7 @@ void AdvancedSettingsWidget::onResetToDefaultClicked()
|
||||||
setBooleanTweakOption(m_ui.tweakOptionTable, i++, true); // Apply compatibility settings
|
setBooleanTweakOption(m_ui.tweakOptionTable, i++, true); // Apply compatibility settings
|
||||||
setIntRangeTweakOption(m_ui.tweakOptionTable, i++, 0); // Display FPS limit
|
setIntRangeTweakOption(m_ui.tweakOptionTable, i++, 0); // Display FPS limit
|
||||||
setChoiceTweakOption(m_ui.tweakOptionTable, i++, 0); // Multisample antialiasing
|
setChoiceTweakOption(m_ui.tweakOptionTable, i++, 0); // Multisample antialiasing
|
||||||
|
setChoiceTweakOption(m_ui.tweakOptionTable, i++, 0); // Wireframe mode
|
||||||
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // PGXP vertex cache
|
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // PGXP vertex cache
|
||||||
setFloatRangeTweakOption(m_ui.tweakOptionTable, i++, -1.0f); // PGXP geometry tolerance
|
setFloatRangeTweakOption(m_ui.tweakOptionTable, i++, -1.0f); // PGXP geometry tolerance
|
||||||
setFloatRangeTweakOption(m_ui.tweakOptionTable, i++,
|
setFloatRangeTweakOption(m_ui.tweakOptionTable, i++,
|
||||||
|
|
|
@ -489,41 +489,44 @@ void ShaderGen::DeclareFragmentEntryPoint(
|
||||||
{
|
{
|
||||||
if (m_glsl)
|
if (m_glsl)
|
||||||
{
|
{
|
||||||
if (m_use_glsl_interface_blocks)
|
if (num_color_inputs > 0 || num_texcoord_inputs > 0 || additional_inputs.size() > 0)
|
||||||
{
|
{
|
||||||
const char* qualifier = GetInterpolationQualifier(true, msaa, ssaa, false);
|
if (m_use_glsl_interface_blocks)
|
||||||
|
|
||||||
if (m_spirv)
|
|
||||||
ss << "layout(location = 0) ";
|
|
||||||
|
|
||||||
ss << "in VertexData {\n";
|
|
||||||
for (u32 i = 0; i < num_color_inputs; i++)
|
|
||||||
ss << " " << qualifier << (noperspective_color ? "noperspective " : "") << "float4 v_col" << i << ";\n";
|
|
||||||
|
|
||||||
for (u32 i = 0; i < num_texcoord_inputs; i++)
|
|
||||||
ss << " " << qualifier << "float2 v_tex" << i << ";\n";
|
|
||||||
|
|
||||||
for (const auto& [qualifiers, name] : additional_inputs)
|
|
||||||
{
|
{
|
||||||
const char* qualifier_to_use = (std::strlen(qualifiers) > 0) ? qualifiers : qualifier;
|
const char* qualifier = GetInterpolationQualifier(true, msaa, ssaa, false);
|
||||||
ss << " " << qualifier_to_use << " " << name << ";\n";
|
|
||||||
|
if (m_spirv)
|
||||||
|
ss << "layout(location = 0) ";
|
||||||
|
|
||||||
|
ss << "in VertexData {\n";
|
||||||
|
for (u32 i = 0; i < num_color_inputs; i++)
|
||||||
|
ss << " " << qualifier << (noperspective_color ? "noperspective " : "") << "float4 v_col" << i << ";\n";
|
||||||
|
|
||||||
|
for (u32 i = 0; i < num_texcoord_inputs; i++)
|
||||||
|
ss << " " << qualifier << "float2 v_tex" << i << ";\n";
|
||||||
|
|
||||||
|
for (const auto& [qualifiers, name] : additional_inputs)
|
||||||
|
{
|
||||||
|
const char* qualifier_to_use = (std::strlen(qualifiers) > 0) ? qualifiers : qualifier;
|
||||||
|
ss << " " << qualifier_to_use << " " << name << ";\n";
|
||||||
|
}
|
||||||
|
ss << "};\n";
|
||||||
}
|
}
|
||||||
ss << "};\n";
|
else
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const char* qualifier = GetInterpolationQualifier(false, msaa, ssaa, false);
|
|
||||||
|
|
||||||
for (u32 i = 0; i < num_color_inputs; i++)
|
|
||||||
ss << qualifier << (noperspective_color ? "noperspective " : "") << "in float4 v_col" << i << ";\n";
|
|
||||||
|
|
||||||
for (u32 i = 0; i < num_texcoord_inputs; i++)
|
|
||||||
ss << qualifier << "in float2 v_tex" << i << ";\n";
|
|
||||||
|
|
||||||
for (const auto& [qualifiers, name] : additional_inputs)
|
|
||||||
{
|
{
|
||||||
const char* qualifier_to_use = (std::strlen(qualifiers) > 0) ? qualifiers : qualifier;
|
const char* qualifier = GetInterpolationQualifier(false, msaa, ssaa, false);
|
||||||
ss << qualifier_to_use << " in " << name << ";\n";
|
|
||||||
|
for (u32 i = 0; i < num_color_inputs; i++)
|
||||||
|
ss << qualifier << (noperspective_color ? "noperspective " : "") << "in float4 v_col" << i << ";\n";
|
||||||
|
|
||||||
|
for (u32 i = 0; i < num_texcoord_inputs; i++)
|
||||||
|
ss << qualifier << "in float2 v_tex" << i << ";\n";
|
||||||
|
|
||||||
|
for (const auto& [qualifiers, name] : additional_inputs)
|
||||||
|
{
|
||||||
|
const char* qualifier_to_use = (std::strlen(qualifiers) > 0) ? qualifiers : qualifier;
|
||||||
|
ss << qualifier_to_use << " in " << name << ";\n";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue