From 7ad1b8d093ccfaeb250de35ffb4c85e5bfce54a2 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 2 Sep 2023 22:26:03 +1000 Subject: [PATCH] GPU: Add wireframe rendering/overlay --- src/core/fullscreen_ui.cpp | 9 +- src/core/gpu_hw.cpp | 115 ++++++++++++++---- src/core/gpu_hw.h | 19 ++- src/core/gpu_hw_shadergen.cpp | 90 ++++++++++++++ src/core/gpu_hw_shadergen.h | 2 + src/core/settings.cpp | 87 +++++++++---- src/core/settings.h | 6 + src/core/system.cpp | 1 + src/core/types.h | 8 ++ src/duckstation-qt/advancedsettingswidget.cpp | 6 + src/util/shadergen.cpp | 65 +++++----- 11 files changed, 314 insertions(+), 94 deletions(-) diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index 2ed6856eb..1c728ec6d 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -2872,8 +2872,8 @@ void FullscreenUI::DrawBIOSSettingsPage() FSUI_CSTR("Patches the BIOS to skip the boot animation. Safe to enable."), "BIOS", "PatchFastBoot", Settings::DEFAULT_FAST_BOOT_VALUE); DrawToggleSetting(bsi, FSUI_CSTR("Enable TTY Logging"), - FSUI_CSTR("Logs BIOS calls to printf(). Not all games contain debugging messages."), - "BIOS", "TTYLogging", false); + FSUI_CSTR("Logs BIOS calls to printf(). Not all games contain debugging messages."), "BIOS", + "TTYLogging", false); EndMenuButtons(); } @@ -4695,6 +4695,11 @@ void FullscreenUI::DrawAdvancedSettingsPage() bsi, FSUI_CSTR("Stretch Display Vertically"), FSUI_CSTR("Stretches the display to match the aspect ratio by multiplying vertically instead of horizontally."), "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")); diff --git a/src/core/gpu_hw.cpp b/src/core/gpu_hw.cpp index 28eb9d62c..1f62c1853 100644 --- a/src/core/gpu_hw.cpp +++ b/src/core/gpu_hw.cpp @@ -45,6 +45,11 @@ ALWAYS_INLINE static constexpr std::tuple MinMax(T v1, T v2) return std::tie(v1, v2); } +ALWAYS_INLINE static u32 GetMaxResolutionScale() +{ + return g_gpu_device->GetMaxTextureSize() / VRAM_WIDTH; +} + ALWAYS_INLINE static bool ShouldUseUVLimits() { // 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. -static bool TextureFilterRequiresDualSourceBlend(GPUTextureFilter filter) +ALWAYS_INLINE static bool TextureFilterRequiresDualSourceBlend(GPUTextureFilter filter) { return (filter == GPUTextureFilter::Bilinear || filter == GPUTextureFilter::JINC2 || filter == GPUTextureFilter::xBR); } @@ -148,42 +153,50 @@ bool GPU_HW::Initialize() return false; 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_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_scaled_dithering = g_settings.gpu_scaled_dithering; m_texture_filtering = g_settings.gpu_texture_filter; m_using_uv_limits = ShouldUseUVLimits(); m_chroma_smoothing = g_settings.gpu_24bit_chroma_smoothing; 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) { - 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); } 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); } - if (!m_supports_dual_source_blend && TextureFilterRequiresDualSourceBlend(m_texture_filtering)) + if (!features.dual_source_blend && TextureFilterRequiresDualSourceBlend(m_texture_filtering)) { 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)); 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."); + 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(); UpdateSoftwareRenderer(false); @@ -290,12 +303,16 @@ void GPU_HW::UpdateSettings(const Settings& old_settings) { GPU::UpdateSettings(old_settings); + const GPUDevice::Features features = g_gpu_device->GetFeatures(); + const u32 resolution_scale = CalculateResolutionScale(); 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 GPUWireframeMode wireframe_mode = + features.geometry_shaders ? g_settings.gpu_wireframe_mode : GPUWireframeMode::Disabled; 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 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_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_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); 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_chroma_smoothing = g_settings.gpu_24bit_chroma_smoothing; m_downsample_mode = downsample_mode; + m_wireframe_mode = wireframe_mode; m_disable_color_perspective = disable_color_perspective; 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 { + const u32 max_resolution_scale = GetMaxResolutionScale(); + u32 scale; if (g_settings.gpu_resolution_scale != 0) { - scale = std::clamp(g_settings.gpu_resolution_scale, 1, m_max_resolution_scale); + scale = std::clamp(g_settings.gpu_resolution_scale, 1, max_resolution_scale); } else { @@ -404,7 +425,7 @@ u32 GPU_HW::CalculateResolutionScale() const static_cast(std::ceil(static_cast(g_gpu_device->GetWindowHeight()) / height)); Log_InfoPrintf("Height = %d, preferred scale = %d", height, preferred_scale); - scale = static_cast(std::clamp(preferred_scale, 1, m_max_resolution_scale)); + scale = static_cast(std::clamp(preferred_scale, 1, max_resolution_scale)); } if (g_settings.gpu_downsample_mode == GPUDownsampleMode::Adaptive && scale > 1 && !Common::IsPow2(scale)) @@ -474,7 +495,7 @@ std::tuple GPU_HW::GetFullDisplayResolution(bool scaled /* = true */) void GPU_HW::PrintSettingsToLog() { 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("Dithering: %s%s", m_true_color ? "Disabled" : "Enabled", (!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("Depth buffer: %s", m_pgxp_depth_buffer ? "YES" : "NO"); 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"); } @@ -772,6 +794,39 @@ bool GPU_HW::CompilePipelines() } } + if (m_wireframe_mode != GPUWireframeMode::Disabled) + { + std::unique_ptr gs = + g_gpu_device->CreateShader(GPUShaderStage::Geometry, shadergen.GenerateWireframeGeometryShader()); + std::unique_ptr 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(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(); std::unique_ptr fullscreen_quad_vertex_shader = @@ -1026,6 +1081,8 @@ void GPU_HW::DestroyPipelines() { static constexpr auto destroy = [](std::unique_ptr& p) { p.reset(); }; + m_wireframe_pipeline.reset(); + m_batch_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; } -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] const u8 depth_test = m_batch.use_depth_buffer ? static_cast(2) : BoolToUInt8(m_batch.check_mask_before_draw); @@ -2365,16 +2422,26 @@ void GPU_HW::FlushRender() m_batch_ubo_dirty = false; } - if (NeedsTwoPassRendering()) + if (m_wireframe_mode != GPUWireframeMode::OnlyWireframe) { - m_renderer_stats.num_batches += 2; - DrawBatchVertices(BatchRenderMode::OnlyOpaque, m_batch_base_vertex, vertex_count); - DrawBatchVertices(BatchRenderMode::OnlyTransparent, m_batch_base_vertex, vertex_count); + if (NeedsTwoPassRendering()) + { + 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++; - 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); } } diff --git a/src/core/gpu_hw.h b/src/core/gpu_hw.h index 8dd320d83..edf25862a 100644 --- a/src/core/gpu_hw.h +++ b/src/core/gpu_hw.h @@ -157,7 +157,7 @@ private: void SetScissor(); void MapBatchVertexPointer(u32 required_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 UpdateDisplay() override; @@ -266,24 +266,22 @@ private: u32 m_resolution_scale = 1; u32 m_multisamples = 1; - u32 m_max_resolution_scale = 1; - bool m_true_color = true; union { - BitField m_supports_per_sample_shading; - BitField m_supports_dual_source_blend; - BitField m_supports_disable_color_perspective; - BitField m_per_sample_shading; - BitField m_scaled_dithering; - BitField m_chroma_smoothing; - BitField m_disable_color_perspective; + BitField m_supports_dual_source_blend; + BitField m_per_sample_shading; + BitField m_scaled_dithering; + BitField m_chroma_smoothing; + BitField m_disable_color_perspective; u8 bits = 0; }; GPUTextureFilter m_texture_filtering = GPUTextureFilter::Nearest; 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_pgxp_depth_buffer = false; @@ -298,6 +296,7 @@ private: // [depth_test][render_mode][texture_mode][transparency_mode][dithering][interlacing] DimensionalArray, 2, 2, 5, 9, 4, 3> m_batch_pipelines{}; + std::unique_ptr m_wireframe_pipeline; // [wrapped][interlaced] DimensionalArray, 2, 2> m_vram_fill_pipelines{}; diff --git a/src/core/gpu_hw_shadergen.cpp b/src/core/gpu_hw_shadergen.cpp index bc32c2ed4..ded3bccf0 100644 --- a/src/core/gpu_hw_shadergen.cpp +++ b/src/core/gpu_hw_shadergen.cpp @@ -1087,6 +1087,96 @@ float3 SampleVRAM24Smoothed(uint2 icoords) 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 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::stringstream ss; diff --git a/src/core/gpu_hw_shadergen.h b/src/core/gpu_hw_shadergen.h index 8254c8fe3..82fd5a49d 100644 --- a/src/core/gpu_hw_shadergen.h +++ b/src/core/gpu_hw_shadergen.h @@ -18,6 +18,8 @@ public: bool dithering, bool interlacing); std::string GenerateDisplayFragmentShader(bool depth_24bit, GPU_HW::InterlacedRenderMode interlace_mode, bool smooth_chroma); + std::string GenerateWireframeGeometryShader(); + std::string GenerateWireframeFragmentShader(); std::string GenerateVRAMReadFragmentShader(); std::string GenerateVRAMWriteFragmentShader(bool use_ssbo); std::string GenerateVRAMCopyFragmentShader(); diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 2cf782936..6e39f6393 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -161,7 +161,7 @@ void Settings::Load(SettingsInterface& si) region = ParseConsoleRegionName( 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); emulation_speed = si.GetFloatValue("Main", "EmulationSpeed", 1.0f); @@ -192,7 +192,7 @@ void Settings::Load(SettingsInterface& si) cpu_execution_mode = ParseCPUExecutionMode( 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_denominator = std::max(si.GetIntValue("CPU", "OverclockDenominator", 1), 1); 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_icache = si.GetBoolValue("CPU", "RecompilerICache", false); cpu_fastmem_mode = ParseCPUFastmemMode( - si.GetStringValue("CPU", "FastmemMode", GetCPUFastmemModeName(DEFAULT_CPU_FASTMEM_MODE)).c_str()) - .value_or(DEFAULT_CPU_FASTMEM_MODE); + si.GetStringValue("CPU", "FastmemMode", GetCPUFastmemModeName(DEFAULT_CPU_FASTMEM_MODE)).c_str()) + .value_or(DEFAULT_CPU_FASTMEM_MODE); 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_resolution_scale = static_cast(si.GetIntValue("GPU", "ResolutionScale", 1)); gpu_multisamples = static_cast(si.GetIntValue("GPU", "Multisamples", 1)); @@ -220,11 +220,15 @@ void Settings::Load(SettingsInterface& si) gpu_texture_filter = ParseTextureFilterName( 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 = ParseDownsampleModeName( 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_force_ntsc_timings = si.GetBoolValue("GPU", "ForceNTSCTimings", false); gpu_widescreen_hack = si.GetBoolValue("GPU", "WidescreenHack", false); @@ -243,11 +247,11 @@ void Settings::Load(SettingsInterface& si) display_crop_mode = ParseDisplayCropMode( 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 = ParseDisplayAspectRatio( 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( std::clamp(si.GetIntValue("Display", "CustomAspectRatioNumerator", 4), 1, std::numeric_limits::max())); display_aspect_ratio_custom_denominator = static_cast( @@ -255,10 +259,10 @@ void Settings::Load(SettingsInterface& si) display_alignment = ParseDisplayAlignment( si.GetStringValue("Display", "Alignment", GetDisplayAlignmentName(DEFAULT_DISPLAY_ALIGNMENT)).c_str()) - .value_or(DEFAULT_DISPLAY_ALIGNMENT); + .value_or(DEFAULT_DISPLAY_ALIGNMENT); display_scaling = 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_active_start_offset = static_cast(si.GetIntValue("Display", "ActiveStartOffset", 0)); display_active_end_offset = static_cast(si.GetIntValue("Display", "ActiveEndOffset", 0)); @@ -292,13 +296,13 @@ void Settings::Load(SettingsInterface& si) audio_backend = 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_output_device = si.GetStringValue("Audio", "OutputDevice"); audio_stretch_mode = AudioStream::ParseStretchMode( 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_buffer_ms = si.GetUIntValue("Audio", "BufferMS", DEFAULT_AUDIO_BUFFER_MS); audio_output_volume = si.GetUIntValue("Audio", "OutputVolume", 100); @@ -323,14 +327,14 @@ void Settings::Load(SettingsInterface& si) multitap_mode = ParseMultitapModeName( 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", - GetControllerTypeName(DEFAULT_CONTROLLER_1_TYPE)) - .c_str()) - .value_or(DEFAULT_CONTROLLER_1_TYPE); + GetControllerTypeName(DEFAULT_CONTROLLER_1_TYPE)) + .c_str()) + .value_or(DEFAULT_CONTROLLER_1_TYPE); - const std::array mtap_enabled = { {IsPort1MultitapEnabled(), IsPort2MultitapEnabled()} }; + const std::array mtap_enabled = {{IsPort1MultitapEnabled(), IsPort2MultitapEnabled()}}; for (u32 i = 1; i < NUM_CONTROLLER_AND_CARD_PORTS; i++) { // 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", - GetControllerTypeName(DEFAULT_CONTROLLER_2_TYPE)) - .c_str()) - .value_or(DEFAULT_CONTROLLER_2_TYPE); + GetControllerTypeName(DEFAULT_CONTROLLER_2_TYPE)) + .c_str()) + .value_or(DEFAULT_CONTROLLER_2_TYPE); } memory_card_types[0] = ParseMemoryCardTypeName( 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] = ParseMemoryCardTypeName( 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[1] = si.GetStringValue("MemoryCards", "Card2Path", ""); 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); 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_to_console = si.GetBoolValue("Logging", "LogToConsole", DEFAULT_LOG_TO_CONSOLE); 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.SetStringValue("GPU", "TextureFilter", GetTextureFilterName(gpu_texture_filter)); 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", "ForceNTSCTimings", gpu_force_ntsc_timings); 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(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 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(index); + + index++; + } + + return std::nullopt; +} + +const char* Settings::GetGPUWireframeModeName(GPUWireframeMode mode) +{ + return s_wireframe_mode_names[static_cast(mode)]; +} + +const char* Settings::GetGPUWireframeModeDisplayName(GPUWireframeMode mode) +{ + return Host::TranslateToCString("GPUWireframeMode", s_wireframe_mode_display_names[static_cast(mode)]); +} + static std::array s_display_crop_mode_names = {{"None", "Overscan", "Borders"}}; static std::array s_display_crop_mode_display_names = { {TRANSLATE_NOOP("DisplayCropMode", "None"), TRANSLATE_NOOP("DisplayCropMode", "Only Overscan Area"), @@ -1082,9 +1116,8 @@ const char* Settings::GetDisplayCropModeDisplayName(DisplayCropMode crop_mode) } static std::array(DisplayAspectRatio::Count)> s_display_aspect_ratio_names = { - {TRANSLATE_NOOP("DisplayAspectRatio", "Auto (Game Native)"), - TRANSLATE_NOOP("DisplayAspectRatio", "Stretch To Fill"), TRANSLATE_NOOP("DisplayAspectRatio", "Custom"), "4:3", - "16:9", "19:9", "20:9", "PAR 1:1"}}; + {TRANSLATE_NOOP("DisplayAspectRatio", "Auto (Game Native)"), TRANSLATE_NOOP("DisplayAspectRatio", "Stretch To Fill"), + TRANSLATE_NOOP("DisplayAspectRatio", "Custom"), "4:3", "16:9", "19:9", "20:9", "PAR 1:1"}}; static constexpr std::array(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}}; diff --git a/src/core/settings.h b/src/core/settings.h index bd0a4b596..bb20cef8d 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -106,6 +106,7 @@ struct Settings bool gpu_scaled_dithering = true; GPUTextureFilter gpu_texture_filter = DEFAULT_GPU_TEXTURE_FILTER; GPUDownsampleMode gpu_downsample_mode = DEFAULT_GPU_DOWNSAMPLE_MODE; + GPUWireframeMode gpu_wireframe_mode = DEFAULT_GPU_WIREFRAME_MODE; bool gpu_disable_interlacing = true; bool gpu_force_ntsc_timings = false; bool gpu_widescreen_hack = false; @@ -373,6 +374,10 @@ struct Settings static const char* GetDownsampleModeName(GPUDownsampleMode mode); static const char* GetDownsampleModeDisplayName(GPUDownsampleMode mode); + static std::optional ParseGPUWireframeMode(const char* str); + static const char* GetGPUWireframeModeName(GPUWireframeMode mode); + static const char* GetGPUWireframeModeDisplayName(GPUWireframeMode mode); + static std::optional ParseDisplayCropMode(const char* str); static const char* GetDisplayCropModeName(DisplayCropMode crop_mode); static const char* GetDisplayCropModeDisplayName(DisplayCropMode crop_mode); @@ -421,6 +426,7 @@ struct Settings #endif static constexpr GPUTextureFilter DEFAULT_GPU_TEXTURE_FILTER = GPUTextureFilter::Nearest; 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 float DEFAULT_GPU_PGXP_DEPTH_THRESHOLD = 300.0f; static constexpr float GPU_PGXP_DEPTH_THRESHOLD_SCALE = 4096.0f; diff --git a/src/core/system.cpp b/src/core/system.cpp index ad05f0f2c..d7bda6928 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -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_24bit_chroma_smoothing != old_settings.gpu_24bit_chroma_smoothing || 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_aspect_ratio != old_settings.display_aspect_ratio || g_settings.display_alignment != old_settings.display_alignment || diff --git a/src/core/types.h b/src/core/types.h index 91ac0c35d..a67ff2c12 100644 --- a/src/core/types.h +++ b/src/core/types.h @@ -95,6 +95,14 @@ enum class GPUDownsampleMode : u8 Count }; +enum class GPUWireframeMode : u8 +{ + Disabled, + OverlayWireframe, + OnlyWireframe, + Count, +}; + enum class DisplayCropMode : u8 { None, diff --git a/src/duckstation-qt/advancedsettingswidget.cpp b/src/duckstation-qt/advancedsettingswidget.cpp index 67e032cb1..24c56215a 100644 --- a/src/duckstation-qt/advancedsettingswidget.cpp +++ b/src/duckstation-qt/advancedsettingswidget.cpp @@ -279,6 +279,11 @@ void AdvancedSettingsWidget::addTweakOptions() 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(GPUWireframeMode::Count), + GPUWireframeMode::Disabled); + if (m_dialog->isPerGameSettings()) { 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 setIntRangeTweakOption(m_ui.tweakOptionTable, i++, 0); // Display FPS limit 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 setFloatRangeTweakOption(m_ui.tweakOptionTable, i++, -1.0f); // PGXP geometry tolerance setFloatRangeTweakOption(m_ui.tweakOptionTable, i++, diff --git a/src/util/shadergen.cpp b/src/util/shadergen.cpp index a2de5908e..8fe06f167 100644 --- a/src/util/shadergen.cpp +++ b/src/util/shadergen.cpp @@ -489,41 +489,44 @@ void ShaderGen::DeclareFragmentEntryPoint( { 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_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) + if (m_use_glsl_interface_blocks) { - const char* qualifier_to_use = (std::strlen(qualifiers) > 0) ? qualifiers : qualifier; - ss << " " << qualifier_to_use << " " << name << ";\n"; + const char* qualifier = GetInterpolationQualifier(true, msaa, ssaa, false); + + 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 - { - 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) + else { - const char* qualifier_to_use = (std::strlen(qualifiers) > 0) ? qualifiers : qualifier; - ss << qualifier_to_use << " in " << name << ";\n"; + 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; + ss << qualifier_to_use << " in " << name << ";\n"; + } } }