diff --git a/Common/KeyMap.cpp b/Common/KeyMap.cpp index d514a5a4d7..33063c5caf 100644 --- a/Common/KeyMap.cpp +++ b/Common/KeyMap.cpp @@ -44,6 +44,7 @@ struct DefMappingStruct { }; KeyMapping g_controllerMap; +int g_controllerMapGeneration = 0; std::set g_seenPads; bool g_swapped_keys = false; @@ -837,6 +838,7 @@ void SetKeyMapping(int btn, KeyDef key, bool replace) { } g_controllerMap[btn].push_back(key); } + g_controllerMapGeneration++; UpdateNativeMenuKeys(); } @@ -960,6 +962,7 @@ void AutoConfForPad(const std::string &name) { // Add a couple of convenient keyboard mappings by default, too. g_controllerMap[VIRTKEY_PAUSE].push_back(KeyDef(DEVICE_ID_KEYBOARD, NKCODE_ESCAPE)); g_controllerMap[VIRTKEY_UNTHROTTLE].push_back(KeyDef(DEVICE_ID_KEYBOARD, NKCODE_TAB)); + g_controllerMapGeneration++; #endif } diff --git a/Common/KeyMap.h b/Common/KeyMap.h index 5aafa6d707..28e60c044b 100644 --- a/Common/KeyMap.h +++ b/Common/KeyMap.h @@ -83,6 +83,7 @@ class IniFile; namespace KeyMap { extern KeyMapping g_controllerMap; + extern int g_controllerMapGeneration; // Key & Button names struct KeyMap_IntStrPair { diff --git a/Core/Config.cpp b/Core/Config.cpp index 8b6edc4099..041abd7aa2 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -511,10 +511,6 @@ static int DefaultZoomType() { return (int)SmallDisplayZoom::AUTO; } -static bool DefaultTimerHack() { - return false; -} - static int DefaultAndroidHwScale() { #ifdef __ANDROID__ if (System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 19 || System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_TV) { diff --git a/Core/Dialog/PSPMsgDialog.cpp b/Core/Dialog/PSPMsgDialog.cpp index b50dee2a9f..302113b3b5 100755 --- a/Core/Dialog/PSPMsgDialog.cpp +++ b/Core/Dialog/PSPMsgDialog.cpp @@ -207,8 +207,8 @@ void PSPMsgDialog::DisplayMessage(std::string text, bool hasYesNo, bool hasOK) ey = y2 + 25.0f; } - PPGeDrawTextWrapped(text.c_str(), 241.0f, y+2, WRAP_WIDTH, PPGE_ALIGN_CENTER, FONT_SCALE, CalcFadedColor(0x80000000)); - PPGeDrawTextWrapped(text.c_str(), 240.0f, y, WRAP_WIDTH, PPGE_ALIGN_CENTER, FONT_SCALE, CalcFadedColor(0xFFFFFFFF)); + PPGeDrawTextWrapped(text.c_str(), 241.0f, y+2, WRAP_WIDTH, 0, PPGE_ALIGN_CENTER, FONT_SCALE, CalcFadedColor(0x80000000)); + PPGeDrawTextWrapped(text.c_str(), 240.0f, y, WRAP_WIDTH, 0, PPGE_ALIGN_CENTER, FONT_SCALE, CalcFadedColor(0xFFFFFFFF)); sy = 125.0f - h2; PPGeDrawRect(40.0f, sy, 440.0f, sy + 1.0f, CalcFadedColor(0xFFFFFFFF)); PPGeDrawRect(40.0f, ey, 440.0f, ey + 1.0f, CalcFadedColor(0xFFFFFFFF)); diff --git a/Core/Dialog/PSPNetconfDialog.cpp b/Core/Dialog/PSPNetconfDialog.cpp index d9f6ff1c54..25c9d42baf 100644 --- a/Core/Dialog/PSPNetconfDialog.cpp +++ b/Core/Dialog/PSPNetconfDialog.cpp @@ -81,7 +81,7 @@ int PSPNetconfDialog::Update(int animSpeed) { StartDraw(); DrawBanner(); PPGeDrawRect(0, 0, 480, 272, CalcFadedColor(0x63636363)); - PPGeDrawTextWrapped(err->T("PPSSPPDoesNotSupportInternet", "PPSSPP currently does not support connecting to the Internet for DLC, PSN, or game updates."), 241, 132, WRAP_WIDTH, PPGE_ALIGN_CENTER, 0.5f, CalcFadedColor(0xFFFFFFFF)); + PPGeDrawTextWrapped(err->T("PPSSPPDoesNotSupportInternet", "PPSSPP currently does not support connecting to the Internet for DLC, PSN, or game updates."), 241, 132, WRAP_WIDTH, 0, PPGE_ALIGN_CENTER, 0.5f, CalcFadedColor(0xFFFFFFFF)); PPGeDrawImage(confirmBtnImage, 195, 250, 20, 20, 0, CalcFadedColor(0xFFFFFFFF)); PPGeDrawText(di->T("OK"), 225, 252, PPGE_ALIGN_LEFT, 0.5f, CalcFadedColor(0xFFFFFFFF)); diff --git a/Core/Dialog/PSPSaveDialog.cpp b/Core/Dialog/PSPSaveDialog.cpp index 09b32dcc01..c06a436152 100755 --- a/Core/Dialog/PSPSaveDialog.cpp +++ b/Core/Dialog/PSPSaveDialog.cpp @@ -458,8 +458,8 @@ void PSPSaveDialog::DisplaySaveDataInfo1() PPGeDrawText(timeTxt.c_str(), 180, 137, PPGE_ALIGN_LEFT, 0.5f, CalcFadedColor(0xFFFFFFFF)); PPGeDrawText(saveTitleTxt.c_str(), 176, 162, PPGE_ALIGN_LEFT, 0.55f, CalcFadedColor(0x80000000)); PPGeDrawText(saveTitleTxt.c_str(), 175, 159, PPGE_ALIGN_LEFT, 0.55f, CalcFadedColor(0xFFFFFFFF)); - PPGeDrawText(saveDetailTxt.c_str(), 176, 183, PPGE_ALIGN_LEFT, 0.5f, CalcFadedColor(0x80000000)); - PPGeDrawText(saveDetailTxt.c_str(), 175, 181, PPGE_ALIGN_LEFT, 0.5f, CalcFadedColor(0xFFFFFFFF)); + PPGeDrawTextWrapped(saveDetailTxt.c_str(), 176, 183, 480 - 175, 250 - 183, PPGE_ALIGN_LEFT, 0.5f, CalcFadedColor(0x80000000)); + PPGeDrawTextWrapped(saveDetailTxt.c_str(), 175, 181, 480 - 175, 250 - 181, PPGE_ALIGN_LEFT, 0.5f, CalcFadedColor(0xFFFFFFFF)); } } @@ -573,8 +573,8 @@ void PSPSaveDialog::DisplayMessage(std::string text, bool hasYesNo) yesnoChoice = 0; } } - PPGeDrawTextWrapped(text.c_str(), 335.0f, y+2, WRAP_WIDTH, PPGE_ALIGN_CENTER, FONT_SCALE, CalcFadedColor(0x80000000)); - PPGeDrawTextWrapped(text.c_str(), 334.0f, y, WRAP_WIDTH, PPGE_ALIGN_CENTER, FONT_SCALE, CalcFadedColor(0xFFFFFFFF)); + PPGeDrawTextWrapped(text.c_str(), 335.0f, y+2, WRAP_WIDTH, 0, PPGE_ALIGN_CENTER, FONT_SCALE, CalcFadedColor(0x80000000)); + PPGeDrawTextWrapped(text.c_str(), 334.0f, y, WRAP_WIDTH, 0, PPGE_ALIGN_CENTER, FONT_SCALE, CalcFadedColor(0xFFFFFFFF)); float sy = 122.0f - h2, ey = 150.0f + h2; PPGeDrawRect(202.0f, sy, 466.0f, sy + 1.0f, CalcFadedColor(0xFFFFFFFF)); PPGeDrawRect(202.0f, ey, 466.0f, ey + 1.0f, CalcFadedColor(0xFFFFFFFF)); diff --git a/Core/HLE/sceDisplay.cpp b/Core/HLE/sceDisplay.cpp index 34f130575e..6a36edb0d5 100644 --- a/Core/HLE/sceDisplay.cpp +++ b/Core/HLE/sceDisplay.cpp @@ -180,6 +180,11 @@ void __DisplayFlip(int cyclesLate); static void ScheduleLagSync(int over = 0) { lagSyncScheduled = g_Config.bForceLagSync; if (lagSyncScheduled) { + // Reset over if it became too high, such as after pausing or initial loading. + // There's no real sense in it being more than 1/60th of a second. + if (over > 1000000 / 60) { + over = 0; + } CoreTiming::ScheduleEvent(usToCycles(1000 + over), lagSyncEvent, 0); lastLagSync = real_time_now(); } diff --git a/Core/Util/PPGeDraw.cpp b/Core/Util/PPGeDraw.cpp index cb091c837a..74dc3baa8d 100644 --- a/Core/Util/PPGeDraw.cpp +++ b/Core/Util/PPGeDraw.cpp @@ -17,6 +17,7 @@ #include +#include "base/stringutil.h" #include "image/zim_load.h" #include "image/png_load.h" #include "util/text/utf8.h" @@ -377,7 +378,7 @@ static const AtlasChar *PPGeGetChar(const AtlasFont &atlasfont, unsigned int cva // Break a single text string into mutiple lines. static AtlasTextMetrics BreakLines(const char *text, const AtlasFont &atlasfont, float x, float y, - int align, float scale, int wrapType, float wrapWidth, bool dryRun) + int align, float scale, float lineHeightScale, int wrapType, float wrapWidth, bool dryRun) { y += atlasfont.ascend * scale; float sx = x, sy = y; @@ -399,7 +400,7 @@ static AtlasTextMetrics BreakLines(const char *text, const AtlasFont &atlasfont, int numLines = 1; float maxw = 0; - float lineHeight = atlasfont.height * scale; + float lineHeight = atlasfont.height * lineHeightScale; for (UTF8 utf(text); !utf.end(); ) { float lineWidth = 0; @@ -617,16 +618,16 @@ void PPGeMeasureText(float *w, float *h, int *n, const char *text, float scale, int WrapType, int wrapWidth) { const AtlasFont &atlasfont = *ppge_atlas.fonts[0]; - AtlasTextMetrics metrics = BreakLines(text, atlasfont, 0, 0, 0, scale, WrapType, wrapWidth, true); + AtlasTextMetrics metrics = BreakLines(text, atlasfont, 0, 0, 0, scale, scale, WrapType, wrapWidth, true); if (w) *w = metrics.maxWidth; if (h) *h = metrics.lineHeight; if (n) *n = metrics.numLines; } -void PPGePrepareText(const char *text, float x, float y, int align, float scale, int WrapType, int wrapWidth) +void PPGePrepareText(const char *text, float x, float y, int align, float scale, float lineHeightScale, int WrapType, int wrapWidth) { const AtlasFont &atlasfont = *ppge_atlas.fonts[0]; - char_lines_metrics = BreakLines(text, atlasfont, x, y, align, scale, WrapType, wrapWidth, false); + char_lines_metrics = BreakLines(text, atlasfont, x, y, align, scale, lineHeightScale, WrapType, wrapWidth, false); } void PPGeMeasureCurrentText(float *x, float *y, float *w, float *h, int *n) @@ -638,6 +639,13 @@ void PPGeMeasureCurrentText(float *x, float *y, float *w, float *h, int *n) if (n) *n = char_lines_metrics.numLines; } +static void PPGeResetCurrentText() { + char_one_line.clear(); + char_lines.clear(); + AtlasTextMetrics zeroBox = { 0 }; + char_lines_metrics = zeroBox; +} + // Draws some text using the one font we have. // Mostly rewritten. void PPGeDrawCurrentText(u32 color) @@ -661,21 +669,64 @@ void PPGeDrawCurrentText(u32 color) } EndVertexDataAndDraw(GE_PRIM_RECTANGLES); } - char_one_line.clear(); - char_lines.clear(); - AtlasTextMetrics zeroBox = { 0 }; - char_lines_metrics = zeroBox; + PPGeResetCurrentText(); } void PPGeDrawText(const char *text, float x, float y, int align, float scale, u32 color) { - PPGePrepareText(text, x, y, align, scale, PPGE_LINE_USE_ELLIPSIS); + PPGePrepareText(text, x, y, align, scale, scale, PPGE_LINE_USE_ELLIPSIS); PPGeDrawCurrentText(color); } -void PPGeDrawTextWrapped(const char *text, float x, float y, float wrapWidth, int align, float scale, u32 color) -{ - PPGePrepareText(text, x, y, align, scale, PPGE_LINE_USE_ELLIPSIS | PPGE_LINE_WRAP_WORD, wrapWidth); +static std::string StripTrailingWhite(const std::string &s) { + size_t lastChar = s.find_last_not_of(" \t\r\n"); + if (lastChar != s.npos) { + return s.substr(0, lastChar + 1); + } + return s; +} + +static std::string CropLinesToCount(const std::string &s, int numLines) { + std::vector lines; + SplitString(s, '\n', lines); + if ((int)lines.size() <= numLines) { + return s; + } + + size_t len = 0; + for (int i = 0; i < numLines; ++i) { + len += lines[i].length() + 1; + } + + return s.substr(0, len); +} + +void PPGeDrawTextWrapped(const char *text, float x, float y, float wrapWidth, float wrapHeight, int align, float scale, u32 color) { + std::string s = text; + if (wrapHeight != 0.0f) { + s = StripTrailingWhite(s); + } + + PPGePrepareText(s.c_str(), x, y, align, scale, scale, PPGE_LINE_USE_ELLIPSIS | PPGE_LINE_WRAP_WORD, wrapWidth); + + int zoom = (PSP_CoreParameter().pixelHeight + 479) / 480; + float maxScaleDown = zoom == 1 ? 1.3f : 2.0f; + float actualHeight = char_lines_metrics.lineHeight * char_lines_metrics.numLines; + if (wrapHeight != 0.0f && actualHeight > wrapHeight) { + if (actualHeight > wrapHeight * maxScaleDown) { + float maxLines = floor(wrapHeight * maxScaleDown / char_lines_metrics.lineHeight); + actualHeight = (maxLines + 1) * char_lines_metrics.lineHeight; + // Add an ellipsis if it's just too long to be readable. + // On a PSP, it does this without scaling it down. + s = StripTrailingWhite(CropLinesToCount(s, (int)maxLines)) + "\n..."; + } + + // Measure the text again after scaling down. + PPGeResetCurrentText(); + float reduced = scale * wrapHeight / actualHeight; + // Try to keep the font as large as possible, so reduce the line height some. + PPGePrepareText(s.c_str(), x, y, align, reduced * 1.15, reduced, PPGE_LINE_USE_ELLIPSIS | PPGE_LINE_WRAP_WORD, wrapWidth); + } PPGeDrawCurrentText(color); } diff --git a/Core/Util/PPGeDraw.h b/Core/Util/PPGeDraw.h index 8283d99f76..df18dd0dbc 100644 --- a/Core/Util/PPGeDraw.h +++ b/Core/Util/PPGeDraw.h @@ -80,7 +80,7 @@ void PPGeMeasureText(float *w, float *h, int *n, const char *text, float scale, int WrapType = PPGE_LINE_NONE, int wrapWidth = 0); // Overwrite the current text lines buffer so it can be drawn later. -void PPGePrepareText(const char *text, float x, float y, int align, float scale, +void PPGePrepareText(const char *text, float x, float y, int align, float scale, float lineHeightScale, int WrapType = PPGE_LINE_NONE, int wrapWidth = 0); // Get the metrics of the bounding box of the currently stated text. @@ -95,7 +95,7 @@ void PPGeDrawCurrentText(u32 color = 0xFFFFFFFF); // Draws some text using the one font we have. // Clears the text buffer when done. void PPGeDrawText(const char *text, float x, float y, int align, float scale = 1.0f, u32 color = 0xFFFFFFFF); -void PPGeDrawTextWrapped(const char *text, float x, float y, float wrapWidth, int align, float scale = 1.0f, u32 color = 0xFFFFFFFF); +void PPGeDrawTextWrapped(const char *text, float x, float y, float wrapWidth, float wrapHeight, int align, float scale = 1.0f, u32 color = 0xFFFFFFFF); // Draws a "4-patch" for button-like things that can be resized. void PPGeDraw4Patch(int atlasImage, float x, float y, float w, float h, u32 color = 0xFFFFFFFF); diff --git a/GPU/Common/ShaderId.cpp b/GPU/Common/ShaderId.cpp index f3a8e09228..21b4519af9 100644 --- a/GPU/Common/ShaderId.cpp +++ b/GPU/Common/ShaderId.cpp @@ -11,7 +11,7 @@ #include "GPU/Common/ShaderId.h" #include "GPU/Common/VertexDecoderCommon.h" -std::string VertexShaderDesc(const ShaderID &id) { +std::string VertexShaderDesc(const VShaderID &id) { std::stringstream desc; desc << StringFromFormat("%08x:%08x ", id.d[1], id.d[0]); if (id.Bit(VS_BIT_IS_THROUGH)) desc << "THR "; @@ -60,7 +60,7 @@ std::string VertexShaderDesc(const ShaderID &id) { return desc.str(); } -void ComputeVertexShaderID(ShaderID *id_out, u32 vertType, bool useHWTransform) { +void ComputeVertexShaderID(VShaderID *id_out, u32 vertType, bool useHWTransform) { bool isModeThrough = (vertType & GE_VTYPE_THROUGH) != 0; bool doTexture = gstate.isTextureMapEnabled() && !gstate.isModeClear(); bool doTextureTransform = gstate.getUVGenMode() == GE_TEXMAP_TEXTURE_MATRIX; @@ -81,7 +81,7 @@ void ComputeVertexShaderID(ShaderID *id_out, u32 vertType, bool useHWTransform) bool enableFog = gstate.isFogEnabled() && !isModeThrough && !gstate.isModeClear(); bool lmode = gstate.isUsingSecondaryColor() && gstate.isLightingEnabled() && !isModeThrough; - ShaderID id; + VShaderID id; id.SetBit(VS_BIT_LMODE, lmode); id.SetBit(VS_BIT_IS_THROUGH, isModeThrough); id.SetBit(VS_BIT_ENABLE_FOG, enableFog); @@ -160,7 +160,7 @@ static bool MatrixNeedsProjection(const float m[12]) { return m[2] != 0.0f || m[5] != 0.0f || m[8] != 0.0f || m[11] != 1.0f; } -std::string FragmentShaderDesc(const ShaderID &id) { +std::string FragmentShaderDesc(const FShaderID &id) { std::stringstream desc; desc << StringFromFormat("%08x:%08x ", id.d[1], id.d[0]); if (id.Bit(FS_BIT_CLEARMODE)) desc << "Clear "; @@ -226,8 +226,8 @@ std::string FragmentShaderDesc(const ShaderID &id) { // Here we must take all the bits of the gstate that determine what the fragment shader will // look like, and concatenate them together into an ID. -void ComputeFragmentShaderID(ShaderID *id_out, const Draw::Bugs &bugs) { - ShaderID id; +void ComputeFragmentShaderID(FShaderID *id_out, const Draw::Bugs &bugs) { + FShaderID id; if (gstate.isModeClear()) { // We only need one clear shader, so let's ignore the rest of the bits. id.SetBit(FS_BIT_CLEARMODE); diff --git a/GPU/Common/ShaderId.h b/GPU/Common/ShaderId.h index ec57b0273f..e614d8f112 100644 --- a/GPU/Common/ShaderId.h +++ b/GPU/Common/ShaderId.h @@ -8,7 +8,7 @@ // TODO: There will be additional bits, indicating that groups of these will be // sent to the shader and processed there. This will cut down the number of shaders ("ubershader approach") // This is probably only really worth doing for lighting and bones. -enum { +enum VShaderBit : uint8_t { VS_BIT_LMODE = 0, VS_BIT_IS_THROUGH = 1, VS_BIT_ENABLE_FOG = 2, @@ -55,9 +55,12 @@ enum { // No more free }; +static inline VShaderBit operator +(VShaderBit bit, int i) { + return VShaderBit((int)bit + i); +} // Local -enum { +enum FShaderBit : uint8_t { FS_BIT_CLEARMODE = 0, FS_BIT_DO_TEXTURE = 1, FS_BIT_TEXFUNC = 2, // 3 bits @@ -91,6 +94,10 @@ enum { // 50+ are free. }; +static inline FShaderBit operator +(FShaderBit bit, int i) { + return FShaderBit((int)bit + i); +} + struct ShaderID { ShaderID() { clear(); @@ -126,6 +133,20 @@ struct ShaderID { bool operator != (const ShaderID &other) const { return !(*this == other); } + + uint32_t Word(int word) const { + return d[word]; + } + + void ToString(std::string *dest) const { + dest->resize(sizeof(d)); + memcpy(&(*dest)[0], d, sizeof(d)); + } + void FromString(std::string src) { + memcpy(d, &(src)[0], sizeof(d)); + } + +protected: bool Bit(int bit) const { return (d[bit >> 5] >> (bit & 31)) & 1; } @@ -134,9 +155,6 @@ struct ShaderID { const int mask = (1 << count) - 1; return (d[bit >> 5] >> (bit & 31)) & mask; } - uint32_t Word(int word) const { - return d[word]; - } void SetBit(int bit, bool value = true) { if (value) { d[bit >> 5] |= 1 << (bit & 31); @@ -148,14 +166,6 @@ struct ShaderID { d[bit >> 5] |= (value & mask) << (bit & 31); } } - - void ToString(std::string *dest) const { - dest->resize(sizeof(d)); - memcpy(&(*dest)[0], d, sizeof(d)); - } - void FromString(std::string src) { - memcpy(d, &(src)[0], sizeof(d)); - } }; struct VShaderID : ShaderID { @@ -165,6 +175,22 @@ struct VShaderID : ShaderID { explicit VShaderID(ShaderID &src) { memcpy(d, src.d, sizeof(d)); } + + bool Bit(VShaderBit bit) const { + return ShaderID::Bit((int)bit); + } + + int Bits(VShaderBit bit, int count) const { + return ShaderID::Bits((int)bit, count); + } + + void SetBit(VShaderBit bit, bool value = true) { + ShaderID::SetBit((int)bit, value); + } + + void SetBits(VShaderBit bit, int count, int value) { + ShaderID::SetBits((int)bit, count, value); + } }; struct FShaderID : ShaderID { @@ -174,6 +200,22 @@ struct FShaderID : ShaderID { explicit FShaderID(ShaderID &src) { memcpy(d, src.d, sizeof(d)); } + + bool Bit(FShaderBit bit) const { + return ShaderID::Bit((int)bit); + } + + int Bits(FShaderBit bit, int count) const { + return ShaderID::Bits((int)bit, count); + } + + void SetBit(FShaderBit bit, bool value = true) { + ShaderID::SetBit((int)bit, value); + } + + void SetBits(FShaderBit bit, int count, int value) { + ShaderID::SetBits((int)bit, count, value); + } }; namespace Draw { @@ -181,10 +223,10 @@ class Bugs; } -void ComputeVertexShaderID(ShaderID *id, uint32_t vertexType, bool useHWTransform); +void ComputeVertexShaderID(VShaderID *id, uint32_t vertexType, bool useHWTransform); // Generates a compact string that describes the shader. Useful in a list to get an overview // of the current flora of shaders. -std::string VertexShaderDesc(const ShaderID &id); +std::string VertexShaderDesc(const VShaderID &id); -void ComputeFragmentShaderID(ShaderID *id, const Draw::Bugs &bugs); -std::string FragmentShaderDesc(const ShaderID &id); +void ComputeFragmentShaderID(FShaderID *id, const Draw::Bugs &bugs); +std::string FragmentShaderDesc(const FShaderID &id); diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index 585a6cf459..ffafcaa362 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -762,7 +762,7 @@ std::string Shader::GetShaderString(DebugShaderStringType type, ShaderID id) con case SHADER_STRING_SOURCE_CODE: return source_; case SHADER_STRING_SHORT_DESC: - return isFragment_ ? FragmentShaderDesc(id) : VertexShaderDesc(id); + return isFragment_ ? FragmentShaderDesc(FShaderID(id)) : VertexShaderDesc(VShaderID(id)); default: return "N/A"; } diff --git a/Qt/QtMain.cpp b/Qt/QtMain.cpp index 178f3f82c4..32129d42dd 100644 --- a/Qt/QtMain.cpp +++ b/Qt/QtMain.cpp @@ -6,13 +6,15 @@ // Currently supports: Android, Linux, Windows, Mac OSX #include -#include -#include #include #include +#include +#include +#include #include #include #include +#include #include "ext/glslang/glslang/Public/ShaderLang.h" @@ -29,6 +31,7 @@ #endif #include "QtMain.h" #include "gfx_es2/gpu_features.h" +#include "i18n/i18n.h" #include "math/math_util.h" #include "thread/threadutil.h" #include "util/text/utf8.h" @@ -37,8 +40,10 @@ #include -MainUI *emugl = NULL; +MainUI *emugl = nullptr; static int refreshRate = 60000; +static int browseFileEvent = -1; +static int browseFolderEvent = -1; #ifdef SDL extern void mixaudio(void *userdata, Uint8 *stream, int len) { @@ -94,6 +99,8 @@ bool System_GetPropertyBool(SystemProperty prop) { switch (prop) { case SYSPROP_HAS_BACK_BUTTON: return true; + case SYSPROP_HAS_FILE_BROWSER: + return true; case SYSPROP_APP_GOLD: #ifdef GOLD return true; @@ -108,6 +115,10 @@ bool System_GetPropertyBool(SystemProperty prop) { void System_SendMessage(const char *command, const char *parameter) { if (!strcmp(command, "finish")) { qApp->exit(0); + } else if (!strcmp(command, "browse_file")) { + QCoreApplication::postEvent(emugl, new QEvent((QEvent::Type)browseFileEvent)); + } else if (!strcmp(command, "browse_folder")) { + QCoreApplication::postEvent(emugl, new QEvent((QEvent::Type)browseFolderEvent)); } } @@ -190,6 +201,10 @@ static int mainInternal(QApplication &a) { QScopedPointer audio(new MainAudio()); audio->run(); #endif + + browseFileEvent = QEvent::registerEventType(); + browseFolderEvent = QEvent::registerEventType(); + int retval = a.exec(); delete emugl; return retval; @@ -227,9 +242,8 @@ void MainUI::EmuThreadJoin() { emuThread = std::thread(); } -MainUI::MainUI(QWidget *parent): - QGLWidget(parent) -{ +MainUI::MainUI(QWidget *parent) + : QGLWidget(parent) { emuThreadState = (int)EmuThreadState::DISABLED; setAttribute(Qt::WA_AcceptTouchEvents); #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) @@ -244,8 +258,7 @@ MainUI::MainUI(QWidget *parent): startTimer(16); } -MainUI::~MainUI() -{ +MainUI::~MainUI() { ILOG("MainUI::Destructor"); if (emuThreadState != (int)EmuThreadState::DISABLED) { ILOG("EmuThreadStop"); @@ -264,134 +277,148 @@ MainUI::~MainUI() graphicsContext = nullptr; } -QString MainUI::InputBoxGetQString(QString title, QString defaultValue) -{ - bool ok; - QString text = QInputDialog::getText(this, title, title, QLineEdit::Normal, defaultValue, &ok); - if (!ok) - text = QString(); - return text; +QString MainUI::InputBoxGetQString(QString title, QString defaultValue) { + bool ok; + QString text = QInputDialog::getText(this, title, title, QLineEdit::Normal, defaultValue, &ok); + if (!ok) + text = QString(); + return text; } -void MainUI::resizeGL(int w, int h) -{ - if (UpdateScreenScale(w, h)) { - NativeMessageReceived("gpu_resized", ""); - } - xscale = w / this->width(); - yscale = h / this->height(); +void MainUI::resizeGL(int w, int h) { + if (UpdateScreenScale(w, h)) { + NativeMessageReceived("gpu_resized", ""); + } + xscale = w / this->width(); + yscale = h / this->height(); - PSP_CoreParameter().pixelWidth = pixel_xres; - PSP_CoreParameter().pixelHeight = pixel_yres; + PSP_CoreParameter().pixelWidth = pixel_xres; + PSP_CoreParameter().pixelHeight = pixel_yres; } -void MainUI::timerEvent(QTimerEvent *) -{ - updateGL(); - emit newFrame(); +void MainUI::timerEvent(QTimerEvent *) { + updateGL(); + emit newFrame(); } -void MainUI::changeEvent(QEvent *e) -{ - QGLWidget::changeEvent(e); - if(e->type() == QEvent::WindowStateChange) - Core_NotifyWindowHidden(isMinimized()); +void MainUI::changeEvent(QEvent *e) { + QGLWidget::changeEvent(e); + if (e->type() == QEvent::WindowStateChange) + Core_NotifyWindowHidden(isMinimized()); } -bool MainUI::event(QEvent *e) -{ - TouchInput input; - QList touchPoints; - switch(e->type()) - { - case QEvent::TouchBegin: - case QEvent::TouchUpdate: - case QEvent::TouchEnd: - touchPoints = static_cast(e)->touchPoints(); - foreach (const QTouchEvent::TouchPoint &touchPoint, touchPoints) { - switch (touchPoint.state()) { - case Qt::TouchPointStationary: - break; - case Qt::TouchPointPressed: - case Qt::TouchPointReleased: - input.x = touchPoint.pos().x() * g_dpi_scale_x * xscale; - input.y = touchPoint.pos().y() * g_dpi_scale_y * yscale; - input.flags = (touchPoint.state() == Qt::TouchPointPressed) ? TOUCH_DOWN : TOUCH_UP; - input.id = touchPoint.id(); - NativeTouch(input); - break; - case Qt::TouchPointMoved: - input.x = touchPoint.pos().x() * g_dpi_scale_x * xscale; - input.y = touchPoint.pos().y() * g_dpi_scale_y * yscale; - input.flags = TOUCH_MOVE; - input.id = touchPoint.id(); - NativeTouch(input); - break; - default: - break; - } - } - break; - case QEvent::MouseButtonDblClick: - if (!g_Config.bShowTouchControls || GetUIState() != UISTATE_INGAME) - emit doubleClick(); - break; - case QEvent::MouseButtonPress: - case QEvent::MouseButtonRelease: - input.x = ((QMouseEvent*)e)->pos().x() * g_dpi_scale_x * xscale; - input.y = ((QMouseEvent*)e)->pos().y() * g_dpi_scale_y * yscale; - input.flags = (e->type() == QEvent::MouseButtonPress) ? TOUCH_DOWN : TOUCH_UP; - input.id = 0; - NativeTouch(input); - break; - case QEvent::MouseMove: - input.x = ((QMouseEvent*)e)->pos().x() * g_dpi_scale_x * xscale; - input.y = ((QMouseEvent*)e)->pos().y() * g_dpi_scale_y * yscale; - input.flags = TOUCH_MOVE; - input.id = 0; - NativeTouch(input); - break; - case QEvent::Wheel: - NativeKey(KeyInput(DEVICE_ID_MOUSE, ((QWheelEvent*)e)->delta()<0 ? NKCODE_EXT_MOUSEWHEEL_DOWN : NKCODE_EXT_MOUSEWHEEL_UP, KEY_DOWN)); - break; - case QEvent::KeyPress: - { - auto qtKeycode = ((QKeyEvent*)e)->key(); - auto iter = KeyMapRawQttoNative.find(qtKeycode); - int nativeKeycode = 0; - if (iter != KeyMapRawQttoNative.end()) { - nativeKeycode = iter->second; - NativeKey(KeyInput(DEVICE_ID_KEYBOARD, nativeKeycode, KEY_DOWN)); - } +bool MainUI::event(QEvent *e) { + TouchInput input; + QList touchPoints; - // Also get the unicode value. - QString text = ((QKeyEvent*)e)->text(); - std::string str = text.toStdString(); - // Now, we don't want CHAR events for non-printable characters. Not quite sure how we'll best - // do that, but here's one attempt.... - switch (nativeKeycode) { - case NKCODE_DEL: - case NKCODE_FORWARD_DEL: - case NKCODE_TAB: - break; - default: - if (str.size()) { - int pos = 0; - int code = u8_nextchar(str.c_str(), &pos); - NativeKey(KeyInput(DEVICE_ID_KEYBOARD, code, KEY_CHAR)); - } - break; - } + switch (e->type()) { + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + touchPoints = static_cast(e)->touchPoints(); + foreach (const QTouchEvent::TouchPoint &touchPoint, touchPoints) { + switch (touchPoint.state()) { + case Qt::TouchPointStationary: + break; + case Qt::TouchPointPressed: + case Qt::TouchPointReleased: + input.x = touchPoint.pos().x() * g_dpi_scale_x * xscale; + input.y = touchPoint.pos().y() * g_dpi_scale_y * yscale; + input.flags = (touchPoint.state() == Qt::TouchPointPressed) ? TOUCH_DOWN : TOUCH_UP; + input.id = touchPoint.id(); + NativeTouch(input); + break; + case Qt::TouchPointMoved: + input.x = touchPoint.pos().x() * g_dpi_scale_x * xscale; + input.y = touchPoint.pos().y() * g_dpi_scale_y * yscale; + input.flags = TOUCH_MOVE; + input.id = touchPoint.id(); + NativeTouch(input); + break; + default: + break; + } + } + break; + case QEvent::MouseButtonDblClick: + if (!g_Config.bShowTouchControls || GetUIState() != UISTATE_INGAME) + emit doubleClick(); + break; + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + input.x = ((QMouseEvent*)e)->pos().x() * g_dpi_scale_x * xscale; + input.y = ((QMouseEvent*)e)->pos().y() * g_dpi_scale_y * yscale; + input.flags = (e->type() == QEvent::MouseButtonPress) ? TOUCH_DOWN : TOUCH_UP; + input.id = 0; + NativeTouch(input); + break; + case QEvent::MouseMove: + input.x = ((QMouseEvent*)e)->pos().x() * g_dpi_scale_x * xscale; + input.y = ((QMouseEvent*)e)->pos().y() * g_dpi_scale_y * yscale; + input.flags = TOUCH_MOVE; + input.id = 0; + NativeTouch(input); + break; + case QEvent::Wheel: + NativeKey(KeyInput(DEVICE_ID_MOUSE, ((QWheelEvent*)e)->delta()<0 ? NKCODE_EXT_MOUSEWHEEL_DOWN : NKCODE_EXT_MOUSEWHEEL_UP, KEY_DOWN)); + break; + case QEvent::KeyPress: + { + auto qtKeycode = ((QKeyEvent*)e)->key(); + auto iter = KeyMapRawQttoNative.find(qtKeycode); + int nativeKeycode = 0; + if (iter != KeyMapRawQttoNative.end()) { + nativeKeycode = iter->second; + NativeKey(KeyInput(DEVICE_ID_KEYBOARD, nativeKeycode, KEY_DOWN)); + } + + // Also get the unicode value. + QString text = ((QKeyEvent*)e)->text(); + std::string str = text.toStdString(); + // Now, we don't want CHAR events for non-printable characters. Not quite sure how we'll best + // do that, but here's one attempt.... + switch (nativeKeycode) { + case NKCODE_DEL: + case NKCODE_FORWARD_DEL: + case NKCODE_TAB: + break; + default: + if (str.size()) { + int pos = 0; + int code = u8_nextchar(str.c_str(), &pos); + NativeKey(KeyInput(DEVICE_ID_KEYBOARD, code, KEY_CHAR)); } - break; - case QEvent::KeyRelease: - NativeKey(KeyInput(DEVICE_ID_KEYBOARD, KeyMapRawQttoNative.find(((QKeyEvent*)e)->key())->second, KEY_UP)); - break; - default: - return QWidget::event(e); - } - e->accept(); - return true; + break; + } + } + break; + case QEvent::KeyRelease: + NativeKey(KeyInput(DEVICE_ID_KEYBOARD, KeyMapRawQttoNative.find(((QKeyEvent*)e)->key())->second, KEY_UP)); + break; + + default: + if (e->type() == browseFileEvent) { + QString fileName = QFileDialog::getOpenFileName(nullptr, "Load ROM", g_Config.currentDirectory.c_str(), "PSP ROMs (*.iso *.cso *.pbp *.elf *.zip *.ppdmp)"); + if (QFile::exists(fileName)) { + QDir newPath; + g_Config.currentDirectory = newPath.filePath(fileName).toStdString(); + g_Config.Save(); + + NativeMessageReceived("boot", fileName.toStdString().c_str()); + } + break; + } else if (e->type() == browseFolderEvent) { + I18NCategory *mm = GetI18NCategory("MainMenu"); + QString fileName = QFileDialog::getExistingDirectory(nullptr, mm->T("Choose folder"), g_Config.currentDirectory.c_str()); + if (QDir(fileName).exists()) { + NativeMessageReceived("browse_folderSelect", fileName.toStdString().c_str()); + } + } else { + return QWidget::event(e); + } + } + e->accept(); + return true; } void MainUI::initializeGL() { @@ -425,9 +452,9 @@ void MainUI::initializeGL() { } void MainUI::paintGL() { - #ifdef SDL +#ifdef SDL SDL_PumpEvents(); - #endif +#endif updateAccelerometer(); if (emuThreadState == (int)EmuThreadState::DISABLED) { UpdateRunLoop(); @@ -437,28 +464,27 @@ void MainUI::paintGL() { } } -void MainUI::updateAccelerometer() -{ +void MainUI::updateAccelerometer() { #if defined(MOBILE_DEVICE) - // TODO: Toggle it depending on whether it is enabled - QAccelerometerReading *reading = acc->reading(); - if (reading) { - AxisInput axis; - axis.deviceId = DEVICE_ID_ACCELEROMETER; - axis.flags = 0; + // TODO: Toggle it depending on whether it is enabled + QAccelerometerReading *reading = acc->reading(); + if (reading) { + AxisInput axis; + axis.deviceId = DEVICE_ID_ACCELEROMETER; + axis.flags = 0; - axis.axisId = JOYSTICK_AXIS_ACCELEROMETER_X; - axis.value = reading->x(); - NativeAxis(axis); + axis.axisId = JOYSTICK_AXIS_ACCELEROMETER_X; + axis.value = reading->x(); + NativeAxis(axis); - axis.axisId = JOYSTICK_AXIS_ACCELEROMETER_Y; - axis.value = reading->y(); - NativeAxis(axis); + axis.axisId = JOYSTICK_AXIS_ACCELEROMETER_Y; + axis.value = reading->y(); + NativeAxis(axis); - axis.axisId = JOYSTICK_AXIS_ACCELEROMETER_Z; - axis.value = reading->z(); - NativeAxis(axis); - } + axis.axisId = JOYSTICK_AXIS_ACCELEROMETER_Z; + axis.value = reading->z(); + NativeAxis(axis); + } #endif } @@ -470,49 +496,46 @@ void MainUI::updateAccelerometer() #define AUDIO_SAMPLESIZE 16 #define AUDIO_BUFFERS 5 -MainAudio::~MainAudio() -{ - if (feed != NULL) { - killTimer(timer); - feed->close(); - } - if (output) { - output->stop(); - delete output; - } - if (mixbuf) - free(mixbuf); +MainAudio::~MainAudio() { + if (feed != nullptr) { + killTimer(timer); + feed->close(); + } + if (output) { + output->stop(); + delete output; + } + if (mixbuf) + free(mixbuf); } -void MainAudio::run() -{ - QAudioFormat fmt; - fmt.setSampleRate(AUDIO_FREQ); - fmt.setCodec("audio/pcm"); - fmt.setChannelCount(AUDIO_CHANNELS); - fmt.setSampleSize(AUDIO_SAMPLESIZE); - fmt.setByteOrder(QAudioFormat::LittleEndian); - fmt.setSampleType(QAudioFormat::SignedInt); - mixlen = sizeof(short)*AUDIO_BUFFERS*AUDIO_CHANNELS*AUDIO_SAMPLES; - mixbuf = (char*)malloc(mixlen); - output = new QAudioOutput(fmt); - output->setBufferSize(mixlen); - feed = output->start(); - if (feed != NULL) { - // buffering has already done in the internal mixed buffer - // use a small interval to copy mixed audio stream from - // internal buffer to audio output buffer as soon as possible - // use 1 instead of 0 to prevent CPU exhausting - timer = startTimer(1); - } +void MainAudio::run() { + QAudioFormat fmt; + fmt.setSampleRate(AUDIO_FREQ); + fmt.setCodec("audio/pcm"); + fmt.setChannelCount(AUDIO_CHANNELS); + fmt.setSampleSize(AUDIO_SAMPLESIZE); + fmt.setByteOrder(QAudioFormat::LittleEndian); + fmt.setSampleType(QAudioFormat::SignedInt); + mixlen = sizeof(short)*AUDIO_BUFFERS*AUDIO_CHANNELS*AUDIO_SAMPLES; + mixbuf = (char*)malloc(mixlen); + output = new QAudioOutput(fmt); + output->setBufferSize(mixlen); + feed = output->start(); + if (feed != nullptr) { + // buffering has already done in the internal mixed buffer + // use a small interval to copy mixed audio stream from + // internal buffer to audio output buffer as soon as possible + // use 1 instead of 0 to prevent CPU exhausting + timer = startTimer(1); + } } -void MainAudio::timerEvent(QTimerEvent *) -{ - memset(mixbuf, 0, mixlen); - size_t frames = NativeMix((short *)mixbuf, AUDIO_BUFFERS*AUDIO_SAMPLES); - if (frames > 0) - feed->write(mixbuf, sizeof(short) * AUDIO_CHANNELS * frames); +void MainAudio::timerEvent(QTimerEvent *) { + memset(mixbuf, 0, mixlen); + size_t frames = NativeMix((short *)mixbuf, AUDIO_BUFFERS*AUDIO_SAMPLES); + if (frames > 0) + feed->write(mixbuf, sizeof(short) * AUDIO_CHANNELS * frames); } #endif diff --git a/SDL/SDLMain.cpp b/SDL/SDLMain.cpp index db786c3f17..3f07c9fb6a 100644 --- a/SDL/SDLMain.cpp +++ b/SDL/SDLMain.cpp @@ -435,16 +435,18 @@ int main(int argc, char *argv[]) { SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetSwapInterval(1); - // Is resolution is too low to run windowed + // Force fullscreen if the resolution is too low to run windowed. if (g_DesktopWidth < 480 * 2 && g_DesktopHeight < 272 * 2) { mode |= SDL_WINDOW_FULLSCREEN_DESKTOP; } // If we're on mobile, don't try for windowed either. #if defined(MOBILE_DEVICE) - mode |= SDL_WINDOW_FULLSCREEN; + mode |= SDL_WINDOW_FULLSCREEN; +#elif defined(USING_FBDEV) + mode |= SDL_WINDOW_FULLSCREEN_DESKTOP; #else - mode |= SDL_WINDOW_RESIZABLE; + mode |= SDL_WINDOW_RESIZABLE; #endif if (mode & SDL_WINDOW_FULLSCREEN_DESKTOP) { @@ -600,6 +602,7 @@ int main(int argc, char *argv[]) { } graphicsContext->ThreadStart(); + bool windowHidden = false; while (true) { double startTime = time_now_d(); SDL_Event event, touchEvent; @@ -636,6 +639,19 @@ int main(int argc, char *argv[]) { break; } + case SDL_WINDOWEVENT_MINIMIZED: + case SDL_WINDOWEVENT_HIDDEN: + windowHidden = true; + Core_NotifyWindowHidden(windowHidden); + break; + case SDL_WINDOWEVENT_EXPOSED: + case SDL_WINDOWEVENT_SHOWN: + case SDL_WINDOWEVENT_MAXIMIZED: + case SDL_WINDOWEVENT_RESTORED: + windowHidden = false; + Core_NotifyWindowHidden(windowHidden); + break; + default: break; } @@ -682,6 +698,8 @@ int main(int argc, char *argv[]) { NativeKey(key); break; } +// This behavior doesn't feel right on a macbook with a touchpad. +#if !PPSSPP_PLATFORM(MAC) case SDL_FINGERMOTION: { SDL_GetWindowSize(window, &w, &h); @@ -744,6 +762,7 @@ int main(int argc, char *argv[]) { SDL_PushEvent(&touchEvent); break; } +#endif case SDL_MOUSEBUTTONDOWN: switch (event.button.button) { case SDL_BUTTON_LEFT: @@ -847,7 +866,8 @@ int main(int argc, char *argv[]) { // glsl_refresh(); // auto-reloads modified GLSL shaders once per second. } - if (emuThreadState != (int)EmuThreadState::DISABLED) { + bool renderThreadPaused = windowHidden && g_Config.bPauseWhenMinimized && emuThreadState != (int)EmuThreadState::DISABLED; + if (emuThreadState != (int)EmuThreadState::DISABLED && !renderThreadPaused) { if (!graphicsContext->ThreadFrame()) break; } @@ -859,7 +879,7 @@ int main(int argc, char *argv[]) { // Simple throttling to not burn the GPU in the menu. time_update(); - if (GetUIState() != UISTATE_INGAME || !PSP_IsInited()) { + if (GetUIState() != UISTATE_INGAME || !PSP_IsInited() || renderThreadPaused) { double diffTime = time_now_d() - startTime; int sleepTime = (int)(1000.0 / 60.0) - (int)(diffTime * 1000.0); if (sleepTime > 0) diff --git a/UI/ControlMappingScreen.cpp b/UI/ControlMappingScreen.cpp index 1a9189352a..75dd0ed64b 100644 --- a/UI/ControlMappingScreen.cpp +++ b/UI/ControlMappingScreen.cpp @@ -29,6 +29,7 @@ #include "ui/view.h" #include "ui/viewgroup.h" +#include "Core/Host.h" #include "Core/HLE/sceCtrl.h" #include "Core/System.h" #include "Common/KeyMap.h" @@ -79,6 +80,7 @@ void ControlMapper::Update() { if (refresh_) { refresh_ = false; Refresh(); + host->UpdateUI(); } } @@ -169,6 +171,7 @@ void ControlMapper::MappedCallback(KeyDef kdf) { break; case REPLACEONE: KeyMap::g_controllerMap[pspKey_][actionIndex_] = kdf; + KeyMap::g_controllerMapGeneration++; break; default: ; @@ -211,6 +214,7 @@ UI::EventReturn ControlMapper::OnAddMouse(UI::EventParams ¶ms) { UI::EventReturn ControlMapper::OnDelete(UI::EventParams ¶ms) { int index = atoi(params.v->Tag().c_str()); KeyMap::g_controllerMap[pspKey_].erase(KeyMap::g_controllerMap[pspKey_].begin() + index); + KeyMap::g_controllerMapGeneration++; refresh_ = true; return UI::EVENT_DONE; } diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 9f19188ccd..b4c3885e1d 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -804,7 +804,11 @@ void GameSettingsScreen::CreateViews() { #if defined(USING_WIN_UI) systemSettings->Add(new CheckBox(&g_Config.bBypassOSKWithKeyboard, sy->T("Enable Windows native keyboard", "Enable Windows native keyboard"))); #endif -#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) +#if PPSSPP_PLATFORM(ANDROID) + auto memstickPath = systemSettings->Add(new ChoiceWithValueDisplay(&g_Config.memStickDirectory, sy->T("Change Memory Stick folder"), (const char *)nullptr)); + memstickPath->SetEnabled(!PSP_IsInited()); + memstickPath->OnClick.Handle(this, &GameSettingsScreen::OnChangeMemStickDir); +#elif defined(_WIN32) && !PPSSPP_PLATFORM(UWP) SavePathInMyDocumentChoice = systemSettings->Add(new CheckBox(&installed_, sy->T("Save path in My Documents", "Save path in My Documents"))); SavePathInMyDocumentChoice->OnClick.Handle(this, &GameSettingsScreen::OnSavePathMydoc); SavePathInOtherChoice = systemSettings->Add(new CheckBox(&otherinstalled_, sy->T("Save path in installed.txt", "Save path in installed.txt"))); @@ -982,7 +986,15 @@ UI::EventReturn GameSettingsScreen::OnJitAffectingSetting(UI::EventParams &e) { return UI::EVENT_DONE; } -#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) +#if PPSSPP_PLATFORM(ANDROID) + +UI::EventReturn GameSettingsScreen::OnChangeMemStickDir(UI::EventParams &e) { + I18NCategory *sy = GetI18NCategory("System"); + System_SendMessage("inputbox", (std::string(sy->T("Memory Stick Folder")) + ":" + g_Config.memStickDirectory).c_str()); + return UI::EVENT_DONE; +} + +#elif defined(_WIN32) && !PPSSPP_PLATFORM(UWP) UI::EventReturn GameSettingsScreen::OnSavePathMydoc(UI::EventParams &e) { const std::string PPSSPPpath = File::GetExeDirectory(); @@ -1145,6 +1157,70 @@ void GameSettingsScreen::onFinish(DialogResult result) { NativeMessageReceived("gpu_clearShaderCache", ""); } +void GameSettingsScreen::sendMessage(const char *message, const char *value) { + UIDialogScreenWithGameBackground::sendMessage(message, value); + + I18NCategory *sy = GetI18NCategory("System"); + I18NCategory *di = GetI18NCategory("Dialog"); + + if (!strcmp(message, "inputbox_completed")) { + std::vector inputboxValue; + SplitString(value, ':', inputboxValue); + +#if PPSSPP_PLATFORM(ANDROID) + if (inputboxValue.size() >= 2 && inputboxValue[0] == sy->T("Memory Stick Folder")) { + // Allow colons in the path. + std::string newPath = std::string(value).substr(inputboxValue[0].size() + 1); + size_t pos = newPath.find_last_not_of("/"); + // Gotta have at least something but a /, and also needs to start with a /. + if (newPath.empty() || pos == newPath.npos || newPath[0] != '/') { + settingInfo_->Show(sy->T("ChangingMemstickPathInvalid", "That path couldn't be used to save Memory Stick files."), nullptr); + return; + } + if (pos != newPath.size() - 1) { + newPath = newPath.substr(0, pos + 1); + } + + pendingMemstickFolder_ = newPath; + std::string promptMessage = sy->T("ChangingMemstickPath", "Save games, save states, and other data will not be copied to this folder.\n\nChange the Memory Stick folder?"); + if (!File::Exists(newPath)) { + promptMessage = sy->T("ChangingMemstickPathNotExists", "That folder doesn't exist yet.\n\nSave games, save states, and other data will not be copied to this folder.\n\nCreate a new Memory Stick folder?"); + } + // Add the path for clarity and proper confirmation. + promptMessage += "\n\n" + newPath + "/"; + screenManager()->push(new PromptScreen(promptMessage, di->T("Yes"), di->T("No"), std::bind(&GameSettingsScreen::CallbackMemstickFolder, this, std::placeholders::_1))); + } +#endif + } +} + +#if PPSSPP_PLATFORM(ANDROID) +void GameSettingsScreen::CallbackMemstickFolder(bool yes) { + I18NCategory *sy = GetI18NCategory("System"); + + if (yes) { + std::string memstickDirFile = g_Config.internalDataDirectory + "/memstick_dir.txt"; + std::string testWriteFile = pendingMemstickFolder_ + "/.write_verify_file"; + + // Already, create away. + if (!File::Exists(pendingMemstickFolder_)) { + File::CreateFullPath(pendingMemstickFolder_); + } + if (!writeDataToFile(true, "1", 1, testWriteFile.c_str())) { + settingInfo_->Show(sy->T("ChangingMemstickPathInvalid", "That path couldn't be used to save Memory Stick files."), nullptr); + return; + } + File::Delete(testWriteFile); + + writeDataToFile(true, pendingMemstickFolder_.c_str(), pendingMemstickFolder_.size(), memstickDirFile.c_str()); + // Save so the settings, at least, are transferred. + g_Config.memStickDirectory = pendingMemstickFolder_ + "/"; + g_Config.Save(); + screenManager()->RecreateAllViews(); + } +} +#endif + void GameSettingsScreen::CallbackRenderingBackend(bool yes) { // If the user ends up deciding not to restart, set the config back to the current backend // so it doesn't get switched by accident. diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h index 8a10b7093f..1a4a40841b 100644 --- a/UI/GameSettingsScreen.h +++ b/UI/GameSettingsScreen.h @@ -17,6 +17,7 @@ #pragma once +#include "ppsspp_config.h" #include "ui/ui_screen.h" #include "UI/MiscScreens.h" @@ -30,6 +31,7 @@ public: void update() override; void onFinish(DialogResult result) override; + void sendMessage(const char *message, const char *value) override; std::string tag() const override { return "settings"; } UI::Event OnRecentChanged; @@ -39,6 +41,9 @@ protected: void CallbackRestoreDefaults(bool yes); void CallbackRenderingBackend(bool yes); void CallbackRenderingDevice(bool yes); +#if PPSSPP_PLATFORM(ANDROID) + void CallbackMemstickFolder(bool yes); +#endif bool UseVerticalLayout() const; private: @@ -91,7 +96,9 @@ private: UI::EventReturn OnRenderingBackend(UI::EventParams &e); UI::EventReturn OnRenderingDevice(UI::EventParams &e); UI::EventReturn OnJitAffectingSetting(UI::EventParams &e); -#ifdef _WIN32 +#if PPSSPP_PLATFORM(ANDROID) + UI::EventReturn OnChangeMemStickDir(UI::EventParams &e); +#elif defined(_WIN32) && !PPSSPP_PLATFORM(UWP) UI::EventReturn OnSavePathMydoc(UI::EventParams &e); UI::EventReturn OnSavePathOther(UI::EventParams &e); #endif @@ -107,8 +114,7 @@ private: UI::EventReturn OnSavedataManager(UI::EventParams &e); UI::EventReturn OnSysInfo(UI::EventParams &e); - // Temporaries to convert bools to int settings - bool cap60FPS_; + // Temporaries to convert setting types. int iAlternateSpeedPercent1_; int iAlternateSpeedPercent2_; bool enableReports_; @@ -122,6 +128,10 @@ private: bool resolutionEnable_; bool bloomHackEnable_; bool tessHWEnable_; + +#if PPSSPP_PLATFORM(ANDROID) + std::string pendingMemstickFolder_; +#endif }; class SettingInfoMessage : public UI::LinearLayout { diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index 3baeaf315d..17efb30f8d 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -58,8 +58,8 @@ #include "Core/HLE/sceUmd.h" #ifdef _WIN32 -#include "Windows/W32Util/ShellUtil.h" -#include "Windows/MainWindow.h" +// Unfortunate, for undef DrawText... +#include "Common/CommonWindows.h" #endif #ifdef ANDROID_NDK_PROFILER @@ -67,12 +67,6 @@ #include "android/android-ndk-profiler/prof.h" #endif -#ifdef USING_QT_UI -#include -#include -#include -#endif - #include bool MainScreen::showHomebrewTab = false; @@ -447,12 +441,18 @@ GameBrowser::GameBrowser(std::string path, bool allowBrowsing, bool *gridStyle, Refresh(); } -void GameBrowser::FocusGame(std::string gamePath) { +void GameBrowser::FocusGame(const std::string &gamePath) { focusGamePath_ = gamePath; Refresh(); focusGamePath_.clear(); } +void GameBrowser::SetPath(const std::string &path) { + path_.SetPath(path); + g_Config.currentDirectory = path_.GetPath(); + Refresh(); +} + UI::EventReturn GameBrowser::LayoutChange(UI::EventParams &e) { *gridStyle_ = e.a == 0 ? true : false; Refresh(); @@ -466,30 +466,18 @@ UI::EventReturn GameBrowser::LastClick(UI::EventParams &e) { UI::EventReturn GameBrowser::HomeClick(UI::EventParams &e) { #ifdef __ANDROID__ - path_.SetPath(g_Config.memStickDirectory); -#elif defined(USING_QT_UI) - I18NCategory *mm = GetI18NCategory("MainMenu"); - QString fileName = QFileDialog::getExistingDirectory(NULL, "Browse for Folder", g_Config.currentDirectory.c_str()); - if (QDir(fileName).exists()) - path_.SetPath(fileName.toStdString()); - else - return UI::EVENT_DONE; -#elif defined(_WIN32) -#if PPSSPP_PLATFORM(UWP) + SetPath(g_Config.memStickDirectory); +#elif defined(USING_QT_UI) || defined(USING_WIN_UI) + if (System_GetPropertyBool(SYSPROP_HAS_FILE_BROWSER)) { + System_SendMessage("browse_folder", ""); + } +#elif PPSSPP_PLATFORM(UWP) // TODO UWP + SetPath(g_Config.memStickDirectory); #else - I18NCategory *mm = GetI18NCategory("MainMenu"); - std::string folder = W32Util::BrowseForFolder(MainWindow::GetHWND(), mm->T("Choose folder")); - if (!folder.size()) - return UI::EVENT_DONE; - path_.SetPath(folder); -#endif -#else - path_.SetPath(getenv("HOME")); + SetPath(getenv("HOME")); #endif - g_Config.currentDirectory = path_.GetPath(); - Refresh(); return UI::EVENT_DONE; } @@ -533,7 +521,7 @@ void GameBrowser::Refresh() { if (allowBrowsing_) { topBar->Add(new Spacer(2.0f)); topBar->Add(new TextView(path_.GetFriendlyPath().c_str(), ALIGN_VCENTER | FLAG_WRAP_TEXT, true, new LinearLayoutParams(FILL_PARENT, 64.0f, 1.0f))); -#if defined(_WIN32) || defined(USING_QT_UI) +#if defined(USING_WIN_UI) || defined(USING_QT_UI) topBar->Add(new Choice(mm->T("Browse", "Browse..."), new LayoutParams(WRAP_CONTENT, 64.0f)))->OnClick.Handle(this, &GameBrowser::HomeClick); #else topBar->Add(new Choice(mm->T("Home"), new LayoutParams(WRAP_CONTENT, 64.0f)))->OnClick.Handle(this, &GameBrowser::HomeClick); @@ -742,7 +730,7 @@ UI::EventReturn GameBrowser::GameButtonHighlight(UI::EventParams &e) { } UI::EventReturn GameBrowser::NavigateClick(UI::EventParams &e) { - DirButton *button = static_cast(e.v); + DirButton *button = static_cast(e.v); std::string text = button->GetPath(); if (button->PathAbsolute()) { path_.SetPath(text); @@ -916,8 +904,7 @@ void MainScreen::CreateViews() { TextView *ver = rightColumnItems->Add(new TextView(versionString, new LinearLayoutParams(Margins(70, -6, 0, 0)))); ver->SetSmall(true); ver->SetClip(false); -// Temporarily disabled the load button for Qt. See #11721 -#if defined(_WIN32) // || defined(USING_QT_UI) +#if defined(USING_WIN_UI) || defined(USING_QT_UI) rightColumnItems->Add(new Choice(mm->T("Load","Load...")))->OnClick.Handle(this, &MainScreen::OnLoadFile); #endif rightColumnItems->Add(new Choice(mm->T("Game Settings", "Settings")))->OnClick.Handle(this, &MainScreen::OnGameSettings); @@ -1009,8 +996,16 @@ void MainScreen::sendMessage(const char *message, const char *value) { // Always call the base class method first to handle the most common messages. UIScreenWithBackground::sendMessage(message, value); - if (!strcmp(message, "boot") && screenManager()->topScreen() == this) { - screenManager()->switchScreen(new EmuScreen(value)); + if (screenManager()->topScreen() == this) { + if (!strcmp(message, "boot")) { + screenManager()->switchScreen(new EmuScreen(value)); + } + if (!strcmp(message, "browse_folderSelect")) { + int tab = tabHolder_->GetCurrentTab(); + if (tab >= 0 && tab < (int)gameBrowsers_.size()) { + gameBrowsers_[tab]->SetPath(value); + } + } } if (!strcmp(message, "permission_granted") && !strcmp(value, "storage")) { RecreateViews(); @@ -1032,16 +1027,6 @@ bool MainScreen::UseVerticalLayout() const { } UI::EventReturn MainScreen::OnLoadFile(UI::EventParams &e) { -#if defined(USING_QT_UI) - QString fileName = QFileDialog::getOpenFileName(NULL, "Load ROM", g_Config.currentDirectory.c_str(), "PSP ROMs (*.iso *.cso *.pbp *.elf *.zip *.ppdmp)"); - if (QFile::exists(fileName)) { - QDir newPath; - g_Config.currentDirectory = newPath.filePath(fileName).toStdString(); - g_Config.Save(); - screenManager()->switchScreen(new EmuScreen(fileName.toStdString())); - } -#endif - if (System_GetPropertyBool(SYSPROP_HAS_FILE_BROWSER)) { System_SendMessage("browse_file", ""); } diff --git a/UI/MainScreen.h b/UI/MainScreen.h index d4957e1d7a..93d21a2984 100644 --- a/UI/MainScreen.h +++ b/UI/MainScreen.h @@ -34,7 +34,8 @@ public: UI::Choice *HomebrewStoreButton() { return homebrewStoreButton_; } - void FocusGame(std::string gamePath); + void FocusGame(const std::string &gamePath); + void SetPath(const std::string &path); protected: virtual bool DisplayTopBar(); diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index b97119a7ad..472e11b462 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -466,6 +466,15 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch // most sense. g_Config.memStickDirectory = std::string(external_dir) + "/"; g_Config.flash0Directory = std::string(external_dir) + "/flash0/"; + + std::string memstickDirFile = g_Config.internalDataDirectory + "/memstick_dir.txt"; + if (File::Exists(memstickDirFile)) { + std::string memstickDir; + readFileToString(true, memstickDirFile.c_str(), memstickDir); + if (!memstickDir.empty() && File::Exists(memstickDir)) { + g_Config.memStickDirectory = memstickDir + "/"; + } + } #elif defined(IOS) g_Config.memStickDirectory = user_data_path; g_Config.flash0Directory = std::string(external_dir) + "/flash0/"; diff --git a/Windows/MainWindowMenu.cpp b/Windows/MainWindowMenu.cpp index 2bf599539e..c3ec9f6a7f 100644 --- a/Windows/MainWindowMenu.cpp +++ b/Windows/MainWindowMenu.cpp @@ -50,6 +50,7 @@ namespace MainWindow { static std::unordered_map initialMenuKeys; static std::vector availableShaders; static std::string menuLanguageID = ""; + static int menuKeymapGeneration = -1; static bool menuShaderInfoLoaded = false; std::vector menuShaderInfo; @@ -224,6 +225,10 @@ namespace MainWindow { } void DoTranslateMenus(HWND hWnd, HMENU menu) { + auto useDefHotkey = [](int virtkey) { + return KeyMap::g_controllerMap[virtkey].empty(); + }; + TranslateMenuItem(menu, ID_FILE_MENU); TranslateMenuItem(menu, ID_EMULATION_MENU); TranslateMenuItem(menu, ID_DEBUG_MENU); @@ -236,9 +241,9 @@ namespace MainWindow { TranslateMenuItem(menu, ID_FILE_LOAD_DIR); TranslateMenuItem(menu, ID_FILE_LOAD_MEMSTICK); TranslateMenuItem(menu, ID_FILE_MEMSTICK); - TranslateMenuItem(menu, ID_FILE_SAVESTATE_SLOT_MENU, L"\tF3"); - TranslateMenuItem(menu, ID_FILE_QUICKLOADSTATE, L"\tF4"); - TranslateMenuItem(menu, ID_FILE_QUICKSAVESTATE, L"\tF2"); + TranslateMenuItem(menu, ID_FILE_SAVESTATE_SLOT_MENU, useDefHotkey(VIRTKEY_NEXT_SLOT) ? L"\tF3" : L""); + TranslateMenuItem(menu, ID_FILE_QUICKLOADSTATE, useDefHotkey(VIRTKEY_LOAD_STATE) ? L"\tF4" : L""); + TranslateMenuItem(menu, ID_FILE_QUICKSAVESTATE, useDefHotkey(VIRTKEY_SAVE_STATE) ? L"\tF2" : L""); TranslateMenuItem(menu, ID_FILE_LOADSTATEFILE); TranslateMenuItem(menu, ID_FILE_SAVESTATEFILE); TranslateMenuItem(menu, ID_FILE_RECORD_MENU); @@ -349,7 +354,7 @@ namespace MainWindow { bool changed = false; const std::string curLanguageID = i18nrepo.LanguageID(); - if (curLanguageID != menuLanguageID) { + if (curLanguageID != menuLanguageID || menuKeymapGeneration != KeyMap::g_controllerMapGeneration) { DoTranslateMenus(hWnd, menu); menuLanguageID = curLanguageID; changed = true; diff --git a/Windows/main.cpp b/Windows/main.cpp index 92e0310ea2..5ed2a7331d 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -21,15 +21,17 @@ #include "Common/CommonWindows.h" #include "Common/OSVersion.h" +#include "Common/Vulkan/VulkanLoader.h" #include #include #include +#include "base/NativeApp.h" #include "base/display.h" #include "file/vfs.h" #include "file/zip_read.h" -#include "base/NativeApp.h" +#include "i18n/i18n.h" #include "profiler/profiler.h" #include "thread/threadutil.h" #include "util/text/utf8.h" @@ -57,6 +59,7 @@ #include "Windows/GEDebugger/GEDebugger.h" #include "Windows/W32Util/DialogManager.h" +#include "Windows/W32Util/ShellUtil.h" #include "Windows/Debugger/CtrlDisAsmView.h" #include "Windows/Debugger/CtrlMemView.h" @@ -273,6 +276,11 @@ void System_SendMessage(const char *command, const char *parameter) { } } else if (!strcmp(command, "browse_file")) { MainWindow::BrowseAndBoot(""); + } else if (!strcmp(command, "browse_folder")) { + I18NCategory *mm = GetI18NCategory("MainMenu"); + std::string folder = W32Util::BrowseForFolder(MainWindow::GetHWND(), mm->T("Choose folder")); + if (folder.size()) + NativeMessageReceived("browse_folderSelect", folder.c_str()); } else if (!strcmp(command, "bgImage_browse")) { MainWindow::BrowseBackground(); } else if (!strcmp(command, "toggle_fullscreen")) { @@ -350,6 +358,39 @@ static std::string GetDefaultLangRegion() { } } +static const int EXIT_CODE_VULKAN_WORKS = 42; + +static bool DetectVulkanInExternalProcess() { + wchar_t moduleFilename[MAX_PATH]; + wchar_t workingDirectory[MAX_PATH]; + GetCurrentDirectoryW(MAX_PATH, workingDirectory); + const wchar_t *cmdline = L"--vulkan-available-check"; + GetModuleFileName(GetModuleHandle(NULL), moduleFilename, MAX_PATH); + + SHELLEXECUTEINFO info{ sizeof(SHELLEXECUTEINFO) }; + info.fMask = SEE_MASK_NOCLOSEPROCESS; + info.lpFile = moduleFilename; + info.lpParameters = cmdline; + info.lpDirectory = workingDirectory; + info.nShow = SW_HIDE; + if (ShellExecuteEx(&info) != TRUE) { + return false; + } + if (info.hProcess == nullptr) { + return false; + } + + DWORD result = WaitForSingleObject(info.hProcess, 10000); + DWORD exitCode = 0; + if (result == WAIT_FAILED || GetExitCodeProcess(info.hProcess, &exitCode) == 0) { + CloseHandle(info.hProcess); + return false; + } + CloseHandle(info.hProcess); + + return exitCode == EXIT_CODE_VULKAN_WORKS; +} + std::vector GetWideCmdLine() { wchar_t **wargv; int wargc = -1; @@ -361,9 +402,7 @@ std::vector GetWideCmdLine() { return wideArgs; } -int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, int iCmdShow) { - setCurrentThreadName("Main"); - +static void WinMainInit() { CoInitializeEx(NULL, COINIT_MULTITHREADED); net::Init(); // This needs to happen before we load the config. So on Windows we also run it in Main. It's fine to call multiple times. @@ -384,6 +423,21 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin // FMA3 support in the 2013 CRT is broken on Vista and Windows 7 RTM (fixed in SP1). Just disable it. _set_FMA3_enable(0); #endif +} + +static void WinMainCleanup() { + if (g_Config.bRestartRequired) { + W32Util::ExitAndRestart(); + } + + net::Shutdown(); + CoUninitialize(); +} + +int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, int iCmdShow) { + setCurrentThreadName("Main"); + + WinMainInit(); #ifndef _DEBUG bool showLog = false; @@ -486,12 +540,28 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin g_Config.bSoftwareRendering = true; } } + + // This should only be called by DetectVulkanInExternalProcess(). + if (wideArgs[i] == L"--vulkan-available-check") { + // Just call it, this way it will crash here if it doesn't work. + // (this is an external process.) + bool result = VulkanMayBeAvailable(); + + LogManager::Shutdown(); + WinMainCleanup(); + return result ? EXIT_CODE_VULKAN_WORKS : EXIT_FAILURE; + } } } #ifdef _DEBUG g_Config.bEnableLogging = true; #endif +#ifndef _DEBUG + // See #11719 - too many Vulkan drivers crash on basic init. + VulkanSetAvailable(DetectVulkanInExternalProcess()); +#endif + if (iCmdShow == SW_MAXIMIZE) { // Consider this to mean --fullscreen. g_Config.bFullScreen = true; @@ -595,13 +665,7 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin timeEndPeriod(1); LogManager::Shutdown(); - - if (g_Config.bRestartRequired) { - W32Util::ExitAndRestart(); - } - - net::Shutdown(); - CoUninitialize(); + WinMainCleanup(); return 0; } diff --git a/assets/compat.ini b/assets/compat.ini index 92e41445f1..d09d36147a 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -31,15 +31,31 @@ [VertexDepthRounding] # Phantasy Star Portable needs depth rounding to 16-bit precision for text to show up. # It's enough to do it at the vertex granularity. #3777 +# Phantasy Star Portable ULJM05309 = true ULUS10410 = true ULES01218 = true ULJM08023 = true ULES01218 = true -# Phantasy Star Portable 2 JP (missing text) +# Phantasy Star Portable 1 Demo +NPUH90023 = true +# Phantasy Star Portable 2 +ULES01439 = true +ULUS10529 = true ULJM05493 = true NPJH50043 = true ULJM08030 = true +NPUH90023 = true +ULJM91014 = true +NPJH90002 = true +ULJM05732 = true +NPJH50332 = true +# Phantasy Star Portable 2 JP Demo +ULJM91018 = true +NPJH90062 = true +# Phantasy Star Portable Infinity Demo +NPJH90157 = true # Infinity demo + # Puyo Puyo Fever 2 #3663 (layering) ULJM05058 = true # NBA 2K13 #6603 (menu glitches) @@ -72,23 +88,30 @@ ULJS00454 = true # Heroes Phantasia Limited Edition Disc requires pixel depth rounding. ULJS00455 = true # Phantasy Star games flickering -# Phantasy Star Portable 1 +# Phantasy Star Portable ULJM05309 = true ULUS10410 = true ULES01218 = true ULJM08023 = true ULES01218 = true -# Phantasy Star Portable 2 JP +# Phantasy Star Portable 1 Demo +NPUH90023 = true +# Phantasy Star Portable 2 +ULES01439 = true +ULUS10529 = true ULJM05493 = true NPJH50043 = true ULJM08030 = true -# Phantasy Star Portable 1 Demo NPUH90023 = true ULJM91014 = true NPJH90002 = true +ULJM05732 = true +NPJH50332 = true # Phantasy Star Portable 2 JP Demo ULJM91018 = true NPJH90062 = true +# Phantasy Star Portable Infinity Demo +NPJH90157 = true # Infinity demo # Nayuta no Kiseki see #8744 - flickering textures at some locations NPJH50625 = true @@ -96,25 +119,54 @@ NPJH50625 = true # Phantasy Star Portable 2 and Infinity both use viewport depth outside [0, 1]. # This gets clamped in our current implementation, but attempts to fix it run into # Other bugs, so we've restored this hack for now. +# Phantasy Star Portable +ULJM05309 = true +ULUS10410 = true +ULES01218 = true +ULJM08023 = true +ULES01218 = true +# Phantasy Star Portable 1 Demo +NPUH90023 = true +# Phantasy Star Portable 2 +ULES01439 = true +ULUS10529 = true ULJM05493 = true NPJH50043 = true ULJM08030 = true -ULES01439 = true -ULUS10529 = true -ULJM91018 = true # Infinity demo disc? -NPJH90157 = true # Infinity demo +NPUH90023 = true +ULJM91014 = true +NPJH90002 = true ULJM05732 = true NPJH50332 = true +# Phantasy Star Portable 2 JP Demo +ULJM91018 = true +NPJH90062 = true +# Phantasy Star Portable Infinity Demo +NPJH90157 = true # Infinity demo [ClearToRAM] # SOCOM Navy Seals games require this. See issue #8973. -# Navy Seals -UCUS98615 = true -ULES00038 = true -UCKS45021 = true -# Fireteam Bravo 3 -UCJS10102 = true +# Navy Seals : Tactical Strike +UCES00855 = true +UCUS98649 = true NPJG00035 = true +NPJG90068 = true +UCJS10102 = true +# Tactical Strike demo +NPUG70003 = true +# Fireteam Bravo +UCKS45021 = true +UCUS98615 = true +UCES00038 = true +ULES00038 = true +# Fireteam Bravo 2 +UCES00543 = true +UCUS98645 = true +# Fireteam Bravo 2 Demo +UCUS98677 = true +UCUS98691 = true +# Fireteam Bravo 3 +NPHG00032 = true UCUS98716 = true UCES01242 = true @@ -146,10 +198,13 @@ UCES00373 = true # It makes sceGeDrawSync eat a lot of cycles which can affect timing in lots of games, # might be negative for others, but happens to fix games below. # Crash Tag Team Racing needs it to pass checking memory stick screen. -ULUS10044 = true ULES00168 = true +ULES00169 = true +ULES00170 = true +ULES00171 = true ULES00172 = true ULJM05036 = true +ULUS10044 = true # Gundam Battle Royale might need it to avoid crashes when certain Ace enemies shows up ULJS00083 = true ULKS46104 = true @@ -171,6 +226,7 @@ UCKS45033 = true UCJS18014 = true UCUS98700 = true NPJG00032 = true +UCJX90019 = true # Fixes Patapon 2 title screen double framerate UCJS10089 = true NPJG00010 = true @@ -296,17 +352,41 @@ ULJM05255 = true ULJM05359 = true ULJM05885 = true NPJH50825 = true +# GOW : JP/Korean +UCJS10114 = true +UCKS45084 = true +# GOW : Ghost of Sparta +UCUS98737 = true +UCAS40323 = true +NPHG00092 = true +NPEG00044 = true +NPEG00045 = true +NPJG00120 = true +NPUG80508 = true +UCJS10114 = true +UCES01401 = true +UCES01473 = true +# GOW : Ghost of Sparta Demo +NPJG90095 = true +NPEG90035 = true +NPUG70125 = true # GOW : Chains Of Olympus +UCAS40198 = true UCUS98653 = true -UCUS98705 = true UCES00842 = true ULJM05438 = true ULJM05348 = true UCKS45084 = true NPUG80325 = true NPEG00023 = true +NPHG00027 = true NPHG00028 = true -NPJG00120 = true +NPJH50170 = true +UCET00844 = true +# GOW: Chains of Olympus Demo +UCUS98705 = true +UCED00971 = true +UCUS98713 = true # Daxter UCUS98618 = true UCES00044 = true @@ -355,6 +435,7 @@ ULES00740 = true # Chili Con Carnage ULUS10216 = true ULES00629 = true +# TODO: There are many more. [RequireBlockTransfer] # Warn the user that the game will have issue graphic, if simulate block transfer is not enabled. @@ -372,6 +453,8 @@ ULES01556 = true ULJM05170 = true ULJM08033 = true NPJH50373 = true +NPUH10191 = true +NPUH10197 = true # Grand Knights History need it to fix blackboxes on characters and flickering texture . See issues #2135 , #6099 ULJS00394 = true ULJS19068 = true @@ -401,6 +484,9 @@ ULUS10025 = true ULJM05228 = true NPJH50305 = true ULJM05049 = true +ULKS46027 = true +ULAS42019 = true + # Dominator ULUS10236 = true ULES00750 = true @@ -408,6 +494,7 @@ ULJM05242 = true ULJM05371 = true NPJH50304 = true ULES00703 = true +ULAS42095 = true [RequireDefaultCPUClock] # GOW : Ghost of Sparta @@ -415,12 +502,22 @@ UCUS98737 = true UCAS40323 = true NPHG00092 = true NPEG00044 = true +NPEG00045 = true NPJG00120 = true +NPUG80508 = true UCJS10114 = true UCES01401 = true +UCES01473 = true +# GOW : Ghost of Sparta Demo +NPJG90095 = true +NPEG90035 = true +NPUG70125 = true [MGS2AcidHack] ULES00008 = true +ULES00284 = true +ULJM05047 = true +ULKS46065 = true ULJM08001 = true ULJM05001 = true ULAS42007 = true @@ -432,6 +529,7 @@ ULES00622 = true # SR1 ULUS10195 = true # SR1 ULUS10323 = true # SR2 ULES00940 = true # SR2 +ULET00958 = true [AdhocHackDisableNBSwitch] ULJS00250 = true @@ -444,6 +542,12 @@ NPJH50686 = true # Digimon Adventures (JP, and English patches...) ULJS00078 = true # MotoGP ULUS10153 = true # MotoGP UCES00373 = true # MotoGP +ULUS10551 = true # Ys Seven +ULJM05475 = true # Ys Seven JP +ULUS10549 = true # Ys Seven +NPEH00065 = true # Ys Seven +NPJH50350 = true # Ys Seven +ULJM08041 = true # Ys Seven [YugiohSaveFix] # The cause of Yu-gi-oh series 's bad save (cannot save) are load "save status" and use cwcheat, @@ -472,10 +576,11 @@ ULES01362 = true # Yu-Gi-Oh! 5D's Tag Force 5 ULUS10555 = true ULJM05734 = true +ULES01474 = true # Yu-Gi-Oh! 5D's Tag Force 6 -ULJM-05940 = true -NPJH-50794 = true +ULJM05940 = true +NPJH50794 = true # Yu-Gi-Oh! 5D's Tag Force ULJM05940 = true @@ -487,31 +592,50 @@ NPJH00142 = true # F1 2006 won't boot at all with our standard unrealistically fast timing. UCES00238 = true UCJS10045 = true +# F1 2005, japan only? +UCJS10019 = true [ForceMax60FPS] # The GOW games are very heavy and render as fast as they can. They benefit greatly from # capping the framerate at 60fps. +UCJS10114 = true +UCKS45084 = true # GOW : Ghost of Sparta UCUS98737 = true UCAS40323 = true NPHG00092 = true NPEG00044 = true +NPEG00045 = true NPJG00120 = true +NPUG80508 = true UCJS10114 = true UCES01401 = true +UCES01473 = true +# GOW : Ghost of Sparta Demo +NPEG90035 = true +NPUG70125 = true +NPJG90095 = true # GOW : Chains Of Olympus +UCAS40198 = true UCUS98653 = true -UCUS98705 = true UCES00842 = true ULJM05438 = true ULJM05348 = true UCKS45084 = true NPUG80325 = true NPEG00023 = true +NPHG00027 = true NPHG00028 = true -NPJG00120 = true +NPJH50170 = true +UCET00844 = true +# GOW: Chains of Olympus Demo +UCUS98705 = true +UCED00971 = true +UCUS98713 = true # F1 2006 has extremely long loading times if we don't limit the framerate. UCES00238 = true UCJS10045 = true +# F1 2005, japan only? +UCJS10019 = true diff --git a/ext/native/gfx_es2/draw_text_qt.cpp b/ext/native/gfx_es2/draw_text_qt.cpp index a2fae32f39..a29b363397 100644 --- a/ext/native/gfx_es2/draw_text_qt.cpp +++ b/ext/native/gfx_es2/draw_text_qt.cpp @@ -81,8 +81,6 @@ void TextDrawerQt::DrawString(DrawBuffer &target, const char *str, float x, floa auto iter = cache_.find(entryHash); if (iter != cache_.end()) { entry = iter->second.get(); - entry->lastUsedFrame = frameCount_; - draw_->BindTexture(0, entry->texture); } else { QFont *font = fontMap_.find(fontHash_)->second; QFontMetrics fm(*font); @@ -104,7 +102,6 @@ void TextDrawerQt::DrawString(DrawBuffer &target, const char *str, float x, floa entry = new TextStringEntry(); entry->bmWidth = entry->width = image.width(); entry->bmHeight = entry->height = image.height(); - entry->lastUsedFrame = frameCount_; TextureDesc desc{}; desc.type = TextureType::LINEAR2D; @@ -126,8 +123,11 @@ void TextDrawerQt::DrawString(DrawBuffer &target, const char *str, float x, floa delete[] bitmapData; cache_[entryHash] = std::unique_ptr(entry); } + float w = entry->bmWidth * fontScaleX_; float h = entry->bmHeight * fontScaleY_; + entry->lastUsedFrame = frameCount_; + draw_->BindTexture(0, entry->texture); DrawBuffer::DoAlign(align, &x, &y, &w, &h); target.DrawTexRect(x, y, x + w, y + h, 0.0f, 0.0f, 1.0f, 1.0f, color); target.Flush(true); diff --git a/pspautotests b/pspautotests index 98b83d2cb0..32d3e0426a 160000 --- a/pspautotests +++ b/pspautotests @@ -1 +1 @@ -Subproject commit 98b83d2cb06e78c6e24f287b24a89a0ef87ac53b +Subproject commit 32d3e0426af6fccd67fe00fb4a345e6d2f3ea1a4