diff --git a/data/resources/gamedb.yaml b/data/resources/gamedb.yaml index c8b25e594..b364684ba 100644 --- a/data/resources/gamedb.yaml +++ b/data/resources/gamedb.yaml @@ -43128,8 +43128,9 @@ SLES-00132: controllers: - DigitalController traits: - - DisableUpscaling - - DisablePGXP + - DisablePGXP # 2.5D, not beneficial. + settings: + gpuLineDetectMode: BasicTriangles # Fixes upscaled rendering. metadata: publisher: "GT Interactive / Williams Entertainment" developer: "Midway Studios San Diego" @@ -43151,8 +43152,9 @@ SLPS-00308: controllers: - DigitalController traits: - - DisableUpscaling - - DisablePGXP + - DisablePGXP # 2.5D, not beneficial. + settings: + gpuLineDetectMode: BasicTriangles # Fixes upscaled rendering. metadata: publisher: "Soft Bank" developer: "Midway Studios San Diego" @@ -43172,12 +43174,12 @@ SLUS-00077: compatibility: rating: NoIssues versionTested: "0.1-908-g9f22684" - upscalingIssues: "Broken when upscaling" controllers: - DigitalController traits: - - DisableUpscaling - - DisablePGXP + - DisablePGXP # 2.5D, not beneficial. + settings: + gpuLineDetectMode: BasicTriangles # Fixes upscaled rendering. metadata: publisher: "GT Interactive / Williams Entertainment" developer: "Midway Studios San Diego" @@ -46145,8 +46147,9 @@ SLES-00703: - AnalogController - DigitalController traits: - - DisableUpscaling - - DisablePGXP + - DisablePGXP # 2.5D, not beneficial. + settings: + gpuLineDetectMode: BasicTriangles # Fixes upscaled rendering. metadata: publisher: "GT Interactive" developer: "3D Realms Entertainment" @@ -46167,8 +46170,9 @@ SLES-00987: - AnalogController - DigitalController traits: - - DisableUpscaling - - DisablePGXP + - DisablePGXP # 2.5D, not beneficial. + settings: + gpuLineDetectMode: BasicTriangles # Fixes upscaled rendering. metadata: publisher: "GT Interactive" developer: "3D Realms Entertainment" @@ -46186,8 +46190,9 @@ SLES-00987: SLED-01027: name: "Duke Nukem (France) (Demo)" traits: - - DisableUpscaling - - DisablePGXP + - DisablePGXP # 2.5D, not beneficial. + settings: + gpuLineDetectMode: BasicTriangles # Fixes upscaled rendering. SLES-03405: name: "Duke Nukem - Land of the Babes (Europe) (En,Fr,De,Es,It)" controllers: @@ -46360,8 +46365,9 @@ SLPS-01557: - AnalogController - DigitalController traits: - - DisableUpscaling - - DisablePGXP + - DisablePGXP # 2.5D, not beneficial. + settings: + gpuLineDetectMode: BasicTriangles # Fixes upscaled rendering. metadata: publisher: "King Record Co. Ltd" developer: "3D Realms Entertainment" @@ -46385,8 +46391,9 @@ SLUS-00355: - AnalogController - DigitalController traits: - - DisableUpscaling - - DisablePGXP + - DisablePGXP # 2.5D, not beneficial. + settings: + gpuLineDetectMode: BasicTriangles # Fixes upscaled rendering. metadata: publisher: "GT Interactive" developer: "3D Realms Entertainment" @@ -54792,8 +54799,9 @@ SLES-00487: - DigitalController - PlayStationMouse traits: - - DisableUpscaling - - DisablePGXP + - DisablePGXP # 2.5D, not beneficial. + settings: + gpuLineDetectMode: BasicTriangles # Fixes upscaled rendering. metadata: publisher: "GT Interactive / Williams Entertainment" developer: "id Software, Inc." @@ -54814,8 +54822,9 @@ SLPS-00727: - DigitalController - PlayStationMouse traits: - - DisableUpscaling - - DisablePGXP + - DisablePGXP # 2.5D, not beneficial. + settings: + gpuLineDetectMode: BasicTriangles # Fixes upscaled rendering. metadata: publisher: "Soft Bank" developer: "id Software, Inc." @@ -54835,13 +54844,13 @@ SLUS-00331: compatibility: rating: NoIssues versionTested: "0.1-986-gfc911de1" - upscalingIssues: "Rendering is broken with any upscaling." controllers: - DigitalController - PlayStationMouse traits: - - DisableUpscaling - - DisablePGXP + - DisablePGXP # 2.5D, not beneficial. + settings: + gpuLineDetectMode: BasicTriangles # Fixes upscaled rendering. metadata: publisher: "GT Interactive / Williams Entertainment" developer: "id Software, Inc." @@ -149474,6 +149483,8 @@ SCES-00577: versionTested: "0.1-2949-gcd2c581f" controllers: - DigitalController + settings: + gpuLineDetectMode: BasicTriangles # Fixes upscaled water rendering. metadata: publisher: "Sony Computer Entertaiment Europe" developer: "Namco" @@ -149493,9 +149504,10 @@ SLUS-00240: compatibility: rating: NoIssues versionTested: "0.1-1409-ge198e315" - upscalingIssues: "Water error in Li Long stage (Issue #371)" controllers: - DigitalController + settings: + gpuLineDetectMode: BasicTriangles # Fixes upscaled water rendering. metadata: publisher: "Namco" developer: "Namco" @@ -153089,8 +153101,9 @@ SLES-00585: controllers: - DigitalController traits: - - DisableUpscaling - - DisablePGXP + - DisablePGXP # 2.5D, PGXP is not beneficial. + settings: + gpuLineDetectMode: AggressiveTriangles # Fixes upscaled rendering. metadata: publisher: "Lucasarts" developer: "Lucasarts / Big Bang Software, Inc" @@ -153110,8 +153123,9 @@ SLES-00640: controllers: - DigitalController traits: - - DisableUpscaling - - DisablePGXP + - DisablePGXP # 2.5D, PGXP is not beneficial. + settings: + gpuLineDetectMode: AggressiveTriangles # Fixes upscaled rendering. metadata: publisher: "Lucasarts" developer: "Lucasarts / Big Bang Software, Inc" @@ -153132,8 +153146,9 @@ SLPS-00685: controllers: - DigitalController traits: - - DisableUpscaling - - DisablePGXP + - DisablePGXP # 2.5D, PGXP is not beneficial. + settings: + gpuLineDetectMode: AggressiveTriangles # Fixes upscaled rendering. metadata: publisher: "Bullet-Proof Software" developer: "Lucasarts / Big Bang Software, Inc" @@ -153153,8 +153168,9 @@ SLES-00646: controllers: - DigitalController traits: - - DisableUpscaling - - DisablePGXP + - DisablePGXP # 2.5D, PGXP is not beneficial. + settings: + gpuLineDetectMode: AggressiveTriangles # Fixes upscaled rendering. metadata: publisher: "Erbe Software" developer: "Lucasarts / Big Bang Software, Inc" @@ -153175,12 +153191,12 @@ SLUS-00297: compatibility: rating: NoIssues versionTested: "0.1-986-gfc911de1" - upscalingIssues: "Rendering is broken when upscaling is used." controllers: - DigitalController traits: - - DisableUpscaling - - DisablePGXP + - DisablePGXP # 2.5D, PGXP is not beneficial. + settings: + gpuLineDetectMode: AggressiveTriangles # Fixes upscaled rendering. metadata: publisher: "Lucasarts" developer: "Lucasarts / Big Bang Software, Inc" @@ -163808,6 +163824,8 @@ SLPS-00365: name: "Tekkyuu - True Pinball (Japan)" controllers: - DigitalController + traits: + - DisableUpscaling metadata: publisher: "Ocean" developer: "Digital Illusions CE AB" @@ -172671,6 +172689,8 @@ SLES-00052: name: "True Pinball (Europe)" controllers: - DigitalController + traits: + - DisableUpscaling metadata: publisher: "Ocean" developer: "Digital Illusions CE AB" @@ -172695,6 +172715,7 @@ SLUS-00337: - DigitalController traits: - ForceInterlacing + - DisableUpscaling metadata: publisher: "Ocean" developer: "Digital Illusions CE AB" @@ -180948,6 +180969,8 @@ SCUS-94592: name: "Wild Arms 2 (USA) (Demo)" traits: - ForcePGXPCPUMode + settings: + gpuLineDetectMode: Quads SCUS-94484: name: "Wild Arms 2 (USA) (Disc 1)" discSet: @@ -180962,6 +180985,8 @@ SCUS-94484: - DigitalController traits: - ForcePGXPCPUMode + settings: + gpuLineDetectMode: Quads metadata: publisher: "Sony" developer: "Media.Vision Entertainment / Contrail" @@ -180987,6 +181012,8 @@ SCUS-94498: - DigitalController traits: - ForcePGXPCPUMode + settings: + gpuLineDetectMode: Quads metadata: publisher: "Sony" developer: "Media.Vision Entertainment / Contrail" @@ -181007,6 +181034,8 @@ SCPS-45429: - DigitalController traits: - ForcePGXPCPUMode + settings: + gpuLineDetectMode: Quads codes: - SCPS-45429 - SCPS-45430 diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index 776dc59cd..fc9d44d6a 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -2467,13 +2467,12 @@ void FullscreenUI::DrawSettingsWindow() static constexpr float ITEM_WIDTH = 25.0f; static constexpr const char* global_icons[] = { - ICON_FA_TV, ICON_FA_DICE_D20, ICON_FA_COGS, ICON_PF_MICROCHIP, - ICON_PF_PICTURE, ICON_FA_MAGIC, ICON_PF_SOUND, ICON_PF_GAMEPAD_ALT, - ICON_PF_KEYBOARD_ALT, ICON_PF_MEMORY_CARD, ICON_FA_TROPHY, ICON_FA_EXCLAMATION_TRIANGLE}; - static constexpr const char* per_game_icons[] = { - ICON_FA_PARAGRAPH, ICON_FA_HDD, ICON_FA_COGS, - ICON_PF_PICTURE, ICON_PF_SOUND, ICON_PF_GAMEPAD_ALT, - ICON_PF_MEMORY_CARD, ICON_FA_TROPHY, ICON_FA_EXCLAMATION_TRIANGLE}; + ICON_FA_TV, ICON_FA_DICE_D20, ICON_FA_COGS, ICON_PF_MICROCHIP, + ICON_PF_PICTURE, ICON_FA_MAGIC, ICON_PF_SOUND, ICON_PF_GAMEPAD_ALT, + ICON_PF_KEYBOARD_ALT, ICON_PF_MEMORY_CARD, ICON_FA_TROPHY, ICON_FA_EXCLAMATION_TRIANGLE}; + static constexpr const char* per_game_icons[] = {ICON_FA_PARAGRAPH, ICON_FA_HDD, ICON_FA_COGS, + ICON_PF_PICTURE, ICON_PF_SOUND, ICON_PF_GAMEPAD_ALT, + ICON_PF_MEMORY_CARD, ICON_FA_TROPHY, ICON_FA_EXCLAMATION_TRIANGLE}; static constexpr SettingsPage global_pages[] = { SettingsPage::Interface, SettingsPage::Console, SettingsPage::Emulation, SettingsPage::BIOS, SettingsPage::Display, SettingsPage::PostProcessing, SettingsPage::Audio, SettingsPage::Controller, @@ -3908,6 +3907,13 @@ void FullscreenUI::DrawDisplaySettingsPage() "GPU", "TextureFilter", Settings::DEFAULT_GPU_TEXTURE_FILTER, &Settings::ParseTextureFilterName, &Settings::GetTextureFilterName, &Settings::GetTextureFilterDisplayName, GPUTextureFilter::Count, is_hardware); + DrawEnumSetting(bsi, FSUI_CSTR("Line Detection"), + FSUI_CSTR("Attempts to detect one pixel high/wide lines that rely on non-upscaled rasterization " + "behavior, filling in gaps introduced by upscaling."), + "GPU", "LineDetectMode", Settings::DEFAULT_GPU_LINE_DETECT_MODE, &Settings::ParseLineDetectModeName, + &Settings::GetLineDetectModeName, &Settings::GetLineDetectModeDisplayName, GPULineDetectMode::Count, + is_hardware); + DrawToggleSetting(bsi, FSUI_CSTR("True Color Rendering"), FSUI_CSTR("Disables dithering and uses the full 8 bits per channel of color information."), "GPU", "TrueColor", true, is_hardware); @@ -6547,6 +6553,7 @@ TRANSLATE_NOOP("FullscreenUI", "Apply Image Patches"); TRANSLATE_NOOP("FullscreenUI", "Apply Per-Game Settings"); TRANSLATE_NOOP("FullscreenUI", "Are you sure you want to clear the current post-processing chain? All configuration will be lost."); TRANSLATE_NOOP("FullscreenUI", "Aspect Ratio"); +TRANSLATE_NOOP("FullscreenUI", "Attempts to detect one pixel high/wide lines that rely on non-upscaled rasterization behavior, filling in gaps introduced by upscaling."); TRANSLATE_NOOP("FullscreenUI", "Attempts to map the selected port to a chosen controller."); TRANSLATE_NOOP("FullscreenUI", "Audio Backend"); TRANSLATE_NOOP("FullscreenUI", "Audio Control"); @@ -6777,6 +6784,7 @@ TRANSLATE_NOOP("FullscreenUI", "Leaderboard Notifications"); TRANSLATE_NOOP("FullscreenUI", "Leaderboards"); TRANSLATE_NOOP("FullscreenUI", "Leaderboards are not enabled."); TRANSLATE_NOOP("FullscreenUI", "Limits how many frames are displayed to the screen. These frames are still rendered."); +TRANSLATE_NOOP("FullscreenUI", "Line Detection"); TRANSLATE_NOOP("FullscreenUI", "List Settings"); TRANSLATE_NOOP("FullscreenUI", "Load Devices From Save States"); TRANSLATE_NOOP("FullscreenUI", "Load Profile"); diff --git a/src/core/game_database.cpp b/src/core/game_database.cpp index 3e1d432e1..cc8451f52 100644 --- a/src/core/game_database.cpp +++ b/src/core/game_database.cpp @@ -33,7 +33,7 @@ namespace GameDatabase { enum : u32 { GAME_DATABASE_CACHE_SIGNATURE = 0x45434C48, - GAME_DATABASE_CACHE_VERSION = 6, + GAME_DATABASE_CACHE_VERSION = 7, }; static Entry* GetMutableEntry(const std::string_view& serial); @@ -184,6 +184,31 @@ static std::optional GetOptionalTFromObject(const ryml::ConstNodeRef& object, return ret; } +template +static std::optional ParseOptionalTFromObject(const ryml::ConstNodeRef& object, std::string_view key, + std::optional (*from_string_function)(const char* str)) +{ + std::optional ret; + + const ryml::ConstNodeRef member = object.find_child(to_csubstr(key)); + if (member.valid()) + { + const c4::csubstr val = member.val(); + if (!val.empty()) + { + ret = from_string_function(TinyString(to_stringview(val))); + if (!ret.has_value()) + Log_ErrorFmt("Unknown value for {}: {}", key, to_stringview(val)); + } + else + { + Log_ErrorFmt("Unexpected empty value in {}", key); + } + } + + return ret; +} + void GameDatabase::EnsureLoaded() { if (s_loaded) @@ -335,6 +360,8 @@ void GameDatabase::Entry::ApplySettings(Settings& settings, bool display_osd_mes settings.gpu_pgxp_tolerance = gpu_pgxp_tolerance.value(); if (gpu_pgxp_depth_threshold.has_value()) settings.SetPGXPDepthClearThreshold(gpu_pgxp_depth_threshold.value()); + if (gpu_line_detect_mode.has_value()) + settings.gpu_line_detect_mode = gpu_line_detect_mode.value(); if (HasTrait(Trait::ForceInterpreter)) { @@ -713,6 +740,7 @@ bool GameDatabase::LoadFromCache() !ReadOptionalFromStream(stream.get(), &entry.gpu_max_run_ahead) || !ReadOptionalFromStream(stream.get(), &entry.gpu_pgxp_tolerance) || !ReadOptionalFromStream(stream.get(), &entry.gpu_pgxp_depth_threshold) || + !ReadOptionalFromStream(stream.get(), &entry.gpu_line_detect_mode) || !stream->ReadSizePrefixedString(&entry.disc_set_name) || !stream->ReadU32(&num_disc_set_serials)) { Log_DevPrintf("Cache entry is corrupted."); @@ -811,6 +839,7 @@ bool GameDatabase::SaveToCache() result = result && WriteOptionalToStream(stream.get(), entry.gpu_max_run_ahead); result = result && WriteOptionalToStream(stream.get(), entry.gpu_pgxp_tolerance); result = result && WriteOptionalToStream(stream.get(), entry.gpu_pgxp_depth_threshold); + result = result && WriteOptionalToStream(stream.get(), entry.gpu_line_detect_mode); result = result && stream->WriteSizePrefixedString(entry.disc_set_name); result = result && stream->WriteU32(static_cast(entry.disc_set_serials.size())); @@ -1019,6 +1048,8 @@ bool GameDatabase::ParseYamlEntry(Entry* entry, const ryml::ConstNodeRef& value) entry->gpu_max_run_ahead = GetOptionalTFromObject(settings, "gpuMaxRunAhead"); entry->gpu_pgxp_tolerance = GetOptionalTFromObject(settings, "gpuPGXPTolerance"); entry->gpu_pgxp_depth_threshold = GetOptionalTFromObject(settings, "gpuPGXPDepthThreshold"); + entry->gpu_line_detect_mode = + ParseOptionalTFromObject(settings, "gpuLineDetectMode", &Settings::ParseLineDetectModeName); } if (const ryml::ConstNodeRef disc_set = value.find_child("discSet"); disc_set.valid() && disc_set.has_children()) diff --git a/src/core/game_database.h b/src/core/game_database.h index 81b4ad68c..daabaae77 100644 --- a/src/core/game_database.h +++ b/src/core/game_database.h @@ -80,6 +80,7 @@ struct Entry std::optional gpu_max_run_ahead; std::optional gpu_pgxp_tolerance; std::optional gpu_pgxp_depth_threshold; + std::optional gpu_line_detect_mode; std::string disc_set_name; std::vector disc_set_serials; diff --git a/src/core/gpu_hw.cpp b/src/core/gpu_hw.cpp index da69aa622..7ef0702b1 100644 --- a/src/core/gpu_hw.cpp +++ b/src/core/gpu_hw.cpp @@ -200,6 +200,7 @@ bool GPU_HW::Initialize() m_debanding = g_settings.gpu_debanding; m_scaled_dithering = g_settings.gpu_scaled_dithering; 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_chroma_smoothing = g_settings.gpu_24bit_chroma_smoothing; @@ -386,6 +387,7 @@ void GPU_HW::UpdateSettings(const Settings& old_settings) m_debanding = g_settings.gpu_debanding; m_scaled_dithering = g_settings.gpu_scaled_dithering; 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 = clamp_uvs; m_compute_uv_range = m_clamp_uvs; m_chroma_smoothing = g_settings.gpu_24bit_chroma_smoothing; @@ -1410,7 +1412,7 @@ void GPU_HW::ClearDisplay() g_gpu_device->ClearRenderTarget(m_display_private_texture.get(), 0xFF000000u); } -void GPU_HW::HandleFlippedQuadTextureCoordinates(BatchVertex* vertices) +ALWAYS_INLINE_RELEASE void GPU_HW::HandleFlippedQuadTextureCoordinates(BatchVertex* vertices) { // Taken from beetle-psx gpu_polygon.cpp // For X/Y flipped 2D sprites, PSX games rely on a very specific rasterization behavior. If U or V is decreasing in X @@ -1433,6 +1435,24 @@ void GPU_HW::HandleFlippedQuadTextureCoordinates(BatchVertex* vertices) const float cax = vertices[0].x - vertices[2].x; const float cay = vertices[0].y - vertices[2].y; + // Hack for Wild Arms 2: The player sprite is drawn one line at a time with a quad, but the bottom V coordinates + // are set to a large distance from the top V coordinate. When upscaling, this means that the coordinate is + // interpolated between these two values, result in out-of-bounds sampling. At native, it's fine, because at the + // top of the primitive, no amount is added to the coordinates. So, in this case, just set all coordinates to the + // same value, from the first vertex, ensuring no interpolation occurs. Gate it based on the Y distance being one + // pixel, limiting the risk of false positives. + if (m_line_detect_mode == GPULineDetectMode::Quads && + (std::max(vertices[0].y, std::max(vertices[1].y, std::max(vertices[2].y, vertices[3].y))) - + std::min(vertices[0].y, std::min(vertices[1].y, std::min(vertices[2].y, vertices[3].y)))) == 1.0f) [[unlikely]] + { + GL_INS_FMT("HLineQuad detected at [{},{}={},{} {},{}={},{} {},{}={},{} {},{}={},{}", vertices[0].x, vertices[0].y, + vertices[0].u, vertices[0].v, vertices[1].x, vertices[1].y, vertices[1].u, vertices[1].v, vertices[2].x, + vertices[2].y, vertices[2].u, vertices[2].v, vertices[3].x, vertices[3].y, vertices[3].u, vertices[3].v); + vertices[1].v = vertices[0].v; + vertices[2].v = vertices[0].v; + vertices[3].v = vertices[0].v; + } + // Compute static derivatives, just assume W is uniform across the primitive and that the plane equation remains the // same across the quad. (which it is, there is no Z.. yet). const float dudx = -aby * static_cast(vertices[2].u) - bcy * static_cast(vertices[0].u) - @@ -1449,11 +1469,8 @@ void GPU_HW::HandleFlippedQuadTextureCoordinates(BatchVertex* vertices) 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); - // Leverage PGXP to further avoid 3D polygons that just happen to align this way after projection - const bool is_3d = (vertices[0].w != vertices[1].w || vertices[0].w != vertices[2].w); - // Shouldn't matter as degenerate primitives will be culled anyways. - if (area == 0.0f || texArea == 0 || is_3d) + if (area == 0.0f || texArea == 0) return; // Use floats here as it'll be faster than integer divides. @@ -1500,6 +1517,156 @@ void GPU_HW::HandleFlippedQuadTextureCoordinates(BatchVertex* vertices) } } +ALWAYS_INLINE_RELEASE void GPU_HW::ExpandLineTriangles(BatchVertex* vertices, u32 base_vertex) +{ + // Line expansion inspired by beetle-psx. + BatchVertex *vshort, *vlong; + bool vertical, horizontal; + + if (m_line_detect_mode == GPULineDetectMode::BasicTriangles) + { + // Given a tall/one-pixel-wide triangle, determine which vertex is the corner with axis-aligned edges. + BatchVertex* vcorner; + if (vertices[0].u == vertices[1].u && vertices[0].v == vertices[1].v) + { + // A,B,C + vcorner = &vertices[0]; + vshort = &vertices[1]; + vlong = &vertices[2]; + } + else if (vertices[1].u == vertices[2].u && vertices[1].v == vertices[2].v) + { + // B,C,A + vcorner = &vertices[1]; + vshort = &vertices[2]; + vlong = &vertices[0]; + } + else if (vertices[2].u == vertices[0].u && vertices[2].v == vertices[0].v) + { + // C,A,B + vcorner = &vertices[2]; + vshort = &vertices[0]; + vlong = &vertices[1]; + } + else + { + return; + } + + // Determine line direction. Vertical lines will have a width of 1, horizontal lines a height of 1. + vertical = ((vcorner->y == vshort->y) && (std::abs(vcorner->x - vshort->x) == 1.0f)); + horizontal = ((vcorner->x == vshort->x) && (std::abs(vcorner->y - vshort->y) == 1.0f)); + if (vertical) + { + // Line should be vertical. Make sure the triangle is actually a right angle. + if (vshort->x == vlong->x) + std::swap(vshort, vcorner); + else if (vcorner->x != vlong->x) + return; + + GL_INS_FMT("Vertical line from Y={} to {}", vcorner->y, vlong->y); + } + else if (horizontal) + { + // Line should be horizontal. Make sure the triangle is actually a right angle. + if (vshort->y == vlong->y) + std::swap(vshort, vcorner); + else if (vcorner->y != vlong->y) + return; + + GL_INS_FMT("Horizontal line from X={} to {}", vcorner->x, vlong->x); + } + else + { + // Not a line-like triangle. + return; + } + + // We could adjust the short texture coordinate to +1 from its original position, rather than leaving it the same. + // However, since the texture is unlikely to be a higher resolution than the one-wide triangle, there would be no + // benefit in doing so. + } + else + { + DebugAssert(m_line_detect_mode == GPULineDetectMode::AggressiveTriangles); + + // Find direction of line based on horizontal position. + BatchVertex *va, *vb, *vc; + if (vertices[0].x == vertices[1].x) + { + va = &vertices[0]; + vb = &vertices[1]; + vc = &vertices[2]; + } + else if (vertices[1].x == vertices[2].x) + { + va = &vertices[1]; + vb = &vertices[2]; + vc = &vertices[0]; + } + else if (vertices[2].x == vertices[0].x) + { + va = &vertices[2]; + vb = &vertices[0]; + vc = &vertices[1]; + } + else + { + return; + } + + // Determine line direction. Vertical lines will have a width of 1, horizontal lines a height of 1. + vertical = (std::abs(va->x - vc->x) == 1.0f); + horizontal = (std::abs(va->y - vb->y) == 1.0f); + if (!vertical && !horizontal) + return; + + // Determine which vertex is the right angle, based on the vertical position. + const BatchVertex* vcorner; + if (va->y == vc->y) + vcorner = va; + else if (vb->y == vc->y) + vcorner = vb; + else + return; + + // Find short/long edge of the triangle. + BatchVertex* vother = ((vcorner == va) ? vb : va); + vshort = horizontal ? vother : vc; + vlong = vertical ? vother : vc; + + // Dark Forces draws its gun sprite vertically, but rotated compared to the sprite date in VRAM. + // Therefore the difference in V should be ignored. + vshort->u = vcorner->u; + vshort->v = vcorner->v; + + // We need to re-compute the UV limits, since we adjusted them above. + if (m_compute_uv_range) + ComputePolygonUVLimits(vertices[0].texpage, vertices, 3); + + // This is super jank, but because we rewrote the UVs on one of the vertices above, we need to rewrite it to GPU + // memory again. Has to be all of them as well, not just vshort, because the UV limits may have changed. + DebugAssert(m_batch_vertex_count >= 3); + std::memcpy(m_batch_vertex_ptr - 3, vertices, sizeof(BatchVertex) * 3); + } + + // Need to write the 4th vertex to the GPU. + DebugAssert(m_batch_vertex_space >= 1); + BatchVertex* last = &(*(m_batch_vertex_ptr++) = *vlong); + last->x = vertical ? vshort->x : vlong->x; + last->y = horizontal ? vshort->y : vlong->y; + m_batch_vertex_count++; + m_batch_vertex_space--; + + // Generate indices for second triangle. + DebugAssert(m_batch_index_space >= 3); + *(m_batch_index_ptr++) = Truncate16(base_vertex + (vshort - vertices)); + *(m_batch_index_ptr++) = Truncate16(base_vertex + (vlong - vertices)); + *(m_batch_index_ptr++) = Truncate16(base_vertex + 3); + m_batch_index_count += 3; + m_batch_index_space -= 3; +} + void GPU_HW::ComputePolygonUVLimits(u32 texpage, BatchVertex* vertices, u32 num_vertices) { u32 min_u = vertices[0].u, max_u = vertices[0].u, min_v = vertices[0].v, max_v = vertices[0].v; @@ -1727,7 +1894,9 @@ void GPU_HW::LoadVertices() } } - if (rc.quad_polygon && m_resolution_scale > 1) + // Use PGXP to exclude primitives that are definitely 3D. + 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()); if (m_compute_uv_range && textured) @@ -1761,8 +1930,9 @@ void GPU_HW::LoadVertices() const s32 max_x = std::max(max_x_12, native_vertex_positions[0][0]); const s32 min_y = std::min(min_y_12, native_vertex_positions[0][1]); const s32 max_y = std::max(max_y_12, native_vertex_positions[0][1]); + const bool first_tri_culled = ((max_x - min_x) >= MAX_PRIMITIVE_WIDTH || (max_y - min_y) >= MAX_PRIMITIVE_HEIGHT); - if ((max_x - min_x) >= MAX_PRIMITIVE_WIDTH || (max_y - min_y) >= MAX_PRIMITIVE_HEIGHT) + if (first_tri_culled) { Log_DebugFmt("Culling too-large polygon: {},{} {},{} {},{}", native_vertex_positions[0][0], native_vertex_positions[0][1], native_vertex_positions[1][0], native_vertex_positions[1][1], @@ -1828,6 +1998,12 @@ void GPU_HW::LoadVertices() m_batch_index_space -= 3; } } + else + { + // Expand lines to triangles (Doom, Soul Blade, etc.) + if (m_line_detect_mode >= GPULineDetectMode::BasicTriangles && !is_3d && !first_tri_culled) + ExpandLineTriangles(vertices.data(), start_index); + } if (m_sw_renderer) { diff --git a/src/core/gpu_hw.h b/src/core/gpu_hw.h index e0917d416..02702b557 100644 --- a/src/core/gpu_hw.h +++ b/src/core/gpu_hw.h @@ -189,7 +189,8 @@ private: void DrawLine(float x0, float y0, u32 col0, float x1, float y1, u32 col1, float depth); /// Handles quads with flipped texture coordinate directions. - static void HandleFlippedQuadTextureCoordinates(BatchVertex* vertices); + void HandleFlippedQuadTextureCoordinates(BatchVertex* vertices); + void ExpandLineTriangles(BatchVertex* vertices, u32 base_vertex); /// Computes polygon U/V boundaries. void ComputePolygonUVLimits(u32 texpage, BatchVertex* vertices, u32 num_vertices); @@ -240,6 +241,7 @@ private: bool m_disable_color_perspective : 1 = false; GPUTextureFilter m_texture_filtering = GPUTextureFilter::Nearest; + GPULineDetectMode m_line_detect_mode = GPULineDetectMode::Disabled; GPUDownsampleMode m_downsample_mode = GPUDownsampleMode::Disabled; GPUWireframeMode m_wireframe_mode = GPUWireframeMode::Disabled; bool m_true_color : 1 = true; diff --git a/src/core/settings.cpp b/src/core/settings.cpp index e37da687c..b8f9e9246 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -196,6 +196,10 @@ void Settings::Load(SettingsInterface& si) ParseTextureFilterName( si.GetStringValue("GPU", "TextureFilter", GetTextureFilterName(DEFAULT_GPU_TEXTURE_FILTER)).c_str()) .value_or(DEFAULT_GPU_TEXTURE_FILTER); + gpu_line_detect_mode = + ParseLineDetectModeName( + si.GetStringValue("GPU", "LineDetectMode", GetLineDetectModeName(DEFAULT_GPU_LINE_DETECT_MODE)).c_str()) + .value_or(DEFAULT_GPU_LINE_DETECT_MODE); gpu_downsample_mode = ParseDownsampleModeName( si.GetStringValue("GPU", "DownsampleMode", GetDownsampleModeName(DEFAULT_GPU_DOWNSAMPLE_MODE)).c_str()) @@ -461,6 +465,7 @@ void Settings::Save(SettingsInterface& si) const si.SetBoolValue("GPU", "Debanding", gpu_debanding); si.SetBoolValue("GPU", "ScaledDithering", gpu_scaled_dithering); si.SetStringValue("GPU", "TextureFilter", GetTextureFilterName(gpu_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); si.SetStringValue("GPU", "WireframeMode", GetGPUWireframeModeName(gpu_wireframe_mode)); @@ -621,6 +626,7 @@ void Settings::FixIncompatibleSettings(bool display_osd_messages) g_settings.gpu_debanding = false; g_settings.gpu_scaled_dithering = false; g_settings.gpu_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; g_settings.gpu_widescreen_hack = false; @@ -1005,8 +1011,9 @@ RenderAPI Settings::GetRenderAPIForRenderer(GPURenderer renderer) } } -static constexpr const std::array s_texture_filter_names = {"Nearest", "Bilinear", "BilinearBinAlpha", "JINC2", - "JINC2BinAlpha", "xBR", "xBRBinAlpha"}; +static constexpr const std::array s_texture_filter_names = { + "Nearest", "Bilinear", "BilinearBinAlpha", "JINC2", "JINC2BinAlpha", "xBR", "xBRBinAlpha", +}; static constexpr const std::array s_texture_filter_display_names = { TRANSLATE_NOOP("GPUTextureFilter", "Nearest-Neighbor"), TRANSLATE_NOOP("GPUTextureFilter", "Bilinear"), @@ -1014,7 +1021,8 @@ static constexpr const std::array s_texture_filter_display_names = { TRANSLATE_NOOP("GPUTextureFilter", "JINC2 (Slow)"), TRANSLATE_NOOP("GPUTextureFilter", "JINC2 (Slow, No Edge Blending)"), TRANSLATE_NOOP("GPUTextureFilter", "xBR (Very Slow)"), - TRANSLATE_NOOP("GPUTextureFilter", "xBR (Very Slow, No Edge Blending)")}; + TRANSLATE_NOOP("GPUTextureFilter", "xBR (Very Slow, No Edge Blending)"), +}; std::optional Settings::ParseTextureFilterName(const char* str) { @@ -1032,7 +1040,7 @@ std::optional Settings::ParseTextureFilterName(const char* str const char* Settings::GetTextureFilterName(GPUTextureFilter filter) { - return s_texture_filter_names[static_cast(filter)]; + return s_texture_filter_names[static_cast(filter)]; } const char* Settings::GetTextureFilterDisplayName(GPUTextureFilter filter) @@ -1040,6 +1048,43 @@ const char* Settings::GetTextureFilterDisplayName(GPUTextureFilter filter) return Host::TranslateToCString("GPUTextureFilter", s_texture_filter_display_names[static_cast(filter)]); } +static constexpr const std::array s_line_detect_mode_names = { + "Disabled", + "Quads", + "BasicTriangles", + "AggressiveTriangles", +}; +static constexpr const std::array s_line_detect_mode_detect_names = { + TRANSLATE_NOOP("GPULineDetectMode", "Disabled"), + TRANSLATE_NOOP("GPULineDetectMode", "Quads"), + TRANSLATE_NOOP("GPULineDetectMode", "Triangles (Basic)"), + TRANSLATE_NOOP("GPULineDetectMode", "Triangles (Aggressive)"), +}; + +std::optional Settings::ParseLineDetectModeName(const char* str) +{ + int index = 0; + for (const char* name : s_line_detect_mode_names) + { + if (StringUtil::Strcasecmp(name, str) == 0) + return static_cast(index); + + index++; + } + + return std::nullopt; +} + +const char* Settings::GetLineDetectModeName(GPULineDetectMode mode) +{ + return s_line_detect_mode_names[static_cast(mode)]; +} + +const char* Settings::GetLineDetectModeDisplayName(GPULineDetectMode mode) +{ + return Host::TranslateToCString("GPULineDetectMode", s_line_detect_mode_detect_names[static_cast(mode)]); +} + static constexpr const std::array s_downsample_mode_names = {"Disabled", "Box", "Adaptive"}; static constexpr const std::array s_downsample_mode_display_names = { TRANSLATE_NOOP("GPUDownsampleMode", "Disabled"), diff --git a/src/core/settings.h b/src/core/settings.h index cd37f628f..d0c028327 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -115,6 +115,7 @@ struct Settings bool gpu_debanding : 1 = false; bool gpu_scaled_dithering : 1 = true; GPUTextureFilter gpu_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; GPUWireframeMode gpu_wireframe_mode = DEFAULT_GPU_WIREFRAME_MODE; @@ -378,6 +379,10 @@ struct Settings static const char* GetTextureFilterName(GPUTextureFilter filter); static const char* GetTextureFilterDisplayName(GPUTextureFilter filter); + static std::optional ParseLineDetectModeName(const char* str); + static const char* GetLineDetectModeName(GPULineDetectMode filter); + static const char* GetLineDetectModeDisplayName(GPULineDetectMode filter); + static std::optional ParseDownsampleModeName(const char* str); static const char* GetDownsampleModeName(GPUDownsampleMode mode); static const char* GetDownsampleModeDisplayName(GPUDownsampleMode mode); @@ -428,6 +433,7 @@ struct Settings static constexpr GPURenderer DEFAULT_GPU_RENDERER = GPURenderer::Automatic; static constexpr GPUTextureFilter DEFAULT_GPU_TEXTURE_FILTER = GPUTextureFilter::Nearest; + static constexpr GPULineDetectMode DEFAULT_GPU_LINE_DETECT_MODE = GPULineDetectMode::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; diff --git a/src/core/system.cpp b/src/core/system.cpp index 4dd09b704..bd82b83fc 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -3644,6 +3644,7 @@ void System::CheckForSettingsChanges(const Settings& old_settings) g_settings.gpu_debanding != old_settings.gpu_debanding || g_settings.gpu_scaled_dithering != old_settings.gpu_scaled_dithering || g_settings.gpu_texture_filter != old_settings.gpu_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 || g_settings.gpu_24bit_chroma_smoothing != old_settings.gpu_24bit_chroma_smoothing || diff --git a/src/core/types.h b/src/core/types.h index d2096bcc6..8138fe313 100644 --- a/src/core/types.h +++ b/src/core/types.h @@ -105,6 +105,15 @@ enum class GPUWireframeMode : u8 Count, }; +enum class GPULineDetectMode : u8 +{ + Disabled, + Quads, + BasicTriangles, + AggressiveTriangles, + Count +}; + enum class DisplayCropMode : u8 { None, diff --git a/src/duckstation-qt/enhancementsettingswidget.cpp b/src/duckstation-qt/enhancementsettingswidget.cpp index 7f5036b20..4ffb7a782 100644 --- a/src/duckstation-qt/enhancementsettingswidget.cpp +++ b/src/duckstation-qt/enhancementsettingswidget.cpp @@ -17,6 +17,12 @@ EnhancementSettingsWidget::EnhancementSettingsWidget(SettingsWindow* dialog, QWi setupAdditionalUi(); SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.resolutionScale, "GPU", "ResolutionScale", 1); + SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.textureFiltering, "GPU", "TextureFilter", + &Settings::ParseTextureFilterName, &Settings::GetTextureFilterName, + Settings::DEFAULT_GPU_TEXTURE_FILTER); + SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.gpuLineDetectMode, "GPU", "LineDetectMode", + &Settings::ParseLineDetectModeName, &Settings::GetLineDetectModeName, + Settings::DEFAULT_GPU_LINE_DETECT_MODE); SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.gpuDownsampleMode, "GPU", "DownsampleMode", &Settings::ParseDownsampleModeName, &Settings::GetDownsampleModeName, Settings::DEFAULT_GPU_DOWNSAMPLE_MODE); @@ -28,9 +34,6 @@ EnhancementSettingsWidget::EnhancementSettingsWidget(SettingsWindow* dialog, QWi SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.forceNTSCTimings, "GPU", "ForceNTSCTimings", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.force43For24Bit, "Display", "Force4_3For24Bit", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.chromaSmoothingFor24Bit, "GPU", "ChromaSmoothing24Bit", false); - SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.textureFiltering, "GPU", "TextureFilter", - &Settings::ParseTextureFilterName, &Settings::GetTextureFilterName, - Settings::DEFAULT_GPU_TEXTURE_FILTER); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.widescreenHack, "GPU", "WidescreenHack", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.useSoftwareRendererForReadbacks, "GPU", "UseSoftwareRendererForReadbacks", false); @@ -105,10 +108,14 @@ EnhancementSettingsWidget::EnhancementSettingsWidget(SettingsWindow* dialog, QWi "Only applies to the hardware renderers.")); dialog->registerWidgetHelp( m_ui.textureFiltering, tr("Texture Filtering"), - QString::fromUtf8(Settings::GetTextureFilterDisplayName(GPUTextureFilter::Nearest)), + QString::fromUtf8(Settings::GetTextureFilterDisplayName(Settings::DEFAULT_GPU_TEXTURE_FILTER)), tr("Smooths out the blockiness of magnified textures on 3D object by using filtering.
Will have a " "greater effect on higher resolution scales. Only applies to the hardware renderers.
The JINC2 and " "especially xBR filtering modes are very demanding, and may not be worth the speed penalty.")); + dialog->registerWidgetHelp(m_ui.gpuLineDetectMode, tr("Line Detection"), + QString::fromUtf8(Settings::GetLineDetectModeName(Settings::DEFAULT_GPU_LINE_DETECT_MODE)), + tr("Attempts to detect one pixel high/wide lines that rely on non-upscaled rasterization " + "behavior, filling in gaps introduced by upscaling.")); dialog->registerWidgetHelp( m_ui.widescreenHack, tr("Widescreen Hack"), tr("Unchecked"), tr("Scales vertex positions in screen-space to a widescreen aspect ratio, essentially " @@ -190,6 +197,12 @@ void EnhancementSettingsWidget::setupAdditionalUi() QString::fromUtf8(Settings::GetTextureFilterDisplayName(static_cast(i)))); } + for (u32 i = 0; i < static_cast(GPULineDetectMode::Count); i++) + { + m_ui.gpuLineDetectMode->addItem( + QString::fromUtf8(Settings::GetLineDetectModeDisplayName(static_cast(i)))); + } + for (u32 i = 0; i < static_cast(GPUDownsampleMode::Count); i++) { m_ui.gpuDownsampleMode->addItem( diff --git a/src/duckstation-qt/enhancementsettingswidget.ui b/src/duckstation-qt/enhancementsettingswidget.ui index 2ea47d4dc..9cf7cb407 100644 --- a/src/duckstation-qt/enhancementsettingswidget.ui +++ b/src/duckstation-qt/enhancementsettingswidget.ui @@ -7,7 +7,7 @@ 0 0 640 - 452 + 480 @@ -49,14 +49,14 @@ - + Downsampling: - + @@ -76,7 +76,7 @@ - + @@ -122,6 +122,16 @@ + + + + Line Detection: + + + + + +