Compare commits

...

10 Commits

Author SHA1 Message Date
refractionpcsx2 22cb6b0c9c GS/HW: Remove zero clear from GSUploadQueue, use transfer_type instead 2026-05-08 18:15:18 +02:00
PCSX2 Bot 81df4e5def [ci skip] Qt: Update Base Translation. 2026-05-07 02:15:53 +02:00
Ziemas a3f3e8733d GS: fix build with libstdc++ 16 2026-05-06 18:05:51 -04:00
SternXD 6fc84f76bb FullscreenUI: Fix RA login state, and add dismiss dialog 2026-05-06 20:55:44 +02:00
TJnotJT 0949f193b8 GS/SW: Adjust bounds in assertion to present false positive.
Account for the fact that bottom/right coordinates are exclusive.
2026-05-06 20:46:51 +02:00
TJnotJT 5f3dc1a76e GS/SW: Early exit optimization for multithreaded horizontal lines. 2026-05-06 20:46:51 +02:00
TJnotJT 9afea55bb6 GS/SW: Adjust bbox to account for AA1 and points/line rounding. 2026-05-06 20:46:51 +02:00
TJnotJT 433614ae85 GS: Adjust primitive bbox in vertex kick to account for AA1. 2026-05-06 20:46:51 +02:00
TJnotJT 00c85c4a8f GS/TC: Use temporary source on invalid TEX0. 2026-05-06 19:18:04 +02:00
PCSX2 Bot f9885315bd [ci skip] Qt: Update Base Translation. 2026-05-06 11:59:56 +02:00
15 changed files with 548 additions and 456 deletions
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -91,11 +91,11 @@ void GSDrawingContext::UpdateScissor()
scissor.in = rscissor + GSVector4i::cxpr(0, 0, 1, 1);
// Fixed-point scissor min/max, used for rejecting primitives which are entirely outside.
scissor.cull = rscissor.sll32<4>();
// Bottom-right end point should be exclusive for rectangle intersection test to work correctly.
scissor.cull = rscissor.sll32<4>() + GSVector4i::cxpr(0, 0, 1, 1);
// Offset applied to vertices for culling, zw is for native resolution culling
// We want to round subpixels down, because at least one pixel gets filled per scanline.
scissor.xyof = GSVector4i::loadl(&XYOFFSET.U64).xyxy().sub32(GSVector4i::cxpr(0, 0, 15, 15));
// Offset applied to vertices for culling.
scissor.xyof = GSVector4i::loadl(&XYOFFSET.U64).xyxy();
}
GIFRegTEX0 GSDrawingContext::GetSizeFixedTEX0(const GSVector4& st, bool linear, bool mipmap) const
+47 -70
View File
@@ -1279,12 +1279,12 @@ void GSState::DumpTransferList(const std::string& filename)
(*file) << std::endl;
// clear, EE->GS, or GS->GS
(*file) << LIST_ITEM << "type: " << (transfer.zero_clear ? "clear" : ((transfer.transfer_type == EEGS_TransferType::EE_to_GS) ? "EE_to_GS" : "GS_to_GS")) << std::endl;
(*file) << LIST_ITEM << "type: " << ((transfer.transfer_type == EEGS_TransferType::Clear) ? "clear" : ((transfer.transfer_type == EEGS_TransferType::EE_to_GS) ? "EE_to_GS" : "GS_to_GS")) << std::endl;
// Dump BITBLTBUF
(*file) << INDENT << "BITBLTBUF: " << OPEN_MAP;
const bool gs_to_gs = (transfer.transfer_type == EEGS_TransferType::GS_to_GS) && !transfer.zero_clear;
const bool gs_to_gs = (transfer.transfer_type == EEGS_TransferType::GS_to_GS) && transfer.transfer_type != EEGS_TransferType::Clear;
if (gs_to_gs)
{
@@ -1330,11 +1330,11 @@ void GSState::DumpTransferImages()
const GSUploadQueue& transfer = m_draw_transfers[i];
std::string filename;
if ((transfer.transfer_type == EEGS_TransferType::EE_to_GS) || transfer.zero_clear)
if ((transfer.transfer_type == EEGS_TransferType::EE_to_GS) || transfer.transfer_type == EEGS_TransferType::Clear)
{
// clear or EE->GS: only the destination info is relevant.
filename = GetDrawDumpPath("%05lld_transfer%02d_%s_%04x_%d_%s_%d_%d_%d_%d.png",
s_n, transfer_n++, (transfer.zero_clear ? "clear" : "EE_to_GS"), transfer.blit.DBP, transfer.blit.DBW,
s_n, transfer_n++, ((transfer.transfer_type == EEGS_TransferType::Clear) ? "clear" : "EE_to_GS"), transfer.blit.DBP, transfer.blit.DBW,
GSUtil::GetPSMName(transfer.blit.DPSM), transfer.rect.x, transfer.rect.y, transfer.rect.z, transfer.rect.w);
}
else
@@ -2666,7 +2666,6 @@ void GSState::FlushPrim()
GSVector4i* RESTRICT vert_ptr = (GSVector4i*)&m_vertex->buff[i];
GSVector4i v = vert_ptr[1];
v = v.xxxx().u16to32().sub32(m_xyof);
v = v.blend32<12>(v.sra32<4>());
m_vertex->xy[i & 3] = v;
m_vertex->xy_tail = unused;
}
@@ -2912,12 +2911,11 @@ void GSState::Write(const u8* mem, int len)
m_draw_transfers.pop_back();
transfer.rect = transfer.rect.runion(r);
transfer.draw = s_n;
transfer.zero_clear = false;
m_draw_transfers.push_back(transfer);
}
else
{
const GSUploadQueue new_transfer = {blit, r, s_n, false, EEGS_TransferType::EE_to_GS};
const GSUploadQueue new_transfer = {blit, s_n, r, EEGS_TransferType::EE_to_GS};
m_draw_transfers.push_back(new_transfer);
}
@@ -3112,12 +3110,11 @@ void GSState::Move()
m_draw_transfers.pop_back();
transfer.rect = transfer.rect.runion(r);
transfer.draw = s_n;
transfer.zero_clear = false;
m_draw_transfers.push_back(transfer);
}
else
{
const GSUploadQueue new_transfer = {m_env.BITBLTBUF, r, s_n, false, EEGS_TransferType::GS_to_GS};
const GSUploadQueue new_transfer = {m_env.BITBLTBUF, s_n, r, EEGS_TransferType::GS_to_GS};
m_draw_transfers.push_back(new_transfer);
}
@@ -3790,10 +3787,8 @@ void GSState::UpdateContext()
void GSState::UpdateScissor()
{
m_scissor_cull_min = m_context->scissor.cull.xyxy();
m_scissor_cull_max = m_context->scissor.cull.zwzw();
m_xyof = m_context->scissor.xyof;
m_scissor_invalid = !m_context->scissor.in.gt32(m_context->scissor.in.zwzw()).allfalse();
m_scissor_invalid = m_context->scissor.in.rempty();
}
void GSState::UpdateVertexKick()
@@ -5841,6 +5836,7 @@ template <u32 prim, bool auto_flush>
__forceinline void GSState::VertexKick(u32 skip)
{
constexpr u32 n = NumIndicesForPrim(prim);
constexpr int primclass = GSUtil::GetPrimClass(prim);
static_assert(n > 0);
pxAssert(m_vertex->tail < m_vertex->maxcount + 3);
@@ -5884,11 +5880,7 @@ __forceinline void GSState::VertexKick(u32 skip)
// We maintain the X/Y coordinates for the last 4 vertices, as well as the head for triangle fans, so we can compute
// the min/max, and cull degenerate triangles, which saves draws in some cases. Why 4? Mod 4 is cheaper than Mod 3.
// These vertices are a full vector containing <X_Fixed_Point, Y_Fixed_Point, X_Integer, Y_Integer>. We use the
// integer coordinates for culling at native resolution, and the fixed point for all others. The XY offset has to be
// applied, then we split it into the fixed/integer portions.
const GSVector4i xy_ofs = new_v1.xxxx().u16to32().sub32(m_xyof);
const GSVector4i xy = xy_ofs.blend32<12>(xy_ofs.sra32<4>());
const GSVector4i xy = new_v1.xxxx().u16to32().sub32(m_xyof);
m_vertex->xy[xy_tail & 3] = xy;
// Backup head for triangle fans so we can read it later, otherwise it'll get lost after the 4th vertex.
@@ -5918,69 +5910,55 @@ __forceinline void GSState::VertexKick(u32 skip)
// Skip draws when scissor is out of range (i.e. bottom-right is less than top-left), since everything will get clipped.
skip |= static_cast<u32>(m_scissor_invalid);
GSVector4i pmin, pmax;
GSVector4i bbox;
if (skip == 0)
{
const GSVector4i v0 = m_vertex->xy[(xy_tail - 1) & 3];
const GSVector4i v1 = m_vertex->xy[(xy_tail - 2) & 3];
const GSVector4i v2 = (prim == GS_TRIANGLEFAN) ? m_vertex->xyhead : m_vertex->xy[(xy_tail - 3) & 3];
switch (prim)
if constexpr (n == 1)
{
case GS_POINTLIST:
pmin = v0;
pmax = v0;
break;
case GS_LINELIST:
case GS_LINESTRIP:
case GS_SPRITE:
pmin = v0.min_i32(v1);
pmax = v0.max_i32(v1);
break;
case GS_TRIANGLELIST:
case GS_TRIANGLESTRIP:
case GS_TRIANGLEFAN:
pmin = v0.min_i32(v1.min_i32(v2));
pmax = v0.max_i32(v1.max_i32(v2));
break;
default:
break;
bbox = v0;
}
else if constexpr (n == 2)
{
bbox = v0.runion(v1);
}
else if constexpr (n == 3)
{
bbox = v0.runion(v1).runion(v2);
}
GSVector4i test = pmax.lt32(m_scissor_cull_min) | pmin.gt32(m_scissor_cull_max);
switch (prim)
if (m_nativeres && (primclass == GS_TRIANGLE_CLASS || primclass == GS_SPRITE_CLASS))
{
case GS_TRIANGLELIST:
case GS_TRIANGLESTRIP:
case GS_TRIANGLEFAN:
case GS_SPRITE:
{
// Discard degenerate triangles which don't cover at least one pixel. Since the vertices are in native
// resolution space, we can use the integer locations. When upscaling, we can't, because a primitive which
// does not span a single pixel at 1x may span multiple pixels at higher resolutions.
const GSVector4i degen_test = pmin.eq32(pmax);
test |= m_nativeres ? degen_test.zwzw() : degen_test;
}
break;
default:
break;
// For triangles and sprites at native res take the region strictly within the bounds
// omitting the bottom/right.
bbox = (bbox + GSVector4i(0xF, 0xF, -1, -1)) & GSVector4i(~0xF);
}
switch (prim)
// For AA1 triangles and lines, expand the bounds by 1 pixel on all sides.
// Note: redundant check for the AA1 flag to avoid calling a function if not needed.
if ((primclass == GS_TRIANGLE_CLASS || primclass == GS_LINE_CLASS) && PRIM->AA1 && IsCoverageAlphaSupported())
{
case GS_TRIANGLELIST:
case GS_TRIANGLESTRIP:
case GS_TRIANGLEFAN:
test = (test | v0.eq64(v1)) | (v1.eq64(v2) | v0.eq64(v2));
break;
default:
break;
bbox += GSVector4i(-0x10, -0x10, 0x10, 0x10);
}
// Make the bottom-right endpoints exclusive for rectangle intersection to work correctly.
bbox += GSVector4i::cxpr(0, 0, 1, 1);
// Do scissor test and empty bbox test.
GSVector4i test = GSVector4i(bbox.rintersects(m_context->scissor.cull)) == GSVector4i::cxpr(0);
// Test for degenerate triangle.
if (primclass == GS_TRIANGLE_CLASS)
{
test |= v0.eq64(v1) | v1.eq64(v2) | v0.eq64(v2);
}
#ifndef ARCH_ARM64
// We only care about the xy passing the skip test. zw is the offset coordinates for native culling.
skip |= test.mask() & 0xff;
// We only care about the xy passing the skip test.
skip |= test.mask();
#else
// mask() is slow on ARM, so just pull the bits out instead, thankfully we only care about the first 4 bytes.
skip |= (static_cast<u64>(test.extract64<0>()) & UINT64_C(0x8080808080808080)) != 0;
@@ -6094,13 +6072,12 @@ __forceinline void GSState::VertexKick(u32 skip)
ASSUME(0);
}
// Update rectangle for the current draw. We can use the re-integer coordinates from min/max here.
const GSVector4i draw_min = pmin.zwzw();
const GSVector4i draw_max = pmax;
if (m_index->tail != n)
temp_draw_rect = temp_draw_rect.min_i32(draw_min).blend32<12>(temp_draw_rect.max_i32(draw_max));
// Update rectangle for the current draw. Needs exclusive endpoints.
const GSVector4i draw_rect = bbox.sra32<4>() + GSVector4i(0, 0, 1, 1);
if (m_vertex->tail != n)
temp_draw_rect = temp_draw_rect.runion(draw_rect);
else
temp_draw_rect = draw_min.blend32<12>(draw_max);
temp_draw_rect = draw_rect;
temp_draw_rect = temp_draw_rect.rintersect(m_context->scissor.in);
constexpr u32 max_vertices = MaxVerticesForPrim(prim);
+3 -5
View File
@@ -131,8 +131,6 @@ protected:
GSVertex m_v = {};
float m_q = 1.0f;
GSVector4i m_scissor_cull_min = {};
GSVector4i m_scissor_cull_max = {};
GSVector4i m_xyof = {};
int m_used_buffers_idx = 0;
int m_current_buffer_idx = 0;
@@ -260,15 +258,15 @@ public:
{
EE_to_GS,
GS_to_GS,
GS_to_EE
GS_to_EE,
Clear
};
struct GSUploadQueue
{
GIFRegBITBLTBUF blit;
GSVector4i rect;
u64 draw;
bool zero_clear;
GSVector4i rect;
EEGS_TransferType transfer_type;
};
+24
View File
@@ -339,3 +339,27 @@ const char* GSUtil::GetPSMName(int psm)
}
return "BAD_PSM";
}
bool GSUtil::IsValidPSM(int psm)
{
switch (psm)
{
case PSMCT32:
case PSMCT24:
case PSMCT16:
case PSMCT16S:
case PSMT8:
case PSMT4:
case PSMT8H:
case PSMT4HL:
case PSMT4HH:
case PSMZ32:
case PSMZ24:
case PSMZ16:
case PSMZ16S:
case PSGPU24:
return true;
default:
return false;
}
}
+3
View File
@@ -6,6 +6,7 @@
#include "GS.h"
#include "GSRegs.h"
#include "GSPerfMon.h"
#include <climits>
class GSUtil
{
@@ -28,6 +29,8 @@ public:
static const char* GetACName(u32 ac);
static const char* GetPerfMonCounterName(GSPerfMon::counter_t counter, bool hw = true);
static bool IsValidPSM(int psm);
static const u32* HasSharedBitsPtr(u32 dpsm);
static bool HasSharedBits(u32 spsm, const u32* ptr);
static bool HasSharedBits(u32 spsm, u32 dpsm);
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0+
#include "GSDirtyRect.h"
#include <climits>
#include <vector>
GSDirtyRect::GSDirtyRect() :
+1
View File
@@ -4,6 +4,7 @@
#pragma once
#include "common/AlignedMalloc.h"
#include <climits>
template <class T>
struct Element
+2 -2
View File
@@ -9761,12 +9761,12 @@ bool GSRendererHW::TryGSMemClear(bool no_rt, bool preserve_rt, bool invalidate_r
rt_end_bp, m_cached_ctx.FRAME.PSM, m_cached_ctx.FRAME.FBW, m_cached_ctx.FRAME.FBMSK);
GSUploadQueue clear_queue;
clear_queue.transfer_type = EEGS_TransferType::Clear;
clear_queue.draw = s_n;
clear_queue.rect = m_r;
clear_queue.blit.DBP = m_cached_ctx.FRAME.Block();
clear_queue.blit.DBW = m_cached_ctx.FRAME.FBW;
clear_queue.blit.DPSM = m_cached_ctx.FRAME.PSM;
clear_queue.zero_clear = true;
m_draw_transfers.push_back(clear_queue);
}
else
@@ -9791,12 +9791,12 @@ bool GSRendererHW::TryGSMemClear(bool no_rt, bool preserve_rt, bool invalidate_r
ds_end_bp, m_cached_ctx.ZBUF.PSM, m_cached_ctx.FRAME.FBW);
GSUploadQueue clear_queue;
clear_queue.transfer_type = EEGS_TransferType::Clear;
clear_queue.draw = s_n;
clear_queue.rect = m_r;
clear_queue.blit.DBP = m_cached_ctx.ZBUF.Block();
clear_queue.blit.DBW = m_cached_ctx.FRAME.FBW;
clear_queue.blit.DPSM = m_cached_ctx.ZBUF.PSM;
clear_queue.zero_clear = true;
m_draw_transfers.push_back(clear_queue);
}
}
@@ -551,13 +551,13 @@ bool GSRendererHWFunctions::SwPrimRender(GSRendererHW& hw, bool invalidate_tc, b
if (add_ee_transfer)
{
GSRendererHW::GSUploadQueue uq;
uq.transfer_type = GSRendererHW::GetInstance()->EEGS_TransferType::EE_to_GS;
uq.blit.U64 = 0;
uq.blit.DBP = hw.m_cached_ctx.FRAME.Block();
uq.blit.DBW = hw.m_cached_ctx.FRAME.FBW;
uq.blit.DPSM = hw.m_cached_ctx.FRAME.PSM;
uq.draw = GSState::s_n;
uq.rect = bbox;
uq.zero_clear = false;
hw.m_draw_transfers.push_back(uq);
}
+13 -5
View File
@@ -1340,6 +1340,14 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const
return nullptr;
}
// Invalid values show up in dumps but it's unclear if it's a core issue or a bug in the games.
// Return a temporary source to prevent the lookup from corrupting the TC.
if (TEX0.TBW > 32 && !GSUtil::IsValidPSM(static_cast<int>(TEX0.PSM)))
{
GL_CACHE("TC: Invalid TEX0 (TBW=%d, PSM=%x), using temporary source.", TEX0.TBW, TEX0.PSM);
return CreateSource(TEX0, TEXA, CLAMP, nullptr, 0, 0, nullptr, nullptr, nullptr, SourceRegion(), true);
}
const GSVector2i compare_lod(lod ? *lod : GSVector2i(0, 0));
Source* src = nullptr;
@@ -3474,14 +3482,15 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons
else
eerect = eerect.runion(targetr);
const bool transfer_is_clear = iter->transfer_type == GSRendererHW::GetInstance()->EEGS_TransferType::Clear;
// Later writes might be partial over a previously cleared area. We want to upload in these cases.
hw_clear = hw_clear.has_value() ? (hw_clear.value() && iter->zero_clear) : iter->zero_clear;
hw_clear = hw_clear.has_value() ? (hw_clear.value() && transfer_is_clear) : transfer_is_clear;
// When the write covers the entire target, don't bother checking any earlier writes.
if (iter->blit.DBP <= TEX0.TBP0 && transfer_end >= rect_end)
{
// If it was a clear draw then we can use that as our target size.
if (iter->zero_clear && iter->blit.DBP == TEX0.TBP0 && iter->blit.DPSM == TEX0.PSM)
if (iter->transfer_type == GSRendererHW::GetInstance()->EEGS_TransferType::Clear && iter->blit.DBP == TEX0.TBP0 && iter->blit.DPSM == TEX0.PSM)
dst->UpdateValidity(iter->rect);
// Some games clear RT and Z at the same time, only erase if it's specifically this target.
@@ -3520,7 +3529,7 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons
if (iter->draw < (GSState::s_n - 1))
break;
if (iter->zero_clear && iter->blit.DBP == TEX0.TBP0 && iter->blit.DBW == TEX0.TBW)
if (iter->transfer_type == GSRendererHW::GetInstance()->EEGS_TransferType::Clear && iter->blit.DBP == TEX0.TBP0 && iter->blit.DBW == TEX0.TBW)
{
hw_clear = true;
transfers.erase(std::next(iter).base());
@@ -3855,7 +3864,6 @@ GSTextureCache::Target* GSTextureCache::LookupDisplayTarget(GIFRegTEX0 TEX0, con
const u32 bp = TEX0.TBP0;
Target* dst = nullptr;
Target* dst_match = nullptr;
auto* list = &m_dst[RenderTarget];
// Let's try to find a perfect frame that contains valid data
@@ -4011,7 +4019,7 @@ GSTextureCache::Target* GSTextureCache::LookupDisplayTarget(GIFRegTEX0 TEX0, con
else
eerect = eerect.runion(targetr);
if (iter->zero_clear && iter->draw == last_draw)
if (iter->transfer_type == GSRendererHW::GetInstance()->EEGS_TransferType::Clear && iter->draw == last_draw)
{
can_create = false;
break;
+5 -1
View File
@@ -527,6 +527,10 @@ void GSRasterizer::DrawEdgeLine(const GSVertexSW& v0, const GSVertexSW& v1, cons
const int rxi1 = static_cast<int>(rx1);
const int ryi1 = static_cast<int>(ry1);
// Early exit for horizontal lines.
if (delta_y == 0.0f && !IsOneOfMyScanlines(ryi0) && !aa)
return;
const GSVertexSW dedge = dv / GSVector4(std::abs(step_x ? delta_x : delta_y));
GSVertexSW edge(v0);
@@ -1546,7 +1550,7 @@ void GSRasterizerList::Queue(const GSRingHeap::SharedPtr<GSRasterizerData>& data
m_ds.SetupDraw(*data.get());
}
pxAssert(r.top >= 0 && r.top < 2048 && r.bottom >= 0 && r.bottom < 2048);
pxAssert(r.top >= 0 && r.top <= 2048 && r.bottom >= 0 && r.bottom <= 2048);
int top = r.top >> m_thread_height;
int bottom = std::min<int>((r.bottom + (1 << m_thread_height) - 1) >> m_thread_height, top + (int)m_workers.size());
+19 -1
View File
@@ -468,7 +468,25 @@ void GSRendererSW::Draw()
std::memcpy(sd->index, m_index->buff, sizeof(u16) * m_index->tail);
GSVector4i scissor = context->scissor.in;
GSVector4i bbox = GSVector4i(m_vt.m_min.p.floor().upld(m_vt.m_max.p.floor())) + GSVector4i(0, 0, 1, 1); // right/bottom should be exclusive so +1
GSVector4i bbox;
if (m_vt.m_primclass == GS_LINE_CLASS || m_vt.m_primclass == GS_POINT_CLASS)
{
// min: round, max: round
bbox = GSVector4i((m_vt.m_min.p + GSVector4(0.5f)).floor().upld((m_vt.m_max.p + GSVector4(0.5f)).floor()));
}
else
{
// min: ceil, max: floor
bbox = GSVector4i(m_vt.m_min.p.ceil().upld(m_vt.m_max.p.floor()));
}
if (PRIM->AA1 && (m_vt.m_primclass == GS_LINE_CLASS || m_vt.m_primclass == GS_TRIANGLE_CLASS))
{
bbox += GSVector4i(-1, -1, 1, 1); // Expand bbox by 1 on all sides when using antialiasing.
}
bbox += GSVector4i(0, 0, 1, 1); // right/bottom should be exclusive so +1
GSVector4i r = bbox.rintersect(scissor);
+77 -30
View File
@@ -649,7 +649,7 @@ void FullscreenUI::Render()
if (s_about_window_open)
DrawAboutWindow();
if (s_achievements_login_open)
if (s_achievements_login_open || ImGui::IsPopupOpen("RetroAchievements"))
DrawAchievementsLoginWindow();
if (s_input_binding_type != InputBindingInfo::Type::Unknown)
@@ -3499,14 +3499,18 @@ bool FullscreenUI::OpenAchievementsWindow()
void FullscreenUI::DrawAchievementsLoginWindow()
{
ImGui::SetNextWindowSize(LayoutScale(400.0f, 330.0f));
if (s_achievements_login_open && !ImGui::IsPopupOpen("RetroAchievements"))
ImGui::OpenPopup("RetroAchievements");
const float dialog_width = std::clamp(LayoutScale(420.0f), 300.0f, ImGui::GetIO().DisplaySize.x);
ImGui::SetNextWindowSizeConstraints(ImVec2(dialog_width, 0.0f), ImVec2(dialog_width, ImGui::GetIO().DisplaySize.y));
ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(ImGuiFullscreen::LAYOUT_WINDOW_ROUNDING));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(24.0f, 24.0f));
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0.13f, 0.13f, 0.13f, 0.95f));
if (ImGui::BeginPopupModal("RetroAchievements", &s_achievements_login_open, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar))
if (ImGui::BeginPopupModal("RetroAchievements", &s_achievements_login_open, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize))
{
const float content_width = ImGui::GetContentRegionAvail().x;
@@ -3553,7 +3557,7 @@ void FullscreenUI::DrawAchievementsLoginWindow()
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.4f, 0.4f, 0.4f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
if (s_achievements_login_logging_in)
if (s_achievements_login_logging_in || s_achievements_login_show_dismiss)
ImGui::BeginDisabled();
ImGui::SetNextItemWidth(content_width);
@@ -3567,7 +3571,7 @@ void FullscreenUI::DrawAchievementsLoginWindow()
ImGui::PopStyleColor(5);
ImGui::PopStyleVar(3);
if (s_achievements_login_logging_in)
if (s_achievements_login_logging_in || s_achievements_login_show_dismiss)
ImGui::EndDisabled();
ImGui::Spacing();
@@ -3582,20 +3586,67 @@ void FullscreenUI::DrawAchievementsLoginWindow()
ImGui::PopStyleColor();
ImGui::Spacing();
}
else if (s_achievements_login_show_dismiss)
{
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.35f, 0.9f, 0.35f, 1.0f));
const TinyString username = Host::GetBaseTinyStringSettingValue("Achievements", "Username", "");
const SmallString success_text = SmallString::from_format(
FSUI_FSTR("Successfully logged in as {}."), username.empty() ? "Unknown" : username.view());
const float status_width = ImGui::CalcTextSize(success_text.c_str()).x;
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (content_width - status_width) * 0.5f);
ImGui::TextUnformatted(success_text.c_str());
ImGui::PopStyleColor();
ImGui::Spacing();
}
const float button_height = LayoutScale(36.0f);
const float button_width = LayoutScale(100.0f);
const float button_height = std::max(LayoutScale(36.0f), 28.0f);
const float button_spacing = LayoutScale(12.0f);
const float total_width = (button_width * 2) + button_spacing;
const float start_x = (content_width - total_width) * 0.5f;
const float button_width = (content_width - button_spacing) * 0.5f;
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + start_x);
auto CloseLoginPopup = []() {
ImGui::CloseCurrentPopup();
s_achievements_login_open = false;
s_achievements_login_logging_in = false;
s_achievements_login_show_dismiss = false;
s_achievements_login_username[0] = '\0';
s_achievements_login_password[0] = '\0';
};
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, LayoutScale(ImGuiFullscreen::LAYOUT_FRAME_ROUNDING));
const bool can_login = !s_achievements_login_logging_in &&
strlen(s_achievements_login_username) > 0 &&
strlen(s_achievements_login_password) > 0;
const bool can_login = !s_achievements_login_show_dismiss && !s_achievements_login_logging_in &&
strlen(s_achievements_login_username) > 0 &&
strlen(s_achievements_login_password) > 0;
if (s_achievements_login_show_dismiss)
{
// keep dialog open and let user explicitly dismiss.
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.5f, 0.9f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.3f, 0.6f, 1.0f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.1f, 0.4f, 0.8f, 1.0f));
if (ImGui::Button(FSUI_CSTR("Dismiss"), ImVec2(button_width, button_height)) && !s_achievements_login_logging_in)
CloseLoginPopup();
ImGui::PopStyleColor(3);
ImGui::SameLine(0, button_spacing);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
ImGui::BeginDisabled();
ImGui::Button(FSUI_CSTR("Login"), ImVec2(button_width, button_height));
ImGui::EndDisabled();
ImGui::PopStyleColor(3);
ImGui::PopStyleVar();
ImGui::EndPopup();
ImGui::PopStyleColor();
ImGui::PopStyleVar(2);
return;
}
if (can_login)
{
@@ -3613,6 +3664,7 @@ void FullscreenUI::DrawAchievementsLoginWindow()
if (ImGui::Button(FSUI_CSTR("Login"), ImVec2(button_width, button_height)) && can_login)
{
s_achievements_login_logging_in = true;
s_achievements_login_show_dismiss = false;
Host::RunOnCPUThread([username = std::string(s_achievements_login_username),
password = std::string(s_achievements_login_password)]() {
@@ -3624,14 +3676,10 @@ void FullscreenUI::DrawAchievementsLoginWindow()
if (!result)
{
ShowToast(std::string(), fmt::format(FSUI_FSTR("Login failed.\nError: {}\n\nPlease check your username and password, and try again."),
error.GetDescription()));
error.GetDescription()));
return;
}
ImGui::CloseCurrentPopup();
s_achievements_login_open = false;
s_achievements_login_username[0] = '\0';
s_achievements_login_password[0] = '\0';
if (s_achievements_login_reason == Achievements::LoginRequestReason::UserInitiated)
@@ -3683,11 +3731,12 @@ void FullscreenUI::DrawAchievementsLoginWindow()
});
}
}
s_achievements_login_show_dismiss = true;
});
}
ImGui::PopStyleColor(3);
ImGui::SameLine(0, button_spacing);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.4f, 0.4f, 0.4f, 1.0f));
@@ -3702,12 +3751,7 @@ void FullscreenUI::DrawAchievementsLoginWindow()
Achievements::DisableHardcoreMode();
}
ImGui::CloseCurrentPopup();
s_achievements_login_open = false;
s_achievements_login_logging_in = false;
s_achievements_login_username[0] = '\0';
s_achievements_login_password[0] = '\0';
CloseLoginPopup();
}
ImGui::PopStyleColor(3);
@@ -3718,9 +3762,6 @@ void FullscreenUI::DrawAchievementsLoginWindow()
ImGui::PopStyleColor();
ImGui::PopStyleVar(2);
if (!ImGui::IsPopupOpen("RetroAchievements"))
ImGui::OpenPopup("RetroAchievements");
}
bool FullscreenUI::IsAchievementsWindowOpen()
@@ -3960,11 +4001,14 @@ void FullscreenUI::DrawAchievementsSettingsPage(std::unique_lock<std::mutex>& se
"sounds/achievements/lbsubmit.wav", FSUI_ICONSTR(ICON_FA_FOLDER_OPEN, "Select Leaderboard Submit Sound"));
MenuHeading(FSUI_CSTR("Account"));
if (bsi->ContainsValue("Achievements", "Token"))
SettingsInterface* secrets_si = Host::Internal::GetSecretsSettingsLayer();
const TinyString username = bsi->GetTinyStringValue("Achievements", "Username", "");
const bool has_token = (secrets_si && secrets_si->ContainsValue("Achievements", "Token"));
if (has_token)
{
ImGui::PushStyleColor(ImGuiCol_TextDisabled, ImGui::GetStyle().Colors[ImGuiCol_Text]);
ActiveButton(SmallString::from_format(
fmt::runtime(FSUI_ICONSTR(ICON_FA_USER, "Username: {}")), bsi->GetTinyStringValue("Achievements", "Username")),
fmt::runtime(FSUI_ICONSTR(ICON_FA_USER, "Username: {}")), username.empty() ? "Unknown" : username.view()),
false, false, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
ActiveButton(SmallString::from_format(fmt::runtime(FSUI_ICONSTR(ICON_FA_CLOCK, "Login token generated on {}")),
TimeToPrintableString(static_cast<time_t>(
@@ -3984,6 +4028,7 @@ void FullscreenUI::DrawAchievementsSettingsPage(std::unique_lock<std::mutex>& se
if (MenuButton(FSUI_ICONSTR(ICON_FA_KEY, "Login"), FSUI_CSTR("Logs in to RetroAchievements.")))
{
s_achievements_login_reason = Achievements::LoginRequestReason::UserInitiated;
s_achievements_login_show_dismiss = false;
s_achievements_login_open = true;
}
}
@@ -4172,6 +4217,7 @@ TRANSLATE_NOOP("FullscreenUI", "Please enter your user name and password for ret
TRANSLATE_NOOP("FullscreenUI", "Username");
TRANSLATE_NOOP("FullscreenUI", "Password");
TRANSLATE_NOOP("FullscreenUI", "Logging in...");
TRANSLATE_NOOP("FullscreenUI", "Dismiss");
TRANSLATE_NOOP("FullscreenUI", "Login");
TRANSLATE_NOOP("FullscreenUI", "Cancel");
TRANSLATE_NOOP("FullscreenUI", "When enabled and logged in, PCSX2 will scan for achievements on startup.");
@@ -4210,6 +4256,7 @@ TRANSLATE_NOOP("FullscreenUI", "Last Played: {}");
TRANSLATE_NOOP("FullscreenUI", "Size: {:.2f} MB");
TRANSLATE_NOOP("FullscreenUI", "Are you sure you want to reset the play time for '{}' ({})?\n\nYour current play time is {}.\n\nThis action cannot be undone.");
TRANSLATE_NOOP("FullscreenUI", "Version: {}");
TRANSLATE_NOOP("FullscreenUI", "Successfully logged in as {}.");
TRANSLATE_NOOP("FullscreenUI", "Login failed.\nError: {}\n\nPlease check your username and password, and try again.");
TRANSLATE_NOOP("FullscreenUI", "Failed to preview sound:\n{}");
TRANSLATE_NOOP("FullscreenUI", "Failed to Load State From Backup Slot {}");
+1
View File
@@ -234,6 +234,7 @@ namespace FullscreenUI
// achievements login dialog state
inline bool s_achievements_login_open = false;
inline bool s_achievements_login_logging_in = false;
inline bool s_achievements_login_show_dismiss = false;
inline char s_achievements_login_username[256] = {};
inline char s_achievements_login_password[256] = {};
inline Achievements::LoginRequestReason s_achievements_login_reason = Achievements::LoginRequestReason::UserInitiated;