GPU/HW: Add quad line detection (Wild Arms 2)

This commit is contained in:
Stenzek 2024-03-02 15:50:46 +10:00
parent 250fb56838
commit 713d396a7e
No known key found for this signature in database
12 changed files with 395 additions and 64 deletions

View File

@ -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

View File

@ -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");

View File

@ -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<T> GetOptionalTFromObject(const ryml::ConstNodeRef& object,
return ret;
}
template<typename T>
static std::optional<T> ParseOptionalTFromObject(const ryml::ConstNodeRef& object, std::string_view key,
std::optional<T> (*from_string_function)(const char* str))
{
std::optional<T> 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<u32>(entry.disc_set_serials.size()));
@ -1019,6 +1048,8 @@ bool GameDatabase::ParseYamlEntry(Entry* entry, const ryml::ConstNodeRef& value)
entry->gpu_max_run_ahead = GetOptionalTFromObject<u32>(settings, "gpuMaxRunAhead");
entry->gpu_pgxp_tolerance = GetOptionalTFromObject<float>(settings, "gpuPGXPTolerance");
entry->gpu_pgxp_depth_threshold = GetOptionalTFromObject<float>(settings, "gpuPGXPDepthThreshold");
entry->gpu_line_detect_mode =
ParseOptionalTFromObject<GPULineDetectMode>(settings, "gpuLineDetectMode", &Settings::ParseLineDetectModeName);
}
if (const ryml::ConstNodeRef disc_set = value.find_child("discSet"); disc_set.valid() && disc_set.has_children())

View File

@ -80,6 +80,7 @@ struct Entry
std::optional<u32> gpu_max_run_ahead;
std::optional<float> gpu_pgxp_tolerance;
std::optional<float> gpu_pgxp_depth_threshold;
std::optional<GPULineDetectMode> gpu_line_detect_mode;
std::string disc_set_name;
std::vector<std::string> disc_set_serials;

View File

@ -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<float>(vertices[2].u) - bcy * static_cast<float>(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)
{

View File

@ -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;

View File

@ -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<GPUTextureFilter> Settings::ParseTextureFilterName(const char* str)
{
@ -1032,7 +1040,7 @@ std::optional<GPUTextureFilter> Settings::ParseTextureFilterName(const char* str
const char* Settings::GetTextureFilterName(GPUTextureFilter filter)
{
return s_texture_filter_names[static_cast<int>(filter)];
return s_texture_filter_names[static_cast<size_t>(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<int>(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<GPULineDetectMode> 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<GPULineDetectMode>(index);
index++;
}
return std::nullopt;
}
const char* Settings::GetLineDetectModeName(GPULineDetectMode mode)
{
return s_line_detect_mode_names[static_cast<size_t>(mode)];
}
const char* Settings::GetLineDetectModeDisplayName(GPULineDetectMode mode)
{
return Host::TranslateToCString("GPULineDetectMode", s_line_detect_mode_detect_names[static_cast<size_t>(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"),

View File

@ -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<GPULineDetectMode> ParseLineDetectModeName(const char* str);
static const char* GetLineDetectModeName(GPULineDetectMode filter);
static const char* GetLineDetectModeDisplayName(GPULineDetectMode filter);
static std::optional<GPUDownsampleMode> 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;

View File

@ -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 ||

View File

@ -105,6 +105,15 @@ enum class GPUWireframeMode : u8
Count,
};
enum class GPULineDetectMode : u8
{
Disabled,
Quads,
BasicTriangles,
AggressiveTriangles,
Count
};
enum class DisplayCropMode : u8
{
None,

View File

@ -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. <br>Will have a "
"greater effect on higher resolution scales. Only applies to the hardware renderers. <br>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<GPUTextureFilter>(i))));
}
for (u32 i = 0; i < static_cast<u32>(GPULineDetectMode::Count); i++)
{
m_ui.gpuLineDetectMode->addItem(
QString::fromUtf8(Settings::GetLineDetectModeDisplayName(static_cast<GPULineDetectMode>(i))));
}
for (u32 i = 0; i < static_cast<u32>(GPUDownsampleMode::Count); i++)
{
m_ui.gpuDownsampleMode->addItem(

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>640</width>
<height>452</height>
<height>480</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
@ -49,14 +49,14 @@
<item row="2" column="1">
<widget class="QComboBox" name="textureFiltering"/>
</item>
<item row="3" column="0">
<item row="4" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Downsampling:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<item row="4" column="1">
<layout class="QHBoxLayout" name="gpuDownsampleLayout" stretch="1,0">
<item>
<widget class="QComboBox" name="gpuDownsampleMode"/>
@ -76,7 +76,7 @@
</item>
</layout>
</item>
<item row="4" column="0" colspan="2">
<item row="5" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QCheckBox" name="trueColor">
@ -122,6 +122,16 @@
</item>
</layout>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Line Detection:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="gpuLineDetectMode"/>
</item>
</layout>
</widget>
</item>