From 34f20798a1b8b191100b3041a501605fc69b5369 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Mon, 17 Jun 2024 15:49:55 +1000 Subject: [PATCH] GPU/HW: Add separate 3D/2D (sprite) texture filtering --- src/core/game_database.cpp | 4 +- src/core/gpu_hw.cpp | 177 ++++++++++++++---- src/core/gpu_hw.h | 11 ++ src/core/gpu_hw_shadergen.cpp | 33 ++-- src/core/gpu_hw_shadergen.h | 10 +- src/core/imgui_overlays.cpp | 18 +- src/core/settings.cpp | 7 + src/core/settings.h | 1 + src/core/system.cpp | 1 + src/duckstation-qt/graphicssettingswidget.cpp | 5 + src/duckstation-qt/graphicssettingswidget.ui | 36 ++-- src/duckstation-qt/settingswindow.ui | 2 +- 12 files changed, 227 insertions(+), 78 deletions(-) diff --git a/src/core/game_database.cpp b/src/core/game_database.cpp index 3c8de1291..939914db8 100644 --- a/src/core/game_database.cpp +++ b/src/core/game_database.cpp @@ -479,7 +479,8 @@ void GameDatabase::Entry::ApplySettings(Settings& settings, bool display_osd_mes if (HasTrait(Trait::DisableTextureFiltering)) { - if (display_osd_messages && settings.gpu_texture_filter != GPUTextureFilter::Nearest) + if (display_osd_messages && (settings.gpu_texture_filter != GPUTextureFilter::Nearest || + g_settings.gpu_sprite_texture_filter != GPUTextureFilter::Nearest)) { Host::AddIconOSDMessage("gamedb_disable_upscaling", ICON_FA_MAGIC, TRANSLATE_STR("OSDMessage", "Texture filtering disabled by compatibility settings."), @@ -487,6 +488,7 @@ void GameDatabase::Entry::ApplySettings(Settings& settings, bool display_osd_mes } settings.gpu_texture_filter = GPUTextureFilter::Nearest; + settings.gpu_sprite_texture_filter = GPUTextureFilter::Nearest; } if (HasTrait(Trait::DisableScaledDithering)) diff --git a/src/core/gpu_hw.cpp b/src/core/gpu_hw.cpp index 893753f46..9f72f44f8 100644 --- a/src/core/gpu_hw.cpp +++ b/src/core/gpu_hw.cpp @@ -59,10 +59,17 @@ ALWAYS_INLINE_RELEASE static u32 GetBoxDownsampleScale(u32 resolution_scale) return scale; } -ALWAYS_INLINE static bool ShouldClampUVs() +ALWAYS_INLINE static bool ShouldClampUVs(GPUTextureFilter texture_filter) { // We only need UV limits if PGXP is enabled, or texture filtering is enabled. - return g_settings.gpu_pgxp_enable || g_settings.gpu_texture_filter != GPUTextureFilter::Nearest; + return g_settings.gpu_pgxp_enable || texture_filter != GPUTextureFilter::Nearest; +} + +ALWAYS_INLINE static bool ShouldAllowSpriteMode(u8 resolution_scale, GPUTextureFilter texture_filter, + GPUTextureFilter sprite_texture_filter) +{ + // Use sprite shaders/mode when texcoord rounding is forced, or if the filters are different. + return (sprite_texture_filter != texture_filter || (resolution_scale > 1 && g_settings.gpu_force_round_texcoords)); } ALWAYS_INLINE static bool ShouldDisableColorPerspective() @@ -73,7 +80,16 @@ ALWAYS_INLINE static bool ShouldDisableColorPerspective() /// Returns true if the specified texture filtering mode requires dual-source blending. ALWAYS_INLINE static bool IsBlendedTextureFiltering(GPUTextureFilter filter) { - return (filter == GPUTextureFilter::Bilinear || filter == GPUTextureFilter::JINC2 || filter == GPUTextureFilter::xBR); + // return (filter == GPUTextureFilter::Bilinear || filter == GPUTextureFilter::JINC2 || filter == + // GPUTextureFilter::xBR); + static_assert(((static_cast(GPUTextureFilter::Nearest) & 1u) == 0u) && + ((static_cast(GPUTextureFilter::Bilinear) & 1u) == 1u) && + ((static_cast(GPUTextureFilter::BilinearBinAlpha) & 1u) == 0u) && + ((static_cast(GPUTextureFilter::JINC2) & 1u) == 1u) && + ((static_cast(GPUTextureFilter::JINC2BinAlpha) & 1u) == 0u) && + ((static_cast(GPUTextureFilter::xBR) & 1u) == 1u) && + ((static_cast(GPUTextureFilter::xBRBinAlpha) & 1u) == 0u)); + return ((static_cast(filter) & 1u) == 1u); } /// Computes the area affected by a VRAM transfer, including wrap-around of X. @@ -193,17 +209,18 @@ bool GPU_HW::Initialize() m_resolution_scale = Truncate8(CalculateResolutionScale()); m_multisamples = Truncate8(std::min(g_settings.gpu_multisamples, g_gpu_device->GetMaxMultisamples())); + m_texture_filtering = g_settings.gpu_texture_filter; + m_sprite_texture_filtering = g_settings.gpu_sprite_texture_filter; + m_line_detect_mode = (m_resolution_scale > 1) ? g_settings.gpu_line_detect_mode : GPULineDetectMode::Disabled; + m_downsample_mode = GetDownsampleMode(m_resolution_scale); + m_wireframe_mode = g_settings.gpu_wireframe_mode; m_supports_dual_source_blend = features.dual_source_blend; m_supports_framebuffer_fetch = features.framebuffer_fetch; m_true_color = g_settings.gpu_true_color; - - m_texture_filtering = g_settings.gpu_texture_filter; - m_line_detect_mode = (m_resolution_scale > 1) ? g_settings.gpu_line_detect_mode : GPULineDetectMode::Disabled; - m_clamp_uvs = ShouldClampUVs(); - m_compute_uv_range = m_clamp_uvs; - m_downsample_mode = GetDownsampleMode(m_resolution_scale); - m_wireframe_mode = g_settings.gpu_wireframe_mode; m_pgxp_depth_buffer = g_settings.UsingPGXPDepthBuffer(); + m_clamp_uvs = ShouldClampUVs(m_texture_filtering) || ShouldClampUVs(m_sprite_texture_filtering); + m_compute_uv_range = m_clamp_uvs; + m_allow_sprite_mode = ShouldAllowSpriteMode(m_resolution_scale, m_texture_filtering, m_sprite_texture_filtering); CheckSettings(); @@ -317,7 +334,7 @@ void GPU_HW::UpdateSettings(const Settings& old_settings) const u8 resolution_scale = Truncate8(CalculateResolutionScale()); const u8 multisamples = Truncate8(std::min(g_settings.gpu_multisamples, g_gpu_device->GetMaxMultisamples())); - const bool clamp_uvs = ShouldClampUVs(); + const bool clamp_uvs = ShouldClampUVs(m_texture_filtering) || ShouldClampUVs(m_sprite_texture_filtering); const bool framebuffer_changed = (m_resolution_scale != resolution_scale || m_multisamples != multisamples || (static_cast(m_vram_depth_texture) != (g_settings.UsingPGXPDepthBuffer() || !m_supports_framebuffer_fetch))); @@ -328,14 +345,17 @@ void GPU_HW::UpdateSettings(const Settings& old_settings) (resolution_scale > 1 && g_settings.gpu_scaled_dithering != old_settings.gpu_scaled_dithering) || (resolution_scale > 1 && g_settings.gpu_texture_filter == GPUTextureFilter::Nearest && g_settings.gpu_force_round_texcoords != old_settings.gpu_force_round_texcoords) || - m_texture_filtering != g_settings.gpu_texture_filter || m_clamp_uvs != clamp_uvs || + m_texture_filtering != g_settings.gpu_texture_filter || + m_sprite_texture_filtering != g_settings.gpu_sprite_texture_filter || m_clamp_uvs != clamp_uvs || (resolution_scale > 1 && (g_settings.gpu_downsample_mode != old_settings.gpu_downsample_mode || (m_downsample_mode == GPUDownsampleMode::Box && g_settings.gpu_downsample_scale != old_settings.gpu_downsample_scale))) || (features.geometry_shaders && g_settings.gpu_wireframe_mode != old_settings.gpu_wireframe_mode) || m_pgxp_depth_buffer != g_settings.UsingPGXPDepthBuffer() || (features.noperspective_interpolation && - ShouldDisableColorPerspective() != old_settings.gpu_pgxp_color_correction)); + ShouldDisableColorPerspective() != old_settings.gpu_pgxp_color_correction) || + m_allow_sprite_mode != + ShouldAllowSpriteMode(m_resolution_scale, g_settings.gpu_texture_filter, g_settings.gpu_sprite_texture_filter)); if (m_resolution_scale != resolution_scale) { @@ -376,13 +396,15 @@ void GPU_HW::UpdateSettings(const Settings& old_settings) m_resolution_scale = resolution_scale; m_multisamples = multisamples; - m_true_color = g_settings.gpu_true_color; m_texture_filtering = g_settings.gpu_texture_filter; + m_sprite_texture_filtering = g_settings.gpu_sprite_texture_filter; m_line_detect_mode = (m_resolution_scale > 1) ? g_settings.gpu_line_detect_mode : GPULineDetectMode::Disabled; - m_clamp_uvs = clamp_uvs; - m_compute_uv_range = m_clamp_uvs; m_downsample_mode = GetDownsampleMode(resolution_scale); m_wireframe_mode = g_settings.gpu_wireframe_mode; + m_true_color = g_settings.gpu_true_color; + m_clamp_uvs = clamp_uvs; + m_compute_uv_range = m_clamp_uvs; + m_allow_sprite_mode = ShouldAllowSpriteMode(resolution_scale, m_texture_filtering, m_sprite_texture_filtering); CheckSettings(); @@ -454,13 +476,17 @@ void GPU_HW::CheckSettings() TRANSLATE_STR("GPU_HW", "SSAA is not supported, using MSAA instead."), Host::OSD_ERROR_DURATION); } - if (!features.dual_source_blend && !features.framebuffer_fetch && IsBlendedTextureFiltering(m_texture_filtering)) + if (!features.dual_source_blend && !features.framebuffer_fetch && + (IsBlendedTextureFiltering(m_texture_filtering) || IsBlendedTextureFiltering(m_sprite_texture_filtering))) { Host::AddIconOSDMessage( "TextureFilterUnsupported", ICON_FA_EXCLAMATION_TRIANGLE, - fmt::format(TRANSLATE_FS("GPU_HW", "Texture filter '{}' is not supported with the current renderer."), - Settings::GetTextureFilterDisplayName(m_texture_filtering), Host::OSD_ERROR_DURATION)); + fmt::format(TRANSLATE_FS("GPU_HW", "Texture filter '{}/{}' is not supported with the current renderer."), + Settings::GetTextureFilterDisplayName(m_texture_filtering), + Settings::GetTextureFilterName(m_sprite_texture_filtering), Host::OSD_ERROR_DURATION)); m_texture_filtering = GPUTextureFilter::Nearest; + m_sprite_texture_filtering = GPUTextureFilter::Nearest; + m_allow_sprite_mode = ShouldAllowSpriteMode(m_resolution_scale, m_texture_filtering, m_sprite_texture_filtering); } if (!features.noperspective_interpolation && !ShouldDisableColorPerspective()) @@ -650,7 +676,8 @@ void GPU_HW::PrintSettingsToLog() ((m_true_color && g_settings.gpu_debanding) ? " (Debanding)" : "")); INFO_LOG("Force round texture coordinates: {}", (m_resolution_scale > 1 && g_settings.gpu_force_round_texcoords) ? "Enabled" : "Disabled"); - INFO_LOG("Texture Filtering: {}", Settings::GetTextureFilterDisplayName(m_texture_filtering)); + INFO_LOG("Texture Filtering: {}/{}", Settings::GetTextureFilterDisplayName(m_texture_filtering), + Settings::GetTextureFilterDisplayName(m_sprite_texture_filtering)); INFO_LOG("Dual-source blending: {}", m_supports_dual_source_blend ? "Supported" : "Not supported"); INFO_LOG("Clamping UVs: {}", m_clamp_uvs ? "YES" : "NO"); INFO_LOG("Depth buffer: {}", m_pgxp_depth_buffer ? "YES" : "NO"); @@ -658,6 +685,7 @@ void GPU_HW::PrintSettingsToLog() INFO_LOG("Wireframe rendering: {}", Settings::GetGPUWireframeModeDisplayName(m_wireframe_mode)); INFO_LOG("Line detection: {}", Settings::GetLineDetectModeDisplayName(m_line_detect_mode)); INFO_LOG("Using software renderer for readbacks: {}", m_sw_renderer ? "YES" : "NO"); + INFO_LOG("Separate sprite shaders: {}", m_allow_sprite_mode ? "YES" : "NO"); } bool GPU_HW::NeedsDepthBuffer() const @@ -776,19 +804,23 @@ bool GPU_HW::CompilePipelines() { const GPUDevice::Features features = g_gpu_device->GetFeatures(); const bool per_sample_shading = g_settings.gpu_per_sample_shading && features.per_sample_shading; - const bool force_round_texcoords = (m_resolution_scale > 1 && g_settings.gpu_force_round_texcoords); + const bool force_round_texcoords = (m_resolution_scale > 1 && m_texture_filtering == GPUTextureFilter::Nearest && + g_settings.gpu_force_round_texcoords); const bool needs_depth_buffer = NeedsDepthBuffer(); const bool write_mask_as_depth = (!m_pgxp_depth_buffer && needs_depth_buffer); m_allow_shader_blend = (features.feedback_loops && (m_pgxp_depth_buffer || !needs_depth_buffer)); GPU_HW_ShaderGen shadergen(g_gpu_device->GetRenderAPI(), m_resolution_scale, m_multisamples, per_sample_shading, - m_true_color, (m_resolution_scale > 1 && g_settings.gpu_scaled_dithering), m_clamp_uvs, + m_true_color, (m_resolution_scale > 1 && g_settings.gpu_scaled_dithering), write_mask_as_depth, ShouldDisableColorPerspective(), m_supports_dual_source_blend, m_supports_framebuffer_fetch, g_settings.gpu_true_color && g_settings.gpu_debanding); - constexpr u32 active_texture_modes = 4; + const u32 active_texture_modes = + m_allow_sprite_mode ? NUM_TEXTURE_MODES : + (NUM_TEXTURE_MODES - (NUM_TEXTURE_MODES - static_cast(BatchTextureMode::SpriteStart))); + const u32 active_vertex_shaders = m_allow_sprite_mode ? 3 : 2; const u32 total_pipelines = - 2 + // vertex shaders + active_vertex_shaders + // vertex shaders (active_texture_modes * 5 * 9 * 2 * 2 * 2) + // fragment shaders ((m_pgxp_depth_buffer ? 2 : 1) * 5 * 5 * active_texture_modes * 2 * 2 * 2) + // batch pipelines ((m_wireframe_mode != GPUWireframeMode::Disabled) ? 1 : 0) + // wireframe @@ -804,19 +836,22 @@ bool GPU_HW::CompilePipelines() ShaderCompileProgressTracker progress("Compiling Pipelines", total_pipelines); - // vertex shaders - [textured] + // vertex shaders - [non-textured/textured/sprite] // fragment shaders - [render_mode][transparency_mode][texture_mode][check_mask][dithering][interlacing] static constexpr auto destroy_shader = [](std::unique_ptr& s) { s.reset(); }; - DimensionalArray, 2> batch_vertex_shaders{}; + DimensionalArray, 3> batch_vertex_shaders{}; DimensionalArray, 2, 2, 2, NUM_TEXTURE_MODES, 5, 5> batch_fragment_shaders{}; ScopedGuard batch_shader_guard([&batch_vertex_shaders, &batch_fragment_shaders]() { batch_vertex_shaders.enumerate(destroy_shader); batch_fragment_shaders.enumerate(destroy_shader); }); - for (u8 textured = 0; textured < 2; textured++) + for (u8 textured = 0; textured < active_vertex_shaders; textured++) { - const std::string vs = shadergen.GenerateBatchVertexShader(ConvertToBoolUnchecked(textured), m_pgxp_depth_buffer); + const bool sprite = (textured > 1); + const bool uv_limits = ShouldClampUVs(sprite ? m_sprite_texture_filtering : m_texture_filtering); + const std::string vs = shadergen.GenerateBatchVertexShader(textured != 0, uv_limits, + !sprite && force_round_texcoords, m_pgxp_depth_buffer); if (!(batch_vertex_shaders[textured] = g_gpu_device->CreateShader(GPUShaderStage::Vertex, shadergen.GetLanguage(), vs))) { @@ -858,11 +893,15 @@ bool GPU_HW::CompilePipelines() { for (u8 interlacing = 0; interlacing < 2; interlacing++) { + const bool sprite = (static_cast(texture_mode) >= BatchTextureMode::SpriteStart); + const bool uv_limits = ShouldClampUVs(sprite ? m_sprite_texture_filtering : m_texture_filtering); + const BatchTextureMode shader_texmode = static_cast( + texture_mode - (sprite ? static_cast(BatchTextureMode::SpriteStart) : 0)); const std::string fs = shadergen.GenerateBatchFragmentShader( static_cast(render_mode), static_cast(transparency_mode), - static_cast(texture_mode), m_texture_filtering, force_round_texcoords, - ConvertToBoolUnchecked(dithering), ConvertToBoolUnchecked(interlacing), - ConvertToBoolUnchecked(check_mask)); + shader_texmode, sprite ? m_sprite_texture_filtering : m_texture_filtering, uv_limits, + !sprite && force_round_texcoords, ConvertToBoolUnchecked(dithering), + ConvertToBoolUnchecked(interlacing), ConvertToBoolUnchecked(check_mask)); if (!(batch_fragment_shaders[render_mode][transparency_mode][texture_mode][check_mask][dithering] [interlacing] = g_gpu_device->CreateShader(GPUShaderStage::Fragment, @@ -940,6 +979,8 @@ bool GPU_HW::CompilePipelines() for (u8 check_mask = 0; check_mask < 2; check_mask++) { const bool textured = (static_cast(texture_mode) != BatchTextureMode::Disabled); + const bool sprite = (static_cast(texture_mode) >= BatchTextureMode::SpriteStart); + const bool uv_limits = ShouldClampUVs(sprite ? m_sprite_texture_filtering : m_texture_filtering); const bool use_shader_blending = (render_mode == static_cast(BatchRenderMode::ShaderBlend) && ((textured && @@ -948,13 +989,13 @@ bool GPU_HW::CompilePipelines() plconfig.input_layout.vertex_attributes = textured ? - (m_clamp_uvs ? std::span( - vertex_attributes, NUM_BATCH_TEXTURED_LIMITS_VERTEX_ATTRIBUTES) : - std::span( - vertex_attributes, NUM_BATCH_TEXTURED_VERTEX_ATTRIBUTES)) : + (uv_limits ? std::span( + vertex_attributes, NUM_BATCH_TEXTURED_LIMITS_VERTEX_ATTRIBUTES) : + std::span(vertex_attributes, + NUM_BATCH_TEXTURED_VERTEX_ATTRIBUTES)) : std::span(vertex_attributes, NUM_BATCH_VERTEX_ATTRIBUTES); - plconfig.vertex_shader = batch_vertex_shaders[BoolToUInt8(textured)].get(); + plconfig.vertex_shader = batch_vertex_shaders[BoolToUInt8(textured) + BoolToUInt8(sprite)].get(); plconfig.fragment_shader = batch_fragment_shaders[render_mode] [use_shader_blending ? transparency_mode : @@ -982,7 +1023,8 @@ bool GPU_HW::CompilePipelines() ((static_cast(transparency_mode) != GPUTransparencyMode::Disabled && (static_cast(render_mode) != BatchRenderMode::TransparencyDisabled && static_cast(render_mode) != BatchRenderMode::OnlyOpaque)) || - (textured && IsBlendedTextureFiltering(m_texture_filtering)))) + (textured && + IsBlendedTextureFiltering(sprite ? m_sprite_texture_filtering : m_texture_filtering)))) { plconfig.blend.enable = true; plconfig.blend.src_alpha_blend = GPUPipeline::BlendFunc::One; @@ -1512,12 +1554,15 @@ ALWAYS_INLINE_RELEASE void GPU_HW::DrawBatchVertices(BatchRenderMode render_mode u32 base_vertex) { // [depth_test][transparency_mode][render_mode][texture_mode][dithering][interlacing][check_mask] + const u8 texture_mode = static_cast(m_batch.texture_mode) + + ((m_batch.texture_mode != BatchTextureMode::Disabled && m_batch.sprite_mode) ? + static_cast(BatchTextureMode::SpriteStart) : + 0); const u8 depth_test = BoolToUInt8(m_batch.use_depth_buffer); const u8 check_mask = BoolToUInt8(m_batch.check_mask_before_draw); g_gpu_device->SetPipeline(m_batch_pipelines[depth_test][static_cast(m_batch.transparency_mode)][static_cast( - render_mode)][static_cast(m_batch.texture_mode)][BoolToUInt8(m_batch.dithering)] - [BoolToUInt8(m_batch.interlacing)][check_mask] - .get()); + render_mode)][texture_mode][BoolToUInt8(m_batch.dithering)][BoolToUInt8(m_batch.interlacing)][check_mask] + .get()); if (render_mode != BatchRenderMode::ShaderBlend || m_supports_framebuffer_fetch) g_gpu_device->DrawIndexed(num_indices, base_index, base_vertex); @@ -1628,6 +1673,36 @@ ALWAYS_INLINE_RELEASE void GPU_HW::HandleFlippedQuadTextureCoordinates(BatchVert vertices[2].v++; vertices[3].v++; } + + // 2D polygons should have zero change in V on the X axis, and vice versa. + if (m_allow_sprite_mode) + SetBatchSpriteMode(zero_dudy && zero_dvdx); +} + +bool GPU_HW::IsPossibleSpritePolygon(const BatchVertex* vertices) const +{ + const float abx = vertices[1].x - vertices[0].x; + const float aby = vertices[1].y - vertices[0].y; + const float bcx = vertices[2].x - vertices[1].x; + const float bcy = vertices[2].y - vertices[1].y; + const float cax = vertices[0].x - vertices[2].x; + const float cay = vertices[0].y - vertices[2].y; + const float dvdx = -aby * static_cast(vertices[2].v) - bcy * static_cast(vertices[0].v) - + cay * static_cast(vertices[1].v); + const float dudy = +abx * static_cast(vertices[2].u) + bcx * static_cast(vertices[0].u) + + cax * static_cast(vertices[1].u); + const float area = bcx * cay - bcy * cax; + const s32 texArea = (vertices[1].u - vertices[0].u) * (vertices[2].v - vertices[0].v) - + (vertices[2].u - vertices[0].u) * (vertices[1].v - vertices[0].v); + + // Doesn't matter. + if (area == 0.0f || texArea == 0) + return m_batch.sprite_mode; + + const float rcp_area = 1.0f / area; + const bool zero_dudy = ((dudy * rcp_area) == 0.0f); + const bool zero_dvdx = ((dvdx * rcp_area) == 0.0f); + return (zero_dudy && zero_dvdx); } ALWAYS_INLINE_RELEASE void GPU_HW::ExpandLineTriangles(BatchVertex* vertices, u32 base_vertex) @@ -1837,6 +1912,22 @@ void GPU_HW::CheckForDepthClear(const BatchVertex* vertices, u32 num_vertices) m_last_depth_z = average_z; } +void GPU_HW::SetBatchSpriteMode(bool enabled) +{ + if (m_batch.sprite_mode == enabled) + return; + + if (m_batch_index_count > 0) + { + FlushRender(); + EnsureVertexBufferSpaceForCurrentCommand(); + } + + GL_INS_FMT("Sprite mode is now {}", enabled ? "ON" : "OFF"); + + m_batch.sprite_mode = enabled; +} + void GPU_HW::DrawLine(float x0, float y0, u32 col0, float x1, float y1, u32 col1, float depth) { DebugAssert(m_batch_vertex_space >= 4 && m_batch_index_space >= 6); @@ -1997,6 +2088,8 @@ void GPU_HW::LoadVertices() const bool is_3d = (vertices[0].w != vertices[1].w || vertices[0].w != vertices[2].w); if (m_resolution_scale > 1 && !is_3d && rc.quad_polygon) HandleFlippedQuadTextureCoordinates(vertices.data()); + else if (m_allow_sprite_mode) + SetBatchSpriteMode((pgxp && !is_3d) || IsPossibleSpritePolygon(vertices.data())); if (m_compute_uv_range && textured) ComputePolygonUVLimits(texpage, vertices.data(), num_vertices); @@ -2160,6 +2253,7 @@ void GPU_HW::LoadVertices() // we can split the rectangle up into potentially 8 quads SetBatchDepthBuffer(false); + SetBatchSpriteMode(m_allow_sprite_mode); DebugAssert(m_batch_vertex_space >= MAX_VERTICES_FOR_RECTANGLE && m_batch_index_space >= MAX_VERTICES_FOR_RECTANGLE); @@ -2499,7 +2593,8 @@ ALWAYS_INLINE_RELEASE bool GPU_HW::NeedsShaderBlending(GPUTransparencyMode trans ((check_mask && (m_pgxp_depth_buffer || !m_vram_depth_texture)) || transparency == GPUTransparencyMode::BackgroundMinusForeground || (!m_supports_dual_source_blend && - (transparency != GPUTransparencyMode::Disabled || IsBlendedTextureFiltering(m_texture_filtering))))); + (transparency != GPUTransparencyMode::Disabled || IsBlendedTextureFiltering(m_texture_filtering) || + IsBlendedTextureFiltering(m_sprite_texture_filtering))))); } void GPU_HW::EnsureVertexBufferSpace(u32 required_vertices, u32 required_indices) diff --git a/src/core/gpu_hw.h b/src/core/gpu_hw.h index 23cd6f216..df0b2a6ae 100644 --- a/src/core/gpu_hw.h +++ b/src/core/gpu_hw.h @@ -40,7 +40,13 @@ public: Direct16Bit, Disabled, + SpritePalette4Bit, + SpritePalette8Bit, + SpriteDirect16Bit, + MaxCount, + + SpriteStart = SpritePalette4Bit, }; static_assert(static_cast(BatchTextureMode::Palette4Bit) == static_cast(GPUTextureMode::Palette4Bit) && static_cast(BatchTextureMode::Palette8Bit) == static_cast(GPUTextureMode::Palette8Bit) && @@ -108,6 +114,7 @@ private: bool set_mask_while_drawing = false; bool check_mask_before_draw = false; bool use_depth_buffer = false; + bool sprite_mode = false; // Returns the render mode for this batch. BatchRenderMode GetRenderMode() const; @@ -201,6 +208,7 @@ private: /// Handles quads with flipped texture coordinate directions. void HandleFlippedQuadTextureCoordinates(BatchVertex* vertices); + bool IsPossibleSpritePolygon(const BatchVertex* vertices) const; void ExpandLineTriangles(BatchVertex* vertices, u32 base_vertex); /// Computes polygon U/V boundaries. @@ -209,6 +217,7 @@ private: /// Sets the depth test flag for PGXP depth buffering. void SetBatchDepthBuffer(bool enabled); void CheckForDepthClear(const BatchVertex* vertices, u32 num_vertices); + void SetBatchSpriteMode(bool enabled); void UpdateDownsamplingLevels(); void DownsampleFramebuffer(GPUTexture* source, u32 left, u32 top, u32 width, u32 height); @@ -242,6 +251,7 @@ private: u8 m_multisamples = 1; GPUTextureFilter m_texture_filtering = GPUTextureFilter::Nearest; + GPUTextureFilter m_sprite_texture_filtering = GPUTextureFilter::Nearest; GPULineDetectMode m_line_detect_mode = GPULineDetectMode::Disabled; GPUDownsampleMode m_downsample_mode = GPUDownsampleMode::Disabled; GPUWireframeMode m_wireframe_mode = GPUWireframeMode::Disabled; @@ -252,6 +262,7 @@ private: bool m_pgxp_depth_buffer : 1 = false; bool m_clamp_uvs : 1 = false; bool m_compute_uv_range : 1 = false; + bool m_allow_sprite_mode : 1 = false; bool m_allow_shader_blend : 1 = false; u8 m_texpage_dirty = 0; diff --git a/src/core/gpu_hw_shadergen.cpp b/src/core/gpu_hw_shadergen.cpp index 62808f4dc..ccf1b222a 100644 --- a/src/core/gpu_hw_shadergen.cpp +++ b/src/core/gpu_hw_shadergen.cpp @@ -6,14 +6,13 @@ #include GPU_HW_ShaderGen::GPU_HW_ShaderGen(RenderAPI render_api, u32 resolution_scale, u32 multisamples, - bool per_sample_shading, bool true_color, bool scaled_dithering, bool uv_limits, + bool per_sample_shading, bool true_color, bool scaled_dithering, bool write_mask_as_depth, bool disable_color_perspective, bool supports_dual_source_blend, bool supports_framebuffer_fetch, bool debanding) : ShaderGen(render_api, GetShaderLanguageForAPI(render_api), supports_dual_source_blend, supports_framebuffer_fetch), m_resolution_scale(resolution_scale), m_multisamples(multisamples), m_per_sample_shading(per_sample_shading), - m_true_color(true_color), m_scaled_dithering(scaled_dithering), m_uv_limits(uv_limits), - m_write_mask_as_depth(write_mask_as_depth), m_disable_color_perspective(disable_color_perspective), - m_debanding(debanding) + m_true_color(true_color), m_scaled_dithering(scaled_dithering), m_write_mask_as_depth(write_mask_as_depth), + m_disable_color_perspective(disable_color_perspective), m_debanding(debanding) { } @@ -58,12 +57,14 @@ void GPU_HW_ShaderGen::WriteBatchUniformBuffer(std::stringstream& ss) false); } -std::string GPU_HW_ShaderGen::GenerateBatchVertexShader(bool textured, bool pgxp_depth) +std::string GPU_HW_ShaderGen::GenerateBatchVertexShader(bool textured, bool uv_limits, bool force_round_texcoords, + bool pgxp_depth) { std::stringstream ss; WriteHeader(ss); DefineMacro(ss, "TEXTURED", textured); - DefineMacro(ss, "UV_LIMITS", m_uv_limits); + DefineMacro(ss, "UV_LIMITS", uv_limits); + DefineMacro(ss, "FORCE_ROUND_TEXCOORDS", force_round_texcoords); DefineMacro(ss, "PGXP_DEPTH", pgxp_depth); WriteCommonFunctions(ss); @@ -71,7 +72,7 @@ std::string GPU_HW_ShaderGen::GenerateBatchVertexShader(bool textured, bool pgxp if (textured) { - if (m_uv_limits) + if (uv_limits) { DeclareVertexEntryPoint( ss, {"float4 a_pos", "float4 a_col0", "uint a_texcoord", "uint a_texpage", "float4 a_uv_limits"}, 1, 1, @@ -137,6 +138,11 @@ std::string GPU_HW_ShaderGen::GenerateBatchVertexShader(bool textured, bool pgxp #if UV_LIMITS v_uv_limits = a_uv_limits * float4(255.0, 255.0, 255.0, 255.0); + #if FORCE_ROUND_TEXCOORDS + // Add 0.5 to the upper bounds when upscaling, to work around interpolation differences. + // Limited to force-round-texcoord hack, to avoid breaking other games. + v_uv_limits.zw += 0.5; + #endif #endif #endif } @@ -630,9 +636,12 @@ void FilteredSampleFromVRAM(uint4 texpage, float2 coords, float4 uv_limits, } } -std::string GPU_HW_ShaderGen::GenerateBatchFragmentShader( - GPU_HW::BatchRenderMode render_mode, GPUTransparencyMode transparency, GPU_HW::BatchTextureMode texture_mode, - GPUTextureFilter texture_filtering, bool force_round_texcoords, bool dithering, bool interlacing, bool check_mask) +std::string GPU_HW_ShaderGen::GenerateBatchFragmentShader(GPU_HW::BatchRenderMode render_mode, + GPUTransparencyMode transparency, + GPU_HW::BatchTextureMode texture_mode, + GPUTextureFilter texture_filtering, bool uv_limits, + bool force_round_texcoords, bool dithering, bool interlacing, + bool check_mask) { // TODO: don't write depth for shader blend DebugAssert(transparency == GPUTransparencyMode::Disabled || render_mode == GPU_HW::BatchRenderMode::ShaderBlend); @@ -666,7 +675,7 @@ std::string GPU_HW_ShaderGen::GenerateBatchFragmentShader( DefineMacro(ss, "INTERLACING", interlacing); DefineMacro(ss, "TRUE_COLOR", m_true_color); DefineMacro(ss, "TEXTURE_FILTERING", texture_filtering != GPUTextureFilter::Nearest); - DefineMacro(ss, "UV_LIMITS", m_uv_limits); + DefineMacro(ss, "UV_LIMITS", uv_limits); DefineMacro(ss, "USE_DUAL_SOURCE", use_dual_source); DefineMacro(ss, "WRITE_MASK_AS_DEPTH", m_write_mask_as_depth); DefineMacro(ss, "FORCE_ROUND_TEXCOORDS", force_round_texcoords); @@ -796,7 +805,7 @@ float3 ApplyDebanding(float2 frag_coord) if (texture_filtering != GPUTextureFilter::Nearest) WriteBatchTextureFilter(ss, texture_filtering); - if (m_uv_limits) + if (uv_limits) { DeclareFragmentEntryPoint(ss, 1, 1, {{"nointerpolation", "uint4 v_texpage"}, {"nointerpolation", "float4 v_uv_limits"}}, diff --git a/src/core/gpu_hw_shadergen.h b/src/core/gpu_hw_shadergen.h index e1e9a0041..e5fdf81d3 100644 --- a/src/core/gpu_hw_shadergen.h +++ b/src/core/gpu_hw_shadergen.h @@ -9,15 +9,14 @@ class GPU_HW_ShaderGen : public ShaderGen { public: GPU_HW_ShaderGen(RenderAPI render_api, u32 resolution_scale, u32 multisamples, bool per_sample_shading, - bool true_color, bool scaled_dithering, bool uv_limits, bool write_mask_as_depth, - bool disable_color_perspective, bool supports_dual_source_blend, bool supports_framebuffer_fetch, - bool debanding); + bool true_color, bool scaled_dithering, bool write_mask_as_depth, bool disable_color_perspective, + bool supports_dual_source_blend, bool supports_framebuffer_fetch, bool debanding); ~GPU_HW_ShaderGen(); - std::string GenerateBatchVertexShader(bool textured, bool pgxp_depth); + std::string GenerateBatchVertexShader(bool textured, bool uv_limits, bool force_round_texcoords, bool pgxp_depth); std::string GenerateBatchFragmentShader(GPU_HW::BatchRenderMode render_mode, GPUTransparencyMode transparency, GPU_HW::BatchTextureMode texture_mode, GPUTextureFilter texture_filtering, - bool force_round_texcoords, bool dithering, bool interlacing, + bool uv_limits, bool force_round_texcoords, bool dithering, bool interlacing, bool check_mask); std::string GenerateWireframeGeometryShader(); std::string GenerateWireframeFragmentShader(); @@ -48,7 +47,6 @@ private: bool m_per_sample_shading; bool m_true_color; bool m_scaled_dithering; - bool m_uv_limits; bool m_write_mask_as_depth; bool m_disable_color_perspective; bool m_debanding; diff --git a/src/core/imgui_overlays.cpp b/src/core/imgui_overlays.cpp index 15b0ff5de..62c5bcfb5 100644 --- a/src/core/imgui_overlays.cpp +++ b/src/core/imgui_overlays.cpp @@ -548,7 +548,17 @@ void ImGuiManager::DrawEnhancementsOverlay() if (g_settings.gpu_force_ntsc_timings && System::GetRegion() == ConsoleRegion::PAL) text.append(" PAL60"); if (g_settings.gpu_texture_filter != GPUTextureFilter::Nearest) - text.append_format(" {}", Settings::GetTextureFilterName(g_settings.gpu_texture_filter)); + { + if (g_settings.gpu_sprite_texture_filter != g_settings.gpu_texture_filter) + { + text.append_format(" {}/{}", Settings::GetTextureFilterName(g_settings.gpu_texture_filter), + Settings::GetTextureFilterName(g_settings.gpu_sprite_texture_filter)); + } + else + { + text.append_format(" {}", Settings::GetTextureFilterName(g_settings.gpu_texture_filter)); + } + } if (g_settings.gpu_widescreen_hack && g_settings.display_aspect_ratio != DisplayAspectRatio::Auto && g_settings.display_aspect_ratio != DisplayAspectRatio::R4_3) { @@ -1144,9 +1154,9 @@ void SaveStateSelectorUI::SaveCurrentSlot() if (!System::SaveState(path.c_str(), &error, g_settings.create_save_state_backups)) { Host::AddIconOSDMessage("SaveState", ICON_FA_EXCLAMATION_TRIANGLE, - fmt::format(TRANSLATE_FS("OSDMessage", "Failed to save state to slot {0}:\n{1}"), - GetCurrentSlot(), error.GetDescription()), - Host::OSD_ERROR_DURATION); + fmt::format(TRANSLATE_FS("OSDMessage", "Failed to save state to slot {0}:\n{1}"), + GetCurrentSlot(), error.GetDescription()), + Host::OSD_ERROR_DURATION); } } diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 5f760e3c2..af6107d55 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -202,6 +202,9 @@ void Settings::Load(SettingsInterface& si) ParseTextureFilterName( si.GetStringValue("GPU", "TextureFilter", GetTextureFilterName(DEFAULT_GPU_TEXTURE_FILTER)).c_str()) .value_or(DEFAULT_GPU_TEXTURE_FILTER); + gpu_sprite_texture_filter = + ParseTextureFilterName(si.GetStringValue("GPU", "SpriteTextureFilter", GetTextureFilterName(gpu_texture_filter)).c_str()) + .value_or(gpu_texture_filter); gpu_line_detect_mode = ParseLineDetectModeName( si.GetStringValue("GPU", "LineDetectMode", GetLineDetectModeName(DEFAULT_GPU_LINE_DETECT_MODE)).c_str()) @@ -498,6 +501,9 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const si.SetBoolValue("GPU", "ScaledDithering", gpu_scaled_dithering); si.SetBoolValue("GPU", "ForceRoundTextureCoordinates", gpu_force_round_texcoords); si.SetStringValue("GPU", "TextureFilter", GetTextureFilterName(gpu_texture_filter)); + si.SetStringValue( + "GPU", "SpriteTextureFilter", + (gpu_sprite_texture_filter != gpu_texture_filter) ? GetTextureFilterName(gpu_sprite_texture_filter) : ""); si.SetStringValue("GPU", "LineDetectMode", GetLineDetectModeName(gpu_line_detect_mode)); si.SetStringValue("GPU", "DownsampleMode", GetDownsampleModeName(gpu_downsample_mode)); si.SetUIntValue("GPU", "DownsampleScale", gpu_downsample_scale); @@ -706,6 +712,7 @@ void Settings::FixIncompatibleSettings(bool display_osd_messages) g_settings.gpu_scaled_dithering = false; g_settings.gpu_force_round_texcoords = false; g_settings.gpu_texture_filter = GPUTextureFilter::Nearest; + g_settings.gpu_sprite_texture_filter = GPUTextureFilter::Nearest; g_settings.gpu_line_detect_mode = GPULineDetectMode::Disabled; g_settings.gpu_disable_interlacing = false; g_settings.gpu_force_ntsc_timings = false; diff --git a/src/core/settings.h b/src/core/settings.h index dad916242..6b5729870 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -119,6 +119,7 @@ struct Settings bool gpu_scaled_dithering : 1 = true; bool gpu_force_round_texcoords : 1 = false; GPUTextureFilter gpu_texture_filter = DEFAULT_GPU_TEXTURE_FILTER; + GPUTextureFilter gpu_sprite_texture_filter = DEFAULT_GPU_TEXTURE_FILTER; GPULineDetectMode gpu_line_detect_mode = DEFAULT_GPU_LINE_DETECT_MODE; GPUDownsampleMode gpu_downsample_mode = DEFAULT_GPU_DOWNSAMPLE_MODE; u8 gpu_downsample_scale = 1; diff --git a/src/core/system.cpp b/src/core/system.cpp index 1fe0dc251..5221743e7 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -4017,6 +4017,7 @@ void System::CheckForSettingsChanges(const Settings& old_settings) g_settings.gpu_scaled_dithering != old_settings.gpu_scaled_dithering || g_settings.gpu_force_round_texcoords != old_settings.gpu_force_round_texcoords || g_settings.gpu_texture_filter != old_settings.gpu_texture_filter || + g_settings.gpu_sprite_texture_filter != old_settings.gpu_sprite_texture_filter || g_settings.gpu_line_detect_mode != old_settings.gpu_line_detect_mode || g_settings.gpu_disable_interlacing != old_settings.gpu_disable_interlacing || g_settings.gpu_force_ntsc_timings != old_settings.gpu_force_ntsc_timings || diff --git a/src/duckstation-qt/graphicssettingswidget.cpp b/src/duckstation-qt/graphicssettingswidget.cpp index 0a74afc49..ce37638b5 100644 --- a/src/duckstation-qt/graphicssettingswidget.cpp +++ b/src/duckstation-qt/graphicssettingswidget.cpp @@ -55,6 +55,9 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget* SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.textureFiltering, "GPU", "TextureFilter", &Settings::ParseTextureFilterName, &Settings::GetTextureFilterName, Settings::DEFAULT_GPU_TEXTURE_FILTER); + SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.spriteTextureFiltering, "GPU", "SpriteTextureFilter", + &Settings::ParseTextureFilterName, &Settings::GetTextureFilterName, + Settings::DEFAULT_GPU_TEXTURE_FILTER); SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.gpuDownsampleMode, "GPU", "DownsampleMode", &Settings::ParseDownsampleModeName, &Settings::GetDownsampleModeName, Settings::DEFAULT_GPU_DOWNSAMPLE_MODE); @@ -526,6 +529,8 @@ void GraphicsSettingsWidget::setupAdditionalUi() { m_ui.textureFiltering->addItem( QString::fromUtf8(Settings::GetTextureFilterDisplayName(static_cast(i)))); + m_ui.spriteTextureFiltering->addItem( + QString::fromUtf8(Settings::GetTextureFilterDisplayName(static_cast(i)))); } for (u32 i = 0; i < static_cast(GPUDownsampleMode::Count); i++) diff --git a/src/duckstation-qt/graphicssettingswidget.ui b/src/duckstation-qt/graphicssettingswidget.ui index a2938e91c..2489db323 100644 --- a/src/duckstation-qt/graphicssettingswidget.ui +++ b/src/duckstation-qt/graphicssettingswidget.ui @@ -7,7 +7,7 @@ 0 0 584 - 430 + 450 @@ -216,17 +216,14 @@ - - - - + Aspect Ratio: - + @@ -260,37 +257,37 @@ - + Deinterlacing: - + - + Crop: - + - + Scaling: - + - + @@ -350,6 +347,19 @@ + + + + + + + Sprite Texture Filtering: + + + + + + diff --git a/src/duckstation-qt/settingswindow.ui b/src/duckstation-qt/settingswindow.ui index 8b90c89fe..6f7f1e061 100644 --- a/src/duckstation-qt/settingswindow.ui +++ b/src/duckstation-qt/settingswindow.ui @@ -7,7 +7,7 @@ 0 0 780 - 490 + 650