diff --git a/CMakeLists.txt b/CMakeLists.txt index 28b38d91e5..534d4e9b9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,11 @@ if(${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") message("Clang enabled") endif() +if(FORCED_CPU) + message("Detected CPU (${CMAKE_SYSTEM_PROCESSOR}) overridden as: ${FORCED_CPU}") + set(CMAKE_SYSTEM_PROCESSOR ${FORCED_CPU}) +endif() + # Detect CPU from CMAKE configuration. Toolchains should set this up if(CMAKE_SYSTEM_PROCESSOR) if(${CMAKE_SYSTEM_PROCESSOR} MATCHES "^arm") @@ -109,6 +114,7 @@ option(USING_EGL "Set to ON if target environment uses EGL" ${USING_EGL}) option(USING_FBDEV "Set to ON if target environment uses fbdev (eg. Pandora)" ${USING_FBDEV}) option(USING_GLES2 "Set to ON if target device uses OpenGL ES 2.0" ${USING_GLES2}) option(RASPBIAN "Set to ON to workaround threading issues when building for ARMV7 on Raspbian" ${RASPBIAN}) +option(USING_X11_VULKAN "Set to OFF if target environment doesn't use X11 for Vulkan" ON) # :: Frontends option(USING_QT_UI "Set to ON if you wish to use the Qt frontend wrapper" ${USING_QT_UI}) option(MOBILE_DEVICE "Set to ON when targeting a mobile device" ${MOBILE_DEVICE}) @@ -124,7 +130,12 @@ option(USE_WAYLAND_WSI "Set to ON to require Wayland support for Vulkan" ${USE_W option(USE_ADDRESS_SANITIZER "Use Clang memory sanitizer" ${USE_ADDRESS_SANITIZER}) if(UNIX AND NOT (APPLE OR ANDROID) AND VULKAN) - add_definitions(-DVK_USE_PLATFORM_XLIB_KHR) + if(USING_X11_VULKAN) + message("Using X11 for Vulkan") + add_definitions(-DVK_USE_PLATFORM_XLIB_KHR) + else() + message("NOT using X11 for Vulkan") + endif() # add_definitions(-DVK_USE_PLATFORM_XCB_KHR) find_package(Wayland) if (USE_WAYLAND_WSI AND NOT WAYLAND_FOUND) @@ -273,8 +284,13 @@ if(NOT MSVC) if(IOS) elseif(APPLE AND NOT CMAKE_CROSSCOMPILING) # We want C++11, so target 10.7+ - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=10.7") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.7 -stdlib=libc++ -U__STRICT_ANSI__") + if(USING_QT_UI) + set(MACOSX_VERSION_MIN "10.8") + else() + set(MACOSX_VERSION_MIN "10.7") + endif() + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=${MACOSX_VERSION_MIN}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=${MACOSX_VERSION_MIN} -stdlib=libc++ -U__STRICT_ANSI__") set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++") elseif(NOT ANDROID) # TODO: See if we can get rid of no-psabi @@ -458,11 +474,9 @@ if(USING_GLES2) find_package(X11) endif() -add_library(vjson STATIC - ext/native/ext/vjson/json.cpp - ext/native/ext/vjson/json.h - ext/native/ext/vjson/block_allocator.cpp - ext/native/ext/vjson/block_allocator.h +add_library(gason STATIC + ext/native/ext/gason/gason.cpp + ext/native/ext/gason/gason.h ) add_library(rg_etc1 STATIC @@ -751,43 +765,16 @@ elseif(USING_QT_UI) if(NOT SDL2_FOUND) find_package(Qt5 COMPONENTS Multimedia) endif(NOT SDL2_FOUND) - set(Qt_UI - Qt/Debugger/debugger_disasm.ui - Qt/Debugger/debugger_displaylist.ui - Qt/Debugger/debugger_memory.ui - Qt/Debugger/debugger_memorytex.ui - Qt/Debugger/debugger_vfpu.ui - ) - qt5_wrap_ui(QT_UI_GEN ${Qt_UI}) list(APPEND NativeAppSource - ${QT_UI_GEN} Qt/QtMain.cpp Qt/QtMain.h Qt/QtHost.cpp Qt/QtHost.h Qt/mainwindow.cpp Qt/mainwindow.h - Qt/Debugger/ctrldisasmview.cpp - Qt/Debugger/ctrldisasmview.h - Qt/Debugger/ctrlmemview.cpp - Qt/Debugger/ctrlmemview.h - Qt/Debugger/ctrlregisterlist.cpp - Qt/Debugger/ctrlregisterlist.h - Qt/Debugger/ctrlvfpuview.cpp - Qt/Debugger/ctrlvfpuview.h - Qt/Debugger/debugger_disasm.cpp - Qt/Debugger/debugger_disasm.h - Qt/Debugger/debugger_displaylist.cpp - Qt/Debugger/debugger_displaylist.h - Qt/Debugger/debugger_memory.cpp - Qt/Debugger/debugger_memory.h - Qt/Debugger/debugger_memorytex.cpp - Qt/Debugger/debugger_memorytex.h - Qt/Debugger/debugger_vfpu.cpp - Qt/Debugger/debugger_vfpu.h ) add_definitions(-DUSING_QT_UI) - include_directories(${CMAKE_CURRENT_BINARY_DIR} Qt Qt/Debugger) + include_directories(${CMAKE_CURRENT_BINARY_DIR} Qt) set(nativeExtraLibs ${nativeExtraLibs} Qt5::OpenGL Qt5::Gui Qt5::Core) set(TargetBin PPSSPPQt) @@ -917,12 +904,10 @@ add_library(native STATIC ext/native/base/stringutil.h ext/native/base/timeutil.cpp ext/native/base/timeutil.h + ext/native/data/base64.cpp + ext/native/data/base64.h ext/native/data/compression.cpp ext/native/data/compression.h - ext/native/ext/vjson/json.cpp - ext/native/ext/vjson/json.h - ext/native/ext/vjson/block_allocator.cpp - ext/native/ext/vjson/block_allocator.h ext/native/file/chunk_file.cpp ext/native/file/chunk_file.h ext/native/file/fd_util.cpp @@ -976,6 +961,10 @@ add_library(native STATIC ext/native/input/keycodes.h ext/native/input/input_state.h ext/native/input/input_state.cpp + ext/native/json/json_reader.h + ext/native/json/json_reader.cpp + ext/native/json/json_writer.h + ext/native/json/json_writer.cpp ext/native/math/fast/fast_math.c ext/native/math/fast/fast_matrix.c ext/native/math/fast/fast_matrix_neon.S @@ -1009,6 +998,8 @@ add_library(native STATIC ext/native/net/sinks.h ext/native/net/url.cpp ext/native/net/url.h + ext/native/net/websocket_server.cpp + ext/native/net/websocket_server.h ext/native/profiler/profiler.cpp ext/native/profiler/profiler.h ext/native/thin3d/thin3d.cpp @@ -1062,7 +1053,7 @@ if(ANDROID) SET(ATOMIC_LIB atomic) endif() -target_link_libraries(native ${LIBZIP_LIBRARY} ${PNG_LIBRARY} ${ZLIB_LIBRARY} rg_etc1 vjson udis86 ${RT_LIB} ${nativeExtraLibs} ${ATOMIC_LIB}) +target_link_libraries(native ${LIBZIP_LIBRARY} ${PNG_LIBRARY} ${ZLIB_LIBRARY} rg_etc1 gason udis86 ${RT_LIB} ${nativeExtraLibs} ${ATOMIC_LIB}) if(TARGET Ext::GLEW) target_link_libraries(native Ext::GLEW) endif() @@ -1111,10 +1102,6 @@ include_directories(ext/xxhash) set(CoreExtra) set(CoreExtraLibs) -if(WIN32 OR ANDROID) - set(CoreExtraLibs ${CoreExtraLibs} armips) -endif() - set(CoreExtra ${CoreExtra} Core/MIPS/IR/IRCompALU.cpp Core/MIPS/IR/IRCompBranch.cpp @@ -1405,6 +1392,7 @@ add_library(${CoreLibName} ${CoreLinkType} ${CoreExtra} Core/Config.cpp Core/Config.h + Core/ConfigValues.h Core/Core.cpp Core/Core.h Core/Compatibility.cpp @@ -1417,6 +1405,8 @@ add_library(${CoreLibName} ${CoreLinkType} Core/HDRemaster.cpp Core/HDRemaster.h Core/ThreadEventQueue.h + Core/WebServer.cpp + Core/WebServer.h Core/Debugger/Breakpoints.cpp Core/Debugger/Breakpoints.h Core/Debugger/DebugInterface.h @@ -1424,6 +1414,30 @@ add_library(${CoreLibName} ${CoreLinkType} Core/Debugger/SymbolMap.h Core/Debugger/DisassemblyManager.cpp Core/Debugger/DisassemblyManager.h + Core/Debugger/WebSocket.cpp + Core/Debugger/WebSocket.h + Core/Debugger/WebSocket/BreakpointSubscriber.cpp + Core/Debugger/WebSocket/BreakpointSubscriber.h + Core/Debugger/WebSocket/CPUCoreSubscriber.cpp + Core/Debugger/WebSocket/CPUCoreSubscriber.h + Core/Debugger/WebSocket/DisasmSubscriber.cpp + Core/Debugger/WebSocket/DisasmSubscriber.h + Core/Debugger/WebSocket/GameBroadcaster.cpp + Core/Debugger/WebSocket/GameBroadcaster.h + Core/Debugger/WebSocket/GameSubscriber.cpp + Core/Debugger/WebSocket/GameSubscriber.h + Core/Debugger/WebSocket/GPUBufferSubscriber.cpp + Core/Debugger/WebSocket/GPUBufferSubscriber.h + Core/Debugger/WebSocket/HLESubscriber.cpp + Core/Debugger/WebSocket/HLESubscriber.h + Core/Debugger/WebSocket/LogBroadcaster.cpp + Core/Debugger/WebSocket/LogBroadcaster.h + Core/Debugger/WebSocket/SteppingBroadcaster.cpp + Core/Debugger/WebSocket/SteppingBroadcaster.h + Core/Debugger/WebSocket/SteppingSubscriber.cpp + Core/Debugger/WebSocket/SteppingSubscriber.h + Core/Debugger/WebSocket/WebSocketUtils.cpp + Core/Debugger/WebSocket/WebSocketUtils.h Core/Dialog/PSPDialog.cpp Core/Dialog/PSPDialog.h Core/Dialog/PSPGamedataInstallDialog.cpp @@ -1701,7 +1715,6 @@ add_library(${CoreLibName} ${CoreLinkType} Core/Util/ppge_atlas.h ${CORE_NEON} ${GPU_SOURCES} - git-version.cpp ext/disarm.cpp git-version.cpp) @@ -1751,7 +1764,8 @@ add_custom_command(OUTPUT something_that_never_exists -P ${CMAKE_CURRENT_SOURCE_DIR}/git-version.cmake) set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/git-version.cpp - PROPERTIES GENERATED TRUE) + PROPERTIES GENERATED TRUE + SKIP_AUTOMOC ON) add_dependencies(${CoreLibName} GitVersion) set(WindowsFiles diff --git a/Common/ChunkFile.cpp b/Common/ChunkFile.cpp index 3560c6eeb4..4e2334e735 100644 --- a/Common/ChunkFile.cpp +++ b/Common/ChunkFile.cpp @@ -225,7 +225,7 @@ CChunkFileReader::Error CChunkFileReader::GetFileTitle(const std::string &filena return LoadFileHeader(pFile, header, title); } -CChunkFileReader::Error CChunkFileReader::LoadFile(const std::string &filename, const char *gitVersion, u8 *&_buffer, size_t &sz, std::string *failureReason) { +CChunkFileReader::Error CChunkFileReader::LoadFile(const std::string &filename, std::string *gitVersion, u8 *&_buffer, size_t &sz, std::string *failureReason) { if (!File::Exists(filename)) { *failureReason = "LoadStateDoesntExist"; ERROR_LOG(SAVESTATE, "ChunkReader: File doesn't exist"); @@ -264,6 +264,12 @@ CChunkFileReader::Error CChunkFileReader::LoadFile(const std::string &filename, delete [] buffer; } + if (header.GitVersion[31]) { + *gitVersion = std::string(header.GitVersion, 32); + } else { + *gitVersion = header.GitVersion; + } + return ERROR_NONE; } @@ -294,7 +300,7 @@ CChunkFileReader::Error CChunkFileReader::SaveFile(const std::string &filename, } // Create header - SChunkHeader header; + SChunkHeader header{}; header.Compress = compressed_buffer ? 1 : 0; header.Revision = REVISION_CURRENT; header.ExpectedSize = (u32)write_len; @@ -302,7 +308,7 @@ CChunkFileReader::Error CChunkFileReader::SaveFile(const std::string &filename, truncate_cpy(header.GitVersion, gitVersion); // Setup the fixed-length title. - char titleFixed[128]; + char titleFixed[128]{}; truncate_cpy(titleFixed, title.c_str()); // Now let's start writing out the file... diff --git a/Common/ChunkFile.h b/Common/ChunkFile.h index 6fc76ddb29..dba69b0da0 100644 --- a/Common/ChunkFile.h +++ b/Common/ChunkFile.h @@ -622,7 +622,7 @@ public: // Load file template template - static Error Load(const std::string &filename, const char *gitVersion, T& _class, std::string *failureReason) + static Error Load(const std::string &filename, std::string *gitVersion, T& _class, std::string *failureReason) { *failureReason = "LoadStateWrongVersion"; @@ -700,7 +700,7 @@ private: REVISION_CURRENT = REVISION_TITLE, }; - static Error LoadFile(const std::string &filename, const char *gitVersion, u8 *&buffer, size_t &sz, std::string *failureReason); + static Error LoadFile(const std::string &filename, std::string *gitVersion, u8 *&buffer, size_t &sz, std::string *failureReason); static Error SaveFile(const std::string &filename, const std::string &title, const char *gitVersion, u8 *buffer, size_t sz); static Error LoadFileHeader(File::IOFile &pFile, SChunkHeader &header, std::string *title); }; diff --git a/Common/ConsoleListener.cpp b/Common/ConsoleListener.cpp index 34491ec49f..b5285e1f25 100644 --- a/Common/ConsoleListener.cpp +++ b/Common/ConsoleListener.cpp @@ -600,7 +600,7 @@ void ConsoleListener::PixelSpace(int Left, int Top, int Width, int Height, bool void ConsoleListener::Log(const LogMessage &msg) { char Text[2048]; - snprintf(Text, sizeof(Text), "%s %s", msg.header, msg.msg.c_str()); + snprintf(Text, sizeof(Text), "%s %s %s", msg.timestamp, msg.header, msg.msg.c_str()); Text[sizeof(Text) - 2] = '\n'; Text[sizeof(Text) - 1] = '\0'; diff --git a/Common/FixedSizeQueue.h b/Common/FixedSizeQueue.h index 60532cc693..d59555f7d8 100644 --- a/Common/FixedSizeQueue.h +++ b/Common/FixedSizeQueue.h @@ -46,6 +46,8 @@ public: head_ = 0; tail_ = 0; count_ = 0; + // Not entirely necessary, but keeps things clean. + memset(storage_, 0, sizeof(T) * N); } void push(T t) { diff --git a/Common/GL/GLInterface/EGL.cpp b/Common/GL/GLInterface/EGL.cpp index 90a67dc285..fb215af598 100644 --- a/Common/GL/GLInterface/EGL.cpp +++ b/Common/GL/GLInterface/EGL.cpp @@ -202,14 +202,21 @@ bool cInterfaceEGL::ChooseAndCreate(void *window_handle, bool core, bool use565) EGLint ctx_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, - EGL_NONE, 0 + EGL_NONE, 0, + EGL_NONE, 0, + EGL_NONE, 0, + EGL_NONE, 0, }; switch (s_opengl_mode) { case MODE_OPENGL: EGL_ILOG("Setting RENDERABLE_TYPE to EGL_OPENGL_BIT"); attribs[1] = EGL_OPENGL_BIT; - ctx_attribs[0] = EGL_NONE; + // 1 will be major version, and 3 the minor version. + ctx_attribs[2] = 0x30FB; /* EGL_CONTEXT_MINOR_VERSION_KHR */ + // Let's always use a core profile here. + ctx_attribs[4] = 0x30FD; /* EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR */ + ctx_attribs[5] = 1; /* EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR */ break; case MODE_OPENGLES2: EGL_ILOG("Setting RENDERABLE_TYPE to EGL_OPENGL_ES2_BIT"); @@ -230,7 +237,7 @@ bool cInterfaceEGL::ChooseAndCreate(void *window_handle, bool core, bool use565) EGL_ILOG("Calling eglChooseConfig to get number of configs (use16bit=%d)...", (int)use565); EGLConfig *configs; - EGLint num_configs; + EGLint num_configs = 0; if (!eglChooseConfig(egl_dpy, attribs, NULL, 0, &num_configs) || num_configs == 0) { EGL_ILOG("Error: couldn't get a number of configs. Trying with fallback config (no stencil, not specifying transparent:none)\n"); attribsFallback[1] = attribs[1]; @@ -298,7 +305,23 @@ bool cInterfaceEGL::ChooseAndCreate(void *window_handle, bool core, bool use565) s = eglQueryString(egl_dpy, EGL_CLIENT_APIS); EGL_ILOG("EGL_CLIENT_APIS = %s\n", s); - egl_ctx = eglCreateContext(egl_dpy, configs[chosenConfig], EGL_NO_CONTEXT, ctx_attribs); + + if (s_opengl_mode == MODE_OPENGL) { + EGL_ILOG("Finding a good GL version"); + egl_ctx = nullptr; + for (int minor = 6; minor >= 0 && !egl_ctx; --minor) { + ctx_attribs[1] = 4; + ctx_attribs[3] = minor; + egl_ctx = eglCreateContext(egl_dpy, configs[chosenConfig], EGL_NO_CONTEXT, ctx_attribs); + } + if (!egl_ctx) { + ctx_attribs[1] = 3; + ctx_attribs[3] = 3; + egl_ctx = eglCreateContext(egl_dpy, configs[chosenConfig], EGL_NO_CONTEXT, ctx_attribs); + } + } else { + egl_ctx = eglCreateContext(egl_dpy, configs[chosenConfig], EGL_NO_CONTEXT, ctx_attribs); + } if (!egl_ctx) { EGL_ILOG("Error: eglCreateContext failed: %s\n", EGLGetErrorString(eglGetError())); delete[] configs; @@ -339,7 +362,7 @@ bool cInterfaceEGL::Create(void *window_handle, bool core, bool use565) { if (s_opengl_mode == MODE_DETECT || s_opengl_mode == MODE_DETECT_ES) DetectMode(); - if (!ChooseAndCreate(window_handle, core, use565) && s_opengl_mode == MODE_OPENGLES3) { + if (!ChooseAndCreate(window_handle, core, use565) && (s_opengl_mode == MODE_OPENGLES3 || s_opengl_mode == MODE_OPENGL)) { // Fallback to ES 2.0 and try again. s_opengl_mode = MODE_OPENGLES2; if (!ChooseAndCreate(window_handle, core, use565)) { diff --git a/Common/KeyMap.cpp b/Common/KeyMap.cpp index ca9f73af48..d514a5a4d7 100644 --- a/Common/KeyMap.cpp +++ b/Common/KeyMap.cpp @@ -649,6 +649,8 @@ const KeyMap_IntStrPair psp_button_names[] = { {VIRTKEY_RAPID_FIRE, "RapidFire"}, {VIRTKEY_UNTHROTTLE, "Unthrottle"}, {VIRTKEY_SPEED_TOGGLE, "SpeedToggle"}, + {VIRTKEY_SPEED_CUSTOM1, "Alt speed 1"}, + {VIRTKEY_SPEED_CUSTOM2, "Alt speed 2"}, {VIRTKEY_PAUSE, "Pause"}, #ifndef MOBILE_DEVICE {VIRTKEY_FRAME_ADVANCE, "Frame Advance"}, diff --git a/Common/KeyMap.h b/Common/KeyMap.h index fd56d4d8f5..9f408ed7f6 100644 --- a/Common/KeyMap.h +++ b/Common/KeyMap.h @@ -52,6 +52,8 @@ enum { VIRTKEY_DEVMENU = 0x40000014, VIRTKEY_FRAME_ADVANCE = 0x40000015, VIRTKEY_RECORD = 0x40000016, + VIRTKEY_SPEED_CUSTOM1 = 0x40000017, + VIRTKEY_SPEED_CUSTOM2 = 0x40000018, VIRTKEY_LAST, VIRTKEY_COUNT = VIRTKEY_LAST - VIRTKEY_FIRST }; diff --git a/Common/LogManager.cpp b/Common/LogManager.cpp index c138f34533..65545b1fc0 100644 --- a/Common/LogManager.cpp +++ b/Common/LogManager.cpp @@ -225,22 +225,17 @@ void LogManager::Log(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type, const if (fileshort != file) file = fileshort + 1; } - - char formattedTime[13]; std::lock_guard lk(log_lock_); - Common::Timer::GetTimeFormatted(formattedTime); + Common::Timer::GetTimeFormatted(message.timestamp); - size_t prefixLen; if (hleCurrentThreadName) { - prefixLen = snprintf(message.header, sizeof(message.header), "%s %-12.12s %c[%s]: %s:%d", - formattedTime, + snprintf(message.header, sizeof(message.header), "%-12.12s %c[%s]: %s:%d", hleCurrentThreadName, level_to_char[(int)level], log.m_shortName, file, line); } else { - prefixLen = snprintf(message.header, sizeof(message.header), "%s %s:%d %c[%s]:", - formattedTime, + snprintf(message.header, sizeof(message.header), "%s:%d %c[%s]:", file, line, level_to_char[(int)level], log.m_shortName); } @@ -250,15 +245,14 @@ void LogManager::Log(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type, const va_copy(args_copy, args); size_t neededBytes = vsnprintf(msgBuf, sizeof(msgBuf), format, args); + message.msg.resize(neededBytes + 1); if (neededBytes > sizeof(msgBuf)) { // Needed more space? Re-run vsnprintf. - message.msg.resize(neededBytes + 1); vsnprintf(&message.msg[0], neededBytes + 1, format, args_copy); } else { - message.msg.resize(neededBytes + 1); memcpy(&message.msg[0], msgBuf, neededBytes); } - message.msg[message.msg.size() - 1] = '\n'; + message.msg[neededBytes] = '\n'; va_end(args_copy); std::lock_guard listeners_lock(listeners_lock_); @@ -313,7 +307,7 @@ void FileLogListener::Log(const LogMessage &message) { return; std::lock_guard lk(m_log_lock); - m_logfile << message.header << " " << message.msg << std::flush; + m_logfile << message.timestamp << " " << message.header << " " << message.msg << std::flush; } void OutputDebugStringLogListener::Log(const LogMessage &message) { diff --git a/Common/LogManager.h b/Common/LogManager.h index 3a778ba11c..1e8e39547d 100644 --- a/Common/LogManager.h +++ b/Common/LogManager.h @@ -34,7 +34,8 @@ extern const char *hleCurrentThreadName; // Struct that listeners can output how they want. For example, on Android we don't want to add // timestamp or write the level as a string, those already exist. struct LogMessage { - char header[64]; // timestamp and the other stuff in front... + char timestamp[16]; + char header[64]; // Filename/thread/etc. in front. LogTypes::LOG_LEVELS level; const char *log; std::string msg; // The actual log message. diff --git a/Common/Vulkan/VulkanContext.cpp b/Common/Vulkan/VulkanContext.cpp index 5b3da0b7d7..3b43e80c02 100644 --- a/Common/Vulkan/VulkanContext.cpp +++ b/Common/Vulkan/VulkanContext.cpp @@ -213,6 +213,7 @@ VkResult VulkanContext::CreateInstance(const CreateInfo &info) { assert(gpu_count > 0); physical_devices_.resize(gpu_count); + physicalDeviceProperties_.resize(gpu_count); res = vkEnumeratePhysicalDevices(instance_, &gpu_count, physical_devices_.data()); if (res != VK_SUCCESS) { init_error_ = "Failed to enumerate physical devices"; @@ -221,6 +222,9 @@ VkResult VulkanContext::CreateInstance(const CreateInfo &info) { return res; } + for (uint32_t i = 0; i < gpu_count; i++) { + vkGetPhysicalDeviceProperties(physical_devices_[i], &physicalDeviceProperties_[i]); + } return VK_SUCCESS; } @@ -417,6 +421,14 @@ bool VulkanContext::CheckLayers(const std::vector &layer_props, return true; } +int VulkanContext::GetPhysicalDeviceByName(std::string name) { + for (size_t i = 0; i < physical_devices_.size(); i++) { + if (physicalDeviceProperties_[i].deviceName == name) + return (int)i; + } + return -1; +} + int VulkanContext::GetBestPhysicalDevice() { // Rules: Prefer discrete over embedded. // Prefer nVidia over Intel. @@ -432,12 +444,17 @@ int VulkanContext::GetBestPhysicalDevice() { case VK_PHYSICAL_DEVICE_TYPE_CPU: score += 1; break; + case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: + score += 2; + break; case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: score += 20; break; case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: score += 10; break; + default: + break; } if (props.vendorID == VULKAN_VENDOR_AMD) { score += 5; @@ -490,7 +507,6 @@ void VulkanContext::ChooseDevice(int physical_device) { // This is as good a place as any to do this vkGetPhysicalDeviceMemoryProperties(physical_devices_[physical_device_], &memory_properties); - vkGetPhysicalDeviceProperties(physical_devices_[physical_device_], &gpu_props); // Optional features vkGetPhysicalDeviceFeatures(physical_devices_[physical_device_], &featuresAvailable_); diff --git a/Common/Vulkan/VulkanContext.h b/Common/Vulkan/VulkanContext.h index 6466f7b390..e58824cd26 100644 --- a/Common/Vulkan/VulkanContext.h +++ b/Common/Vulkan/VulkanContext.h @@ -128,6 +128,7 @@ public: void DestroyInstance(); int GetBestPhysicalDevice(); + int GetPhysicalDeviceByName(std::string name); void ChooseDevice(int physical_device); bool EnableDeviceExtension(const char *extension); VkResult CreateDevice(); @@ -173,6 +174,12 @@ public: VkPhysicalDevice GetPhysicalDevice(int n = 0) const { return physical_devices_[n]; } + int GetCurrentPhysicalDevice() const { + return physical_device_; + } + int GetNumPhysicalDevices() const { + return (int)physical_devices_.size(); + } VkQueue GetGraphicsQueue() const { return gfx_queue_; @@ -182,8 +189,8 @@ public: return graphics_queue_family_index_; } - const VkPhysicalDeviceProperties &GetPhysicalDeviceProperties() { - return gpu_props; + const VkPhysicalDeviceProperties &GetPhysicalDeviceProperties(int i) const { + return physicalDeviceProperties_[i]; } VkResult GetInstanceLayerExtensionList(const char *layerName, std::vector &extensions); @@ -281,7 +288,7 @@ private: int physical_device_ = -1; uint32_t graphics_queue_family_index_ = -1; - VkPhysicalDeviceProperties gpu_props{}; + std::vector physicalDeviceProperties_{}; std::vector queue_props; VkPhysicalDeviceMemoryProperties memory_properties{}; diff --git a/Common/Vulkan/VulkanLoader.cpp b/Common/Vulkan/VulkanLoader.cpp index 0d41785a28..04755dc555 100644 --- a/Common/Vulkan/VulkanLoader.cpp +++ b/Common/Vulkan/VulkanLoader.cpp @@ -313,6 +313,8 @@ bool VulkanMayBeAvailable() { case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: anyGood = true; break; + default: + break; } // TODO: Should also check queuefamilyproperties for a GRAPHICS queue family? Oh well. } diff --git a/Core/Config.cpp b/Core/Config.cpp index bd87dfa9e9..af2fe695f1 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -22,9 +22,9 @@ #include "base/display.h" #include "base/NativeApp.h" -#include "ext/vjson/json.h" #include "file/ini_file.h" #include "i18n/i18n.h" +#include "json/json_reader.h" #include "gfx_es2/gpu_features.h" #include "net/http_client.h" #include "util/text/parsers.h" @@ -38,6 +38,7 @@ #include "Common/StringUtils.h" #include "Common/Vulkan/VulkanLoader.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/Loaders.h" #include "Core/HLE/sceUtility.h" #include "GPU/Common/FramebufferCommon.h" @@ -63,6 +64,7 @@ struct ConfigSetting { TYPE_UINT32, TYPE_FLOAT, TYPE_STRING, + TYPE_TOUCH_POS, }; union Value { bool b; @@ -70,6 +72,7 @@ struct ConfigSetting { uint32_t u; float f; const char *s; + ConfigTouchPos touchPos; }; union SettingPtr { bool *b; @@ -77,6 +80,7 @@ struct ConfigSetting { uint32_t *u; float *f; std::string *s; + ConfigTouchPos *touchPos; }; typedef bool (*BoolDefaultCallback)(); @@ -84,6 +88,7 @@ struct ConfigSetting { typedef uint32_t (*Uint32DefaultCallback)(); typedef float (*FloatDefaultCallback)(); typedef const char *(*StringDefaultCallback)(); + typedef ConfigTouchPos (*TouchPosDefaultCallback)(); union Callback { BoolDefaultCallback b; @@ -91,6 +96,7 @@ struct ConfigSetting { Uint32DefaultCallback u; FloatDefaultCallback f; StringDefaultCallback s; + TouchPosDefaultCallback touchPos; }; ConfigSetting(bool v) @@ -134,6 +140,13 @@ struct ConfigSetting { default_.s = def; } + ConfigSetting(const char *iniX, const char *iniY, const char *iniScale, const char *iniShow, ConfigTouchPos *v, ConfigTouchPos def, bool save = true, bool perGame = false) + : ini_(iniX), ini2_(iniY), ini3_(iniScale), ini4_(iniShow), type_(TYPE_TOUCH_POS), report_(false), save_(save), perGame_(perGame) { + ptr_.touchPos = v; + cb_.touchPos = nullptr; + default_.touchPos = def; + } + ConfigSetting(const char *ini, bool *v, BoolDefaultCallback def, bool save = true, bool perGame = false) : ini_(ini), type_(TYPE_BOOL), report_(false), save_(save), perGame_(perGame) { ptr_.b = v; @@ -164,6 +177,12 @@ struct ConfigSetting { cb_.s = def; } + ConfigSetting(const char *iniX, const char *iniY, const char *iniScale, const char *iniShow, ConfigTouchPos *v, TouchPosDefaultCallback def, bool save = true, bool perGame = false) + : ini_(iniX), ini2_(iniY), ini3_(iniScale), ini4_(iniShow), type_(TYPE_TOUCH_POS), report_(false), save_(save), perGame_(perGame) { + ptr_.touchPos = v; + cb_.touchPos = def; + } + bool HasMore() const { return type_ != TYPE_TERMINATOR; } @@ -195,6 +214,19 @@ struct ConfigSetting { default_.s = cb_.s(); } return section->Get(ini_, ptr_.s, default_.s); + case TYPE_TOUCH_POS: + if (cb_.touchPos) { + default_.touchPos = cb_.touchPos(); + } + section->Get(ini_, &ptr_.touchPos->x, default_.touchPos.x); + section->Get(ini2_, &ptr_.touchPos->y, default_.touchPos.y); + section->Get(ini3_, &ptr_.touchPos->scale, default_.touchPos.scale); + if (ini4_) { + section->Get(ini4_, &ptr_.touchPos->show, default_.touchPos.show); + } else { + ptr_.touchPos->show = default_.touchPos.show; + } + return true; default: _dbg_assert_msg_(LOADER, false, "Unexpected ini setting type"); return false; @@ -216,6 +248,14 @@ struct ConfigSetting { return section->Set(ini_, *ptr_.f); case TYPE_STRING: return section->Set(ini_, *ptr_.s); + case TYPE_TOUCH_POS: + section->Set(ini_, ptr_.touchPos->x); + section->Set(ini2_, ptr_.touchPos->y); + section->Set(ini3_, ptr_.touchPos->scale); + if (ini4_) { + section->Set(ini4_, ptr_.touchPos->show); + } + return; default: _dbg_assert_msg_(LOADER, false, "Unexpected ini setting type"); return; @@ -237,6 +277,9 @@ struct ConfigSetting { return data.Add(prefix + ini_, *ptr_.f); case TYPE_STRING: return data.Add(prefix + ini_, *ptr_.s); + case TYPE_TOUCH_POS: + // Doesn't report. + return; default: _dbg_assert_msg_(LOADER, false, "Unexpected ini setting type"); return; @@ -244,6 +287,9 @@ struct ConfigSetting { } const char *ini_; + const char *ini2_; + const char *ini3_; + const char *ini4_; Type type_; bool report_; bool save_; @@ -356,7 +402,7 @@ static ConfigSetting generalSettings[] = { ConfigSetting("ForceLagSync", &g_Config.bForceLagSync, false, true, true), ReportedConfigSetting("NumWorkerThreads", &g_Config.iNumWorkerThreads, &DefaultNumWorkers, true, true), - ConfigSetting("EnableAutoLoad", &g_Config.bEnableAutoLoad, false, true, true), + ConfigSetting("AutoLoadSaveState", &g_Config.iAutoLoadSaveState, 0, true, true), ReportedConfigSetting("EnableCheats", &g_Config.bEnableCheats, true, true, true), ConfigSetting("CwCheatRefreshRate", &g_Config.iCwCheatRefreshRate, 77, true, true), @@ -385,11 +431,12 @@ static ConfigSetting generalSettings[] = { ConfigSetting("RemoteISOManualConfig", &g_Config.bRemoteISOManual, false), ConfigSetting("RemoteShareOnStartup", &g_Config.bRemoteShareOnStartup, false), ConfigSetting("RemoteISOSubdir", &g_Config.sRemoteISOSubdir, "/"), + ConfigSetting("RemoteDebuggerOnStartup", &g_Config.bRemoteDebuggerOnStartup, false), #ifdef __ANDROID__ - ConfigSetting("ScreenRotation", &g_Config.iScreenRotation, 1), + ConfigSetting("ScreenRotation", &g_Config.iScreenRotation, ROTATION_LOCKED_HORIZONTAL), #endif - ConfigSetting("InternalScreenRotation", &g_Config.iInternalScreenRotation, 1), + ConfigSetting("InternalScreenRotation", &g_Config.iInternalScreenRotation, ROTATION_LOCKED_HORIZONTAL), #if defined(USING_WIN_UI) ConfigSetting("TopMost", &g_Config.bTopMost, false), @@ -422,6 +469,7 @@ static ConfigSetting cpuSettings[] = { ConfigSetting("FastMemoryAccess", &g_Config.bFastMemory, false, true, true), ReportedConfigSetting("FuncReplacements", &g_Config.bFuncReplacements, true, true, true), ConfigSetting("HideSlowWarnings", &g_Config.bHideSlowWarnings, true, true, false), + ConfigSetting("HideStateWarnings", &g_Config.bHideStateWarnings, false, true, false), ConfigSetting("PreloadFunctions", &g_Config.bPreloadFunctions, false, true, true), ReportedConfigSetting("CPUSpeed", &g_Config.iLockedCPUSpeed, 0, true, true), @@ -456,7 +504,7 @@ static bool DefaultFrameskipUnthrottle() { } static int DefaultZoomType() { - return 2; + return (int)SmallDisplayZoom::AUTO; } static bool DefaultTimerHack() { @@ -512,20 +560,24 @@ static ConfigSetting graphicsSettings[] = { ConfigSetting("CardboardYShift", &g_Config.iCardboardXShift, 0, true, true), ConfigSetting("ShowFPSCounter", &g_Config.iShowFPSCounter, 0, true, true), ReportedConfigSetting("GraphicsBackend", &g_Config.iGPUBackend, &DefaultGPUBackend), + ConfigSetting("VulkanDevice", &g_Config.sVulkanDevice, "", true, false), +#ifdef _WIN32 + ConfigSetting("D3D11Device", &g_Config.sD3D11Device, "", true, false), +#endif ReportedConfigSetting("RenderingMode", &g_Config.iRenderingMode, &DefaultRenderingMode, true, true), ConfigSetting("SoftwareRenderer", &g_Config.bSoftwareRendering, false, true, true), ReportedConfigSetting("HardwareTransform", &g_Config.bHardwareTransform, true, true, true), ReportedConfigSetting("SoftwareSkinning", &g_Config.bSoftwareSkinning, true, true, true), ConfigSetting("ShaderDepal", &g_Config.bShaderDepal, false, true, true), ReportedConfigSetting("TextureFiltering", &g_Config.iTexFiltering, 1, true, true), - ReportedConfigSetting("BufferFiltering", &g_Config.iBufFilter, 1, true, true), + ReportedConfigSetting("BufferFiltering", &g_Config.iBufFilter, SCALE_LINEAR, true, true), ReportedConfigSetting("InternalResolution", &g_Config.iInternalResolution, &DefaultInternalResolution, true, true), ReportedConfigSetting("AndroidHwScale", &g_Config.iAndroidHwScale, &DefaultAndroidHwScale), ReportedConfigSetting("HighQualityDepth", &g_Config.bHighQualityDepth, true, true, true), ReportedConfigSetting("FrameSkip", &g_Config.iFrameSkip, 0, true, true), ReportedConfigSetting("AutoFrameSkip", &g_Config.bAutoFrameSkip, false, true, true), - ConfigSetting("FrameRate", &g_Config.iFpsLimit, 0, true, true), - ConfigSetting("UnToggleFpsLimit", &g_Config.bUnToggleFpsLimit, false, true, true), + ConfigSetting("FrameRate", &g_Config.iFpsLimit1, 0, true, true), + ConfigSetting("FrameRate2", &g_Config.iFpsLimit2, -1, true, true), ConfigSetting("FrameSkipUnthrottle", &g_Config.bFrameSkipUnthrottle, &DefaultFrameskipUnthrottle, true, false), #if defined(USING_WIN_UI) ConfigSetting("RestartRequired", &g_Config.bRestartRequired, false, false), @@ -617,6 +669,8 @@ static bool DefaultShowTouchControls() { } static const float defaultControlScale = 1.15f; +static const ConfigTouchPos defaultTouchPosShow = { -1.0f, -1.0f, defaultControlScale, true }; +static const ConfigTouchPos defaultTouchPosHide = { -1.0f, -1.0f, defaultControlScale, false }; static ConfigSetting controlSettings[] = { ConfigSetting("HapticFeedback", &g_Config.bHapticFeedback, false, true, true), @@ -624,19 +678,7 @@ static ConfigSetting controlSettings[] = { ConfigSetting("ShowTouchCircle", &g_Config.bShowTouchCircle, true, true, true), ConfigSetting("ShowTouchSquare", &g_Config.bShowTouchSquare, true, true, true), ConfigSetting("ShowTouchTriangle", &g_Config.bShowTouchTriangle, true, true, true), - ConfigSetting("ShowTouchStart", &g_Config.bShowTouchStart, true, true, true), - ConfigSetting("ShowTouchSelect", &g_Config.bShowTouchSelect, true, true, true), - ConfigSetting("ShowTouchLTrigger", &g_Config.bShowTouchLTrigger, true, true, true), - ConfigSetting("ShowTouchRTrigger", &g_Config.bShowTouchRTrigger, true, true, true), - ConfigSetting("ShowAnalogStick", &g_Config.bShowTouchAnalogStick, true, true, true), - ConfigSetting("ShowTouchDpad", &g_Config.bShowTouchDpad, true, true, true), - ConfigSetting("ShowTouchUnthrottle", &g_Config.bShowTouchUnthrottle, true, true, true), - ConfigSetting("ShowComboKey0", &g_Config.bShowComboKey0, false, true, true), - ConfigSetting("ShowComboKey1", &g_Config.bShowComboKey1, false, true, true), - ConfigSetting("ShowComboKey2", &g_Config.bShowComboKey2, false, true, true), - ConfigSetting("ShowComboKey3", &g_Config.bShowComboKey3, false, true, true), - ConfigSetting("ShowComboKey4", &g_Config.bShowComboKey4, false, true, true), ConfigSetting("ComboKey0Mapping", &g_Config.iCombokey0, 0, true, true), ConfigSetting("ComboKey1Mapping", &g_Config.iCombokey1, 0, true, true), ConfigSetting("ComboKey2Mapping", &g_Config.iCombokey2, 0, true, true), @@ -676,49 +718,26 @@ static ConfigSetting controlSettings[] = { // -1.0f means uninitialized, set in GamepadEmu::CreatePadLayout(). ConfigSetting("ActionButtonSpacing2", &g_Config.fActionButtonSpacing, 1.0f, true, true), - ConfigSetting("ActionButtonCenterX", &g_Config.fActionButtonCenterX, -1.0f, true, true), - ConfigSetting("ActionButtonCenterY", &g_Config.fActionButtonCenterY, -1.0f, true, true), - ConfigSetting("ActionButtonScale", &g_Config.fActionButtonScale, defaultControlScale, true, true), - ConfigSetting("DPadX", &g_Config.fDpadX, -1.0f, true, true), - ConfigSetting("DPadY", &g_Config.fDpadY, -1.0f, true, true), + ConfigSetting("ActionButtonCenterX", "ActionButtonCenterY", "ActionButtonScale", nullptr, &g_Config.touchActionButtonCenter, defaultTouchPosShow, true, true), + ConfigSetting("DPadX", "DPadY", "DPadScale", "ShowTouchDpad", &g_Config.touchDpad, defaultTouchPosShow, true, true), // Note: these will be overwritten if DPadRadius is set. - ConfigSetting("DPadScale", &g_Config.fDpadScale, defaultControlScale, true, true), ConfigSetting("DPadSpacing", &g_Config.fDpadSpacing, 1.0f, true, true), - ConfigSetting("StartKeyX", &g_Config.fStartKeyX, -1.0f, true, true), - ConfigSetting("StartKeyY", &g_Config.fStartKeyY, -1.0f, true, true), - ConfigSetting("StartKeyScale", &g_Config.fStartKeyScale, defaultControlScale, true, true), - ConfigSetting("SelectKeyX", &g_Config.fSelectKeyX, -1.0f, true, true), - ConfigSetting("SelectKeyY", &g_Config.fSelectKeyY, -1.0f, true, true), - ConfigSetting("SelectKeyScale", &g_Config.fSelectKeyScale, defaultControlScale, true, true), - ConfigSetting("UnthrottleKeyX", &g_Config.fUnthrottleKeyX, -1.0f, true, true), - ConfigSetting("UnthrottleKeyY", &g_Config.fUnthrottleKeyY, -1.0f, true, true), - ConfigSetting("UnthrottleKeyScale", &g_Config.fUnthrottleKeyScale, defaultControlScale, true, true), - ConfigSetting("LKeyX", &g_Config.fLKeyX, -1.0f, true, true), - ConfigSetting("LKeyY", &g_Config.fLKeyY, -1.0f, true, true), - ConfigSetting("LKeyScale", &g_Config.fLKeyScale, defaultControlScale, true, true), - ConfigSetting("RKeyX", &g_Config.fRKeyX, -1.0f, true, true), - ConfigSetting("RKeyY", &g_Config.fRKeyY, -1.0f, true, true), - ConfigSetting("RKeyScale", &g_Config.fRKeyScale, defaultControlScale, true, true), - ConfigSetting("AnalogStickX", &g_Config.fAnalogStickX, -1.0f, true, true), - ConfigSetting("AnalogStickY", &g_Config.fAnalogStickY, -1.0f, true, true), - ConfigSetting("AnalogStickScale", &g_Config.fAnalogStickScale, defaultControlScale, true, true), + ConfigSetting("StartKeyX", "StartKeyY", "StartKeyScale", "ShowTouchStart", &g_Config.touchStartKey, defaultTouchPosShow, true, true), + ConfigSetting("SelectKeyX", "SelectKeyY", "SelectKeyScale", "ShowTouchSelect", &g_Config.touchSelectKey, defaultTouchPosShow, true, true), + ConfigSetting("UnthrottleKeyX", "UnthrottleKeyY", "UnthrottleKeyScale", "ShowTouchUnthrottle", &g_Config.touchUnthrottleKey, defaultTouchPosShow, true, true), + ConfigSetting("LKeyX", "LKeyY", "LKeyScale", "ShowTouchLTrigger", &g_Config.touchLKey, defaultTouchPosShow, true, true), + ConfigSetting("RKeyX", "RKeyY", "RKeyScale", "ShowTouchRTrigger", &g_Config.touchRKey, defaultTouchPosShow, true, true), + ConfigSetting("AnalogStickX", "AnalogStickY", "AnalogStickScale", "ShowAnalogStick", &g_Config.touchAnalogStick, defaultTouchPosShow, true, true), + + ConfigSetting("fcombo0X", "fcombo0Y", "comboKeyScale0", "ShowComboKey0", &g_Config.touchCombo0, defaultTouchPosHide, true, true), + ConfigSetting("fcombo1X", "fcombo1Y", "comboKeyScale1", "ShowComboKey1", &g_Config.touchCombo1, defaultTouchPosHide, true, true), + ConfigSetting("fcombo2X", "fcombo2Y", "comboKeyScale2", "ShowComboKey2", &g_Config.touchCombo2, defaultTouchPosHide, true, true), + ConfigSetting("fcombo3X", "fcombo3Y", "comboKeyScale3", "ShowComboKey3", &g_Config.touchCombo3, defaultTouchPosHide, true, true), + ConfigSetting("fcombo4X", "fcombo4Y", "comboKeyScale4", "ShowComboKey4", &g_Config.touchCombo4, defaultTouchPosHide, true, true), + ConfigSetting("Speed1KeyX", "Speed1KeyY", "Speed1KeyScale", "ShowSpeed1Key", &g_Config.touchSpeed1Key, defaultTouchPosHide, true, true), + ConfigSetting("Speed2KeyX", "Speed2KeyY", "Speed2KeyScale", "ShowSpeed2Key", &g_Config.touchSpeed2Key, defaultTouchPosHide, true, true), - ConfigSetting("fcombo0X", &g_Config.fcombo0X, -1.0f, true, true), - ConfigSetting("fcombo0Y", &g_Config.fcombo0Y, -1.0f, true, true), - ConfigSetting("comboKeyScale0", &g_Config.fcomboScale0, defaultControlScale, true, true), - ConfigSetting("fcombo1X", &g_Config.fcombo1X, -1.0f, true, true), - ConfigSetting("fcombo1Y", &g_Config.fcombo1Y, -1.0f, true, true), - ConfigSetting("comboKeyScale1", &g_Config.fcomboScale1, defaultControlScale, true, true), - ConfigSetting("fcombo2X", &g_Config.fcombo2X, -1.0f, true, true), - ConfigSetting("fcombo2Y", &g_Config.fcombo2Y, -1.0f, true, true), - ConfigSetting("comboKeyScale2", &g_Config.fcomboScale2, defaultControlScale, true, true), - ConfigSetting("fcombo3X", &g_Config.fcombo3X, -1.0f, true, true), - ConfigSetting("fcombo3Y", &g_Config.fcombo3Y, -1.0f, true, true), - ConfigSetting("comboKeyScale3", &g_Config.fcomboScale3, defaultControlScale, true, true), - ConfigSetting("fcombo4X", &g_Config.fcombo4X, -1.0f, true, true), - ConfigSetting("fcombo4Y", &g_Config.fcombo4Y, -1.0f, true, true), - ConfigSetting("comboKeyScale4", &g_Config.fcomboScale4, defaultControlScale, true, true), #ifdef _WIN32 ConfigSetting("DInputAnalogDeadzone", &g_Config.fDInputAnalogDeadzone, 0.1f, true, true), ConfigSetting("DInputAnalogInverseMode", &g_Config.iDInputAnalogInverseMode, 0, true, true), @@ -791,7 +810,7 @@ static ConfigSetting systemParamSettings[] = { #endif ConfigSetting("WlanPowerSave", &g_Config.bWlanPowerSave, (bool) PSP_SYSTEMPARAM_WLAN_POWERSAVE_OFF, true, true), ReportedConfigSetting("EncryptSave", &g_Config.bEncryptSave, true, true, true), - ConfigSetting("SavedataUpgrade", &g_Config.bSavedataUpgrade, false, true, false), + ConfigSetting("SavedataUpgradeVersion", &g_Config.bSavedataUpgrade, true, true, false), ConfigSetting(false), }; @@ -1005,40 +1024,6 @@ void Config::Load(const char *iniFileName, const char *controllerIniFilename) { ResetControlLayout(); } - // MIGRATION: For users who had the old static touch layout, aren't I nice? - // We can probably kill this in 0.9.8 or something. - if (fDpadX > 1.0 || fDpadY > 1.0) { // Likely the rest are too! - float screen_width = dp_xres; - float screen_height = dp_yres; - - fActionButtonCenterX /= screen_width; - fActionButtonCenterY /= screen_height; - fDpadX /= screen_width; - fDpadY /= screen_height; - fStartKeyX /= screen_width; - fStartKeyY /= screen_height; - fSelectKeyX /= screen_width; - fSelectKeyY /= screen_height; - fUnthrottleKeyX /= screen_width; - fUnthrottleKeyY /= screen_height; - fLKeyX /= screen_width; - fLKeyY /= screen_height; - fRKeyX /= screen_width; - fRKeyY /= screen_height; - fAnalogStickX /= screen_width; - fAnalogStickY /= screen_height; - fcombo0X /= screen_width; - fcombo0Y /= screen_height; - fcombo1X /= screen_width; - fcombo1Y /= screen_height; - fcombo2X /= screen_width; - fcombo2Y /= screen_height; - fcombo3X /= screen_width; - fcombo3Y /= screen_height; - fcombo4X /= screen_width; - fcombo4Y /= screen_height; - } - const char *gitVer = PPSSPP_GIT_VERSION; Version installed(gitVer); Version upgrade(upgradeVersion); @@ -1189,13 +1174,13 @@ void Config::DownloadCompletedCallback(http::Download &download) { } JsonReader reader(data.c_str(), data.size()); - const json_value *root = reader.root(); + const JsonGet root = reader.root(); if (!root) { ERROR_LOG(LOADER, "Failed to parse json"); return; } - std::string version = root->getString("version", ""); + std::string version = root.getString("version", ""); const char *gitVer = PPSSPP_GIT_VERSION; Version installed(gitVer); @@ -1436,47 +1421,28 @@ void Config::LoadStandardControllerIni() { } void Config::ResetControlLayout() { - g_Config.fActionButtonScale = defaultControlScale; + auto reset = [](ConfigTouchPos &pos) { + pos.x = defaultTouchPosShow.x; + pos.y = defaultTouchPosShow.y; + pos.scale = defaultTouchPosShow.scale; + }; + reset(g_Config.touchActionButtonCenter); g_Config.fActionButtonSpacing = 1.0f; - g_Config.fActionButtonCenterX = -1.0; - g_Config.fActionButtonCenterY = -1.0; - g_Config.fDpadScale = defaultControlScale; + reset(g_Config.touchDpad); g_Config.fDpadSpacing = 1.0f; - g_Config.fDpadX = -1.0; - g_Config.fDpadY = -1.0; - g_Config.fStartKeyX = -1.0; - g_Config.fStartKeyY = -1.0; - g_Config.fStartKeyScale = defaultControlScale; - g_Config.fSelectKeyX = -1.0; - g_Config.fSelectKeyY = -1.0; - g_Config.fSelectKeyScale = defaultControlScale; - g_Config.fUnthrottleKeyX = -1.0; - g_Config.fUnthrottleKeyY = -1.0; - g_Config.fUnthrottleKeyScale = defaultControlScale; - g_Config.fLKeyX = -1.0; - g_Config.fLKeyY = -1.0; - g_Config.fLKeyScale = defaultControlScale; - g_Config.fRKeyX = -1.0; - g_Config.fRKeyY = -1.0; - g_Config.fRKeyScale = defaultControlScale; - g_Config.fAnalogStickX = -1.0; - g_Config.fAnalogStickY = -1.0; - g_Config.fAnalogStickScale = defaultControlScale; - g_Config.fcombo0X = -1.0; - g_Config.fcombo0Y = -1.0; - g_Config.fcomboScale0 = defaultControlScale; - g_Config.fcombo1X = -1.0f; - g_Config.fcombo1Y = -1.0f; - g_Config.fcomboScale1 = defaultControlScale; - g_Config.fcombo2X = -1.0f; - g_Config.fcombo2Y = -1.0f; - g_Config.fcomboScale2 = defaultControlScale; - g_Config.fcombo3X = -1.0f; - g_Config.fcombo3Y = -1.0f; - g_Config.fcomboScale3 = defaultControlScale; - g_Config.fcombo4X = -1.0f; - g_Config.fcombo4Y = -1.0f; - g_Config.fcomboScale4 = defaultControlScale; + reset(g_Config.touchStartKey); + reset(g_Config.touchSelectKey); + reset(g_Config.touchUnthrottleKey); + reset(g_Config.touchLKey); + reset(g_Config.touchRKey); + reset(g_Config.touchAnalogStick); + reset(g_Config.touchCombo0); + reset(g_Config.touchCombo1); + reset(g_Config.touchCombo2); + reset(g_Config.touchCombo3); + reset(g_Config.touchCombo4); + reset(g_Config.touchSpeed1Key); + reset(g_Config.touchSpeed2Key); } void Config::GetReportingInfo(UrlEncoder &data) { @@ -1487,3 +1453,7 @@ void Config::GetReportingInfo(UrlEncoder &data) { } } } + +bool Config::IsPortrait() const { + return (iInternalScreenRotation == ROTATION_LOCKED_VERTICAL || iInternalScreenRotation == ROTATION_LOCKED_VERTICAL180) && iRenderingMode != FB_NON_BUFFERED_MODE; +} diff --git a/Core/Config.h b/Core/Config.h index 55a42dba78..a69768a98e 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -25,53 +25,6 @@ extern const char *PPSSPP_GIT_VERSION; -const int MAX_CONFIG_VOLUME = 8; -const int PSP_MODEL_FAT = 0; -const int PSP_MODEL_SLIM = 1; -const int PSP_DEFAULT_FIRMWARE = 150; -static const s8 VOLUME_OFF = 0; -static const s8 VOLUME_MAX = 10; - -enum class CPUCore { - INTERPRETER = 0, - JIT = 1, - IR_JIT = 2, -}; - -enum { - ROTATION_AUTO = 0, - ROTATION_LOCKED_HORIZONTAL = 1, - ROTATION_LOCKED_VERTICAL = 2, - ROTATION_LOCKED_HORIZONTAL180 = 3, - ROTATION_LOCKED_VERTICAL180 = 4, -}; - -enum BufferFilter { - SCALE_LINEAR = 1, - SCALE_NEAREST = 2, -}; - -// Software is not among these because it will have one of these perform the blit to display. -enum class GPUBackend { - OPENGL = 0, - DIRECT3D9 = 1, - DIRECT3D11 = 2, - VULKAN = 3, -}; - -enum AudioBackendType { - AUDIO_BACKEND_AUTO, - AUDIO_BACKEND_DSOUND, - AUDIO_BACKEND_WASAPI, -}; - -// For iIOTimingMethod. -enum IOTimingMethods { - IOTIMING_FAST = 0, - IOTIMING_HOST = 1, - IOTIMING_REALISTIC = 2, -}; - namespace http { class Download; class Downloader; @@ -79,6 +32,14 @@ namespace http { struct UrlEncoder; +struct ConfigTouchPos { + float x; + float y; + float scale; + // Note: Show is not used for all settings. + bool show; +}; + struct Config { public: Config(); @@ -108,10 +69,12 @@ public: #if defined(USING_WIN_UI) bool bPauseOnLostFocus; bool bTopMost; - std::string sFont; bool bIgnoreWindowsKey; bool bRestartRequired; #endif +#if defined(USING_WIN_UI) || defined(USING_QT_UI) + std::string sFont; +#endif bool bPauseWhenMinimized; @@ -131,6 +94,7 @@ public: bool bForceLagSync; bool bFuncReplacements; bool bHideSlowWarnings; + bool bHideStateWarnings; bool bPreloadFunctions; bool bVulkanMultithreading; @@ -147,6 +111,7 @@ public: bool bRemoteISOManual; bool bRemoteShareOnStartup; std::string sRemoteISOSubdir; + bool bRemoteDebuggerOnStartup; bool bMemStickInserted; int iScreenRotation; // The rotation angle of the PPSSPP UI. Only supported on Android and possibly other mobile platforms. @@ -159,6 +124,12 @@ public: // GFX int iGPUBackend; + // We have separate device parameters for each backend so it doesn't get erased if you switch backends. + // If not set, will use the "best" device. + std::string sVulkanDevice; +#ifdef _WIN32 + std::string sD3D11Device; +#endif bool bSoftwareRendering; bool bHardwareTransform; // only used in the GLES backend bool bSoftwareSkinning; // may speed up some games @@ -201,19 +172,19 @@ public: bool bTrueColor; bool bReplaceTextures; bool bSaveNewTextures; - int iTexScalingLevel; // 1 = off, 2 = 2x, ..., 5 = 5x + int iTexScalingLevel; // 0 = auto, 1 = off, 2 = 2x, ..., 5 = 5x int iTexScalingType; // 0 = xBRZ, 1 = Hybrid bool bRealtimeTexScaling; bool bTexDeposterize; - int iFpsLimit; + int iFpsLimit1; + int iFpsLimit2; bool bRefreshAt60Hz; - bool bUnToggleFpsLimit; int iForceMaxEmulatedFPS; int iMaxRecent; int iCurrentStateSlot; int iRewindFlipFrequency; bool bEnableStateUndo; - bool bEnableAutoLoad; + int iAutoLoadSaveState; // 0 = off, 1 = oldest, 2 = newest, >2 = slot number + 3 bool bEnableCheats; bool bReloadCheats; int iCwCheatRefreshRate; @@ -312,46 +283,27 @@ public: //space between PSP buttons //the PSP button's center (triangle, circle, square, cross) - float fActionButtonCenterX, fActionButtonCenterY; - float fActionButtonScale; + ConfigTouchPos touchActionButtonCenter; float fActionButtonSpacing; //radius of the D-pad (PSP cross) // int iDpadRadius; //the D-pad (PSP cross) position - float fDpadX, fDpadY; - float fDpadScale; + ConfigTouchPos touchDpad; float fDpadSpacing; - //the start key position - float fStartKeyX, fStartKeyY; - float fStartKeyScale; - //the select key position; - float fSelectKeyX, fSelectKeyY; - float fSelectKeyScale; + ConfigTouchPos touchStartKey; + ConfigTouchPos touchSelectKey; + ConfigTouchPos touchUnthrottleKey; + ConfigTouchPos touchLKey; + ConfigTouchPos touchRKey; + ConfigTouchPos touchAnalogStick; - float fUnthrottleKeyX, fUnthrottleKeyY; - float fUnthrottleKeyScale; - - float fLKeyX, fLKeyY; - float fLKeyScale; - - float fRKeyX, fRKeyY; - float fRKeyScale; - - //position of the analog stick - float fAnalogStickX, fAnalogStickY; - float fAnalogStickScale; - - //the Combo Button position - float fcombo0X, fcombo0Y; - float fcomboScale0; - float fcombo1X, fcombo1Y; - float fcomboScale1; - float fcombo2X, fcombo2Y; - float fcomboScale2; - float fcombo3X, fcombo3Y; - float fcomboScale3; - float fcombo4X, fcombo4Y; - float fcomboScale4; + ConfigTouchPos touchCombo0; + ConfigTouchPos touchCombo1; + ConfigTouchPos touchCombo2; + ConfigTouchPos touchCombo3; + ConfigTouchPos touchCombo4; + ConfigTouchPos touchSpeed1Key; + ConfigTouchPos touchSpeed2Key; // Controls Visibility bool bShowTouchControls; @@ -361,23 +313,6 @@ public: bool bShowTouchTriangle; bool bShowTouchSquare; - bool bShowTouchStart; - bool bShowTouchSelect; - bool bShowTouchUnthrottle; - - bool bShowTouchLTrigger; - bool bShowTouchRTrigger; - - bool bShowTouchAnalogStick; - bool bShowTouchDpad; - - //Combo Button Visibility - bool bShowComboKey0; - bool bShowComboKey1; - bool bShowComboKey2; - bool bShowComboKey3; - bool bShowComboKey4; - // Combo_key mapping. These are bitfields. int iCombokey0; int iCombokey1; @@ -512,9 +447,7 @@ public: void GetReportingInfo(UrlEncoder &data); - bool IsPortrait() const { - return (iInternalScreenRotation == ROTATION_LOCKED_VERTICAL || iInternalScreenRotation == ROTATION_LOCKED_VERTICAL180) && iRenderingMode != 0; - } + bool IsPortrait() const; protected: void LoadStandardControllerIni(); diff --git a/Core/ConfigValues.h b/Core/ConfigValues.h new file mode 100644 index 0000000000..e714fa9b0c --- /dev/null +++ b/Core/ConfigValues.h @@ -0,0 +1,79 @@ +// Copyright (c) 2012- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include + +const int PSP_MODEL_FAT = 0; +const int PSP_MODEL_SLIM = 1; +const int PSP_DEFAULT_FIRMWARE = 660; +static const int8_t VOLUME_OFF = 0; +static const int8_t VOLUME_MAX = 10; + +enum class CPUCore { + INTERPRETER = 0, + JIT = 1, + IR_JIT = 2, +}; + +enum { + ROTATION_AUTO = 0, + ROTATION_LOCKED_HORIZONTAL = 1, + ROTATION_LOCKED_VERTICAL = 2, + ROTATION_LOCKED_HORIZONTAL180 = 3, + ROTATION_LOCKED_VERTICAL180 = 4, +}; + +enum BufferFilter { + SCALE_LINEAR = 1, + SCALE_NEAREST = 2, +}; + +// Software is not among these because it will have one of these perform the blit to display. +enum class GPUBackend { + OPENGL = 0, + DIRECT3D9 = 1, + DIRECT3D11 = 2, + VULKAN = 3, +}; + +enum AudioBackendType { + AUDIO_BACKEND_AUTO, + AUDIO_BACKEND_DSOUND, + AUDIO_BACKEND_WASAPI, +}; + +// For iIOTimingMethod. +enum IOTimingMethods { + IOTIMING_FAST = 0, + IOTIMING_HOST = 1, + IOTIMING_REALISTIC = 2, +}; + +enum class SmallDisplayZoom { + STRETCH = 0, + PARTIAL_STRETCH = 1, + AUTO = 2, + MANUAL = 3, +}; + +enum class AutoLoadSaveState { + OFF = 0, + OLDEST = 1, + NEWEST = 2, +}; diff --git a/Core/Core.cpp b/Core/Core.cpp index d549d79c4f..32db6ab451 100644 --- a/Core/Core.cpp +++ b/Core/Core.cpp @@ -28,22 +28,22 @@ #include "thread/threadutil.h" #include "profiler/profiler.h" +#include "Common/GraphicsContext.h" #include "Core/Core.h" #include "Core/Config.h" +#include "Core/Host.h" #include "Core/MemMap.h" #include "Core/SaveState.h" #include "Core/System.h" +#include "Core/Debugger/Breakpoints.h" #include "Core/MIPS/MIPS.h" -#include "Common/GraphicsContext.h" +#include "GPU/Debugger/Stepping.h" #ifdef _WIN32 #include "Common/CommonWindows.h" #include "Windows/InputDevice.h" #endif -#include "Host.h" - -#include "Core/Debugger/Breakpoints.h" // Time until we stop considering the core active without user input. // Should this be configurable? 2 hours currently. @@ -54,7 +54,9 @@ static std::mutex m_hStepMutex; static std::condition_variable m_InactiveCond; static std::mutex m_hInactiveMutex; static bool singleStepPending = false; -static std::set shutdownFuncs; +static int steppingCounter = 0; +static std::set lifecycleFuncs; +static std::set stopFuncs; static bool windowHidden = false; static double lastActivity = 0.0; static double lastKeepAwake = 0.0; @@ -75,30 +77,25 @@ void Core_NotifyActivity() { lastActivity = time_now_d(); } -void Core_ListenShutdown(Core_ShutdownFunc func) { - shutdownFuncs.insert(func); +void Core_ListenLifecycle(CoreLifecycleFunc func) { + lifecycleFuncs.insert(func); } -void Core_NotifyShutdown() { - for (auto it = shutdownFuncs.begin(); it != shutdownFuncs.end(); ++it) { - (*it)(); +void Core_NotifyLifecycle(CoreLifecycle stage) { + for (auto func : lifecycleFuncs) { + func(stage); } } -void Core_ErrorPause() { - Core_UpdateState(CORE_ERROR); -} - -void Core_Halt(const char *msg) { - Core_EnableStepping(true); - ERROR_LOG(CPU, "CPU HALTED : %s",msg); - _dbg_update_(); +void Core_ListenStopRequest(CoreStopRequestFunc func) { + stopFuncs.insert(func); } void Core_Stop() { Core_UpdateState(CORE_POWERDOWN); - Core_NotifyShutdown(); - m_StepCond.notify_one(); + for (auto func : stopFuncs) { + func(); + } } bool Core_IsStepping() { @@ -113,6 +110,14 @@ bool Core_IsInactive() { return coreState != CORE_RUNNING && coreState != CORE_NEXTFRAME && !coreStatePending; } +static inline void Core_StateProcessed() { + if (coreStatePending) { + std::lock_guard guard(m_hInactiveMutex); + coreStatePending = false; + m_InactiveCond.notify_all(); + } +} + void Core_WaitInactive() { while (Core_IsActive()) { std::unique_lock guard(m_hInactiveMutex); @@ -184,6 +189,7 @@ bool UpdateScreenScale(int width, int height) { return false; } +// Note: not used on Android. void UpdateRunLoop() { if (windowHidden && g_Config.bPauseWhenMinimized) { sleep_ms(16); @@ -202,6 +208,8 @@ void KeepScreenAwake() { void Core_RunLoop(GraphicsContext *ctx) { graphicsContext = ctx; while ((GetUIState() != UISTATE_INGAME || !PSP_IsInited()) && GetUIState() != UISTATE_EXIT) { + // In case it was pending, we're not in game anymore. We won't get to Core_Run(). + Core_StateProcessed(); time_update(); double startTime = time_now_d(); UpdateRunLoop(); @@ -217,7 +225,7 @@ void Core_RunLoop(GraphicsContext *ctx) { } } - while (!coreState && GetUIState() == UISTATE_INGAME) { + while ((coreState == CORE_RUNNING || coreState == CORE_STEPPING) && GetUIState() == UISTATE_INGAME) { time_update(); UpdateRunLoop(); if (!windowHidden && !Core_IsStepping()) { @@ -238,22 +246,63 @@ void Core_RunLoop(GraphicsContext *ctx) { } void Core_DoSingleStep() { + std::lock_guard guard(m_hStepMutex); singleStepPending = true; - m_StepCond.notify_one(); + m_StepCond.notify_all(); } void Core_UpdateSingleStep() { - m_StepCond.notify_one(); + std::lock_guard guard(m_hStepMutex); + m_StepCond.notify_all(); } void Core_SingleStep() { currentMIPS->SingleStep(); + if (coreState == CORE_STEPPING) + steppingCounter++; } -static inline void CoreStateProcessed() { - if (coreStatePending) { - coreStatePending = false; - m_InactiveCond.notify_one(); +static inline bool Core_WaitStepping() { + std::unique_lock guard(m_hStepMutex); + // We only wait 16ms so that we can still draw UI or react to events. + if (!singleStepPending && coreState == CORE_STEPPING) + m_StepCond.wait_for(guard, std::chrono::milliseconds(16)); + + bool result = singleStepPending; + singleStepPending = false; + return result; +} + +void Core_ProcessStepping() { + Core_StateProcessed(); + + // Check if there's any pending save state actions. + SaveState::Process(); + if (coreState != CORE_STEPPING) { + return; + } + + // Or any GPU actions. + GPUStepping::SingleStep(); + + // We're not inside jit now, so it's safe to clear the breakpoints. + static int lastSteppingCounter = -1; + if (lastSteppingCounter != steppingCounter) { + CBreakPoints::ClearTemporaryBreakPoints(); + host->UpdateDisassembly(); + host->UpdateMemView(); + lastSteppingCounter = steppingCounter; + } + + // Need to check inside the lock to avoid races. + bool doStep = Core_WaitStepping(); + + // We may still be stepping without singleStepPending to process a save state. + if (doStep && coreState == CORE_STEPPING) { + Core_SingleStep(); + // Update disasm dialog. + host->UpdateDisassembly(); + host->UpdateMemView(); } } @@ -262,9 +311,8 @@ static inline void CoreStateProcessed() { void Core_Run(GraphicsContext *ctx) { host->UpdateDisassembly(); while (true) { -reswitch: if (GetUIState() != UISTATE_INGAME) { - CoreStateProcessed(); + Core_StateProcessed(); if (GetUIState() == UISTATE_EXIT) { UpdateRunLoop(); return; @@ -275,51 +323,20 @@ reswitch: switch (coreState) { case CORE_RUNNING: + case CORE_STEPPING: // enter a fast runloop Core_RunLoop(ctx); - break; - - // We should never get here on Android. - case CORE_STEPPING: - singleStepPending = false; - CoreStateProcessed(); - - // Check if there's any pending savestate actions. - SaveState::Process(); if (coreState == CORE_POWERDOWN) { + Core_StateProcessed(); return; } - - // wait for step command.. - host->UpdateDisassembly(); - host->UpdateMemView(); - host->SendCoreWait(true); - - { - std::unique_lock guard(m_hStepMutex); - m_StepCond.wait(guard); - } - - host->SendCoreWait(false); - // No step pending? Let's go back to the wait. - if (!singleStepPending || coreState != CORE_STEPPING) { - if (coreState == CORE_POWERDOWN) { - return; - } - goto reswitch; - } - - Core_SingleStep(); - // update disasm dialog - host->UpdateDisassembly(); - host->UpdateMemView(); break; case CORE_POWERUP: case CORE_POWERDOWN: case CORE_ERROR: // Exit loop!! - CoreStateProcessed(); + Core_StateProcessed(); return; @@ -331,13 +348,17 @@ reswitch: void Core_EnableStepping(bool step) { if (step) { - sleep_ms(1); host->SetDebugMode(true); Core_UpdateState(CORE_STEPPING); + steppingCounter++; } else { host->SetDebugMode(false); coreState = CORE_RUNNING; coreStatePending = false; - m_StepCond.notify_one(); + m_StepCond.notify_all(); } } + +int Core_GetSteppingCounter() { + return steppingCounter; +} diff --git a/Core/Core.h b/Core/Core.h index 9adbd6356e..69749f0d70 100644 --- a/Core/Core.h +++ b/Core/Core.h @@ -27,26 +27,44 @@ void UpdateRunLoop(); void Core_Run(GraphicsContext *ctx); void Core_Stop(); -void Core_ErrorPause(); // For platforms that don't call Core_Run void Core_SetGraphicsContext(GraphicsContext *ctx); -void Core_RunRenderThreadFrame(); - // called from gui void Core_EnableStepping(bool step); void Core_DoSingleStep(); void Core_UpdateSingleStep(); +void Core_ProcessStepping(); +// Changes every time we enter stepping. +int Core_GetSteppingCounter(); -typedef void (* Core_ShutdownFunc)(); -void Core_ListenShutdown(Core_ShutdownFunc func); -void Core_NotifyShutdown(); -void Core_Halt(const char *msg); +enum class CoreLifecycle { + STARTING, + // Note: includes failure cases. Guaranteed call after STARTING. + START_COMPLETE, + STOPPING, + // Guaranteed call after STOPPING. + STOPPED, + + // Sometimes called for save states. Guaranteed sequence, and never during STARTING or STOPPING. + MEMORY_REINITING, + MEMORY_REINITED, +}; + +// Callback is called on the Emu thread. +typedef void (* CoreLifecycleFunc)(CoreLifecycle stage); +void Core_ListenLifecycle(CoreLifecycleFunc func); +void Core_NotifyLifecycle(CoreLifecycle stage); + +// Callback is executed on requesting thread. +typedef void (* CoreStopRequestFunc)(); +void Core_ListenStopRequest(CoreStopRequestFunc callback); bool Core_IsStepping(); bool Core_IsActive(); bool Core_IsInactive(); +// Warning: these currently work only on Windows. void Core_WaitInactive(); void Core_WaitInactive(int milliseconds); diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 209890e9f1..c0751c917a 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -184,6 +184,18 @@ + + + + + + + + + + + + @@ -511,6 +523,7 @@ AnySuitable + @@ -531,7 +544,21 @@ + + + + + + + + + + + + + + @@ -746,6 +773,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 2efbdbd29a..b398758157 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -70,6 +70,9 @@ {119ac973-e457-4025-9e1e-4fb34022ae23} + + {c21d9bb5-614d-451b-8c0b-3078b29122d8} + @@ -689,6 +692,45 @@ Core + + Core + + + Debugger + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + @@ -1268,6 +1310,51 @@ Core + + Core + + + Debugger + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + HLE\Kernel + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Core + diff --git a/Core/CoreParameter.h b/Core/CoreParameter.h index 9e6ec3ef18..736ecd9797 100644 --- a/Core/CoreParameter.h +++ b/Core/CoreParameter.h @@ -31,6 +31,12 @@ enum GPUCore { GPUCORE_VULKAN, }; +enum class FPSLimit { + NORMAL = 0, + CUSTOM1 = 1, + CUSTOM2 = 2, +}; + class FileLoader; class GraphicsContext; @@ -42,13 +48,13 @@ enum class CPUCore; // PSP_CoreParameter() struct CoreParameter { - CoreParameter() : thin3d(nullptr), collectEmuLog(0), unthrottle(false), fpsLimit(0), updateRecent(true), freezeNext(false), frozen(false), mountIsoLoader(nullptr) {} + CoreParameter() {} CPUCore cpuCore; GPUCore gpuCore; - GraphicsContext *graphicsContext; // TODO: Find a better place. - Draw::DrawContext *thin3d; + GraphicsContext *graphicsContext = nullptr; // TODO: Find a better place. + Draw::DrawContext *thin3d = nullptr; bool enableSound; // there aren't multiple sound cores. std::string fileToStart; @@ -56,9 +62,9 @@ struct CoreParameter { std::string mountRoot; // If non-empty, and fileToStart is an ELF or PBP, mount this as host0: / umd0:. std::string errorString; - bool startPaused; + bool startBreak; bool printfEmuLog; // writes "emulator:" logging to stdout - std::string *collectEmuLog; + std::string *collectEmuLog = nullptr; bool headLess; // Try to avoid messageboxes etc // Internal PSP resolution @@ -70,16 +76,16 @@ struct CoreParameter { int pixelHeight; // Can be modified at runtime. - bool unthrottle; - int fpsLimit; + bool unthrottle = false; + FPSLimit fpsLimit = FPSLimit::NORMAL; - bool updateRecent; + bool updateRecent = true; // Freeze-frame. For nvidia perfhud profiling. Developers only. - bool freezeNext; - bool frozen; + bool freezeNext = false; + bool frozen = false; - FileLoader *mountIsoLoader; + FileLoader *mountIsoLoader = nullptr; Compatibility compat; }; diff --git a/Core/CoreTiming.cpp b/Core/CoreTiming.cpp index 507de88457..f8b355e417 100644 --- a/Core/CoreTiming.cpp +++ b/Core/CoreTiming.cpp @@ -34,6 +34,7 @@ #include "Core/Reporting.h" #include "Common/ChunkFile.h" +static const int initialHz = 222000000; int CPU_HZ = 222000000; // is this really necessary? @@ -182,7 +183,7 @@ int RegisterEvent(const char *name, TimedCallback callback) void AntiCrashCallback(u64 userdata, int cyclesLate) { ERROR_LOG(SAVESTATE, "Savestate broken: an unregistered event was called."); - Core_Halt("invalid timing events"); + Core_EnableStepping(true); } void RestoreRegisterEvent(int event_type, const char *name, TimedCallback callback) @@ -211,6 +212,7 @@ void Init() lastGlobalTimeUs = 0; hasTsEvents = 0; mhzChangeCallbacks.clear(); + CPU_HZ = initialHz; } void Shutdown() diff --git a/Core/CwCheat.cpp b/Core/CwCheat.cpp index 71e6070399..5722145ef5 100644 --- a/Core/CwCheat.cpp +++ b/Core/CwCheat.cpp @@ -870,25 +870,25 @@ void CWCheatEngine::ExecuteOp(const CheatOperation &op, const CheatCode &cheat, break; case CheatOp::IfAddrEqual: - if (!TestIf(op, [](int a, int b) { return a == b; })) { + if (!TestIfAddr(op, [](int a, int b) { return a == b; })) { i += (size_t)op.ifAddrTypes.skip; } break; case CheatOp::IfAddrNotEqual: - if (!TestIf(op, [](int a, int b) { return a != b; })) { + if (!TestIfAddr(op, [](int a, int b) { return a != b; })) { i += (size_t)op.ifAddrTypes.skip; } break; case CheatOp::IfAddrLess: - if (!TestIf(op, [](int a, int b) { return a < b; })) { + if (!TestIfAddr(op, [](int a, int b) { return a < b; })) { i += (size_t)op.ifAddrTypes.skip; } break; case CheatOp::IfAddrGreater: - if (!TestIf(op, [](int a, int b) { return a > b; })) { + if (!TestIfAddr(op, [](int a, int b) { return a > b; })) { i += (size_t)op.ifAddrTypes.skip; } break; diff --git a/Core/Debugger/Breakpoints.cpp b/Core/Debugger/Breakpoints.cpp index 9f7a27d39f..dd6184d2ce 100644 --- a/Core/Debugger/Breakpoints.cpp +++ b/Core/Debugger/Breakpoints.cpp @@ -16,6 +16,7 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include +#include #include "Common/Log.h" #include "Core/Core.h" @@ -27,17 +28,14 @@ #include "Core/MIPS/JitCommon/JitCommon.h" #include "Core/CoreTiming.h" +static std::mutex breakPointsMutex_; std::vector CBreakPoints::breakPoints_; u32 CBreakPoints::breakSkipFirstAt_ = 0; u64 CBreakPoints::breakSkipFirstTicks_ = 0; +static std::mutex memCheckMutex_; std::vector CBreakPoints::memChecks_; std::vector CBreakPoints::cleanupMemChecks_; -MemCheck::MemCheck() -{ - numHits = 0; -} - void MemCheck::Log(u32 addr, bool write, int size, u32 pc) { if (result & BREAK_ACTION_LOG) { if (logFormat.empty()) { @@ -50,16 +48,21 @@ void MemCheck::Log(u32 addr, bool write, int size, u32 pc) { } } -BreakAction MemCheck::Action(u32 addr, bool write, int size, u32 pc) -{ +BreakAction MemCheck::Apply(u32 addr, bool write, int size, u32 pc) { int mask = write ? MEMCHECK_WRITE : MEMCHECK_READ; - if (cond & mask) - { + if (cond & mask) { ++numHits; + return result; + } + return BREAK_ACTION_IGNORE; +} + +BreakAction MemCheck::Action(u32 addr, bool write, int size, u32 pc) { + int mask = write ? MEMCHECK_WRITE : MEMCHECK_READ; + if (cond & mask) { Log(addr, write, size, pc); - if (result & BREAK_ACTION_PAUSE) - { + if (result & BREAK_ACTION_PAUSE) { Core_EnableStepping(true); host->SetDebugMode(true); } @@ -70,38 +73,46 @@ BreakAction MemCheck::Action(u32 addr, bool write, int size, u32 pc) return BREAK_ACTION_IGNORE; } -void MemCheck::JitBefore(u32 addr, bool write, int size, u32 pc) -{ +void MemCheck::JitBeforeApply(u32 addr, bool write, int size, u32 pc) { int mask = MEMCHECK_WRITE | MEMCHECK_WRITE_ONCHANGE; - if (write && (cond & mask) == mask) - { + if (write && (cond & mask) == mask) { lastAddr = addr; lastPC = pc; lastSize = size; + } else { + lastAddr = 0; + Apply(addr, write, size, pc); + } +} +void MemCheck::JitBeforeAction(u32 addr, bool write, int size, u32 pc) { + if (lastAddr) { // We have to break to find out if it changed. Core_EnableStepping(true); - } - else - { - lastAddr = 0; + } else { Action(addr, write, size, pc); } } -void MemCheck::JitCleanup() -{ +bool MemCheck::JitApplyChanged() { if (lastAddr == 0 || lastPC == 0) - return; + return false; // Here's the tricky part: would this have changed memory? // Note that it did not actually get written. bool changed = MIPSAnalyst::OpWouldChangeMemory(lastPC, lastAddr, lastSize); if (changed) - { ++numHits; + return changed; +} + +void MemCheck::JitCleanup(bool changed) +{ + if (lastAddr == 0 || lastPC == 0) + return; + + if (changed) Log(lastAddr, true, lastSize, lastPC); - } // Resume if it should not have gone to stepping, or if it did not change. if ((!(result & BREAK_ACTION_PAUSE) || !changed) && coreState == CORE_STEPPING) @@ -113,6 +124,7 @@ void MemCheck::JitCleanup() host->SetDebugMode(true); } +// Note: must lock while calling this. size_t CBreakPoints::FindBreakpoint(u32 addr, bool matchTemp, bool temp) { size_t found = INVALID_BREAKPOINT; @@ -145,12 +157,14 @@ size_t CBreakPoints::FindMemCheck(u32 start, u32 end) bool CBreakPoints::IsAddressBreakPoint(u32 addr) { + std::lock_guard guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr); return bp != INVALID_BREAKPOINT && breakPoints_[bp].result != BREAK_ACTION_IGNORE; } bool CBreakPoints::IsAddressBreakPoint(u32 addr, bool* enabled) { + std::lock_guard guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr); if (bp == INVALID_BREAKPOINT) return false; if (enabled != nullptr) @@ -160,12 +174,14 @@ bool CBreakPoints::IsAddressBreakPoint(u32 addr, bool* enabled) bool CBreakPoints::IsTempBreakPoint(u32 addr) { + std::lock_guard guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr, true, true); return bp != INVALID_BREAKPOINT; } bool CBreakPoints::RangeContainsBreakPoint(u32 addr, u32 size) { + std::lock_guard guard(breakPointsMutex_); const u32 end = addr + size; for (const auto &bp : breakPoints_) { @@ -178,6 +194,7 @@ bool CBreakPoints::RangeContainsBreakPoint(u32 addr, u32 size) void CBreakPoints::AddBreakPoint(u32 addr, bool temp) { + std::unique_lock guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr, true, temp); if (bp == INVALID_BREAKPOINT) { @@ -187,18 +204,21 @@ void CBreakPoints::AddBreakPoint(u32 addr, bool temp) pt.addr = addr; breakPoints_.push_back(pt); + guard.unlock(); Update(addr); } else if (!breakPoints_[bp].IsEnabled()) { breakPoints_[bp].result |= BREAK_ACTION_PAUSE; breakPoints_[bp].hasCond = false; + guard.unlock(); Update(addr); } } void CBreakPoints::RemoveBreakPoint(u32 addr) { + std::unique_lock guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr); if (bp != INVALID_BREAKPOINT) { @@ -209,12 +229,14 @@ void CBreakPoints::RemoveBreakPoint(u32 addr) if (bp != INVALID_BREAKPOINT) breakPoints_.erase(breakPoints_.begin() + bp); + guard.unlock(); Update(addr); } } void CBreakPoints::ChangeBreakPoint(u32 addr, bool status) { + std::unique_lock guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr); if (bp != INVALID_BREAKPOINT) { @@ -222,31 +244,38 @@ void CBreakPoints::ChangeBreakPoint(u32 addr, bool status) breakPoints_[bp].result |= BREAK_ACTION_PAUSE; else breakPoints_[bp].result = BreakAction(breakPoints_[bp].result & ~BREAK_ACTION_PAUSE); + + guard.unlock(); Update(addr); } } void CBreakPoints::ChangeBreakPoint(u32 addr, BreakAction result) { + std::unique_lock guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr); if (bp != INVALID_BREAKPOINT) { breakPoints_[bp].result = result; + guard.unlock(); Update(addr); } } void CBreakPoints::ClearAllBreakPoints() { + std::unique_lock guard(breakPointsMutex_); if (!breakPoints_.empty()) { breakPoints_.clear(); + guard.unlock(); Update(); } } void CBreakPoints::ClearTemporaryBreakPoints() { + std::unique_lock guard(breakPointsMutex_); if (breakPoints_.empty()) return; @@ -259,73 +288,85 @@ void CBreakPoints::ClearTemporaryBreakPoints() update = true; } } - + + guard.unlock(); if (update) Update(); } void CBreakPoints::ChangeBreakPointAddCond(u32 addr, const BreakPointCond &cond) { - size_t bp = FindBreakpoint(addr, true, false); + std::unique_lock guard(breakPointsMutex_); + size_t bp = FindBreakpoint(addr); if (bp != INVALID_BREAKPOINT) { breakPoints_[bp].hasCond = true; breakPoints_[bp].cond = cond; + guard.unlock(); Update(addr); } } void CBreakPoints::ChangeBreakPointRemoveCond(u32 addr) { - size_t bp = FindBreakpoint(addr, true, false); + std::unique_lock guard(breakPointsMutex_); + size_t bp = FindBreakpoint(addr); if (bp != INVALID_BREAKPOINT) { breakPoints_[bp].hasCond = false; + guard.unlock(); Update(addr); } } BreakPointCond *CBreakPoints::GetBreakPointCondition(u32 addr) { - size_t bp = FindBreakpoint(addr, true, false); + std::lock_guard guard(breakPointsMutex_); + size_t bp = FindBreakpoint(addr); if (bp != INVALID_BREAKPOINT && breakPoints_[bp].hasCond) return &breakPoints_[bp].cond; return NULL; } void CBreakPoints::ChangeBreakPointLogFormat(u32 addr, const std::string &fmt) { + std::unique_lock guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr, true, false); if (bp != INVALID_BREAKPOINT) { breakPoints_[bp].logFormat = fmt; + guard.unlock(); Update(addr); } } BreakAction CBreakPoints::ExecBreakPoint(u32 addr) { + std::unique_lock guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr, false); if (bp != INVALID_BREAKPOINT) { - if (breakPoints_[bp].hasCond) { + BreakPoint info = breakPoints_[bp]; + guard.unlock(); + + if (info.hasCond) { // Evaluate the breakpoint and abort if necessary. auto cond = CBreakPoints::GetBreakPointCondition(currentMIPS->pc); if (cond && !cond->Evaluate()) return BREAK_ACTION_IGNORE; } - if (breakPoints_[bp].result & BREAK_ACTION_LOG) { - if (breakPoints_[bp].logFormat.empty()) { + if (info.result & BREAK_ACTION_LOG) { + if (info.logFormat.empty()) { NOTICE_LOG(JIT, "BKP PC=%08x (%s)", addr, g_symbolMap->GetDescription(addr).c_str()); } else { std::string formatted; - CBreakPoints::EvaluateLogFormat(currentDebugMIPS, breakPoints_[bp].logFormat, formatted); + CBreakPoints::EvaluateLogFormat(currentDebugMIPS, info.logFormat, formatted); NOTICE_LOG(JIT, "BKP PC=%08x: %s", addr, formatted.c_str()); } } - if (breakPoints_[bp].result & BREAK_ACTION_PAUSE) { + if (info.result & BREAK_ACTION_PAUSE) { Core_EnableStepping(true); host->SetDebugMode(true); } - return breakPoints_[bp].result; + return info.result; } return BREAK_ACTION_IGNORE; @@ -333,6 +374,7 @@ BreakAction CBreakPoints::ExecBreakPoint(u32 addr) { void CBreakPoints::AddMemCheck(u32 start, u32 end, MemCheckCondition cond, BreakAction result) { + std::unique_lock guard(memCheckMutex_); // This will ruin any pending memchecks. cleanupMemChecks_.clear(); @@ -346,18 +388,21 @@ void CBreakPoints::AddMemCheck(u32 start, u32 end, MemCheckCondition cond, Break check.result = result; memChecks_.push_back(check); + guard.unlock(); Update(); } else { memChecks_[mc].cond = (MemCheckCondition)(memChecks_[mc].cond | cond); memChecks_[mc].result = (BreakAction)(memChecks_[mc].result | result); + guard.unlock(); Update(); } } void CBreakPoints::RemoveMemCheck(u32 start, u32 end) { + std::unique_lock guard(memCheckMutex_); // This will ruin any pending memchecks. cleanupMemChecks_.clear(); @@ -365,49 +410,73 @@ void CBreakPoints::RemoveMemCheck(u32 start, u32 end) if (mc != INVALID_MEMCHECK) { memChecks_.erase(memChecks_.begin() + mc); + guard.unlock(); Update(); } } void CBreakPoints::ChangeMemCheck(u32 start, u32 end, MemCheckCondition cond, BreakAction result) { + std::unique_lock guard(memCheckMutex_); size_t mc = FindMemCheck(start, end); if (mc != INVALID_MEMCHECK) { memChecks_[mc].cond = cond; memChecks_[mc].result = result; + guard.unlock(); Update(); } } void CBreakPoints::ClearAllMemChecks() { + std::unique_lock guard(memCheckMutex_); // This will ruin any pending memchecks. cleanupMemChecks_.clear(); if (!memChecks_.empty()) { memChecks_.clear(); + guard.unlock(); Update(); } } void CBreakPoints::ChangeMemCheckLogFormat(u32 start, u32 end, const std::string &fmt) { + std::unique_lock guard(memCheckMutex_); size_t mc = FindMemCheck(start, end); if (mc != INVALID_MEMCHECK) { memChecks_[mc].logFormat = fmt; + guard.unlock(); Update(); } } +bool CBreakPoints::GetMemCheck(u32 start, u32 end, MemCheck *check) { + std::lock_guard guard(memCheckMutex_); + size_t mc = FindMemCheck(start, end); + if (mc != INVALID_MEMCHECK) { + *check = memChecks_[mc]; + return true; + } + return false; +} + static inline u32 NotCached(u32 val) { // Remove the cached part of the address. return val & ~0x40000000; } -MemCheck *CBreakPoints::GetMemCheck(u32 address, int size) -{ +bool CBreakPoints::GetMemCheckInRange(u32 address, int size, MemCheck *check) { + std::lock_guard guard(memCheckMutex_); + auto result = GetMemCheckLocked(address, size); + if (result) + *check = *result; + return result != nullptr; +} + +MemCheck *CBreakPoints::GetMemCheckLocked(u32 address, int size) { std::vector::iterator iter; for (iter = memChecks_.begin(); iter != memChecks_.end(); ++iter) { @@ -430,9 +499,14 @@ MemCheck *CBreakPoints::GetMemCheck(u32 address, int size) BreakAction CBreakPoints::ExecMemCheck(u32 address, bool write, int size, u32 pc) { - auto check = GetMemCheck(address, size); - if (check) - return check->Action(address, write, size, pc); + std::unique_lock guard(memCheckMutex_); + auto check = GetMemCheckLocked(address, size); + if (check) { + check->Apply(address, write, size, pc); + auto copy = *check; + guard.unlock(); + return copy.Action(address, write, size, pc); + } return BREAK_ACTION_IGNORE; } @@ -448,15 +522,23 @@ BreakAction CBreakPoints::ExecOpMemCheck(u32 address, u32 pc) } bool write = MIPSAnalyst::IsOpMemoryWrite(pc); - auto check = GetMemCheck(address, size); + std::unique_lock guard(memCheckMutex_); + auto check = GetMemCheckLocked(address, size); if (check) { int mask = MEMCHECK_WRITE | MEMCHECK_WRITE_ONCHANGE; + bool apply = false; if (write && (check->cond & mask) == mask) { if (MIPSAnalyst::OpWouldChangeMemory(pc, address, size)) { - return check->Action(address, write, size, pc); + apply = true; } } else { - return check->Action(address, write, size, pc); + apply = true; + } + if (apply) { + check->Apply(address, write, size, pc); + auto copy = *check; + guard.unlock(); + return copy.Action(address, write, size, pc); } } return BREAK_ACTION_IGNORE; @@ -464,18 +546,28 @@ BreakAction CBreakPoints::ExecOpMemCheck(u32 address, u32 pc) void CBreakPoints::ExecMemCheckJitBefore(u32 address, bool write, int size, u32 pc) { - auto check = GetMemCheck(address, size); + std::unique_lock guard(memCheckMutex_); + auto check = GetMemCheckLocked(address, size); if (check) { - check->JitBefore(address, write, size, pc); + check->JitBeforeApply(address, write, size, pc); + auto copy = *check; + guard.unlock(); + copy.JitBeforeAction(address, write, size, pc); + guard.lock(); cleanupMemChecks_.push_back(check); } } void CBreakPoints::ExecMemCheckJitCleanup() { + std::unique_lock guard(memCheckMutex_); for (auto it = cleanupMemChecks_.begin(), end = cleanupMemChecks_.end(); it != end; ++it) { auto check = *it; - check->JitCleanup(); + bool changed = check->JitApplyChanged(); + auto copy = *check; + guard.unlock(); + copy.JitCleanup(changed); + guard.lock(); } cleanupMemChecks_.clear(); } @@ -493,17 +585,21 @@ u32 CBreakPoints::CheckSkipFirst() return 0; } -const std::vector CBreakPoints::GetMemCheckRanges() -{ +const std::vector CBreakPoints::GetMemCheckRanges(bool write) { + std::lock_guard guard(memCheckMutex_); std::vector ranges = memChecks_; - for (auto it = memChecks_.begin(), end = memChecks_.end(); it != end; ++it) - { - MemCheck check = *it; + for (const auto &check : memChecks_) { + if (!(check.cond & MEMCHECK_READ) && !write) + continue; + if (!(check.cond & MEMCHECK_WRITE) && write) + continue; + + MemCheck copy = check; // Toggle the cached part of the address. - check.start ^= 0x40000000; - if (check.end != 0) - check.end ^= 0x40000000; - ranges.push_back(check); + copy.start ^= 0x40000000; + if (copy.end != 0) + copy.end ^= 0x40000000; + ranges.push_back(copy); } return ranges; @@ -511,16 +607,19 @@ const std::vector CBreakPoints::GetMemCheckRanges() const std::vector CBreakPoints::GetMemChecks() { + std::lock_guard guard(memCheckMutex_); return memChecks_; } const std::vector CBreakPoints::GetBreakpoints() { + std::lock_guard guard(breakPointsMutex_); return breakPoints_; } bool CBreakPoints::HasMemChecks() { + std::lock_guard guard(memCheckMutex_); return !memChecks_.empty(); } diff --git a/Core/Debugger/Breakpoints.h b/Core/Debugger/Breakpoints.h index 934667914a..bd3dd670e4 100644 --- a/Core/Debugger/Breakpoints.h +++ b/Core/Debugger/Breakpoints.h @@ -21,8 +21,7 @@ #include "Core/Debugger/DebugInterface.h" -enum BreakAction -{ +enum BreakAction { BREAK_ACTION_IGNORE = 0x00, BREAK_ACTION_LOG = 0x01, BREAK_ACTION_PAUSE = 0x02, @@ -37,35 +36,27 @@ static inline BreakAction operator | (const BreakAction &lhs, const BreakAction return BreakAction((u32)lhs | (u32)rhs); } -struct BreakPointCond -{ - DebugInterface *debug; +struct BreakPointCond { + DebugInterface *debug = nullptr; PostfixExpression expression; std::string expressionString; - BreakPointCond() : debug(nullptr) - { - } - - u32 Evaluate() - { + u32 Evaluate() { u32 result; - if (debug->parseExpression(expression,result) == false) return 0; + if (debug->parseExpression(expression, result) == false) + return 0; return result; } }; -struct BreakPoint -{ - BreakPoint() : hasCond(false) {} - +struct BreakPoint { u32 addr; bool temporary; - BreakAction result; + BreakAction result = BREAK_ACTION_IGNORE; std::string logFormat; - bool hasCond; + bool hasCond = false; BreakPointCond cond; bool IsEnabled() const { @@ -80,8 +71,7 @@ struct BreakPoint } }; -enum MemCheckCondition -{ +enum MemCheckCondition { MEMCHECK_READ = 0x01, MEMCHECK_WRITE = 0x02, MEMCHECK_WRITE_ONCHANGE = 0x04, @@ -89,25 +79,28 @@ enum MemCheckCondition MEMCHECK_READWRITE = 0x03, }; -struct MemCheck -{ - MemCheck(); +struct MemCheck { u32 start; u32 end; - MemCheckCondition cond; - BreakAction result; + MemCheckCondition cond = MEMCHECK_READ; + BreakAction result = BREAK_ACTION_IGNORE; std::string logFormat; - u32 numHits; + u32 numHits = 0; - u32 lastPC; - u32 lastAddr; - int lastSize; + u32 lastPC = 0; + u32 lastAddr = 0; + int lastSize = 0; + // Called on the stored memcheck (affects numHits, etc.) + BreakAction Apply(u32 addr, bool write, int size, u32 pc); + // Called on a copy. BreakAction Action(u32 addr, bool write, int size, u32 pc); - void JitBefore(u32 addr, bool write, int size, u32 pc); - void JitCleanup(); + void JitBeforeApply(u32 addr, bool write, int size, u32 pc); + void JitBeforeAction(u32 addr, bool write, int size, u32 pc); + bool JitApplyChanged(); + void JitCleanup(bool changed); void Log(u32 addr, bool write, int size, u32 pc); @@ -140,7 +133,7 @@ public: static void ClearAllBreakPoints(); static void ClearTemporaryBreakPoints(); - // Makes a copy. Temporary breakpoints can't have conditions. + // Makes a copy of the condition. static void ChangeBreakPointAddCond(u32 addr, const BreakPointCond &cond); static void ChangeBreakPointRemoveCond(u32 addr); static BreakPointCond *GetBreakPointCondition(u32 addr); @@ -156,7 +149,8 @@ public: static void ChangeMemCheckLogFormat(u32 start, u32 end, const std::string &fmt); - static MemCheck *GetMemCheck(u32 address, int size); + static bool GetMemCheck(u32 start, u32 end, MemCheck *check); + static bool GetMemCheckInRange(u32 address, int size, MemCheck *check); static BreakAction ExecMemCheck(u32 address, bool write, int size, u32 pc); static BreakAction ExecOpMemCheck(u32 address, u32 pc); @@ -168,7 +162,7 @@ public: static u32 CheckSkipFirst(); // Includes uncached addresses. - static const std::vector GetMemCheckRanges(); + static const std::vector GetMemCheckRanges(bool write); static const std::vector GetMemChecks(); static const std::vector GetBreakpoints(); @@ -184,6 +178,7 @@ private: static size_t FindBreakpoint(u32 addr, bool matchTemp = false, bool temp = false); // Finds exactly, not using a range check. static size_t FindMemCheck(u32 start, u32 end); + static MemCheck *GetMemCheckLocked(u32 address, int size); static std::vector breakPoints_; static u32 breakSkipFirstAt_; diff --git a/Core/Debugger/DisassemblyManager.cpp b/Core/Debugger/DisassemblyManager.cpp index 4ff4c58fd3..b04c40cc16 100644 --- a/Core/Debugger/DisassemblyManager.cpp +++ b/Core/Debugger/DisassemblyManager.cpp @@ -31,6 +31,7 @@ #include "Core/Debugger/DisassemblyManager.h" std::map DisassemblyManager::entries; +std::recursive_mutex DisassemblyManager::entriesLock_; DebugInterface* DisassemblyManager::cpu; int DisassemblyManager::maxParamChars = 29; @@ -149,7 +150,9 @@ void DisassemblyManager::analyze(u32 address, u32 size = 1024) if (!PSP_IsInited()) return; - auto it = findDisassemblyEntry(entries,address,false); + auto memLock = Memory::Lock(); + std::lock_guard guard(entriesLock_); + auto it = findDisassemblyEntry(entries, address, false); if (it != entries.end()) { DisassemblyEntry* entry = it->second; @@ -219,7 +222,8 @@ void DisassemblyManager::analyze(u32 address, u32 size = 1024) std::vector DisassemblyManager::getBranchLines(u32 start, u32 size) { std::vector result; - + + std::lock_guard guard(entriesLock_); auto it = findDisassemblyEntry(entries,start,false); if (it != entries.end()) { @@ -233,40 +237,44 @@ std::vector DisassemblyManager::getBranchLines(u32 start, u32 size) return result; } -void DisassemblyManager::getLine(u32 address, bool insertSymbols, DisassemblyLineInfo& dest) +void DisassemblyManager::getLine(u32 address, bool insertSymbols, DisassemblyLineInfo &dest, DebugInterface *cpuDebug) { + // This is here really to avoid lock ordering issues. + auto memLock = Memory::Lock(); + std::lock_guard guard(entriesLock_); auto it = findDisassemblyEntry(entries,address,false); if (it == entries.end()) { analyze(address); it = findDisassemblyEntry(entries,address,false); - - if (it == entries.end()) - { - if (address % 4) - dest.totalSize = ((address+3) & ~3)-address; - else - dest.totalSize = 4; - dest.name = "ERROR"; - dest.params = "Disassembly failure"; - return; - } } - DisassemblyEntry* entry = it->second; - if (entry->disassemble(address,dest,insertSymbols)) - return; - + if (it != entries.end()) { + DisassemblyEntry *entry = it->second; + if (entry->disassemble(address, dest, insertSymbols, cpuDebug)) + return; + } + + dest.type = DISTYPE_OTHER; + memset(&dest.info, 0, sizeof(dest.info)); + dest.info.opcodeAddress = address; if (address % 4) dest.totalSize = ((address+3) & ~3)-address; else dest.totalSize = 4; - dest.name = "ERROR"; - dest.params = "Disassembly failure"; + if (Memory::IsValidRange(address, 4)) { + dest.name = "ERROR"; + dest.params = "Disassembly failure"; + } else { + dest.name = "-"; + dest.params = ""; + } } u32 DisassemblyManager::getStartAddress(u32 address) { + auto memLock = Memory::Lock(); + std::lock_guard guard(entriesLock_); auto it = findDisassemblyEntry(entries,address,false); if (it == entries.end()) { @@ -283,6 +291,8 @@ u32 DisassemblyManager::getStartAddress(u32 address) u32 DisassemblyManager::getNthPreviousAddress(u32 address, int n) { + auto memLock = Memory::Lock(); + std::lock_guard guard(entriesLock_); while (Memory::IsValidAddress(address)) { auto it = findDisassemblyEntry(entries,address,false); @@ -311,6 +321,8 @@ u32 DisassemblyManager::getNthPreviousAddress(u32 address, int n) u32 DisassemblyManager::getNthNextAddress(u32 address, int n) { + auto memLock = Memory::Lock(); + std::lock_guard guard(entriesLock_); while (Memory::IsValidAddress(address)) { auto it = findDisassemblyEntry(entries,address,false); @@ -340,11 +352,12 @@ u32 DisassemblyManager::getNthNextAddress(u32 address, int n) } DisassemblyManager::~DisassemblyManager() { - clear(); } void DisassemblyManager::clear() { + auto memLock = Memory::Lock(); + std::lock_guard guard(entriesLock_); for (auto it = entries.begin(); it != entries.end(); it++) { delete it->second; @@ -383,11 +396,13 @@ void DisassemblyFunction::recheck() int DisassemblyFunction::getNumLines() { + std::lock_guard guard(lock_); return (int) lineAddresses.size(); } int DisassemblyFunction::getLineNum(u32 address, bool findStart) { + std::lock_guard guard(lock_); if (findStart) { int last = (int)lineAddresses.size() - 1; @@ -418,22 +433,25 @@ int DisassemblyFunction::getLineNum(u32 address, bool findStart) u32 DisassemblyFunction::getLineAddress(int line) { + std::lock_guard guard(lock_); return lineAddresses[line]; } -bool DisassemblyFunction::disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols) +bool DisassemblyFunction::disassemble(u32 address, DisassemblyLineInfo &dest, bool insertSymbols, DebugInterface *cpuDebug) { + std::lock_guard guard(lock_); auto it = findDisassemblyEntry(entries,address,false); if (it == entries.end()) return false; - return it->second->disassemble(address,dest,insertSymbols); + return it->second->disassemble(address, dest, insertSymbols, cpuDebug); } void DisassemblyFunction::getBranchLines(u32 start, u32 size, std::vector& dest) { u32 end = start+size; + std::lock_guard guard(lock_); for (size_t i = 0; i < lines.size(); i++) { BranchLine& line = lines[i]; @@ -466,17 +484,19 @@ void DisassemblyFunction::generateBranchLines() u32 end = address+size; + std::lock_guard guard(lock_); DebugInterface* cpu = DisassemblyManager::getCpu(); for (u32 funcPos = address; funcPos < end; funcPos += 4) { MIPSAnalyst::MipsOpcodeInfo opInfo = MIPSAnalyst::GetOpcodeInfo(cpu,funcPos); bool inFunction = (opInfo.branchTarget >= address && opInfo.branchTarget < end); - if (opInfo.isBranch && !opInfo.isBranchToRegister && !opInfo.isLinkedBranch && inFunction) - { + if (opInfo.isBranch && !opInfo.isBranchToRegister && !opInfo.isLinkedBranch && inFunction) { + if (!Memory::IsValidAddress(opInfo.branchTarget)) + continue; + BranchLine line; - if (opInfo.branchTarget < funcPos) - { + if (opInfo.branchTarget < funcPos) { line.first = opInfo.branchTarget; line.second = funcPos; line.type = LINE_UP; @@ -511,7 +531,8 @@ void DisassemblyFunction::generateBranchLines() if (lane == -1) { - // error + // Let's just pile on. + lines[i].laneIndex = 15; continue; } @@ -524,6 +545,7 @@ void DisassemblyFunction::generateBranchLines() void DisassemblyFunction::addOpcodeSequence(u32 start, u32 end) { DisassemblyOpcode* opcode = new DisassemblyOpcode(start,(end-start)/4); + std::lock_guard guard(lock_); entries[start] = opcode; for (u32 pos = start; pos < end; pos += 4) { @@ -537,18 +559,21 @@ void DisassemblyFunction::load() // gather all branch targets std::set branchTargets; - for (size_t i = 0; i < lines.size(); i++) { - switch (lines[i].type) + std::lock_guard guard(lock_); + for (size_t i = 0; i < lines.size(); i++) { - case LINE_DOWN: - branchTargets.insert(lines[i].second); - break; - case LINE_UP: - branchTargets.insert(lines[i].first); - break; - default: - break; + switch (lines[i].type) + { + case LINE_DOWN: + branchTargets.insert(lines[i].second); + break; + case LINE_UP: + branchTargets.insert(lines[i].first); + break; + default: + break; + } } } @@ -566,6 +591,7 @@ void DisassemblyFunction::load() addOpcodeSequence(opcodeSequenceStart,funcPos); DisassemblyData* data = new DisassemblyData(funcPos,g_symbolMap->GetDataSize(funcPos),g_symbolMap->GetDataType(funcPos)); + std::lock_guard guard(lock_); entries[funcPos] = data; lineAddresses.push_back(funcPos); funcPos += data->getTotalSize(); @@ -581,6 +607,7 @@ void DisassemblyFunction::load() u32 nextPos = (funcPos+3) & ~3; DisassemblyComment* comment = new DisassemblyComment(funcPos,nextPos-funcPos,".align","4"); + std::lock_guard guard(lock_); entries[funcPos] = comment; lineAddresses.push_back(funcPos); @@ -665,6 +692,7 @@ void DisassemblyFunction::load() if (opcodeSequenceStart != opAddress) addOpcodeSequence(opcodeSequenceStart,opAddress); + std::lock_guard guard(lock_); entries[opAddress] = macro; for (int i = 0; i < macro->getNumLines(); i++) { @@ -686,6 +714,7 @@ void DisassemblyFunction::load() void DisassemblyFunction::clear() { + std::lock_guard guard(lock_); for (auto it = entries.begin(); it != entries.end(); it++) { delete it->second; @@ -697,16 +726,19 @@ void DisassemblyFunction::clear() hash = 0; } -bool DisassemblyOpcode::disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols) +bool DisassemblyOpcode::disassemble(u32 address, DisassemblyLineInfo &dest, bool insertSymbols, DebugInterface *cpuDebug) { + if (!cpuDebug) + cpuDebug = DisassemblyManager::getCpu(); + char opcode[64],arguments[256]; - const char *dizz = DisassemblyManager::getCpu()->disasm(address,4); + const char *dizz = cpuDebug->disasm(address, 4); parseDisasm(dizz,opcode,arguments,insertSymbols); dest.type = DISTYPE_OPCODE; dest.name = opcode; dest.params = arguments; dest.totalSize = 4; - dest.info = MIPSAnalyst::GetOpcodeInfo(DisassemblyManager::getCpu(),address); + dest.info = MIPSAnalyst::GetOpcodeInfo(cpuDebug, address); return true; } @@ -725,13 +757,14 @@ void DisassemblyOpcode::getBranchLines(u32 start, u32 size, std::vectorGetLabelString(immediate); - if (!addressSymbol.empty() && insertSymbols) - { - sprintf(buffer,"%s,%s",DisassemblyManager::getCpu()->GetRegName(0,rt),addressSymbol.c_str()); + if (!addressSymbol.empty() && insertSymbols) { + sprintf(buffer, "%s,%s", cpuDebug->GetRegName(0, rt), addressSymbol.c_str()); } else { - sprintf(buffer,"%s,0x%08X",DisassemblyManager::getCpu()->GetRegName(0,rt),immediate); + sprintf(buffer, "%s,0x%08X", cpuDebug->GetRegName(0, rt), immediate); } dest.params = buffer; @@ -795,11 +830,10 @@ bool DisassemblyMacro::disassemble(u32 address, DisassemblyLineInfo& dest, bool dest.name = name; addressSymbol = g_symbolMap->GetLabelString(immediate); - if (!addressSymbol.empty() && insertSymbols) - { - sprintf(buffer,"%s,%s",DisassemblyManager::getCpu()->GetRegName(0,rt),addressSymbol.c_str()); + if (!addressSymbol.empty() && insertSymbols) { + sprintf(buffer, "%s,%s", cpuDebug->GetRegName(0, rt), addressSymbol.c_str()); } else { - sprintf(buffer,"%s,0x%08X",DisassemblyManager::getCpu()->GetRegName(0,rt),immediate); + sprintf(buffer, "%s,0x%08X", cpuDebug->GetRegName(0, rt), immediate); } dest.params = buffer; @@ -844,7 +878,7 @@ void DisassemblyData::recheck() } } -bool DisassemblyData::disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols) +bool DisassemblyData::disassemble(u32 address, DisassemblyLineInfo &dest, bool insertSymbols, DebugInterface *cpuDebug) { dest.type = DISTYPE_DATA; @@ -866,6 +900,7 @@ bool DisassemblyData::disassemble(u32 address, DisassemblyLineInfo& dest, bool i return false; } + std::lock_guard guard(lock_); auto it = lines.find(address); if (it == lines.end()) return false; @@ -877,6 +912,7 @@ bool DisassemblyData::disassemble(u32 address, DisassemblyLineInfo& dest, bool i int DisassemblyData::getLineNum(u32 address, bool findStart) { + std::lock_guard guard(lock_); auto it = lines.upper_bound(address); if (it != lines.end()) { @@ -891,6 +927,7 @@ int DisassemblyData::getLineNum(u32 address, bool findStart) void DisassemblyData::createLines() { + std::lock_guard guard(lock_); lines.clear(); lineAddresses.clear(); @@ -1039,7 +1076,7 @@ DisassemblyComment::DisassemblyComment(u32 _address, u32 _size, std::string _nam } -bool DisassemblyComment::disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols) +bool DisassemblyComment::disassemble(u32 address, DisassemblyLineInfo &dest, bool insertSymbols, DebugInterface *cpuDebug) { dest.type = DISTYPE_OTHER; dest.name = name; diff --git a/Core/Debugger/DisassemblyManager.h b/Core/Debugger/DisassemblyManager.h index 9002f1f8c1..44ae397f6c 100644 --- a/Core/Debugger/DisassemblyManager.h +++ b/Core/Debugger/DisassemblyManager.h @@ -17,6 +17,7 @@ #pragma once +#include #include "Common/CommonTypes.h" #include "Core/Debugger/SymbolMap.h" #include "Core/MIPS/MIPSAnalyst.h" @@ -62,7 +63,7 @@ public: virtual int getLineNum(u32 address, bool findStart) = 0; virtual u32 getLineAddress(int line) = 0; virtual u32 getTotalSize() = 0; - virtual bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols) = 0; + virtual bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols, DebugInterface *cpuDebug) = 0; virtual void getBranchLines(u32 start, u32 size, std::vector& dest) { }; }; @@ -76,7 +77,7 @@ public: int getLineNum(u32 address, bool findStart) override; u32 getLineAddress(int line) override; u32 getTotalSize() override { return size; }; - bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols) override; + bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols, DebugInterface *cpuDebug) override; void getBranchLines(u32 start, u32 size, std::vector& dest) override; private: @@ -91,6 +92,7 @@ private: std::vector lines; std::map entries; std::vector lineAddresses; + std::recursive_mutex lock_; }; class DisassemblyOpcode: public DisassemblyEntry @@ -103,7 +105,7 @@ public: int getLineNum(u32 address, bool findStart) override { return (address - this->address) / 4; }; u32 getLineAddress(int line) override { return address + line * 4; }; u32 getTotalSize() override { return num * 4; }; - bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols) override; + bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols, DebugInterface *cpuDebug) override; void getBranchLines(u32 start, u32 size, std::vector& dest) override; private: @@ -126,7 +128,7 @@ public: int getLineNum(u32 address, bool findStart) override { return 0; }; u32 getLineAddress(int line) override { return address; }; u32 getTotalSize() override { return numOpcodes * 4; }; - bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols) override; + bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols, DebugInterface *cpuDebug) override; private: enum MacroType { MACRO_LI, MACRO_MEMORYIMM }; @@ -151,7 +153,7 @@ public: int getLineNum(u32 address, bool findStart) override; u32 getLineAddress(int line) override { return lineAddresses[line]; }; u32 getTotalSize() override { return size; }; - bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols) override; + bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols, DebugInterface *cpuDebug) override; private: void createLines(); @@ -169,6 +171,7 @@ private: DataType type; std::map lines; std::vector lineAddresses; + std::recursive_mutex lock_; }; class DisassemblyComment: public DisassemblyEntry @@ -182,7 +185,7 @@ public: int getLineNum(u32 address, bool findStart) override { return 0; }; u32 getLineAddress(int line) override { return address; }; u32 getTotalSize() override { return size; }; - bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols) override; + bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols, DebugInterface *cpuDebug) override; private: u32 address; @@ -202,7 +205,7 @@ public: void setCpu(DebugInterface* _cpu) { cpu = _cpu; }; void setMaxParamChars(int num) { maxParamChars = num; clear(); }; - void getLine(u32 address, bool insertSymbols, DisassemblyLineInfo& dest); + void getLine(u32 address, bool insertSymbols, DisassemblyLineInfo &dest, DebugInterface *cpuDebug = nullptr); void analyze(u32 address, u32 size); std::vector getBranchLines(u32 start, u32 size); @@ -214,6 +217,7 @@ public: static int getMaxParamChars() { return maxParamChars; }; private: static std::map entries; + static std::recursive_mutex entriesLock_; static DebugInterface* cpu; static int maxParamChars; }; diff --git a/Core/Debugger/SymbolMap.cpp b/Core/Debugger/SymbolMap.cpp index b369c014ce..5786898624 100644 --- a/Core/Debugger/SymbolMap.cpp +++ b/Core/Debugger/SymbolMap.cpp @@ -29,6 +29,7 @@ #endif #include +#include #include "util/text/utf8.h" #include "zlib.h" diff --git a/Core/Debugger/WebSocket.cpp b/Core/Debugger/WebSocket.cpp new file mode 100644 index 0000000000..4504c827f7 --- /dev/null +++ b/Core/Debugger/WebSocket.cpp @@ -0,0 +1,211 @@ +// Copyright (c) 2017- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include +#include +#include "thread/threadutil.h" +#include "Core/Debugger/WebSocket.h" +#include "Core/Debugger/WebSocket/WebSocketUtils.h" +#include "Core/MemMap.h" + +// This WebSocket (connected through the same port as disc sharing) allows API/debugger access to PPSSPP. +// Currently, the only subprotocol "debugger.ppsspp.org" uses a simple JSON based interface. +// +// Messages to and from PPSSPP follow the same basic format: +// { "event": "NAME", ... } +// +// And are primarily of these types: +// * Events from the debugger/client (you) to PPSSPP +// If there's a response, it will generally use the same name. It may not be immedate - it's an event. +// * Spontaneous events from PPSSPP +// Things like logs, breakpoint hits, etc. not directly requested. +// +// Otherwise you may see error events which indicate PPSSPP couldn't understand or failed internally: +// - "event": "error" +// - "message": A string describing what happened. +// - "level": Integer severity level. (1 = NOTICE, 2 = ERROR, 3 = WARN, 4 = INFO, 5 = DEBUG, 6 = VERBOSE) +// - "ticket": Optional, present if in response to an event with a "ticket" field, simply repeats that value. +// +// At start, please send a "version" event. See WebSocket/GameSubscriber.cpp for more details. +// +// For other events, look inside Core/Debugger/WebSocket/ for details on each event. + +#include "Core/Debugger/WebSocket/GameBroadcaster.h" +#include "Core/Debugger/WebSocket/LogBroadcaster.h" +#include "Core/Debugger/WebSocket/SteppingBroadcaster.h" + +#include "Core/Debugger/WebSocket/BreakpointSubscriber.h" +#include "Core/Debugger/WebSocket/CPUCoreSubscriber.h" +#include "Core/Debugger/WebSocket/DisasmSubscriber.h" +#include "Core/Debugger/WebSocket/GameSubscriber.h" +#include "Core/Debugger/WebSocket/GPUBufferSubscriber.h" +#include "Core/Debugger/WebSocket/HLESubscriber.h" +#include "Core/Debugger/WebSocket/SteppingSubscriber.h" + +typedef void *(*SubscriberInit)(DebuggerEventHandlerMap &map); +typedef void (*Subscribershutdown)(void *p); +struct SubscriberInfo { + SubscriberInit init; + Subscribershutdown shutdown; +}; + +static const std::vector subscribers({ + { &WebSocketBreakpointInit, nullptr }, + { &WebSocketCPUCoreInit, nullptr }, + { &WebSocketDisasmInit, &WebSocketDisasmShutdown }, + { &WebSocketGameInit, nullptr }, + { &WebSocketGPUBufferInit, nullptr }, + { &WebSocketHLEInit, nullptr }, + { &WebSocketSteppingInit, &WebSocketSteppingShutdown }, +}); + +// To handle webserver restart, keep track of how many running. +static volatile int debuggersConnected = 0; +static volatile bool stopRequested = false; +static std::mutex stopLock; +static std::condition_variable stopCond; + +// Prevent threading surprises and obscure crashes by locking startup/shutdown. +static bool lifecycleLockSetup = false; +static std::mutex lifecycleLock; + +static void UpdateConnected(int delta) { + std::lock_guard guard(stopLock); + debuggersConnected += delta; + stopCond.notify_all(); +} + +static void WebSocketNotifyLifecycle(CoreLifecycle stage) { + switch (stage) { + case CoreLifecycle::STARTING: + case CoreLifecycle::STOPPING: + case CoreLifecycle::MEMORY_REINITING: + if (debuggersConnected > 0) { + DEBUG_LOG(SYSTEM, "Waiting for debugger to complete on shutdown"); + } + lifecycleLock.lock(); + break; + + case CoreLifecycle::START_COMPLETE: + case CoreLifecycle::STOPPED: + case CoreLifecycle::MEMORY_REINITED: + lifecycleLock.unlock(); + if (debuggersConnected > 0) { + DEBUG_LOG(SYSTEM, "Debugger ready for shutdown"); + } + break; + } +} + +static void SetupDebuggerLock() { + if (!lifecycleLockSetup) { + Core_ListenLifecycle(&WebSocketNotifyLifecycle); + lifecycleLockSetup = true; + } +} + +void HandleDebuggerRequest(const http::Request &request) { + net::WebSocketServer *ws = net::WebSocketServer::CreateAsUpgrade(request, "debugger.ppsspp.org"); + if (!ws) + return; + + setCurrentThreadName("Debugger"); + UpdateConnected(1); + SetupDebuggerLock(); + + LogBroadcaster logger; + GameBroadcaster game; + SteppingBroadcaster stepping; + + std::unordered_map eventHandlers; + std::vector subscriberData; + for (auto info : subscribers) { + std::lock_guard guard(lifecycleLock); + subscriberData.push_back(info.init(eventHandlers)); + } + + // There's a tradeoff between responsiveness to incoming events, and polling for changes. + int highActivity = 0; + ws->SetTextHandler([&](const std::string &t) { + JsonReader reader(t.c_str(), t.size()); + if (!reader.ok()) { + ws->Send(DebuggerErrorEvent("Bad message: invalid JSON", LogTypes::LERROR)); + return; + } + + const JsonGet root = reader.root(); + const char *event = root ? root.getString("event", nullptr) : nullptr; + if (!event) { + ws->Send(DebuggerErrorEvent("Bad message: no event property", LogTypes::LERROR, root)); + return; + } + + DebuggerRequest req(event, ws, root); + auto eventFunc = eventHandlers.find(event); + if (eventFunc != eventHandlers.end()) { + std::lock_guard guard(lifecycleLock); + eventFunc->second(req); + if (!req.Finish()) { + // Poll more frequently for a second in case this triggers something. + highActivity = 1000; + } + } else { + req.Fail("Bad message: unknown event"); + } + }); + ws->SetBinaryHandler([&](const std::vector &d) { + ws->Send(DebuggerErrorEvent("Bad message", LogTypes::LERROR)); + }); + + while (ws->Process(highActivity ? 1.0f / 1000.0f : 1.0f / 60.0f)) { + std::lock_guard guard(lifecycleLock); + // These send events that aren't just responses to requests. + logger.Broadcast(ws); + game.Broadcast(ws); + stepping.Broadcast(ws); + + if (stopRequested) { + ws->Close(net::WebSocketClose::GOING_AWAY); + } + if (highActivity > 0) { + highActivity--; + } + } + + std::lock_guard guard(lifecycleLock); + for (size_t i = 0; i < subscribers.size(); ++i) { + if (subscribers[i].shutdown) { + subscribers[i].shutdown(subscriberData[i]); + } else { + assert(!subscriberData[i]); + } + } + + delete ws; + UpdateConnected(-1); +} + +void StopAllDebuggers() { + std::unique_lock guard(stopLock); + while (debuggersConnected != 0) { + stopRequested = true; + stopCond.wait(guard); + } + + // Reset it back for next time. + stopRequested = false; +} diff --git a/Core/Debugger/WebSocket.h b/Core/Debugger/WebSocket.h new file mode 100644 index 0000000000..0151ca686a --- /dev/null +++ b/Core/Debugger/WebSocket.h @@ -0,0 +1,26 @@ +// Copyright (c) 2017- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +namespace http { +class Request; +} + +void HandleDebuggerRequest(const http::Request &request); +// Note: blocks. +void StopAllDebuggers(); diff --git a/Core/Debugger/WebSocket/BreakpointSubscriber.cpp b/Core/Debugger/WebSocket/BreakpointSubscriber.cpp new file mode 100644 index 0000000000..ffe1797ce5 --- /dev/null +++ b/Core/Debugger/WebSocket/BreakpointSubscriber.cpp @@ -0,0 +1,446 @@ +// Copyright (c) 2018- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "Common/StringUtils.h" +#include "Core/Debugger/Breakpoints.h" +#include "Core/Debugger/DisassemblyManager.h" +#include "Core/Debugger/SymbolMap.h" +#include "Core/Debugger/WebSocket/BreakpointSubscriber.h" +#include "Core/Debugger/WebSocket/WebSocketUtils.h" +#include "Core/MIPS/MIPSDebugInterface.h" + +void *WebSocketBreakpointInit(DebuggerEventHandlerMap &map) { + // No need to bind or alloc state, these are all global. + map["cpu.breakpoint.add"] = &WebSocketCPUBreakpointAdd; + map["cpu.breakpoint.update"] = &WebSocketCPUBreakpointUpdate; + map["cpu.breakpoint.remove"] = &WebSocketCPUBreakpointRemove; + map["cpu.breakpoint.list"] = &WebSocketCPUBreakpointList; + + map["memory.breakpoint.add"] = &WebSocketMemoryBreakpointAdd; + map["memory.breakpoint.update"] = &WebSocketMemoryBreakpointUpdate; + map["memory.breakpoint.remove"] = &WebSocketMemoryBreakpointRemove; + map["memory.breakpoint.list"] = &WebSocketMemoryBreakpointList; + + return nullptr; +} + +struct WebSocketCPUBreakpointParams { + uint32_t address = 0; + bool hasEnabled = false; + bool hasLog = false; + bool hasCondition = false; + bool hasLogFormat = false; + + bool enabled; + bool log; + std::string condition; + PostfixExpression compiledCondition; + std::string logFormat; + + bool Parse(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) { + req.Fail("CPU not started"); + return false; + } + + if (!req.ParamU32("address", &address)) + return false; + + hasEnabled = req.HasParam("enabled"); + if (hasEnabled) { + if (!req.ParamBool("enabled", &enabled)) + return false; + } + hasLog = req.HasParam("log"); + if (hasLog) { + if (!req.ParamBool("log", &log)) + return false; + } + hasCondition = req.HasParam("condition"); + if (hasCondition) { + if (!req.ParamString("condition", &condition)) + return false; + if (!currentDebugMIPS->initExpression(condition.c_str(), compiledCondition)) { + req.Fail(StringFromFormat("Could not parse expression syntax: %s", getExpressionError())); + return false; + } + } + hasLogFormat = req.HasParam("logFormat"); + if (hasLogFormat) { + if (!req.ParamString("logFormat", &logFormat)) + return false; + } + + return true; + } + + void Apply() { + if (hasCondition && !condition.empty()) { + BreakPointCond cond; + cond.debug = currentDebugMIPS; + cond.expressionString = condition; + cond.expression = compiledCondition; + CBreakPoints::ChangeBreakPointAddCond(address, cond); + } else if (hasCondition && condition.empty()) { + CBreakPoints::ChangeBreakPointRemoveCond(address); + } + + if (hasLogFormat) { + CBreakPoints::ChangeBreakPointLogFormat(address, logFormat); + } + + // TODO: Fix this interface. + if (hasLog && !hasEnabled) { + CBreakPoints::IsAddressBreakPoint(address, &enabled); + hasEnabled = true; + } + if (hasLog && hasEnabled) { + BreakAction result = BREAK_ACTION_IGNORE; + if (log) + result |= BREAK_ACTION_LOG; + if (enabled) + result |= BREAK_ACTION_PAUSE; + CBreakPoints::ChangeBreakPoint(address, result); + } else if (hasEnabled) { + CBreakPoints::ChangeBreakPoint(address, enabled); + } + } +}; + +// Add a new CPU instruction breakpoint (cpu.breakpoint.add) +// +// Parameters: +// - address: unsigned integer address of instruction to break at. +// - enabled: optional boolean, whether to actually enter stepping when this breakpoint trips. +// - log: optional boolean, whether to log when this breakpoint trips. +// - condition: optional string expression to evaluate - breakpoint does not trip if false. +// - logFormat: optional string to log when breakpoint trips, may include {expression} parts. +// +// Response (same event name) with no extra data. +// +// Note: will replace any breakpoint at the same address. +void WebSocketCPUBreakpointAdd(DebuggerRequest &req) { + WebSocketCPUBreakpointParams params; + if (!params.Parse(req)) + return; + + CBreakPoints::AddBreakPoint(params.address); + params.Apply(); + req.Respond(); +} + +// Update a CPU instruction breakpoint (cpu.breakpoint.update) +// +// Parameters: +// - address: unsigned integer address of instruction to break at. +// - enabled: optional boolean, whether to actually enter stepping when this breakpoint trips. +// - log: optional boolean, whether to log when this breakpoint trips. +// - condition: optional string expression to evaluate - breakpoint does not trip if false. +// - logFormat: optional string to log when breakpoint trips, may include {expression} parts. +// +// Response (same event name) with no extra data. +void WebSocketCPUBreakpointUpdate(DebuggerRequest &req) { + WebSocketCPUBreakpointParams params; + if (!params.Parse(req)) + return; + bool enabled; + if (!CBreakPoints::IsAddressBreakPoint(params.address, &enabled)) + return req.Fail("Breakpoint not found"); + + params.Apply(); + req.Respond(); +} + +// Remove a CPU instruction breakpoint (cpu.breakpoint.remove) +// +// Parameters: +// - address: unsigned integer address of instruction to break at. +// +// Response (same event name) with no extra data. +void WebSocketCPUBreakpointRemove(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) { + return req.Fail("CPU not started"); + } + + uint32_t address; + if (!req.ParamU32("address", &address)) + return; + + CBreakPoints::RemoveBreakPoint(address); + req.Respond(); +} + +// List all CPU instruction breakpoints (cpu.breakpoint.list) +// +// No parameters. +// +// Response (same event name): +// - breakpoints: array of objects, each with properties: +// - address: unsigned integer address of instruction to break at. +// - enabled: boolean, whether to actually enter stepping when this breakpoint trips. +// - log: optional boolean, whether to log when this breakpoint trips. +// - condition: null, or string expression to evaluate - breakpoint does not trip if false. +// - logFormat: null, or string to log when breakpoint trips, may include {expression} parts. +// - symbol: null, or string label or symbol at breakpoint address. +// - code: string disassembly of breakpoint address. +void WebSocketCPUBreakpointList(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) { + return req.Fail("CPU not started"); + } + + JsonWriter &json = req.Respond(); + json.pushArray("breakpoints"); + auto bps = CBreakPoints::GetBreakpoints(); + for (const auto &bp : bps) { + if (bp.temporary) + continue; + + json.pushDict(); + json.writeUint("address", bp.addr); + json.writeBool("enabled", bp.IsEnabled()); + json.writeBool("log", (bp.result & BREAK_ACTION_LOG) != 0); + if (bp.hasCond) + json.writeString("condition", bp.cond.expressionString); + else + json.writeNull("condition"); + if (!bp.logFormat.empty()) + json.writeString("logFormat", bp.logFormat); + else + json.writeNull("logFormat"); + std::string symbol = g_symbolMap->GetLabelString(bp.addr); + if (symbol.empty()) + json.writeNull("symbol"); + else + json.writeString("symbol", symbol); + + DisassemblyManager manager; + DisassemblyLineInfo line; + manager.getLine(manager.getStartAddress(bp.addr), true, line); + json.writeString("code", line.name + " " + line.params); + + json.pop(); + } + json.pop(); +} + +struct WebSocketMemoryBreakpointParams { + uint32_t address = 0; + uint32_t end = 0; + bool hasEnabled = false; + bool hasLog = false; + bool hasCond = false; + bool hasLogFormat = false; + + bool enabled = true; + bool log = true; + MemCheckCondition cond = MEMCHECK_READWRITE; + std::string logFormat; + + bool Parse(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) { + req.Fail("CPU not started"); + return false; + } + + if (!req.ParamU32("address", &address)) + return false; + uint32_t size; + if (!req.ParamU32("size", &size)) + return false; + if (address + size < address) { + req.Fail("Size is too large"); + return false; + } + end = size == 0 ? 0 : address + size; + + hasEnabled = req.HasParam("enabled"); + if (hasEnabled) { + if (!req.ParamBool("enabled", &enabled)) + return false; + } + hasLog = req.HasParam("log"); + if (hasLog) { + if (!req.ParamBool("log", &log)) + return false; + } + hasCond = req.HasParam("read") || req.HasParam("write") || req.HasParam("change"); + if (hasCond) { + bool read, write, change; + if (!req.ParamBool("read", &read) || !req.ParamBool("write", &write) || !req.ParamBool("change", &change)) + return false; + int bits = (read ? MEMCHECK_READ : 0) | (write ? MEMCHECK_WRITE : 0) | (change ? MEMCHECK_WRITE_ONCHANGE : 0); + cond = MemCheckCondition(bits); + } + hasLogFormat = req.HasParam("logFormat"); + if (hasLogFormat) { + if (!req.ParamString("logFormat", &logFormat)) + return false; + } + + return true; + } + + BreakAction Result(bool adding) { + int bits = MEMCHECK_READWRITE; + if (adding || (hasLog && hasEnabled)) { + bits = (enabled ? BREAK_ACTION_PAUSE : 0) | (log ? BREAK_ACTION_LOG : 0); + } else { + MemCheck prev; + if (CBreakPoints::GetMemCheck(address, end, &prev)) + bits = prev.result; + + if (hasEnabled) + bits = (bits & ~BREAK_ACTION_PAUSE) | (enabled ? BREAK_ACTION_PAUSE : 0); + if (hasLog) + bits = (bits & ~BREAK_ACTION_LOG) | (log ? BREAK_ACTION_LOG : 0); + } + + return BreakAction(bits); + } + + void Apply() { + if (hasLogFormat) { + CBreakPoints::ChangeMemCheckLogFormat(address, end, logFormat); + } + } +}; + +// Add a new memory breakpoint (memory.breakpoint.add) +// +// Parameters: +// - address: unsigned integer address for the start of the memory range. +// - size: unsigned integer specifying size of memory range. +// - enabled: optional boolean, whether to actually enter stepping when this breakpoint trips. +// - log: optional boolean, whether to log when this breakpoint trips. +// - read: optional boolean, whether to trip on any read to this address. +// - write: optional boolean, whether to trip on any write to this address. +// - change: optional boolean, whether to trip on a write to this address which modifies data +// (or any write that may modify data.) +// - logFormat: optional string to log when breakpoint trips, may include {expression} parts. +// +// Response (same event name) with no extra data. +// +// Note: will replace any breakpoint that has the same start address and size. +void WebSocketMemoryBreakpointAdd(DebuggerRequest &req) { + WebSocketMemoryBreakpointParams params; + if (!params.Parse(req)) + return; + + CBreakPoints::AddMemCheck(params.address, params.end, params.cond, params.Result(true)); + params.Apply(); + req.Respond(); +} + +// Update a memory breakpoint (memory.breakpoint.update) +// +// Parameters: +// - address: unsigned integer address for the start of the memory range. +// - size: unsigned integer specifying size of memory range. +// - enabled: optional boolean, whether to actually enter stepping when this breakpoint trips. +// - log: optional boolean, whether to log when this breakpoint trips. +// - read: optional boolean, whether to trip on any read to this address. +// - write: optional boolean, whether to trip on any write to this address. +// - change: optional boolean, whether to trip on a write to this address which modifies data +// (or any write that may modify data.) +// - logFormat: optional string to log when breakpoint trips, may include {expression} parts. +// +// Response (same event name) with no extra data. +void WebSocketMemoryBreakpointUpdate(DebuggerRequest &req) { + WebSocketMemoryBreakpointParams params; + if (!params.Parse(req)) + return; + + MemCheck mc; + if (!CBreakPoints::GetMemCheck(params.address, params.end, &mc)) + return req.Fail("Breakpoint not found"); + + CBreakPoints::ChangeMemCheck(params.address, params.end, params.cond, params.Result(true)); + params.Apply(); + req.Respond(); +} + +// Remove a memory breakpoint (memory.breakpoint.remove) +// +// Parameters: +// - address: unsigned integer address for the start of the memory range. +// - size: unsigned integer specifying size of memory range. +// +// Response (same event name) with no extra data. +void WebSocketMemoryBreakpointRemove(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) { + return req.Fail("CPU not started"); + } + + uint32_t address; + if (!req.ParamU32("address", &address)) + return; + uint32_t size; + if (!req.ParamU32("size", &size)) + return; + + CBreakPoints::RemoveMemCheck(address, size == 0 ? 0 : address + size); + req.Respond(); +} + +// List all memory breakpoints (memory.breakpoint.list) +// +// No parameters. +// +// Response (same event name): +// - breakpoints: array of objects, each with properties: +// - address: unsigned integer address for the start of the memory range. +// - size: unsigned integer specifying size of memory range. +// - enabled: boolean, whether to actually enter stepping when this breakpoint trips. +// - log: optional boolean, whether to log when this breakpoint trips. +// - read: optional boolean, whether to trip on any read to this address. +// - write: optional boolean, whether to trip on any write to this address. +// - change: optional boolean, whether to trip on a write to this address which modifies data +// (or any write that may modify data.) +// - logFormat: null, or string to log when breakpoint trips, may include {expression} parts. +// - symbol: null, or string label or symbol at breakpoint address. +void WebSocketMemoryBreakpointList(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) { + return req.Fail("CPU not started"); + } + + JsonWriter &json = req.Respond(); + json.pushArray("breakpoints"); + auto mcs = CBreakPoints::GetMemChecks(); + for (const auto &mc : mcs) { + json.pushDict(); + json.writeUint("address", mc.start); + json.writeUint("size", mc.end == 0 ? 0 : mc.end - mc.start); + json.writeBool("enabled", mc.IsEnabled()); + json.writeBool("log", (mc.result & BREAK_ACTION_LOG) != 0); + json.writeBool("read", (mc.cond & MEMCHECK_READ) != 0); + json.writeBool("write", (mc.cond & MEMCHECK_WRITE) != 0); + json.writeBool("change", (mc.cond & MEMCHECK_WRITE_ONCHANGE) != 0); + json.writeUint("hits", mc.numHits); + if (!mc.logFormat.empty()) + json.writeString("logFormat", mc.logFormat); + else + json.writeNull("logFormat"); + std::string symbol = g_symbolMap->GetLabelString(mc.start); + if (symbol.empty()) + json.writeNull("symbol"); + else + json.writeString("symbol", symbol); + + json.pop(); + } + json.pop(); +} diff --git a/Core/Debugger/WebSocket/BreakpointSubscriber.h b/Core/Debugger/WebSocket/BreakpointSubscriber.h new file mode 100644 index 0000000000..c9c2e1effc --- /dev/null +++ b/Core/Debugger/WebSocket/BreakpointSubscriber.h @@ -0,0 +1,32 @@ +// Copyright (c) 2018- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include "Core/Debugger/WebSocket/WebSocketUtils.h" + +void *WebSocketBreakpointInit(DebuggerEventHandlerMap &map); + +void WebSocketCPUBreakpointAdd(DebuggerRequest &req); +void WebSocketCPUBreakpointUpdate(DebuggerRequest &req); +void WebSocketCPUBreakpointRemove(DebuggerRequest &req); +void WebSocketCPUBreakpointList(DebuggerRequest &req); + +void WebSocketMemoryBreakpointAdd(DebuggerRequest &req); +void WebSocketMemoryBreakpointUpdate(DebuggerRequest &req); +void WebSocketMemoryBreakpointRemove(DebuggerRequest &req); +void WebSocketMemoryBreakpointList(DebuggerRequest &req); diff --git a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp new file mode 100644 index 0000000000..0d2c42f66c --- /dev/null +++ b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp @@ -0,0 +1,412 @@ +// Copyright (c) 2018- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "Common/StringUtils.h" +#include "Core/Core.h" +#include "Core/CoreTiming.h" +#include "Core/Debugger/Breakpoints.h" +#include "Core/Debugger/WebSocket/CPUCoreSubscriber.h" +#include "Core/Debugger/WebSocket/WebSocketUtils.h" +#include "Core/HLE/sceKernelThread.h" +#include "Core/MIPS/MIPS.h" +#include "Core/MIPS/MIPSDebugInterface.h" + +void *WebSocketCPUCoreInit(DebuggerEventHandlerMap &map) { + // No need to bind or alloc state, these are all global. + map["cpu.stepping"] = &WebSocketCPUStepping; + map["cpu.resume"] = &WebSocketCPUResume; + map["cpu.status"] = &WebSocketCPUStatus; + map["cpu.getAllRegs"] = &WebSocketCPUGetAllRegs; + map["cpu.getReg"] = &WebSocketCPUGetReg; + map["cpu.setReg"] = &WebSocketCPUSetReg; + map["cpu.evaluate"] = &WebSocketCPUEvaluate; + + return nullptr; +} + +static std::string RegValueAsFloat(uint32_t u) { + union { + uint32_t u; + float f; + } bits = { u }; + return StringFromFormat("%f", bits.f); +} + +static DebugInterface *CPUFromRequest(DebuggerRequest &req) { + if (!req.HasParam("thread")) + return currentDebugMIPS; + + u32 uid; + if (!req.ParamU32("thread", &uid)) + return nullptr; + + DebugInterface *cpuDebug = KernelDebugThread((SceUID)uid); + if (!cpuDebug) + req.Fail("Thread could not be found"); + return cpuDebug; +} + +// Begin stepping and pause the CPU (cpu.stepping) +// +// No parameters. +// +// No immediate response. Once CPU is stepping, a "cpu.stepping" event will be sent. +void WebSocketCPUStepping(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) { + return req.Fail("CPU not started"); + } + if (!Core_IsStepping() && Core_IsActive()) { + Core_EnableStepping(true); + } +} + +// Stop stepping and resume the CPU (cpu.resume) +// +// No parameters. +// +// No immediate response. Once CPU is stepping, a "cpu.resume" event will be sent. +void WebSocketCPUResume(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) { + return req.Fail("CPU not started"); + } + if (!Core_IsStepping() || coreState == CORE_POWERDOWN) { + return req.Fail("CPU not stepping"); + } + + CBreakPoints::SetSkipFirst(currentMIPS->pc); + if (currentMIPS->inDelaySlot) { + Core_DoSingleStep(); + } + Core_EnableStepping(false); +} + +// Request the current CPU status (cpu.status) +// +// No parameters. +// +// Response (same event name): +// - stepping: boolean, CPU currently stepping. +// - paused: boolean, CPU paused or not started yet. +// - pc: number value of PC register (inaccurate unless stepping.) +// - ticks: number of CPU cycles into emulation. +void WebSocketCPUStatus(DebuggerRequest &req) { + JsonWriter &json = req.Respond(); + json.writeBool("stepping", PSP_IsInited() && Core_IsStepping() && coreState != CORE_POWERDOWN); + json.writeBool("paused", GetUIState() != UISTATE_INGAME); + // Avoid NULL deference. + json.writeUint("pc", PSP_IsInited() ? currentMIPS->pc : 0); + // A double ought to be good enough for a 156 day debug session. + json.writeFloat("ticks", PSP_IsInited() ? CoreTiming::GetTicks() : 0); +} + +// Retrieve all regs and their values (cpu.getAllRegs) +// +// Parameters: +// - thread: optional number indicating the thread id to get regs for. +// +// Response (same event name): +// - categories: array of objects: +// - id: "category" property to use for other events. +// - name: a category name, such as "GPR". +// - registerNames: array of string names of the registers (size varies per category.) +// - uintValues: array of unsigned integer values for the registers. +// - floatValues: array of strings showing float representation. May be "nan", "inf", or "-inf". +void WebSocketCPUGetAllRegs(DebuggerRequest &req) { + auto cpuDebug = CPUFromRequest(req); + if (!cpuDebug) + return; + + JsonWriter &json = req.Respond(); + + json.pushArray("categories"); + for (int c = 0; c < cpuDebug->GetNumCategories(); ++c) { + json.pushDict(); + json.writeInt("id", c); + json.writeString("name", cpuDebug->GetCategoryName(c)); + + int total = cpuDebug->GetNumRegsInCategory(c); + + json.pushArray("registerNames"); + for (int r = 0; r < total; ++r) + json.writeString(cpuDebug->GetRegName(c, r)); + if (c == 0) { + json.writeString("pc"); + json.writeString("hi"); + json.writeString("lo"); + } + json.pop(); + + json.pushArray("uintValues"); + // Writing as floating point to avoid negatives. Actually double, so safe. + for (int r = 0; r < total; ++r) + json.writeUint(cpuDebug->GetRegValue(c, r)); + if (c == 0) { + json.writeUint(cpuDebug->GetPC()); + json.writeUint(cpuDebug->GetHi()); + json.writeUint(cpuDebug->GetLo()); + } + json.pop(); + + json.pushArray("floatValues"); + // Note: String so it can have Infinity and NaN. + for (int r = 0; r < total; ++r) + json.writeString(RegValueAsFloat(cpuDebug->GetRegValue(c, r))); + if (c == 0) { + json.writeString(RegValueAsFloat(cpuDebug->GetPC())); + json.writeString(RegValueAsFloat(cpuDebug->GetHi())); + json.writeString(RegValueAsFloat(cpuDebug->GetLo())); + } + json.pop(); + + json.pop(); + } + json.pop(); +} + +enum class DebuggerRegType { + INVALID, + NORMAL, + PC, + HI, + LO, +}; + +static DebuggerRegType ValidateRegName(DebuggerRequest &req, const std::string &name, int *cat, int *reg) { + if (name == "pc") { + *cat = 0; + *reg = 32; + return DebuggerRegType::PC; + } + if (name == "hi") { + *cat = 0; + *reg = 33; + return DebuggerRegType::HI; + } + if (name == "lo") { + *cat = 0; + *reg = 34; + return DebuggerRegType::LO; + } + + for (int c = 0; c < currentDebugMIPS->GetNumCategories(); ++c) { + int total = currentDebugMIPS->GetNumRegsInCategory(c); + for (int r = 0; r < total; ++r) { + if (name == currentDebugMIPS->GetRegName(c, r)) { + *cat = c; + *reg = r; + return DebuggerRegType::NORMAL; + } + } + } + + req.Fail("Invalid 'name' parameter"); + return DebuggerRegType::INVALID; +} + +static DebuggerRegType ValidateCatReg(DebuggerRequest &req, int *cat, int *reg) { + const char *name = req.data.getString("name", nullptr); + if (name) + return ValidateRegName(req, name, cat, reg); + + *cat = req.data.getInt("category", -1); + *reg = req.data.getInt("register", -1); + + if (*cat < 0 || *cat >= currentDebugMIPS->GetNumCategories()) { + req.Fail("Invalid 'category' parameter"); + return DebuggerRegType::INVALID; + } + + // TODO: We fake it for GPR... not sure yet if this is a good thing. + if (*cat == 0) { + // Intentionally retains the reg value. + if (*reg == 32) + return DebuggerRegType::PC; + if (*reg == 33) + return DebuggerRegType::HI; + if (*reg == 34) + return DebuggerRegType::LO; + } + + if (*reg < 0 || *reg >= currentDebugMIPS->GetNumRegsInCategory(*cat)) { + req.Fail("Invalid 'register' parameter"); + return DebuggerRegType::INVALID; + } + + return DebuggerRegType::NORMAL; +} + +// Retrieve the value of a single register (cpu.getReg) +// +// Parameters (by name): +// - thread: optional number indicating the thread id to get from. +// - name: string name of register to lookup. +// +// Parameters (by category id and index, ignored if name specified): +// - thread: optional number indicating the thread id to get from. +// - category: id of category for the register. +// - register: index into array of registers. +// +// Response (same event name): +// - category: id of category for the register. +// - register: index into array of registers. +// - uintValue: value in register. +// - floatValue: string showing float representation. May be "nan", "inf", or "-inf". +void WebSocketCPUGetReg(DebuggerRequest &req) { + auto cpuDebug = CPUFromRequest(req); + if (!cpuDebug) + return; + + int cat, reg; + uint32_t val; + switch (ValidateCatReg(req, &cat, ®)) { + case DebuggerRegType::NORMAL: + val = cpuDebug->GetRegValue(cat, reg); + break; + + case DebuggerRegType::PC: + val = cpuDebug->GetPC(); + break; + case DebuggerRegType::HI: + val = cpuDebug->GetHi(); + break; + case DebuggerRegType::LO: + val = cpuDebug->GetLo(); + break; + + case DebuggerRegType::INVALID: + // Error response already sent. + return; + } + + JsonWriter &json = req.Respond(); + json.writeInt("category", cat); + json.writeInt("register", reg); + json.writeUint("uintValue", val); + json.writeString("floatValue", RegValueAsFloat(val)); +} + +// Update the value of a single register (cpu.setReg) +// +// Parameters (by name): +// - thread: optional number indicating the thread id to update. +// - name: string name of register to lookup. +// - value: number (uint values only) or string to set to. Values may include +// "0x1234", "1.5", "nan", "-inf", etc. For a float, use a string with decimal e.g. "1.0". +// +// Parameters (by category id and index, ignored if name specified): +// - thread: optional number indicating the thread id to update. +// - category: id of category for the register. +// - register: index into array of registers. +// - value: number (uint values only) or string to set to. Values may include +// "0x1234", "1.5", "nan", "-inf", etc. For a float, use a string with decimal e.g. "1.0". +// +// Response (same event name): +// - category: id of category for the register. +// - register: index into array of registers. +// - uintValue: new value in register. +// - floatValue: string showing float representation. May be "nan", "inf", or "-inf". +// +// NOTE: Cannot be called unless the CPU is currently stepping. +void WebSocketCPUSetReg(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) { + return req.Fail("CPU not started"); + } + if (!Core_IsStepping()) { + return req.Fail("CPU currently running (cpu.stepping first)"); + } + + auto cpuDebug = CPUFromRequest(req); + if (!cpuDebug) + return; + + uint32_t val; + if (!req.ParamU32("value", &val, true)) { + // Already sent error. + return; + } + + int cat, reg; + switch (ValidateCatReg(req, &cat, ®)) { + case DebuggerRegType::NORMAL: + if (cat == 0 && reg == 0 && val != 0) { + return req.Fail("Cannot change reg zero"); + } + cpuDebug->SetRegValue(cat, reg, val); + // In case part of it was ignored (e.g. flags reg.) + val = cpuDebug->GetRegValue(cat, reg); + break; + + case DebuggerRegType::PC: + cpuDebug->SetPC(val); + break; + case DebuggerRegType::HI: + cpuDebug->SetHi(val); + break; + case DebuggerRegType::LO: + cpuDebug->SetLo(val); + break; + + case DebuggerRegType::INVALID: + // Error response already sent. + return; + } + + JsonWriter &json = req.Respond(); + // Repeat it back just to avoid confusion on how it parsed. + json.writeInt("category", cat); + json.writeInt("register", reg); + json.writeUint("uintValue", val); + json.writeString("floatValue", RegValueAsFloat(val)); +} + +// Evaluate an expression (cpu.evaluate) +// +// Parameters: +// - thread: optional number indicating the thread id to update. +// - expression: string containing labels, operators, regs, etc. +// +// Response (same event name): +// - uintValue: value in register. +// - floatValue: string showing float representation. May be "nan", "inf", or "-inf". +void WebSocketCPUEvaluate(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) { + return req.Fail("CPU not started"); + } + + auto cpuDebug = CPUFromRequest(req); + if (!cpuDebug) + return; + + std::string exp; + if (!req.ParamString("expression", &exp)) { + // Already sent error. + return; + } + + u32 val; + PostfixExpression postfix; + if (!cpuDebug->initExpression(exp.c_str(), postfix)) { + return req.Fail(StringFromFormat("Could not parse expression syntax: %s", getExpressionError())); + } + if (!cpuDebug->parseExpression(postfix, val)) { + return req.Fail(StringFromFormat("Could not evaluate expression: %s", getExpressionError())); + } + + JsonWriter &json = req.Respond(); + json.writeUint("uintValue", val); + json.writeString("floatValue", RegValueAsFloat(val)); +} diff --git a/Core/Debugger/WebSocket/CPUCoreSubscriber.h b/Core/Debugger/WebSocket/CPUCoreSubscriber.h new file mode 100644 index 0000000000..3667979e96 --- /dev/null +++ b/Core/Debugger/WebSocket/CPUCoreSubscriber.h @@ -0,0 +1,30 @@ +// Copyright (c) 2018- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include "Core/Debugger/WebSocket/WebSocketUtils.h" + +void *WebSocketCPUCoreInit(DebuggerEventHandlerMap &map); + +void WebSocketCPUStepping(DebuggerRequest &req); +void WebSocketCPUResume(DebuggerRequest &req); +void WebSocketCPUStatus(DebuggerRequest &req); +void WebSocketCPUGetAllRegs(DebuggerRequest &req); +void WebSocketCPUGetReg(DebuggerRequest &req); +void WebSocketCPUSetReg(DebuggerRequest &req); +void WebSocketCPUEvaluate(DebuggerRequest &req); diff --git a/Core/Debugger/WebSocket/DisasmSubscriber.cpp b/Core/Debugger/WebSocket/DisasmSubscriber.cpp new file mode 100644 index 0000000000..78ee607fdf --- /dev/null +++ b/Core/Debugger/WebSocket/DisasmSubscriber.cpp @@ -0,0 +1,481 @@ +// Copyright (c) 2018- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include +#include +#include "base/stringutil.h" +#include "util/text/utf8.h" +#include "Core/Debugger/Breakpoints.h" +#include "Core/Debugger/DisassemblyManager.h" +#include "Core/Debugger/WebSocket/DisasmSubscriber.h" +#include "Core/Debugger/WebSocket/WebSocketUtils.h" +#include "Core/HLE/sceKernelThread.h" +#include "Core/MemMap.h" +#include "Core/MIPS/MIPSAsm.h" +#include "Core/MIPS/MIPSDebugInterface.h" + +struct WebSocketDisasmState { + WebSocketDisasmState() { + disasm_.setCpu(currentDebugMIPS); + } + ~WebSocketDisasmState() { + disasm_.clear(); + } + + void Base(DebuggerRequest &req); + void Disasm(DebuggerRequest &req); + void SearchDisasm(DebuggerRequest &req); + void Assemble(DebuggerRequest &req); + +protected: + void WriteDisasmLine(JsonWriter &json, const DisassemblyLineInfo &l); + void WriteBranchGuide(JsonWriter &json, const BranchLine &l); + uint32_t RoundAddressUp(uint32_t addr); + + DisassemblyManager disasm_; +}; + +void *WebSocketDisasmInit(DebuggerEventHandlerMap &map) { + auto p = new WebSocketDisasmState(); + map["memory.base"] = std::bind(&WebSocketDisasmState::Base, p, std::placeholders::_1); + map["memory.disasm"] = std::bind(&WebSocketDisasmState::Disasm, p, std::placeholders::_1); + map["memory.searchDisasm"] = std::bind(&WebSocketDisasmState::SearchDisasm, p, std::placeholders::_1); + map["memory.assemble"] = std::bind(&WebSocketDisasmState::Assemble, p, std::placeholders::_1); + + return p; +} + +void WebSocketDisasmShutdown(void *p) { + delete static_cast(p); +} + +static DebugInterface *CPUFromRequest(DebuggerRequest &req) { + if (!req.HasParam("thread")) + return currentDebugMIPS; + + u32 uid; + if (!req.ParamU32("thread", &uid)) + return nullptr; + + DebugInterface *cpuDebug = KernelDebugThread((SceUID)uid); + if (!cpuDebug) + req.Fail("Thread could not be found"); + return cpuDebug; +} + +void WebSocketDisasmState::WriteDisasmLine(JsonWriter &json, const DisassemblyLineInfo &l) { + u32 addr = l.info.opcodeAddress; + json.pushDict(); + if (l.type == DISTYPE_OPCODE) + json.writeString("type", "opcode"); + else if (l.type == DISTYPE_MACRO) + json.writeString("type", "macro"); + else if (l.type == DISTYPE_DATA) + json.writeString("type", "data"); + else if (l.type == DISTYPE_OTHER) + json.writeString("type", "other"); + + json.writeUint("address", addr); + json.writeInt("addressSize", l.totalSize); + json.writeUint("encoding", Memory::IsValidAddress(addr) ? Memory::Read_Instruction(addr).encoding : 0); + if (l.totalSize >= 8 && Memory::IsValidRange(addr, l.totalSize)) { + json.pushArray("macroEncoding"); + for (u32 off = 0; off < l.totalSize; off += 4) { + json.writeUint(Memory::Read_Instruction(addr + off).encoding); + } + json.pop(); + } else { + json.writeNull("macroEncoding"); + } + int c = currentDebugMIPS->getColor(addr) & 0x00FFFFFF; + json.writeString("backgroundColor", StringFromFormat("#%02x%02x%02x", c & 0xFF, (c >> 8) & 0xFF, c >> 16)); + json.writeString("name", l.name); + json.writeString("params", l.params); + + const std::string addressSymbol = g_symbolMap->GetLabelString(addr); + if (addressSymbol.empty()) + json.writeNull("symbol"); + else + json.writeString("symbol", addressSymbol); + + const u32 funcAddress = g_symbolMap->GetFunctionStart(addr); + const std::string funcName = g_symbolMap->GetLabelString(funcAddress); + if (funcName.empty()) + json.writeNull("function"); + else + json.writeString("function", funcName); + + if (l.type == DISTYPE_DATA) { + u32 dataStart = g_symbolMap->GetDataStart(addr); + if (dataStart == -1) + dataStart = addr; + const std::string dataLabel = g_symbolMap->GetLabelString(dataStart); + + json.pushDict("dataSymbol"); + json.writeUint("start", dataStart); + if (dataLabel.empty()) + json.writeNull("label"); + else + json.writeString("label", dataLabel); + json.pop(); + } else { + json.writeNull("dataSymbol"); + } + + bool enabled; + // TODO: Account for bp inside macro? + if (CBreakPoints::IsAddressBreakPoint(addr, &enabled)) { + json.pushDict("breakpoint"); + json.writeBool("enabled", enabled); + auto cond = CBreakPoints::GetBreakPointCondition(addr); + if (cond) + json.writeString("condition", cond->expressionString); + else + json.writeNull("condition"); + json.pop(); + } else { + json.writeNull("breakpoint"); + } + + // This is always the current execution's PC. + json.writeBool("isCurrentPC", currentDebugMIPS->GetPC() == addr); + if (l.info.isBranch) { + json.pushDict("branch"); + std::string targetSymbol; + if (!l.info.isBranchToRegister) { + targetSymbol = g_symbolMap->GetLabelString(l.info.branchTarget); + json.writeUint("targetAddress", l.info.branchTarget); + json.writeNull("register"); + } else { + json.writeNull("targetAddress"); + json.writeInt("register", l.info.branchRegisterNum); + } + json.writeBool("isLinked", l.info.isLinkedBranch); + json.writeBool("isLikely", l.info.isLikelyBranch); + if (targetSymbol.empty()) + json.writeNull("symbol"); + else + json.writeString("symbol", targetSymbol); + json.pop(); + } else { + json.writeNull("branch"); + } + + if (l.info.hasRelevantAddress) { + json.pushDict("relevantData"); + json.writeUint("address", l.info.relevantAddress); + if (Memory::IsValidRange(l.info.relevantAddress, 4)) + json.writeUint("uintValue", Memory::ReadUnchecked_U32(l.info.relevantAddress)); + else + json.writeNull("uintValue"); + json.pop(); + } else { + json.writeNull("relevantData"); + } + + if (l.info.isConditional) + json.writeBool("conditionMet", l.info.conditionMet); + else + json.writeNull("conditionMet"); + + if (l.info.isDataAccess) { + json.pushDict("dataAccess"); + json.writeUint("address", l.info.dataAddress); + json.writeInt("size", l.info.dataSize); + + std::string dataSymbol = g_symbolMap->GetLabelString(l.info.dataAddress); + std::string valueSymbol; + if (!Memory::IsValidRange(l.info.dataAddress, l.info.dataSize)) + json.writeNull("uintValue"); + else if (l.info.dataSize == 1) + json.writeUint("uintValue", Memory::ReadUnchecked_U8(l.info.dataAddress)); + else if (l.info.dataSize == 2) + json.writeUint("uintValue", Memory::ReadUnchecked_U16(l.info.dataAddress)); + else if (l.info.dataSize >= 4) { + u32 data = Memory::ReadUnchecked_U32(l.info.dataAddress); + valueSymbol = g_symbolMap->GetLabelString(data); + json.writeUint("uintValue", data); + } + + if (!dataSymbol.empty()) + json.writeString("symbol", dataSymbol); + else + json.writeNull("symbol"); + if (!valueSymbol.empty()) + json.writeString("valueSymbol", valueSymbol); + else + json.writeNull("valueSymbol"); + json.pop(); + } else { + json.writeNull("dataAccess"); + } + + json.pop(); +} + +void WebSocketDisasmState::WriteBranchGuide(JsonWriter &json, const BranchLine &l) { + json.pushDict(); + json.writeUint("top", l.first); + json.writeUint("bottom", l.second); + if (l.type == LINE_UP) + json.writeString("direction", "up"); + else if (l.type == LINE_DOWN) + json.writeString("direction", "down"); + else if (l.type == LINE_RIGHT) + json.writeString("direction", "right"); + json.writeInt("lane", l.laneIndex); + json.pop(); +} + +uint32_t WebSocketDisasmState::RoundAddressUp(uint32_t addr) { + if (addr < PSP_GetScratchpadMemoryBase()) + return PSP_GetScratchpadMemoryBase(); + else if (addr >= PSP_GetScratchpadMemoryEnd() && addr < PSP_GetVidMemBase()) + return PSP_GetVidMemBase(); + else if (addr >= PSP_GetVidMemEnd() && addr < PSP_GetKernelMemoryBase()) + return PSP_GetKernelMemoryBase(); + else if (addr >= PSP_GetUserMemoryEnd()) + return PSP_GetScratchpadMemoryBase(); + return addr; +} + +// Request the current PSP memory base address (memory.base) +// +// WARNING: Avoid this unless you have a good reason. Uses PPSSPP's address space. +// +// No parameters. +// +// Response (same event name): +// - addressHex: string indicating base address in hexadecimal (may be 64 bit.) +void WebSocketDisasmState::Base(DebuggerRequest &req) { + JsonWriter &json = req.Respond(); + json.writeString("addressHex", StringFromFormat("%016llx", Memory::base)); +} + +// Disassemble a range of memory as CPU instructions (memory.disasm) +// +// Parameters (by count): +// - thread: optional number indicating the thread id for branch info. +// - address: number specifying the start address. +// - count: number of lines to return (may be clamped to an internal limit.) +// - displaySymbols: boolean true to show symbol names in instruction params. +// +// Parameters (by end address): +// - thread: optional number indicating the thread id for branch info. +// - address: number specifying the start address. +// - end: number which must be after the start address (may be clamped to an internal limit.) +// - displaySymbols: boolean true to show symbol names in instruction params. +// +// Response (same event name): +// - range: object with result "start" and "end" properties, the addresses actually used. +// (disassembly may have snapped to a nearby instruction.) +// - branchGuides: array of objects: +// - top: the earlier address as a number. +// - bottom: the later address as a number. +// - direction: "up", "down", or "right" depending on the flow of the branch. +// - lane: number index to avoid overlapping guides. +// - lines: array of objects: +// - type: "opcode", "macro", "data", or "other". +// - address: address of first actual instruction. +// - addressSize: bytes used by this line (might be more than 4.) +// - encoding: uint value of actual instruction (may differ from memory read when using jit.) +// - macroEncoding: null, or an array of encodings if this line represents multiple instructions. +// - name: string name of the instruction. +// - params: formatted parameters for the instruction. +// - (other info about the disassembled line.) +void WebSocketDisasmState::Disasm(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) + return req.Fail("CPU not started"); + auto cpuDebug = CPUFromRequest(req); + if (!cpuDebug) + return; + + // In case of client errors, we limit the range to something that won't make us crash. + static const uint32_t MAX_RANGE = 10000; + + uint32_t start, end; + if (!req.ParamU32("address", &start)) + return; + uint32_t count = 0; + if (!req.ParamU32("count", &count, false, DebuggerParamType::OPTIONAL)) + return; + if (count != 0) { + count = std::min(count, MAX_RANGE); + // Let's assume everything is two instructions. + disasm_.analyze(start - 4, count * 8 + 8); + start = disasm_.getStartAddress(start); + if (start == -1) + req.ParamU32("address", &start); + end = disasm_.getNthNextAddress(start, count); + } else if (req.ParamU32("end", &end)) { + end = std::min(std::max(start, end), start + MAX_RANGE * 4); + // Let's assume everything is two instructions. + disasm_.analyze(start - 4, end - start + 8); + start = disasm_.getStartAddress(start); + if (start == -1) + req.ParamU32("address", &start); + // Correct end and calculate count based on it. + // This accounts for macros as one line, although two instructions. + u32 stop = end; + count = 0; + for (end = start; end < stop; end = disasm_.getNthNextAddress(end, 1)) { + count++; + } + } else { + // Error message already sent. + return; + } + + bool displaySymbols = true; + if (!req.ParamBool("displaySymbols", &displaySymbols, DebuggerParamType::OPTIONAL)) + return; + + JsonWriter &json = req.Respond(); + json.pushDict("range"); + json.writeUint("start", start); + json.writeUint("end", end); + json.pop(); + + json.pushArray("lines"); + DisassemblyLineInfo line; + uint32_t addr = start; + for (uint32_t i = 0; i < count; ++i) { + disasm_.getLine(addr, displaySymbols, line, cpuDebug); + WriteDisasmLine(json, line); + addr += line.totalSize; + + // These are pretty long, so let's grease the wheels a bit. + if (i % 50 == 0) + req.Flush(); + } + json.pop(); + + json.pushArray("branchGuides"); + auto branchGuides = disasm_.getBranchLines(start, end - start); + for (auto bl : branchGuides) + WriteBranchGuide(json, bl); + json.pop(); +} + +// Search disassembly for some text (cpu.searchDisasm) +// +// Parameters: +// - thread: optional number indicating the thread id (may not affect search much.) +// - address: starting address as a number. +// - end: optional end address as a number (otherwise uses start.) +// - match: string to search for. +// - displaySymbols: optional, specify false to hide symbols in the searched parameters. +// +// Response (same event name): +// - address: number address of match or null if none was found. +void WebSocketDisasmState::SearchDisasm(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) + return req.Fail("CPU not started"); + auto cpuDebug = CPUFromRequest(req); + if (!cpuDebug) + return; + + uint32_t start; + if (!req.ParamU32("address", &start)) + return; + uint32_t end = start; + if (!req.ParamU32("end", &end, false, DebuggerParamType::OPTIONAL)) + return; + std::string match; + if (!req.ParamString("match", &match)) + return; + bool displaySymbols = true; + if (!req.ParamBool("displaySymbols", &displaySymbols, DebuggerParamType::OPTIONAL)) + return; + + bool loopSearch = end <= start; + start = RoundAddressUp(start); + if ((end <= start) != loopSearch) { + // We must've passed end by rounding up. + JsonWriter &json = req.Respond(); + json.writeNull("address"); + return; + } + + // We do this after the check in case both were in unused memory. + end = RoundAddressUp(end); + + std::transform(match.begin(), match.end(), match.begin(), ::tolower); + + DisassemblyLineInfo line; + bool found = false; + uint32_t addr = start; + do { + disasm_.getLine(addr, displaySymbols, line, cpuDebug); + const std::string addressSymbol = g_symbolMap->GetLabelString(addr); + + std::string mergeForSearch; + // Address+space (9) + symbol + colon+space (2) + name + space(1) + params = 12 fixed size worst case. + mergeForSearch.resize(12 + addressSymbol.size() + line.name.size() + line.params.size()); + + sprintf(&mergeForSearch[0], "%08x ", addr); + auto inserter = mergeForSearch.begin() + 9; + if (!addressSymbol.empty()) { + inserter = std::transform(addressSymbol.begin(), addressSymbol.end(), inserter, ::tolower); + *inserter++ = ':'; + *inserter++ = ' '; + } + inserter = std::transform(line.name.begin(), line.name.end(), inserter, ::tolower); + *inserter++ = ' '; + inserter = std::transform(line.params.begin(), line.params.end(), inserter, ::tolower); + + if (mergeForSearch.find(match) != mergeForSearch.npos) { + found = true; + break; + } + + addr = RoundAddressUp(addr + line.totalSize); + } while (addr != end); + + JsonWriter &json = req.Respond(); + if (found) + json.writeUint("address", addr); + else + json.writeNull("address"); +} + +// Assemble an instruction (cpu.assemble) +// +// Parameters: +// - address: number indicating the address to write to. +// - code: string containing the instruction to assemble. +// +// Response (same event name): +// - encoding: resulting encoding at this address. Always returns one value, even for macros. +void WebSocketDisasmState::Assemble(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) { + return req.Fail("CPU not started"); + } + + uint32_t address; + if (!req.ParamU32("address", &address)) + return; + std::string code; + if (!req.ParamString("code", &code)) + return; + + if (!MIPSAsm::MipsAssembleOpcode(code.c_str(), currentDebugMIPS, address)) + return req.Fail(StringFromFormat("Could not assemble: %s", ConvertWStringToUTF8(MIPSAsm::GetAssembleError()).c_str())); + + JsonWriter &json = req.Respond(); + json.writeUint("encoding", Memory::Read_Instruction(address).encoding); +} diff --git a/Core/Debugger/WebSocket/DisasmSubscriber.h b/Core/Debugger/WebSocket/DisasmSubscriber.h new file mode 100644 index 0000000000..e6c5b0e71c --- /dev/null +++ b/Core/Debugger/WebSocket/DisasmSubscriber.h @@ -0,0 +1,23 @@ +// Copyright (c) 2018- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include "Core/Debugger/WebSocket/WebSocketUtils.h" + +void *WebSocketDisasmInit(DebuggerEventHandlerMap &map); +void WebSocketDisasmShutdown(void *p); diff --git a/Core/Debugger/WebSocket/GPUBufferSubscriber.cpp b/Core/Debugger/WebSocket/GPUBufferSubscriber.cpp new file mode 100644 index 0000000000..915426755b --- /dev/null +++ b/Core/Debugger/WebSocket/GPUBufferSubscriber.cpp @@ -0,0 +1,332 @@ +// Copyright (c) 2018- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include +#ifndef USING_QT_UI +#include +#include +#endif +#include "data/base64.h" +#include "Common/StringUtils.h" +#include "Core/Debugger/WebSocket/GPUBufferSubscriber.h" +#include "Core/Debugger/WebSocket/WebSocketUtils.h" +#include "Core/MIPS/MIPSDebugInterface.h" +#include "Core/Screenshot.h" +#include "GPU/Debugger/Stepping.h" + +void *WebSocketGPUBufferInit(DebuggerEventHandlerMap &map) { + // No need to bind or alloc state, these are all global. + map["gpu.buffer.screenshot"] = &WebSocketGPUBufferScreenshot; + map["gpu.buffer.renderColor"] = &WebSocketGPUBufferRenderColor; + map["gpu.buffer.renderDepth"] = &WebSocketGPUBufferRenderDepth; + map["gpu.buffer.renderStencil"] = &WebSocketGPUBufferRenderStencil; + + return nullptr; +} + +// Note: Calls req.Respond(). Other data can be added afterward. +static bool StreamBufferToDataURI(DebuggerRequest &req, const GPUDebugBuffer &buf, bool includeAlpha) { +#ifdef USING_QT_UI + req.Fail("Not supported on Qt yet, pull requests accepted"); + return false; +#else + u8 *flipbuffer = nullptr; + u32 w = (u32)-1; + u32 h = (u32)-1; + const u8 *buffer = ConvertBufferToScreenshot(buf, includeAlpha, flipbuffer, w, h); + if (!buffer) { + req.Fail("Internal error converting buffer for PNG encode"); + return false; + } + + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!png_ptr) { + req.Fail("Internal error setting up PNG encoder (png_ptr)"); + return false; + } + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_write_struct(&png_ptr, nullptr); + req.Fail("Internal error setting up PNG encoder (info_ptr)"); + return false; + } + + // Speed. Wireless N should give 35 KB/ms. For most devices, zlib/filters will cost more. + png_set_compression_strategy(png_ptr, Z_RLE); + png_set_compression_level(png_ptr, 1); + png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE, PNG_FILTER_NONE); + + auto &json = req.Respond(); + json.writeInt("width", w); + json.writeInt("height", h); + + // Start a value... + json.writeRaw("uri", ""); + req.Flush(); + // Now we'll write it directly to the stream. + req.ws->AddFragment(false, "\"data:image/png;base64,"); + + struct Context { + DebuggerRequest *req; + uint8_t buf[3]; + size_t bufSize; + }; + Context ctx = { &req, {}, 0 }; + + auto write = [](png_structp png_ptr, png_bytep data, png_size_t length) { + auto ctx = (Context *)png_get_io_ptr(png_ptr); + auto &req = *ctx->req; + + // If we buffered some bytes, fill to 3 bytes for a clean base64 encode. + // This way we don't have padding. + while (length > 0 && ctx->bufSize > 0 && ctx->bufSize != 3) { + ctx->buf[ctx->bufSize++] = data[0]; + data++; + length--; + } + + if (ctx->bufSize == 3) { + req.ws->AddFragment(false, Base64Encode(ctx->buf, ctx->bufSize)); + ctx->bufSize = 0; + } + assert(ctx->bufSize == 0 || length == 0); + + // Save bytes that would result in padding for next time. + size_t toBuffer = length % 3; + for (size_t i = 0; i < toBuffer; ++i) { + ctx->buf[i] = data[length - toBuffer + i]; + ctx->bufSize++; + } + + if (length > toBuffer) { + req.ws->AddFragment(false, Base64Encode(data, length - toBuffer)); + } + }; + auto flush = [](png_structp png_ptr) { + // Nothing, just here to prevent stdio flush. + }; + + png_bytep *row_pointers = new png_bytep[h]; + u32 stride = includeAlpha ? w * 4 : w * 3; + for (u32 i = 0; i < h; ++i) { + row_pointers[i] = (u8 *)buffer + stride * i; + } + + png_set_write_fn(png_ptr, &ctx, write, flush); + int colorType = includeAlpha ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB; + png_set_IHDR(png_ptr, info_ptr, w, h, 8, colorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + png_set_rows(png_ptr, info_ptr, row_pointers); + png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, nullptr); + + png_destroy_write_struct(&png_ptr, &info_ptr); + delete [] row_pointers; + delete [] flipbuffer; + + if (ctx.bufSize > 0) { + req.ws->AddFragment(false, Base64Encode(ctx.buf, ctx.bufSize)); + ctx.bufSize = 0; + } + + // End the string. + req.ws->AddFragment(false, "\""); + return true; +#endif +} + +static std::string DescribeFormat(GPUDebugBufferFormat fmt) { + switch (fmt) { + case GPU_DBG_FORMAT_565: return "B5G6R5_UNORM_PACK16"; + case GPU_DBG_FORMAT_5551: return "A1B5G5R5_UNORM_PACK16"; + case GPU_DBG_FORMAT_4444: return "A4B4G4R4_UNORM_PACK16"; + case GPU_DBG_FORMAT_8888: return "R8G8B8A8_UNORM"; + + case GPU_DBG_FORMAT_565_REV: return "R5G6B5_UNORM_PACK16"; + case GPU_DBG_FORMAT_5551_REV: return "R5G5B5A1_UNORM_PACK16"; + case GPU_DBG_FORMAT_4444_REV: return "R4G4B4A4_UNORM_PACK16"; + + case GPU_DBG_FORMAT_5551_BGRA: return "A1R5G5B5_UNORM_PACK16"; + case GPU_DBG_FORMAT_4444_BGRA: return "A4R4G4B4_UNORM_PACK16"; + case GPU_DBG_FORMAT_8888_BGRA: return "B8G8R8A8_UNORM"; + + case GPU_DBG_FORMAT_FLOAT: return "D32F"; + case GPU_DBG_FORMAT_16BIT: return "D16"; + case GPU_DBG_FORMAT_8BIT: return "S8"; + case GPU_DBG_FORMAT_24BIT_8X: return "D24_X8"; + case GPU_DBG_FORMAT_24X_8BIT: return "X24_S8"; + + case GPU_DBG_FORMAT_FLOAT_DIV_256: return "D32F_DIV_256"; + case GPU_DBG_FORMAT_24BIT_8X_DIV_256: return "D32F_X8_DIV_256"; + + case GPU_DBG_FORMAT_888_RGB: return "R8G8B8_UNORM"; + + case GPU_DBG_FORMAT_INVALID: + case GPU_DBG_FORMAT_BRSWAP_FLAG: + default: + return "UNDEFINED"; + } +} + +// Note: Calls req.Respond(). Other data can be added afterward. +static bool StreamBufferToBase64(DebuggerRequest &req, const GPUDebugBuffer &buf) { + size_t length = buf.GetStride() * buf.GetHeight(); + + auto &json = req.Respond(); + json.writeInt("width", buf.GetStride()); + json.writeInt("height", buf.GetHeight()); + json.writeBool("flipped", buf.GetFlipped()); + json.writeString("format", DescribeFormat(buf.GetFormat())); + + // Start a value without any actual data yet... + json.writeRaw("base64", ""); + req.Flush(); + + // Now we'll write it directly to the stream. + req.ws->AddFragment(false, "\""); + // 65535 is an "even" number of base64 characters. + static const size_t CHUNK_SIZE = 65535; + for (size_t i = 0; i < length; i += CHUNK_SIZE) { + size_t left = std::min(length - i, CHUNK_SIZE); + req.ws->AddFragment(false, Base64Encode(buf.GetData() + i, left)); + } + req.ws->AddFragment(false, "\""); + + return true; +} + +static void GenericStreamBuffer(DebuggerRequest &req, std::function func) { + if (!currentDebugMIPS->isAlive()) { + return req.Fail("CPU not started"); + } + if (coreState != CORE_STEPPING && !GPUStepping::IsStepping()) { + return req.Fail("Neither CPU or GPU is stepping"); + } + + bool includeAlpha = false; + if (!req.ParamBool("alpha", &includeAlpha, DebuggerParamType::OPTIONAL)) + return; + std::string type = "uri"; + if (!req.ParamString("type", &type, DebuggerParamType::OPTIONAL)) + return; + if (type != "uri" && type != "base64") + return req.Fail("Parameter 'type' must be either 'uri' or 'base64'"); + + const GPUDebugBuffer *buf = nullptr; + if (!func(buf)) { + return req.Fail("Could not download output"); + } + assert(buf != nullptr); + + if (type == "base64") { + StreamBufferToBase64(req, *buf); + } else if (type == "uri") { + StreamBufferToDataURI(req, *buf, includeAlpha); + } else { + _assert_(false); + } +} + +// Retrieve a screenshot (gpu.buffer.screenshot) +// +// Parameters: +// - type: either 'uri' or 'base64'. +// - alpha: boolean to include the alpha channel for 'uri' type (not normally useful for screenshots.) +// +// Response (same event name) for 'uri' type: +// - width: numeric width of screenshot. +// - height: numeric height of screenshot. +// - uri: data: URI of PNG image for display. +// +// Response (same event name) for 'base64' type: +// - width: numeric width of screenshot (also stride, in pixels, of binary data.) +// - height: numeric height of screenshot. +// - flipped: boolean to indicate whether buffer is vertically flipped. +// - format: string indicating format, such as 'R8G8B8A8_UNORM' or 'B8G8R8A8_UNORM'. +// - base64: base64 encode of binary data. +void WebSocketGPUBufferScreenshot(DebuggerRequest &req) { + GenericStreamBuffer(req, [](const GPUDebugBuffer *&buf) { + return GPUStepping::GPU_GetOutputFramebuffer(buf); + }); +} + +// Retrieve current color render buffer (gpu.buffer.renderColor) +// +// Parameters: +// - type: either 'uri' or 'base64'. +// - alpha: boolean to include the alpha channel for 'uri' type. +// +// Response (same event name) for 'uri' type: +// - width: numeric width of render buffer (may include stride.) +// - height: numeric height of render buffer. +// - uri: data: URI of PNG image for display. +// +// Response (same event name) for 'base64' type: +// - width: numeric width of render buffer (also stride, in pixels, of binary data.) +// - height: numeric height of render buffer. +// - flipped: boolean to indicate whether buffer is vertically flipped. +// - format: string indicating format, such as 'R8G8B8A8_UNORM' or 'B8G8R8A8_UNORM'. +// - base64: base64 encode of binary data. +void WebSocketGPUBufferRenderColor(DebuggerRequest &req) { + GenericStreamBuffer(req, [](const GPUDebugBuffer *&buf) { + return GPUStepping::GPU_GetCurrentFramebuffer(buf, GPU_DBG_FRAMEBUF_RENDER); + }); +} + +// Retrieve current depth render buffer (gpu.buffer.renderDepth) +// +// Parameters: +// - type: either 'uri' or 'base64'. +// - alpha: true to use alpha to encode depth, otherwise red for 'uri' type. +// +// Response (same event name) for 'uri' type: +// - width: numeric width of render buffer (may include stride.) +// - height: numeric height of render buffer. +// - uri: data: URI of PNG image for display. +// +// Response (same event name) for 'base64' type: +// - width: numeric width of render buffer (also stride, in pixels, of binary data.) +// - height: numeric height of render buffer. +// - flipped: boolean to indicate whether buffer is vertically flipped. +// - format: string indicating format, such as 'D16', 'D24_X8' or 'D32F'. +// - base64: base64 encode of binary data. +void WebSocketGPUBufferRenderDepth(DebuggerRequest &req) { + GenericStreamBuffer(req, [](const GPUDebugBuffer *&buf) { + return GPUStepping::GPU_GetCurrentDepthbuffer(buf); + }); +} + +// Retrieve current stencil render buffer (gpu.buffer.renderStencil) +// +// Parameters: +// - type: either 'uri' or 'base64'. +// - alpha: true to use alpha to encode stencil, otherwise red for 'uri' type. +// +// Response (same event name) for 'uri' type: +// - width: numeric width of render buffer (may include stride.) +// - height: numeric height of render buffer. +// - uri: data: URI of PNG image for display. +// +// Response (same event name) for 'base64' type: +// - width: numeric width of render buffer (also stride, in pixels, of binary data.) +// - height: numeric height of render buffer. +// - flipped: boolean to indicate whether buffer is vertically flipped. +// - format: string indicating format, such as 'X24_S8' or 'S8'. +// - base64: base64 encode of binary data. +void WebSocketGPUBufferRenderStencil(DebuggerRequest &req) { + GenericStreamBuffer(req, [](const GPUDebugBuffer *&buf) { + return GPUStepping::GPU_GetCurrentStencilbuffer(buf); + }); +} diff --git a/Core/Debugger/WebSocket/GPUBufferSubscriber.h b/Core/Debugger/WebSocket/GPUBufferSubscriber.h new file mode 100644 index 0000000000..166b206105 --- /dev/null +++ b/Core/Debugger/WebSocket/GPUBufferSubscriber.h @@ -0,0 +1,27 @@ +// Copyright (c) 2018- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include "Core/Debugger/WebSocket/WebSocketUtils.h" + +void *WebSocketGPUBufferInit(DebuggerEventHandlerMap &map); + +void WebSocketGPUBufferScreenshot(DebuggerRequest &req); +void WebSocketGPUBufferRenderColor(DebuggerRequest &req); +void WebSocketGPUBufferRenderDepth(DebuggerRequest &req); +void WebSocketGPUBufferRenderStencil(DebuggerRequest &req); diff --git a/Core/Debugger/WebSocket/GameBroadcaster.cpp b/Core/Debugger/WebSocket/GameBroadcaster.cpp new file mode 100644 index 0000000000..3045e70fc4 --- /dev/null +++ b/Core/Debugger/WebSocket/GameBroadcaster.cpp @@ -0,0 +1,94 @@ +// Copyright (c) 2018- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "Core/Debugger/WebSocket/GameBroadcaster.h" +#include "Core/Debugger/WebSocket/WebSocketUtils.h" +#include "Core/ELF/ParamSFO.h" +#include "Core/System.h" + +struct GameStatusEvent { + const char *ev; + + operator std::string() { + JsonWriter j; + j.begin(); + j.writeString("event", ev); + if (PSP_IsInited()) { + j.pushDict("game"); + j.writeString("id", g_paramSFO.GetDiscID()); + j.writeString("version", g_paramSFO.GetValueString("DISC_VERSION")); + j.writeString("title", g_paramSFO.GetValueString("TITLE")); + j.pop(); + } else { + j.writeNull("game"); + } + j.end(); + return j.str(); + } +}; + +// Game started (game.start) +// +// Sent unexpectedly with these properties: +// - game: null or an object with properties: +// - id: string disc ID (such as ULUS12345.) +// - version: string disc version. +// - title: string game title. + +// Game quit / ended (game.quit) +// +// Sent unexpectedly with these properties: +// - game: null + +// Game paused (game.pause) +// +// Note: this is not the same as stepping. This means the user went to the pause menu. +// +// Sent unexpectedly with these properties: +// - game: null or an object with properties: +// - id: string disc ID (such as ULUS12345.) +// - version: string disc version. +// - title: string game title. + +// Game resumed (game.pause) +// +// Note: this is not the same as stepping. This means the user resumed from the pause menu. +// +// Sent unexpectedly with these properties: +// - game: null or an object with properties: +// - id: string disc ID (such as ULUS12345.) +// - version: string disc version. +// - title: string game title. +void GameBroadcaster::Broadcast(net::WebSocketServer *ws) { + // TODO: This is ugly. Callbacks instead? + GlobalUIState state = GetUIState(); + if (prevState_ != state) { + if (state == UISTATE_PAUSEMENU) { + ws->Send(GameStatusEvent{"game.pause"}); + prevState_ = state; + } else if (state == UISTATE_INGAME && prevState_ == UISTATE_PAUSEMENU) { + ws->Send(GameStatusEvent{"game.resume"}); + prevState_ = state; + } else if (state == UISTATE_INGAME && PSP_IsInited()) { + ws->Send(GameStatusEvent{"game.start"}); + prevState_ = state; + } else if (state == UISTATE_MENU && !PSP_IsInited() && !PSP_IsQuitting()) { + ws->Send(GameStatusEvent{"game.quit"}); + prevState_ = state; + } + } +} diff --git a/Core/Debugger/WebSocket/GameBroadcaster.h b/Core/Debugger/WebSocket/GameBroadcaster.h new file mode 100644 index 0000000000..63ef3eb346 --- /dev/null +++ b/Core/Debugger/WebSocket/GameBroadcaster.h @@ -0,0 +1,36 @@ +// Copyright (c) 2018- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include "Core/System.h" + +namespace net { +class WebSocketServer; +} + +struct GameBroadcaster { +public: + GameBroadcaster() { + prevState_ = GetUIState(); + } + + void Broadcast(net::WebSocketServer *ws); + +private: + GlobalUIState prevState_; +}; diff --git a/Core/Debugger/WebSocket/GameSubscriber.cpp b/Core/Debugger/WebSocket/GameSubscriber.cpp new file mode 100644 index 0000000000..a17717e262 --- /dev/null +++ b/Core/Debugger/WebSocket/GameSubscriber.cpp @@ -0,0 +1,67 @@ +// Copyright (c) 2018- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "Core/Debugger/WebSocket/GameSubscriber.h" +#include "Core/Debugger/WebSocket/WebSocketUtils.h" +#include "Core/ELF/ParamSFO.h" +#include "Core/System.h" + +void *WebSocketGameInit(DebuggerEventHandlerMap &map) { + map["game.status"] = &WebSocketGameStatus; + map["version"] = &WebSocketVersion; + + return nullptr; +} + +// Check game status (game.status) +// +// No parameters. +// +// Response (same event name): +// - game: null or an object with properties: +// - id: string disc ID (such as ULUS12345.) +// - version: string disc version. +// - title: string game title. +// - paused: boolean, true when gameplay is paused (not the same as stepping.) +void WebSocketGameStatus(DebuggerRequest &req) { + JsonWriter &json = req.Respond(); + if (PSP_IsInited()) { + json.pushDict("game"); + json.writeString("id", g_paramSFO.GetDiscID()); + json.writeString("version", g_paramSFO.GetValueString("DISC_VERSION")); + json.writeString("title", g_paramSFO.GetValueString("TITLE")); + json.pop(); + } else { + json.writeNull("game"); + } + json.writeBool("paused", GetUIState() == UISTATE_PAUSEMENU); +} + +// Notify debugger version info (version) +// +// Parameters: +// - name: string indicating name of app or tool. +// - version: string version. +// +// Response (same event name): +// - name: string, "PPSSPP" unless some special build. +// - version: string, typically starts with "v" and may have git build info. +void WebSocketVersion(DebuggerRequest &req) { + JsonWriter &json = req.Respond(); + json.writeString("name", "PPSSPP"); + json.writeString("version", PPSSPP_GIT_VERSION); +} diff --git a/Core/Debugger/WebSocket/GameSubscriber.h b/Core/Debugger/WebSocket/GameSubscriber.h new file mode 100644 index 0000000000..77cec9e38b --- /dev/null +++ b/Core/Debugger/WebSocket/GameSubscriber.h @@ -0,0 +1,25 @@ +// Copyright (c) 2018- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include "Core/Debugger/WebSocket/WebSocketUtils.h" + +void *WebSocketGameInit(DebuggerEventHandlerMap &map); + +void WebSocketGameStatus(DebuggerRequest &req); +void WebSocketVersion(DebuggerRequest &req); diff --git a/Core/Debugger/WebSocket/HLESubscriber.cpp b/Core/Debugger/WebSocket/HLESubscriber.cpp new file mode 100644 index 0000000000..efb82c3b36 --- /dev/null +++ b/Core/Debugger/WebSocket/HLESubscriber.cpp @@ -0,0 +1,103 @@ +// Copyright (c) 2018- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "Core/Debugger/SymbolMap.h" +#include "Core/Debugger/WebSocket/HLESubscriber.h" +#include "Core/Debugger/WebSocket/WebSocketUtils.h" +#include "Core/HLE/sceKernelThread.h" + +void *WebSocketHLEInit(DebuggerEventHandlerMap &map) { + map["hle.thread.list"] = &WebSocketHLEThreadList; + map["hle.func.list"] = &WebSocketHLEFuncList; + map["hle.module.list"] = &WebSocketHLEModuleList; + + return nullptr; +} + +void WebSocketHLEThreadList(DebuggerRequest &req) { + // Will just return none of the CPU isn't ready yet. + auto threads = GetThreadsInfo(); + + JsonWriter &json = req.Respond(); + json.pushArray("threads"); + for (auto th : threads) { + json.pushDict(); + json.writeUint("id", th.id); + json.writeString("name", th.name); + json.writeInt("status", th.status); + json.pushArray("statuses"); + if (th.status & THREADSTATUS_RUNNING) + json.writeString("running"); + if (th.status & THREADSTATUS_READY) + json.writeString("ready"); + if (th.status & THREADSTATUS_WAIT) + json.writeString("wait"); + if (th.status & THREADSTATUS_SUSPEND) + json.writeString("suspend"); + if (th.status & THREADSTATUS_DORMANT) + json.writeString("dormant"); + if (th.status & THREADSTATUS_DEAD) + json.writeString("dead"); + json.pop(); + json.writeUint("pc", th.curPC); + json.writeUint("entry", th.entrypoint); + json.writeUint("initialStackSize", th.initialStack); + json.writeUint("currentStackSize", th.stackSize); + json.writeInt("priority", th.priority); + json.writeInt("priority", (int)th.waitType); + json.writeBool("isCurrent", th.isCurrent); + json.pop(); + } + json.pop(); +} + +void WebSocketHLEFuncList(DebuggerRequest &req) { + if (!g_symbolMap) + return req.Fail("CPU not active"); + + auto functions = g_symbolMap->GetAllSymbols(ST_FUNCTION); + + JsonWriter &json = req.Respond(); + json.pushArray("functions"); + for (auto f : functions) { + json.pushDict(); + json.writeString("name", f.name); + json.writeUint("address", f.address); + json.writeUint("size", f.size); + json.pop(); + } + json.pop(); +} + +void WebSocketHLEModuleList(DebuggerRequest &req) { + if (!g_symbolMap) + return req.Fail("CPU not active"); + + auto modules = g_symbolMap->getAllModules(); + + JsonWriter &json = req.Respond(); + json.pushArray("functions"); + for (auto m : modules) { + json.pushDict(); + json.writeString("name", m.name); + json.writeUint("address", m.address); + json.writeUint("size", m.size); + json.writeBool("isActive", m.active); + json.pop(); + } + json.pop(); +} diff --git a/Core/Debugger/WebSocket/HLESubscriber.h b/Core/Debugger/WebSocket/HLESubscriber.h new file mode 100644 index 0000000000..61af4dc80d --- /dev/null +++ b/Core/Debugger/WebSocket/HLESubscriber.h @@ -0,0 +1,26 @@ +// Copyright (c) 2018- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include "Core/Debugger/WebSocket/WebSocketUtils.h" + +void *WebSocketHLEInit(DebuggerEventHandlerMap &map); + +void WebSocketHLEThreadList(DebuggerRequest &req); +void WebSocketHLEFuncList(DebuggerRequest &req); +void WebSocketHLEModuleList(DebuggerRequest &req); diff --git a/Core/Debugger/WebSocket/LogBroadcaster.cpp b/Core/Debugger/WebSocket/LogBroadcaster.cpp new file mode 100644 index 0000000000..07070be7b0 --- /dev/null +++ b/Core/Debugger/WebSocket/LogBroadcaster.cpp @@ -0,0 +1,118 @@ +// Copyright (c) 2018- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include +#include +#include "Common/LogManager.h" +#include "Core/Debugger/WebSocket/LogBroadcaster.h" +#include "Core/Debugger/WebSocket/WebSocketUtils.h" + +class DebuggerLogListener : public LogListener { +public: + void Log(const LogMessage &msg) override { + std::lock_guard guard(lock_); + messages_[nextMessage_] = msg; + nextMessage_++; + if (nextMessage_ >= BUFFER_SIZE) + nextMessage_ -= BUFFER_SIZE; + count_++; + } + + std::vector GetMessages() { + std::lock_guard guard(lock_); + int splitPoint; + int readCount; + if (read_ + BUFFER_SIZE < count_) { + // We'll start with our oldest then. + splitPoint = nextMessage_; + readCount = Count(); + } else { + splitPoint = read_; + readCount = count_ - read_; + } + + read_ = count_; + + std::vector results; + int splitEnd = std::min(splitPoint + readCount, (int)BUFFER_SIZE); + for (int i = splitPoint; i < splitEnd; ++i) { + results.push_back(messages_[i]); + readCount--; + } + for (int i = 0; i < readCount; ++i) { + results.push_back(messages_[i]); + } + + return results; + } + + int Count() const { + return count_ < BUFFER_SIZE ? count_ : BUFFER_SIZE; + } + +private: + enum { BUFFER_SIZE = 1024 }; + LogMessage messages_[BUFFER_SIZE]; + std::mutex lock_; + int nextMessage_ = 0; + int count_ = 0; + int read_ = 0; +}; + +LogBroadcaster::LogBroadcaster() { + listener_ = new DebuggerLogListener(); + if (LogManager::GetInstance()) + LogManager::GetInstance()->AddListener(listener_); +} + +LogBroadcaster::~LogBroadcaster() { + if (LogManager::GetInstance()) + LogManager::GetInstance()->RemoveListener(listener_); + delete listener_; +} + +struct DebuggerLogEvent { + const LogMessage &l; + + operator std::string() { + JsonWriter j; + j.begin(); + j.writeString("event", "log"); + j.writeString("timestamp", l.timestamp); + j.writeString("header", l.header); + j.writeString("message", l.msg); + j.writeInt("level", l.level); + j.writeString("channel", l.log); + j.end(); + return j.str(); + } +}; + +// Log message (log) +// +// Sent unexpectedly with these properties: +// - timestamp: string timestamp of event. +// - header: string header information about the event (including file/line.) +// - message: actual log message as a string. +// - level: number severity level (1 = highest.) +// - channel: string describing log channel / grouping. +void LogBroadcaster::Broadcast(net::WebSocketServer *ws) { + auto messages = listener_->GetMessages(); + for (auto msg : messages) { + ws->Send(DebuggerLogEvent{msg}); + } +} diff --git a/Core/Debugger/WebSocket/LogBroadcaster.h b/Core/Debugger/WebSocket/LogBroadcaster.h new file mode 100644 index 0000000000..13f26d4a06 --- /dev/null +++ b/Core/Debugger/WebSocket/LogBroadcaster.h @@ -0,0 +1,35 @@ +// Copyright (c) 2018- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +namespace net { +class WebSocketServer; +} + +class DebuggerLogListener; + +struct LogBroadcaster { +public: + LogBroadcaster(); + ~LogBroadcaster(); + + void Broadcast(net::WebSocketServer *ws); + +private: + DebuggerLogListener *listener_; +}; diff --git a/Core/Debugger/WebSocket/SteppingBroadcaster.cpp b/Core/Debugger/WebSocket/SteppingBroadcaster.cpp new file mode 100644 index 0000000000..bde51e1f2f --- /dev/null +++ b/Core/Debugger/WebSocket/SteppingBroadcaster.cpp @@ -0,0 +1,61 @@ +// Copyright (c) 2018- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "Core/Core.h" +#include "Core/CoreTiming.h" +#include "Core/Debugger/WebSocket/SteppingBroadcaster.h" +#include "Core/Debugger/WebSocket/WebSocketUtils.h" +#include "Core/MIPS/MIPS.h" +#include "Core/System.h" + +struct CPUSteppingEvent { + operator std::string() { + JsonWriter j; + j.begin(); + j.writeString("event", "cpu.stepping"); + j.writeUint("pc", currentMIPS->pc); + // A double ought to be good enough for a 156 day debug session. + j.writeFloat("ticks", CoreTiming::GetTicks()); + j.end(); + return j.str(); + } +}; + +// CPU has begun stepping (cpu.stepping) +// +// Sent unexpectedly with these properties: +// - pc: number value of PC register (inaccurate unless stepping.) +// - ticks: number of CPU cycles into emulation. + +// CPU has resumed from stepping (cpu.resume) +// +// Sent unexpectedly with no other properties. +void SteppingBroadcaster::Broadcast(net::WebSocketServer *ws) { + if (PSP_IsInited()) { + int steppingCounter = Core_GetSteppingCounter(); + // We ignore CORE_POWERDOWN as a stepping state. + if (coreState == CORE_STEPPING && steppingCounter != lastCounter_) { + ws->Send(CPUSteppingEvent()); + } else if (prevState_ == CORE_STEPPING && coreState != CORE_STEPPING && Core_IsActive()) { + ws->Send(R"({"event":"cpu.resume"})"); + } + lastCounter_ = steppingCounter; + prevState_ = coreState; + } else { + prevState_ = CORE_POWERDOWN; + } +} diff --git a/Core/Debugger/WebSocket/SteppingBroadcaster.h b/Core/Debugger/WebSocket/SteppingBroadcaster.h new file mode 100644 index 0000000000..4ca34f4552 --- /dev/null +++ b/Core/Debugger/WebSocket/SteppingBroadcaster.h @@ -0,0 +1,37 @@ +// Copyright (c) 2018- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include "Core/Core.h" + +namespace net { +class WebSocketServer; +} + +struct SteppingBroadcaster { +public: + SteppingBroadcaster() { + prevState_ = coreState; + } + + void Broadcast(net::WebSocketServer *ws); + +private: + CoreState prevState_; + int lastCounter_ = 0; +}; diff --git a/Core/Debugger/WebSocket/SteppingSubscriber.cpp b/Core/Debugger/WebSocket/SteppingSubscriber.cpp new file mode 100644 index 0000000000..e1347f3a14 --- /dev/null +++ b/Core/Debugger/WebSocket/SteppingSubscriber.cpp @@ -0,0 +1,298 @@ +// Copyright (c) 2018- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "base/stringutil.h" +#include "Core/Debugger/Breakpoints.h" +#include "Core/Debugger/DisassemblyManager.h" +#include "Core/Debugger/WebSocket/SteppingSubscriber.h" +#include "Core/Debugger/WebSocket/WebSocketUtils.h" +#include "Core/Core.h" +#include "Core/HLE/HLE.h" +#include "Core/HLE/sceKernelThread.h" +#include "Core/MIPS/MIPSDebugInterface.h" +#include "Core/MIPS/MIPSStackWalk.h" + +using namespace MIPSAnalyst; + +struct WebSocketSteppingState { + WebSocketSteppingState() { + disasm_.setCpu(currentDebugMIPS); + } + ~WebSocketSteppingState() { + disasm_.clear(); + } + + void Into(DebuggerRequest &req); + void Over(DebuggerRequest &req); + void Out(DebuggerRequest &req); + void RunUntil(DebuggerRequest &req); + void HLE(DebuggerRequest &req); + +protected: + uint32_t GetNextAddress(DebugInterface *cpuDebug); + int GetNextInstructionCount(DebugInterface *cpuDebug); + void PrepareResume(); + void AddThreadCondition(uint32_t breakpointAddress, uint32_t threadID); + + DisassemblyManager disasm_; +}; + +void *WebSocketSteppingInit(DebuggerEventHandlerMap &map) { + auto p = new WebSocketSteppingState(); + map["cpu.stepInto"] = std::bind(&WebSocketSteppingState::Into, p, std::placeholders::_1); + map["cpu.stepOver"] = std::bind(&WebSocketSteppingState::Over, p, std::placeholders::_1); + map["cpu.stepOut"] = std::bind(&WebSocketSteppingState::Out, p, std::placeholders::_1); + map["cpu.runUntil"] = std::bind(&WebSocketSteppingState::RunUntil, p, std::placeholders::_1); + map["cpu.nextHLE"] = std::bind(&WebSocketSteppingState::HLE, p, std::placeholders::_1); + + return p; +} + +void WebSocketSteppingShutdown(void *p) { + delete static_cast(p); +} + +static DebugInterface *CPUFromRequest(DebuggerRequest &req, uint32_t *threadID = nullptr) { + if (!req.HasParam("thread")) { + if (threadID) + *threadID = -1; + return currentDebugMIPS; + } + + uint32_t uid; + if (!req.ParamU32("thread", &uid)) + return nullptr; + + DebugInterface *cpuDebug = KernelDebugThread((SceUID)uid); + if (!cpuDebug) + req.Fail("Thread could not be found"); + if (threadID) + *threadID = uid; + return cpuDebug; +} + +// Single step into the next instruction (cpu.stepInto) +// +// Parameters: +// - thread: optional number indicating the thread id to plan stepping on. +// +// No immediate response. A cpu.stepping event will be sent once complete. +// +// Note: any thread can wake the cpu when it hits the next instruction currently. +void WebSocketSteppingState::Into(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) + return req.Fail("CPU not started"); + if (!Core_IsStepping()) { + Core_EnableStepping(true); + return; + } + + uint32_t threadID; + auto cpuDebug = CPUFromRequest(req, &threadID); + if (!cpuDebug) + return; + + if (cpuDebug == currentDebugMIPS) { + // If the current PC is on a breakpoint, the user doesn't want to do nothing. + CBreakPoints::SetSkipFirst(currentMIPS->pc); + + int c = GetNextInstructionCount(cpuDebug); + for (int i = 0; i < c; ++i) { + Core_DoSingleStep(); + } + } else { + uint32_t breakpointAddress = cpuDebug->GetPC(); + PrepareResume(); + // Could have advanced to the breakpoint already in PrepareResume(). + // Note: we need to get cpuDebug again anyway (in case we ran some HLE above.) + cpuDebug = CPUFromRequest(req); + if (cpuDebug != currentDebugMIPS) { + CBreakPoints::AddBreakPoint(breakpointAddress, true); + AddThreadCondition(breakpointAddress, threadID); + Core_EnableStepping(false); + } + } +} + +// Step over the next instruction (cpu.stepOver) +// +// Note: this jumps over function calls, but also delay slots. +// +// Parameters: +// - thread: optional number indicating the thread id to plan stepping on. +// +// No immediate response. A cpu.stepping event will be sent once complete. +// +// Note: any thread can wake the cpu when it hits the next instruction currently. +void WebSocketSteppingState::Over(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) + return req.Fail("CPU not started"); + if (!Core_IsStepping()) + return req.Fail("CPU currently running (cpu.stepping first)"); + + uint32_t threadID; + auto cpuDebug = CPUFromRequest(req, &threadID); + if (!cpuDebug) + return; + + MipsOpcodeInfo info = GetOpcodeInfo(cpuDebug, cpuDebug->GetPC()); + uint32_t breakpointAddress = GetNextAddress(cpuDebug); + if (info.isBranch) { + if (info.isConditional && !info.isLinkedBranch) { + if (info.conditionMet) { + breakpointAddress = info.branchTarget; + } else { + // Skip over the delay slot. + breakpointAddress += 4; + } + } else { + if (info.isLinkedBranch) { + // jal or jalr - a function call. Skip the delay slot. + breakpointAddress += 4; + } else { + // j - for absolute branches, set the breakpoint at the branch target. + breakpointAddress = info.branchTarget; + } + } + } + + PrepareResume(); + // Could have advanced to the breakpoint already in PrepareResume(). + cpuDebug = CPUFromRequest(req); + if (cpuDebug->GetPC() != breakpointAddress) { + CBreakPoints::AddBreakPoint(breakpointAddress, true); + if (cpuDebug != currentDebugMIPS) + AddThreadCondition(breakpointAddress, threadID); + Core_EnableStepping(false); + } +} + +// Step out of a function based on a stack walk (cpu.stepOut) +// +// Parameters: +// - thread: optional number indicating the thread id to plan stepping on. +// +// No immediate response. A cpu.stepping event will be sent once complete. +// +// Note: any thread can wake the cpu when it hits the next instruction currently. +void WebSocketSteppingState::Out(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) + return req.Fail("CPU not started"); + if (!Core_IsStepping()) + return req.Fail("CPU currently running (cpu.stepping first)"); + + uint32_t threadID; + auto cpuDebug = CPUFromRequest(req, &threadID); + if (!cpuDebug) + return; + + auto threads = GetThreadsInfo(); + uint32_t entry = cpuDebug->GetPC(); + uint32_t stackTop = 0; + for (const DebugThreadInfo &th : threads) { + if ((threadID == -1 && th.isCurrent) || th.id == threadID) { + entry = th.entrypoint; + stackTop = th.initialStack; + break; + } + } + + uint32_t ra = cpuDebug->GetRegValue(0, MIPS_REG_RA); + uint32_t sp = cpuDebug->GetRegValue(0, MIPS_REG_SP); + auto frames = MIPSStackWalk::Walk(cpuDebug->GetPC(), ra, sp, entry, stackTop); + if (frames.size() < 2) { + return req.Fail("Could not find function call to step out into"); + } + + uint32_t breakpointAddress = frames[1].pc; + PrepareResume(); + // Could have advanced to the breakpoint already in PrepareResume(). + cpuDebug = CPUFromRequest(req); + if (cpuDebug->GetPC() != breakpointAddress) { + CBreakPoints::AddBreakPoint(breakpointAddress, true); + if (cpuDebug != currentDebugMIPS) + AddThreadCondition(breakpointAddress, threadID); + Core_EnableStepping(false); + } +} + +// Run until a certain address (cpu.runUntil) +// +// Parameters: +// - address: number parameter for destination. +// +// No immediate response. A cpu.stepping event will be sent once complete. +void WebSocketSteppingState::RunUntil(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) { + return req.Fail("CPU not started"); + } + + uint32_t address = 0; + if (!req.ParamU32("address", &address)) { + // Error already sent. + return; + } + + bool wasAtAddress = currentMIPS->pc == address; + PrepareResume(); + // We may have arrived already if PauseResume() stepped out of a delay slot. + if (currentMIPS->pc != address || wasAtAddress) { + CBreakPoints::AddBreakPoint(address, true); + Core_EnableStepping(false); + } +} + +// Jump after the next HLE call (cpu.nextHLE) +// +// No parameters. +// +// No immediate response. A cpu.stepping event will be sent once complete. +void WebSocketSteppingState::HLE(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) { + return req.Fail("CPU not started"); + } + + PrepareResume(); + hleDebugBreak(); + Core_EnableStepping(false); +} + +uint32_t WebSocketSteppingState::GetNextAddress(DebugInterface *cpuDebug) { + uint32_t current = disasm_.getStartAddress(cpuDebug->GetPC()); + return disasm_.getNthNextAddress(current, 1); +} + +int WebSocketSteppingState::GetNextInstructionCount(DebugInterface *cpuDebug) { + return (GetNextAddress(cpuDebug) - cpuDebug->GetPC()) / 4; +} + +void WebSocketSteppingState::PrepareResume() { + if (currentMIPS->inDelaySlot) { + Core_DoSingleStep(); + } else { + // If the current PC is on a breakpoint, the user doesn't want to do nothing. + CBreakPoints::SetSkipFirst(currentMIPS->pc); + } +} + +void WebSocketSteppingState::AddThreadCondition(uint32_t breakpointAddress, uint32_t threadID) { + BreakPointCond cond; + cond.debug = currentDebugMIPS; + cond.expressionString = StringFromFormat("threadid == 0x%08x", threadID); + if (currentDebugMIPS->initExpression(cond.expressionString.c_str(), cond.expression)) + CBreakPoints::ChangeBreakPointAddCond(breakpointAddress, cond); +} diff --git a/Core/Debugger/WebSocket/SteppingSubscriber.h b/Core/Debugger/WebSocket/SteppingSubscriber.h new file mode 100644 index 0000000000..319c608caa --- /dev/null +++ b/Core/Debugger/WebSocket/SteppingSubscriber.h @@ -0,0 +1,23 @@ +// Copyright (c) 2018- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include "Core/Debugger/WebSocket/WebSocketUtils.h" + +void *WebSocketSteppingInit(DebuggerEventHandlerMap &map); +void WebSocketSteppingShutdown(void *p); diff --git a/Core/Debugger/WebSocket/WebSocketUtils.cpp b/Core/Debugger/WebSocket/WebSocketUtils.cpp new file mode 100644 index 0000000000..3a3536684c --- /dev/null +++ b/Core/Debugger/WebSocket/WebSocketUtils.cpp @@ -0,0 +1,267 @@ +// Copyright (c) 2018- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include +#include +#include "Common/StringUtils.h" +#include "Core/Debugger/WebSocket/WebSocketUtils.h" + +JsonWriter &DebuggerRequest::Respond() { + writer_.begin(); + writer_.writeString("event", name); + DebuggerJsonAddTicket(writer_, data); + + responseBegun_ = true; + return writer_; +} + +bool DebuggerRequest::Finish() { + if (responseBegun_ && !responseSent_) { + writer_.end(); + if (responsePartial_) + ws->AddFragment(true, writer_.str()); + else + ws->Send(writer_.str()); + responseBegun_ = false; + responseSent_ = true; + responsePartial_ = false; + } + + return responseSent_; +} + +void DebuggerRequest::Flush() { + ws->AddFragment(false, writer_.flush()); + responsePartial_ = true; +} + +static bool U32FromString(const char *str, uint32_t *out, bool allowFloat) { + if (TryParse(str, out)) + return true; + + // Now let's try signed (the above parses only positive.) + if (str[0] == '-' && TryParse(&str[1], out)) { + *out = static_cast(-static_cast(*out)); + return true; + } + + // We have to try float last because we use float bits, so 1.0 != 1. + if (allowFloat) { + union { + uint32_t u; + float f; + } bits; + if (TryParse(str, &bits.f)) { + *out = bits.u; + return true; + } + + if (!strcasecmp(str, "nan")) { + *out = 0x7FC00000; + return true; + } else if (!strcasecmp(str, "infinity") || !strcasecmp(str, "inf")) { + *out = 0x7F800000; + return true; + } else if (!strcasecmp(str, "-infinity") || !strcasecmp(str, "-inf")) { + *out = 0xFF800000; + return true; + } + } + + return false; +} + +bool DebuggerRequest::HasParam(const char *name, bool ignoreNull) { + const JsonNode *node = data.get(name); + if (!node) { + return false; + } + if (node->value.getTag() == JSON_NULL) { + return !ignoreNull; + } + return true; +} + +bool DebuggerRequest::ParamU32(const char *name, uint32_t *out, bool allowFloatBits, DebuggerParamType type) { + bool allowLoose = type == DebuggerParamType::REQUIRED_LOOSE || type == DebuggerParamType::OPTIONAL_LOOSE; + bool required = type == DebuggerParamType::REQUIRED || type == DebuggerParamType::REQUIRED_LOOSE; + + const JsonNode *node = data.get(name); + if (!node) { + if (required) + Fail(StringFromFormat("Missing '%s' parameter", name)); + return !required; + } + + auto tag = node->value.getTag(); + if (tag == JSON_NUMBER) { + double val = node->value.toNumber(); + bool isInteger = trunc(val) == val; + if (!isInteger && !allowLoose) { + // JSON doesn't give a great way to differentiate ints and floats. + // Let's play it safe and require a string. + if (allowFloatBits) + Fail(StringFromFormat("Could not parse '%s' parameter: use a string for non integer values", name)); + else + Fail(StringFromFormat("Could not parse '%s' parameter: integer required", name)); + return false; + } else if (!isInteger && allowFloatBits) { + union { + float f; + uint32_t u; + } bits = { (float)val }; + *out = bits.u; + return true; + } + + if (val < 0 && val >= std::numeric_limits::min()) { + // Convert to unsigned representation. + *out = (uint32_t)(int32_t)val; + return true; + } else if (val >= 0 && val <= std::numeric_limits::max()) { + *out = (uint32_t)val; + return true; + } else if (allowLoose) { + *out = val >= 0 ? std::numeric_limits::max() : std::numeric_limits::min(); + return true; + } + + if (allowFloatBits) + Fail(StringFromFormat("Could not parse '%s' parameter: outside 32 bit range (use string for float)", name)); + else + Fail(StringFromFormat("Could not parse '%s' parameter: outside 32 bit range", name)); + return false; + } + if (tag != JSON_STRING) { + if (type == DebuggerParamType::REQUIRED || tag != JSON_NULL) { + Fail(StringFromFormat("Invalid '%s' parameter type", name)); + return false; + } + return true; + } + + if (U32FromString(node->value.toString(), out, allowFloatBits)) + return true; + + if (allowFloatBits) + Fail(StringFromFormat("Could not parse '%s' parameter: number expected", name)); + else + Fail(StringFromFormat("Could not parse '%s' parameter: integer required", name)); + return false; +} + +bool DebuggerRequest::ParamBool(const char *name, bool *out, DebuggerParamType type) { + bool allowLoose = type == DebuggerParamType::REQUIRED_LOOSE || type == DebuggerParamType::OPTIONAL_LOOSE; + bool required = type == DebuggerParamType::REQUIRED || type == DebuggerParamType::REQUIRED_LOOSE; + + const JsonNode *node = data.get(name); + if (!node) { + if (required) + Fail(StringFromFormat("Missing '%s' parameter", name)); + return !required; + } + + auto tag = node->value.getTag(); + if (tag == JSON_NUMBER) { + double val = node->value.toNumber(); + if (val == 1.0 || val == 0.0 || allowLoose) { + *out = val != 0.0; + return true; + } + + Fail(StringFromFormat("Could not parse '%s' parameter: should be true/1 or false/0", name)); + return false; + } + if (tag == JSON_TRUE) { + *out = true; + return true; + } + if (tag == JSON_FALSE) { + *out = false; + return true; + } + if (tag != JSON_STRING) { + if (type == DebuggerParamType::REQUIRED || tag != JSON_NULL) { + Fail(StringFromFormat("Invalid '%s' parameter type", name)); + return false; + } + return true; + } + + const std::string s = node->value.toString(); + if (s == "1" || s == "true") { + *out = true; + return true; + } + if (s == "0" || s == "false" || (s == "" && allowLoose)) { + *out = false; + return true; + } + + if (allowLoose) { + *out = true; + return true; + } + + Fail(StringFromFormat("Could not parse '%s' parameter: boolean required", name)); + return false; +} + +bool DebuggerRequest::ParamString(const char *name, std::string *out, DebuggerParamType type) { + bool allowLoose = type == DebuggerParamType::REQUIRED_LOOSE || type == DebuggerParamType::OPTIONAL_LOOSE; + bool required = type == DebuggerParamType::REQUIRED || type == DebuggerParamType::REQUIRED_LOOSE; + + const JsonNode *node = data.get(name); + if (!node) { + if (required) + Fail(StringFromFormat("Missing '%s' parameter", name)); + return !required; + } + + auto tag = node->value.getTag(); + if (tag == JSON_STRING) { + *out = node->value.toString(); + return true; + } else if (!allowLoose) { + if (required || tag != JSON_NULL) { + Fail(StringFromFormat("Invalid '%s' parameter type", name)); + return false; + } + return true; + } + + // For loose, let's allow a few things. + if (tag == JSON_TRUE) { + *out = "true"; + return true; + } else if (tag == JSON_FALSE) { + *out = "false"; + return true; + } else if (tag == JSON_NULL) { + if (required) { + *out = ""; + } + return true; + } else if (tag == JSON_NUMBER) { + // Will have a decimal place, though. + *out = StringFromFormat("%f", node->value.toNumber()); + return true; + } + + Fail(StringFromFormat("Invalid '%s' parameter type", name)); + return false; +} diff --git a/Core/Debugger/WebSocket/WebSocketUtils.h b/Core/Debugger/WebSocket/WebSocketUtils.h new file mode 100644 index 0000000000..e49647dcf8 --- /dev/null +++ b/Core/Debugger/WebSocket/WebSocketUtils.h @@ -0,0 +1,100 @@ +// Copyright (c) 2018- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include +#include +#include "json/json_reader.h" +#include "json/json_writer.h" +#include "net/websocket_server.h" +#include "Common/Log.h" + +static inline void DebuggerJsonAddTicket(JsonWriter &writer, const JsonGet &data) { + const JsonNode *value = data.get("ticket"); + if (value) + writer.writeRaw("ticket", json_stringify(value)); +} + +struct DebuggerErrorEvent { + DebuggerErrorEvent(const std::string m, LogTypes::LOG_LEVELS l, const JsonGet data = JsonValue(JSON_NULL)) + : message(m), level(l) { + // Need to format right away, before it's out of scope. + if (data) { + const JsonNode *value = data.get("ticket"); + if (value) + ticketRaw = json_stringify(value); + } + } + + std::string message; + LogTypes::LOG_LEVELS level; + std::string ticketRaw; + + operator std::string() { + JsonWriter j; + j.begin(); + j.writeString("event", "error"); + j.writeString("message", message); + j.writeInt("level", level); + if (!ticketRaw.empty()) { + j.writeRaw("ticket", ticketRaw); + } + j.end(); + return j.str(); + } +}; + +enum class DebuggerParamType { + REQUIRED, + OPTIONAL, + REQUIRED_LOOSE, + OPTIONAL_LOOSE, +}; + +struct DebuggerRequest { + DebuggerRequest(const char *n, net::WebSocketServer *w, const JsonGet &d) + : name(n), ws(w), data(d) { + } + + const char *name; + net::WebSocketServer *ws; + const JsonGet data; + + void Fail(const std::string &message) { + ws->Send(DebuggerErrorEvent(message, LogTypes::LERROR, data)); + responseSent_ = true; + } + + bool HasParam(const char *name, bool ignoreNull = false); + bool ParamU32(const char *name, uint32_t *out, bool allowFloatBits = false, DebuggerParamType type = DebuggerParamType::REQUIRED); + bool ParamBool(const char *name, bool *out, DebuggerParamType type = DebuggerParamType::REQUIRED); + bool ParamString(const char *name, std::string *out, DebuggerParamType type = DebuggerParamType::REQUIRED); + + JsonWriter &Respond(); + void Flush(); + bool Finish(); + +private: + JsonWriter writer_; + bool responseBegun_ = false; + bool responseSent_ = false; + bool responsePartial_ = false; +}; + +typedef std::function DebuggerEventHandler; +typedef std::unordered_map DebuggerEventHandlerMap; diff --git a/Core/Dialog/PSPSaveDialog.cpp b/Core/Dialog/PSPSaveDialog.cpp index a37184a45f..cb68396f6a 100755 --- a/Core/Dialog/PSPSaveDialog.cpp +++ b/Core/Dialog/PSPSaveDialog.cpp @@ -1019,14 +1019,14 @@ void PSPSaveDialog::ExecuteIOAction() { std::lock_guard guard(paramLock); switch (display) { case DS_LOAD_LOADING: - if (param.Load(param.GetPspParam(), GetSelectedSaveDirName(), currentSelectedSave)) { + if (param.Load(param.GetPspParam(), GetSelectedSaveDirName(), currentSelectedSave) == 0) { display = DS_LOAD_DONE; } else { display = DS_LOAD_FAILED; } break; case DS_SAVE_SAVING: - if (param.Save(param.GetPspParam(), GetSelectedSaveDirName())) { + if (param.Save(param.GetPspParam(), GetSelectedSaveDirName()) == 0) { display = DS_SAVE_DONE; } else { display = DS_SAVE_FAILED; @@ -1052,87 +1052,81 @@ void PSPSaveDialog::ExecuteIOAction() { } void PSPSaveDialog::ExecuteNotVisibleIOAction() { + auto &result = param.GetPspParam()->common.result; + switch ((SceUtilitySavedataType)(u32)param.GetPspParam()->mode) { case SCE_UTILITY_SAVEDATA_TYPE_LOAD: // Only load and exit case SCE_UTILITY_SAVEDATA_TYPE_AUTOLOAD: - if (param.Load(param.GetPspParam(), GetSelectedSaveDirName(), currentSelectedSave)) { - param.GetPspParam()->common.result = 0; - } else { - param.GetPspParam()->common.result = SCE_UTILITY_SAVEDATA_ERROR_LOAD_NO_DATA; - } + result = param.Load(param.GetPspParam(), GetSelectedSaveDirName(), currentSelectedSave); break; case SCE_UTILITY_SAVEDATA_TYPE_SAVE: // Only save and exit case SCE_UTILITY_SAVEDATA_TYPE_AUTOSAVE: - if (param.Save(param.GetPspParam(), GetSelectedSaveDirName())) { - param.GetPspParam()->common.result = 0; - } else { - param.GetPspParam()->common.result = SCE_UTILITY_SAVEDATA_ERROR_SAVE_MS_NOSPACE; - } + result = param.Save(param.GetPspParam(), GetSelectedSaveDirName()); break; case SCE_UTILITY_SAVEDATA_TYPE_SIZES: - param.GetPspParam()->common.result = param.GetSizes(param.GetPspParam()); + result = param.GetSizes(param.GetPspParam()); break; case SCE_UTILITY_SAVEDATA_TYPE_LIST: param.GetList(param.GetPspParam()); - param.GetPspParam()->common.result = 0; + result = 0; break; case SCE_UTILITY_SAVEDATA_TYPE_FILES: - param.GetPspParam()->common.result = param.GetFilesList(param.GetPspParam()); + result = param.GetFilesList(param.GetPspParam()); break; case SCE_UTILITY_SAVEDATA_TYPE_GETSIZE: { - bool result = param.GetSize(param.GetPspParam()); + bool sizeResult = param.GetSize(param.GetPspParam()); // TODO: According to JPCSP, should test/verify this part but seems edge casey. if (MemoryStick_State() != PSP_MEMORYSTICK_STATE_INSERTED) { - param.GetPspParam()->common.result = SCE_UTILITY_SAVEDATA_ERROR_RW_NO_MEMSTICK; - } else if (result) { - param.GetPspParam()->common.result = 0; + result = SCE_UTILITY_SAVEDATA_ERROR_RW_NO_MEMSTICK; + } else if (sizeResult) { + result = 0; } else { - param.GetPspParam()->common.result = SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA; + result = SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA; } } break; case SCE_UTILITY_SAVEDATA_TYPE_DELETEDATA: DEBUG_LOG(SCEUTILITY, "sceUtilitySavedata DELETEDATA: %s", param.GetPspParam()->saveName); - param.GetPspParam()->common.result = param.DeleteData(param.GetPspParam()); + result = param.DeleteData(param.GetPspParam()); break; //case SCE_UTILITY_SAVEDATA_TYPE_AUTODELETE: case SCE_UTILITY_SAVEDATA_TYPE_SINGLEDELETE: if (param.Delete(param.GetPspParam(), param.GetSelectedSave())) { - param.GetPspParam()->common.result = 0; + result = 0; } else { - param.GetPspParam()->common.result = SCE_UTILITY_SAVEDATA_ERROR_DELETE_NO_DATA; + result = SCE_UTILITY_SAVEDATA_ERROR_DELETE_NO_DATA; } break; // TODO: Should reset the directory's other files. case SCE_UTILITY_SAVEDATA_TYPE_MAKEDATA: case SCE_UTILITY_SAVEDATA_TYPE_MAKEDATASECURE: - if (param.Save(param.GetPspParam(), GetSelectedSaveDirName(), param.GetPspParam()->mode == SCE_UTILITY_SAVEDATA_TYPE_MAKEDATASECURE)) { - param.GetPspParam()->common.result = 0; + if (param.Save(param.GetPspParam(), GetSelectedSaveDirName(), param.GetPspParam()->mode == SCE_UTILITY_SAVEDATA_TYPE_MAKEDATASECURE) == 0) { + result = 0; } else if (MemoryStick_FreeSpace() == 0) { - param.GetPspParam()->common.result = SCE_UTILITY_SAVEDATA_ERROR_RW_MEMSTICK_FULL; + result = SCE_UTILITY_SAVEDATA_ERROR_RW_MEMSTICK_FULL; } else { - param.GetPspParam()->common.result = SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA; + result = SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA; } break; case SCE_UTILITY_SAVEDATA_TYPE_WRITEDATA: case SCE_UTILITY_SAVEDATA_TYPE_WRITEDATASECURE: - if (param.Save(param.GetPspParam(), GetSelectedSaveDirName(), param.GetPspParam()->mode == SCE_UTILITY_SAVEDATA_TYPE_WRITEDATASECURE)) { - param.GetPspParam()->common.result = 0; + if (param.Save(param.GetPspParam(), GetSelectedSaveDirName(), param.GetPspParam()->mode == SCE_UTILITY_SAVEDATA_TYPE_WRITEDATASECURE) == 0) { + result = 0; } else { - param.GetPspParam()->common.result = SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA; + result = SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA; } break; case SCE_UTILITY_SAVEDATA_TYPE_READDATA: case SCE_UTILITY_SAVEDATA_TYPE_READDATASECURE: if (!param.IsSaveDirectoryExist(param.GetPspParam())){ - param.GetPspParam()->common.result = SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA; + result = SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA; } else if (!param.IsSfoFileExist(param.GetPspParam())) { - param.GetPspParam()->common.result = SCE_UTILITY_SAVEDATA_ERROR_RW_DATA_BROKEN; - } else if (param.Load(param.GetPspParam(), GetSelectedSaveDirName(), currentSelectedSave, param.GetPspParam()->mode == SCE_UTILITY_SAVEDATA_TYPE_READDATASECURE)) { - param.GetPspParam()->common.result = 0; + result = SCE_UTILITY_SAVEDATA_ERROR_RW_DATA_BROKEN; + } else if (param.Load(param.GetPspParam(), GetSelectedSaveDirName(), currentSelectedSave, param.GetPspParam()->mode == SCE_UTILITY_SAVEDATA_TYPE_READDATASECURE) == 0) { + result = 0; } else { - param.GetPspParam()->common.result = SCE_UTILITY_SAVEDATA_ERROR_RW_FILE_NOT_FOUND; + result = SCE_UTILITY_SAVEDATA_ERROR_RW_FILE_NOT_FOUND; } break; default: diff --git a/Core/Dialog/SavedataParam.cpp b/Core/Dialog/SavedataParam.cpp index ff5a6d964a..3182f3b3df 100644 --- a/Core/Dialog/SavedataParam.cpp +++ b/Core/Dialog/SavedataParam.cpp @@ -358,9 +358,19 @@ int SavedataParam::DeleteData(SceUtilitySavedataParam* param) { return 0; } -bool SavedataParam::Save(SceUtilitySavedataParam* param, const std::string &saveDirName, bool secureMode) { +int SavedataParam::Save(SceUtilitySavedataParam* param, const std::string &saveDirName, bool secureMode) { if (!param) { - return false; + return SCE_UTILITY_SAVEDATA_ERROR_SAVE_MS_NOSPACE; + } + if (param->secureVersion > 3) { + ERROR_LOG_REPORT(SCEUTILITY, "Savedata version requested on save: %d", param->secureVersion); + return SCE_UTILITY_SAVEDATA_ERROR_SAVE_PARAM; + } else if (param->secureVersion != 0) { + if (param->secureVersion != 1 && !HasKey(param)) { + ERROR_LOG_REPORT(SCEUTILITY, "Savedata version with missing key on save: %d", param->secureVersion); + return SCE_UTILITY_SAVEDATA_ERROR_SAVE_PARAM; + } + WARN_LOG_REPORT(SCEUTILITY, "Savedata version requested on save: %d", param->secureVersion); } std::string dirPath = GetSaveFilePath(param, GetSaveDir(param, saveDirName)); @@ -390,8 +400,13 @@ bool SavedataParam::Save(SceUtilitySavedataParam* param, const std::string &save memcpy(cryptedData, data_, cryptedSize); int decryptMode = DetermineCryptMode(param); - if (EncryptData(decryptMode, cryptedData, &cryptedSize, &aligned_len, cryptedHash, (HasKey(param) ? param->key : 0)) != 0) - { + bool hasKey = decryptMode > 1; + if (hasKey && !HasKey(param)) { + delete[] cryptedData; + return SCE_UTILITY_SAVEDATA_ERROR_SAVE_PARAM; + } + + if (EncryptData(decryptMode, cryptedData, &cryptedSize, &aligned_len, cryptedHash, (hasKey ? param->key : 0)) != 0) { I18NCategory *err = GetI18NCategory("Error"); host->NotifyUserMessage(err->T("Save encryption failed. This save won't work on real PSP"), 6.0f); ERROR_LOG(SCEUTILITY,"Save encryption failed. This save won't work on real PSP"); @@ -420,39 +435,31 @@ bool SavedataParam::Save(SceUtilitySavedataParam* param, const std::string &save sfoFile.SetValue("SAVEDATA_DIRECTORY", GetSaveDir(param, saveDirName), 64); // For each file, 13 bytes for filename, 16 bytes for file hash (0 in PPSSPP), 3 byte for padding - if (secureMode) - { - const int FILE_LIST_ITEM_SIZE = 13 + 16 + 3; + if (secureMode) { const int FILE_LIST_COUNT_MAX = 99; - const u32 FILE_LIST_TOTAL_SIZE = FILE_LIST_ITEM_SIZE * FILE_LIST_COUNT_MAX; + const u32 FILE_LIST_TOTAL_SIZE = sizeof(SaveSFOFileListEntry) * FILE_LIST_COUNT_MAX; u32 tmpDataSize = 0; - u8 *tmpDataOrig = sfoFile.GetValueData("SAVEDATA_FILE_LIST", &tmpDataSize); - u8 *tmpData = new u8[FILE_LIST_TOTAL_SIZE]; + SaveSFOFileListEntry *tmpDataOrig = (SaveSFOFileListEntry *)sfoFile.GetValueData("SAVEDATA_FILE_LIST", &tmpDataSize); + SaveSFOFileListEntry *updatedList = new SaveSFOFileListEntry[FILE_LIST_COUNT_MAX]; + if (tmpDataSize != 0) + memcpy(updatedList, tmpDataOrig, std::min(tmpDataSize, FILE_LIST_TOTAL_SIZE)); + if (tmpDataSize < FILE_LIST_TOTAL_SIZE) + memset(updatedList + tmpDataSize, 0, FILE_LIST_TOTAL_SIZE - tmpDataSize); - if (tmpDataOrig != NULL) - memcpy(tmpData, tmpDataOrig, tmpDataSize > FILE_LIST_TOTAL_SIZE ? FILE_LIST_TOTAL_SIZE : tmpDataSize); - else - memset(tmpData, 0, FILE_LIST_TOTAL_SIZE); + if (param->dataBuf.IsValid()) { + const std::string saveFilename = GetFileName(param); + for (auto entry = updatedList; entry < updatedList + FILE_LIST_COUNT_MAX; ++entry) { + if (entry->filename[0] != '\0') { + if (strncmp(entry->filename, saveFilename.c_str(), sizeof(entry->filename)) != 0) + continue; + } - if (param->dataBuf.IsValid()) - { - char *fName = (char*)tmpData; - for(int i = 0; i < FILE_LIST_COUNT_MAX; i++) - { - if(fName[0] == 0) - break; // End of list - if(strncmp(fName,GetFileName(param).c_str(),20) == 0) - break; - fName += FILE_LIST_ITEM_SIZE; + snprintf(entry->filename, sizeof(entry->filename), "%s", saveFilename.c_str()); + memcpy(entry->hash, cryptedHash, 16); } - - if (fName + 13 <= (char*)tmpData + FILE_LIST_TOTAL_SIZE) - snprintf(fName, 13, "%s",GetFileName(param).c_str()); - if (fName + 13 + 16 <= (char*)tmpData + FILE_LIST_TOTAL_SIZE) - memcpy(fName+13, cryptedHash, 16); } - sfoFile.SetValue("SAVEDATA_FILE_LIST", tmpData, FILE_LIST_TOTAL_SIZE, (int)FILE_LIST_TOTAL_SIZE); - delete[] tmpData; + sfoFile.SetValue("SAVEDATA_FILE_LIST", (u8 *)updatedList, FILE_LIST_TOTAL_SIZE, (int)FILE_LIST_TOTAL_SIZE); + delete[] updatedList; } // Init param with 0. This will be used to detect crypted save or not on loading @@ -502,11 +509,8 @@ bool SavedataParam::Save(SceUtilitySavedataParam* param, const std::string &save if (!WritePSPFile(filePath, data_, saveSize)) { ERROR_LOG(SCEUTILITY,"Error writing file %s",filePath.c_str()); - if(cryptedData != 0) - { - delete[] cryptedData; - } - return false; + delete[] cryptedData; + return SCE_UTILITY_SAVEDATA_ERROR_SAVE_MS_NOSPACE; } delete[] cryptedData; } @@ -538,23 +542,23 @@ bool SavedataParam::Save(SceUtilitySavedataParam* param, const std::string &save WritePSPFile(snd0path, param->snd0FileData.buf, param->snd0FileData.bufSize); } - return true; + return 0; } -bool SavedataParam::Load(SceUtilitySavedataParam *param, const std::string &saveDirName, int saveId, bool secureMode) -{ +int SavedataParam::Load(SceUtilitySavedataParam *param, const std::string &saveDirName, int saveId, bool secureMode) { if (!param) { - return false; + return SCE_UTILITY_SAVEDATA_ERROR_LOAD_NO_DATA; } std::string dirPath = GetSaveFilePath(param, GetSaveDir(param, saveDirName)); std::string filePath = dirPath + "/" + GetFileName(param); if (!pspFileSystem.GetFileInfo(filePath).exists) { - return false; + return SCE_UTILITY_SAVEDATA_ERROR_LOAD_NO_DATA; } - if(!LoadSaveData(param, saveDirName, dirPath, secureMode)) // Load main savedata - return false; + int result = LoadSaveData(param, saveDirName, dirPath, secureMode); + if (result != 0) + return result; LoadSFO(param, dirPath); // Load sfo @@ -572,19 +576,30 @@ bool SavedataParam::Load(SceUtilitySavedataParam *param, const std::string &save // Load SND0.AT3 LoadFile(dirPath, SND0_FILENAME, ¶m->snd0FileData); - return true; + return 0; } -bool SavedataParam::LoadSaveData(SceUtilitySavedataParam *param, const std::string &saveDirName, const std::string& dirPath, bool secureMode) { +int SavedataParam::LoadSaveData(SceUtilitySavedataParam *param, const std::string &saveDirName, const std::string &dirPath, bool secureMode) { + if (param->secureVersion > 3) { + ERROR_LOG_REPORT(SCEUTILITY, "Savedata version requested: %d", param->secureVersion); + return SCE_UTILITY_SAVEDATA_ERROR_LOAD_PARAM; + } else if (param->secureVersion != 0) { + if (param->secureVersion != 1 && !HasKey(param)) { + ERROR_LOG_REPORT(SCEUTILITY, "Savedata version with missing key: %d", param->secureVersion); + return SCE_UTILITY_SAVEDATA_ERROR_LOAD_PARAM; + } + WARN_LOG_REPORT(SCEUTILITY, "Savedata version requested: %d", param->secureVersion); + } u8 *data_ = param->dataBuf; - std::string filePath = dirPath+"/"+GetFileName(param); + std::string filename = GetFileName(param); + std::string filePath = dirPath + "/" + filename; s64 readSize; INFO_LOG(SCEUTILITY,"Loading file with size %u in %s",param->dataBufSize,filePath.c_str()); - u8* saveData = 0; + u8 *saveData = nullptr; int saveSize = -1; if (!ReadPSPFile(filePath, &saveData, saveSize, &readSize)) { ERROR_LOG(SCEUTILITY,"Error reading file %s",filePath.c_str()); - return false; + return SCE_UTILITY_SAVEDATA_ERROR_LOAD_NO_DATA; } saveSize = (int)readSize; @@ -595,7 +610,13 @@ bool SavedataParam::LoadSaveData(SceUtilitySavedataParam *param, const std::stri bool isCrypted = prevCryptMode != 0 && secureMode; bool saveDone = false; if (isCrypted) { - LoadCryptedSave(param, data_, saveData, saveSize, prevCryptMode, saveDone); + if (DetermineCryptMode(param) > 1 && !HasKey(param)) + return SCE_UTILITY_SAVEDATA_ERROR_LOAD_PARAM; + + u8 hash[16]; + bool hasExpectedHash = GetExpectedHash(dirPath, filename, hash); + LoadCryptedSave(param, data_, saveData, saveSize, prevCryptMode, hasExpectedHash ? hash : nullptr, saveDone); + // TODO: Should return SCE_UTILITY_SAVEDATA_ERROR_LOAD_DATA_BROKEN here if !saveDone. } if (!saveDone) { LoadNotCryptedSave(param, data_, saveData, saveSize); @@ -603,36 +624,43 @@ bool SavedataParam::LoadSaveData(SceUtilitySavedataParam *param, const std::stri param->dataSize = (SceSize)saveSize; delete[] saveData; - return true; + return 0; } int SavedataParam::DetermineCryptMode(const SceUtilitySavedataParam *param) const { int decryptMode = 1; - if (HasKey(param)) { + if (param->secureVersion == 1) { + decryptMode = 1; + } else if (param->secureVersion == 2) { + decryptMode = 3; + } else if (param->secureVersion == 3) { + decryptMode = GetSDKMainVersion(sceKernelGetCompiledSdkVersion()) >= 4 ? 5 : 1; + } else if (HasKey(param)) { + // TODO: This should ignore HasKey(), which would trigger errors. Not doing that yet to play it safe. decryptMode = GetSDKMainVersion(sceKernelGetCompiledSdkVersion()) >= 4 ? 5 : 3; } return decryptMode; } -void SavedataParam::LoadCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8 *saveData, int &saveSize, int prevCryptMode, bool &saveDone) { +void SavedataParam::LoadCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8 *saveData, int &saveSize, int prevCryptMode, const u8 *expectedHash, bool &saveDone) { int align_len = align16(saveSize); u8 *data_base = new u8[align_len]; u8 *cryptKey = new u8[0x10]; memset(cryptKey, 0, 0x10); - bool hasKey = HasKey(param); + int decryptMode = DetermineCryptMode(param); + const int detectedMode = decryptMode; + bool hasKey = decryptMode > 1; if (hasKey) { memcpy(cryptKey, param->key, 0x10); } memset(data_base + saveSize, 0, align_len - saveSize); memcpy(data_base, saveData, saveSize); - int decryptMode = DetermineCryptMode(param); if (decryptMode != prevCryptMode) { if (prevCryptMode == 1 && param->key[0] == 0) { // Backwards compat for a bug we used to have. WARN_LOG(SCEUTILITY, "Savedata loading with hashmode %d instead of detected %d", prevCryptMode, decryptMode); - hasKey = false; decryptMode = prevCryptMode; // Don't notify the user if we're not going to upgrade the save. @@ -654,9 +682,22 @@ void SavedataParam::LoadCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8 host->NotifyUserMessage(di->T("Old savedata detected"), 6.0f); } } + hasKey = decryptMode > 1; } - if (DecryptSave(decryptMode, data_base, &saveSize, &align_len, (hasKey?cryptKey:0)) == 0) { + int err = DecryptSave(decryptMode, data_base, &saveSize, &align_len, hasKey ? cryptKey : nullptr, expectedHash); + // Perhaps the file had the wrong mode.... + if (err != 0 && detectedMode != decryptMode) { + hasKey = detectedMode > 1; + err = DecryptSave(detectedMode, data_base, &saveSize, &align_len, hasKey ? cryptKey : nullptr, expectedHash); + } + // TODO: Should return an error, but let's just try with a bad hash. + if (err != 0 && expectedHash != nullptr) { + WARN_LOG(SCEUTILITY, "Incorrect hash on save data, likely corrupt"); + err = DecryptSave(decryptMode, data_base, &saveSize, &align_len, hasKey ? cryptKey : nullptr, nullptr); + } + + if (err == 0) { if (param->dataBuf.IsValid()) memcpy(data, data_base, std::min((u32)saveSize, (u32)param->dataBufSize)); saveDone = true; @@ -689,39 +730,54 @@ void SavedataParam::LoadSFO(SceUtilitySavedataParam *param, const std::string& d } } -std::set SavedataParam::getSecureFileNames(std::string dirPath) { - PSPFileInfo sfoFileInfo = pspFileSystem.GetFileInfo(dirPath + "/" + SFO_FILENAME); - std::set secureFileNames; - if (!sfoFileInfo.exists) - return secureFileNames; +std::vector SavedataParam::GetSFOEntries(const std::string &dirPath) { + std::vector result; + const std::string sfoPath = dirPath + "/" + SFO_FILENAME; + if (!pspFileSystem.GetFileInfo(sfoPath).exists) + return result; ParamSFOData sfoFile; std::vector sfoData; - if (pspFileSystem.ReadEntireFile(dirPath + "/" + SFO_FILENAME, sfoData) >= 0) { + if (pspFileSystem.ReadEntireFile(dirPath + "/" + SFO_FILENAME, sfoData) >= 0) sfoFile.ReadSFO(sfoData); + + const int FILE_LIST_COUNT_MAX = 99; + u32 sfoFileListSize = 0; + SaveSFOFileListEntry *sfoFileList = (SaveSFOFileListEntry *)sfoFile.GetValueData("SAVEDATA_FILE_LIST", &sfoFileListSize); + const u32 count = std::min((u32)FILE_LIST_COUNT_MAX, sfoFileListSize / (u32)sizeof(SaveSFOFileListEntry)); + + for (u32 i = 0; i < count; ++i) { + if (sfoFileList[i].filename[0] != '\0') + result.push_back(sfoFileList[i]); } - u32 sfoFileListSize = 0; - char *sfoFileList = (char *)sfoFile.GetValueData("SAVEDATA_FILE_LIST", &sfoFileListSize); - const int FILE_LIST_ITEM_SIZE = 13 + 16 + 3; - const u32 FILE_LIST_COUNT_MAX = 99; + return result; +} - // Filenames are 13 bytes long at most. Add a NULL so there's no surprises. - char temp[14]; - temp[13] = '\0'; +std::set SavedataParam::GetSecureFileNames(const std::string &dirPath) { + auto entries = GetSFOEntries(dirPath); - for (u32 i = 0; i < FILE_LIST_COUNT_MAX; ++i) { - // Ends at a NULL filename. - if (i * FILE_LIST_ITEM_SIZE >= sfoFileListSize || sfoFileList[i * FILE_LIST_ITEM_SIZE] == '\0') { - break; - } - - strncpy(temp, &sfoFileList[i * FILE_LIST_ITEM_SIZE], 13); + std::set secureFileNames; + for (auto entry : entries) { + char temp[14]; + truncate_cpy(temp, entry.filename); secureFileNames.insert(temp); } return secureFileNames; } +bool SavedataParam::GetExpectedHash(const std::string &dirPath, const std::string &filename, u8 hash[16]) { + auto entries = GetSFOEntries(dirPath); + + for (auto entry : entries) { + if (strncmp(entry.filename, filename.c_str(), sizeof(entry.filename)) == 0) { + memcpy(hash, entry.hash, sizeof(entry.hash)); + return true; + } + } + return false; +} + void SavedataParam::LoadFile(const std::string& dirPath, const std::string& filename, PspUtilitySavedataFileData *fileData) { std::string filePath = dirPath + "/" + filename; s64 readSize = -1; @@ -784,13 +840,7 @@ int SavedataParam::EncryptData(unsigned int mode, return 0; } -int SavedataParam::DecryptSave(unsigned int mode, - unsigned char *data, - int *dataLen, - int *alignedLen, - unsigned char *cryptkey) -{ - +int SavedataParam::DecryptSave(unsigned int mode, unsigned char *data, int *dataLen, int *alignedLen, unsigned char *cryptkey, const u8 *expectedHash) { pspChnnlsvContext1 ctx1; pspChnnlsvContext2 ctx2; @@ -820,6 +870,14 @@ int SavedataParam::DecryptSave(unsigned int mode, if (sceChnnlsv_21BE78B4_(ctx2) < 0) return -7; + if (expectedHash) { + u8 hash[16]; + if (sceSdGetLastIndex_(ctx1, hash, cryptkey) < 0) + return -7; + if (memcmp(hash, expectedHash, sizeof(hash)) != 0) + return -8; + } + /* The decrypted data starts at data + 0x10, so shift it back. */ memmove(data, data + 0x10, *dataLen); return 0; @@ -1122,7 +1180,7 @@ int SavedataParam::GetFilesList(SceUtilitySavedataParam *param) std::set secureFilenames; if (sfoFileInfo.exists) { - secureFilenames = getSecureFileNames(dirPath); + secureFilenames = GetSecureFileNames(dirPath); } else { return SCE_UTILITY_SAVEDATA_ERROR_RW_DATA_BROKEN; } diff --git a/Core/Dialog/SavedataParam.h b/Core/Dialog/SavedataParam.h index 0ea0e4ae61..0a26f1aeb1 100644 --- a/Core/Dialog/SavedataParam.h +++ b/Core/Dialog/SavedataParam.h @@ -253,7 +253,7 @@ struct SceUtilitySavedataParam u8 key[16]; - s32_le secureVersion; + u32_le secureVersion; s32_le multiStatus; // Function 11 LIST @@ -293,6 +293,12 @@ struct SaveFileInfo void DoState(PointerWrap &p); }; +struct SaveSFOFileListEntry { + char filename[13]; + u8 hash[16]; + u8 pad[3]; +}; + class SavedataParam { public: @@ -306,8 +312,8 @@ public: std::string GetSaveDir(const SceUtilitySavedataParam *param, const std::string &saveDirName) const; bool Delete(SceUtilitySavedataParam* param, int saveId = -1); int DeleteData(SceUtilitySavedataParam* param); - bool Save(SceUtilitySavedataParam* param, const std::string &saveDirName, bool secureMode = true); - bool Load(SceUtilitySavedataParam* param, const std::string &saveDirName, int saveId = -1, bool secureMode = true); + int Save(SceUtilitySavedataParam* param, const std::string &saveDirName, bool secureMode = true); + int Load(SceUtilitySavedataParam* param, const std::string &saveDirName, int saveId = -1, bool secureMode = true); int GetSizes(SceUtilitySavedataParam* param); bool GetList(SceUtilitySavedataParam* param); int GetFilesList(SceUtilitySavedataParam* param); @@ -354,19 +360,21 @@ private: void SetFileInfo(SaveFileInfo &saveInfo, PSPFileInfo &info, std::string saveName); void ClearFileInfo(SaveFileInfo &saveInfo, const std::string &saveName); - bool LoadSaveData(SceUtilitySavedataParam *param, const std::string &saveDirName, const std::string& dirPath, bool secureMode); - void LoadCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8 *saveData, int &saveSize, int prevCryptMode, bool &saveDone); + int LoadSaveData(SceUtilitySavedataParam *param, const std::string &saveDirName, const std::string& dirPath, bool secureMode); + void LoadCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8 *saveData, int &saveSize, int prevCryptMode, const u8 *expectedHash, bool &saveDone); void LoadNotCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8 *saveData, int &saveSize); void LoadSFO(SceUtilitySavedataParam *param, const std::string& dirPath); void LoadFile(const std::string& dirPath, const std::string& filename, PspUtilitySavedataFileData *fileData); - int DecryptSave(unsigned int mode, unsigned char *data, int *dataLen, int *alignedLen, unsigned char *cryptkey); + int DecryptSave(unsigned int mode, unsigned char *data, int *dataLen, int *alignedLen, unsigned char *cryptkey, const u8 *expectedHash); int EncryptData(unsigned int mode, unsigned char *data, int *dataLen, int *alignedLen, unsigned char *hash, unsigned char *cryptkey); int UpdateHash(u8* sfoData, int sfoSize, int sfoDataParamsOffset, int encryptmode); int BuildHash(unsigned char *output, unsigned char *data, unsigned int len, unsigned int alignedLen, int mode, unsigned char *cryptkey); int DetermineCryptMode(const SceUtilitySavedataParam *param) const; - std::set getSecureFileNames(std::string dirPath); + std::vector GetSFOEntries(const std::string &dirPath); + std::set GetSecureFileNames(const std::string &dirPath); + bool GetExpectedHash(const std::string &dirPath, const std::string &filename, u8 hash[16]); SceUtilitySavedataParam* pspParam; int selectedSave; diff --git a/Core/ELF/ElfReader.cpp b/Core/ELF/ElfReader.cpp index 68f7531e2d..942236c68a 100644 --- a/Core/ELF/ElfReader.cpp +++ b/Core/ELF/ElfReader.cpp @@ -18,7 +18,7 @@ #include "Core/MemMap.h" #include "Core/Reporting.h" #include "Core/MIPS/MIPSTables.h" -#include "ElfReader.h" +#include "Core/ELF/ElfReader.h" #include "Core/Debugger/Breakpoints.h" #include "Core/Debugger/SymbolMap.h" #include "Core/HLE/sceKernelMemory.h" diff --git a/Core/FileSystems/DirectoryFileSystem.cpp b/Core/FileSystems/DirectoryFileSystem.cpp index dc1f6a1ac2..71e91d06d8 100644 --- a/Core/FileSystems/DirectoryFileSystem.cpp +++ b/Core/FileSystems/DirectoryFileSystem.cpp @@ -181,8 +181,10 @@ bool DirectoryFileHandle::Open(std::string &basePath, std::string &fileName, Fil #if HOST_IS_CASE_SENSITIVE if (access & (FILEACCESS_APPEND|FILEACCESS_CREATE|FILEACCESS_WRITE)) { DEBUG_LOG(FILESYS, "Checking case for path %s", fileName.c_str()); - if (!FixPathCase(basePath, fileName, FPC_PATH_MUST_EXIST) ) + if (!FixPathCase(basePath, fileName, FPC_PATH_MUST_EXIST)) { + error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND; return false; // or go on and attempt (for a better error code than just 0?) + } } // else we try fopen first (in case we're lucky) before simulating case insensitivity #endif @@ -250,6 +252,8 @@ bool DirectoryFileHandle::Open(std::string &basePath, std::string &fileName, Fil I18NCategory *err = GetI18NCategory("Error"); host->NotifyUserMessage(err->T("Disk full while writing data")); error = SCE_KERNEL_ERROR_ERRNO_NO_PERM; + } else if (!success) { + error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND; } } #else @@ -310,6 +314,8 @@ bool DirectoryFileHandle::Open(std::string &basePath, std::string &fileName, Fil I18NCategory *err = GetI18NCategory("Error"); host->NotifyUserMessage(err->T("Disk full while writing data")); error = SCE_KERNEL_ERROR_ERRNO_NO_PERM; + } else { + error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND; } #endif @@ -597,7 +603,7 @@ u32 DirectoryFileSystem::OpenFile(std::string filename, FileAccess access, const u32 err = 0; bool success = entry.hFile.Open(basePath, filename, access, err); if (err == 0 && !success) { - err = -1; + err = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND; } err = ReplayApplyDisk(ReplayAction::FILE_OPEN, err, CoreTiming::GetGlobalTimeUs()); diff --git a/Core/HLE/HLE.cpp b/Core/HLE/HLE.cpp index 01fdfa6c61..b0bf0a06f6 100644 --- a/Core/HLE/HLE.cpp +++ b/Core/HLE/HLE.cpp @@ -265,7 +265,7 @@ void hleCheckCurrentCallbacks() void hleReSchedule(const char *reason) { #ifdef _DEBUG - _dbg_assert_msg_(HLE, reason != 0 && strlen(reason) < 256, "hleReSchedule: Invalid or too long reason."); + _dbg_assert_msg_(HLE, reason != nullptr && strlen(reason) < 256, "hleReSchedule: Invalid or too long reason."); #endif hleAfterSyscall |= HLE_AFTER_RESCHED; diff --git a/Core/HLE/KernelThreadDebugInterface.h b/Core/HLE/KernelThreadDebugInterface.h new file mode 100644 index 0000000000..012a93fc37 --- /dev/null +++ b/Core/HLE/KernelThreadDebugInterface.h @@ -0,0 +1,90 @@ +// Copyright (c) 2018- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include +#include "Core/HLE/sceKernelThread.h" +#include "Core/MIPS/MIPSDebugInterface.h" + +class KernelThreadDebugInterface : public MIPSDebugInterface { +public: + KernelThreadDebugInterface(MIPSState *c, ThreadContext &t) : MIPSDebugInterface(c), ctx(t) { + } + + unsigned int getPC() override { return ctx.pc; } + void setPC(unsigned int address) override { ctx.pc = address; } + + u32 GetGPR32Value(int reg) override { return ctx.r[reg]; } + u32 GetPC() override { return ctx.pc; } + u32 GetLR() override { return ctx.r[MIPS_REG_RA]; } + void SetPC(u32 _pc) override { ctx.pc = _pc; } + + void PrintRegValue(int cat, int index, char *out) override { + switch (cat) { + case 0: sprintf(out, "%08X", ctx.r[index]); break; + case 1: sprintf(out, "%f", ctx.f[index]); break; + case 2: sprintf(out, "N/A"); break; + } + } + + u32 GetHi() override { + return ctx.hi; + } + + u32 GetLo() override { + return ctx.lo; + } + + void SetHi(u32 val) override { + ctx.hi = val; + } + + void SetLo(u32 val) override { + ctx.lo = val; + } + + u32 GetRegValue(int cat, int index) override { + switch (cat) { + case 0: return ctx.r[index]; + case 1: return ctx.fi[index]; + case 2: return ctx.vi[voffset[index]]; + default: return 0; + } + } + + void SetRegValue(int cat, int index, u32 value) override { + switch (cat) { + case 0: + if (index != 0) + ctx.r[index] = value; + break; + + case 1: + ctx.fi[index] = value; + break; + + case 2: + ctx.vi[voffset[index]] = value; + break; + + default: + break; + } + } + +protected: + ThreadContext &ctx; +}; diff --git a/Core/HLE/ReplaceTables.cpp b/Core/HLE/ReplaceTables.cpp index 8dd52b2dcd..dec3f24636 100644 --- a/Core/HLE/ReplaceTables.cpp +++ b/Core/HLE/ReplaceTables.cpp @@ -152,10 +152,10 @@ static int Replace_memcpy() { } } RETURN(destPtr); -#ifndef MOBILE_DEVICE + CBreakPoints::ExecMemCheck(srcPtr, false, bytes, currentMIPS->pc); CBreakPoints::ExecMemCheck(destPtr, true, bytes, currentMIPS->pc); -#endif + return 10 + bytes / 4; // approximation } @@ -194,10 +194,10 @@ static int Replace_memcpy_jak() { currentMIPS->r[MIPS_REG_A2] = 0; currentMIPS->r[MIPS_REG_A3] = destPtr + bytes; RETURN(destPtr); -#ifndef MOBILE_DEVICE + CBreakPoints::ExecMemCheck(srcPtr, false, bytes, currentMIPS->pc); CBreakPoints::ExecMemCheck(destPtr, true, bytes, currentMIPS->pc); -#endif + return 5 + bytes * 8 + 2; // approximation. This is a slow memcpy - a byte copy loop.. } @@ -222,10 +222,10 @@ static int Replace_memcpy16() { } } RETURN(destPtr); -#ifndef MOBILE_DEVICE + CBreakPoints::ExecMemCheck(srcPtr, false, bytes, currentMIPS->pc); CBreakPoints::ExecMemCheck(destPtr, true, bytes, currentMIPS->pc); -#endif + return 10 + bytes / 4; // approximation } @@ -260,10 +260,10 @@ static int Replace_memcpy_swizzled() { } RETURN(0); -#ifndef MOBILE_DEVICE + CBreakPoints::ExecMemCheck(srcPtr, false, pitch * h, currentMIPS->pc); CBreakPoints::ExecMemCheck(destPtr, true, pitch * h, currentMIPS->pc); -#endif + return 10 + (pitch * h) / 4; // approximation } @@ -288,10 +288,10 @@ static int Replace_memmove() { } } RETURN(destPtr); -#ifndef MOBILE_DEVICE + CBreakPoints::ExecMemCheck(srcPtr, false, bytes, currentMIPS->pc); CBreakPoints::ExecMemCheck(destPtr, true, bytes, currentMIPS->pc); -#endif + return 10 + bytes / 4; // approximation } @@ -310,9 +310,9 @@ static int Replace_memset() { } } RETURN(destPtr); -#ifndef MOBILE_DEVICE + CBreakPoints::ExecMemCheck(destPtr, true, bytes, currentMIPS->pc); -#endif + return 10 + bytes / 4; // approximation } @@ -342,9 +342,8 @@ static int Replace_memset_jak() { currentMIPS->r[MIPS_REG_A3] = -1; RETURN(destPtr); -#ifndef MOBILE_DEVICE CBreakPoints::ExecMemCheck(destPtr, true, bytes, currentMIPS->pc); -#endif + return 5 + bytes * 6 + 2; // approximation (hm, inspecting the disasm this should be 5 + 6 * bytes + 2, but this is what works..) } @@ -591,11 +590,9 @@ static int Replace_dl_write_matrix() { #endif } -#ifndef MOBILE_DEVICE CBreakPoints::ExecMemCheck(PARAM(2), false, count * sizeof(float), currentMIPS->pc); CBreakPoints::ExecMemCheck(PARAM(0) + 2 * sizeof(u32), true, sizeof(u32), currentMIPS->pc); CBreakPoints::ExecMemCheck(dlStruct[2], true, (count + 1) * sizeof(u32), currentMIPS->pc); -#endif dlStruct[2] += (1 + count) * 4; RETURN(dlStruct[2]); diff --git a/Core/HLE/__sceAudio.cpp b/Core/HLE/__sceAudio.cpp index 86111980f1..b5cdbe61c5 100644 --- a/Core/HLE/__sceAudio.cpp +++ b/Core/HLE/__sceAudio.cpp @@ -160,6 +160,7 @@ void __AudioDoState(PointerWrap &p) { p.Do(mixFrequency); + // TODO: This never happens because maxVer=1. if (s >= 2) { resampler.DoState(p); } else { diff --git a/Core/HLE/sceCtrl.cpp b/Core/HLE/sceCtrl.cpp index 1ef00dbdaf..1b552a4191 100644 --- a/Core/HLE/sceCtrl.cpp +++ b/Core/HLE/sceCtrl.cpp @@ -503,18 +503,20 @@ static int sceCtrlPeekBufferNegative(u32 ctrlDataPtr, u32 nBufs) return done; } -void __CtrlWriteUserLatch(CtrlLatch *userLatch) { +static void __CtrlWriteUserLatch(CtrlLatch *userLatch, int bufs) { *userLatch = latch; userLatch->btnBreak &= CTRL_MASK_USER; userLatch->btnMake &= CTRL_MASK_USER; userLatch->btnPress &= CTRL_MASK_USER; - userLatch->btnRelease &= CTRL_MASK_USER; + if (bufs > 0) { + userLatch->btnRelease |= CTRL_MASK_USER; + } } static u32 sceCtrlPeekLatch(u32 latchDataPtr) { auto userLatch = PSPPointer::Create(latchDataPtr); if (userLatch.IsValid()) { - __CtrlWriteUserLatch(userLatch); + __CtrlWriteUserLatch(userLatch, ctrlLatchBufs); } return hleLogSuccessI(SCECTRL, ctrlLatchBufs); } @@ -522,7 +524,7 @@ static u32 sceCtrlPeekLatch(u32 latchDataPtr) { static u32 sceCtrlReadLatch(u32 latchDataPtr) { auto userLatch = PSPPointer::Create(latchDataPtr); if (userLatch.IsValid()) { - __CtrlWriteUserLatch(userLatch); + __CtrlWriteUserLatch(userLatch, ctrlLatchBufs); } return hleLogSuccessI(SCECTRL, __CtrlResetLatch()); } diff --git a/Core/HLE/sceDisplay.cpp b/Core/HLE/sceDisplay.cpp index dfa757ecf0..f84d180ea5 100644 --- a/Core/HLE/sceDisplay.cpp +++ b/Core/HLE/sceDisplay.cpp @@ -476,17 +476,17 @@ void __DisplayGetDebugStats(char *stats, size_t bufsize) { statbuf); } -enum { - FPS_LIMIT_NORMAL = 0, - FPS_LIMIT_CUSTOM = 1, -}; + void __DisplaySetWasPaused() { wasPaused = true; } static bool FrameTimingThrottled() { - if (PSP_CoreParameter().fpsLimit == FPS_LIMIT_CUSTOM && g_Config.iFpsLimit == 0) { + if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM1 && g_Config.iFpsLimit1 == 0) { + return false; + } + if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM2 && g_Config.iFpsLimit2 == 0) { return false; } return !PSP_CoreParameter().unthrottle; @@ -505,7 +505,7 @@ static void DoFrameDropLogging(float scaledTimestep) { // Let's collect all the throttling and frameskipping logic here. static void DoFrameTiming(bool &throttle, bool &skipFrame, float timestep) { PROFILE_THIS_SCOPE("timing"); - int fpsLimiter = PSP_CoreParameter().fpsLimit; + FPSLimit fpsLimiter = PSP_CoreParameter().fpsLimit; throttle = FrameTimingThrottled(); skipFrame = false; @@ -528,8 +528,10 @@ static void DoFrameTiming(bool &throttle, bool &skipFrame, float timestep) { time_update(); float scaledTimestep = timestep; - if (fpsLimiter == FPS_LIMIT_CUSTOM && g_Config.iFpsLimit != 0) { - scaledTimestep *= 60.0f / g_Config.iFpsLimit; + if (fpsLimiter == FPSLimit::CUSTOM1 && g_Config.iFpsLimit1 > 0) { + scaledTimestep *= 60.0f / g_Config.iFpsLimit1; + } else if (fpsLimiter == FPSLimit::CUSTOM2 && g_Config.iFpsLimit2 > 0) { + scaledTimestep *= 60.0f / g_Config.iFpsLimit2; } if (lastFrameTime == 0.0 || wasPaused) { @@ -549,7 +551,8 @@ static void DoFrameTiming(bool &throttle, bool &skipFrame, float timestep) { // Auto-frameskip automatically if speed limit is set differently than the default. bool useAutoFrameskip = g_Config.bAutoFrameSkip && g_Config.iRenderingMode != FB_NON_BUFFERED_MODE; - if (g_Config.bAutoFrameSkip || (g_Config.iFrameSkip == 0 && fpsLimiter == FPS_LIMIT_CUSTOM && g_Config.iFpsLimit > 60)) { + bool forceFrameskip = (fpsLimiter == FPSLimit::CUSTOM1 && g_Config.iFpsLimit1 > 60) || (fpsLimiter == FPSLimit::CUSTOM2 && g_Config.iFpsLimit2 > 60); + if (g_Config.bAutoFrameSkip || forceFrameskip) { // autoframeskip // Argh, we are falling behind! Let's skip a frame and see if we catch up. if (curFrameTime > nextFrameTime && doFrameSkip) { @@ -601,9 +604,11 @@ static void DoFrameIdleTiming() { } float scaledVblank = timePerVblank; - if (PSP_CoreParameter().fpsLimit == FPS_LIMIT_CUSTOM) { + if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM1 && g_Config.iFpsLimit1 > 0) { // 0 is handled in FrameTimingThrottled(). - scaledVblank *= 60.0f / g_Config.iFpsLimit; + scaledVblank *= 60.0f / g_Config.iFpsLimit1; + } else if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM2 && g_Config.iFpsLimit2 > 0) { + scaledVblank *= 60.0f / g_Config.iFpsLimit2; } // If we have over at least a vblank of spare time, maintain at least 30fps in delay. @@ -695,7 +700,7 @@ void __DisplayFlip(int cyclesLate) { // Let the user know if we're running slow, so they know to adjust settings. // Sometimes users just think the sound emulation is broken. static bool hasNotifiedSlow = false; - if (!g_Config.bHideSlowWarnings && !hasNotifiedSlow && PSP_CoreParameter().fpsLimit == FPS_LIMIT_NORMAL && IsRunningSlow()) { + if (!g_Config.bHideSlowWarnings && !hasNotifiedSlow && PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL && IsRunningSlow()) { #ifndef _DEBUG I18NCategory *err = GetI18NCategory("Error"); if (g_Config.bSoftwareRendering) { @@ -787,9 +792,11 @@ void hleLagSync(u64 userdata, int cyclesLate) { } float scale = 1.0f; - if (PSP_CoreParameter().fpsLimit == FPS_LIMIT_CUSTOM) { + if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM1 && g_Config.iFpsLimit1 > 0) { // 0 is handled in FrameTimingThrottled(). - scale = 60.0f / g_Config.iFpsLimit; + scale = 60.0f / g_Config.iFpsLimit1; + } else if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM2 && g_Config.iFpsLimit2 > 0) { + scale = 60.0f / g_Config.iFpsLimit2; } const double goal = lastLagSync + (scale / 1000.0f); @@ -937,7 +944,8 @@ u32 sceDisplaySetFramebuf(u32 topaddr, int linesize, int pixelformat, int sync) __DisplaySetFramebuf(topaddr, linesize, pixelformat, sync); - if (delayCycles > 0) { + // No delaying while inside an interrupt. It'll cause idle threads to starve. + if (delayCycles > 0 && !__IsInInterrupt()) { // Okay, the game is going at too high a frame rate. God of War and Fat Princess both do this. // Simply eating the cycles works and is fast, but breaks other games (like Jeanne d'Arc.) // So, instead, we delay this HLE thread only (a small deviation from correct behavior.) diff --git a/Core/HLE/sceIo.cpp b/Core/HLE/sceIo.cpp index ece0f84ff7..3bf8c721c6 100644 --- a/Core/HLE/sceIo.cpp +++ b/Core/HLE/sceIo.cpp @@ -24,6 +24,7 @@ #include "Core/Core.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/Debugger/Breakpoints.h" #include "Core/ELF/ParamSFO.h" #include "Core/MemMapHelpers.h" @@ -62,7 +63,6 @@ extern "C" { // For headless screenshots. #include "Core/HLE/sceDisplay.h" -static const int ERROR_ERRNO_FILE_NOT_FOUND = 0x80010002; static const int ERROR_ERRNO_IO_ERROR = 0x80010005; static const int ERROR_MEMSTICK_DEVCTL_BAD_PARAMS = 0x80220081; static const int ERROR_MEMSTICK_DEVCTL_TOO_MANY_CALLBACKS = 0x80220082; @@ -497,10 +497,12 @@ static void __IoManagerThread() { } } -static void __IoWakeManager() { +static void __IoWakeManager(CoreLifecycle stage) { // Ping the thread so that it knows to check coreState. - ioManagerThreadEnabled = false; - ioManager.FinishEventLoop(); + if (stage == CoreLifecycle::STOPPING) { + ioManagerThreadEnabled = false; + ioManager.FinishEventLoop(); + } } static void __IoVblank() { @@ -587,7 +589,7 @@ void __IoInit() { ioManagerThreadEnabled = g_Config.bSeparateIOThread; ioManager.SetThreadEnabled(ioManagerThreadEnabled); if (ioManagerThreadEnabled) { - Core_ListenShutdown(&__IoWakeManager); + Core_ListenLifecycle(&__IoWakeManager); ioManagerThread = new std::thread(&__IoManagerThread); ioManagerThread->detach(); } @@ -805,7 +807,7 @@ static u32 sceIoGetstat(const char *filename, u32 addr) { } } else { DEBUG_LOG(SCEIO, "sceIoGetstat(%s, %08x) : FILE NOT FOUND", filename, addr); - return hleDelayResult(ERROR_ERRNO_FILE_NOT_FOUND, "io getstat", usec); + return hleDelayResult(SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND, "io getstat", usec); } } @@ -1383,7 +1385,7 @@ static u32 sceIoOpen(const char *filename, int flags, int mode) { else { ERROR_LOG(SCEIO, "ERROR_ERRNO_FILE_NOT_FOUND=sceIoOpen(%s, %08x, %08x) - file not found", filename, flags, mode); - return hleDelayResult(ERROR_ERRNO_FILE_NOT_FOUND, "file opened", 10000); + return hleDelayResult(SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND, "file opened", 10000); } } @@ -1412,7 +1414,7 @@ static u32 sceIoRemove(const char *filename) { // TODO: This timing isn't necessarily accurate, low end for now. if(!pspFileSystem.GetFileInfo(filename).exists) - return hleDelayResult(ERROR_ERRNO_FILE_NOT_FOUND, "file removed", 100); + return hleDelayResult(SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND, "file removed", 100); pspFileSystem.RemoveFile(filename); return hleDelayResult(0, "file removed", 100); @@ -1433,7 +1435,7 @@ static u32 sceIoRmdir(const char *dirname) { if (pspFileSystem.RmDir(dirname)) return hleDelayResult(0, "rmdir", 1000); else - return hleDelayResult(ERROR_ERRNO_FILE_NOT_FOUND, "rmdir", 1000); + return hleDelayResult(SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND, "rmdir", 1000); } static u32 sceIoSync(const char *devicename, int flag) { @@ -1855,7 +1857,7 @@ static u32 sceIoRename(const char *from, const char *to) { // TODO: Timing isn't terribly accurate. if (!pspFileSystem.GetFileInfo(from).exists) - return hleDelayResult(ERROR_ERRNO_FILE_NOT_FOUND, "file renamed", 1000); + return hleDelayResult(SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND, "file renamed", 1000); int result = pspFileSystem.RenameFile(from, to); if (result < 0) @@ -1933,7 +1935,7 @@ static u32 sceIoOpenAsync(const char *filename, int flags, int mode) f = new FileNode(); f->handle = kernelObjects.Create(f); f->fullpath = filename; - f->asyncResult = error == 0 ? ERROR_ERRNO_FILE_NOT_FOUND : error; + f->asyncResult = error == 0 ? SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND : error; f->closePending = true; fd = __IoAllocFd(f); @@ -2087,7 +2089,7 @@ static u32 sceIoPollAsync(int id, u32 address) { FileNode *f = __IoGetFd(id, error); if (f) { if (f->pendingAsyncResult) { - DEBUG_LOG(SCEIO, "%lli = sceIoPollAsync(%i, %08x): not ready", f->asyncResult, id, address); + VERBOSE_LOG(SCEIO, "%lli = sceIoPollAsync(%i, %08x): not ready", f->asyncResult, id, address); return 1; } else if (f->hasAsyncResult) { DEBUG_LOG(SCEIO, "%lli = sceIoPollAsync(%i, %08x)", f->asyncResult, id, address); @@ -2142,9 +2144,8 @@ public: static u32 sceIoDopen(const char *path) { DEBUG_LOG(SCEIO, "sceIoDopen(\"%s\")", path); - if(!pspFileSystem.GetFileInfo(path).exists) - { - return ERROR_ERRNO_FILE_NOT_FOUND; + if (!pspFileSystem.GetFileInfo(path).exists) { + return SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND; } DirListing *dir = new DirListing(); diff --git a/Core/HLE/sceKernel.cpp b/Core/HLE/sceKernel.cpp index 1db8b5d60d..bf409a247c 100644 --- a/Core/HLE/sceKernel.cpp +++ b/Core/HLE/sceKernel.cpp @@ -313,7 +313,7 @@ u32 sceKernelDevkitVersion() int revision = firmwareVersion % 10; int devkitVersion = (major << 24) | (minor << 16) | (revision << 8) | 0x10; - DEBUG_LOG_REPORT_ONCE(devkitVer, SCEKERNEL, "%08x=sceKernelDevkitVersion()", devkitVersion); + DEBUG_LOG(SCEKERNEL, "%08x=sceKernelDevkitVersion()", devkitVersion); return devkitVersion; } diff --git a/Core/HLE/sceKernel.h b/Core/HLE/sceKernel.h index 7bc309717f..fa5928584b 100644 --- a/Core/HLE/sceKernel.h +++ b/Core/HLE/sceKernel.h @@ -44,6 +44,7 @@ enum { SCE_KERNEL_ERROR_BAD_FILE = 0x80000209, SCE_KERNEL_ERROR_ACCESS_ERROR = 0x8000020D, + SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND = 0x80010002, SCE_KERNEL_ERROR_ERRNO_ARG_LIST_TOO_LONG = 0x80010007, SCE_KERNEL_ERROR_ERRNO_INVALID_FILE_DESCRIPTOR = 0x80010009, SCE_KERNEL_ERROR_ERRNO_RESOURCE_UNAVAILABLE = 0x8001000B, diff --git a/Core/HLE/sceKernelInterrupt.cpp b/Core/HLE/sceKernelInterrupt.cpp index ba9d7c1588..9513d2d019 100644 --- a/Core/HLE/sceKernelInterrupt.cpp +++ b/Core/HLE/sceKernelInterrupt.cpp @@ -317,6 +317,7 @@ void InterruptState::restore() void InterruptState::clear() { + savedCpu.reset(); } // http://forums.ps2dev.org/viewtopic.php?t=5687 @@ -654,10 +655,10 @@ static u32 sceKernelMemcpy(u32 dst, u32 src, u32 size) *dstp++ = *srcp++; } } -#ifndef MOBILE_DEVICE + CBreakPoints::ExecMemCheck(src, false, size, currentMIPS->pc); CBreakPoints::ExecMemCheck(dst, true, size, currentMIPS->pc); -#endif + return dst; } diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index 82da25de65..fe5cd6acf5 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -1811,8 +1811,7 @@ u32 sceKernelLoadModule(const char *name, u32 flags, u32 optionAddr) { s64 size = (s64)info.size; if (!info.exists) { - const int ERROR_ERRNO_FILE_NOT_FOUND = 0x80010002; - const u32 error = hleLogError(LOADER, ERROR_ERRNO_FILE_NOT_FOUND, "file does not exist"); + const u32 error = hleLogError(LOADER, SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND, "file does not exist"); return hleDelayResult(error, "module loaded", 500); } diff --git a/Core/HLE/sceKernelThread.cpp b/Core/HLE/sceKernelThread.cpp index 23d075cd17..858090883a 100644 --- a/Core/HLE/sceKernelThread.cpp +++ b/Core/HLE/sceKernelThread.cpp @@ -15,10 +15,11 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. -#include -#include -#include #include +#include +#include +#include +#include #include "base/logging.h" @@ -29,6 +30,7 @@ #include "Core/MIPS/MIPSAnalyst.h" #include "Core/MIPS/MIPSCodeUtils.h" #include "Core/MIPS/MIPS.h" +#include "Core/MIPS/MIPSDebugInterface.h" #include "Core/CoreTiming.h" #include "Core/MemMapHelpers.h" #include "Core/MIPS/JitCommon/JitCommon.h" @@ -42,6 +44,7 @@ #include "Core/HLE/sceKernelThread.h" #include "Core/HLE/sceKernelModule.h" #include "Core/HLE/sceKernelInterrupt.h" +#include "Core/HLE/KernelThreadDebugInterface.h" #include "Core/HLE/KernelWaitHelpers.h" #include "Core/HLE/ThreadQueueList.h" @@ -494,7 +497,7 @@ public: return true; } - Thread() + Thread() : debug(currentMIPS, context) { currentStack.start = 0; } @@ -542,7 +545,7 @@ public: p.Do(currentMipscallId); p.Do(currentCallbackId); - // TODO: How do I "version" adding a DoState method to ThreadContext? + // TODO: If we want to "version" a DoState method here, we can just use minVer = 0. p.Do(context); if (s <= 3) @@ -587,6 +590,7 @@ public: SceUID currentCallbackId; ThreadContext context; + KernelThreadDebugInterface debug; std::vector callbacks; @@ -645,6 +649,8 @@ static std::vector pendingDeleteThreads; // Lists all thread ids that aren't deleted/etc. std::vector threadqueue; +// Only for debugger, so not needed to read, just write. +std::mutex threadqueueLock; // Lists only ready thread ids. ThreadQueueList threadReadyQueue; @@ -1152,8 +1158,9 @@ void __KernelIdle() __KernelReSchedule("idle"); } -void __KernelThreadingShutdown() -{ +void __KernelThreadingShutdown() { + std::lock_guard guard(threadqueueLock); + kernelMemory.Free(threadReturnHackAddr); threadqueue.clear(); threadReadyQueue.clear(); @@ -1588,8 +1595,9 @@ void __KernelCancelThreadEndTimeout(SceUID threadID) CoreTiming::UnscheduleEvent(eventThreadEndTimeout, threadID); } -static void __KernelRemoveFromThreadQueue(SceUID threadID) -{ +static void __KernelRemoveFromThreadQueue(SceUID threadID) { + std::lock_guard guard(threadqueueLock); + int prio = __KernelGetThreadPrio(threadID); if (prio != 0) threadReadyQueue.remove(prio, threadID); @@ -1817,6 +1825,8 @@ void ThreadContext::reset() fcr31 = 0x00000e00; hi = 0xDEADBEEF; lo = 0xDEADBEEF; + // Just for a clean state. + other[5] = 0; } void __KernelResetThread(Thread *t, int lowestPriority) @@ -1848,8 +1858,9 @@ void __KernelResetThread(Thread *t, int lowestPriority) ERROR_LOG_REPORT(SCEKERNEL, "Resetting thread with threads waiting on end?"); } -Thread *__KernelCreateThread(SceUID &id, SceUID moduleId, const char *name, u32 entryPoint, u32 priority, int stacksize, u32 attr) -{ +Thread *__KernelCreateThread(SceUID &id, SceUID moduleId, const char *name, u32 entryPoint, u32 priority, int stacksize, u32 attr) { + std::lock_guard guard(threadqueueLock); + Thread *t = new Thread; id = kernelObjects.Create(t); @@ -3511,19 +3522,18 @@ void __KernelRegisterWaitTypeFuncs(WaitType type, WaitBeginCallbackFunc beginFun waitTypeFuncs[type].endFunc = endFunc; } -std::vector GetThreadsInfo() -{ +std::vector GetThreadsInfo() { + std::lock_guard guard(threadqueueLock); std::vector threadList; u32 error; - for (auto iter = threadqueue.begin(); iter != threadqueue.end(); ++iter) - { - Thread *t = kernelObjects.Get(*iter, error); + for (const auto uid : threadqueue) { + Thread *t = kernelObjects.Get(uid, error); if (!t) continue; DebugThreadInfo info; - info.id = *iter; + info.id = uid; strncpy(info.name,t->GetName(),KERNELOBJECT_MAX_NAME_LENGTH); info.name[KERNELOBJECT_MAX_NAME_LENGTH] = 0; info.status = t->nt.status; @@ -3532,17 +3542,31 @@ std::vector GetThreadsInfo() info.stackSize = (u32)t->nt.stackSize; info.priority = t->nt.currentPriority; info.waitType = (WaitType)(u32)t->nt.waitType; - if(*iter == currentThread) + info.isCurrent = uid == currentThread; + if (info.isCurrent) info.curPC = currentMIPS->pc; else info.curPC = t->context.pc; - info.isCurrent = (*iter == currentThread); threadList.push_back(info); } return threadList; } +DebugInterface *KernelDebugThread(SceUID threadID) { + if (threadID == currentThread) { + return currentDebugMIPS; + } + + u32 error; + Thread *t = kernelObjects.Get(threadID, error); + if (t) { + return &t->debug; + } + + return nullptr; +} + void __KernelChangeThreadState(SceUID threadId, ThreadStatus newStatus) { u32 error; diff --git a/Core/HLE/sceKernelThread.h b/Core/HLE/sceKernelThread.h index 53fa5e4367..6216fc02ec 100644 --- a/Core/HLE/sceKernelThread.h +++ b/Core/HLE/sceKernelThread.h @@ -27,6 +27,7 @@ // http://code.google.com/p/jpcsp/source/browse/trunk/src/jpcsp/HLE/modules150/ThreadManForUser.java class Thread; +class DebugInterface; int sceKernelChangeThreadPriority(SceUID threadID, int priority); SceUID __KernelCreateThreadInternal(const char *threadName, SceUID moduleID, u32 entry, u32 prio, int stacksize, u32 attr); @@ -316,6 +317,7 @@ struct DebugThreadInfo }; std::vector GetThreadsInfo(); +DebugInterface *KernelDebugThread(SceUID threadID); void __KernelChangeThreadState(SceUID threadId, ThreadStatus newStatus); int LoadExecForUser_362A956B(); diff --git a/Core/HLE/sceUmd.cpp b/Core/HLE/sceUmd.cpp index f293a72d2b..bd4cb56d7b 100644 --- a/Core/HLE/sceUmd.cpp +++ b/Core/HLE/sceUmd.cpp @@ -342,7 +342,8 @@ static u32 sceUmdGetDriveStat() } //u32 retVal = PSP_UMD_INITED | PSP_UMD_READY | PSP_UMD_PRESENT; u32 retVal = __KernelUmdGetState(); - DEBUG_LOG(SCEIO,"0x%02x=sceUmdGetDriveStat()", retVal); + // This one can be very spammy. + VERBOSE_LOG(SCEIO,"0x%02x=sceUmdGetDriveStat()", retVal); return retVal; } diff --git a/Core/HW/MediaEngine.cpp b/Core/HW/MediaEngine.cpp index cd6887fa99..870cc3f768 100644 --- a/Core/HW/MediaEngine.cpp +++ b/Core/HW/MediaEngine.cpp @@ -477,6 +477,14 @@ bool MediaEngine::setVideoStream(int streamNum, bool force) { return false; } AVCodecContext *m_pCodecCtx = m_pFormatCtx->streams[streamNum]->codec; +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57,33,100) + AVCodecParameters *m_pCodecPar = m_pFormatCtx->streams[streamNum]->codecpar; + + // Update from deprecated public codec context + if (avcodec_parameters_from_context(m_pCodecPar, m_pCodecCtx) < 0) { + return false; + } +#endif // Find the decoder for the video stream AVCodec *pCodec = avcodec_find_decoder(m_pCodecCtx->codec_id); @@ -788,9 +796,8 @@ int MediaEngine::writeVideoImage(u32 bufferPtr, int frameWidth, int videoPixelMo delete [] imgbuf; } -#ifndef MOBILE_DEVICE CBreakPoints::ExecMemCheck(bufferPtr, true, videoImageSize, currentMIPS->pc); -#endif + return videoImageSize; #endif // USE_FFMPEG return 0; @@ -844,9 +851,7 @@ int MediaEngine::writeVideoImageWithRange(u32 bufferPtr, int frameWidth, int vid writeVideoLineRGBA(imgbuf, data, width); data += m_desWidth * sizeof(u32); imgbuf += videoLineSize; -#ifndef MOBILE_DEVICE CBreakPoints::ExecMemCheck(bufferPtr + y * frameWidth * sizeof(u32), true, width * sizeof(u32), currentMIPS->pc); -#endif } break; @@ -856,9 +861,7 @@ int MediaEngine::writeVideoImageWithRange(u32 bufferPtr, int frameWidth, int vid writeVideoLineABGR5650(imgbuf, data, width); data += m_desWidth * sizeof(u16); imgbuf += videoLineSize; -#ifndef MOBILE_DEVICE CBreakPoints::ExecMemCheck(bufferPtr + y * frameWidth * sizeof(u16), true, width * sizeof(u16), currentMIPS->pc); -#endif } break; @@ -868,9 +871,7 @@ int MediaEngine::writeVideoImageWithRange(u32 bufferPtr, int frameWidth, int vid writeVideoLineABGR5551(imgbuf, data, width); data += m_desWidth * sizeof(u16); imgbuf += videoLineSize; -#ifndef MOBILE_DEVICE CBreakPoints::ExecMemCheck(bufferPtr + y * frameWidth * sizeof(u16), true, width * sizeof(u16), currentMIPS->pc); -#endif } break; @@ -880,9 +881,7 @@ int MediaEngine::writeVideoImageWithRange(u32 bufferPtr, int frameWidth, int vid writeVideoLineABGR4444(imgbuf, data, width); data += m_desWidth * sizeof(u16); imgbuf += videoLineSize; -#ifndef MOBILE_DEVICE CBreakPoints::ExecMemCheck(bufferPtr + y * frameWidth * sizeof(u16), true, width * sizeof(u16), currentMIPS->pc); -#endif } break; @@ -976,9 +975,8 @@ int MediaEngine::getAudioSamples(u32 bufferPtr) { if (!m_audioContext->Decode(audioFrame, frameSize, buffer, &outbytes)) { ERROR_LOG(ME, "Audio (%s) decode failed during video playback", GetCodecName(m_audioType)); } -#ifndef MOBILE_DEVICE + CBreakPoints::ExecMemCheck(bufferPtr, true, outbytes, currentMIPS->pc); -#endif } return 0x2000; diff --git a/Core/HW/SasAudio.cpp b/Core/HW/SasAudio.cpp index f2fcda48ef..4a1a7459ed 100644 --- a/Core/HW/SasAudio.cpp +++ b/Core/HW/SasAudio.cpp @@ -65,6 +65,11 @@ void VagDecoder::Start(u32 data, u32 vagSize, bool loopEnabled) { } void VagDecoder::DecodeBlock(u8 *&read_pointer) { + if (curBlock_ == numBlocks_ - 1) { + end_ = true; + return; + } + u8 *readp = read_pointer; int predict_nr = *readp++; int shift_factor = predict_nr & 0xf; @@ -106,9 +111,6 @@ void VagDecoder::DecodeBlock(u8 *&read_pointer) { s_2 = s2; curSample = 0; curBlock_++; - if (curBlock_ == numBlocks_) { - end_ = true; - } read_pointer = readp; } @@ -508,16 +510,31 @@ void SasInstance::MixVoice(SasVoice &voice) { ERROR_LOG(SCESAS, "Too many samples to read (%d)! This shouldn't happen.", samplesToRead); samplesToRead = ARRAY_SIZE(mixTemp_) - 2; } - voice.ReadSamples(&mixTemp_[2], samplesToRead); - int tempPos = 2 + samplesToRead; + int readPos = 2; + if (voice.envelope.NeedsKeyOn()) { + readPos = 0; + samplesToRead += 2; + } + voice.ReadSamples(&mixTemp_[readPos], samplesToRead); + int tempPos = readPos + samplesToRead; int volumeShift = (12 + MAX_CONFIG_VOLUME - g_Config.iSASVolume); if (volumeShift < 0) volumeShift = 0; + for (int i = 0; i < delay; ++i) { + // Walk the curve. This means we'll reach ATTACK already, likely. + // This matches the results of tests (but maybe we can just remove the STATE_KEYON_STEP hack.) + voice.envelope.Step(); + } + + const bool needsInterp = voicePitch != PSP_SAS_PITCH_BASE || (sampleFrac & PSP_SAS_PITCH_MASK) != 0; for (int i = delay; i < grainSize; i++) { const int16_t *s = mixTemp_ + (sampleFrac >> PSP_SAS_PITCH_BASE_SHIFT); // Linear interpolation. Good enough. Need to make resampleHist bigger if we want more. - int f = sampleFrac & PSP_SAS_PITCH_MASK; - int sample = (s[0] * (PSP_SAS_PITCH_MASK - f) + s[1] * f) >> PSP_SAS_PITCH_BASE_SHIFT; + int sample = s[0]; + if (needsInterp) { + int f = sampleFrac & PSP_SAS_PITCH_MASK; + sample = (s[0] * (PSP_SAS_PITCH_MASK - f) + s[1] * f) >> PSP_SAS_PITCH_BASE_SHIFT; + } sampleFrac += voicePitch; // The maximum envelope height (PSP_SAS_ENVELOPE_HEIGHT_MAX) is (1 << 30) - 1. @@ -542,7 +559,7 @@ void SasInstance::MixVoice(SasVoice &voice) { voice.resampleHist[0] = mixTemp_[tempPos - 2]; voice.resampleHist[1] = mixTemp_[tempPos - 1]; - voice.sampleFrac = sampleFrac - (tempPos - 2) * PSP_SAS_PITCH_BASE;; + voice.sampleFrac = sampleFrac - (tempPos - 2) * PSP_SAS_PITCH_BASE; if (voice.HaveSamplesEnded()) voice.envelope.End(); diff --git a/Core/HW/SasAudio.h b/Core/HW/SasAudio.h index 59e47c76d1..480250dc70 100644 --- a/Core/HW/SasAudio.h +++ b/Core/HW/SasAudio.h @@ -111,21 +111,21 @@ public: private: s16 samples[28]; - int curSample; + int curSample = 0; - u32 data_; - u32 read_; - int curBlock_; - int loopStartBlock_; - int numBlocks_; + u32 data_ = 0; + u32 read_ = 0; + int curBlock_ = -1; + int loopStartBlock_ = -1; + int numBlocks_ = 0; // rolling state. start at 0, should probably reset to 0 on loops? - int s_1; - int s_2; + int s_1 = 0; + int s_2 = 0; - bool loopEnabled_; - bool loopAtNextBlock_; - bool end_; + bool loopEnabled_ = false; + bool loopAtNextBlock_ = false; + bool end_ = false; }; class SasAtrac3 { diff --git a/Core/HW/StereoResampler.cpp b/Core/HW/StereoResampler.cpp index ee636a89fe..8b2fcfadd1 100644 --- a/Core/HW/StereoResampler.cpp +++ b/Core/HW/StereoResampler.cpp @@ -37,6 +37,7 @@ #include "Common/MathUtil.h" #include "Common/Atomics.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/HW/StereoResampler.h" #include "Core/HLE/__sceAudio.h" #include "Core/Util/AudioFormat.h" // for clamp_u8 diff --git a/Core/Host.h b/Core/Host.h index c7eed94de1..b06473c4dc 100644 --- a/Core/Host.h +++ b/Core/Host.h @@ -50,8 +50,6 @@ public: virtual void SaveSymbolMap() {} virtual void SetWindowTitle(const char *message) {} - virtual void SendCoreWait(bool) {} - // While debugging is active, it's perfectly fine for these to block. virtual bool GPUDebuggingActive() { return false; } virtual void GPUNotifyCommand(u32 pc) {} diff --git a/Core/Loaders.h b/Core/Loaders.h index 05837433f8..f0e83adab4 100644 --- a/Core/Loaders.h +++ b/Core/Loaders.h @@ -110,6 +110,7 @@ IdentifiedFileType Identify_File(FileLoader *fileLoader); class FileLoaderFactory { public: + virtual ~FileLoaderFactory() {} virtual FileLoader *ConstructFileLoader(const std::string &filename) = 0; }; void RegisterFileLoaderFactory(std::string name, std::unique_ptr factory); diff --git a/Core/MIPS/ARM/ArmAsm.cpp b/Core/MIPS/ARM/ArmAsm.cpp index 7e037a7052..3a5bcba1a5 100644 --- a/Core/MIPS/ARM/ArmAsm.cpp +++ b/Core/MIPS/ARM/ArmAsm.cpp @@ -238,7 +238,6 @@ void ArmJit::GenerateFixedCode() { B_CC(CC_EQ, outerLoop); SetJumpTarget(badCoreState); - breakpointBailout = GetCodePtr(); SaveDowncount(); RestoreRoundingMode(true); diff --git a/Core/MIPS/ARM/ArmCompFPU.cpp b/Core/MIPS/ARM/ArmCompFPU.cpp index 971f8cebdc..f4ec293382 100644 --- a/Core/MIPS/ARM/ArmCompFPU.cpp +++ b/Core/MIPS/ARM/ArmCompFPU.cpp @@ -93,6 +93,7 @@ extern int logBlocks; void ArmJit::Comp_FPULS(MIPSOpcode op) { CONDITIONAL_DISABLE; + CheckMemoryBreakpoint(); s32 offset = (s16)(op & 0xFFFF); int ft = _FT; diff --git a/Core/MIPS/ARM/ArmCompLoadStore.cpp b/Core/MIPS/ARM/ArmCompLoadStore.cpp index 2c0baac10b..16a3753fd2 100644 --- a/Core/MIPS/ARM/ArmCompLoadStore.cpp +++ b/Core/MIPS/ARM/ArmCompLoadStore.cpp @@ -112,6 +112,7 @@ namespace MIPSComp void ArmJit::Comp_ITypeMemLR(MIPSOpcode op, bool load) { CONDITIONAL_DISABLE; + CheckMemoryBreakpoint(); int offset = (signed short)(op & 0xFFFF); MIPSGPReg rt = _RT; MIPSGPReg rs = _RS; @@ -120,6 +121,7 @@ namespace MIPSComp if (!js.inDelaySlot) { // Optimisation: Combine to single unaligned load/store bool isLeft = (o == 34 || o == 42); + CheckMemoryBreakpoint(1); MIPSOpcode nextOp = GetOffsetInstruction(1); // Find a matching shift in opposite direction with opposite offset. if (nextOp == (isLeft ? (op.encoding + (4<<26) - 3) @@ -259,6 +261,7 @@ namespace MIPSComp void ArmJit::Comp_ITypeMem(MIPSOpcode op) { CONDITIONAL_DISABLE; + CheckMemoryBreakpoint(); int offset = (signed short)(op&0xFFFF); bool load = false; MIPSGPReg rt = _RT; diff --git a/Core/MIPS/ARM/ArmCompVFPU.cpp b/Core/MIPS/ARM/ArmCompVFPU.cpp index 3f275c58be..c171424075 100644 --- a/Core/MIPS/ARM/ArmCompVFPU.cpp +++ b/Core/MIPS/ARM/ArmCompVFPU.cpp @@ -226,6 +226,7 @@ namespace MIPSComp void ArmJit::Comp_SV(MIPSOpcode op) { NEON_IF_AVAILABLE(CompNEON_SV); CONDITIONAL_DISABLE; + CheckMemoryBreakpoint(); s32 offset = (signed short)(op & 0xFFFC); int vt = ((op >> 16) & 0x1f) | ((op & 3) << 5); @@ -332,6 +333,7 @@ namespace MIPSComp { NEON_IF_AVAILABLE(CompNEON_SVQ); CONDITIONAL_DISABLE; + CheckMemoryBreakpoint(); int imm = (signed short)(op&0xFFFC); int vt = (((op >> 16) & 0x1f)) | ((op&1) << 5); diff --git a/Core/MIPS/ARM/ArmCompVFPUNEON.cpp b/Core/MIPS/ARM/ArmCompVFPUNEON.cpp index fe49e2e509..9f04436bf4 100644 --- a/Core/MIPS/ARM/ArmCompVFPUNEON.cpp +++ b/Core/MIPS/ARM/ArmCompVFPUNEON.cpp @@ -159,6 +159,7 @@ void ArmJit::CompNEON_VecDo3(MIPSOpcode op) { void ArmJit::CompNEON_SV(MIPSOpcode op) { CONDITIONAL_DISABLE; + CheckMemoryBreakpoint(); // Remember to use single lane stores here and not VLDR/VSTR - switching usage // between NEON and VFPU can be expensive on some chips. @@ -276,6 +277,7 @@ inline int MIPS_GET_VQVT(u32 op) { void ArmJit::CompNEON_SVQ(MIPSOpcode op) { CONDITIONAL_DISABLE; + CheckMemoryBreakpoint(); int offset = (signed short)(op & 0xFFFC); int vt = MIPS_GET_VQVT(op.encoding); diff --git a/Core/MIPS/ARM/ArmJit.cpp b/Core/MIPS/ARM/ArmJit.cpp index ea206a6d26..87bb801087 100644 --- a/Core/MIPS/ARM/ArmJit.cpp +++ b/Core/MIPS/ARM/ArmJit.cpp @@ -26,6 +26,7 @@ #include "Core/Config.h" #include "Core/Core.h" #include "Core/CoreTiming.h" +#include "Core/Debugger/Breakpoints.h" #include "Core/Debugger/SymbolMap.h" #include "Core/MemMap.h" @@ -45,7 +46,7 @@ using namespace ArmJitConstants; -void DisassembleArm(const u8 *data, int size) { +static void DisassembleArm(const u8 *data, int size) { char temp[256]; for (int i = 0; i < size; i += 4) { const u32 *codePtr = (const u32 *)(data + i); @@ -69,6 +70,33 @@ void DisassembleArm(const u8 *data, int size) { } } +static u32 JitBreakpoint() { + // Should we skip this breakpoint? + if (CBreakPoints::CheckSkipFirst() == currentMIPS->pc) + return 0; + + BreakAction result = CBreakPoints::ExecBreakPoint(currentMIPS->pc); + if ((result & BREAK_ACTION_PAUSE) == 0) + return 0; + + return 1; +} + +static u32 JitMemCheck(u32 pc) { + if (CBreakPoints::CheckSkipFirst() == currentMIPS->pc) + return 0; + + // Note: pc may be the delay slot. + const auto op = Memory::Read_Instruction(pc, true); + s32 offset = (s16)(op & 0xFFFF); + if (MIPSGetInfo(op) & IS_VFPU) + offset &= 0xFFFC; + u32 addr = currentMIPS->r[MIPS_GET_RS(op)] + offset; + + CBreakPoints::ExecOpMemCheck(addr, pc); + return coreState == CORE_RUNNING || coreState == CORE_NEXTFRAME ? 0 : 1; +} + namespace MIPSComp { using namespace ArmGen; @@ -86,6 +114,10 @@ ArmJit::ArmJit(MIPSState *mips) : blocks(mips, this), gpr(mips, &js, &jo), fpr(m INFO_LOG(JIT, "ARM JIT initialized: %d MB of code space", GetSpaceLeft() / (1024 * 1024)); js.startDefaultPrefix = mips_->HasDefaultPrefix(); + + // The debugger sets this so that "go" on a breakpoint will actually... go. + // But if they reset, we can end up hitting it by mistake, since it's based on PC and ticks. + CBreakPoints::SetSkipFirst(0); } ArmJit::~ArmJit() { @@ -104,6 +136,10 @@ void ArmJit::DoState(PointerWrap &p) } else { js.hasSetRounding = 1; } + + // The debugger sets this so that "go" on a breakpoint will actually... go. + // But if they reset, we can end up hitting it by mistake, since it's based on PC and ticks. + CBreakPoints::SetSkipFirst(0); } void ArmJit::UpdateFCR31() { @@ -158,13 +194,16 @@ void ArmJit::EatInstruction(MIPSOpcode op) { ERROR_LOG_REPORT_ONCE(ateInDelaySlot, JIT, "Ate an instruction inside a delay slot."); } + CheckJitBreakpoint(GetCompilerPC() + 4, 0); js.numInstructions++; js.compilerPC += 4; js.downcountAmount += MIPSGetInstructionCycleEstimate(op); } -void ArmJit::CompileDelaySlot(int flags) -{ +void ArmJit::CompileDelaySlot(int flags) { + // Need to offset the downcount which was already incremented for the branch + delay slot. + CheckJitBreakpoint(GetCompilerPC() + 4, -2); + // preserve flag around the delay slot! Maybe this is not always necessary on ARM where // we can (mostly) control whether we set the flag or not. Of course, if someone puts an slt in to the // delay slot, we're screwed. @@ -295,6 +334,9 @@ const u8 *ArmJit::DoJit(u32 em_address, JitBlock *b) while (js.compiling) { gpr.SetCompilerPC(GetCompilerPC()); // Let it know for log messages + // Jit breakpoints are quite fast, so let's do them in release too. + CheckJitBreakpoint(GetCompilerPC(), 0); + MIPSOpcode inst = Memory::Read_Opcode_JIT(GetCompilerPC()); //MIPSInfo info = MIPSGetInfo(inst); //if (info & IS_VFPU) { @@ -492,7 +534,18 @@ void ArmJit::Comp_ReplacementFunc(MIPSOpcode op) return; } - if (entry->flags & REPFLAG_DISABLED) { + u32 funcSize = g_symbolMap->GetFunctionSize(GetCompilerPC()); + bool disabled = (entry->flags & REPFLAG_DISABLED) != 0; + if (!disabled && funcSize != SymbolMap::INVALID_ADDRESS && funcSize > sizeof(u32)) { + // We don't need to disable hooks, the code will still run. + if ((entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) == 0) { + // Any breakpoint at the func entry was already tripped, so we can still run the replacement. + // That's a common case - just to see how often the replacement hits. + disabled = CBreakPoints::RangeContainsBreakPoint(GetCompilerPC() + sizeof(u32), funcSize - sizeof(u32)); + } + } + + if (disabled) { MIPSCompileOp(Memory::Read_Instruction(GetCompilerPC(), true), this); } else if (entry->jitReplaceFunc) { MIPSReplaceFunc repl = entry->jitReplaceFunc; @@ -686,6 +739,60 @@ void ArmJit::WriteSyscallExit() B((const void *)dispatcherCheckCoreState); } +bool ArmJit::CheckJitBreakpoint(u32 addr, int downcountOffset) { + if (CBreakPoints::IsAddressBreakPoint(addr)) { + MRS(R8); + FlushAll(); + MOVI2R(SCRATCHREG1, GetCompilerPC()); + MovToPC(SCRATCHREG1); + RestoreRoundingMode(); + QuickCallFunction(SCRATCHREG1, &JitBreakpoint); + + // If 0, the conditional breakpoint wasn't taken. + CMPI2R(R0, 0, SCRATCHREG2); + FixupBranch skip = B_CC(CC_EQ); + WriteDownCount(downcountOffset); + ApplyRoundingMode(); + B((const void *)dispatcherCheckCoreState); + SetJumpTarget(skip); + + ApplyRoundingMode(); + _MSR(true, false, R8); + return true; + } + + return false; +} + +bool ArmJit::CheckMemoryBreakpoint(int instructionOffset) { + if (CBreakPoints::HasMemChecks()) { + int off = instructionOffset + (js.inDelaySlot ? 1 : 0); + + MRS(R8); + FlushAll(); + RestoreRoundingMode(); + MOVI2R(R0, GetCompilerPC()); + MovToPC(R0); + if (off != 0) + ADDI2R(R0, R0, off, SCRATCHREG2); + QuickCallFunction(SCRATCHREG2, &JitMemCheck); + + // If 0, the breakpoint wasn't tripped. + CMPI2R(R0, 0, SCRATCHREG2); + FixupBranch skip = B_CC(CC_EQ); + WriteDownCount(-1 - off); + ApplyRoundingMode(); + B((const void *)dispatcherCheckCoreState); + SetJumpTarget(skip); + + ApplyRoundingMode(); + _MSR(true, false, R8); + return true; + } + + return false; +} + void ArmJit::Comp_DoNothing(MIPSOpcode op) { } MIPSOpcode ArmJit::GetOriginalOp(MIPSOpcode op) { diff --git a/Core/MIPS/ARM/ArmJit.h b/Core/MIPS/ARM/ArmJit.h index af2b6a128d..80a3bc6ae9 100644 --- a/Core/MIPS/ARM/ArmJit.h +++ b/Core/MIPS/ARM/ArmJit.h @@ -215,6 +215,8 @@ private: void WriteExit(u32 destination, int exit_num); void WriteExitDestInR(ArmGen::ARMReg Reg); void WriteSyscallExit(); + bool CheckJitBreakpoint(u32 addr, int downcountOffset); + bool CheckMemoryBreakpoint(int instructionOffset = 0); // Utility compilation functions void BranchFPFlag(MIPSOpcode op, CCFlags cc, bool likely); @@ -311,8 +313,6 @@ public: const u8 *restoreRoundingMode; const u8 *applyRoundingMode; - - const u8 *breakpointBailout; }; } // namespace MIPSComp diff --git a/Core/MIPS/ARM64/Arm64Asm.cpp b/Core/MIPS/ARM64/Arm64Asm.cpp index 2390ed9673..1abf148d7c 100644 --- a/Core/MIPS/ARM64/Arm64Asm.cpp +++ b/Core/MIPS/ARM64/Arm64Asm.cpp @@ -275,7 +275,6 @@ void Arm64Jit::GenerateFixedCode(const JitOptions &jo) { B(CC_EQ, outerLoop); SetJumpTarget(badCoreState); - breakpointBailout = GetCodePtr(); SaveStaticRegisters(); RestoreRoundingMode(true); diff --git a/Core/MIPS/ARM64/Arm64CompFPU.cpp b/Core/MIPS/ARM64/Arm64CompFPU.cpp index e608baa6d4..55298ae577 100644 --- a/Core/MIPS/ARM64/Arm64CompFPU.cpp +++ b/Core/MIPS/ARM64/Arm64CompFPU.cpp @@ -81,6 +81,7 @@ void Arm64Jit::Comp_FPU3op(MIPSOpcode op) { void Arm64Jit::Comp_FPULS(MIPSOpcode op) { CONDITIONAL_DISABLE; + CheckMemoryBreakpoint(); // Surprisingly, these work fine alraedy. @@ -88,7 +89,6 @@ void Arm64Jit::Comp_FPULS(MIPSOpcode op) int ft = _FT; MIPSGPReg rs = _RS; // u32 addr = R(rs) + offset; - // logBlocks = 1; std::vector skips; switch (op >> 26) { case 49: //FI(ft) = Memory::Read_U32(addr); break; //lwc1 diff --git a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp index 4e5f42f053..7b962f0e95 100644 --- a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp +++ b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp @@ -111,6 +111,7 @@ namespace MIPSComp { void Arm64Jit::Comp_ITypeMemLR(MIPSOpcode op, bool load) { CONDITIONAL_DISABLE; + CheckMemoryBreakpoint(); int offset = (signed short)(op & 0xFFFF); MIPSGPReg rt = _RT; MIPSGPReg rs = _RS; @@ -119,6 +120,7 @@ namespace MIPSComp { if (!js.inDelaySlot) { // Optimisation: Combine to single unaligned load/store bool isLeft = (o == 34 || o == 42); + CheckMemoryBreakpoint(1); MIPSOpcode nextOp = GetOffsetInstruction(1); // Find a matching shift in opposite direction with opposite offset. if (nextOp == (isLeft ? (op.encoding + (4 << 26) - 3) : (op.encoding - (4 << 26) + 3))) { @@ -255,6 +257,7 @@ namespace MIPSComp { void Arm64Jit::Comp_ITypeMem(MIPSOpcode op) { CONDITIONAL_DISABLE; + CheckMemoryBreakpoint(); int offset = (signed short)(op & 0xFFFF); bool load = false; diff --git a/Core/MIPS/ARM64/Arm64CompVFPU.cpp b/Core/MIPS/ARM64/Arm64CompVFPU.cpp index 0277df28e7..7799dfbfa4 100644 --- a/Core/MIPS/ARM64/Arm64CompVFPU.cpp +++ b/Core/MIPS/ARM64/Arm64CompVFPU.cpp @@ -202,6 +202,7 @@ namespace MIPSComp { void Arm64Jit::Comp_SV(MIPSOpcode op) { CONDITIONAL_DISABLE; + CheckMemoryBreakpoint(); s32 offset = (signed short)(op & 0xFFFC); int vt = ((op >> 16) & 0x1f) | ((op & 3) << 5); @@ -275,6 +276,7 @@ namespace MIPSComp { void Arm64Jit::Comp_SVQ(MIPSOpcode op) { CONDITIONAL_DISABLE; + CheckMemoryBreakpoint(); int imm = (signed short)(op&0xFFFC); int vt = (((op >> 16) & 0x1f)) | ((op&1) << 5); diff --git a/Core/MIPS/ARM64/Arm64Jit.cpp b/Core/MIPS/ARM64/Arm64Jit.cpp index 46b1a9bf3e..afadca29e0 100644 --- a/Core/MIPS/ARM64/Arm64Jit.cpp +++ b/Core/MIPS/ARM64/Arm64Jit.cpp @@ -28,6 +28,7 @@ #include "Core/Config.h" #include "Core/Core.h" #include "Core/CoreTiming.h" +#include "Core/Debugger/Breakpoints.h" #include "Core/Debugger/SymbolMap.h" #include "Core/MemMap.h" @@ -44,7 +45,7 @@ using namespace Arm64JitConstants; -void DisassembleArm64Print(const u8 *data, int size) { +static void DisassembleArm64Print(const u8 *data, int size) { std::vector lines = DisassembleArm64(data, size); for (auto s : lines) { ILOG("%s", s.c_str()); @@ -60,6 +61,33 @@ void DisassembleArm64Print(const u8 *data, int size) { ILOG("===");*/ } +static u32 JitBreakpoint() { + // Should we skip this breakpoint? + if (CBreakPoints::CheckSkipFirst() == currentMIPS->pc) + return 0; + + BreakAction result = CBreakPoints::ExecBreakPoint(currentMIPS->pc); + if ((result & BREAK_ACTION_PAUSE) == 0) + return 0; + + return 1; +} + +static u32 JitMemCheck(u32 pc) { + if (CBreakPoints::CheckSkipFirst() == currentMIPS->pc) + return 0; + + // Note: pc may be the delay slot. + const auto op = Memory::Read_Instruction(pc, true); + s32 offset = (s16)(op & 0xFFFF); + if (MIPSGetInfo(op) & IS_VFPU) + offset &= 0xFFFC; + u32 addr = currentMIPS->r[MIPS_GET_RS(op)] + offset; + + CBreakPoints::ExecOpMemCheck(addr, pc); + return coreState == CORE_RUNNING || coreState == CORE_NEXTFRAME ? 0 : 1; +} + namespace MIPSComp { using namespace Arm64Gen; @@ -80,6 +108,10 @@ Arm64Jit::Arm64Jit(MIPSState *mips) : blocks(mips, this), gpr(mips, &js, &jo), f GenerateFixedCode(jo); js.startDefaultPrefix = mips_->HasDefaultPrefix(); js.currentRoundingFunc = convertS0ToSCRATCH1[mips_->fcr31 & 3]; + + // The debugger sets this so that "go" on a breakpoint will actually... go. + // But if they reset, we can end up hitting it by mistake, since it's based on PC and ticks. + CBreakPoints::SetSkipFirst(0); } Arm64Jit::~Arm64Jit() { @@ -99,6 +131,10 @@ void Arm64Jit::DoState(PointerWrap &p) { } // Note: we can't update the currentRoundingFunc here because fcr31 wasn't loaded yet. + + // The debugger sets this so that "go" on a breakpoint will actually... go. + // But if they reset, we can end up hitting it by mistake, since it's based on PC and ticks. + CBreakPoints::SetSkipFirst(0); } void Arm64Jit::UpdateFCR31() { @@ -151,12 +187,16 @@ void Arm64Jit::EatInstruction(MIPSOpcode op) { ERROR_LOG_REPORT_ONCE(ateInDelaySlot, JIT, "Ate an instruction inside a delay slot."); } + CheckJitBreakpoint(GetCompilerPC() + 4, 0); js.numInstructions++; js.compilerPC += 4; js.downcountAmount += MIPSGetInstructionCycleEstimate(op); } void Arm64Jit::CompileDelaySlot(int flags) { + // Need to offset the downcount which was already incremented for the branch + delay slot. + CheckJitBreakpoint(GetCompilerPC() + 4, -2); + // preserve flag around the delay slot! Maybe this is not always necessary on ARM where // we can (mostly) control whether we set the flag or not. Of course, if someone puts an slt in to the // delay slot, we're screwed. @@ -285,8 +325,10 @@ const u8 *Arm64Jit::DoJit(u32 em_address, JitBlock *b) { js.numInstructions = 0; while (js.compiling) { gpr.SetCompilerPC(GetCompilerPC()); // Let it know for log messages + // Jit breakpoints are quite fast, so let's do them in release too. + CheckJitBreakpoint(GetCompilerPC(), 0); + MIPSOpcode inst = Memory::Read_Opcode_JIT(GetCompilerPC()); - js.downcountAmount += MIPSGetInstructionCycleEstimate(inst); MIPSCompileOp(inst, this); @@ -473,7 +515,18 @@ void Arm64Jit::Comp_ReplacementFunc(MIPSOpcode op) return; } - if (entry->flags & REPFLAG_DISABLED) { + u32 funcSize = g_symbolMap->GetFunctionSize(GetCompilerPC()); + bool disabled = (entry->flags & REPFLAG_DISABLED) != 0; + if (!disabled && funcSize != SymbolMap::INVALID_ADDRESS && funcSize > sizeof(u32)) { + // We don't need to disable hooks, the code will still run. + if ((entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) == 0) { + // Any breakpoint at the func entry was already tripped, so we can still run the replacement. + // That's a common case - just to see how often the replacement hits. + disabled = CBreakPoints::RangeContainsBreakPoint(GetCompilerPC() + sizeof(u32), funcSize - sizeof(u32)); + } + } + + if (disabled) { MIPSCompileOp(Memory::Read_Instruction(GetCompilerPC(), true), this); } else if (entry->jitReplaceFunc) { MIPSReplaceFunc repl = entry->jitReplaceFunc; @@ -646,6 +699,60 @@ void Arm64Jit::WriteSyscallExit() { B((const void *)dispatcherCheckCoreState); } +bool Arm64Jit::CheckJitBreakpoint(u32 addr, int downcountOffset) { + if (CBreakPoints::IsAddressBreakPoint(addr)) { + MRS(FLAGTEMPREG, FIELD_NZCV); + FlushAll(); + MOVI2R(SCRATCH1, GetCompilerPC()); + MovToPC(SCRATCH1); + RestoreRoundingMode(); + QuickCallFunction(SCRATCH1_64, &JitBreakpoint); + + // If 0, the conditional breakpoint wasn't taken. + CMPI2R(W0, 0); + FixupBranch skip = B(CC_EQ); + WriteDownCount(downcountOffset); + ApplyRoundingMode(); + B((const void *)dispatcherCheckCoreState); + SetJumpTarget(skip); + + ApplyRoundingMode(); + _MSR(FIELD_NZCV, FLAGTEMPREG); + return true; + } + + return false; +} + +bool Arm64Jit::CheckMemoryBreakpoint(int instructionOffset) { + if (CBreakPoints::HasMemChecks()) { + int off = instructionOffset + (js.inDelaySlot ? 1 : 0); + + MRS(FLAGTEMPREG, FIELD_NZCV); + FlushAll(); + RestoreRoundingMode(); + MOVI2R(W0, GetCompilerPC()); + MovToPC(W0); + if (off != 0) + ADDI2R(W0, W0, off * 4); + QuickCallFunction(SCRATCH2_64, &JitMemCheck); + + // If 0, the breakpoint wasn't tripped. + CMPI2R(W0, 0); + FixupBranch skip = B(CC_EQ); + WriteDownCount(-1 - off); + ApplyRoundingMode(); + B((const void *)dispatcherCheckCoreState); + SetJumpTarget(skip); + + ApplyRoundingMode(); + _MSR(FIELD_NZCV, FLAGTEMPREG); + return true; + } + + return false; +} + void Arm64Jit::Comp_DoNothing(MIPSOpcode op) { } MIPSOpcode Arm64Jit::GetOriginalOp(MIPSOpcode op) { diff --git a/Core/MIPS/ARM64/Arm64Jit.h b/Core/MIPS/ARM64/Arm64Jit.h index 3d84451384..6ad5921d53 100644 --- a/Core/MIPS/ARM64/Arm64Jit.h +++ b/Core/MIPS/ARM64/Arm64Jit.h @@ -214,6 +214,8 @@ private: void WriteExit(u32 destination, int exit_num); void WriteExitDestInR(Arm64Gen::ARM64Reg Reg); void WriteSyscallExit(); + bool CheckJitBreakpoint(u32 addr, int downcountOffset); + bool CheckMemoryBreakpoint(int instructionOffset = 0); // Utility compilation functions void BranchFPFlag(MIPSOpcode op, CCFlags cc, bool likely); @@ -272,8 +274,6 @@ public: const u8 *dispatcher; const u8 *dispatcherNoCheck; - const u8 *breakpointBailout; - const u8 *saveStaticRegisters; const u8 *loadStaticRegisters; diff --git a/Core/MIPS/MIPS.cpp b/Core/MIPS/MIPS.cpp index 37f16025ad..a04e8f2387 100644 --- a/Core/MIPS/MIPS.cpp +++ b/Core/MIPS/MIPS.cpp @@ -22,6 +22,7 @@ #include "Common.h" #include "Common/ChunkFile.h" +#include "Core/ConfigValues.h" #include "Core/MIPS/MIPS.h" #include "Core/MIPS/MIPSInt.h" #include "Core/MIPS/MIPSTables.h" diff --git a/Core/MIPS/MIPS/MipsJit.h b/Core/MIPS/MIPS/MipsJit.h index 88c1c2dc6d..23b164b1e7 100644 --- a/Core/MIPS/MIPS/MipsJit.h +++ b/Core/MIPS/MIPS/MipsJit.h @@ -181,8 +181,6 @@ public: const u8 *dispatcherPCInR0; const u8 *dispatcher; const u8 *dispatcherNoCheck; - - const u8 *breakpointBailout; }; typedef void (MipsJit::*MIPSCompileFunc)(MIPSOpcode opcode); diff --git a/Core/MIPS/MIPSAnalyst.cpp b/Core/MIPS/MIPSAnalyst.cpp index 569f49df5a..43e5019a66 100644 --- a/Core/MIPS/MIPSAnalyst.cpp +++ b/Core/MIPS/MIPSAnalyst.cpp @@ -1415,6 +1415,7 @@ skip: memset(&info, 0, sizeof(info)); if (!Memory::IsValidAddress(address)) { + info.opcodeAddress = address; return info; } diff --git a/Core/MIPS/MIPSAsm.cpp b/Core/MIPS/MIPSAsm.cpp index 1defcb9477..08965b4fc2 100644 --- a/Core/MIPS/MIPSAsm.cpp +++ b/Core/MIPS/MIPSAsm.cpp @@ -3,20 +3,13 @@ #endif #include #include +#include #include #include "Common/CommonTypes.h" -#if defined(_WIN32) || defined(__ANDROID__) -// Temporarily turned off on Android -#define USE_ARMIPS -#endif - - -#ifdef USE_ARMIPS // This has to be before basictypes to avoid a define conflict. #include "ext/armips/Core/Assembler.h" -#endif #include "util/text/utf8.h" #include "Core/Debugger/SymbolMap.h" @@ -33,7 +26,6 @@ std::wstring GetAssembleError() return errorText; } -#ifdef USE_ARMIPS class PspAssemblerFile: public AssemblerFile { public: @@ -72,12 +64,9 @@ private: u64 address; std::wstring dummyWFilename_; }; -#endif bool MipsAssembleOpcode(const char* line, DebugInterface* cpu, u32 address) { -#ifdef USE_ARMIPS - PspAssemblerFile file; StringList errors; wchar_t str[64]; @@ -87,7 +76,7 @@ bool MipsAssembleOpcode(const char* line, DebugInterface* cpu, u32 address) args.mode = ArmipsMode::MEMORY; args.content = str + ConvertUTF8ToWString(line); args.silent = true; - args.memoryFile = &file; + args.memoryFile.reset(new PspAssemblerFile()); args.errorsResult = &errors; if (g_symbolMap) { @@ -108,10 +97,6 @@ bool MipsAssembleOpcode(const char* line, DebugInterface* cpu, u32 address) } return true; -#else - errorText = L"Unsupported platform"; - return false; -#endif } } // namespace diff --git a/Core/MIPS/MIPSDebugInterface.cpp b/Core/MIPS/MIPSDebugInterface.cpp index 943e564938..ae1a9a7a6c 100644 --- a/Core/MIPS/MIPSDebugInterface.cpp +++ b/Core/MIPS/MIPSDebugInterface.cpp @@ -23,6 +23,7 @@ #include "Core/Debugger/DebugInterface.h" #include "Core/MIPS/MIPSDebugInterface.h" +#include "Core/HLE/sceKernelThread.h" #include "Core/MemMap.h" #include "Core/MIPS/MIPSTables.h" #include "Core/MIPS/MIPS.h" @@ -37,13 +38,16 @@ enum ReferenceIndexType { REF_INDEX_VFPU = 0x4000, REF_INDEX_VFPU_INT = 0x8000, REF_INDEX_IS_FLOAT = REF_INDEX_FPU | REF_INDEX_VFPU, + REF_INDEX_HLE = 0x10000, + REF_INDEX_THREAD = REF_INDEX_HLE | 0, + REF_INDEX_MODULE = REF_INDEX_HLE | 1, }; class MipsExpressionFunctions: public IExpressionFunctions { public: - MipsExpressionFunctions(DebugInterface* cpu): cpu(cpu) { }; + MipsExpressionFunctions(DebugInterface* cpu): cpu(cpu) { } bool parseReference(char* str, uint32_t& referenceIndex) override { @@ -106,6 +110,15 @@ public: return true; } + if (strcasecmp(str, "threadid") == 0) { + referenceIndex = REF_INDEX_THREAD; + return true; + } + if (strcasecmp(str, "moduleid") == 0) { + referenceIndex = REF_INDEX_MODULE; + return true; + } + return false; } @@ -124,6 +137,10 @@ public: return cpu->GetHi(); if (referenceIndex == REF_INDEX_LO) return cpu->GetLo(); + if (referenceIndex == REF_INDEX_THREAD) + return __KernelGetCurThread(); + if (referenceIndex == REF_INDEX_MODULE) + return __KernelGetCurThreadModuleId(); if ((referenceIndex & ~(REF_INDEX_FPU | REF_INDEX_FPU_INT)) < 32) return cpu->GetRegValue(1, referenceIndex & ~(REF_INDEX_FPU | REF_INDEX_FPU_INT)); if ((referenceIndex & ~(REF_INDEX_VFPU | REF_INDEX_VFPU_INT)) < 128) diff --git a/Core/MIPS/MIPSDebugInterface.h b/Core/MIPS/MIPSDebugInterface.h index 31cbdde414..088256e802 100644 --- a/Core/MIPS/MIPSDebugInterface.h +++ b/Core/MIPS/MIPSDebugInterface.h @@ -54,85 +54,70 @@ public: u32 GetLR() override { return cpu->r[MIPS_REG_RA]; } void SetPC(u32 _pc) override { cpu->pc = _pc; } - const char *GetCategoryName(int cat) override - { - const char *names[3] = {("GPR"),("FPU"),("VFPU")}; + const char *GetCategoryName(int cat) override { + static const char *const names[3] = { "GPR", "FPU", "VFPU" }; return names[cat]; } int GetNumCategories() override { return 3; } - int GetNumRegsInCategory(int cat) override - { - int r[3] = {32,32,32}; + int GetNumRegsInCategory(int cat) override { + static int r[3] = { 32, 32, 128 }; return r[cat]; } const char *GetRegName(int cat, int index) override; - void PrintRegValue(int cat, int index, char *out) override - { - switch (cat) - { + void PrintRegValue(int cat, int index, char *out) override { + switch (cat) { case 0: sprintf(out, "%08X", cpu->r[index]); break; case 1: sprintf(out, "%f", cpu->f[index]); break; case 2: sprintf(out, "N/A"); break; } } - u32 GetHi() override - { + u32 GetHi() override { return cpu->hi; } - u32 GetLo() override - { + u32 GetLo() override { return cpu->lo; } - void SetHi(u32 val) override - { + void SetHi(u32 val) override { cpu->hi = val; } - void SetLo(u32 val) override - { + void SetLo(u32 val) override { cpu->lo = val; } - u32 GetRegValue(int cat, int index) override - { - u32 temp; - switch (cat) - { + u32 GetRegValue(int cat, int index) override { + switch (cat) { case 0: return cpu->r[index]; case 1: - memcpy(&temp, &cpu->f[index], 4); - return temp; + return cpu->fi[index]; case 2: - memcpy(&temp, &cpu->v[voffset[index]], 4); - return temp; + return cpu->vi[voffset[index]]; default: return 0; } } - void SetRegValue(int cat, int index, u32 value) override - { - switch (cat) - { + void SetRegValue(int cat, int index, u32 value) override { + switch (cat) { case 0: if (index != 0) cpu->r[index] = value; break; case 1: - memcpy(&cpu->f[index], &value, 4); + cpu->fi[index] = value; break; case 2: - memcpy(&cpu->v[voffset[index]], &value, 4); + cpu->vi[voffset[index]] = value; break; default: diff --git a/Core/MIPS/MIPSDisVFPU.cpp b/Core/MIPS/MIPSDisVFPU.cpp index 3a0b16dfd7..772bf691a0 100644 --- a/Core/MIPS/MIPSDisVFPU.cpp +++ b/Core/MIPS/MIPSDisVFPU.cpp @@ -258,7 +258,7 @@ namespace MIPSDis { const char *name = MIPSGetName(op); int vd = _VD; - MatrixSize sz = GetMtxSize(op); + MatrixSize sz = GetMtxSizeSafe(op); sprintf(out, "%s%s\t%s",name,VSuff(op),MN(vd, sz)); } void Dis_MatrixSet2(MIPSOpcode op, char *out) @@ -266,7 +266,7 @@ namespace MIPSDis const char *name = MIPSGetName(op); int vd = _VD; int vs = _VS; - MatrixSize sz = GetMtxSize(op); + MatrixSize sz = GetMtxSizeSafe(op); sprintf(out, "%s%s\t%s, %s",name,VSuff(op),MN(vd, sz),MN(vs,sz)); } void Dis_MatrixSet3(MIPSOpcode op, char *out) @@ -275,7 +275,7 @@ namespace MIPSDis int vd = _VD; int vs = _VS; int vt = _VT; - MatrixSize sz = GetMtxSize(op); + MatrixSize sz = GetMtxSizeSafe(op); sprintf(out, "%s%s\t%s, %s, %s",name,VSuff(op),MN(vd, sz),MN(vs,sz),MN(vt,sz)); } @@ -285,7 +285,7 @@ namespace MIPSDis int vd = _VD; int vs = _VS; int vt = _VT; - MatrixSize sz = GetMtxSize(op); + MatrixSize sz = GetMtxSizeSafe(op); // TODO: Xpose? sprintf(out, "%s%s\t%s, %s, %s",name,VSuff(op),MN(vd, sz),MN(Xpose(vs),sz),MN(vt,sz)); } @@ -296,7 +296,7 @@ namespace MIPSDis int vd = _VD; int vs = _VS; int vt = _VT; - MatrixSize sz = GetMtxSize(op); + MatrixSize sz = GetMtxSizeSafe(op); sprintf(out, "%s%s\t%s, %s, %s", name, VSuff(op), MN(vd, sz), MN(vs, sz), VN(vt, V_Single)); } @@ -306,7 +306,7 @@ namespace MIPSDis int vd = _VD; int vs = _VS; int vt = _VT; - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); sprintf(out, "%s%s\t%s, %s, %s", name, VSuff(op), VN(vd, V_Single), VN(vs,sz), VN(vt, sz)); } @@ -316,8 +316,8 @@ namespace MIPSDis int vs = _VS; int vt = _VT; int ins = (op>>23) & 7; - VectorSize sz = GetVecSize(op); - MatrixSize msz = GetMtxSize(op); + VectorSize sz = GetVecSizeSafe(op); + MatrixSize msz = GetMtxSizeSafe(op); int n = GetNumVectorElements(sz); if (n == ins) @@ -346,7 +346,7 @@ namespace MIPSDis int vt = _VT; int vs = _VS; int vd = _VS; - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); if (sz != V_Triple) { sprintf(out, "vcrs\tERROR"); @@ -362,7 +362,7 @@ namespace MIPSDis int vt = _VT; int vs = _VS; int cond = op&15; - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); const char *condNames[16] = {"FL","EQ","LT","LE","TR","NE","GE","GT","EZ","EN","EI","ES","NZ","NN","NI","NS"}; sprintf(out, "%s%s\t%s, %s, %s", name, VSuff(op), condNames[cond], VN(vs, sz), VN(vt,sz)); } @@ -370,7 +370,7 @@ namespace MIPSDis void Dis_Vcmov(MIPSOpcode op, char *out) { const char *name = MIPSGetName(op); - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); int vd = _VD; int vs = _VS; int tf = (op >> 19)&3; @@ -391,7 +391,7 @@ namespace MIPSDis const char *name = MIPSGetName(op); int vd = _VD; int vs = _VS; - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); sprintf(out, "%s%s\t%s, %s", name, VSuff(op), VN(vd, V_Single), VN(vs,sz)); } @@ -401,7 +401,7 @@ namespace MIPSDis int vd = _VD; int vs = _VS; int vt = _VT; - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); sprintf(out, "%s%s\t%s, %s, %s", name, VSuff(op), VN(vd, sz), VN(vs,sz), VN(vt, V_Single)); } @@ -409,7 +409,7 @@ namespace MIPSDis { const char *name = MIPSGetName(op); int vd = _VD; - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); sprintf(out, "%s%s\t%s",name,VSuff(op),VN(vd, sz)); } void Dis_VectorSet2(MIPSOpcode op, char *out) @@ -417,7 +417,7 @@ namespace MIPSDis const char *name = MIPSGetName(op); int vd = _VD; int vs = _VS; - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); sprintf(out, "%s%s\t%s, %s",name,VSuff(op),VN(vd, sz),VN(vs, sz)); } void Dis_VectorSet3(MIPSOpcode op, char *out) @@ -426,7 +426,7 @@ namespace MIPSDis int vd = _VD; int vs = _VS; int vt = _VT; - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); sprintf(out, "%s%s\t%s, %s, %s", name, VSuff(op), VN(vd, sz), VN(vs,sz), VN(vt, sz)); } @@ -445,7 +445,7 @@ namespace MIPSDis } c[(imm>>2) & 3] = 'S'; c[imm&3] = 'C'; - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); int numElems = GetNumVectorElements(sz); int pos = 0; temp[pos++] = '['; @@ -465,7 +465,7 @@ namespace MIPSDis void Dis_CrossQuat(MIPSOpcode op, char *out) { - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); const char *name; switch (sz) { @@ -490,7 +490,7 @@ namespace MIPSDis void Dis_Vbfy(MIPSOpcode op, char *out) { - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); int vd = _VD; int vs = _VS; const char *name = MIPSGetName(op); @@ -499,7 +499,7 @@ namespace MIPSDis void Dis_Vf2i(MIPSOpcode op, char *out) { - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); int vd = _VD; int vs = _VS; int imm = (op>>16)&0x1f; @@ -509,7 +509,7 @@ namespace MIPSDis void Dis_Vs2i(MIPSOpcode op, char *out) { - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); int vd = _VD; int vs = _VS; const char *name = MIPSGetName(op); @@ -518,8 +518,8 @@ namespace MIPSDis void Dis_Vi2x(MIPSOpcode op, char *out) { - VectorSize sz = GetVecSize(op); - VectorSize dsz = GetHalfVectorSize(sz); + VectorSize sz = GetVecSizeSafe(op); + VectorSize dsz = GetHalfVectorSizeSafe(sz); if (((op>>16)&3)==0) dsz = V_Single; @@ -531,7 +531,7 @@ namespace MIPSDis void Dis_Vwbn(MIPSOpcode op, char *out) { - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); int vd = _VD; int vs = _VS; @@ -542,8 +542,8 @@ namespace MIPSDis void Dis_Vf2h(MIPSOpcode op, char *out) { - VectorSize sz = GetVecSize(op); - VectorSize dsz = GetHalfVectorSize(sz); + VectorSize sz = GetVecSizeSafe(op); + VectorSize dsz = GetHalfVectorSizeSafe(sz); if (((op>>16)&3)==0) dsz = V_Single; @@ -555,8 +555,8 @@ namespace MIPSDis void Dis_Vh2f(MIPSOpcode op, char *out) { - VectorSize sz = GetVecSize(op); - VectorSize dsz = GetDoubleVectorSize(sz); + VectorSize sz = GetVecSizeSafe(op); + VectorSize dsz = GetDoubleVectorSizeSafe(sz); int vd = _VD; int vs = _VS; @@ -566,8 +566,8 @@ namespace MIPSDis void Dis_ColorConv(MIPSOpcode op, char *out) { - VectorSize sz = GetVecSize(op); - VectorSize dsz = GetHalfVectorSize(sz); + VectorSize sz = GetVecSizeSafe(op); + VectorSize dsz = GetHalfVectorSizeSafe(sz); int vd = _VD; int vs = _VS; @@ -584,7 +584,7 @@ namespace MIPSDis void Dis_VrndX(MIPSOpcode op, char *out) { - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); int vd = _VD; const char *name = MIPSGetName(op); diff --git a/Core/MIPS/MIPSVFPUUtils.cpp b/Core/MIPS/MIPSVFPUUtils.cpp index 7d99dde262..e493074665 100644 --- a/Core/MIPS/MIPSVFPUUtils.cpp +++ b/Core/MIPS/MIPSVFPUUtils.cpp @@ -303,89 +303,133 @@ int GetNumVectorElements(VectorSize sz) { } } -VectorSize GetHalfVectorSize(VectorSize sz) { +VectorSize GetHalfVectorSizeSafe(VectorSize sz) { switch (sz) { case V_Pair: return V_Single; case V_Quad: return V_Pair; - default: /*_assert_msg_(JIT, 0, "%s: Bad vector size", __FUNCTION__);*/ return V_Invalid; + default: return V_Invalid; } } -VectorSize GetDoubleVectorSize(VectorSize sz) -{ - switch (sz) - { +VectorSize GetHalfVectorSize(VectorSize sz) { + VectorSize res = GetHalfVectorSizeSafe(sz); + _assert_msg_(JIT, res != V_Invalid, "%s: Bad vector size", __FUNCTION__); + return res; +} + +VectorSize GetDoubleVectorSizeSafe(VectorSize sz) { + switch (sz) { case V_Single: return V_Pair; case V_Pair: return V_Quad; - default: /*_assert_msg_(JIT, 0, "%s: Bad vector size", __FUNCTION__);*/ return V_Invalid; + default: return V_Invalid; } } -VectorSize GetVecSize(MIPSOpcode op) -{ - int a = (op>>7)&1; - int b = (op>>15)&1; - a += (b<<1); - switch (a) - { - case 0: return V_Single; - case 1: return V_Pair; - case 2: return V_Triple; - case 3: return V_Quad; - default: /*_assert_msg_(JIT, 0, "%s: Bad vector size", __FUNCTION__);*/ return V_Invalid; +VectorSize GetDoubleVectorSize(VectorSize sz) { + VectorSize res = GetDoubleVectorSizeSafe(sz); + _assert_msg_(JIT, res != V_Invalid, "%s: Bad vector size", __FUNCTION__); + return res; +} + +VectorSize GetVecSizeSafe(MIPSOpcode op) { + int a = (op >> 7) & 1; + int b = (op >> 15) & 1; + a += (b << 1); + switch (a) { + case 0: return V_Single; + case 1: return V_Pair; + case 2: return V_Triple; + case 3: return V_Quad; + default: return V_Invalid; } } -VectorSize GetVectorSize(MatrixSize sz) { +VectorSize GetVecSize(MIPSOpcode op) { + VectorSize res = GetVecSizeSafe(op); + _assert_msg_(JIT, res != V_Invalid, "%s: Bad vector size", __FUNCTION__); + return res; +} + +VectorSize GetVectorSizeSafe(MatrixSize sz) { switch (sz) { case M_2x2: return V_Pair; case M_3x3: return V_Triple; case M_4x4: return V_Quad; - default: /*_assert_msg_(JIT, 0, "%s: Bad vector size", __FUNCTION__);*/ return V_Invalid; + default: return V_Invalid; } } -MatrixSize GetMatrixSize(VectorSize sz) { +VectorSize GetVectorSize(MatrixSize sz) { + VectorSize res = GetVectorSizeSafe(sz); + _assert_msg_(JIT, res != V_Invalid, "%s: Bad vector size", __FUNCTION__); + return res; +} + +MatrixSize GetMatrixSizeSafe(VectorSize sz) { switch (sz) { case V_Single: return M_Invalid; case V_Pair: return M_2x2; case V_Triple: return M_3x3; case V_Quad: return M_4x4; - default: /*_assert_msg_(JIT, 0, "%s: Bad vector size", __FUNCTION__);*/ return M_Invalid; + default: return M_Invalid; } } -MatrixSize GetMtxSize(MIPSOpcode op) { - int a = (op>>7)&1; - int b = (op>>15)&1; - a += (b<<1); +MatrixSize GetMatrixSize(VectorSize sz) { + MatrixSize res = GetMatrixSizeSafe(sz); + _assert_msg_(JIT, res != M_Invalid, "%s: Bad vector size", __FUNCTION__); + return res; +} + +MatrixSize GetMtxSizeSafe(MIPSOpcode op) { + int a = (op >> 7) & 1; + int b = (op >> 15) & 1; + a += (b << 1); switch (a) { case 0: return M_4x4; // This happens in disassembly of junk case 1: return M_2x2; case 2: return M_3x3; case 3: return M_4x4; - default: /*_assert_msg_(JIT, 0, "%s: Bad matrix size", __FUNCTION__);*/ return M_Invalid; + default: return M_Invalid; } } -VectorSize MatrixVectorSize(MatrixSize sz) { +MatrixSize GetMtxSize(MIPSOpcode op) { + MatrixSize res = GetMtxSizeSafe(op); + _assert_msg_(JIT, res != M_Invalid, "%s: Bad matrix size", __FUNCTION__); + return res; +} + +VectorSize MatrixVectorSizeSafe(MatrixSize sz) { switch (sz) { case M_2x2: return V_Pair; case M_3x3: return V_Triple; case M_4x4: return V_Quad; - default: /*_assert_msg_(JIT, 0, "%s: Bad matrix size", __FUNCTION__);*/ return V_Invalid; + default: return V_Invalid; } } -int GetMatrixSide(MatrixSize sz) { +VectorSize MatrixVectorSize(MatrixSize sz) { + VectorSize res = MatrixVectorSizeSafe(sz); + _assert_msg_(JIT, res != V_Invalid, "%s: Bad matrix size", __FUNCTION__); + return res; +} + +int GetMatrixSideSafe(MatrixSize sz) { switch (sz) { case M_2x2: return 2; case M_3x3: return 3; case M_4x4: return 4; - default: /*_assert_msg_(JIT, 0, "%s: Bad matrix size", __FUNCTION__);*/ return 0; + default: return 0; } } +int GetMatrixSide(MatrixSize sz) { + int res = MatrixVectorSizeSafe(sz); + _assert_msg_(JIT, res != 0, "%s: Bad matrix size", __FUNCTION__); + return res; +} + // TODO: Optimize MatrixOverlapType GetMatrixOverlap(int mtx1, int mtx2, MatrixSize msize) { int n = GetMatrixSide(msize); diff --git a/Core/MIPS/MIPSVFPUUtils.h b/Core/MIPS/MIPSVFPUUtils.h index 2e2cb62040..8e25821b1d 100644 --- a/Core/MIPS/MIPSVFPUUtils.h +++ b/Core/MIPS/MIPSVFPUUtils.h @@ -158,12 +158,18 @@ inline int GetMtx(int matrixReg) { return (matrixReg >> 2) & 7; } +VectorSize GetVecSizeSafe(MIPSOpcode op); VectorSize GetVecSize(MIPSOpcode op); +MatrixSize GetMtxSizeSafe(MIPSOpcode op); MatrixSize GetMtxSize(MIPSOpcode op); +VectorSize GetHalfVectorSizeSafe(VectorSize sz); VectorSize GetHalfVectorSize(VectorSize sz); +VectorSize GetDoubleVectorSizeSafe(VectorSize sz); VectorSize GetDoubleVectorSize(VectorSize sz); +VectorSize MatrixVectorSizeSafe(MatrixSize sz); VectorSize MatrixVectorSize(MatrixSize sz); int GetNumVectorElements(VectorSize sz); +int GetMatrixSideSafe(MatrixSize sz); int GetMatrixSide(MatrixSize sz); const char *GetVectorNotation(int reg, VectorSize size); const char *GetMatrixNotation(int reg, MatrixSize size); diff --git a/Core/MIPS/x86/Asm.cpp b/Core/MIPS/x86/Asm.cpp index a6242801ff..fd1815132d 100644 --- a/Core/MIPS/x86/Asm.cpp +++ b/Core/MIPS/x86/Asm.cpp @@ -211,11 +211,6 @@ void Jit::GenerateFixedCode(JitOptions &jo) { ABI_PopAllCalleeSavedRegsAndAdjustStack(); RET(); - breakpointBailout = GetCodePtr(); - RestoreRoundingMode(true); - ABI_PopAllCalleeSavedRegsAndAdjustStack(); - RET(); - // Let's spare the pre-generated code from unprotect-reprotect. endOfPregeneratedCode = AlignCodePage(); EndWrite(); diff --git a/Core/MIPS/x86/Jit.h b/Core/MIPS/x86/Jit.h index 1c397005ff..0eefe89c40 100644 --- a/Core/MIPS/x86/Jit.h +++ b/Core/MIPS/x86/Jit.h @@ -320,8 +320,6 @@ private: const u8 *dispatcherNoCheck; const u8 *dispatcherInEAXNoCheck; - const u8 *breakpointBailout; - const u8 *restoreRoundingMode; const u8 *applyRoundingMode; diff --git a/Core/MIPS/x86/JitSafeMem.cpp b/Core/MIPS/x86/JitSafeMem.cpp index 56374ed828..676f8b1ced 100644 --- a/Core/MIPS/x86/JitSafeMem.cpp +++ b/Core/MIPS/x86/JitSafeMem.cpp @@ -346,14 +346,12 @@ void JitSafeMem::Finish() jit_->SetJumpTarget(*it); } -void JitSafeMem::MemCheckImm(MemoryOpType type) -{ - MemCheck *check = CBreakPoints::GetMemCheck(iaddr_, size_); - if (check) - { - if (!(check->cond & MEMCHECK_READ) && type == MEM_READ) +void JitSafeMem::MemCheckImm(MemoryOpType type) { + MemCheck check; + if (CBreakPoints::GetMemCheckInRange(iaddr_, size_, &check)) { + if (!(check.cond & MEMCHECK_READ) && type == MEM_READ) return; - if (!(check->cond & MEMCHECK_WRITE) && type == MEM_WRITE) + if (!(check.cond & MEMCHECK_WRITE) && type == MEM_WRITE) return; jit_->MOV(32, MIPSSTATE_VAR(pc), Imm32(jit_->GetCompilerPC())); @@ -376,17 +374,10 @@ void JitSafeMem::MemCheckImm(MemoryOpType type) void JitSafeMem::MemCheckAsm(MemoryOpType type) { - const auto memchecks = CBreakPoints::GetMemCheckRanges(); - bool possible = false; + const auto memchecks = CBreakPoints::GetMemCheckRanges(type == MEM_WRITE); + bool possible = !memchecks.empty(); for (auto it = memchecks.begin(), end = memchecks.end(); it != end; ++it) { - if (!(it->cond & MEMCHECK_READ) && type == MEM_READ) - continue; - if (!(it->cond & MEMCHECK_WRITE) && type == MEM_WRITE) - continue; - - possible = true; - FixupBranch skipNext, skipNextRange; if (it->end != 0) { diff --git a/Core/MemMap.cpp b/Core/MemMap.cpp index eb5d49ab14..9c7a2cce19 100644 --- a/Core/MemMap.cpp +++ b/Core/MemMap.cpp @@ -38,6 +38,7 @@ #include "Core/Debugger/SymbolMap.h" #include "Core/Debugger/Breakpoints.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/HLE/ReplaceTables.h" #include "Core/MIPS/JitCommon/JitBlockCache.h" @@ -291,6 +292,14 @@ void Init() { base, m_pPhysicalRAM, m_pUncachedRAM); } +void Reinit() { + _assert_msg_(SYSTEM, PSP_IsInited(), "Cannot reinit during startup/shutdown"); + Core_NotifyLifecycle(CoreLifecycle::MEMORY_REINITING); + Shutdown(); + Init(); + Core_NotifyLifecycle(CoreLifecycle::MEMORY_REINITED); +} + void DoState(PointerWrap &p) { auto s = p.Section("Memory", 1, 3); if (!s) @@ -308,8 +317,7 @@ void DoState(PointerWrap &p) { if (!g_RemasterMode) { g_MemorySize = g_PSPModel == PSP_MODEL_FAT ? RAM_NORMAL_SIZE : RAM_DOUBLE_SIZE; if (oldMemorySize < g_MemorySize) { - Shutdown(); - Init(); + Reinit(); } } } else { @@ -320,8 +328,7 @@ void DoState(PointerWrap &p) { p.DoMarker("PSPModel"); p.Do(g_MemorySize); if (oldMemorySize != g_MemorySize) { - Shutdown(); - Init(); + Reinit(); } } @@ -446,9 +453,8 @@ void Memset(const u32 _Address, const u8 _iValue, const u32 _iLength) { for (size_t i = 0; i < _iLength; i++) Write_U8(_iValue, (u32)(_Address + i)); } -#ifndef MOBILE_DEVICE + CBreakPoints::ExecMemCheck(_Address, true, _iLength, currentMIPS->pc); -#endif } } // namespace diff --git a/Core/MemMapFunctions.cpp b/Core/MemMapFunctions.cpp index bf2cff521d..a463a0e0ae 100644 --- a/Core/MemMapFunctions.cpp +++ b/Core/MemMapFunctions.cpp @@ -21,6 +21,7 @@ #include "Core/Core.h" #include "Core/MemMap.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/Host.h" #include "Core/Reporting.h" diff --git a/Core/MemMapHelpers.h b/Core/MemMapHelpers.h index 1fe06d3ae1..9431db5c0d 100644 --- a/Core/MemMapHelpers.h +++ b/Core/MemMapHelpers.h @@ -33,9 +33,7 @@ inline void Memcpy(const u32 to_address, const void *from_data, const u32 len) u8 *to = GetPointer(to_address); if (to) { memcpy(to, from_data, len); -#ifndef MOBILE_DEVICE CBreakPoints::ExecMemCheck(to_address, true, len, currentMIPS->pc); -#endif } // if not, GetPointer will log. } @@ -45,9 +43,7 @@ inline void Memcpy(void *to_data, const u32 from_address, const u32 len) const u8 *from = GetPointer(from_address); if (from) { memcpy(to_data, from, len); -#ifndef MOBILE_DEVICE CBreakPoints::ExecMemCheck(from_address, false, len, currentMIPS->pc); -#endif } // if not, GetPointer will log. } @@ -55,9 +51,7 @@ inline void Memcpy(void *to_data, const u32 from_address, const u32 len) inline void Memcpy(const u32 to_address, const u32 from_address, const u32 len) { Memcpy(GetPointer(to_address), from_address, len); -#ifndef MOBILE_DEVICE CBreakPoints::ExecMemCheck(to_address, true, len, currentMIPS->pc); -#endif } void Memset(const u32 _Address, const u8 _Data, const u32 _iLength); diff --git a/Core/PSPLoaders.cpp b/Core/PSPLoaders.cpp index 7567d0b29c..8ff92b6e29 100644 --- a/Core/PSPLoaders.cpp +++ b/Core/PSPLoaders.cpp @@ -48,6 +48,7 @@ #include "Host.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/System.h" #include "Core/PSPLoaders.h" #include "Core/HLE/HLE.h" @@ -260,7 +261,7 @@ bool Load_PSP_ISO(FileLoader *fileLoader, std::string *error_string) { // TODO: We can't use the initial error_string pointer. bool success = __KernelLoadExec(bootpath.c_str(), 0, &PSP_CoreParameter().errorString); if (success && coreState == CORE_POWERUP) { - coreState = PSP_CoreParameter().startPaused ? CORE_STEPPING : CORE_RUNNING; + coreState = PSP_CoreParameter().startBreak ? CORE_STEPPING : CORE_RUNNING; } else { coreState = CORE_ERROR; // TODO: This is a crummy way to communicate the error... @@ -375,7 +376,7 @@ bool Load_PSP_ELF_PBP(FileLoader *fileLoader, std::string *error_string) { std::thread th([finalName] { bool success = __KernelLoadExec(finalName.c_str(), 0, &PSP_CoreParameter().errorString); if (success && coreState == CORE_POWERUP) { - coreState = PSP_CoreParameter().startPaused ? CORE_STEPPING : CORE_RUNNING; + coreState = PSP_CoreParameter().startBreak ? CORE_STEPPING : CORE_RUNNING; } else { coreState = CORE_ERROR; // TODO: This is a crummy way to communicate the error... @@ -393,7 +394,7 @@ bool Load_PSP_GE_Dump(FileLoader *fileLoader, std::string *error_string) { std::thread th([] { bool success = __KernelLoadGEDump("disc0:/data.ppdmp", &PSP_CoreParameter().errorString); if (success && coreState == CORE_POWERUP) { - coreState = PSP_CoreParameter().startPaused ? CORE_STEPPING : CORE_RUNNING; + coreState = PSP_CoreParameter().startBreak ? CORE_STEPPING : CORE_RUNNING; } else { coreState = CORE_ERROR; // TODO: This is a crummy way to communicate the error... diff --git a/Core/Reporting.cpp b/Core/Reporting.cpp index 02503301d8..a927c55510 100644 --- a/Core/Reporting.cpp +++ b/Core/Reporting.cpp @@ -67,6 +67,8 @@ namespace Reporting static bool currentSupported = false; // Whether the most recent server request seemed successful. static bool serverWorking = true; + // The latest compatibility result from the server. + static std::vector lastCompatResult; enum class RequestType { @@ -423,6 +425,7 @@ namespace Reporting postdata.Add("speed", StringFromFormat("%d", payload.int2)); postdata.Add("gameplay", StringFromFormat("%d", payload.int3)); postdata.Add("crc", StringFromFormat("%08x", Core_GetPowerSaving() ? 0 : RetrieveCRC())); + postdata.Add("suggestions", payload.string1 != "perfect" && payload.string1 != "playable" ? "1" : "0"); AddScreenshotData(postdata, payload.string2); payload.string1.clear(); payload.string2.clear(); @@ -432,12 +435,14 @@ namespace Reporting if (!SendReportRequest("/report/compat", postdata.ToString(), postdata.GetMimeType(), &output)) { serverWorking = false; } else { - char res = 0; - if (!output.empty()) { - output.Take(1, &res); - } - if (res == 0) + std::string result; + output.TakeAll(&result); + + lastCompatResult.clear(); + if (result.empty() || result[0] == '0') serverWorking = false; + else if (result[0] != '1') + SplitString(result, '\n', lastCompatResult); } break; @@ -594,4 +599,8 @@ namespace Reporting std::thread th(Process, pos); th.detach(); } + + std::vector CompatibilitySuggestions() { + return lastCompatResult; + } } diff --git a/Core/Reporting.h b/Core/Reporting.h index 08acc9d267..c34f815119 100644 --- a/Core/Reporting.h +++ b/Core/Reporting.h @@ -20,6 +20,7 @@ #include "Common/CommonTypes.h" #include "Common/Log.h" #include +#include #define DEBUG_LOG_REPORT(t,...) do { DEBUG_LOG(t, __VA_ARGS__); Reporting::ReportMessage(__VA_ARGS__); } while (false) #define ERROR_LOG_REPORT(t,...) do { ERROR_LOG(t, __VA_ARGS__); Reporting::ReportMessage(__VA_ARGS__); } while (false) @@ -74,6 +75,9 @@ namespace Reporting // Report the compatibility of the current game / configuration. void ReportCompatibility(const char *compat, int graphics, int speed, int gameplay, const std::string &screenshotFilename); + // Get the latest compatibility result. Only valid when GetStatus() is not BUSY. + std::vector CompatibilitySuggestions(); + // Returns true if that identifier has not been logged yet. bool ShouldLogOnce(const char *identifier); diff --git a/Core/SaveState.cpp b/Core/SaveState.cpp index f3a3bf72f1..3f14e7dc4e 100644 --- a/Core/SaveState.cpp +++ b/Core/SaveState.cpp @@ -23,6 +23,7 @@ #include "base/timeutil.h" #include "i18n/i18n.h" #include "thread/threadutil.h" +#include "util/text/parsers.h" #include "Common/FileUtil.h" #include "Common/ChunkFile.h" @@ -241,6 +242,11 @@ namespace SaveState static std::vector pending; static std::mutex mutex; static bool hasLoadedState = false; + static const int STALE_STATE_USES = 2; + // 4 hours of total gameplay since the virtual PSP started the game. + static const u64 STALE_STATE_TIME = 4 * 3600 * 1000000ULL; + static int saveStateGeneration = 0; + static std::string saveStateInitialGitVersion = ""; // TODO: Should this be configurable? static const int REWIND_NUM_STATES = 20; @@ -253,10 +259,22 @@ namespace SaveState void SaveStart::DoState(PointerWrap &p) { - auto s = p.Section("SaveStart", 1); + auto s = p.Section("SaveStart", 1, 2); if (!s) return; + if (s >= 2) { + // This only increments on save, of course. + ++saveStateGeneration; + p.Do(saveStateGeneration); + // This saves the first git version to create this save state (or generation of save states.) + if (saveStateInitialGitVersion.empty()) + saveStateInitialGitVersion = PPSSPP_GIT_VERSION; + p.Do(saveStateInitialGitVersion); + } else { + saveStateGeneration = 1; + } + // Gotta do CoreTiming first since we'll restore into it. CoreTiming::DoState(p); @@ -420,7 +438,29 @@ namespace SaveState } else { I18NCategory *sy = GetI18NCategory("System"); if (callback) - callback(false, sy->T("Failed to load state. Error in the file system."), cbUserData); + callback(Status::FAILURE, sy->T("Failed to load state. Error in the file system."), cbUserData); + } + } + + static void DeleteIfExists(const std::string &fn) { + // Just avoiding error messages. + if (File::Exists(fn)) { + File::Delete(fn); + } + } + + static void RenameIfExists(const std::string &from, const std::string &to) { + if (File::Exists(from)) { + File::Rename(from, to); + } + } + + static void SwapIfExists(const std::string &from, const std::string &to) { + std::string temp = from + ".tmp"; + if (File::Exists(from)) { + File::Rename(from, temp); + File::Rename(to, from); + File::Rename(temp, to); } } @@ -431,13 +471,13 @@ namespace SaveState std::string fnUndo = GenerateSaveSlotFilename(gameFilename, slot, UNDO_STATE_EXTENSION); std::string shotUndo = GenerateSaveSlotFilename(gameFilename, slot, UNDO_SCREENSHOT_EXTENSION); if (!fn.empty()) { - auto renameCallback = [=](bool status, const std::string &message, void *data) { - if (status) { - if (File::Exists(fnUndo) && g_Config.bEnableStateUndo) { - File::Delete(fnUndo); - } - if (File::Exists(fn) && g_Config.bEnableStateUndo) { - File::Rename(fn, fnUndo); + auto renameCallback = [=](Status status, const std::string &message, void *data) { + if (status != Status::FAILURE) { + if (g_Config.bEnableStateUndo) { + DeleteIfExists(fnUndo); + RenameIfExists(fn, fnUndo); + } else { + DeleteIfExists(fn); } File::Rename(fn + ".tmp", fn); } @@ -446,15 +486,16 @@ namespace SaveState } }; // Let's also create a screenshot. - if (File::Exists(shot) && g_Config.bEnableStateUndo) { - File::Rename(shot, shotUndo); + if (g_Config.bEnableStateUndo) { + DeleteIfExists(shotUndo); + RenameIfExists(shot, shotUndo); } SaveScreenshot(shot, Callback(), 0); Save(fn + ".tmp", renameCallback, cbUserData); } else { I18NCategory *sy = GetI18NCategory("System"); if (callback) - callback(false, sy->T("Failed to save state. Error in the file system."), cbUserData); + callback(Status::FAILURE, sy->T("Failed to save state. Error in the file system."), cbUserData); } } @@ -467,15 +508,8 @@ namespace SaveState // Do nothing if there's no undo. if (File::Exists(fnUndo)) { // Swap them so they can undo again to redo. Mistakes happen. - if (File::Exists(shotUndo)) { - File::Rename(shot, shot + ".tmp"); - File::Rename(shotUndo, shot); - File::Rename(shot + ".tmp", shotUndo); - } - - File::Rename(fn, fn + ".tmp"); - File::Rename(fnUndo, fn); - File::Rename(fn + ".tmp", fnUndo); + SwapIfExists(shotUndo, shot); + SwapIfExists(fnUndo, fn); return true; } @@ -517,6 +551,27 @@ namespace SaveState return false; } + bool operator > (const tm &t1, const tm &t2) { + if (t1.tm_year > t2.tm_year) return true; + if (t1.tm_year < t2.tm_year) return false; + if (t1.tm_mon > t2.tm_mon) return true; + if (t1.tm_mon < t2.tm_mon) return false; + if (t1.tm_mday > t2.tm_mday) return true; + if (t1.tm_mday < t2.tm_mday) return false; + if (t1.tm_hour > t2.tm_hour) return true; + if (t1.tm_hour < t2.tm_hour) return false; + if (t1.tm_min > t2.tm_min) return true; + if (t1.tm_min < t2.tm_min) return false; + if (t1.tm_sec > t2.tm_sec) return true; + if (t1.tm_sec < t2.tm_sec) return false; + return false; + } + + bool operator ! (const tm &t1) { + if (t1.tm_year || t1.tm_mon || t1.tm_mday || t1.tm_hour || t1.tm_min || t1.tm_sec) return false; + return true; + } + int GetNewestSlot(const std::string &gameFilename) { int newestSlot = -1; tm newestDate = {0}; @@ -534,6 +589,23 @@ namespace SaveState return newestSlot; } + int GetOldestSlot(const std::string &gameFilename) { + int oldestSlot = -1; + tm oldestDate = {0}; + for (int i = 0; i < NUM_SLOTS; i++) { + std::string fn = GenerateSaveSlotFilename(gameFilename, i, STATE_EXTENSION); + if (File::Exists(fn)) { + tm time; + bool success = File::GetModifTime(fn, time); + if (success && (!oldestDate || oldestDate > time)) { + oldestDate = time; + oldestSlot = i; + } + } + } + return oldestSlot; + } + std::string GetSlotDateAsString(const std::string &gameFilename, int slot) { std::string fn = GenerateSaveSlotFilename(gameFilename, slot, STATE_EXTENSION); if (File::Exists(fn)) { @@ -603,11 +675,29 @@ namespace SaveState } #endif - bool HasLoadedState() - { + bool HasLoadedState() { return hasLoadedState; } + bool IsStale() { + if (saveStateGeneration >= STALE_STATE_USES) { + return CoreTiming::GetGlobalTimeUs() > STALE_STATE_TIME; + } + return false; + } + + bool IsOldVersion() { + if (saveStateInitialGitVersion.empty()) + return false; + + Version state(saveStateInitialGitVersion); + Version gitVer(PPSSPP_GIT_VERSION); + if (!state.IsValid() || !gitVer.IsValid()) + return false; + + return state < gitVer; + } + void Process() { #ifndef MOBILE_DEVICE @@ -632,7 +722,8 @@ namespace SaveState { Operation &op = operations[i]; CChunkFileReader::Error result; - bool callbackResult; + Status callbackResult; + bool tempResult; std::string callbackMessage; std::string reason; std::string title; @@ -649,11 +740,26 @@ namespace SaveState { case SAVESTATE_LOAD: INFO_LOG(SAVESTATE, "Loading state from %s", op.filename.c_str()); - result = CChunkFileReader::Load(op.filename, PPSSPP_GIT_VERSION, state, &reason); + // Use the state's latest version as a guess for saveStateInitialGitVersion. + result = CChunkFileReader::Load(op.filename, &saveStateInitialGitVersion, state, &reason); if (result == CChunkFileReader::ERROR_NONE) { callbackMessage = sc->T("Loaded State"); - callbackResult = true; + callbackResult = Status::SUCCESS; hasLoadedState = true; + + if (!g_Config.bHideStateWarnings && IsStale()) { + // For anyone wondering why (too long to put on the screen in an osm): + // Using save states instead of saves simulates many hour play sessions. + // Sometimes this exposes game bugs that were rarely seen on real devices, + // because few people played on a real PSP for 10 hours straight. + callbackMessage = sc->T("Loaded. Save in game, restart, and load for less bugs."); + callbackResult = Status::WARNING; + } else if (!g_Config.bHideStateWarnings && IsOldVersion()) { + // Save states also preserve bugs from old PPSSPP versions, so warn. + callbackMessage = sc->T("Loaded. Save in game, restart, and load for less bugs."); + callbackResult = Status::WARNING; + } + #ifndef MOBILE_DEVICE if (g_Config.bSaveLoadResetsAVdumping) { if (g_Config.bDumpFrames) { @@ -669,10 +775,10 @@ namespace SaveState HandleFailure(); callbackMessage = i18nLoadFailure; ERROR_LOG(SAVESTATE, "Load state failure: %s", reason.c_str()); - callbackResult = false; + callbackResult = Status::FAILURE; } else { callbackMessage = sc->T(reason.c_str(), i18nLoadFailure); - callbackResult = false; + callbackResult = Status::FAILURE; } break; @@ -688,7 +794,7 @@ namespace SaveState result = CChunkFileReader::Save(op.filename, title, PPSSPP_GIT_VERSION, state); if (result == CChunkFileReader::ERROR_NONE) { callbackMessage = sc->T("Saved State"); - callbackResult = true; + callbackResult = Status::SUCCESS; #ifndef MOBILE_DEVICE if (g_Config.bSaveLoadResetsAVdumping) { if (g_Config.bDumpFrames) { @@ -704,16 +810,17 @@ namespace SaveState HandleFailure(); callbackMessage = i18nSaveFailure; ERROR_LOG(SAVESTATE, "Save state failure: %s", reason.c_str()); - callbackResult = false; + callbackResult = Status::FAILURE; } else { callbackMessage = i18nSaveFailure; - callbackResult = false; + callbackResult = Status::FAILURE; } break; case SAVESTATE_VERIFY: - callbackResult = CChunkFileReader::Verify(state) == CChunkFileReader::ERROR_NONE; - if (callbackResult) { + tempResult = CChunkFileReader::Verify(state) == CChunkFileReader::ERROR_NONE; + callbackResult = tempResult ? Status::SUCCESS : Status::FAILURE; + if (tempResult) { INFO_LOG(SAVESTATE, "Verified save state system"); } else { ERROR_LOG(SAVESTATE, "Save state system verification failed"); @@ -725,35 +832,38 @@ namespace SaveState result = rewindStates.Restore(); if (result == CChunkFileReader::ERROR_NONE) { callbackMessage = sc->T("Loaded State"); - callbackResult = true; + callbackResult = Status::SUCCESS; hasLoadedState = true; } else if (result == CChunkFileReader::ERROR_BROKEN_STATE) { // Cripes. Good news is, we might have more. Let's try those too, better than a reset. if (HandleFailure()) { // Well, we did rewind, even if too much... callbackMessage = sc->T("Loaded State"); - callbackResult = true; + callbackResult = Status::SUCCESS; hasLoadedState = true; } else { callbackMessage = i18nLoadFailure; - callbackResult = false; + callbackResult = Status::FAILURE; } } else { callbackMessage = i18nLoadFailure; - callbackResult = false; + callbackResult = Status::FAILURE; } break; case SAVESTATE_SAVE_SCREENSHOT: - callbackResult = TakeGameScreenshot(op.filename.c_str(), ScreenshotFormat::JPG, SCREENSHOT_DISPLAY); - if (!callbackResult) { + { + int maxRes = g_Config.iInternalResolution > 2 ? 2 : -1; + tempResult = TakeGameScreenshot(op.filename.c_str(), ScreenshotFormat::JPG, SCREENSHOT_DISPLAY, nullptr, nullptr, maxRes); + callbackResult = tempResult ? Status::SUCCESS : Status::FAILURE; + if (!tempResult) { ERROR_LOG(SAVESTATE, "Failed to take a screenshot for the savestate! %s", op.filename.c_str()); } break; - + } default: ERROR_LOG(SAVESTATE, "Savestate failure: unknown operation type %d", op.type); - callbackResult = false; + callbackResult = Status::FAILURE; break; } @@ -775,6 +885,8 @@ namespace SaveState rewindStates.Clear(); hasLoadedState = false; + saveStateGeneration = 0; + saveStateInitialGitVersion.clear(); } void Shutdown() diff --git a/Core/SaveState.h b/Core/SaveState.h index 64c4dbe0c7..e190e6e65d 100644 --- a/Core/SaveState.h +++ b/Core/SaveState.h @@ -23,7 +23,12 @@ namespace SaveState { - typedef std::function Callback; + enum class Status { + FAILURE, + WARNING, + SUCCESS, + }; + typedef std::function Callback; static const int NUM_SLOTS = 5; static const char *STATE_EXTENSION = "ppst"; @@ -46,9 +51,10 @@ namespace SaveState int GetCurrentSlot(); - // Returns -1 if there's no newest slot. + // Returns -1 if there's no oldest/newest slot. int GetNewestSlot(const std::string &gameFilename); - + int GetOldestSlot(const std::string &gameFilename); + std::string GetSlotDateAsString(const std::string &gameFilename, int slot); std::string GenerateSaveSlotFilename(const std::string &gameFilename, int slot, const char *extension); @@ -79,6 +85,12 @@ namespace SaveState // Returns true if a savestate has been used during this session. bool HasLoadedState(); + // Returns true if the state has been reused instead of real saves many times. + bool IsStale(); + + // Returns true if state is from an older PPSSPP version. + bool IsOldVersion(); + // Check if there's any save stating needing to be done. Normally called once per frame. void Process(); }; diff --git a/Core/System.cpp b/Core/System.cpp index cc559308c4..bd55510b06 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -30,6 +30,7 @@ #include #include "base/timeutil.h" +#include "base/NativeApp.h" #include "math/math_util.h" #include "thread/threadutil.h" #include "util/text/utf8.h" @@ -106,6 +107,16 @@ void UpdateUIState(GlobalUIState newState) { if (globalUIState != newState && globalUIState != UISTATE_EXIT) { globalUIState = newState; host->UpdateDisassembly(); + const char *state = nullptr; + switch (globalUIState) { + case UISTATE_EXIT: state = "exit"; break; + case UISTATE_INGAME: state = "ingame"; break; + case UISTATE_MENU: state = "menu"; break; + case UISTATE_PAUSEMENU: state = "pausemenu"; break; + } + if (state) { + System_SendMessage("uistate", state); + } } } @@ -306,6 +317,7 @@ bool PSP_InitStart(const CoreParameter &coreParam, std::string *error_string) { INFO_LOG(BOOT, "PPSSPP %s", PPSSPP_GIT_VERSION); #endif + Core_NotifyLifecycle(CoreLifecycle::STARTING); GraphicsContext *temp = coreParameter.graphicsContext; coreParameter = coreParam; if (coreParameter.graphicsContext == nullptr) { @@ -320,6 +332,7 @@ bool PSP_InitStart(const CoreParameter &coreParam, std::string *error_string) { *error_string = coreParameter.errorString; bool success = coreParameter.fileToStart != ""; if (!success) { + Core_NotifyLifecycle(CoreLifecycle::START_COMPLETE); pspIsIniting = false; } return success; @@ -340,13 +353,20 @@ bool PSP_InitUpdate(std::string *error_string) { PSP_SetLoading("Starting graphics..."); success = GPU_Init(coreParameter.graphicsContext, coreParameter.thin3d); if (!success) { - PSP_Shutdown(); *error_string = "Unable to initialize rendering engine."; } } - pspIsInited = success && GPU_IsReady(); - pspIsIniting = success && !pspIsInited; - return !success || pspIsInited; + if (!success) { + PSP_Shutdown(); + return true; + } + + pspIsInited = GPU_IsReady(); + pspIsIniting = !pspIsInited; + if (pspIsInited) { + Core_NotifyLifecycle(CoreLifecycle::START_COMPLETE); + } + return pspIsInited; } bool PSP_Init(const CoreParameter &coreParam, std::string *error_string) { @@ -365,23 +385,30 @@ bool PSP_IsInited() { return pspIsInited && !pspIsQuitting; } +bool PSP_IsQuitting() { + return pspIsQuitting; +} + void PSP_Shutdown() { // Do nothing if we never inited. if (!pspIsInited && !pspIsIniting && !pspIsQuitting) { return; } + // Make sure things know right away that PSP memory, etc. is going away. + pspIsQuitting = true; + if (coreState == CORE_RUNNING) + Core_UpdateState(CORE_ERROR); + #ifndef MOBILE_DEVICE if (g_Config.bFuncHashMap) { MIPSAnalyst::StoreHashMap(); } #endif - // Make sure things know right away that PSP memory, etc. is going away. - pspIsQuitting = true; - if (coreState == CORE_RUNNING) - Core_UpdateState(CORE_ERROR); - Core_NotifyShutdown(); + if (pspIsIniting) + Core_NotifyLifecycle(CoreLifecycle::START_COMPLETE); + Core_NotifyLifecycle(CoreLifecycle::STOPPING); CPU_Shutdown(); GPU_Shutdown(); g_paramSFO.Clear(); @@ -391,6 +418,7 @@ void PSP_Shutdown() { pspIsIniting = false; pspIsQuitting = false; g_Config.unloadGameConfig(); + Core_NotifyLifecycle(CoreLifecycle::STOPPED); } void PSP_BeginHostFrame() { @@ -406,10 +434,28 @@ void PSP_EndHostFrame() { } } +void PSP_RunLoopWhileState() { + // We just run the CPU until we get to vblank. This will quickly sync up pretty nicely. + // The actual number of cycles doesn't matter so much here as we will break due to CORE_NEXTFRAME, most of the time hopefully... + int blockTicks = usToCycles(1000000 / 10); + + // Run until CORE_NEXTFRAME + while (coreState == CORE_RUNNING || coreState == CORE_STEPPING) { + PSP_RunLoopFor(blockTicks); + if (coreState == CORE_STEPPING) { + // Keep the UI responsive. + break; + } + } +} + void PSP_RunLoopUntil(u64 globalticks) { SaveState::Process(); if (coreState == CORE_POWERDOWN || coreState == CORE_ERROR) { return; + } else if (coreState == CORE_STEPPING) { + Core_ProcessStepping(); + return; } mipsr4k.RunLoopUntil(globalticks); @@ -553,10 +599,6 @@ void InitSysDirectories() { File::CreateDir(g_Config.memStickDirectory + "PSP/GAME"); File::CreateDir(g_Config.memStickDirectory + "PSP/SAVEDATA"); File::CreateDir(g_Config.memStickDirectory + "PSP/PPSSPP_STATE"); -#ifdef ANDROID - // Avoid media scanners in PPSSPP_STATE directory - File::CreateEmptyFile(g_Config.memStickDirectory + "PSP/PPSSPP_STATE/.nomedia"); -#endif if (g_Config.currentDirectory.empty()) { g_Config.currentDirectory = GetSysDirectory(DIRECTORY_GAME); diff --git a/Core/System.h b/Core/System.h index a2a3ed5c48..f25974970e 100644 --- a/Core/System.h +++ b/Core/System.h @@ -66,10 +66,12 @@ bool PSP_InitStart(const CoreParameter &coreParam, std::string *error_string); bool PSP_InitUpdate(std::string *error_string); bool PSP_IsIniting(); bool PSP_IsInited(); +bool PSP_IsQuitting(); void PSP_Shutdown(); void PSP_BeginHostFrame(); void PSP_EndHostFrame(); +void PSP_RunLoopWhileState(); void PSP_RunLoopUntil(u64 globalticks); void PSP_RunLoopFor(int cycles); diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index 3090df864b..fdb9254ec4 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -15,7 +15,9 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. -#ifndef USING_QT_UI +#ifdef USING_QT_UI +#include +#else #include #endif @@ -279,39 +281,50 @@ void TextureReplacer::PopulateReplacement(ReplacedTexture *result, u64 cachekey, break; } + bool good = false; ReplacedTextureLevel level; level.fmt = ReplacedTextureFormat::F_8888; level.file = filename; #ifdef USING_QT_UI - ERROR_LOG(G3D, "Replacement texture loading not implemented for Qt"); + QImage image(filename.c_str(), "PNG"); + if (image.isNull()) { + ERROR_LOG(G3D, "Could not load texture replacement info: %s", filename.c_str()); + } else { + level.w = (image.width() * w) / newW; + level.h = (image.height() * h) / newH; + good = true; + } #else png_image png = {}; png.version = PNG_IMAGE_VERSION; FILE *fp = File::OpenCFile(filename, "rb"); - bool bad = false; if (png_image_begin_read_from_stdio(&png, fp)) { // We pad files that have been hashrange'd so they are the same texture size. level.w = (png.width * w) / newW; level.h = (png.height * h) / newH; - if (i != 0) { - // Check that the mipmap size is correct. Can't load mips of the wrong size. - if (level.w != (result->levels_[0].w >> i) || level.h != (result->levels_[0].h >> i)) { - WARN_LOG(G3D, "Replacement mipmap invalid: size=%dx%d, expected=%dx%d (level %d, '%s')", level.w, level.h, result->levels_[0].w >> i, result->levels_[0].h >> i, i, filename.c_str()); - bad = true; - } - } - if (!bad) - result->levels_.push_back(level); + good = true; } else { ERROR_LOG(G3D, "Could not load texture replacement info: %s - %s", filename.c_str(), png.message); } fclose(fp); png_image_free(&png); - if (bad) - break; // Don't try to load any more mips. #endif + + if (good && i != 0) { + // Check that the mipmap size is correct. Can't load mips of the wrong size. + if (level.w != (result->levels_[0].w >> i) || level.h != (result->levels_[0].h >> i)) { + WARN_LOG(G3D, "Replacement mipmap invalid: size=%dx%d, expected=%dx%d (level %d, '%s')", level.w, level.h, result->levels_[0].w >> i, result->levels_[0].h >> i, i, filename.c_str()); + good = false; + } + } + + if (good) + result->levels_.push_back(level); + // Otherwise, we're done loading mips (bad PNG or bad size, either way.) + else + break; } result->alphaStatus_ = ReplacedTextureAlpha::UNKNOWN; @@ -540,7 +553,32 @@ void ReplacedTexture::Load(int level, void *out, int rowPitch) { const ReplacedTextureLevel &info = levels_[level]; #ifdef USING_QT_UI - ERROR_LOG(G3D, "Replacement texture loading not implemented for Qt"); + QImage image(info.file.c_str(), "PNG"); + if (image.isNull()) { + ERROR_LOG(G3D, "Could not load texture replacement info: %s", info.file.c_str()); + return; + } + + image = image.convertToFormat(QImage::Format_ARGB32); + bool alphaFull = true; + for (int y = 0; y < image.height(); ++y) { + const QRgb *src = (const QRgb *)image.constScanLine(y); + uint8_t *outLine = (uint8_t *)out + y * rowPitch; + for (int x = 0; x < image.width(); ++x) { + outLine[x * 4 + 0] = qRed(src[x]); + outLine[x * 4 + 1] = qGreen(src[x]); + outLine[x * 4 + 2] = qBlue(src[x]); + outLine[x * 4 + 3] = qAlpha(src[x]); + // We're already scanning each pixel... + if (qAlpha(src[x]) != 255) { + alphaFull = false; + } + } + } + + if (level == 0 || !alphaFull) { + alphaStatus_ = alphaFull ? ReplacedTextureAlpha::FULL : ReplacedTextureAlpha::UNKNOWN; + } #else png_image png = {}; png.version = PNG_IMAGE_VERSION; diff --git a/Core/TextureReplacer.h b/Core/TextureReplacer.h index 49606c0164..1e4b7cdae0 100644 --- a/Core/TextureReplacer.h +++ b/Core/TextureReplacer.h @@ -43,7 +43,6 @@ enum class ReplacedTextureFormat { enum class ReplacedTextureAlpha { UNKNOWN = 0x04, FULL = 0x00, - SIMPLE = 0x08, }; // For forward comatibility, we specify the hash. diff --git a/Core/Util/BlockAllocator.cpp b/Core/Util/BlockAllocator.cpp index 6c14dfe2b1..02496432b1 100644 --- a/Core/Util/BlockAllocator.cpp +++ b/Core/Util/BlockAllocator.cpp @@ -501,5 +501,10 @@ void BlockAllocator::Block::DoState(PointerWrap &p) p.Do(start); p.Do(size); p.Do(taken); + // Since we use truncate_cpy, the empty space is not zeroed. Zero it now. + // This avoids saving uninitialized memory. + size_t tagLen = strlen(tag); + if (tagLen != sizeof(tag)) + memset(tag + tagLen, 0, sizeof(tag) - tagLen); p.DoArray(tag, sizeof(tag)); } diff --git a/Core/WebServer.cpp b/Core/WebServer.cpp new file mode 100644 index 0000000000..284c1c0333 --- /dev/null +++ b/Core/WebServer.cpp @@ -0,0 +1,285 @@ +// Copyright (c) 2014- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include +#include +#include +#include +#include "base/stringutil.h" +#include "base/timeutil.h" +#include "file/fd_util.h" +#include "net/http_client.h" +#include "net/http_server.h" +#include "net/sinks.h" +#include "thread/threadutil.h" +#include "Common/FileUtil.h" +#include "Common/Log.h" +#include "Core/Config.h" +#include "Core/Debugger/WebSocket.h" +#include "Core/WebServer.h" + +enum class ServerStatus { + STOPPED, + STARTING, + RUNNING, + STOPPING, + RESTARTING, +}; + +static const char *REPORT_HOSTNAME = "report.ppsspp.org"; +static const int REPORT_PORT = 80; + +static std::thread serverThread; +static ServerStatus serverStatus; +static std::mutex serverStatusLock; +static int serverFlags; + +static void UpdateStatus(ServerStatus s) { + std::lock_guard guard(serverStatusLock); + serverStatus = s; +} + +static bool UpdateStatus(ServerStatus s, ServerStatus old) { + std::lock_guard guard(serverStatusLock); + if (serverStatus == old) { + serverStatus = s; + return true; + } + return false; +} + +static ServerStatus RetrieveStatus() { + std::lock_guard guard(serverStatusLock); + return serverStatus; +} + +// This reports the local IP address to report.ppsspp.org, which can then +// relay that address to a mobile device searching for the server. +static void RegisterServer(int port) { + http::Client http; + Buffer theVoid; + + char resource4[1024] = {}; + if (http.Resolve(REPORT_HOSTNAME, REPORT_PORT, net::DNSType::IPV4)) { + if (http.Connect()) { + std::string ip = fd_util::GetLocalIP(http.sock()); + snprintf(resource4, sizeof(resource4) - 1, "/match/update?local=%s&port=%d", ip.c_str(), port); + + http.GET(resource4, &theVoid); + theVoid.Skip(theVoid.size()); + http.Disconnect(); + } + } + + if (http.Resolve(REPORT_HOSTNAME, REPORT_PORT, net::DNSType::IPV6)) { + // We register both IPv4 and IPv6 in case the other client is using a different one. + if (resource4[0] != 0 && http.Connect()) { + http.GET(resource4, &theVoid); + theVoid.Skip(theVoid.size()); + http.Disconnect(); + } + + // Currently, we're not using keepalive, so gotta reconnect... + if (http.Connect()) { + char resource6[1024] = {}; + std::string ip = fd_util::GetLocalIP(http.sock()); + snprintf(resource6, sizeof(resource6) - 1, "/match/update?local=%s&port=%d", ip.c_str(), port); + + http.GET(resource6, &theVoid); + theVoid.Skip(theVoid.size()); + http.Disconnect(); + } + } +} + +static void RegisterDiscHandlers(http::Server *http, std::unordered_map *paths) { + for (std::string filename : g_Config.recentIsos) { +#ifdef _WIN32 + static const std::string sep = "\\/"; +#else + static const std::string sep = "/"; +#endif + size_t basepos = filename.find_last_of(sep); + std::string basename = "/" + (basepos == filename.npos ? filename : filename.substr(basepos + 1)); + + // Let's not serve directories, since they won't work. Only single files. + // Maybe can do PBPs and other files later. Would be neat to stream virtual disc filesystems. + if (endsWithNoCase(basename, ".cso") || endsWithNoCase(basename, ".iso")) { + (*paths)[ReplaceAll(basename, " ", "%20")] = filename; + } + } + + auto handler = [paths](const http::Request &request) { + std::string filename = (*paths)[request.resource()]; + s64 sz = File::GetFileSize(filename); + + std::string range; + if (request.Method() == http::RequestHeader::HEAD) { + request.WriteHttpResponseHeader(200, sz, "application/octet-stream", "Accept-Ranges: bytes\r\n"); + } else if (request.GetHeader("range", &range)) { + s64 begin = 0, last = 0; + if (sscanf(range.c_str(), "bytes=%lld-%lld", &begin, &last) != 2) { + request.WriteHttpResponseHeader(400, -1, "text/plain"); + request.Out()->Push("Could not understand range request."); + return; + } + + if (begin < 0 || begin > last || last >= sz) { + request.WriteHttpResponseHeader(416, -1, "text/plain"); + request.Out()->Push("Range goes outside of file."); + return; + } + + FILE *fp = File::OpenCFile(filename, "rb"); + if (!fp || fseek(fp, begin, SEEK_SET) != 0) { + request.WriteHttpResponseHeader(500, -1, "text/plain"); + request.Out()->Push("File access failed."); + if (fp) { + fclose(fp); + } + return; + } + + s64 len = last - begin + 1; + char contentRange[1024]; + sprintf(contentRange, "Content-Range: bytes %lld-%lld/%lld\r\n", begin, last, sz); + request.WriteHttpResponseHeader(206, len, "application/octet-stream", contentRange); + + const size_t CHUNK_SIZE = 16 * 1024; + char *buf = new char[CHUNK_SIZE]; + for (s64 pos = 0; pos < len; pos += CHUNK_SIZE) { + s64 chunklen = std::min(len - pos, (s64)CHUNK_SIZE); + if (fread(buf, chunklen, 1, fp) != 1) + break; + request.Out()->Push(buf, chunklen); + } + fclose(fp); + delete [] buf; + request.Out()->Flush(); + } else { + request.WriteHttpResponseHeader(418, -1, "text/plain"); + request.Out()->Push("This server only supports range requests."); + } + }; + + for (auto pair : *paths) { + http->RegisterHandler(pair.first.c_str(), handler); + } +} + +static void ExecuteWebServer() { + setCurrentThreadName("HTTPServer"); + + auto http = new http::Server(new threading::NewThreadExecutor()); + std::unordered_map discPaths; + + if (serverFlags & (int)WebServerFlags::DISCS) { + RegisterDiscHandlers(http, &discPaths); + } + if (serverFlags & (int)WebServerFlags::DEBUGGER) { + http->RegisterHandler("/debugger", &HandleDebuggerRequest); + } + + if (!http->Listen(g_Config.iRemoteISOPort)) { + if (!http->Listen(0)) { + ERROR_LOG(FILESYS, "Unable to listen on any port"); + UpdateStatus(ServerStatus::STOPPED); + return; + } + } + UpdateStatus(ServerStatus::RUNNING); + + g_Config.iRemoteISOPort = http->Port(); + RegisterServer(http->Port()); + double lastRegister = real_time_now(); + while (RetrieveStatus() == ServerStatus::RUNNING) { + http->RunSlice(1.0); + + double now = real_time_now(); + if (now > lastRegister + 540.0) { + RegisterServer(http->Port()); + lastRegister = now; + } + } + + http->Stop(); + StopAllDebuggers(); + delete http; + + // Move to STARTING to lock flags/STOPPING. + if (UpdateStatus(ServerStatus::STARTING, ServerStatus::RESTARTING)) { + ExecuteWebServer(); + } else { + UpdateStatus(ServerStatus::STOPPED); + } +} + +bool StartWebServer(WebServerFlags flags) { + std::lock_guard guard(serverStatusLock); + switch (serverStatus) { + case ServerStatus::RUNNING: + case ServerStatus::RESTARTING: + if ((serverFlags & (int)flags) == (int)flags) { + // Already running those flags. + return false; + } + serverStatus = ServerStatus::RESTARTING; + serverFlags |= (int)flags; + return true; + + case ServerStatus::STOPPED: + serverStatus = ServerStatus::STARTING; + serverFlags = (int)flags; + serverThread = std::thread(&ExecuteWebServer); + serverThread.detach(); + return true; + + default: + return false; + } +} + +bool StopWebServer(WebServerFlags flags) { + std::lock_guard guard(serverStatusLock); + if (serverStatus != ServerStatus::RUNNING && serverStatus != ServerStatus::RESTARTING) { + return false; + } + + serverFlags &= ~(int)flags; + if (serverFlags == 0) { + serverStatus = ServerStatus::STOPPING; + } else { + serverStatus = ServerStatus::RESTARTING; + } + return true; +} + +bool WebServerStopping(WebServerFlags flags) { + std::lock_guard guard(serverStatusLock); + if (serverStatus == ServerStatus::RESTARTING) { + return (serverFlags & (int)flags) == 0; + } + return serverStatus == ServerStatus::STOPPING; +} + +bool WebServerStopped(WebServerFlags flags) { + std::lock_guard guard(serverStatusLock); + if (serverStatus == ServerStatus::RUNNING) { + return (serverFlags & (int)flags) == 0; + } + return serverStatus == ServerStatus::STOPPED; +} diff --git a/Core/WebServer.h b/Core/WebServer.h new file mode 100644 index 0000000000..07c2efb4f0 --- /dev/null +++ b/Core/WebServer.h @@ -0,0 +1,28 @@ +// Copyright (c) 2014- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +enum class WebServerFlags { + DISCS = 1, + DEBUGGER = 2, + + ALL = 1 | 2, +}; + +bool StartWebServer(WebServerFlags flags); +bool StopWebServer(WebServerFlags flags); +bool WebServerStopping(WebServerFlags flags); +bool WebServerStopped(WebServerFlags flags); diff --git a/GPU/Common/DepalettizeShaderCommon.cpp b/GPU/Common/DepalettizeShaderCommon.cpp index 0ff61cc995..e691f25459 100644 --- a/GPU/Common/DepalettizeShaderCommon.cpp +++ b/GPU/Common/DepalettizeShaderCommon.cpp @@ -36,9 +36,9 @@ void GenerateDepalShader300(char *buffer, GEBufferFormat pixelFormat, ShaderLang if (language == HLSL_D3D11) { WRITE(p, "SamplerState texSamp : register(s0);\n"); WRITE(p, "Texture2D tex : register(t0);\n"); - WRITE(p, "Texture2D pal : register(t1);\n"); + WRITE(p, "Texture2D pal : register(t3);\n"); } else if (language == GLSL_VULKAN) { - WRITE(p, "#version 140\n"); + WRITE(p, "#version 450\n"); WRITE(p, "#extension GL_ARB_separate_shader_objects : enable\n"); WRITE(p, "#extension GL_ARB_shading_language_420pack : enable\n"); WRITE(p, "layout(set = 0, binding = 0) uniform sampler2D tex;\n"); @@ -49,6 +49,7 @@ void GenerateDepalShader300(char *buffer, GEBufferFormat pixelFormat, ShaderLang if (gl_extensions.IsGLES) { WRITE(p, "#version 300 es\n"); WRITE(p, "precision mediump float;\n"); + WRITE(p, "precision highp int;\n"); } else { WRITE(p, "#version 330\n"); } @@ -113,7 +114,7 @@ void GenerateDepalShader300(char *buffer, GEBufferFormat pixelFormat, ShaderLang texturePixels = 512; if (shift) { - WRITE(p, " index = (int(uint(index) >> %i) & 0x%02x)", shift, mask); + WRITE(p, " index = (int(uint(index) >> uint(%i)) & 0x%02x)", shift, mask); } else { WRITE(p, " index = (index & 0x%02x)", mask); } @@ -285,6 +286,9 @@ void GenerateDepalShader(char *buffer, GEBufferFormat pixelFormat, ShaderLanguag case HLSL_DX9: GenerateDepalShaderFloat(buffer, pixelFormat, language); break; + case HLSL_D3D11_LEVEL9: + default: + _assert_msg_(G3D, false, "Depal shader language not supported: %d", (int)language); } } diff --git a/GPU/Common/DrawEngineCommon.cpp b/GPU/Common/DrawEngineCommon.cpp index cf3553770d..95d6d5ad2e 100644 --- a/GPU/Common/DrawEngineCommon.cpp +++ b/GPU/Common/DrawEngineCommon.cpp @@ -520,7 +520,12 @@ void DrawEngineCommon::DecodeVertsStep(u8 *dest, int &i, int &decodedVerts) { dec_->DecodeVerts(dest + decodedVerts * (int)dec_->GetDecVtxFmt().stride, dc.verts, indexLowerBound, indexUpperBound); decodedVerts += indexUpperBound - indexLowerBound + 1; - indexGen.AddPrim(dc.prim, dc.vertexCount); + + bool clockwise = true; + if (dc.cullMode != -1 && gstate.isCullEnabled() && gstate.getCullMode() != dc.cullMode) { + clockwise = false; + } + indexGen.AddPrim(dc.prim, dc.vertexCount, clockwise); } else { // It's fairly common that games issue long sequences of PRIM calls, with differing // inds pointer but the same base vertex pointer. We'd like to reuse vertices between diff --git a/GPU/Common/DrawEngineCommon.h b/GPU/Common/DrawEngineCommon.h index 7e7f09443b..03e9e50cf3 100644 --- a/GPU/Common/DrawEngineCommon.h +++ b/GPU/Common/DrawEngineCommon.h @@ -137,9 +137,9 @@ protected: struct DeferredDrawCall { void *verts; void *inds; + u32 vertexCount; u8 indexType; s8 prim; - u16 vertexCount; u16 indexLowerBound; u16 indexUpperBound; UVScale uvScale; diff --git a/GPU/Common/FramebufferCommon.cpp b/GPU/Common/FramebufferCommon.cpp index 12860322ff..be32edfb6d 100644 --- a/GPU/Common/FramebufferCommon.cpp +++ b/GPU/Common/FramebufferCommon.cpp @@ -28,6 +28,7 @@ #include "Common/ColorConv.h" #include "Common/Common.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/CoreParameter.h" #include "Core/Host.h" #include "Core/Reporting.h" @@ -47,11 +48,11 @@ void CenterDisplayOutputRect(float *x, float *y, float *w, float *h, float origW bool rotated = rotation == ROTATION_LOCKED_VERTICAL || rotation == ROTATION_LOCKED_VERTICAL180; - if (g_Config.iSmallDisplayZoomType == 0) { // Stretching + if (g_Config.iSmallDisplayZoomType == (int)SmallDisplayZoom::STRETCH) { outW = frameW; outH = frameH; } else { - if (g_Config.iSmallDisplayZoomType == 3) { // Manual Scaling + if (g_Config.iSmallDisplayZoomType == (int)SmallDisplayZoom::MANUAL) { float offsetX = (g_Config.fSmallDisplayOffsetX - 0.5f) * 2.0f * frameW; float offsetY = (g_Config.fSmallDisplayOffsetY - 0.5f) * 2.0f * frameH; // Have to invert Y for GL @@ -74,7 +75,7 @@ void CenterDisplayOutputRect(float *x, float *y, float *w, float *h, float origW *h = floorf(smallDisplayW); return; } - } else if (g_Config.iSmallDisplayZoomType == 2) { // Auto Scaling + } else if (g_Config.iSmallDisplayZoomType == (int)SmallDisplayZoom::AUTO) { // Stretch to 1080 for 272*4. But don't distort if not widescreen (i.e. ultrawide of halfwide.) float pixelCrop = frameH / 270.0f; float resCommonWidescreen = pixelCrop - floor(pixelCrop); @@ -95,13 +96,13 @@ void CenterDisplayOutputRect(float *x, float *y, float *w, float *h, float origW outW = frameW; outH = frameW / origRatio; // Stretch a little bit - if (!rotated && g_Config.iSmallDisplayZoomType == 1) // Partial Stretch + if (!rotated && g_Config.iSmallDisplayZoomType == (int)SmallDisplayZoom::PARTIAL_STRETCH) outH = (frameH + outH) / 2.0f; // (408 + 720) / 2 = 564 } else { // Image is taller than frame. Center horizontally. outW = frameH * origRatio; outH = frameH; - if (rotated && g_Config.iSmallDisplayZoomType == 1) // Partial Stretch + if (rotated && g_Config.iSmallDisplayZoomType == (int)SmallDisplayZoom::PARTIAL_STRETCH) outW = (frameH + outH) / 2.0f; // (408 + 720) / 2 = 564 } } @@ -1102,7 +1103,7 @@ void FramebufferManagerCommon::DecimateFBOs() { int age = frameLastFramebufUsed_ - it->second.last_frame_used; if (age > FBO_OLD_AGE) { it->second.fbo->Release(); - tempFBOs_.erase(it++); + it = tempFBOs_.erase(it); } else { ++it; } @@ -1834,8 +1835,8 @@ void FramebufferManagerCommon::GetCardboardSettings(CardboardSettings *cardboard cardboardSettings->screenHeight = cardboardScreenHeight; } -Draw::Framebuffer *FramebufferManagerCommon::GetTempFBO(u16 w, u16 h, Draw::FBColorDepth depth) { - u64 key = ((u64)depth << 32) | ((u32)w << 16) | h; +Draw::Framebuffer *FramebufferManagerCommon::GetTempFBO(TempFBO reason, u16 w, u16 h, Draw::FBColorDepth depth) { + u64 key = ((u64)reason << 48) | ((u64)depth << 32) | ((u32)w << 16) | h; auto it = tempFBOs_.find(key); if (it != tempFBOs_.end()) { it->second.last_frame_used = gpuStats.numFlips; @@ -1847,7 +1848,7 @@ Draw::Framebuffer *FramebufferManagerCommon::GetTempFBO(u16 w, u16 h, Draw::FBCo if (!fbo) return fbo; - const TempFBO info = { fbo, gpuStats.numFlips }; + const TempFBOInfo info = { fbo, gpuStats.numFlips }; tempFBOs_[key] = info; return fbo; } @@ -1916,7 +1917,7 @@ bool FramebufferManagerCommon::GetFramebuffer(u32 fb_address, int fb_stride, GEB w = vfb->width * maxRes; h = vfb->height * maxRes; - Draw::Framebuffer *tempFBO = GetTempFBO(w, h); + Draw::Framebuffer *tempFBO = GetTempFBO(TempFBO::COPY, w, h); VirtualFramebuffer tempVfb = *vfb; tempVfb.fbo = tempFBO; tempVfb.bufferWidth = vfb->width; @@ -1939,7 +1940,7 @@ bool FramebufferManagerCommon::GetFramebuffer(u32 fb_address, int fb_stride, GEB // TODO: Maybe should handle flipY inside CopyFramebufferToMemorySync somehow? bool flipY = (GetGPUBackend() == GPUBackend::OPENGL && !useBufferedRendering_) ? true : false; - buffer.Allocate(w, h, GE_FORMAT_8888, flipY, true); + buffer.Allocate(w, h, GE_FORMAT_8888, flipY); bool retval = draw_->CopyFramebufferToMemorySync(bound, Draw::FB_COLOR_BIT, 0, 0, w, h, Draw::DataFormat::R8G8B8A8_UNORM, buffer.GetData(), w); gpuStats.numReadbacks++; // After a readback we'll have flushed and started over, need to dirty a bunch of things to be safe. @@ -2017,8 +2018,12 @@ bool FramebufferManagerCommon::GetStencilbuffer(u32 fb_address, int fb_stride, G bool FramebufferManagerCommon::GetOutputFramebuffer(GPUDebugBuffer &buffer) { int w, h; draw_->GetFramebufferDimensions(nullptr, &w, &h); - buffer.Allocate(w, h, GE_FORMAT_8888, false, true); - bool retval = draw_->CopyFramebufferToMemorySync(nullptr, Draw::FB_COLOR_BIT, 0, 0, w, h, Draw::DataFormat::R8G8B8A8_UNORM, buffer.GetData(), w); + Draw::DataFormat fmt = draw_->PreferredFramebufferReadbackFormat(nullptr); + // Ignore preferred formats other than BGRA. + if (fmt != Draw::DataFormat::B8G8R8A8_UNORM) + fmt = Draw::DataFormat::R8G8B8A8_UNORM; + buffer.Allocate(w, h, fmt == Draw::DataFormat::R8G8B8A8_UNORM ? GPU_DBG_FORMAT_8888 : GPU_DBG_FORMAT_8888_BGRA, false); + bool retval = draw_->CopyFramebufferToMemorySync(nullptr, Draw::FB_COLOR_BIT, 0, 0, w, h, fmt, buffer.GetData(), w); // That may have unbound the framebuffer, rebind to avoid crashes when debugging. RebindFramebuffer(); return retval; diff --git a/GPU/Common/FramebufferCommon.h b/GPU/Common/FramebufferCommon.h index f96174c0c3..4f19debd01 100644 --- a/GPU/Common/FramebufferCommon.h +++ b/GPU/Common/FramebufferCommon.h @@ -19,7 +19,7 @@ #include #include -#include +#include #include "Common/CommonTypes.h" #include "Core/MemMap.h" @@ -86,10 +86,13 @@ struct VirtualFramebuffer { // width/height: The detected size of the current framebuffer. u16 width; u16 height; - // renderWidth/renderHeight: The actual size we render at. May be scaled to render at higher resolutions. + // renderWidth/renderHeight: The scaled size we render at. May be scaled to render at higher resolutions. + // The physical buffer may be larger than renderWidth/renderHeight. u16 renderWidth; u16 renderHeight; - // bufferWidth/bufferHeight: The actual (but non scaled) size of the buffer we render to. May only be bigger than width/height. + // bufferWidth/bufferHeight: The pre-scaling size of the buffer itself. May only be bigger than width/height. + // Actual physical buffer is this size times the render resolution multiplier. + // The buffer may be used to render a width or height from 0 to these values without being recreated. u16 bufferWidth; u16 bufferHeight; @@ -153,6 +156,13 @@ enum DrawTextureFlags { DRAWTEX_KEEP_STENCIL_ALPHA = 4, }; +enum class TempFBO { + DEPAL, + BLIT, + // For copies of framebuffers (e.g. shader blending.) + COPY, +}; + inline Draw::DataFormat GEFormatToThin3D(int geFormat) { switch (geFormat) { case GE_FORMAT_4444: @@ -290,7 +300,7 @@ public: virtual void Resized(); - Draw::Framebuffer *GetTempFBO(u16 w, u16 h, Draw::FBColorDepth colorDepth = Draw::FBO_8888); + Draw::Framebuffer *GetTempFBO(TempFBO reason, u16 w, u16 h, Draw::FBColorDepth colorDepth = Draw::FBO_8888); // Debug features virtual bool GetFramebuffer(u32 fb_address, int fb_stride, GEBufferFormat format, GPUDebugBuffer &buffer, int maxRes); @@ -400,12 +410,12 @@ protected: bool needGLESRebinds_ = false; - struct TempFBO { + struct TempFBOInfo { Draw::Framebuffer *fbo; int last_frame_used; }; - std::map tempFBOs_; + std::unordered_map tempFBOs_; std::vector fbosToDelete_; diff --git a/GPU/Common/GPUStateUtils.cpp b/GPU/Common/GPUStateUtils.cpp index 586101a611..cd337f3bad 100644 --- a/GPU/Common/GPUStateUtils.cpp +++ b/GPU/Common/GPUStateUtils.cpp @@ -22,6 +22,7 @@ #include "Common/StringUtils.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/System.h" #include "GPU/ge_constants.h" diff --git a/GPU/Common/IndexGenerator.cpp b/GPU/Common/IndexGenerator.cpp index f8de86c905..9c1b13d42d 100644 --- a/GPU/Common/IndexGenerator.cpp +++ b/GPU/Common/IndexGenerator.cpp @@ -36,14 +36,14 @@ void IndexGenerator::Setup(u16 *inds) { Reset(); } -void IndexGenerator::AddPrim(int prim, int vertexCount) { +void IndexGenerator::AddPrim(int prim, int vertexCount, bool clockwise) { switch (prim) { case GE_PRIM_POINTS: AddPoints(vertexCount); break; case GE_PRIM_LINES: AddLineList(vertexCount); break; case GE_PRIM_LINE_STRIP: AddLineStrip(vertexCount); break; - case GE_PRIM_TRIANGLES: AddList(vertexCount); break; - case GE_PRIM_TRIANGLE_STRIP: AddStrip(vertexCount); break; - case GE_PRIM_TRIANGLE_FAN: AddFan(vertexCount); break; + case GE_PRIM_TRIANGLES: AddList(vertexCount, clockwise); break; + case GE_PRIM_TRIANGLE_STRIP: AddStrip(vertexCount, clockwise); break; + case GE_PRIM_TRIANGLE_FAN: AddFan(vertexCount, clockwise); break; case GE_PRIM_RECTANGLES: AddRectangles(vertexCount); break; // Same } } @@ -61,13 +61,15 @@ void IndexGenerator::AddPoints(int numVerts) { seenPrims_ |= 1 << GE_PRIM_POINTS; } -void IndexGenerator::AddList(int numVerts) { +void IndexGenerator::AddList(int numVerts, bool clockwise) { u16 *outInds = inds_; const int startIndex = index_; + const int v1 = clockwise ? 1 : 2; + const int v2 = clockwise ? 2 : 1; for (int i = 0; i < numVerts; i += 3) { *outInds++ = startIndex + i; - *outInds++ = startIndex + i + 1; - *outInds++ = startIndex + i + 2; + *outInds++ = startIndex + i + v1; + *outInds++ = startIndex + i + v2; } inds_ = outInds; // ignore overflow verts @@ -77,8 +79,8 @@ void IndexGenerator::AddList(int numVerts) { seenPrims_ |= 1 << GE_PRIM_TRIANGLES; } -void IndexGenerator::AddStrip(int numVerts) { - int wind = 1; +void IndexGenerator::AddStrip(int numVerts, bool clockwise) { + int wind = clockwise ? 1 : 2; const int numTris = numVerts - 2; u16 *outInds = inds_; int ibase = index_; @@ -105,14 +107,16 @@ void IndexGenerator::AddStrip(int numVerts) { } } -void IndexGenerator::AddFan(int numVerts) { +void IndexGenerator::AddFan(int numVerts, bool clockwise) { const int numTris = numVerts - 2; u16 *outInds = inds_; const int startIndex = index_; + const int v1 = clockwise ? 1 : 2; + const int v2 = clockwise ? 2 : 1; for (int i = 0; i < numTris; i++) { *outInds++ = startIndex; - *outInds++ = startIndex + i + 1; - *outInds++ = startIndex + i + 2; + *outInds++ = startIndex + i + v1; + *outInds++ = startIndex + i + v2; } inds_ = outInds; index_ += numVerts; @@ -210,7 +214,7 @@ void IndexGenerator::TranslateLineStrip(int numInds, const ITypeLE *inds, int in } template -void IndexGenerator::TranslateList(int numInds, const ITypeLE *inds, int indexOffset) { +void IndexGenerator::TranslateList(int numInds, const ITypeLE *inds, int indexOffset, bool clockwise) { indexOffset = index_ - indexOffset; // We only bother doing this minor optimization in triangle list, since it's by far the most // common operation that can benefit. @@ -222,10 +226,12 @@ void IndexGenerator::TranslateList(int numInds, const ITypeLE *inds, int indexOf u16 *outInds = inds_; int numTris = numInds / 3; // Round to whole triangles numInds = numTris * 3; + const int v1 = clockwise ? 1 : 2; + const int v2 = clockwise ? 2 : 1; for (int i = 0; i < numInds; i += 3) { *outInds++ = indexOffset + inds[i]; - *outInds++ = indexOffset + inds[i + 1]; - *outInds++ = indexOffset + inds[i + 2]; + *outInds++ = indexOffset + inds[i + v1]; + *outInds++ = indexOffset + inds[i + v2]; } inds_ = outInds; count_ += numInds; @@ -253,15 +259,17 @@ void IndexGenerator::TranslateStrip(int numInds, const ITypeLE *inds, int indexO } template -void IndexGenerator::TranslateFan(int numInds, const ITypeLE *inds, int indexOffset) { +void IndexGenerator::TranslateFan(int numInds, const ITypeLE *inds, int indexOffset, bool clockwise) { if (numInds <= 0) return; indexOffset = index_ - indexOffset; int numTris = numInds - 2; u16 *outInds = inds_; + const int v1 = clockwise ? 1 : 2; + const int v2 = clockwise ? 2 : 1; for (int i = 0; i < numTris; i++) { *outInds++ = indexOffset + inds[0]; - *outInds++ = indexOffset + inds[i + 1]; - *outInds++ = indexOffset + inds[i + 2]; + *outInds++ = indexOffset + inds[i + v1]; + *outInds++ = indexOffset + inds[i + v2]; } inds_ = outInds; count_ += numTris * 3; @@ -291,9 +299,9 @@ void IndexGenerator::TranslatePrim(int prim, int numInds, const u8 *inds, int in case GE_PRIM_POINTS: TranslatePoints(numInds, inds, indexOffset); break; case GE_PRIM_LINES: TranslateLineList(numInds, inds, indexOffset); break; case GE_PRIM_LINE_STRIP: TranslateLineStrip(numInds, inds, indexOffset); break; - case GE_PRIM_TRIANGLES: TranslateList(numInds, inds, indexOffset); break; + case GE_PRIM_TRIANGLES: TranslateList(numInds, inds, indexOffset, clockwise); break; case GE_PRIM_TRIANGLE_STRIP: TranslateStrip(numInds, inds, indexOffset, clockwise); break; - case GE_PRIM_TRIANGLE_FAN: TranslateFan(numInds, inds, indexOffset); break; + case GE_PRIM_TRIANGLE_FAN: TranslateFan(numInds, inds, indexOffset, clockwise); break; case GE_PRIM_RECTANGLES: TranslateRectangles(numInds, inds, indexOffset); break; // Same } } @@ -303,9 +311,9 @@ void IndexGenerator::TranslatePrim(int prim, int numInds, const u16_le *inds, in case GE_PRIM_POINTS: TranslatePoints(numInds, inds, indexOffset); break; case GE_PRIM_LINES: TranslateLineList(numInds, inds, indexOffset); break; case GE_PRIM_LINE_STRIP: TranslateLineStrip(numInds, inds, indexOffset); break; - case GE_PRIM_TRIANGLES: TranslateList(numInds, inds, indexOffset); break; + case GE_PRIM_TRIANGLES: TranslateList(numInds, inds, indexOffset, clockwise); break; case GE_PRIM_TRIANGLE_STRIP: TranslateStrip(numInds, inds, indexOffset, clockwise); break; - case GE_PRIM_TRIANGLE_FAN: TranslateFan(numInds, inds, indexOffset); break; + case GE_PRIM_TRIANGLE_FAN: TranslateFan(numInds, inds, indexOffset, clockwise); break; case GE_PRIM_RECTANGLES: TranslateRectangles(numInds, inds, indexOffset); break; // Same } } @@ -315,9 +323,9 @@ void IndexGenerator::TranslatePrim(int prim, int numInds, const u32_le *inds, in case GE_PRIM_POINTS: TranslatePoints(numInds, inds, indexOffset); break; case GE_PRIM_LINES: TranslateLineList(numInds, inds, indexOffset); break; case GE_PRIM_LINE_STRIP: TranslateLineStrip(numInds, inds, indexOffset); break; - case GE_PRIM_TRIANGLES: TranslateList(numInds, inds, indexOffset); break; + case GE_PRIM_TRIANGLES: TranslateList(numInds, inds, indexOffset, clockwise); break; case GE_PRIM_TRIANGLE_STRIP: TranslateStrip(numInds, inds, indexOffset, clockwise); break; - case GE_PRIM_TRIANGLE_FAN: TranslateFan(numInds, inds, indexOffset); break; + case GE_PRIM_TRIANGLE_FAN: TranslateFan(numInds, inds, indexOffset, clockwise); break; case GE_PRIM_RECTANGLES: TranslateRectangles(numInds, inds, indexOffset); break; // Same } } diff --git a/GPU/Common/IndexGenerator.h b/GPU/Common/IndexGenerator.h index ab33ff4f51..6fb24930ee 100644 --- a/GPU/Common/IndexGenerator.h +++ b/GPU/Common/IndexGenerator.h @@ -49,7 +49,7 @@ public: GEPrimitiveType Prim() const { return prim_; } - void AddPrim(int prim, int vertexCount); + void AddPrim(int prim, int vertexCount, bool clockwise = true); void TranslatePrim(int prim, int numInds, const u8 *inds, int indexOffset, bool clockwise); void TranslatePrim(int prim, int numInds, const u16_le *inds, int indexOffset, bool clockwise); void TranslatePrim(int prim, int numInds, const u32_le *inds, int indexOffset, bool clockwise); @@ -75,9 +75,9 @@ private: // Points (why index these? code simplicity) void AddPoints(int numVerts); // Triangles - void AddList(int numVerts); - void AddStrip(int numVerts); - void AddFan(int numVerts); + void AddList(int numVerts, bool clockwise); + void AddStrip(int numVerts, bool clockwise); + void AddFan(int numVerts, bool clockwise); // Lines void AddLineList(int numVerts); void AddLineStrip(int numVerts); @@ -88,7 +88,7 @@ private: template void TranslatePoints(int numVerts, const ITypeLE *inds, int indexOffset); template - void TranslateList(int numVerts, const ITypeLE *inds, int indexOffset); + void TranslateList(int numVerts, const ITypeLE *inds, int indexOffset, bool clockwise); template inline void TranslateLineList(int numVerts, const ITypeLE *inds, int indexOffset); template @@ -97,7 +97,7 @@ private: template void TranslateStrip(int numVerts, const ITypeLE *inds, int indexOffset, bool clockwise); template - void TranslateFan(int numVerts, const ITypeLE *inds, int indexOffset); + void TranslateFan(int numVerts, const ITypeLE *inds, int indexOffset, bool clockwise); template inline void TranslateRectangles(int numVerts, const ITypeLE *inds, int indexOffset); diff --git a/GPU/Common/ShaderTranslation.cpp b/GPU/Common/ShaderTranslation.cpp index 4bc34908be..2614feb453 100644 --- a/GPU/Common/ShaderTranslation.cpp +++ b/GPU/Common/ShaderTranslation.cpp @@ -310,11 +310,7 @@ bool TranslateShader(std::string *dest, ShaderLanguage destLang, TranslatedShade spirv_cross::ShaderResources resources = glsl.get_shader_resources(); // Set some options. spirv_cross::CompilerGLSL::Options options; - if (gl_extensions.ver[0] >= 4) { - options.version = 400; - } else { - options.version = 300; - } + options.version = gl_extensions.GLSLVersion(); glsl.set_options(options); // Compile to GLSL, ready to give to GL driver. *dest = glsl.compile(); diff --git a/GPU/Common/ShaderUniforms.cpp b/GPU/Common/ShaderUniforms.cpp index 95b382ce67..d607202b3e 100644 --- a/GPU/Common/ShaderUniforms.cpp +++ b/GPU/Common/ShaderUniforms.cpp @@ -7,6 +7,7 @@ #include "math/math_util.h" #include "math/lin/vec3.h" #include "GPU/GPUState.h" +#include "GPU/Common/FramebufferCommon.h" #include "GPU/Common/GPUStateUtils.h" #include "GPU/Math3D.h" #include "Core/Reporting.h" @@ -105,7 +106,7 @@ void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool flipView ConvertProjMatrixToVulkan(flippedMatrix); } - if (g_Config.iRenderingMode == 0 && g_display_rotation != DisplayRotation::ROTATE_0) { + if (g_Config.iRenderingMode == FB_NON_BUFFERED_MODE && g_display_rotation != DisplayRotation::ROTATE_0) { flippedMatrix = flippedMatrix * g_display_rot_matrix; } @@ -119,7 +120,7 @@ void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool flipView } else { proj_through.setOrthoVulkan(0.0f, gstate_c.curRTWidth, 0, gstate_c.curRTHeight, 0, 1); } - if (g_Config.iRenderingMode == 0 && g_display_rotation != DisplayRotation::ROTATE_0) { + if (g_Config.iRenderingMode == FB_NON_BUFFERED_MODE && g_display_rotation != DisplayRotation::ROTATE_0) { proj_through = proj_through * g_display_rot_matrix; } // proj_through.translateAndScale(Vec3(0, 0, 0), Vec3(1.0f / debugscale, 1.0f / debugscale, 0)); diff --git a/GPU/Common/SoftwareTransformCommon.cpp b/GPU/Common/SoftwareTransformCommon.cpp index a29c987c11..93385d8aa3 100644 --- a/GPU/Common/SoftwareTransformCommon.cpp +++ b/GPU/Common/SoftwareTransformCommon.cpp @@ -125,6 +125,33 @@ static bool IsReallyAClear(const TransformedVertex *transformed, int numVerts, f return true; } +static int ColorIndexOffset(int prim, GEShadeMode shadeMode, bool clearMode) { + if (shadeMode != GE_SHADE_FLAT || clearMode) { + return 0; + } + + switch (prim) { + case GE_PRIM_LINES: + case GE_PRIM_LINE_STRIP: + return 1; + + case GE_PRIM_TRIANGLES: + case GE_PRIM_TRIANGLE_STRIP: + return 2; + + case GE_PRIM_TRIANGLE_FAN: + return 1; + + case GE_PRIM_RECTANGLES: + // We already use BR color when expanding, so no need to offset. + return 0; + + default: + break; + } + return 0; +} + void SoftwareTransform( int prim, int vertexCount, u32 vertType, u16 *&inds, int indexType, const DecVtxFormat &decVtxFormat, int &maxIndex, TransformedVertex *&drawBuffer, int &numTrans, bool &drawIndexed, const SoftwareTransformParams *params, SoftwareTransformResult *result) { @@ -168,6 +195,11 @@ void SoftwareTransform( fog_slope = 1.0f; } + int colorIndOffset = 0; + if (params->provokeFlatFirst) { + colorIndOffset = ColorIndexOffset(prim, gstate.getShadeMode(), gstate.isModeClear()); + } + VertexReader reader(decoded, decVtxFormat, vertType); if (throughmode) { for (int index = 0; index < maxIndex; index++) { @@ -178,7 +210,13 @@ void SoftwareTransform( reader.ReadPos(vert.pos); if (reader.hasColor0()) { - reader.ReadColor0_8888(vert.color0); + if (colorIndOffset != 0 && index + colorIndOffset < maxIndex) { + reader.Goto(index + colorIndOffset); + reader.ReadColor0_8888(vert.color0); + reader.Goto(index); + } else { + reader.ReadColor0_8888(vert.color0); + } } else { vert.color0_32 = gstate.getMaterialAmbientRGBA(); } @@ -260,7 +298,13 @@ void SoftwareTransform( // Perform lighting here if enabled. don't need to check through, it's checked above. Vec4f unlitColor = Vec4f(1, 1, 1, 1); if (reader.hasColor0()) { - reader.ReadColor0(&unlitColor.x); + if (colorIndOffset != 0 && index + colorIndOffset < maxIndex) { + reader.Goto(index + colorIndOffset); + reader.ReadColor0(&unlitColor.x); + reader.Goto(index); + } else { + reader.ReadColor0(&unlitColor.x); + } } else { unlitColor = Vec4f::FromRGBA(gstate.getMaterialAmbientRGBA()); } diff --git a/GPU/Common/SoftwareTransformCommon.h b/GPU/Common/SoftwareTransformCommon.h index 9bc2e4da49..a7efc396c9 100644 --- a/GPU/Common/SoftwareTransformCommon.h +++ b/GPU/Common/SoftwareTransformCommon.h @@ -46,6 +46,7 @@ struct SoftwareTransformParams { TextureCacheCommon *texCache; bool allowClear; bool allowSeparateAlphaClear; + bool provokeFlatFirst; }; void SoftwareTransform(int prim, int vertexCount, u32 vertexType, u16 *&inds, int indexType, const DecVtxFormat &decVtxFormat, int &maxIndex, TransformedVertex *&drawBuffer, diff --git a/GPU/Common/SplineCommon.cpp b/GPU/Common/SplineCommon.cpp index 41eb4aec93..7af7926dde 100644 --- a/GPU/Common/SplineCommon.cpp +++ b/GPU/Common/SplineCommon.cpp @@ -24,6 +24,7 @@ #include "Common/MemoryUtil.h" #include "Core/Config.h" +#include "GPU/Common/GPUStateUtils.h" #include "GPU/Common/SplineCommon.h" #include "GPU/Common/DrawEngineCommon.h" #include "GPU/ge_constants.h" @@ -79,7 +80,7 @@ inline __m128 SSENormalizeMultiplier(bool useSSE4, __m128 v) -static void CopyQuad(u8 *&dest, const SimpleVertex *v1, const SimpleVertex *v2, const SimpleVertex* v3, const SimpleVertex *v4) { +static void CopyQuad(u8 *&dest, const SimpleVertex *v1, const SimpleVertex *v2, const SimpleVertex *v3, const SimpleVertex *v4) { int vertexSize = sizeof(SimpleVertex); memcpy(dest, v1, vertexSize); dest += vertexSize; @@ -223,6 +224,13 @@ static void spline_knot(int n, int type, float *knot) { } } +bool CanUseHardwareTessellation(GEPatchPrimType prim) { + if (g_Config.bHardwareTessellation && !g_Config.bSoftwareRendering) { + return CanUseHardwareTransform(PatchPrimToPrim(prim)); + } + return false; +} + // Prepare mesh of one patch for "Instanced Tessellation". static void TessellateSplinePatchHardware(u8 *&dest, u16 *indices, int &count, const SplinePatchLocal &spatch) { SimpleVertex *&vertices = (SimpleVertex*&)dest; @@ -458,7 +466,7 @@ static void SplinePatchFullQuality(u8 *&dest, u16 *indices, int &count, const Sp OutputDebugStringA(temp); Crash(); }*/ - SimpleVertex *a = spatch.points[idx]; + const SimpleVertex *a = spatch.points[idx]; AccumulateWeighted(vert_pos, a->pos, fv); if (origTc) { vert->uv[0] += a->uv[0] * f; @@ -833,7 +841,7 @@ void TessellateBezierPatch(u8 *&dest, u16 *&indices, int &count, int tess_u, int _BezierPatchLowQuality(dest, indices, count, tess_u, tess_v, patch, origVertType); break; case MEDIUM_QUALITY: - _BezierPatchHighQuality(dest, indices, count, tess_u / 2, tess_v / 2, patch, origVertType); + _BezierPatchHighQuality(dest, indices, count, std::max(tess_u / 2, 1), std::max(tess_v / 2, 1), patch, origVertType); break; case HIGH_QUALITY: _BezierPatchHighQuality(dest, indices, count, tess_u, tess_v, patch, origVertType); @@ -841,9 +849,6 @@ void TessellateBezierPatch(u8 *&dest, u16 *&indices, int &count, int tess_u, int } } -// This maps GEPatchPrimType to GEPrimitiveType. -const GEPrimitiveType primType[] = { GE_PRIM_TRIANGLES, GE_PRIM_LINES, GE_PRIM_POINTS, GE_PRIM_POINTS }; - void DrawEngineCommon::SubmitSpline(const void *control_points, const void *indices, int tess_u, int tess_v, int count_u, int count_v, int type_u, int type_v, GEPatchPrimType prim_type, bool computeNormals, bool patchFacing, u32 vertType, int *bytesRead) { PROFILE_THIS_SCOPE("spline"); DispatchFlush(); @@ -877,7 +882,7 @@ void DrawEngineCommon::SubmitSpline(const void *control_points, const void *indi } // TODO: Do something less idiotic to manage this buffer - SimpleVertex **points = new SimpleVertex *[count_u * count_v]; + auto points = new const SimpleVertex *[count_u * count_v]; // Make an array of pointers to the control points, to get rid of indices. for (int idx = 0; idx < count_u * count_v; idx++) { @@ -900,8 +905,7 @@ void DrawEngineCommon::SubmitSpline(const void *control_points, const void *indi patch.primType = prim_type; patch.patchFacing = patchFacing; - if (g_Config.bHardwareTessellation && g_Config.bHardwareTransform && !g_Config.bSoftwareRendering) { - + if (CanUseHardwareTessellation(prim_type)) { float *pos = (float*)(decoded + 65536 * 18); // Size 4 float float *tex = pos + count_u * count_v * 4; // Size 4 float float *col = tex + count_u * count_v * 4; // Size 4 float @@ -952,7 +956,7 @@ void DrawEngineCommon::SubmitSpline(const void *control_points, const void *indi uint32_t vertTypeID = GetVertTypeID(vertTypeWithIndex16, gstate.getUVGenMode()); int generatedBytesRead; - DispatchSubmitPrim(splineBuffer, quadIndices_, primType[prim_type], count, vertTypeID, &generatedBytesRead); + DispatchSubmitPrim(splineBuffer, quadIndices_, PatchPrimToPrim(prim_type), count, vertTypeID, &generatedBytesRead); DispatchFlush(); @@ -1006,14 +1010,14 @@ void DrawEngineCommon::SubmitBezier(const void *control_points, const void *indi int num_patches_u = (count_u - 1) / 3; int num_patches_v = (count_v - 1) / 3; BezierPatch *patches = nullptr; - if (g_Config.bHardwareTessellation && g_Config.bHardwareTransform && !g_Config.bSoftwareRendering) { + if (CanUseHardwareTessellation(prim_type)) { int posStride, texStride, colStride; tessDataTransfer->PrepareBuffers(pos, tex, col, posStride, texStride, colStride, count_u * count_v, hasColor, hasTexCoords); float *p = pos; float *t = tex; float *c = col; for (int idx = 0; idx < count_u * count_v; idx++) { - SimpleVertex *point = simplified_control_points + (indices ? idxConv.convert(idx) : idx); + const SimpleVertex *point = simplified_control_points + (indices ? idxConv.convert(idx) : idx); memcpy(p, point->pos.AsArray(), 3 * sizeof(float)); p += posStride; if (hasTexCoords) { @@ -1026,7 +1030,7 @@ void DrawEngineCommon::SubmitBezier(const void *control_points, const void *indi } } if (!hasColor) { - SimpleVertex *point = simplified_control_points + (indices ? idxConv.convert(0) : 0); + const SimpleVertex *point = simplified_control_points + (indices ? idxConv.convert(0) : 0); memcpy(col, Vec4f::FromRGBA(point->color_32).AsArray(), 4 * sizeof(float)); } } else { @@ -1063,7 +1067,7 @@ void DrawEngineCommon::SubmitBezier(const void *control_points, const void *indi } u16 *inds = quadIndices_; - if (g_Config.bHardwareTessellation && g_Config.bHardwareTransform && !g_Config.bSoftwareRendering) { + if (CanUseHardwareTessellation(prim_type)) { tessDataTransfer->SendDataToShader(pos, tex, col, count_u * count_v, hasColor, hasTexCoords); TessellateBezierPatchHardware(dest, inds, count, tess_u, tess_v, prim_type); numPatches = num_patches_u * num_patches_v; @@ -1095,7 +1099,7 @@ void DrawEngineCommon::SubmitBezier(const void *control_points, const void *indi uint32_t vertTypeID = GetVertTypeID(vertTypeWithIndex16, gstate.getUVGenMode()); int generatedBytesRead; - DispatchSubmitPrim(splineBuffer, quadIndices_, primType[prim_type], count, vertTypeID, &generatedBytesRead); + DispatchSubmitPrim(splineBuffer, quadIndices_, PatchPrimToPrim(prim_type), count, vertTypeID, &generatedBytesRead); DispatchFlush(); diff --git a/GPU/Common/SplineCommon.h b/GPU/Common/SplineCommon.h index e292c2b937..52bf75b69b 100644 --- a/GPU/Common/SplineCommon.h +++ b/GPU/Common/SplineCommon.h @@ -36,7 +36,7 @@ struct SimpleVertex { // We decode all vertices into a common format for easy interpolation and stuff. // Not fast but can be optimized later. struct BezierPatch { - SimpleVertex *points[16]; + const SimpleVertex *points[16]; // These are used to generate UVs. int u_index, v_index; @@ -48,7 +48,7 @@ struct BezierPatch { }; struct SplinePatchLocal { - SimpleVertex **points; + const SimpleVertex **points; int tess_u; int tess_v; int count_u; @@ -66,5 +66,6 @@ enum SplineQuality { HIGH_QUALITY = 2, }; +bool CanUseHardwareTessellation(GEPatchPrimType prim); void TessellateSplinePatch(u8 *&dest, u16 *indices, int &count, const SplinePatchLocal &spatch, u32 origVertType, int maxVertices); -void TessellateBezierPatch(u8 *&dest, u16 *&indices, int &count, int tess_u, int tess_v, const BezierPatch &patch, u32 origVertType, int maxVertices); +void TessellateBezierPatch(u8 *&dest, u16 *&indices, int &count, int tess_u, int tess_v, const BezierPatch &patch, u32 origVertType); diff --git a/GPU/Common/VertexDecoderCommon.cpp b/GPU/Common/VertexDecoderCommon.cpp index 7d40fe5293..0b8ee6736c 100644 --- a/GPU/Common/VertexDecoderCommon.cpp +++ b/GPU/Common/VertexDecoderCommon.cpp @@ -24,6 +24,7 @@ #include "Common/CPUDetect.h" #include "Common/ColorConv.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/MemMap.h" #include "Core/HDRemaster.h" #include "Core/Reporting.h" diff --git a/GPU/D3D11/DrawEngineD3D11.cpp b/GPU/D3D11/DrawEngineD3D11.cpp index 2b49ae2d6e..fb7b001b7b 100644 --- a/GPU/D3D11/DrawEngineD3D11.cpp +++ b/GPU/D3D11/DrawEngineD3D11.cpp @@ -325,13 +325,15 @@ void DrawEngineD3D11::DoFlush() { gpuStats.numFlushes++; gpuStats.numTrackedVertexArrays = (int)vai_.size(); - // This is not done on every drawcall, we should collect vertex data + // This is not done on every drawcall, we collect vertex data // until critical state changes. That's when we draw (flush). GEPrimitiveType prim = prevPrim_; ApplyDrawState(prim); - bool useHWTransform = CanUseHardwareTransform(prim); + // Always use software for flat shading to fix the provoking index. + bool tess = gstate_c.bezier || gstate_c.spline; + bool useHWTransform = CanUseHardwareTransform(prim) && (tess || gstate.getShadeMode() != GE_SHADE_FLAT); if (useHWTransform) { ID3D11Buffer *vb_ = nullptr; @@ -538,7 +540,7 @@ rotateVBO: memcpy(iptr, decIndex, iSize); pushInds_->EndPush(context_); context_->IASetIndexBuffer(pushInds_->Buf(), DXGI_FORMAT_R16_UINT, iOffset); - if (gstate_c.bezier || gstate_c.spline) + if (tess) context_->DrawIndexedInstanced(vertexCount, numPatches, 0, 0, 0); else context_->DrawIndexed(vertexCount, 0, 0); @@ -550,7 +552,7 @@ rotateVBO: context_->IASetVertexBuffers(0, 1, &vb_, &stride, &offset); if (useElements) { context_->IASetIndexBuffer(ib_, DXGI_FORMAT_R16_UINT, 0); - if (gstate_c.bezier || gstate_c.spline) + if (tess) context_->DrawIndexedInstanced(vertexCount, numPatches, 0, 0, 0); else context_->DrawIndexed(vertexCount, 0, 0); @@ -587,6 +589,7 @@ rotateVBO: params.texCache = textureCache_; params.allowClear = true; params.allowSeparateAlphaClear = false; // D3D11 doesn't support separate alpha clears + params.provokeFlatFirst = true; int maxIndex = indexGen.MaxIndex(); SoftwareTransform( @@ -693,71 +696,38 @@ rotateVBO: #endif } -void DrawEngineD3D11::TessellationDataTransferD3D11::SendDataToShader(const float * pos, const float * tex, const float * col, int size, bool hasColor, bool hasTexCoords) { - // Position +void DrawEngineD3D11::TessellationDataTransferD3D11::PrepareBuffers(float *&pos, float *&tex, float *&col, int &posStride, int &texStride, int &colStride, int size, bool hasColor, bool hasTexCoords) { + struct TessData { + float pos[3]; float pad1; + float uv[2]; float pad2[2]; + float color[4]; + }; + if (prevSize < size) { prevSize = size; - if (data_tex[0]) { - data_tex[0]->Release(); - view[0]->Release(); + if (buf) { + buf->Release(); + view->Release(); } - desc.Width = size; - desc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT; - HRESULT hr = device_->CreateTexture1D(&desc, nullptr, &data_tex[0]); - if (FAILED(hr)) { - INFO_LOG(G3D, "Failed to create D3D texture for HW tessellation"); - data_tex[0]->Release(); - return; // TODO: Turn off HW tessellation if texture creation error occured. - } - hr = device_->CreateShaderResourceView(data_tex[0], nullptr, &view[0]); - ASSERT_SUCCESS(hr); - context_->VSSetShaderResources(0, 1, &view[0]); - } - dstBox.right = size; - context_->UpdateSubresource(data_tex[0], 0, &dstBox, pos, 0, 0); + desc.ByteWidth = size * sizeof(TessData); + desc.StructureByteStride = sizeof(TessData); - // Texcoords - if (hasTexCoords) { - if (prevSizeTex < size) { - prevSizeTex = size; - if (data_tex[1]) { - data_tex[1]->Release(); - view[1]->Release(); - } - desc.Width = size; - desc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT; - HRESULT hr = device_->CreateTexture1D(&desc, nullptr, &data_tex[1]); - if (FAILED(hr)) { - INFO_LOG(G3D, "Failed to create D3D texture for HW tessellation"); - data_tex[1]->Release(); - return; - } - hr = device_->CreateShaderResourceView(data_tex[1], nullptr, &view[1]); - context_->VSSetShaderResources(1, 1, &view[1]); - } - dstBox.right = size; - context_->UpdateSubresource(data_tex[1], 0, &dstBox, tex, 0, 0); + device_->CreateBuffer(&desc, nullptr, &buf); + device_->CreateShaderResourceView(buf, 0, &view); + context_->VSSetShaderResources(0, 1, &view); } + D3D11_MAPPED_SUBRESOURCE map; + context_->Map(buf, 0, D3D11_MAP_WRITE_DISCARD, 0, &map); + uint8_t *data = (uint8_t *)map.pData; - // Color - int sizeColor = hasColor ? size : 1; - if (prevSizeCol < sizeColor) { - prevSizeCol = sizeColor; - if (data_tex[2]) { - data_tex[2]->Release(); - view[2]->Release(); - } - desc.Width = sizeColor; - desc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT; - HRESULT hr = device_->CreateTexture1D(&desc, nullptr, &data_tex[2]); - if (FAILED(hr)) { - INFO_LOG(G3D, "Failed to create D3D texture for HW tessellation"); - data_tex[2]->Release(); - return; - } - hr = device_->CreateShaderResourceView(data_tex[2], nullptr, &view[2]); - context_->VSSetShaderResources(2, 1, &view[2]); - } - dstBox.right = sizeColor; - context_->UpdateSubresource(data_tex[2], 0, &dstBox, col, 0, 0); + pos = (float *)(data); + tex = (float *)(data + offsetof(TessData, uv)); + col = (float *)(data + offsetof(TessData, color)); + posStride = sizeof(TessData) / sizeof(float); + colStride = hasColor ? (sizeof(TessData) / sizeof(float)) : 0; + texStride = sizeof(TessData) / sizeof(float); +} + +void DrawEngineD3D11::TessellationDataTransferD3D11::SendDataToShader(const float * pos, const float * tex, const float * col, int size, bool hasColor, bool hasTexCoords) { + context_->Unmap(buf, 0); } diff --git a/GPU/D3D11/DrawEngineD3D11.h b/GPU/D3D11/DrawEngineD3D11.h index 9d4589baae..8b797a3d2d 100644 --- a/GPU/D3D11/DrawEngineD3D11.h +++ b/GPU/D3D11/DrawEngineD3D11.h @@ -203,27 +203,25 @@ private: private: ID3D11DeviceContext *context_; ID3D11Device *device_; - ID3D11Texture1D *data_tex[3]; - ID3D11ShaderResourceView *view[3]; - D3D11_TEXTURE1D_DESC desc; - D3D11_BOX dstBox; + ID3D11Buffer *buf; + ID3D11ShaderResourceView *view; + D3D11_BUFFER_DESC desc; public: - TessellationDataTransferD3D11(ID3D11DeviceContext *context_, ID3D11Device *device_) - : TessellationDataTransfer(), context_(context_), device_(device_), data_tex(), view(), desc(), dstBox{0, 0, 0, 1, 1, 1} { - desc.CPUAccessFlags = 0; - desc.Usage = D3D11_USAGE_DEFAULT; - desc.ArraySize = 1; - desc.MipLevels = 1; + TessellationDataTransferD3D11(ID3D11DeviceContext *context, ID3D11Device *device) + : TessellationDataTransfer(), context_(context), device_(device), buf(), view(), desc() { + desc.Usage = D3D11_USAGE_DYNAMIC; desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED; } ~TessellationDataTransferD3D11() { - for (int i = 0; i < 3; i++) { - if (data_tex[i]) { - data_tex[i]->Release(); - view[i]->Release(); - } + if (buf) { + buf->Release(); + view->Release(); } } + + void PrepareBuffers(float *&pos, float *&tex, float *&col, int &posStride, int &texStride, int &colStride, int size, bool hasColor, bool hasTexCoords) override; void SendDataToShader(const float *pos, const float *tex, const float *col, int size, bool hasColor, bool hasTexCoords) override; }; }; diff --git a/GPU/D3D11/FramebufferManagerD3D11.cpp b/GPU/D3D11/FramebufferManagerD3D11.cpp index 9cd78f97dc..f2910cbec6 100644 --- a/GPU/D3D11/FramebufferManagerD3D11.cpp +++ b/GPU/D3D11/FramebufferManagerD3D11.cpp @@ -31,6 +31,7 @@ #include "Core/Host.h" #include "Core/MemMap.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/System.h" #include "Core/Reporting.h" #include "GPU/ge_constants.h" @@ -161,10 +162,7 @@ FramebufferManagerD3D11::~FramebufferManagerD3D11() { postInputLayout_->Release(); } - // FBO cleanup - for (auto it = tempFBOs_.begin(), end = tempFBOs_.end(); it != end; ++it) { - it->second.fbo->Release(); - } + // Temp FBOs cleared by FramebufferCommon. delete[] convBuf; // Stencil cleanup @@ -266,8 +264,6 @@ void FramebufferManagerD3D11::CompilePostShader() { } void FramebufferManagerD3D11::MakePixelTexture(const u8 *srcPixels, GEBufferFormat srcPixelFormat, int srcStride, int width, int height, float &u1, float &v1) { - u8 *convBuf = nullptr; - // TODO: Check / use D3DCAPS2_DYNAMICTEXTURES? if (drawPixelsTex_ && (drawPixelsTexW_ != width || drawPixelsTexH_ != height)) { drawPixelsTex_->Release(); @@ -301,54 +297,35 @@ void FramebufferManagerD3D11::MakePixelTexture(const u8 *srcPixels, GEBufferForm D3D11_MAPPED_SUBRESOURCE map; context_->Map(drawPixelsTex_, 0, D3D11_MAP_WRITE_DISCARD, 0, &map); - convBuf = (u8*)map.pData; - - if (srcPixelFormat != GE_FORMAT_8888 || srcStride != 512) { - for (int y = 0; y < height; y++) { - switch (srcPixelFormat) { - case GE_FORMAT_565: - { - const u16_le *src = (const u16_le *)srcPixels + srcStride * y; - u32 *dst = (u32 *)(convBuf + map.RowPitch * y); - ConvertRGB565ToBGRA8888(dst, src, width); - } - break; - // faster - case GE_FORMAT_5551: - { - const u16_le *src = (const u16_le *)srcPixels + srcStride * y; - u32 *dst = (u32 *)(convBuf + map.RowPitch * y); - ConvertRGBA5551ToBGRA8888(dst, src, width); - } - break; - case GE_FORMAT_4444: - { - const u16_le *src = (const u16_le *)srcPixels + srcStride * y; - u8 *dst = (u8 *)(convBuf + map.RowPitch * y); - ConvertRGBA4444ToBGRA8888((u32 *)dst, src, width); - } + for (int y = 0; y < height; y++) { + const u16_le *src16 = (const u16_le *)srcPixels + srcStride * y; + const u32_le *src32 = (const u32_le *)srcPixels + srcStride * y; + u32 *dst = (u32 *)((u8 *)map.pData + map.RowPitch * y); + switch (srcPixelFormat) { + case GE_FORMAT_565: + ConvertRGB565ToBGRA8888(dst, src16, width); break; - case GE_FORMAT_8888: - { - const u32_le *src = (const u32_le *)srcPixels + srcStride * y; - u32 *dst = (u32 *)(convBuf + map.RowPitch * y); - ConvertRGBA8888ToBGRA8888(dst, src, width); - } + case GE_FORMAT_5551: + ConvertRGBA5551ToBGRA8888(dst, src16, width); + break; + + case GE_FORMAT_4444: + ConvertRGBA4444ToBGRA8888(dst, src16, width); + break; + + case GE_FORMAT_8888: + ConvertRGBA8888ToBGRA8888(dst, src32, width); + break; + + case GE_FORMAT_INVALID: + _dbg_assert_msg_(G3D, false, "Invalid pixelFormat passed to DrawPixels()."); break; - } - } - } else { - for (int y = 0; y < height; y++) { - const u32_le *src = (const u32_le *)srcPixels + srcStride * y; - u32 *dst = (u32 *)(convBuf + map.RowPitch * y); - ConvertRGBA8888ToBGRA8888(dst, src, width); } } context_->Unmap(drawPixelsTex_, 0); context_->PSSetShaderResources(0, 1, &drawPixelsTexView_); - // D3DXSaveTextureToFile("game:\\cc.png", D3DXIFF_PNG, drawPixelsTex_, NULL); } void FramebufferManagerD3D11::DrawActiveTexture(float x, float y, float w, float h, float destW, float destH, float u0, float v0, float u1, float v1, int uvRotation, int flags) { @@ -544,7 +521,7 @@ void FramebufferManagerD3D11::BindFramebufferAsColorTexture(int stage, VirtualFr // Currently rendering to this framebuffer. Need to make a copy. if (!skipCopy && framebuffer == currentRenderVfb_) { // TODO: Maybe merge with bvfbs_? Not sure if those could be packing, and they're created at a different size. - Draw::Framebuffer *renderCopy = GetTempFBO(framebuffer->renderWidth, framebuffer->renderHeight, (Draw::FBColorDepth)framebuffer->colorDepth); + Draw::Framebuffer *renderCopy = GetTempFBO(TempFBO::COPY, framebuffer->renderWidth, framebuffer->renderHeight, (Draw::FBColorDepth)framebuffer->colorDepth); if (renderCopy) { VirtualFramebuffer copyInfo = *framebuffer; copyInfo.fbo = renderCopy; @@ -569,7 +546,7 @@ void FramebufferManagerD3D11::BindFramebufferAsColorTexture(int stage, VirtualFr bool FramebufferManagerD3D11::CreateDownloadTempBuffer(VirtualFramebuffer *nvfb) { nvfb->colorDepth = Draw::FBO_8888; - nvfb->fbo = draw_->CreateFramebuffer({ nvfb->width, nvfb->height, 1, 1, true, (Draw::FBColorDepth)nvfb->colorDepth }); + nvfb->fbo = draw_->CreateFramebuffer({ nvfb->bufferWidth, nvfb->bufferHeight, 1, 1, true, (Draw::FBColorDepth)nvfb->colorDepth }); if (!(nvfb->fbo)) { ERROR_LOG(FRAMEBUF, "Error creating FBO! %i x %i", nvfb->renderWidth, nvfb->renderHeight); return false; @@ -671,7 +648,7 @@ void FramebufferManagerD3D11::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, // Direct3D doesn't support rect -> self. Draw::Framebuffer *srcFBO = src->fbo; if (src == dst) { - Draw::Framebuffer *tempFBO = GetTempFBO(src->renderWidth, src->renderHeight, (Draw::FBColorDepth)src->colorDepth); + Draw::Framebuffer *tempFBO = GetTempFBO(TempFBO::BLIT, src->renderWidth, src->renderHeight, (Draw::FBColorDepth)src->colorDepth); SimpleBlit(tempFBO, dstX1, dstY1, dstX2, dstY2, src->fbo, srcX1, srcY1, srcX2, srcY2, false); srcFBO = tempFBO; } @@ -718,8 +695,8 @@ void FramebufferManagerD3D11::DestroyAllFBOs() { } bvfbs_.clear(); - for (auto it = tempFBOs_.begin(), end = tempFBOs_.end(); it != end; ++it) { - it->second.fbo->Release(); + for (auto &tempFB : tempFBOs_) { + tempFB.second.fbo->Release(); } tempFBOs_.clear(); diff --git a/GPU/D3D11/FramebufferManagerD3D11.h b/GPU/D3D11/FramebufferManagerD3D11.h index 253bc8dd6e..d73b772b62 100644 --- a/GPU/D3D11/FramebufferManagerD3D11.h +++ b/GPU/D3D11/FramebufferManagerD3D11.h @@ -17,10 +17,6 @@ #pragma once -#include -#include -#include - #include // Keeps track of allocated FBOs. @@ -122,9 +118,4 @@ private: ID3D11InputLayout *postInputLayout_ = nullptr; ID3D11Buffer *postConstants_ = nullptr; static const D3D11_INPUT_ELEMENT_DESC g_PostVertexElements[2]; - -#if 0 - AsyncPBO *pixelBufObj_; //this isn't that large - u8 currentPBO_; -#endif }; diff --git a/GPU/D3D11/GPU_D3D11.h b/GPU/D3D11/GPU_D3D11.h index acb4dd3e07..781cc5f1fb 100644 --- a/GPU/D3D11/GPU_D3D11.h +++ b/GPU/D3D11/GPU_D3D11.h @@ -83,5 +83,4 @@ private: ShaderManagerD3D11 *shaderManagerD3D11_; int lastVsync_; - int vertexCost_ = 0; }; diff --git a/GPU/D3D11/TextureCacheD3D11.cpp b/GPU/D3D11/TextureCacheD3D11.cpp index 7da4b726f7..44da7d3751 100644 --- a/GPU/D3D11/TextureCacheD3D11.cpp +++ b/GPU/D3D11/TextureCacheD3D11.cpp @@ -387,7 +387,7 @@ void TextureCacheD3D11::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFra const GEPaletteFormat clutFormat = gstate.getClutPaletteFormat(); ID3D11ShaderResourceView *clutTexture = depalShaderCache_->GetClutTexture(clutFormat, clutHash_, clutBuf_, expand32); - Draw::Framebuffer *depalFBO = framebufferManagerD3D11_->GetTempFBO(framebuffer->renderWidth, framebuffer->renderHeight, Draw::FBO_8888); + Draw::Framebuffer *depalFBO = framebufferManagerD3D11_->GetTempFBO(TempFBO::DEPAL, framebuffer->renderWidth, framebuffer->renderHeight, Draw::FBO_8888); shaderManager_->DirtyLastShader(); draw_->BindPipeline(nullptr); @@ -398,7 +398,8 @@ void TextureCacheD3D11::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFra shaderApply.ApplyBounds(gstate_c.vertBounds, gstate_c.curTextureXOffset, gstate_c.curTextureYOffset, xoff, yoff); shaderApply.Use(depalShaderCache_->GetDepalettizeVertexShader(), depalShaderCache_->GetInputLayout()); - context_->PSSetShaderResources(1, 1, &clutTexture); + context_->PSSetShaderResources(3, 1, &clutTexture); + context_->PSSetSamplers(3, 1, &stockD3D11.samplerPoint2DWrap); framebufferManagerD3D11_->BindFramebufferAsColorTexture(0, framebuffer, BINDFBCOLOR_SKIP_COPY); context_->PSSetSamplers(0, 1, &stockD3D11.samplerPoint2DWrap); draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); diff --git a/GPU/Debugger/Record.cpp b/GPU/Debugger/Record.cpp index ba98667954..fb130ef34a 100644 --- a/GPU/Debugger/Record.cpp +++ b/GPU/Debugger/Record.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include #include "base/stringutil.h" @@ -86,12 +87,37 @@ static std::vector lastRegisters; static std::vector lastTextures; // TODO: Maybe move execute to another file? -static u32 execMemcpyDest; -static u32 execListBuf; -static u32 execListPos; -static u32 execListID; -static const int LIST_BUF_SIZE = 256 * 1024; -static std::vector execListQueue; +class DumpExecute { +public: + ~DumpExecute(); + + bool Run(); + +private: + void SyncStall(); + bool SubmitCmds(void *p, u32 sz); + void SubmitListEnd(); + + void Init(u32 ptr, u32 sz); + void Registers(u32 ptr, u32 sz); + void Vertices(u32 ptr, u32 sz); + void Indices(u32 ptr, u32 sz); + void Clut(u32 ptr, u32 sz); + void TransferSrc(u32 ptr, u32 sz); + void Memset(u32 ptr, u32 sz); + void MemcpyDest(u32 ptr, u32 sz); + void Memcpy(u32 ptr, u32 sz); + void Texture(int level, u32 ptr, u32 sz); + void Display(u32 ptr, u32 sz); + + u32 execMemcpyDest = 0; + u32 execListBuf = 0; + u32 execListPos = 0; + u32 execListID = 0; + const int LIST_BUF_SIZE = 256 * 1024; + std::vector execListQueue; + u16 lastBufw_[8]{}; +}; // This class maps pushbuffer (dump data) sections to PSP memory. // Dumps can be larger than available PSP memory, because they include generated data too. @@ -102,7 +128,7 @@ static std::vector execListQueue; class BufMapping { public: // Returns a pointer to contiguous memory for this access, or else 0 (failure). - u32 Map(u32 bufpos, u32 sz); + u32 Map(u32 bufpos, u32 sz, const std::function &flush); // Clear and reset allocations made. void Reset() { @@ -117,8 +143,8 @@ public: } protected: - u32 MapSlab(u32 bufpos); - u32 MapExtra(u32 bufpos, u32 sz); + u32 MapSlab(u32 bufpos, const std::function &flush); + u32 MapExtra(u32 bufpos, u32 sz, const std::function &flush); enum { // These numbers kept low because we only have 24 MB of user memory to map into. @@ -189,20 +215,20 @@ protected: static BufMapping execMapping; -u32 BufMapping::Map(u32 bufpos, u32 sz) { +u32 BufMapping::Map(u32 bufpos, u32 sz, const std::function &flush) { int slab1 = bufpos / SLAB_SIZE; int slab2 = (bufpos + sz - 1) / SLAB_SIZE; if (slab1 == slab2) { // Doesn't straddle, so we can just map to a slab. - return MapSlab(bufpos); + return MapSlab(bufpos, flush); } else { // We need contiguous, so we'll just allocate separately. - return MapExtra(bufpos, sz); + return MapExtra(bufpos, sz, flush); } } -u32 BufMapping::MapSlab(u32 bufpos) { +u32 BufMapping::MapSlab(u32 bufpos, const std::function &flush) { u32 slab_pos = (bufpos / SLAB_SIZE) * SLAB_SIZE; int best = 0; @@ -216,6 +242,9 @@ u32 BufMapping::MapSlab(u32 bufpos) { } } + // Stall before mapping a new slab. + flush(); + // Okay, we need to allocate. if (!slabs_[best].Setup(slab_pos)) { return 0; @@ -223,7 +252,7 @@ u32 BufMapping::MapSlab(u32 bufpos) { return slabs_[best].Ptr(bufpos); } -u32 BufMapping::MapExtra(u32 bufpos, u32 sz) { +u32 BufMapping::MapExtra(u32 bufpos, u32 sz, const std::function &flush) { for (int i = 0; i < EXTRA_COUNT; ++i) { // Might be likely to reuse larger buffers straddling slabs. if (extra_[i].Matches(bufpos, sz)) { @@ -231,6 +260,9 @@ u32 BufMapping::MapExtra(u32 bufpos, u32 sz) { } } + // Stall first, so we don't stomp existing RAM. + flush(); + int i = extraOffset_; extraOffset_ = (extraOffset_ + 1) % EXTRA_COUNT; @@ -731,7 +763,18 @@ void NotifyFrame() { } } -static bool ExecuteSubmitCmds(void *p, u32 sz) { +void DumpExecute::SyncStall() { + gpu->UpdateStall(execListID, execListPos); + s64 listTicks = gpu->GetListTicks(execListID); + if (listTicks != -1) { + currentMIPS->downcount -= listTicks - CoreTiming::GetTicks(); + } + + // Make sure downcount doesn't overflow. + CoreTiming::ForceCheck(); +} + +bool DumpExecute::SubmitCmds(void *p, u32 sz) { if (execListBuf == 0) { u32 allocSize = LIST_BUF_SIZE; execListBuf = userMemory.Alloc(allocSize, "List buf"); @@ -761,27 +804,47 @@ static bool ExecuteSubmitCmds(void *p, u32 sz) { Memory::Write_U32((GE_CMD_JUMP << 24) | (execListBuf & 0x00FFFFFF), execListPos + 4); execListPos = execListBuf; + + // Don't continue until we've stalled. + SyncStall(); } Memory::MemcpyUnchecked(execListPos, execListQueue.data(), pendingSize); execListPos += pendingSize; + u32 writePos = execListPos; Memory::MemcpyUnchecked(execListPos, p, sz); execListPos += sz; - execListQueue.clear(); - gpu->UpdateStall(execListID, execListPos); - s64 listTicks = gpu->GetListTicks(execListID); - if (listTicks != -1) { - currentMIPS->downcount -= listTicks - CoreTiming::GetTicks(); + // TODO: Unfortunate. Maybe Texture commands should contain the bufw instead. + // The goal here is to realistically combine prims in dumps. Stalling for the bufw flushes. + u32_le *ops = (u32_le *)Memory::GetPointer(writePos); + for (u32 i = 0; i < sz / 4; ++i) { + u32 cmd = ops[i] >> 24; + if (cmd >= GE_CMD_TEXBUFWIDTH0 && cmd <= GE_CMD_TEXBUFWIDTH7) { + int level = cmd - GE_CMD_TEXBUFWIDTH0; + u16 bufw = ops[i] & 0xFFFF; + + // NOP the address part of the command to avoid a flush too. + if (bufw == lastBufw_[level]) + ops[i] = GE_CMD_NOP << 24; + else + ops[i] = (gstate.texbufwidth[level] & 0xFFFF0000) | bufw; + lastBufw_[level] = bufw; + } + + // Since we're here anyway, also NOP out texture addresses. + // This makes Step Tex not hit phantom textures. + if (cmd >= GE_CMD_TEXADDR0 && cmd <= GE_CMD_TEXADDR7) { + ops[i] = GE_CMD_NOP << 24; + } } - // Make sure downcount doesn't overflow. - CoreTiming::ForceCheck(); + execListQueue.clear(); return true; } -static void ExecuteSubmitListEnd() { +void DumpExecute::SubmitListEnd() { if (execListPos == 0) { return; } @@ -791,26 +854,21 @@ static void ExecuteSubmitListEnd() { Memory::Write_U32(GE_CMD_END << 24, execListPos + 4); execListPos += 8; - gpu->UpdateStall(execListID, execListPos); - currentMIPS->downcount -= gpu->GetListTicks(execListID) - CoreTiming::GetTicks(); - + SyncStall(); gpu->ListSync(execListID, 0); - - // Make sure downcount doesn't overflow. - CoreTiming::ForceCheck(); } -static void ExecuteInit(u32 ptr, u32 sz) { +void DumpExecute::Init(u32 ptr, u32 sz) { gstate.Restore((u32_le *)(pushbuf.data() + ptr)); gpu->ReapplyGfxState(); } -static void ExecuteRegisters(u32 ptr, u32 sz) { - ExecuteSubmitCmds(pushbuf.data() + ptr, sz); +void DumpExecute::Registers(u32 ptr, u32 sz) { + SubmitCmds(pushbuf.data() + ptr, sz); } -static void ExecuteVertices(u32 ptr, u32 sz) { - u32 psp = execMapping.Map(ptr, sz); +void DumpExecute::Vertices(u32 ptr, u32 sz) { + u32 psp = execMapping.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this)); if (psp == 0) { ERROR_LOG(SYSTEM, "Unable to allocate for vertices"); return; @@ -820,8 +878,8 @@ static void ExecuteVertices(u32 ptr, u32 sz) { execListQueue.push_back((GE_CMD_VADDR << 24) | (psp & 0x00FFFFFF)); } -static void ExecuteIndices(u32 ptr, u32 sz) { - u32 psp = execMapping.Map(ptr, sz); +void DumpExecute::Indices(u32 ptr, u32 sz) { + u32 psp = execMapping.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this)); if (psp == 0) { ERROR_LOG(SYSTEM, "Unable to allocate for indices"); return; @@ -831,8 +889,8 @@ static void ExecuteIndices(u32 ptr, u32 sz) { execListQueue.push_back((GE_CMD_IADDR << 24) | (psp & 0x00FFFFFF)); } -static void ExecuteClut(u32 ptr, u32 sz) { - u32 psp = execMapping.Map(ptr, sz); +void DumpExecute::Clut(u32 ptr, u32 sz) { + u32 psp = execMapping.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this)); if (psp == 0) { ERROR_LOG(SYSTEM, "Unable to allocate for clut"); return; @@ -842,18 +900,21 @@ static void ExecuteClut(u32 ptr, u32 sz) { execListQueue.push_back((GE_CMD_CLUTADDR << 24) | (psp & 0x00FFFFFF)); } -static void ExecuteTransferSrc(u32 ptr, u32 sz) { - u32 psp = execMapping.Map(ptr, sz); +void DumpExecute::TransferSrc(u32 ptr, u32 sz) { + u32 psp = execMapping.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this)); if (psp == 0) { ERROR_LOG(SYSTEM, "Unable to allocate for transfer"); return; } + // Need to sync in order to access gstate.transfersrcw. + SyncStall(); + execListQueue.push_back((gstate.transfersrcw & 0xFF00FFFF) | ((psp >> 8) & 0x00FF0000)); execListQueue.push_back(((GE_CMD_TRANSFERSRC) << 24) | (psp & 0x00FFFFFF)); } -static void ExecuteMemset(u32 ptr, u32 sz) { +void DumpExecute::Memset(u32 ptr, u32 sz) { struct MemsetCommand { u32 dest; int value; @@ -863,33 +924,37 @@ static void ExecuteMemset(u32 ptr, u32 sz) { const MemsetCommand *data = (const MemsetCommand *)(pushbuf.data() + ptr); if (Memory::IsVRAMAddress(data->dest)) { + SyncStall(); gpu->PerformMemorySet(data->dest, (u8)data->value, data->sz); } } -static void ExecuteMemcpyDest(u32 ptr, u32 sz) { +void DumpExecute::MemcpyDest(u32 ptr, u32 sz) { execMemcpyDest = *(const u32 *)(pushbuf.data() + ptr); } -static void ExecuteMemcpy(u32 ptr, u32 sz) { +void DumpExecute::Memcpy(u32 ptr, u32 sz) { if (Memory::IsVRAMAddress(execMemcpyDest)) { + SyncStall(); Memory::MemcpyUnchecked(execMemcpyDest, pushbuf.data() + ptr, sz); gpu->PerformMemoryUpload(execMemcpyDest, sz); } } -static void ExecuteTexture(int level, u32 ptr, u32 sz) { - u32 psp = execMapping.Map(ptr, sz); +void DumpExecute::Texture(int level, u32 ptr, u32 sz) { + u32 psp = execMapping.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this)); if (psp == 0) { ERROR_LOG(SYSTEM, "Unable to allocate for texture"); return; } - execListQueue.push_back((gstate.texbufwidth[level] & 0xFF00FFFF) | ((psp >> 8) & 0x00FF0000)); - execListQueue.push_back(((GE_CMD_TEXADDR0 + level) << 24) | (psp & 0x00FFFFFF)); + u32 bufwCmd = GE_CMD_TEXBUFWIDTH0 + level; + u32 addrCmd = GE_CMD_TEXADDR0 + level; + execListQueue.push_back((bufwCmd << 24) | ((psp >> 8) & 0x00FF0000) | lastBufw_[level]); + execListQueue.push_back((addrCmd << 24) | (psp & 0x00FFFFFF)); } -static void ExecuteDisplay(u32 ptr, u32 sz) { +void DumpExecute::Display(u32 ptr, u32 sz) { struct DisplayBufData { PSPPointer topaddr; u32 linesize, pixelFormat; @@ -897,11 +962,14 @@ static void ExecuteDisplay(u32 ptr, u32 sz) { DisplayBufData *disp = (DisplayBufData *)(pushbuf.data() + ptr); + // Sync up drawing. + SyncStall(); + __DisplaySetFramebuf(disp->topaddr.ptr, disp->linesize, disp->pixelFormat, 1); __DisplaySetFramebuf(disp->topaddr.ptr, disp->linesize, disp->pixelFormat, 0); } -static void ExecuteFree() { +DumpExecute::~DumpExecute() { execMemcpyDest = 0; if (execListBuf) { userMemory.Free(execListBuf); @@ -914,43 +982,43 @@ static void ExecuteFree() { pushbuf.clear(); } -static bool ExecuteCommands() { +bool DumpExecute::Run() { for (const Command &cmd : commands) { switch (cmd.type) { case CommandType::INIT: - ExecuteInit(cmd.ptr, cmd.sz); + Init(cmd.ptr, cmd.sz); break; case CommandType::REGISTERS: - ExecuteRegisters(cmd.ptr, cmd.sz); + Registers(cmd.ptr, cmd.sz); break; case CommandType::VERTICES: - ExecuteVertices(cmd.ptr, cmd.sz); + Vertices(cmd.ptr, cmd.sz); break; case CommandType::INDICES: - ExecuteIndices(cmd.ptr, cmd.sz); + Indices(cmd.ptr, cmd.sz); break; case CommandType::CLUT: - ExecuteClut(cmd.ptr, cmd.sz); + Clut(cmd.ptr, cmd.sz); break; case CommandType::TRANSFERSRC: - ExecuteTransferSrc(cmd.ptr, cmd.sz); + TransferSrc(cmd.ptr, cmd.sz); break; case CommandType::MEMSET: - ExecuteMemset(cmd.ptr, cmd.sz); + Memset(cmd.ptr, cmd.sz); break; case CommandType::MEMCPYDEST: - ExecuteMemcpyDest(cmd.ptr, cmd.sz); + MemcpyDest(cmd.ptr, cmd.sz); break; case CommandType::MEMCPYDATA: - ExecuteMemcpy(cmd.ptr, cmd.sz); + Memcpy(cmd.ptr, cmd.sz); break; case CommandType::TEXTURE0: @@ -961,11 +1029,11 @@ static bool ExecuteCommands() { case CommandType::TEXTURE5: case CommandType::TEXTURE6: case CommandType::TEXTURE7: - ExecuteTexture((int)cmd.type - (int)CommandType::TEXTURE0, cmd.ptr, cmd.sz); + Texture((int)cmd.type - (int)CommandType::TEXTURE0, cmd.ptr, cmd.sz); break; case CommandType::DISPLAY: - ExecuteDisplay(cmd.ptr, cmd.sz); + Display(cmd.ptr, cmd.sz); break; default: @@ -974,7 +1042,7 @@ static bool ExecuteCommands() { } } - ExecuteSubmitListEnd(); + SubmitListEnd(); return true; } @@ -1028,13 +1096,11 @@ bool RunMountedReplay(const std::string &filename) { if (truncated) { ERROR_LOG(SYSTEM, "Truncated GE dump"); - ExecuteFree(); return false; } - bool success = ExecuteCommands(); - ExecuteFree(); - return success; + DumpExecute executor; + return executor.Run(); } }; diff --git a/GPU/Debugger/Stepping.cpp b/GPU/Debugger/Stepping.cpp index 7e4962753a..644d833750 100644 --- a/GPU/Debugger/Stepping.cpp +++ b/GPU/Debugger/Stepping.cpp @@ -28,6 +28,7 @@ namespace GPUStepping { enum PauseAction { PAUSE_CONTINUE, PAUSE_BREAK, + PAUSE_GETOUTPUTBUF, PAUSE_GETFRAMEBUF, PAUSE_GETDEPTHBUF, PAUSE_GETSTENCILBUF, @@ -66,8 +67,11 @@ static void SetPauseAction(PauseAction act, bool waitComplete = true) { pauseAction = act; pauseLock.unlock(); + if (coreState == CORE_STEPPING && act != PAUSE_CONTINUE) + Core_UpdateSingleStep(); + actionComplete = false; - pauseWait.notify_one(); + pauseWait.notify_all(); while (waitComplete && !actionComplete) { actionWait.wait(guard); } @@ -84,6 +88,10 @@ static void RunPauseAction() { case PAUSE_BREAK: break; + case PAUSE_GETOUTPUTBUF: + bufferResult = gpuDebug->GetOutputFramebuffer(bufferFrame); + break; + case PAUSE_GETFRAMEBUF: bufferResult = gpuDebug->GetCurrentFramebuffer(bufferFrame, bufferType); break; @@ -113,17 +121,45 @@ static void RunPauseAction() { } actionComplete = true; - actionWait.notify_one(); + actionWait.notify_all(); pauseAction = PAUSE_BREAK; } +bool SingleStep() { + std::unique_lock guard(pauseLock); + if (coreState != CORE_RUNNING && coreState != CORE_NEXTFRAME && coreState != CORE_STEPPING) { + // Shutting down, don't try to step. + actionComplete = true; + actionWait.notify_all(); + return false; + } + if (!gpuDebug || pauseAction == PAUSE_CONTINUE) { + actionComplete = true; + actionWait.notify_all(); + return false; + } + + gpuDebug->NotifySteppingEnter(); + isStepping = true; + + RunPauseAction(); + + gpuDebug->NotifySteppingExit(); + isStepping = false; + return true; +} + bool EnterStepping(std::function callback) { std::unique_lock guard(pauseLock); if (coreState != CORE_RUNNING && coreState != CORE_NEXTFRAME) { // Shutting down, don't try to step. + actionComplete = true; + actionWait.notify_all(); return false; } if (!gpuDebug) { + actionComplete = true; + actionWait.notify_all(); return false; } @@ -152,7 +188,7 @@ bool IsStepping() { } static bool GetBuffer(const GPUDebugBuffer *&buffer, PauseAction type, const GPUDebugBuffer &resultBuffer) { - if (!isStepping) { + if (!isStepping && coreState != CORE_STEPPING) { return false; } @@ -161,6 +197,10 @@ static bool GetBuffer(const GPUDebugBuffer *&buffer, PauseAction type, const GPU return bufferResult; } +bool GPU_GetOutputFramebuffer(const GPUDebugBuffer *&buffer) { + return GetBuffer(buffer, PAUSE_GETOUTPUTBUF, bufferFrame); +} + bool GPU_GetCurrentFramebuffer(const GPUDebugBuffer *&buffer, GPUDebugFramebufferType type) { bufferType = type; return GetBuffer(buffer, PAUSE_GETFRAMEBUF, bufferFrame); @@ -184,7 +224,7 @@ bool GPU_GetCurrentClut(const GPUDebugBuffer *&buffer) { } bool GPU_SetCmdValue(u32 op) { - if (!isStepping) { + if (!isStepping && coreState != CORE_STEPPING) { return false; } @@ -200,7 +240,7 @@ void ResumeFromStepping() { void ForceUnpause() { SetPauseAction(PAUSE_CONTINUE, false); actionComplete = true; - actionWait.notify_one(); + actionWait.notify_all(); } } // namespace diff --git a/GPU/Debugger/Stepping.h b/GPU/Debugger/Stepping.h index cb651af234..fb24cf072a 100644 --- a/GPU/Debugger/Stepping.h +++ b/GPU/Debugger/Stepping.h @@ -20,6 +20,7 @@ #include #include "Common/CommonTypes.h" +#include "Core/Core.h" #include "GPU/Common/GPUDebugInterface.h" namespace GPUStepping { @@ -27,8 +28,10 @@ namespace GPUStepping { // Begins stepping and calls callback while inside a lock preparing stepping. // This would be a good place to deliver a message to code that stepping is ready. bool EnterStepping(std::function callback); + bool SingleStep(); bool IsStepping(); + bool GPU_GetOutputFramebuffer(const GPUDebugBuffer *&buffer); bool GPU_GetCurrentFramebuffer(const GPUDebugBuffer *&buffer, GPUDebugFramebufferType type); bool GPU_GetCurrentDepthbuffer(const GPUDebugBuffer *&buffer); bool GPU_GetCurrentStencilbuffer(const GPUDebugBuffer *&buffer); diff --git a/GPU/Directx9/DrawEngineDX9.cpp b/GPU/Directx9/DrawEngineDX9.cpp index c72d256727..de1a85d093 100644 --- a/GPU/Directx9/DrawEngineDX9.cpp +++ b/GPU/Directx9/DrawEngineDX9.cpp @@ -545,6 +545,7 @@ rotateVBO: params.texCache = textureCache_; params.allowClear = true; params.allowSeparateAlphaClear = true; + params.provokeFlatFirst = true; int maxIndex = indexGen.MaxIndex(); SoftwareTransform( diff --git a/GPU/Directx9/FramebufferDX9.cpp b/GPU/Directx9/FramebufferDX9.cpp index 3d7e7c03b7..a66faf39d1 100644 --- a/GPU/Directx9/FramebufferDX9.cpp +++ b/GPU/Directx9/FramebufferDX9.cpp @@ -22,6 +22,7 @@ #include "Core/Host.h" #include "Core/MemMap.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/System.h" #include "Core/Reporting.h" #include "GPU/ge_constants.h" @@ -82,12 +83,7 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { }; FramebufferManagerDX9::FramebufferManagerDX9(Draw::DrawContext *draw) - : FramebufferManagerCommon(draw), - drawPixelsTex_(0), - convBuf(0), - stencilUploadPS_(nullptr), - stencilUploadVS_(nullptr), - stencilUploadFailed_(false) { + : FramebufferManagerCommon(draw) { device_ = (LPDIRECT3DDEVICE9)draw->GetNativeObject(Draw::NativeObject::DEVICE); deviceEx_ = (LPDIRECT3DDEVICE9)draw->GetNativeObject(Draw::NativeObject::DEVICE_EX); @@ -119,11 +115,8 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { if (drawPixelsTex_) { drawPixelsTex_->Release(); } - for (auto it = tempFBOs_.begin(), end = tempFBOs_.end(); it != end; ++it) { - it->second.fbo->Release(); - } - for (auto it = offscreenSurfaces_.begin(), end = offscreenSurfaces_.end(); it != end; ++it) { - it->second.surface->Release(); + for (auto &it : offscreenSurfaces_) { + it.second.surface->Release(); } delete [] convBuf; if (stencilUploadPS_) { @@ -448,7 +441,7 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { } if (!skipCopy && currentRenderVfb_ && framebuffer->fb_address == gstate.getFrameBufRawAddress()) { // TODO: Maybe merge with bvfbs_? Not sure if those could be packing, and they're created at a different size. - Draw::Framebuffer *renderCopy = GetTempFBO(framebuffer->renderWidth, framebuffer->renderHeight, (Draw::FBColorDepth)framebuffer->colorDepth); + Draw::Framebuffer *renderCopy = GetTempFBO(TempFBO::COPY, framebuffer->renderWidth, framebuffer->renderHeight, (Draw::FBColorDepth)framebuffer->colorDepth); if (renderCopy) { VirtualFramebuffer copyInfo = *framebuffer; copyInfo.fbo = renderCopy; @@ -467,7 +460,7 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { bool FramebufferManagerDX9::CreateDownloadTempBuffer(VirtualFramebuffer *nvfb) { nvfb->colorDepth = Draw::FBO_8888; - nvfb->fbo = draw_->CreateFramebuffer({ nvfb->width, nvfb->height, 1, 1, true, (Draw::FBColorDepth)nvfb->colorDepth }); + nvfb->fbo = draw_->CreateFramebuffer({ nvfb->bufferWidth, nvfb->bufferHeight, 1, 1, true, (Draw::FBColorDepth)nvfb->colorDepth }); if (!(nvfb->fbo)) { ERROR_LOG(FRAMEBUF, "Error creating FBO! %i x %i", nvfb->renderWidth, nvfb->renderHeight); return false; @@ -514,7 +507,7 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { // Direct3D 9 doesn't support rect -> self. Draw::Framebuffer *srcFBO = src->fbo; if (src == dst) { - Draw::Framebuffer *tempFBO = GetTempFBO(src->renderWidth, src->renderHeight, (Draw::FBColorDepth)src->colorDepth); + Draw::Framebuffer *tempFBO = GetTempFBO(TempFBO::BLIT, src->renderWidth, src->renderHeight, (Draw::FBColorDepth)src->colorDepth); bool result = draw_->BlitFramebuffer( src->fbo, srcX1, srcY1, srcX2, srcY2, tempFBO, dstX1, dstY1, dstX2, dstY2, @@ -537,16 +530,7 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { const u32 *src32 = (const u32 *)src; if (format == GE_FORMAT_8888) { - u32 *dst32 = (u32 *)dst; - if (src == dst) { - return; - } else { - for (u32 y = 0; y < height; ++y) { - ConvertBGRA8888ToRGBA8888(dst32, src32, width); - src32 += srcStride; - dst32 += dstStride; - } - } + ConvertFromBGRA8888(dst, src, dstStride, srcStride, width, height, Draw::DataFormat::R8G8B8A8_UNORM); } else { // But here it shouldn't matter if they do intersect u16 *dst16 = (u16 *)dst; @@ -683,7 +667,7 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { int age = frameLastFramebufUsed_ - it->second.last_frame_used; if (age > FBO_OLD_AGE) { it->second.surface->Release(); - offscreenSurfaces_.erase(it++); + it = offscreenSurfaces_.erase(it); } else { ++it; } @@ -709,13 +693,8 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { } bvfbs_.clear(); - for (auto it = tempFBOs_.begin(), end = tempFBOs_.end(); it != end; ++it) { - it->second.fbo->Release(); - } - tempFBOs_.clear(); - - for (auto it = offscreenSurfaces_.begin(), end = offscreenSurfaces_.end(); it != end; ++it) { - it->second.surface->Release(); + for (auto &it : offscreenSurfaces_) { + it.second.surface->Release(); } offscreenSurfaces_.clear(); @@ -761,9 +740,6 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { if (offscreen) { success = GetRenderTargetFramebuffer(renderTarget, offscreen, w, h, buffer); } - if (tempFBO) { - tempFBO->Release(); - } } return success; diff --git a/GPU/Directx9/FramebufferDX9.h b/GPU/Directx9/FramebufferDX9.h index bc2ce7c31d..4c1498b786 100644 --- a/GPU/Directx9/FramebufferDX9.h +++ b/GPU/Directx9/FramebufferDX9.h @@ -17,9 +17,7 @@ #pragma once -#include -#include -#include +#include #include @@ -91,41 +89,31 @@ private: LPDIRECT3DDEVICE9 deviceEx_; // Used by DrawPixels - LPDIRECT3DTEXTURE9 drawPixelsTex_; + LPDIRECT3DTEXTURE9 drawPixelsTex_ = nullptr; int drawPixelsTexW_; int drawPixelsTexH_; - LPDIRECT3DVERTEXSHADER9 pFramebufferVertexShader; - LPDIRECT3DPIXELSHADER9 pFramebufferPixelShader; - LPDIRECT3DVERTEXDECLARATION9 pFramebufferVertexDecl; + LPDIRECT3DVERTEXSHADER9 pFramebufferVertexShader = nullptr; + LPDIRECT3DPIXELSHADER9 pFramebufferPixelShader = nullptr; + LPDIRECT3DVERTEXDECLARATION9 pFramebufferVertexDecl = nullptr; - u8 *convBuf; + u8 *convBuf = nullptr; int plainColorLoc_; - LPDIRECT3DPIXELSHADER9 stencilUploadPS_; - LPDIRECT3DVERTEXSHADER9 stencilUploadVS_; - bool stencilUploadFailed_; + LPDIRECT3DPIXELSHADER9 stencilUploadPS_ = nullptr; + LPDIRECT3DVERTEXSHADER9 stencilUploadVS_ = nullptr; + bool stencilUploadFailed_ = false; TextureCacheDX9 *textureCacheDX9_; ShaderManagerDX9 *shaderManagerDX9_; DrawEngineDX9 *drawEngineD3D9_; - struct TempFBO { - Draw::Framebuffer *fbo; - int last_frame_used; - }; struct OffscreenSurface { LPDIRECT3DSURFACE9 surface; int last_frame_used; }; - std::map tempFBOs_; - std::map offscreenSurfaces_; - -#if 0 - AsyncPBO *pixelBufObj_; //this isn't that large - u8 currentPBO_; -#endif + std::unordered_map offscreenSurfaces_; }; }; diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp index adb50ecbf0..afd5a86d10 100644 --- a/GPU/Directx9/GPU_DX9.cpp +++ b/GPU/Directx9/GPU_DX9.cpp @@ -28,6 +28,7 @@ #include "Core/MIPS/MIPS.h" #include "Core/Host.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/Reporting.h" #include "Core/System.h" @@ -210,7 +211,7 @@ void GPU_DX9::ReapplyGfxState() { void GPU_DX9::BeginFrame() { // Turn off vsync when unthrottled int desiredVSyncInterval = g_Config.bVSync ? 1 : 0; - if ((PSP_CoreParameter().unthrottle) || (PSP_CoreParameter().fpsLimit == 1)) + if (PSP_CoreParameter().unthrottle || PSP_CoreParameter().fpsLimit != FPSLimit::NORMAL) desiredVSyncInterval = 0; if (desiredVSyncInterval != lastVsync_) { dxstate.SetVSyncInterval(desiredVSyncInterval); diff --git a/GPU/Directx9/GPU_DX9.h b/GPU/Directx9/GPU_DX9.h index 3fb4be032d..5bf58fd136 100644 --- a/GPU/Directx9/GPU_DX9.h +++ b/GPU/Directx9/GPU_DX9.h @@ -82,7 +82,6 @@ private: ShaderManagerDX9 *shaderManagerDX9_; int lastVsync_; - int vertexCost_ = 0; }; } // namespace DX9 diff --git a/GPU/Directx9/PixelShaderGeneratorDX9.cpp b/GPU/Directx9/PixelShaderGeneratorDX9.cpp index cdb6c21fce..609f89a792 100644 --- a/GPU/Directx9/PixelShaderGeneratorDX9.cpp +++ b/GPU/Directx9/PixelShaderGeneratorDX9.cpp @@ -139,7 +139,8 @@ bool GenerateFragmentShaderHLSL(const FShaderID &id, char *buffer, char* scalerC if (doTexture) { WRITE(p, " float3 v_texcoord: TEXCOORD0;\n"); } - WRITE(p, " float4 v_color0: COLOR0;\n"); + const char *colorInterpolation = doFlatShading && lang == HLSL_D3D11 ? "nointerpolation " : ""; + WRITE(p, " %sfloat4 v_color0: COLOR0;\n", colorInterpolation); if (lmode) { WRITE(p, " float3 v_color1: COLOR1;\n"); } diff --git a/GPU/Directx9/ShaderManagerDX9.cpp b/GPU/Directx9/ShaderManagerDX9.cpp index 6e99dcbf79..077e80299b 100644 --- a/GPU/Directx9/ShaderManagerDX9.cpp +++ b/GPU/Directx9/ShaderManagerDX9.cpp @@ -554,7 +554,9 @@ void ShaderManagerDX9::DirtyLastShader() { // disables vertex arrays } VSShader *ShaderManagerDX9::ApplyShader(int prim, u32 vertType) { - bool useHWTransform = CanUseHardwareTransform(prim); + // Always use software for flat shading to fix the provoking index. + bool tess = gstate_c.bezier || gstate_c.spline; + bool useHWTransform = CanUseHardwareTransform(prim) && (tess || gstate.getShadeMode() != GE_SHADE_FLAT); VShaderID VSID; if (gstate_c.IsDirty(DIRTY_VERTEXSHADER_STATE)) { diff --git a/GPU/Directx9/StateMappingDX9.cpp b/GPU/Directx9/StateMappingDX9.cpp index eb6fbfe84a..1577201d20 100644 --- a/GPU/Directx9/StateMappingDX9.cpp +++ b/GPU/Directx9/StateMappingDX9.cpp @@ -207,10 +207,13 @@ void DrawEngineDX9::ApplyDrawState(int prim) { if (gstate.isModeClear()) { // Set Cull dxstate.cullMode.set(false, false); + // Well, probably doesn't matter... + dxstate.shadeMode.set(D3DSHADE_GOURAUD); } else { // Set cull bool wantCull = !gstate.isModeThrough() && prim != GE_PRIM_RECTANGLES && gstate.isCullEnabled(); dxstate.cullMode.set(wantCull, gstate.getCullMode()); + dxstate.shadeMode.set(gstate.getShadeMode() == GE_SHADE_GOURAUD ? D3DSHADE_GOURAUD : D3DSHADE_FLAT); } } diff --git a/GPU/Directx9/TextureCacheDX9.cpp b/GPU/Directx9/TextureCacheDX9.cpp index 10d9d72e5e..872d559232 100644 --- a/GPU/Directx9/TextureCacheDX9.cpp +++ b/GPU/Directx9/TextureCacheDX9.cpp @@ -426,7 +426,7 @@ void TextureCacheDX9::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFrame const GEPaletteFormat clutFormat = gstate.getClutPaletteFormat(); LPDIRECT3DTEXTURE9 clutTexture = depalShaderCache_->GetClutTexture(clutFormat, clutHash_, clutBuf_); - Draw::Framebuffer *depalFBO = framebufferManagerDX9_->GetTempFBO(framebuffer->renderWidth, framebuffer->renderHeight, Draw::FBO_8888); + Draw::Framebuffer *depalFBO = framebufferManagerDX9_->GetTempFBO(TempFBO::DEPAL, framebuffer->renderWidth, framebuffer->renderHeight, Draw::FBO_8888); draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); shaderManager_->DirtyLastShader(); diff --git a/GPU/Directx9/VertexShaderGeneratorDX9.cpp b/GPU/Directx9/VertexShaderGeneratorDX9.cpp index 1a14e9988b..515305fc5b 100644 --- a/GPU/Directx9/VertexShaderGeneratorDX9.cpp +++ b/GPU/Directx9/VertexShaderGeneratorDX9.cpp @@ -253,7 +253,8 @@ void GenerateVertexShaderHLSL(const VShaderID &id, char *buffer, ShaderLanguage if (doTexture) { WRITE(p, " float3 v_texcoord : TEXCOORD0;\n"); } - WRITE(p, " float4 v_color0 : COLOR0;\n"); + const char *colorInterpolation = doFlatShading && lang == HLSL_D3D11 ? "nointerpolation " : ""; + WRITE(p, " %sfloat4 v_color0 : COLOR0;\n", colorInterpolation); if (lmode) WRITE(p, " float3 v_color1 : COLOR1;\n"); @@ -287,9 +288,12 @@ void GenerateVertexShaderHLSL(const VShaderID &id, char *buffer, ShaderLanguage // Hardware tessellation if (doSpline || doBezier) { if (lang == HLSL_D3D11 || lang == HLSL_D3D11_LEVEL9) { - WRITE(p, "Texture1D u_tess_pos_tex : register(t0);\n"); - WRITE(p, "Texture1D u_tess_tex_tex : register(t1);\n"); - WRITE(p, "Texture1D u_tess_col_tex : register(t2);\n"); + WRITE(p, "struct TessData {\n"); + WRITE(p, " float3 pos; float pad1;\n"); + WRITE(p, " float2 tex; float2 pad2;\n"); + WRITE(p, " float4 col;\n"); + WRITE(p, "};"); + WRITE(p, "StructuredBuffer tess_data : register(t0);\n"); } const char *init[3] = { "0.0, 0.0", "0.0, 0.0, 0.0", "0.0, 0.0, 0.0, 0.0" }; @@ -433,17 +437,15 @@ void GenerateVertexShaderHLSL(const VShaderID &id, char *buffer, ShaderLanguage WRITE(p, " float3 _pos[16];\n"); WRITE(p, " float2 _tex[16];\n"); WRITE(p, " float4 _col[16];\n"); - WRITE(p, " int idx;\n"); - WRITE(p, " int2 index;\n"); + WRITE(p, " int index;\n"); for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { - WRITE(p, " idx = (%i + v%s) * u_spline_count_u + (%i + u%s);\n", i, doBezier ? " * 3" : "", j, doBezier ? " * 3" : ""); - WRITE(p, " index = int2(idx, 0);\n"); - WRITE(p, " _pos[%i] = u_tess_pos_tex.Load(index).xyz;\n", i * 4 + j); + WRITE(p, " index = (%i + v%s) * u_spline_count_u + (%i + u%s);\n", i, doBezier ? " * 3" : "", j, doBezier ? " * 3" : ""); + WRITE(p, " _pos[%i] = tess_data[index].pos;\n", i * 4 + j); if (doTexture && hasTexcoord && hasTexcoordTess) - WRITE(p, " _tex[%i] = u_tess_tex_tex.Load(index).xy;\n", i * 4 + j); + WRITE(p, " _tex[%i] = tess_data[index].tex;\n", i * 4 + j); if (hasColor && hasColorTess) - WRITE(p, " _col[%i] = u_tess_col_tex.Load(index).rgba;\n", i * 4 + j); + WRITE(p, " _col[%i] = tess_data[index].col;\n", i * 4 + j); } } WRITE(p, " float2 weights[4];\n"); @@ -473,7 +475,7 @@ void GenerateVertexShaderHLSL(const VShaderID &id, char *buffer, ShaderLanguage if (hasColorTess) WRITE(p, " float4 col = tess_sample(_col, weights);\n"); else - WRITE(p, " float4 col = u_tess_col_tex.Load(int2(0, 0)).rgba;\n"); + WRITE(p, " float4 col = tess_data[0].col;\n"); } if (hasNormal) { // Curved surface is probably always need to compute normal(not sampling from control points) diff --git a/GPU/GLES/DepalettizeShaderGLES.cpp b/GPU/GLES/DepalettizeShaderGLES.cpp index 77614c93df..52b1458b13 100644 --- a/GPU/GLES/DepalettizeShaderGLES.cpp +++ b/GPU/GLES/DepalettizeShaderGLES.cpp @@ -25,15 +25,10 @@ #include "GPU/GLES/TextureCacheGLES.h" #include "GPU/Common/DepalettizeShaderCommon.h" -#ifdef _WIN32 -#define SHADERLOG -#endif - static const char *depalVShader100 = -#ifdef USING_GLES2 -"#version 100\n" +"#ifdef GL_ES\n" "precision highp float;\n" -#endif +"#endif\n" "attribute vec4 a_position;\n" "attribute vec2 a_texcoord0;\n" "varying vec2 v_texcoord0;\n" @@ -43,12 +38,9 @@ static const char *depalVShader100 = "}\n"; static const char *depalVShader300 = -#ifdef USING_GLES2 -"#version 300 es\n" +"#ifdef GL_ES\n" "precision highp float;\n" -#else -"#version 330\n" -#endif +"#endif\n" "in vec4 a_position;\n" "in vec2 a_texcoord0;\n" "out vec2 v_texcoord0;\n" @@ -70,9 +62,19 @@ DepalShaderCacheGLES::~DepalShaderCacheGLES() { Clear(); } +void DepalShaderCacheGLES::DeviceRestore(Draw::DrawContext *draw) { + render_ = (GLRenderManager *)draw->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); +} + bool DepalShaderCacheGLES::CreateVertexShader() { std::string src(useGL3_ ? depalVShader300 : depalVShader100); - vertexShader_ = render_->CreateShader(GL_VERTEX_SHADER, src, "depal"); + std::string prelude; + if (gl_extensions.IsGLES) { + prelude = useGL3_ ? "#version 300 es\n" : "#version 100\n"; + } else if (useGL3_) { + prelude = "#version 330\n"; + } + vertexShader_ = render_->CreateShader(GL_VERTEX_SHADER, prelude + src, "depal"); return true; } diff --git a/GPU/GLES/DepalettizeShaderGLES.h b/GPU/GLES/DepalettizeShaderGLES.h index 6fd9749fec..e183ba22b0 100644 --- a/GPU/GLES/DepalettizeShaderGLES.h +++ b/GPU/GLES/DepalettizeShaderGLES.h @@ -54,6 +54,11 @@ public: std::vector DebugGetShaderIDs(DebugShaderType type); std::string DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType); + void DeviceLost() { + Clear(); + } + void DeviceRestore(Draw::DrawContext *draw); + private: bool CreateVertexShader(); diff --git a/GPU/GLES/DrawEngineGLES.cpp b/GPU/GLES/DrawEngineGLES.cpp index c8da0dc244..cfd10ee0f1 100644 --- a/GPU/GLES/DrawEngineGLES.cpp +++ b/GPU/GLES/DrawEngineGLES.cpp @@ -103,15 +103,18 @@ void DrawEngineGLES::DeviceLost() { DestroyDeviceObjects(); } -void DrawEngineGLES::DeviceRestore() { +void DrawEngineGLES::DeviceRestore(Draw::DrawContext *draw) { + draw_ = draw; + render_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); InitDeviceObjects(); } void DrawEngineGLES::InitDeviceObjects() { + _assert_msg_(G3D, render_ != nullptr, "Render manager must be set"); + for (int i = 0; i < GLRenderManager::MAX_INFLIGHT_FRAMES; i++) { frameData_[i].pushVertex = render_->CreatePushBuffer(i, GL_ARRAY_BUFFER, 1024 * 1024); frameData_[i].pushIndex = render_->CreatePushBuffer(i, GL_ELEMENT_ARRAY_BUFFER, 256 * 1024); - } int vertexSize = sizeof(TransformedVertex); @@ -129,8 +132,10 @@ void DrawEngineGLES::DestroyDeviceObjects() { if (!frameData_[i].pushVertex && !frameData_[i].pushIndex) continue; - render_->DeletePushBuffer(frameData_[i].pushVertex); - render_->DeletePushBuffer(frameData_[i].pushIndex); + if (frameData_[i].pushVertex) + render_->DeletePushBuffer(frameData_[i].pushVertex); + if (frameData_[i].pushIndex) + render_->DeletePushBuffer(frameData_[i].pushIndex); frameData_[i].pushVertex = nullptr; frameData_[i].pushIndex = nullptr; } @@ -551,6 +556,7 @@ rotateVBO: params.texCache = textureCache_; params.allowClear = true; params.allowSeparateAlphaClear = true; + params.provokeFlatFirst = false; int maxIndex = indexGen.MaxIndex(); int vertexCount = indexGen.VertexCount(); @@ -667,7 +673,7 @@ void DrawEngineGLES::TessellationDataTransferGLES::SendDataToShader(const float if (data_tex[1]) renderManager_->DeleteTexture(data_tex[1]); uint8_t *tex_data = new uint8_t[size * sizeof(float) * 4]; - memcpy(tex_data, pos, size * sizeof(float) * 4); + memcpy(tex_data, tex, size * sizeof(float) * 4); data_tex[1] = renderManager_->CreateTexture(GL_TEXTURE_2D); renderManager_->TextureImage(data_tex[1], 0, size, 1, GL_RGBA32F, GL_RGBA, GL_FLOAT, tex_data, GLRAllocType::NEW, false); renderManager_->FinalizeTexture(data_tex[1], 0, false); diff --git a/GPU/GLES/DrawEngineGLES.h b/GPU/GLES/DrawEngineGLES.h index 535535d42e..2901a1c384 100644 --- a/GPU/GLES/DrawEngineGLES.h +++ b/GPU/GLES/DrawEngineGLES.h @@ -130,7 +130,7 @@ public: } void DeviceLost(); - void DeviceRestore(); + void DeviceRestore(Draw::DrawContext *draw); void ClearTrackedVertexArrays() override; void DecimateTrackedVertexArrays(); diff --git a/GPU/GLES/FragmentShaderGeneratorGLES.cpp b/GPU/GLES/FragmentShaderGeneratorGLES.cpp index 42bf39c47d..dff4125a9e 100644 --- a/GPU/GLES/FragmentShaderGeneratorGLES.cpp +++ b/GPU/GLES/FragmentShaderGeneratorGLES.cpp @@ -175,6 +175,10 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, uint64_t *uniform bool isModeClear = id.Bit(FS_BIT_CLEARMODE); + if (shaderDepal && gl_extensions.IsGLES) { + WRITE(p, "precision highp int;\n"); + } + const char *shading = ""; if (glslES30) shading = doFlatShading ? "flat" : ""; diff --git a/GPU/GLES/FragmentTestCacheGLES.cpp b/GPU/GLES/FragmentTestCacheGLES.cpp index 0ad098a499..ea482896a7 100644 --- a/GPU/GLES/FragmentTestCacheGLES.cpp +++ b/GPU/GLES/FragmentTestCacheGLES.cpp @@ -35,6 +35,10 @@ FragmentTestCacheGLES::~FragmentTestCacheGLES() { Clear(); } +void FragmentTestCacheGLES::DeviceRestore(Draw::DrawContext *draw) { + render_ = (GLRenderManager *)draw->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); +} + void FragmentTestCacheGLES::BindTestTexture(int slot) { if (!g_Config.bFragmentTestCache) { return; diff --git a/GPU/GLES/FragmentTestCacheGLES.h b/GPU/GLES/FragmentTestCacheGLES.h index 043b236fe6..da7f1c78b8 100644 --- a/GPU/GLES/FragmentTestCacheGLES.h +++ b/GPU/GLES/FragmentTestCacheGLES.h @@ -67,6 +67,10 @@ public: void BindTestTexture(int slot); + void DeviceLost() { + Clear(false); + } + void DeviceRestore(Draw::DrawContext *draw); void Clear(bool deleteThem = true); void Decimate(); diff --git a/GPU/GLES/FramebufferManagerGLES.cpp b/GPU/GLES/FramebufferManagerGLES.cpp index 1211459f02..81b2498346 100644 --- a/GPU/GLES/FramebufferManagerGLES.cpp +++ b/GPU/GLES/FramebufferManagerGLES.cpp @@ -32,6 +32,7 @@ #include "Core/Host.h" #include "Core/MemMap.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/System.h" #include "Core/Reporting.h" #include "GPU/ge_constants.h" @@ -54,9 +55,9 @@ static const char tex_fs[] = "#define gl_FragColor fragColor0\n" "out vec4 fragColor0;\n" "#endif\n" -#ifdef USING_GLES2 + "#ifdef GL_ES\n" "precision mediump float;\n" -#endif + "#endif\n" "uniform sampler2D sampler0;\n" "varying vec2 v_texcoord0;\n" "void main() {\n" @@ -76,10 +77,6 @@ static const char basic_vs[] = " gl_Position = a_position;\n" "}\n"; -const int MAX_PBO = 2; - -void ConvertFromRGBA8888(u8 *dst, const u8 *src, u32 dstStride, u32 srcStride, u32 width, u32 height, GEBufferFormat format); - void FramebufferManagerGLES::CompileDraw2DProgram() { if (!draw2dprogram_) { std::string errorString; @@ -336,62 +333,39 @@ FramebufferManagerGLES::~FramebufferManagerGLES() { } void FramebufferManagerGLES::MakePixelTexture(const u8 *srcPixels, GEBufferFormat srcPixelFormat, int srcStride, int width, int height, float &u1, float &v1) { - // Optimization: skip a copy if possible in a common case. - int texWidth = width; - if (srcPixelFormat == GE_FORMAT_8888 && width < srcStride) { - // Don't up the upload requirements too much if subimages are unsupported. - if (gstate_c.Supports(GPU_SUPPORTS_UNPACK_SUBIMAGE) || width >= 480) { - texWidth = srcStride; - u1 *= (float)width / texWidth; - } - } - if (drawPixelsTex_) { render_->DeleteTexture(drawPixelsTex_); } drawPixelsTex_ = render_->CreateTexture(GL_TEXTURE_2D); - drawPixelsTexW_ = texWidth; + drawPixelsTexW_ = width; drawPixelsTexH_ = height; drawPixelsTexFormat_ = srcPixelFormat; // TODO: We can just change the texture format and flip some bits around instead of this. // Could share code with the texture cache perhaps. - u32 neededSize = texWidth * height * 4; + u32 neededSize = width * height * 4; u8 *convBuf = new u8[neededSize]; for (int y = 0; y < height; y++) { + const u16_le *src16 = (const u16_le *)srcPixels + srcStride * y; + const u32_le *src32 = (const u32_le *)srcPixels + srcStride * y; + u32 *dst = (u32 *)convBuf + width * y; switch (srcPixelFormat) { case GE_FORMAT_565: - { - const u16 *src = (const u16 *)srcPixels + srcStride * y; - u8 *dst = convBuf + 4 * texWidth * y; - ConvertRGBA565ToRGBA8888((u32 *)dst, src, width); - } + ConvertRGBA565ToRGBA8888((u32 *)dst, src16, width); break; case GE_FORMAT_5551: - { - const u16 *src = (const u16 *)srcPixels + srcStride * y; - u8 *dst = convBuf + 4 * texWidth * y; - ConvertRGBA5551ToRGBA8888((u32 *)dst, src, width); - } + ConvertRGBA5551ToRGBA8888((u32 *)dst, src16, width); break; case GE_FORMAT_4444: - { - const u16 *src = (const u16 *)srcPixels + srcStride * y; - u8 *dst = convBuf + 4 * texWidth * y; - ConvertRGBA4444ToRGBA8888((u32 *)dst, src, width); - } + ConvertRGBA4444ToRGBA8888((u32 *)dst, src16, width); break; case GE_FORMAT_8888: - { - const u8 *src = srcPixels + srcStride * 4 * y; - u8 *dst = convBuf + 4 * texWidth * y; - memcpy(dst, src, 4 * width); - } + memcpy(dst, src32, 4 * width); break; case GE_FORMAT_INVALID: @@ -399,7 +373,7 @@ void FramebufferManagerGLES::MakePixelTexture(const u8 *srcPixels, GEBufferForma break; } } - render_->TextureImage(drawPixelsTex_, 0, texWidth, height, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, convBuf, GLRAllocType::NEW, false); + render_->TextureImage(drawPixelsTex_, 0, width, height, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, convBuf, GLRAllocType::NEW, false); render_->FinalizeTexture(drawPixelsTex_, 0, false); // TODO: Return instead? @@ -544,7 +518,7 @@ void FramebufferManagerGLES::BindFramebufferAsColorTexture(int stage, VirtualFra } if (!skipCopy && currentRenderVfb_ && framebuffer->fb_address == gstate.getFrameBufRawAddress()) { // TODO: Maybe merge with bvfbs_? Not sure if those could be packing, and they're created at a different size. - Draw::Framebuffer *renderCopy = GetTempFBO(framebuffer->renderWidth, framebuffer->renderHeight, (Draw::FBColorDepth)framebuffer->colorDepth); + Draw::Framebuffer *renderCopy = GetTempFBO(TempFBO::COPY, framebuffer->renderWidth, framebuffer->renderHeight, (Draw::FBColorDepth)framebuffer->colorDepth); if (renderCopy) { VirtualFramebuffer copyInfo = *framebuffer; copyInfo.fbo = renderCopy; @@ -579,7 +553,7 @@ bool FramebufferManagerGLES::CreateDownloadTempBuffer(VirtualFramebuffer *nvfb) } } - nvfb->fbo = draw_->CreateFramebuffer({ nvfb->width, nvfb->height, 1, 1, false, (Draw::FBColorDepth)nvfb->colorDepth }); + nvfb->fbo = draw_->CreateFramebuffer({ nvfb->bufferWidth, nvfb->bufferHeight, 1, 1, false, (Draw::FBColorDepth)nvfb->colorDepth }); if (!nvfb->fbo) { ERROR_LOG(FRAMEBUF, "Error creating GL FBO! %i x %i", nvfb->renderWidth, nvfb->renderHeight); return false; @@ -679,61 +653,6 @@ void FramebufferManagerGLES::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_BLEND_STATE | DIRTY_RASTER_STATE); } -void ConvertFromRGBA8888(u8 *dst, const u8 *src, u32 dstStride, u32 srcStride, u32 width, u32 height, GEBufferFormat format) { - // Must skip stride in the cases below. Some games pack data into the cracks, like MotoGP. - const u32 *src32 = (const u32 *)src; - - if (format == GE_FORMAT_8888) { - u32 *dst32 = (u32 *)dst; - if (src == dst) { - return; - } else { - // Here let's assume they don't intersect - for (u32 y = 0; y < height; ++y) { - memcpy(dst32, src32, width * 4); - src32 += srcStride; - dst32 += dstStride; - } - } - } else { - // But here it shouldn't matter if they do intersect - u16 *dst16 = (u16 *)dst; - switch (format) { - case GE_FORMAT_565: // BGR 565 - { - for (u32 y = 0; y < height; ++y) { - ConvertRGBA8888ToRGB565(dst16, src32, width); - src32 += srcStride; - dst16 += dstStride; - } - } - break; - case GE_FORMAT_5551: // ABGR 1555 - { - for (u32 y = 0; y < height; ++y) { - ConvertRGBA8888ToRGBA5551(dst16, src32, width); - src32 += srcStride; - dst16 += dstStride; - } - } - break; - case GE_FORMAT_4444: // ABGR 4444 - { - for (u32 y = 0; y < height; ++y) { - ConvertRGBA8888ToRGBA4444(dst16, src32, width); - src32 += srcStride; - dst16 += dstStride; - } - } - break; - case GE_FORMAT_8888: - case GE_FORMAT_INVALID: - // Not possible. - break; - } - } -} - void FramebufferManagerGLES::PackDepthbuffer(VirtualFramebuffer *vfb, int x, int y, int w, int h) { if (!vfb->fbo) { ERROR_LOG_REPORT_ONCE(vfbfbozero, SCEGE, "PackDepthbuffer: vfb->fbo == 0"); @@ -809,8 +728,8 @@ void FramebufferManagerGLES::DestroyAllFBOs() { } bvfbs_.clear(); - for (auto it = tempFBOs_.begin(), end = tempFBOs_.end(); it != end; ++it) { - it->second.fbo->Release(); + for (auto &tempFB : tempFBOs_) { + tempFB.second.fbo->Release(); } tempFBOs_.clear(); diff --git a/GPU/GLES/FramebufferManagerGLES.h b/GPU/GLES/FramebufferManagerGLES.h index c7ad049210..8c0be0ac1e 100644 --- a/GPU/GLES/FramebufferManagerGLES.h +++ b/GPU/GLES/FramebufferManagerGLES.h @@ -17,10 +17,6 @@ #pragma once -#include -#include -#include - #include "ext/native/thin3d/thin3d.h" // Keeps track of allocated FBOs. // Also provides facilities for drawing and later converting raw diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index b1505cfacd..5eca0e9296 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -58,7 +58,7 @@ GPU_GLES::GPU_GLES(GraphicsContext *gfxCtx, Draw::DrawContext *draw) GLRenderManager *render = (GLRenderManager *)draw->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); - shaderManagerGL_ = new ShaderManagerGLES(render); + shaderManagerGL_ = new ShaderManagerGLES(draw); framebufferManagerGL_ = new FramebufferManagerGLES(draw, render); framebufferManager_ = framebufferManagerGL_; textureCacheGL_ = new TextureCacheGLES(draw); @@ -128,13 +128,15 @@ GPU_GLES::~GPU_GLES() { // If we're here during app shutdown (exiting the Windows app in-game, for example) // everything should already be cleared since DeviceLost has been run. + if (!shaderCachePath_.empty() && draw_) { + shaderManagerGL_->Save(shaderCachePath_); + } + framebufferManagerGL_->DestroyAllFBOs(); shaderManagerGL_->ClearCache(true); depalShaderCache_.Clear(); fragmentTestCache_.Clear(); - if (!shaderCachePath_.empty() && draw_) { - shaderManagerGL_->Save(shaderCachePath_); - } + delete shaderManagerGL_; shaderManagerGL_ = nullptr; delete framebufferManagerGL_; @@ -331,11 +333,10 @@ void GPU_GLES::DeviceLost() { // Simply drop all caches and textures. // FBOs appear to survive? Or no? // TransformDraw has registered as a GfxResourceHolder. - drawEngine_.ClearInputLayoutMap(); - shaderManagerGL_->ClearCache(false); + shaderManagerGL_->DeviceLost(); textureCacheGL_->DeviceLost(); - fragmentTestCache_.Clear(false); - depalShaderCache_.Clear(); + fragmentTestCache_.DeviceLost(); + depalShaderCache_.DeviceLost(); drawEngine_.DeviceLost(); framebufferManagerGL_->DeviceLost(); // Don't even try to access the lost device. @@ -349,9 +350,12 @@ void GPU_GLES::DeviceRestore() { UpdateCmdInfo(); UpdateVsyncInterval(true); + shaderManagerGL_->DeviceRestore(draw_); textureCacheGL_->DeviceRestore(draw_); framebufferManagerGL_->DeviceRestore(draw_); - drawEngine_.DeviceRestore(); + drawEngine_.DeviceRestore(draw_); + fragmentTestCache_.DeviceRestore(draw_); + depalShaderCache_.DeviceRestore(draw_); } void GPU_GLES::Reinitialize() { @@ -388,9 +392,10 @@ inline void GPU_GLES::UpdateVsyncInterval(bool force) { if (PSP_CoreParameter().unthrottle) { desiredVSyncInterval = 0; } - if (PSP_CoreParameter().fpsLimit == 1) { + if (PSP_CoreParameter().fpsLimit != FPSLimit::NORMAL) { + int limit = PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM1 ? g_Config.iFpsLimit1 : g_Config.iFpsLimit2; // For an alternative speed that is a clean factor of 60, the user probably still wants vsync. - if (g_Config.iFpsLimit == 0 || (g_Config.iFpsLimit != 15 && g_Config.iFpsLimit != 30 && g_Config.iFpsLimit != 60)) { + if (limit == 0 || (limit >= 0 && limit != 15 && limit != 30 && limit != 60)) { desiredVSyncInterval = 0; } } @@ -455,7 +460,7 @@ void GPU_GLES::CopyDisplayToOutput() { // If buffered, discard the depth buffer of the backbuffer. Don't even know if we need one. #if 0 #ifdef USING_GLES2 - if (gl_extensions.EXT_discard_framebuffer && g_Config.iRenderingMode != 0) { + if (gl_extensions.EXT_discard_framebuffer && g_Config.iRenderingMode != FB_NON_BUFFERED_MODE) { GLenum attachments[] = {GL_DEPTH_EXT, GL_STENCIL_EXT}; glDiscardFramebufferEXT(GL_FRAMEBUFFER, 2, attachments); } diff --git a/GPU/GLES/GPU_GLES.h b/GPU/GLES/GPU_GLES.h index 6789491090..9f70cbec16 100644 --- a/GPU/GLES/GPU_GLES.h +++ b/GPU/GLES/GPU_GLES.h @@ -92,5 +92,4 @@ private: #ifdef _WIN32 int lastVsync_; #endif - int vertexCost_ = 0; }; diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index 3f2834d050..f95bfb73ca 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -583,8 +583,9 @@ void LinkedShader::UpdateUniforms(u32 vertType, const ShaderID &vsid) { } } -ShaderManagerGLES::ShaderManagerGLES(GLRenderManager *render) - : render_(render), lastShader_(nullptr), shaderSwitchDirtyUniforms_(0), diskCacheDirty_(false), fsCache_(16), vsCache_(16) { +ShaderManagerGLES::ShaderManagerGLES(Draw::DrawContext *draw) + : lastShader_(nullptr), shaderSwitchDirtyUniforms_(0), diskCacheDirty_(false), fsCache_(16), vsCache_(16) { + render_ = (GLRenderManager *)draw->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); codeBuffer_ = new char[16384]; lastFSID_.set_invalid(); lastVSID_.set_invalid(); @@ -616,6 +617,14 @@ void ShaderManagerGLES::ClearCache(bool deleteThem) { Clear(); } +void ShaderManagerGLES::DeviceLost() { + Clear(); +} + +void ShaderManagerGLES::DeviceRestore(Draw::DrawContext *draw) { + render_ = (GLRenderManager *)draw->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); +} + void ShaderManagerGLES::DirtyShader() { // Forget the last shader ID lastFSID_.set_invalid(); diff --git a/GPU/GLES/ShaderManagerGLES.h b/GPU/GLES/ShaderManagerGLES.h index d585df45e0..e609e3a744 100644 --- a/GPU/GLES/ShaderManagerGLES.h +++ b/GPU/GLES/ShaderManagerGLES.h @@ -155,7 +155,7 @@ private: class ShaderManagerGLES : public ShaderManagerCommon { public: - ShaderManagerGLES(GLRenderManager *render); + ShaderManagerGLES(Draw::DrawContext *draw); ~ShaderManagerGLES(); void ClearCache(bool deleteThem); // TODO: deleteThem currently not respected @@ -165,6 +165,9 @@ public: Shader *ApplyVertexShader(int prim, u32 vertType, VShaderID *VSID); LinkedShader *ApplyFragmentShader(VShaderID VSID, Shader *vs, u32 vertType, int prim); + void DeviceLost(); + void DeviceRestore(Draw::DrawContext *draw); + void DirtyShader(); void DirtyLastShader() override; // disables vertex arrays diff --git a/GPU/GLES/StencilBufferGLES.cpp b/GPU/GLES/StencilBufferGLES.cpp index 4b59b60b96..d8effd95ae 100644 --- a/GPU/GLES/StencilBufferGLES.cpp +++ b/GPU/GLES/StencilBufferGLES.cpp @@ -16,6 +16,8 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include "gfx_es2/glsl_program.h" +#include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/Reporting.h" #include "GPU/Common/StencilCommon.h" #include "GPU/GLES/FramebufferManagerGLES.h" @@ -126,12 +128,15 @@ bool FramebufferManagerGLES::NotifyStencilUpload(u32 addr, int size, bool skipZe std::vector shaders; shaders.push_back(render_->CreateShader(GL_VERTEX_SHADER, vs_code, "stencil")); shaders.push_back(render_->CreateShader(GL_FRAGMENT_SHADER, fs_code, "stencil")); + std::vector semantics; + semantics.push_back({ 0, "a_position" }); + semantics.push_back({ 1, "a_texcoord0" }); std::vector queries; queries.push_back({ &u_stencilUploadTex, "tex" }); queries.push_back({ &u_stencilValue, "u_stencilValue" }); std::vector inits; inits.push_back({ &u_stencilUploadTex, 0, 0 }); - stencilUploadProgram_ = render_->CreateProgram(shaders, {}, queries, inits, false); + stencilUploadProgram_ = render_->CreateProgram(shaders, semantics, queries, inits, false); for (auto iter : shaders) { render_->DeleteShader(iter); } @@ -146,27 +151,28 @@ bool FramebufferManagerGLES::NotifyStencilUpload(u32 addr, int size, bool skipZe // Our fragment shader (and discard) is slow. Since the source is 1x, we can stencil to 1x. // Then after we're done, we'll just blit it across and stretch it there. - if (dstBuffer->bufferWidth == dstBuffer->renderWidth || !dstBuffer->fbo) { + if (dstBuffer->width == dstBuffer->renderWidth || !dstBuffer->fbo) { useBlit = false; } - u16 w = useBlit ? dstBuffer->bufferWidth : dstBuffer->renderWidth; - u16 h = useBlit ? dstBuffer->bufferHeight : dstBuffer->renderHeight; + u16 w = useBlit ? dstBuffer->width : dstBuffer->renderWidth; + u16 h = useBlit ? dstBuffer->height : dstBuffer->renderHeight; Draw::Framebuffer *blitFBO = nullptr; if (useBlit) { - blitFBO = GetTempFBO(w, h, Draw::FBO_8888); + blitFBO = GetTempFBO(TempFBO::COPY, w, h, Draw::FBO_8888); draw_->BindFramebufferAsRenderTarget(blitFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); } else if (dstBuffer->fbo) { - draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::DONT_CARE }); } render_->SetViewport({ 0, 0, (float)w, (float)h, 0.0f, 1.0f }); float u1 = 1.0f; float v1 = 1.0f; - MakePixelTexture(src, dstBuffer->format, dstBuffer->fb_stride, dstBuffer->bufferWidth, dstBuffer->bufferHeight, u1, v1); + MakePixelTexture(src, dstBuffer->format, dstBuffer->fb_stride, dstBuffer->width, dstBuffer->height, u1, v1); textureCacheGL_->ForgetLastTexture(); // We must bind the program after starting the render pass, and set the color mask after clearing. + render_->SetScissor({ 0, 0, w, h }); render_->SetDepth(false, false, GL_ALWAYS); render_->Clear(0, 0, 0, GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, 0x8, 0, 0, 0, 0); render_->SetStencilFunc(GL_TRUE, GL_ALWAYS, 0xFF, 0xFF); diff --git a/GPU/GLES/TextureCacheGLES.cpp b/GPU/GLES/TextureCacheGLES.cpp index e4975ef0d2..255b3c9193 100644 --- a/GPU/GLES/TextureCacheGLES.cpp +++ b/GPU/GLES/TextureCacheGLES.cpp @@ -328,7 +328,7 @@ void TextureCacheGLES::BindTexture(TexCacheEntry *entry) { lastBoundTexture = entry->textureName; } UpdateSamplingParams(*entry, false); - gstate_c.useShaderDepal = false; + gstate_c.SetUseShaderDepal(false); } void TextureCacheGLES::Unbind() { @@ -459,7 +459,7 @@ void TextureCacheGLES::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFram // Since we started/ended render passes, might need these. gstate_c.Dirty(DIRTY_DEPAL); - gstate_c.useShaderDepal = true; + gstate_c.SetUseShaderDepal(true); gstate_c.depalFramebufferFormat = framebuffer->drawnFormat; const u32 bytesPerColor = clutFormat == GE_CMODE_32BIT_ABGR8888 ? sizeof(u32) : sizeof(u16); const u32 clutTotalColors = clutMaxBytes_ / bytesPerColor; @@ -473,7 +473,7 @@ void TextureCacheGLES::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFram if (depal) { const GEPaletteFormat clutFormat = gstate.getClutPaletteFormat(); GLRTexture *clutTexture = depalShaderCache_->GetClutTexture(clutFormat, clutHash_, clutBuf_); - Draw::Framebuffer *depalFBO = framebufferManagerGL_->GetTempFBO(framebuffer->renderWidth, framebuffer->renderHeight, Draw::FBO_8888); + Draw::Framebuffer *depalFBO = framebufferManagerGL_->GetTempFBO(TempFBO::DEPAL, framebuffer->renderWidth, framebuffer->renderHeight, Draw::FBO_8888); draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); shaderManager_->DirtyLastShader(); @@ -846,6 +846,8 @@ bool TextureCacheGLES::GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level) // Apply texture may need to rebuild the texture if we're about to render, or bind a framebuffer. TexCacheEntry *entry = nextTexture_; + // We might need a render pass to set the sampling params, unfortunately. Otherwise BuildTexture may crash. + framebufferManagerGL_->RebindFramebuffer(); ApplyTexture(); // TODO: Centralize? diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 3c1a8945b3..0b05d24013 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -24,9 +24,10 @@ #include "Core/HLE/sceGe.h" #include "Core/Debugger/Breakpoints.h" #include "Core/MemMapHelpers.h" -#include "GPU/Common/FramebufferCommon.h" -#include "GPU/Common/TextureCacheCommon.h" #include "GPU/Common/DrawEngineCommon.h" +#include "GPU/Common/FramebufferCommon.h" +#include "GPU/Common/SplineCommon.h" +#include "GPU/Common/TextureCacheCommon.h" #include "GPU/Debugger/Record.h" const CommonCommandTableEntry commonCommandTable[] = { @@ -72,7 +73,8 @@ const CommonCommandTableEntry commonCommandTable[] = { { GE_CMD_FOGENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE}, { GE_CMD_TEXMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS | DIRTY_FRAGMENTSHADER_STATE }, { GE_CMD_TEXSHADELS, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE }, - { GE_CMD_SHADEMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE }, + // Raster state for Direct3D 9, uncommon. + { GE_CMD_SHADEMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE }, { GE_CMD_TEXFUNC, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAGMENTSHADER_STATE }, { GE_CMD_COLORTEST, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAGMENTSHADER_STATE }, { GE_CMD_ALPHATESTENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAGMENTSHADER_STATE }, @@ -1735,7 +1737,7 @@ void GPUCommon::Execute_Bezier(u32 op, u32 diff) { bool computeNormals = gstate.isLightingEnabled(); bool patchFacing = gstate.patchfacing & 1; - if (g_Config.bHardwareTessellation && g_Config.bHardwareTransform && !g_Config.bSoftwareRendering) { + if (CanUseHardwareTessellation(patchPrim)) { gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE); gstate_c.bezier = true; if (gstate_c.spline_count_u != bz_ucount) { @@ -1799,7 +1801,7 @@ void GPUCommon::Execute_Spline(u32 op, u32 diff) { bool patchFacing = gstate.patchfacing & 1; u32 vertType = gstate.vertType; - if (g_Config.bHardwareTessellation && g_Config.bHardwareTransform && !g_Config.bSoftwareRendering) { + if (CanUseHardwareTessellation(patchPrim)) { gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE); gstate_c.spline = true; bool countsChanged = gstate_c.spline_count_u != sp_ucount || gstate_c.spline_count_v != sp_vcount; @@ -2610,10 +2612,8 @@ void GPUCommon::DoBlockTransfer(u32 skipDrawReason) { framebufferManager_->NotifyBlockTransferAfter(dstBasePtr, dstStride, dstX, dstY, srcBasePtr, srcStride, srcX, srcY, width, height, bpp, skipDrawReason); } -#ifndef MOBILE_DEVICE CBreakPoints::ExecMemCheck(srcBasePtr + (srcY * srcStride + srcX) * bpp, false, height * srcStride * bpp, currentMIPS->pc); CBreakPoints::ExecMemCheck(dstBasePtr + (dstY * dstStride + dstX) * bpp, true, height * dstStride * bpp, currentMIPS->pc); -#endif // TODO: Correct timing appears to be 1.9, but erring a bit low since some of our other timing is inaccurate. cyclesExecuted += ((height * width * bpp) * 16) / 10; diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 47c6f39170..33abc0126d 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -320,8 +320,8 @@ protected: DisplayList *currentList; DisplayListQueue dlQueue; - bool interruptRunning; - GPURunState gpuState; + bool interruptRunning = false; + GPURunState gpuState = GPUSTATE_RUNNING; bool isbreak; u64 drawCompleteTicks; u64 busyTicks; diff --git a/GPU/GPUInterface.h b/GPU/GPUInterface.h index 101f73e14b..543f757a99 100644 --- a/GPU/GPUInterface.h +++ b/GPU/GPUInterface.h @@ -241,6 +241,7 @@ public: virtual void DumpNextFrame() = 0; virtual void GetReportingInfo(std::string &primaryInfo, std::string &fullInfo) = 0; virtual const std::list& GetDisplayLists() = 0; + // TODO: Currently Qt only, needs to be cleaned up. virtual std::vector GetFramebufferList() = 0; virtual s64 GetListTicks(int listid) = 0; diff --git a/GPU/GPUState.h b/GPU/GPUState.h index 51b78aa35f..06c63f6955 100644 --- a/GPU/GPUState.h +++ b/GPU/GPUState.h @@ -517,6 +517,12 @@ struct GPUStateCache { bool IsDirty(u64 what) const { return (dirty & what) != 0ULL; } + void SetUseShaderDepal(bool depal) { + if (depal != useShaderDepal) { + useShaderDepal = depal; + Dirty(DIRTY_FRAGMENTSHADER_STATE); + } + } void SetTextureFullAlpha(bool fullAlpha) { if (fullAlpha != textureFullAlpha) { textureFullAlpha = fullAlpha; diff --git a/GPU/GeDisasm.cpp b/GPU/GeDisasm.cpp index 8acb1ed4ea..d334385691 100644 --- a/GPU/GeDisasm.cpp +++ b/GPU/GeDisasm.cpp @@ -162,9 +162,9 @@ void GeDisassembleOp(u32 pc, u32 op, u32 prev, char *buffer, int bufsize) { int sp_utype = (data >> 16) & 0x3; int sp_vtype = (data >> 18) & 0x3; if (data & 0xF00000) - snprintf(buffer, bufsize, "DRAW SPLINE: %i x %i, %i x %i (extra %x)", sp_ucount, sp_vcount, sp_utype, sp_vtype, data >> 20); + snprintf(buffer, bufsize, "DRAW SPLINE: %i x %i (type %ix%i, extra %x)", sp_ucount, sp_vcount, sp_utype, sp_vtype, data >> 20); else - snprintf(buffer, bufsize, "DRAW SPLINE: %i x %i, %i x %i", sp_ucount, sp_vcount, sp_utype, sp_vtype); + snprintf(buffer, bufsize, "DRAW SPLINE: %i x %i (type %ix%i)", sp_ucount, sp_vcount, sp_utype, sp_vtype); } break; diff --git a/GPU/Null/NullGpu.cpp b/GPU/Null/NullGpu.cpp index a240840496..29a50f2054 100644 --- a/GPU/Null/NullGpu.cpp +++ b/GPU/Null/NullGpu.cpp @@ -344,10 +344,8 @@ void NullGPU::ExecuteOp(u32 op, u32 diff) { memcpy(dst, src, width * bpp); } -#ifndef MOBILE_DEVICE CBreakPoints::ExecMemCheck(srcBasePtr + (srcY * srcStride + srcX) * bpp, false, height * srcStride * bpp, currentMIPS->pc); CBreakPoints::ExecMemCheck(dstBasePtr + (srcY * dstStride + srcX) * bpp, true, height * dstStride * bpp, currentMIPS->pc); -#endif // TODO: Correct timing appears to be 1.9, but erring a bit low since some of our other timing is inaccurate. cyclesExecuted += ((height * width * bpp) * 16) / 10; diff --git a/GPU/Software/Rasterizer.cpp b/GPU/Software/Rasterizer.cpp index dadabf5a3a..2852feff0e 100644 --- a/GPU/Software/Rasterizer.cpp +++ b/GPU/Software/Rasterizer.cpp @@ -28,6 +28,7 @@ #include "Core/Reporting.h" #include "GPU/GPUState.h" +#include "GPU/Common/TextureCacheCommon.h" #include "GPU/Common/TextureDecoder.h" #include "GPU/Software/SoftGpu.h" #include "GPU/Software/Rasterizer.h" @@ -1081,9 +1082,9 @@ static inline void CalculateSamplingParams(const float ds, const float dt, const levelFrac = 0; } - if (g_Config.iTexFiltering == 3) { + if (g_Config.iTexFiltering == TEX_FILTER_LINEAR) { filt = true; - } else if (g_Config.iTexFiltering == 2) { + } else if (g_Config.iTexFiltering == TEX_FILTER_NEAREST) { filt = false; } else { filt = detail > 0 ? gstate.isMinifyFilteringEnabled() : gstate.isMagnifyFilteringEnabled(); diff --git a/GPU/Software/SamplerX86.cpp b/GPU/Software/SamplerX86.cpp index faee56e718..a0c0a3ea91 100644 --- a/GPU/Software/SamplerX86.cpp +++ b/GPU/Software/SamplerX86.cpp @@ -211,11 +211,18 @@ LinearFunc SamplerJitCache::CompileLinear(const SamplerID &id) { CVTDQ2PS(fpScratchReg5, R(fpScratchReg5)); SHUFPS(fpScratchReg5, R(fpScratchReg5), _MM_SHUFFLE(0, 0, 0, 0)); if (RipAccessible(by256)) { - MULPS(fpScratchReg5, M(by256)); // rip accessible + MULPS(fpScratchReg5, M(by256)); } else { - Crash(); // TODO + MOV(PTRBITS, R(tempReg1), ImmPtr(by256)); + MULPS(fpScratchReg5, MatR(tempReg1)); + } + + if (RipAccessible(ones)) { + MOVAPS(XMM0, M(ones)); + } else { + MOV(PTRBITS, R(tempReg1), ImmPtr(ones)); + MOVAPS(XMM0, MatR(tempReg1)); } - MOVAPS(XMM0, M(ones)); SUBPS(XMM0, R(fpScratchReg5)); MULPS(fpScratchReg1, R(XMM0)); @@ -231,8 +238,18 @@ LinearFunc SamplerJitCache::CompileLinear(const SamplerID &id) { MOVD_xmm(fpScratchReg5, MDisp(RSP, 32)); CVTDQ2PS(fpScratchReg5, R(fpScratchReg5)); SHUFPS(fpScratchReg5, R(fpScratchReg5), _MM_SHUFFLE(0, 0, 0, 0)); - MULPS(fpScratchReg5, M(by256)); - MOVAPS(XMM0, M(ones)); + if (RipAccessible(ones)) { + MULPS(fpScratchReg5, M(by256)); + } else { + MOV(PTRBITS, R(tempReg1), ImmPtr(by256)); + MULPS(fpScratchReg5, MatR(tempReg1)); + } + if (RipAccessible(ones)) { + MOVAPS(XMM0, M(ones)); + } else { + MOV(PTRBITS, R(tempReg1), ImmPtr(ones)); + MOVAPS(XMM0, MatR(tempReg1)); + } SUBPS(XMM0, R(fpScratchReg5)); MULPS(fpScratchReg1, R(XMM0)); @@ -556,9 +573,10 @@ bool SamplerJitCache::Jit_Decode4444() { MOVD_xmm(fpScratchReg1, R(resultReg)); PUNPCKLBW(fpScratchReg1, R(fpScratchReg1)); if (RipAccessible(color4444mask)) { - PAND(fpScratchReg1, M(color4444mask)); // rip accessible + PAND(fpScratchReg1, M(color4444mask)); } else { - Crash(); + MOV(PTRBITS, R(tempReg1), ImmPtr(color4444mask)); + PAND(fpScratchReg1, MatR(tempReg1)); } MOVSS(fpScratchReg2, R(fpScratchReg1)); MOVSS(fpScratchReg3, R(fpScratchReg1)); diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index b24d286940..5d3b20c8e3 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -21,6 +21,7 @@ #include "Common/ColorConv.h" #include "Common/GraphicsContext.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/Debugger/Breakpoints.h" #include "Core/Host.h" #include "Core/MemMap.h" @@ -648,10 +649,8 @@ void SoftGPU::ExecuteOp(u32 op, u32 diff) { memcpy(dst, src, width * bpp); } -#ifndef MOBILE_DEVICE CBreakPoints::ExecMemCheck(srcBasePtr + (srcY * srcStride + srcX) * bpp, false, height * srcStride * bpp, currentMIPS->pc); CBreakPoints::ExecMemCheck(dstBasePtr + (srcY * dstStride + srcX) * bpp, true, height * dstStride * bpp, currentMIPS->pc); -#endif // TODO: Correct timing appears to be 1.9, but erring a bit low since some of our other timing is inaccurate. cyclesExecuted += ((height * width * bpp) * 16) / 10; diff --git a/GPU/Vulkan/DepalettizeShaderVulkan.cpp b/GPU/Vulkan/DepalettizeShaderVulkan.cpp index 1cc701a28f..57e61fef65 100644 --- a/GPU/Vulkan/DepalettizeShaderVulkan.cpp +++ b/GPU/Vulkan/DepalettizeShaderVulkan.cpp @@ -22,7 +22,7 @@ #include "GPU/Vulkan/VulkanUtil.h" #include "Common/Vulkan/VulkanImage.h" -static const char depal_vs[] = R"(#version 400 +static const char depal_vs[] = R"(#version 450 #extension GL_ARB_separate_shader_objects : enable #extension GL_ARB_shading_language_420pack : enable layout (location = 0) in vec3 a_position; diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index 2b6d191bad..11f8d376f4 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -167,7 +167,7 @@ void DrawEngineVulkan::InitDeviceObjects() { vertexCache_ = new VulkanPushBuffer(vulkan_, VERTEX_CACHE_SIZE); - tessDataTransfer = new TessellationDataTransferVulkan(vulkan_, draw_); + tessDataTransfer = new TessellationDataTransferVulkan(vulkan_); } DrawEngineVulkan::~DrawEngineVulkan() { @@ -215,11 +215,6 @@ void DrawEngineVulkan::DestroyDeviceObjects() { vulkan_->Delete().QueueDeletePipelineLayout(pipelineLayout_); if (descriptorSetLayout_ != VK_NULL_HANDLE) vulkan_->Delete().QueueDeleteDescriptorSetLayout(descriptorSetLayout_); - if (nullTexture_) { - nullTexture_->Destroy(); - delete nullTexture_; - nullTexture_ = nullptr; - } if (vertexCache_) { vertexCache_->Destroy(vulkan_); delete vertexCache_; @@ -265,29 +260,6 @@ void DrawEngineVulkan::BeginFrame() { // TODO: How can we make this nicer... ((TessellationDataTransferVulkan *)tessDataTransfer)->SetPushBuffer(frame->pushUBO); - if (!nullTexture_) { - VkCommandBuffer cmdInit = (VkCommandBuffer)draw_->GetNativeObject(Draw::NativeObject::INIT_COMMANDBUFFER); - nullTexture_ = new VulkanTexture(vulkan_, textureCache_->GetAllocator()); - nullTexture_->SetTag("Null"); - int w = 8; - int h = 8; - nullTexture_->CreateDirect(cmdInit, w, h, 1, VK_FORMAT_A8B8G8R8_UNORM_PACK32, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); - uint32_t bindOffset; - VkBuffer bindBuf; - uint32_t *data = (uint32_t *)frame->pushUBO->Push(w * h * 4, &bindOffset, &bindBuf); - for (int y = 0; y < h; y++) { - for (int x = 0; x < w; x++) { - // data[y*w + x] = ((x ^ y) & 1) ? 0xFF808080 : 0xFF000000; // gray/black checkerboard - data[y*w + x] = 0; // black - } - } - nullTexture_->UploadMip(cmdInit, 0, w, h, bindBuf, bindOffset, w); - nullTexture_->EndCreate(cmdInit); - } else { - nullTexture_->Touch(); - } - DirtyAllUBOs(); // Wipe the vertex cache if it's grown too large. @@ -605,7 +577,8 @@ void DrawEngineVulkan::DoFlush() { GEPrimitiveType prim = prevPrim_; - bool useHWTransform = CanUseHardwareTransform(prim); + // Always use software for flat shading to fix the provoking index. + bool useHWTransform = CanUseHardwareTransform(prim) && (tess || gstate.getShadeMode() != GE_SHADE_FLAT); VulkanVertexShader *vshader = nullptr; VulkanFragmentShader *fshader = nullptr; @@ -799,19 +772,19 @@ void DrawEngineVulkan::DoFlush() { textureCache_->ApplyTexture(); textureCache_->GetVulkanHandles(imageView, sampler); if (imageView == VK_NULL_HANDLE) - imageView = nullTexture_->GetImageView(); + imageView = (VkImageView)draw_->GetNativeObject(Draw::NativeObject::NULL_IMAGEVIEW); if (sampler == VK_NULL_HANDLE) sampler = nullSampler_; } - if (!lastPipeline_ || !gstate_c.IsDirty(DIRTY_BLEND_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE) || prim != lastPrim_) { + if (!lastPipeline_ || gstate_c.IsDirty(DIRTY_BLEND_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE) || prim != lastPrim_) { shaderManager_->GetShaders(prim, lastVType_, &vshader, &fshader, true); // usehwtransform _dbg_assert_msg_(G3D, vshader->UseHWTransform(), "Bad vshader"); if (prim != lastPrim_ || gstate_c.IsDirty(DIRTY_BLEND_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE)) { ConvertStateToVulkanKey(*framebufferManager_, shaderManager_, prim, pipelineKey_, dynState_); } - Draw::NativeObject object = g_Config.iRenderingMode != 0 ? Draw::NativeObject::FRAMEBUFFER_RENDERPASS : Draw::NativeObject::BACKBUFFER_RENDERPASS; + Draw::NativeObject object = g_Config.iRenderingMode != FB_NON_BUFFERED_MODE ? Draw::NativeObject::FRAMEBUFFER_RENDERPASS : Draw::NativeObject::BACKBUFFER_RENDERPASS; VkRenderPass renderPass = (VkRenderPass)draw_->GetNativeObject(object); VulkanPipeline *pipeline = pipelineManager_->GetOrCreatePipeline(pipelineLayout_, renderPass, pipelineKey_, &dec_->decFmt, vshader, fshader, true); if (!pipeline || !pipeline->pipeline) { @@ -887,10 +860,11 @@ void DrawEngineVulkan::DoFlush() { params.transformedExpanded = transformedExpanded; params.fbman = framebufferManager_; params.texCache = textureCache_; - // We have to force drawing of primitives if g_Config.iRenderingMode == 0 (non-buffered) because Vulkan clears + // We have to force drawing of primitives if g_Config.iRenderingMode == FB_NON_BUFFERED_MODE because Vulkan clears // do not respect scissor rects. - params.allowClear = g_Config.iRenderingMode != 0; + params.allowClear = g_Config.iRenderingMode != FB_NON_BUFFERED_MODE; params.allowSeparateAlphaClear = false; + params.provokeFlatFirst = true; int maxIndex = indexGen.MaxIndex(); SoftwareTransform( @@ -905,7 +879,7 @@ void DrawEngineVulkan::DoFlush() { textureCache_->ApplyTexture(); textureCache_->GetVulkanHandles(imageView, sampler); if (imageView == VK_NULL_HANDLE) - imageView = nullTexture_->GetImageView(); + imageView = (VkImageView)draw_->GetNativeObject(Draw::NativeObject::NULL_IMAGEVIEW); if (sampler == VK_NULL_HANDLE) sampler = nullSampler_; } @@ -915,7 +889,7 @@ void DrawEngineVulkan::DoFlush() { if (prim != lastPrim_ || gstate_c.IsDirty(DIRTY_BLEND_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE)) { ConvertStateToVulkanKey(*framebufferManager_, shaderManager_, prim, pipelineKey_, dynState_); } - Draw::NativeObject object = g_Config.iRenderingMode != 0 ? Draw::NativeObject::FRAMEBUFFER_RENDERPASS : Draw::NativeObject::BACKBUFFER_RENDERPASS; + Draw::NativeObject object = g_Config.iRenderingMode != FB_NON_BUFFERED_MODE ? Draw::NativeObject::FRAMEBUFFER_RENDERPASS : Draw::NativeObject::BACKBUFFER_RENDERPASS; VkRenderPass renderPass = (VkRenderPass)draw_->GetNativeObject(object); VulkanPipeline *pipeline = pipelineManager_->GetOrCreatePipeline(pipelineLayout_, renderPass, pipelineKey_, &dec_->decFmt, vshader, fshader, false); if (!pipeline || !pipeline->pipeline) { @@ -1020,8 +994,8 @@ void DrawEngineVulkan::UpdateUBOs(FrameData *frame) { } } -DrawEngineVulkan::TessellationDataTransferVulkan::TessellationDataTransferVulkan(VulkanContext *vulkan, Draw::DrawContext *draw) - : TessellationDataTransfer(), vulkan_(vulkan), draw_(draw) { +DrawEngineVulkan::TessellationDataTransferVulkan::TessellationDataTransferVulkan(VulkanContext *vulkan) + : TessellationDataTransfer(), vulkan_(vulkan) { } DrawEngineVulkan::TessellationDataTransferVulkan::~TessellationDataTransferVulkan() { @@ -1038,7 +1012,7 @@ void DrawEngineVulkan::TessellationDataTransferVulkan::PrepareBuffers(float *&po float color[4]; }; - int ssboAlignment = vulkan_->GetPhysicalDeviceProperties().limits.minStorageBufferOffsetAlignment; + int ssboAlignment = vulkan_->GetPhysicalDeviceProperties(vulkan_->GetCurrentPhysicalDevice()).limits.minStorageBufferOffsetAlignment; uint8_t *data = (uint8_t *)push_->PushAligned(size * sizeof(TessData), &offset_, &buf_, ssboAlignment); range_ = size * sizeof(TessData); diff --git a/GPU/Vulkan/DrawEngineVulkan.h b/GPU/Vulkan/DrawEngineVulkan.h index 0aec77065e..494a4ccd4b 100644 --- a/GPU/Vulkan/DrawEngineVulkan.h +++ b/GPU/Vulkan/DrawEngineVulkan.h @@ -180,7 +180,10 @@ public: void SetLineWidth(float lineWidth); void SetDepalTexture(VkImageView depal) { - boundDepal_ = depal; + if (boundDepal_ != depal) { + boundDepal_ = depal; + gstate_c.Dirty(DIRTY_FRAGMENTSHADER_STATE); + } } private: @@ -264,8 +267,7 @@ private: VkImageView imageView = VK_NULL_HANDLE; VkSampler sampler = VK_NULL_HANDLE; - // Null texture - VulkanTexture *nullTexture_ = nullptr; + // For null texture VkSampler nullSampler_ = VK_NULL_HANDLE; DrawEngineVulkanStats stats_; @@ -278,7 +280,7 @@ private: // Hardware tessellation class TessellationDataTransferVulkan : public TessellationDataTransfer { public: - TessellationDataTransferVulkan(VulkanContext *vulkan, Draw::DrawContext *draw); + TessellationDataTransferVulkan(VulkanContext *vulkan); ~TessellationDataTransferVulkan(); void SetPushBuffer(VulkanPushBuffer *push) { push_ = push; } @@ -297,10 +299,8 @@ private: private: VulkanContext *vulkan_; - Draw::DrawContext *draw_; VulkanPushBuffer *push_; // Updated each frame. - int size_ = 0; uint32_t offset_ = 0; uint32_t range_ = 0; VkBuffer buf_ = VK_NULL_HANDLE; diff --git a/GPU/Vulkan/FragmentShaderGeneratorVulkan.cpp b/GPU/Vulkan/FragmentShaderGeneratorVulkan.cpp index 10a2571979..feb83cc62b 100644 --- a/GPU/Vulkan/FragmentShaderGeneratorVulkan.cpp +++ b/GPU/Vulkan/FragmentShaderGeneratorVulkan.cpp @@ -35,9 +35,11 @@ #include "GPU/GPUState.h" static const char *vulkan_glsl_preamble = - "#version 400\n" + "#version 450\n" "#extension GL_ARB_separate_shader_objects : enable\n" - "#extension GL_ARB_shading_language_420pack : enable\n\n"; + "#extension GL_ARB_shading_language_420pack : enable\n" + "#extension GL_ARB_conservative_depth : enable\n" + "#extension GL_ARB_shader_image_load_store : enable\n\n"; #define WRITE p+=sprintf @@ -81,6 +83,13 @@ bool GenerateVulkanGLSLFragmentShader(const FShaderID &id, char *buffer) { bool isModeClear = id.Bit(FS_BIT_CLEARMODE); const char *shading = doFlatShading ? "flat" : ""; + bool earlyFragmentTests = !enableAlphaTest && !enableColorTest && !gstate_c.Supports(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT); + + if (earlyFragmentTests) { + WRITE(p, "layout (early_fragment_tests) in;\n"); + } else { + WRITE(p, "layout (depth_unchanged) out float gl_FragDepth;\n"); + } WRITE(p, "layout (std140, set = 0, binding = 3) uniform baseUBO {\n%s} base;\n", ub_baseStr); if (doTexture) { @@ -176,29 +185,37 @@ bool GenerateVulkanGLSLFragmentShader(const FShaderID &id, char *buffer) { doTextureProjection = false; } - if (doTextureProjection) { - WRITE(p, " vec4 t = textureProj(tex, %s);\n", texcoord); - if (shaderDepal) { - WRITE(p, " vec4 t1 = textureProjOffset(tex, %s, ivec2(1, 0));\n", texcoord); - WRITE(p, " vec4 t2 = textureProjOffset(tex, %s, ivec2(0, 1));\n", texcoord); - WRITE(p, " vec4 t3 = textureProjOffset(tex, %s, ivec2(1, 1));\n", texcoord); + if (!shaderDepal) { + if (doTextureProjection) { + WRITE(p, " vec4 t = textureProj(tex, %s);\n", texcoord); + } else { + WRITE(p, " vec4 t = texture(tex, %s.xy);\n", texcoord); } } else { - WRITE(p, " vec4 t = texture(tex, %s.xy);\n", texcoord); - if (shaderDepal) { - WRITE(p, " vec4 t1 = textureOffset(tex, %s.xy, ivec2(1, 0));\n", texcoord); - WRITE(p, " vec4 t2 = textureOffset(tex, %s.xy, ivec2(0, 1));\n", texcoord); - WRITE(p, " vec4 t3 = textureOffset(tex, %s.xy, ivec2(1, 1));\n", texcoord); + if (doTextureProjection) { + // We don't use textureProj because we need better control and it's probably not much of a savings anyway. + WRITE(p, " vec2 uv = %s.xy/%s.z;\n vec2 uv_round;\n", texcoord, texcoord); + } else { + WRITE(p, " vec2 uv = %s.xy;\n vec2 uv_round;\n", texcoord); } - } - - if (shaderDepal) { + WRITE(p, " vec2 tsize = textureSize(tex, 0);\n"); + WRITE(p, " vec2 fraction;\n"); + WRITE(p, " bool bilinear = (base.depal_mask_shift_off_fmt >> 31) != 0;\n"); + WRITE(p, " if (bilinear) {\n"); + WRITE(p, " uv_round = uv * tsize - vec2(0.5, 0.5);\n"); + WRITE(p, " fraction = fract(uv_round);\n"); + WRITE(p, " uv_round = (uv_round - fraction + vec2(0.5, 0.5)) / tsize;\n"); // We want to take our four point samples at pixel centers. + WRITE(p, " } else {\n"); + WRITE(p, " uv_round = uv;\n"); + WRITE(p, " }\n"); + WRITE(p, " vec4 t = texture(tex, uv_round);\n"); + WRITE(p, " vec4 t1 = textureOffset(tex, uv_round, ivec2(1, 0));\n"); + WRITE(p, " vec4 t2 = textureOffset(tex, uv_round, ivec2(0, 1));\n"); + WRITE(p, " vec4 t3 = textureOffset(tex, uv_round, ivec2(1, 1));\n"); WRITE(p, " uint depalMask = (base.depal_mask_shift_off_fmt & 0xFF);\n"); WRITE(p, " uint depalShift = (base.depal_mask_shift_off_fmt >> 8) & 0xFF;\n"); WRITE(p, " uint depalOffset = ((base.depal_mask_shift_off_fmt >> 16) & 0xFF) << 4;\n"); WRITE(p, " uint depalFmt = (base.depal_mask_shift_off_fmt >> 24) & 0x3;\n"); - WRITE(p, " bool bilinear = (base.depal_mask_shift_off_fmt >> 31) != 0;\n"); - WRITE(p, " vec2 fraction = fract(%s.xy * vec2(textureSize(tex, 0).xy));\n", texcoord); WRITE(p, " uvec4 col; uint index0; uint index1; uint index2; uint index3;\n"); WRITE(p, " switch (depalFmt) {\n"); // We might want to include fmt in the shader ID if this is a performance issue. WRITE(p, " case 0:\n"); // 565 @@ -226,33 +243,33 @@ bool GenerateVulkanGLSLFragmentShader(const FShaderID &id, char *buffer) { WRITE(p, " }\n"); WRITE(p, " break;\n"); WRITE(p, " case 2:\n"); // 4444 - WRITE(p, " col = uvec4(t.rgba * vec4(15.99, 15.99, 15.99, 15.99));\n"); + WRITE(p, " col = uvec4(t.rgba * 15.99);\n"); WRITE(p, " index0 = (col.a << 12) | (col.b << 8) | (col.g << 4) | (col.r);\n"); WRITE(p, " if (bilinear) {\n"); - WRITE(p, " col = uvec4(t1.rgba * vec4(15.99, 15.99, 15.99, 15.99));\n"); + WRITE(p, " col = uvec4(t1.rgba * 15.99);\n"); WRITE(p, " index1 = (col.a << 12) | (col.b << 8) | (col.g << 4) | (col.r);\n"); - WRITE(p, " col = uvec4(t2.rgba * vec4(15.99, 15.99, 15.99, 15.99));\n"); + WRITE(p, " col = uvec4(t2.rgba * 15.99);\n"); WRITE(p, " index2 = (col.a << 12) | (col.b << 8) | (col.g << 4) | (col.r);\n"); - WRITE(p, " col = uvec4(t3.rgba * vec4(15.99, 15.99, 15.99, 15.99));\n"); + WRITE(p, " col = uvec4(t3.rgba * 15.99);\n"); WRITE(p, " index3 = (col.a << 12) | (col.b << 8) | (col.g << 4) | (col.r);\n"); WRITE(p, " }\n"); WRITE(p, " break;\n"); WRITE(p, " case 3:\n"); // 8888 - WRITE(p, " col = uvec4(t.rgba * vec4(255.99, 255.99, 255.99, 255.99));\n"); + WRITE(p, " col = uvec4(t.rgba * 255.99);\n"); WRITE(p, " index0 = (col.a << 24) | (col.b << 16) | (col.g << 8) | (col.r);\n"); WRITE(p, " if (bilinear) {\n"); - WRITE(p, " col = uvec4(t1.rgba * vec4(255.99, 255.99, 255.99, 255.99));\n"); + WRITE(p, " col = uvec4(t1.rgba * 255.99);\n"); WRITE(p, " index1 = (col.a << 24) | (col.b << 16) | (col.g << 8) | (col.r);\n"); - WRITE(p, " col = uvec4(t2.rgba * vec4(255.99, 255.99, 255.99, 255.99));\n"); + WRITE(p, " col = uvec4(t2.rgba * 255.99);\n"); WRITE(p, " index2 = (col.a << 24) | (col.b << 16) | (col.g << 8) | (col.r);\n"); - WRITE(p, " col = uvec4(t3.rgba * vec4(255.99, 255.99, 255.99, 255.99));\n"); + WRITE(p, " col = uvec4(t3.rgba * 255.99);\n"); WRITE(p, " index3 = (col.a << 24) | (col.b << 16) | (col.g << 8) | (col.r);\n"); WRITE(p, " }\n"); WRITE(p, " break;\n"); WRITE(p, " };\n"); WRITE(p, " index0 = ((index0 >> depalShift) & depalMask) | depalOffset;\n"); WRITE(p, " t = texelFetch(pal, ivec2(index0, 0), 0);\n"); - WRITE(p, " if (bilinear) {\n"); + WRITE(p, " if (bilinear && !(index0 == index1 && index1 == index2 && index2 == index3)) {\n"); WRITE(p, " index1 = ((index1 >> depalShift) & depalMask) | depalOffset;\n"); WRITE(p, " index2 = ((index2 >> depalShift) & depalMask) | depalOffset;\n"); WRITE(p, " index3 = ((index3 >> depalShift) & depalMask) | depalOffset;\n"); @@ -566,6 +583,10 @@ bool GenerateVulkanGLSLFragmentShader(const FShaderID &id, char *buffer) { WRITE(p, " z /= 2000.0;\n"); // Nasty hack to completely remove flicker in Heroes Phantasia } WRITE(p, " gl_FragDepth = z;\n"); + } else if (!earlyFragmentTests) { + // Adreno (and possibly MESA/others) apply early frag tests even with discard in the shader. + // Writing depth prevents the bug, even with depth_unchanged specified. + WRITE(p, " gl_FragDepth = gl_FragCoord.z;\n"); } WRITE(p, "}\n"); diff --git a/GPU/Vulkan/FramebufferVulkan.cpp b/GPU/Vulkan/FramebufferVulkan.cpp index 73495acca5..9665019ffa 100644 --- a/GPU/Vulkan/FramebufferVulkan.cpp +++ b/GPU/Vulkan/FramebufferVulkan.cpp @@ -35,6 +35,7 @@ #include "Core/Host.h" #include "Core/MemMap.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/System.h" #include "Core/Reporting.h" #include "Core/HLE/sceDisplay.h" @@ -56,7 +57,7 @@ #include "GPU/Vulkan/ShaderManagerVulkan.h" #include "GPU/Vulkan/VulkanUtil.h" -static const char tex_fs[] = R"(#version 400 +static const char tex_fs[] = R"(#version 450 #extension GL_ARB_separate_shader_objects : enable #extension GL_ARB_shading_language_420pack : enable layout (binding = 0) uniform sampler2D sampler0; @@ -67,7 +68,7 @@ void main() { } )"; -static const char tex_vs[] = R"(#version 400 +static const char tex_vs[] = R"(#version 450 #extension GL_ARB_separate_shader_objects : enable #extension GL_ARB_shading_language_420pack : enable layout (location = 0) in vec3 a_position; @@ -80,18 +81,9 @@ void main() { } )"; -void ConvertFromRGBA8888_Vulkan(u8 *dst, const u8 *src, u32 dstStride, u32 srcStride, u32 width, u32 height, GEBufferFormat format); - FramebufferManagerVulkan::FramebufferManagerVulkan(Draw::DrawContext *draw, VulkanContext *vulkan) : FramebufferManagerCommon(draw), - vulkan_(vulkan), - drawPixelsTex_(nullptr), - drawPixelsTexFormat_(GE_FORMAT_INVALID), - convBuf_(nullptr), - convBufSize_(0), - textureCacheVulkan_(nullptr), - shaderManagerVulkan_(nullptr), - pipelinePostShader_(VK_NULL_HANDLE) { + vulkan_(vulkan) { InitDeviceObjects(); @@ -217,7 +209,7 @@ void FramebufferManagerVulkan::MakePixelTexture(const u8 *srcPixels, GEBufferFor // TODO: We can just change the texture format and flip some bits around instead of this. // Could share code with the texture cache perhaps. - // Could also convert directly into the pushbuffer easily. + // TODO: Could also convert directly into the pushbuffer easily. const uint8_t *data = srcPixels; if (srcPixelFormat != GE_FORMAT_8888 || srcStride != width) { u32 neededSize = width * height * 4; @@ -228,38 +220,25 @@ void FramebufferManagerVulkan::MakePixelTexture(const u8 *srcPixels, GEBufferFor } data = convBuf_; for (int y = 0; y < height; y++) { + const u16_le *src16 = (const u16_le *)srcPixels + srcStride * y; + const u32_le *src32 = (const u32_le *)srcPixels + srcStride * y; + u32 *dst = (u32 *)convBuf_ + width * y; switch (srcPixelFormat) { case GE_FORMAT_565: - { - const u16 *src = (const u16 *)srcPixels + srcStride * y; - u8 *dst = convBuf_ + 4 * width * y; - ConvertRGBA565ToRGBA8888((u32 *)dst, src, width); - } - break; + ConvertRGBA565ToRGBA8888((u32 *)dst, src16, width); + break; case GE_FORMAT_5551: - { - const u16 *src = (const u16 *)srcPixels + srcStride * y; - u8 *dst = convBuf_ + 4 * width * y; - ConvertRGBA5551ToRGBA8888((u32 *)dst, src, width); - } - break; + ConvertRGBA5551ToRGBA8888((u32 *)dst, src16, width); + break; case GE_FORMAT_4444: - { - const u16 *src = (const u16 *)srcPixels + srcStride * y; - u8 *dst = convBuf_ + 4 * width * y; - ConvertRGBA4444ToRGBA8888((u32 *)dst, src, width); - } - break; + ConvertRGBA4444ToRGBA8888((u32 *)dst, src16, width); + break; case GE_FORMAT_8888: - { - const u8 *src = srcPixels + srcStride * 4 * y; - u8 *dst = convBuf_ + 4 * width * y; - memcpy(dst, src, 4 * width); - } - break; + memcpy(dst, src32, 4 * width); + break; case GE_FORMAT_INVALID: _dbg_assert_msg_(G3D, false, "Invalid pixelFormat passed to DrawPixels()."); @@ -435,7 +414,7 @@ VkImageView FramebufferManagerVulkan::BindFramebufferAsColorTexture(int stage, V // Currently rendering to this framebuffer. Need to make a copy. if (!skipCopy && framebuffer == currentRenderVfb_) { // TODO: Maybe merge with bvfbs_? Not sure if those could be packing, and they're created at a different size. - Draw::Framebuffer *renderCopy = GetTempFBO(framebuffer->renderWidth, framebuffer->renderHeight, (Draw::FBColorDepth)framebuffer->colorDepth); + Draw::Framebuffer *renderCopy = GetTempFBO(TempFBO::COPY, framebuffer->renderWidth, framebuffer->renderHeight, (Draw::FBColorDepth)framebuffer->colorDepth); if (renderCopy) { VirtualFramebuffer copyInfo = *framebuffer; copyInfo.fbo = renderCopy; @@ -459,7 +438,7 @@ VkImageView FramebufferManagerVulkan::BindFramebufferAsColorTexture(int stage, V bool FramebufferManagerVulkan::CreateDownloadTempBuffer(VirtualFramebuffer *nvfb) { nvfb->colorDepth = Draw::FBO_8888; - nvfb->fbo = draw_->CreateFramebuffer({ nvfb->bufferWidth, nvfb->height, 1, 1, true, (Draw::FBColorDepth)nvfb->colorDepth }); + nvfb->fbo = draw_->CreateFramebuffer({ nvfb->bufferWidth, nvfb->bufferHeight, 1, 1, true, (Draw::FBColorDepth)nvfb->colorDepth }); if (!(nvfb->fbo)) { ERROR_LOG(FRAMEBUF, "Error creating FBO! %i x %i", nvfb->renderWidth, nvfb->renderHeight); return false; @@ -535,57 +514,6 @@ void FramebufferManagerVulkan::BlitFramebuffer(VirtualFramebuffer *dst, int dstX } } -// TODO: SSE/NEON -// Could also make C fake-simd for 64-bit, two 8888 pixels fit in a register :) -void ConvertFromRGBA8888_Vulkan(u8 *dst, const u8 *src, u32 dstStride, u32 srcStride, u32 width, u32 height, GEBufferFormat format) { - // Must skip stride in the cases below. Some games pack data into the cracks, like MotoGP. - const u32 *src32 = (const u32 *)src; - - if (format == GE_FORMAT_8888) { - u32 *dst32 = (u32 *)dst; - if (src == dst) { - return; - } else { - // Here let's assume they don't intersect - for (u32 y = 0; y < height; ++y) { - memcpy(dst32, src32, width * 4); - src32 += srcStride; - dst32 += dstStride; - } - } - } else { - // But here it shouldn't matter if they do intersect - u16 *dst16 = (u16 *)dst; - switch (format) { - case GE_FORMAT_565: // BGR 565 - for (u32 y = 0; y < height; ++y) { - ConvertRGBA8888ToRGB565(dst16, src32, width); - src32 += srcStride; - dst16 += dstStride; - } - break; - case GE_FORMAT_5551: // ABGR 1555 - for (u32 y = 0; y < height; ++y) { - ConvertBGRA8888ToRGBA5551(dst16, src32, width); - src32 += srcStride; - dst16 += dstStride; - } - break; - case GE_FORMAT_4444: // ABGR 4444 - for (u32 y = 0; y < height; ++y) { - ConvertRGBA8888ToRGBA4444(dst16, src32, width); - src32 += srcStride; - dst16 += dstStride; - } - break; - case GE_FORMAT_8888: - case GE_FORMAT_INVALID: - // Not possible. - break; - } - } -} - void FramebufferManagerVulkan::BeginFrameVulkan() { BeginFrame(); } @@ -624,8 +552,8 @@ void FramebufferManagerVulkan::DestroyAllFBOs() { } bvfbs_.clear(); - for (auto it = tempFBOs_.begin(), end = tempFBOs_.end(); it != end; ++it) { - it->second.fbo->Release(); + for (auto &tempFB : tempFBOs_) { + tempFB.second.fbo->Release(); } tempFBOs_.clear(); diff --git a/GPU/Vulkan/FramebufferVulkan.h b/GPU/Vulkan/FramebufferVulkan.h index 197802e4bc..e7a7fa670d 100644 --- a/GPU/Vulkan/FramebufferVulkan.h +++ b/GPU/Vulkan/FramebufferVulkan.h @@ -24,14 +24,6 @@ #include "GPU/Vulkan/VulkanUtil.h" #include "GPU/Vulkan/DepalettizeShaderVulkan.h" -// TODO: Remove? -enum VulkanFBOColorDepth { - VK_FBO_8888, - VK_FBO_565, - VK_FBO_4444, - VK_FBO_5551, -}; - class TextureCacheVulkan; class DrawEngineVulkan; class VulkanContext; @@ -39,14 +31,6 @@ class ShaderManagerVulkan; class VulkanTexture; class VulkanPushBuffer; -static const char *ub_post_shader = -R"( vec2 texelDelta; - vec2 pixelDelta; - vec4 time; -)"; - -class VulkanPushBuffer; - class FramebufferManagerVulkan : public FramebufferManagerCommon { public: FramebufferManagerVulkan(Draw::DrawContext *draw, VulkanContext *vulkan); @@ -112,9 +96,9 @@ private: u8 *convBuf_ = nullptr; u32 convBufSize_ = 0; - TextureCacheVulkan *textureCacheVulkan_; - ShaderManagerVulkan *shaderManagerVulkan_; - DrawEngineVulkan *drawEngineVulkan_; + TextureCacheVulkan *textureCacheVulkan_ = nullptr; + ShaderManagerVulkan *shaderManagerVulkan_ = nullptr; + DrawEngineVulkan *drawEngineVulkan_ = nullptr; VulkanPushBuffer *push_; enum { diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index 5790fe146e..69cd57feb6 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -175,7 +175,7 @@ GPU_Vulkan::~GPU_Vulkan() { void GPU_Vulkan::CheckGPUFeatures() { uint32_t features = 0; - switch (vulkan_->GetPhysicalDeviceProperties().vendorID) { + switch (vulkan_->GetPhysicalDeviceProperties(vulkan_->GetCurrentPhysicalDevice()).vendorID) { case VULKAN_VENDOR_AMD: // Accurate depth is required on AMD (due to reverse-Z driver bug) so we ignore the compat flag to disable it on those. See #9545 features |= GPU_SUPPORTS_ACCURATE_DEPTH; @@ -183,7 +183,7 @@ void GPU_Vulkan::CheckGPUFeatures() { case VULKAN_VENDOR_ARM: // Also required on older ARM Mali drivers, like the one on many Galaxy S7. if (!PSP_CoreParameter().compat.flags().DisableAccurateDepth || - vulkan_->GetPhysicalDeviceProperties().driverVersion <= VK_MAKE_VERSION(428, 811, 2674)) { + vulkan_->GetPhysicalDeviceProperties(vulkan_->GetCurrentPhysicalDevice()).driverVersion <= VK_MAKE_VERSION(428, 811, 2674)) { features |= GPU_SUPPORTS_ACCURATE_DEPTH; } break; @@ -212,7 +212,7 @@ void GPU_Vulkan::CheckGPUFeatures() { features |= GPU_SUPPORTS_DEPTH_CLAMP; } if (vulkan_->GetFeaturesEnabled().dualSrcBlend) { - switch (vulkan_->GetPhysicalDeviceProperties().vendorID) { + switch (vulkan_->GetPhysicalDeviceProperties(vulkan_->GetCurrentPhysicalDevice()).vendorID) { // We thought we had a bug here on nVidia but turns out we accidentally #ifdef-ed out crucial // code on Android. case VULKAN_VENDOR_INTEL: @@ -220,7 +220,7 @@ void GPU_Vulkan::CheckGPUFeatures() { break; case VULKAN_VENDOR_AMD: // See issue #10074, and also #10065 (AMD) and #10109 for the choice of the driver version to check for - if (vulkan_->GetPhysicalDeviceProperties().driverVersion >= 0x00407000) + if (vulkan_->GetPhysicalDeviceProperties(vulkan_->GetCurrentPhysicalDevice()).driverVersion >= 0x00407000) features |= GPU_SUPPORTS_DUALSOURCE_BLEND; break; default: @@ -310,7 +310,7 @@ void GPU_Vulkan::EndHostFrame() { // Needs to be called on GPU thread, not reporting thread. void GPU_Vulkan::BuildReportingInfo() { - const auto &props = vulkan_->GetPhysicalDeviceProperties(); + const auto &props = vulkan_->GetPhysicalDeviceProperties(vulkan_->GetCurrentPhysicalDevice()); const auto &features = vulkan_->GetFeaturesAvailable(); #define CHECK_BOOL_FEATURE(n) do { if (features.n) { featureNames += ", " #n; } } while (false) diff --git a/GPU/Vulkan/PipelineManagerVulkan.cpp b/GPU/Vulkan/PipelineManagerVulkan.cpp index 4498dedb78..83cdf0ee28 100644 --- a/GPU/Vulkan/PipelineManagerVulkan.cpp +++ b/GPU/Vulkan/PipelineManagerVulkan.cpp @@ -676,7 +676,7 @@ bool PipelineManagerVulkan::LoadCache(FILE *file, bool loadRawPipelineCache, Sha WARN_LOG(G3D, "Bad Vulkan pipeline cache header - ignoring"); return false; } - if (0 != memcmp(header->uuid, vulkan_->GetPhysicalDeviceProperties().pipelineCacheUUID, VK_UUID_SIZE)) { + if (0 != memcmp(header->uuid, vulkan_->GetPhysicalDeviceProperties(vulkan_->GetCurrentPhysicalDevice()).pipelineCacheUUID, VK_UUID_SIZE)) { // Wrong hardware/driver/etc. WARN_LOG(G3D, "Bad Vulkan pipeline cache UUID - ignoring"); return false; diff --git a/GPU/Vulkan/ShaderManagerVulkan.cpp b/GPU/Vulkan/ShaderManagerVulkan.cpp index c7f93ed540..3a06fc2d0d 100644 --- a/GPU/Vulkan/ShaderManagerVulkan.cpp +++ b/GPU/Vulkan/ShaderManagerVulkan.cpp @@ -159,7 +159,7 @@ std::string VulkanVertexShader::GetShaderString(DebugShaderStringType type) cons ShaderManagerVulkan::ShaderManagerVulkan(VulkanContext *vulkan) : vulkan_(vulkan), lastVShader_(nullptr), lastFShader_(nullptr), fsCache_(16), vsCache_(16) { codeBuffer_ = new char[16384]; - uboAlignment_ = vulkan_->GetPhysicalDeviceProperties().limits.minUniformBufferOffsetAlignment; + uboAlignment_ = vulkan_->GetPhysicalDeviceProperties(vulkan_->GetCurrentPhysicalDevice()).limits.minUniformBufferOffsetAlignment; memset(&ub_base, 0, sizeof(ub_base)); memset(&ub_lights, 0, sizeof(ub_lights)); memset(&ub_bones, 0, sizeof(ub_bones)); @@ -176,7 +176,7 @@ ShaderManagerVulkan::~ShaderManagerVulkan() { void ShaderManagerVulkan::DeviceRestore(VulkanContext *vulkan) { vulkan_ = vulkan; - uboAlignment_ = vulkan_->GetPhysicalDeviceProperties().limits.minUniformBufferOffsetAlignment; + uboAlignment_ = vulkan_->GetPhysicalDeviceProperties(vulkan_->GetCurrentPhysicalDevice()).limits.minUniformBufferOffsetAlignment; } void ShaderManagerVulkan::Clear() { @@ -358,7 +358,7 @@ VulkanFragmentShader *ShaderManagerVulkan::GetFragmentShaderFromModule(VkShaderM // instantaneous. #define CACHE_HEADER_MAGIC 0xff51f420 -#define CACHE_VERSION 13 +#define CACHE_VERSION 14 struct VulkanCacheHeader { uint32_t magic; uint32_t version; diff --git a/GPU/Vulkan/StencilBufferVulkan.cpp b/GPU/Vulkan/StencilBufferVulkan.cpp index febe32c61c..0ec2195e79 100644 --- a/GPU/Vulkan/StencilBufferVulkan.cpp +++ b/GPU/Vulkan/StencilBufferVulkan.cpp @@ -27,17 +27,23 @@ #include "GPU/Vulkan/TextureCacheVulkan.h" #include "GPU/Vulkan/VulkanUtil.h" -static const char *stencil_fs = R"(#version 400 +// This shader references gl_FragDepth to prevent early fragment tests. +// They shouldn't happen since it uses discard, but Adreno detects that incorrectly - see #10634. +static const char *stencil_fs = R"(#version 450 #extension GL_ARB_separate_shader_objects : enable #extension GL_ARB_shading_language_420pack : enable +#extension GL_ARB_conservative_depth : enable +layout (depth_unchanged) out float gl_FragDepth; layout (binding = 0) uniform sampler2D tex; -layout(push_constant) uniform params { +layout (push_constant) uniform params { int u_stencilValue; }; layout (location = 0) in vec2 v_texcoord0; layout (location = 0) out vec4 fragColor0; void main() { + gl_FragDepth = gl_FragCoord.z; + if (u_stencilValue == 0) { fragColor0 = vec4(0.0); } else { @@ -50,7 +56,7 @@ void main() { } )"; -static const char stencil_vs[] = R"(#version 400 +static const char stencil_vs[] = R"(#version 450 #extension GL_ARB_separate_shader_objects : enable #extension GL_ARB_shading_language_420pack : enable layout (location = 0) out vec2 v_texcoord0; diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index 44a967eaf3..048b536365 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -328,7 +328,7 @@ void TextureCacheVulkan::BindTexture(TexCacheEntry *entry) { UpdateSamplingParams(*entry, key); curSampler_ = samplerCache_.GetOrCreateSampler(key); drawEngine_->SetDepalTexture(VK_NULL_HANDLE); - gstate_c.useShaderDepal = false; + gstate_c.SetUseShaderDepal(false); } void TextureCacheVulkan::Unbind() { @@ -344,7 +344,7 @@ void TextureCacheVulkan::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFr DepalShaderVulkan *depalShader = nullptr; uint32_t clutMode = gstate.clutformat & 0xFFFFFF; - bool useShaderDepal = true; + bool useShaderDepal = framebufferManager_->GetCurrentRenderVFB() != framebuffer; if ((entry->status & TexCacheEntry::STATUS_DEPALETTIZE) && !g_Config.bDisableSlowFramebufEffects) { if (useShaderDepal) { @@ -356,9 +356,9 @@ void TextureCacheVulkan::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFr samplerKey.magFilt = false; samplerKey.minFilt = false; samplerKey.mipFilt = false; - // Make sure to update the uniforms. + // Make sure to update the uniforms, and also texture - needs a recheck. gstate_c.Dirty(DIRTY_DEPAL); - gstate_c.useShaderDepal = true; + gstate_c.SetUseShaderDepal(true); gstate_c.depalFramebufferFormat = framebuffer->drawnFormat; const u32 bytesPerColor = clutFormat == GE_CMODE_32BIT_ABGR8888 ? sizeof(u32) : sizeof(u16); const u32 clutTotalColors = clutMaxBytes_ / bytesPerColor; @@ -370,6 +370,8 @@ void TextureCacheVulkan::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFr return; } else { depalShader = depalShaderCache_->GetDepalettizeShader(clutMode, framebuffer->drawnFormat); + drawEngine_->SetDepalTexture(VK_NULL_HANDLE); + gstate_c.SetUseShaderDepal(false); } } if (depalShader) { @@ -377,8 +379,7 @@ void TextureCacheVulkan::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFr const GEPaletteFormat clutFormat = gstate.getClutPaletteFormat(); VulkanTexture *clutTexture = depalShaderCache_->GetClutTexture(clutFormat, clutHash_, clutBuf_); - Draw::Framebuffer *depalFBO = framebufferManager_->GetTempFBO( - framebuffer->renderWidth, framebuffer->renderHeight, Draw::FBO_8888); + Draw::Framebuffer *depalFBO = framebufferManager_->GetTempFBO(TempFBO::DEPAL, framebuffer->renderWidth, framebuffer->renderHeight, Draw::FBO_8888); draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); Vulkan2D::Vertex verts[4] = { @@ -466,6 +467,8 @@ void TextureCacheVulkan::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFr framebufferManager_->RebindFramebuffer(); // TODO: This line should usually not be needed. imageView_ = framebufferManagerVulkan_->BindFramebufferAsColorTexture(0, framebuffer, BINDFBCOLOR_MAY_COPY_WITH_UV | BINDFBCOLOR_APPLY_TEX_OFFSET); + drawEngine_->SetDepalTexture(VK_NULL_HANDLE); + gstate_c.SetUseShaderDepal(false); gstate_c.SetTextureFullAlpha(gstate.getTextureFormat() == GE_TFMT_5650); } @@ -685,7 +688,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { uint32_t bufferOffset; VkBuffer texBuf; // nvidia returns 1 but that can't be healthy... let's align by 16 as a minimum. - int pushAlignment = std::max(16, (int)vulkan_->GetPhysicalDeviceProperties().limits.optimalBufferCopyOffsetAlignment); + int pushAlignment = std::max(16, (int)vulkan_->GetPhysicalDeviceProperties(vulkan_->GetCurrentPhysicalDevice()).limits.optimalBufferCopyOffsetAlignment); void *data = drawEngine_->GetPushBufferForTextureData()->PushAligned(size, &bufferOffset, &texBuf, pushAlignment); if (replaced.Valid()) { replaced.Load(i, data, stride); diff --git a/GPU/Vulkan/VertexShaderGeneratorVulkan.cpp b/GPU/Vulkan/VertexShaderGeneratorVulkan.cpp index 7ae8539683..4d23571660 100644 --- a/GPU/Vulkan/VertexShaderGeneratorVulkan.cpp +++ b/GPU/Vulkan/VertexShaderGeneratorVulkan.cpp @@ -38,7 +38,7 @@ #include "GPU/Vulkan/ShaderManagerVulkan.h" static const char *vulkan_glsl_preamble = -"#version 430\n" +"#version 450\n" "#extension GL_ARB_separate_shader_objects : enable\n" "#extension GL_ARB_shading_language_420pack : enable\n\n"; diff --git a/GPU/ge_constants.h b/GPU/ge_constants.h index 1bba05640d..c0b1bc067e 100644 --- a/GPU/ge_constants.h +++ b/GPU/ge_constants.h @@ -563,7 +563,7 @@ inline GEPrimitiveType PatchPrimToPrim(GEPatchPrimType type) { case GE_PATCHPRIM_TRIANGLES: return GE_PRIM_TRIANGLES; case GE_PATCHPRIM_LINES: return GE_PRIM_LINES; case GE_PATCHPRIM_POINTS: return GE_PRIM_POINTS; - case GE_PATCHPRIM_UNKNOWN: default: return GE_PRIM_KEEP_PREVIOUS; // just something + case GE_PATCHPRIM_UNKNOWN: default: return GE_PRIM_POINTS; // Treated as points. } } diff --git a/Qt/Debugger/ctrldisasmview.cpp b/Qt/Debugger/ctrldisasmview.cpp deleted file mode 100644 index af496e30a8..0000000000 --- a/Qt/Debugger/ctrldisasmview.cpp +++ /dev/null @@ -1,422 +0,0 @@ -#include "ctrldisasmview.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#include "math.h" - -#include "debugger_disasm.h" -#include "Core/Debugger/SymbolMap.h" - -namespace -{ - u32 halfAndHalf(u32 a, u32 b) - { - return ((a>>1)&0x7f7f7f7f) + ((b>>1)&0x7f7f7f7f); - } -} - -CtrlDisAsmView::CtrlDisAsmView(QWidget *parent) : - QWidget(parent) -{ - curAddress=0; - rowHeight=14; - align=2; - selecting=false; - showHex=false; -} - -void CtrlDisAsmView::keyPressEvent(QKeyEvent *e) -{ - int page=(rect().bottom()/rowHeight)/2-1; - - switch (e->key()) - { - case Qt::Key_Down: curAddress += align; break; - case Qt::Key_Up: curAddress -= align; break; - case Qt::Key_PageDown: curAddress += page*align; break; - case Qt::Key_PageUp: curAddress -= page*align; break; - default: QWidget::keyPressEvent(e); break; - } - - update(); -} - -void CtrlDisAsmView::mousePressEvent(QMouseEvent *e) -{ - int x = e->pos().x(); - int y = e->pos().y(); - if (x>16) - { - oldSelection=selection; - selection=yToAddress(y); - bool oldselecting=selecting; - selecting=true; - if (!oldselecting || (selection!=oldSelection)) - redraw(); - } - else - { - debugger->toggleBreakpoint(yToAddress(y)); - parentWindow->Update(); - redraw(); - } -} - -void CtrlDisAsmView::wheelEvent(QWheelEvent* e) -{ - int numDegrees = e->delta() / 8; - int numSteps = numDegrees / 15; - if (e->orientation() == Qt::Vertical) - { - curAddress -= numSteps*align; - update(); - } -} - -void CtrlDisAsmView::redraw() -{ - update(); -} - -void CtrlDisAsmView::contextMenu(const QPoint &pos) -{ - QMenu menu(this); - - QAction *copyAddress = new QAction(tr("Copy &address"), this); - connect(copyAddress, SIGNAL(triggered()), this, SLOT(CopyAddress())); - menu.addAction(copyAddress); - - QAction *copyInstrHex = new QAction(tr("Copy instruction (&hex)"), this); - connect(copyInstrHex, SIGNAL(triggered()), this, SLOT(CopyInstrHex())); - menu.addAction(copyInstrHex); - - QAction *copyInstrDisAsm = new QAction(tr("Copy instruction (&disasm)"), this); - connect(copyInstrDisAsm, SIGNAL(triggered()), this, SLOT(CopyInstrDisAsm())); - menu.addAction(copyInstrDisAsm); - - menu.addSeparator(); - - QAction *runToHere = new QAction(tr("&Run to here"), this); - connect(runToHere, SIGNAL(triggered()), this, SLOT(RunToHere())); - menu.addAction(runToHere); - - QAction *setNextStatement = new QAction(tr("&Set Next Statement"), this); - connect(setNextStatement, SIGNAL(triggered()), this, SLOT(SetNextStatement())); - menu.addAction(setNextStatement); - - QAction *toggleBreakpoint = new QAction(tr("&Toggle breakpoint"), this); - connect(toggleBreakpoint, SIGNAL(triggered()), this, SLOT(ToggleBreakpoint())); - menu.addAction(toggleBreakpoint); - - QAction *followBranch = new QAction(tr("&Follow branch"), this); - connect(followBranch, SIGNAL(triggered()), this, SLOT(FollowBranch())); - menu.addAction(followBranch); - - menu.addSeparator(); - - //QAction *showDynarecResults = new QAction(tr("&Show Dynarec Results"), this); - //connect(showDynarecResults, SIGNAL(triggered()), this, SLOT(ShowDynarecResults())); - //menu.addAction(showDynarecResults); - - QAction *goToMemoryView = new QAction(tr("Go to in &Memory View"), this); - connect(goToMemoryView, SIGNAL(triggered()), this, SLOT(GoToMemoryView())); - menu.addAction(goToMemoryView); - - menu.addSeparator(); - - //QAction *killFunction = new QAction(tr("&Kill function"), this); - //connect(killFunction, SIGNAL(triggered()), this, SLOT(KillFunction())); - //menu.addAction(killFunction); - - QAction *renameFunction = new QAction(tr("&Rename function..."), this); - connect(renameFunction, SIGNAL(triggered()), this, SLOT(RenameFunction())); - menu.addAction(renameFunction); - - - menu.exec( mapToGlobal(pos)); -} - -void CtrlDisAsmView::GoToMemoryView() -{ - parentWindow->ShowMemory(selection); -} - -void CtrlDisAsmView::CopyAddress() -{ - QApplication::clipboard()->setText(QString("%1").arg(selection,8,16,QChar('0'))); -} - -void CtrlDisAsmView::CopyInstrDisAsm() -{ - QApplication::clipboard()->setText(debugger->disasm(selection,align)); -} - -void CtrlDisAsmView::CopyInstrHex() -{ - QApplication::clipboard()->setText(QString("%1").arg(debugger->readMemory(selection),8,16,QChar('0'))); -} - -void CtrlDisAsmView::SetNextStatement() -{ - debugger->setPC(selection); - redraw(); -} - -void CtrlDisAsmView::ToggleBreakpoint() -{ - debugger->toggleBreakpoint(selection); - parentWindow->Update(); - redraw(); -} - -void CtrlDisAsmView::FollowBranch() -{ - const char *temp = debugger->disasm(selection,align);; - const char *mojs=strstr(temp,"->$"); - if (mojs) - { - u32 dest; - sscanf(mojs+3,"%08x",&dest); - if (dest) - { - marker = selection; - gotoAddr(dest); - } - } -} - -void CtrlDisAsmView::RunToHere() -{ - debugger->setBreakpoint(selection); - debugger->runToBreakpoint(); - redraw(); -} - -void CtrlDisAsmView::RenameFunction() -{ - u32 funcBegin = g_symbolMap->GetFunctionStart(curAddress); - if (funcBegin != (u32)-1) - { - QString name = QString::fromStdString(g_symbolMap->GetLabelString(funcBegin)); - bool ok; - QString newname = QInputDialog::getText(this, tr("New function name"), - tr("New function name:"), QLineEdit::Normal, - name, &ok); - if (ok && !newname.isEmpty()) - { - g_symbolMap->SetLabelName(newname.toStdString().c_str(),funcBegin); - redraw(); - parentWindow->NotifyMapLoaded(); - } - } - else - { - QMessageBox::information(this,tr("Warning"),tr("No symbol selected"),QMessageBox::Ok); - } -} - -void CtrlDisAsmView::paintEvent(QPaintEvent *) -{ - QPainter painter(this); - painter.setBrush(Qt::white); - painter.setPen(Qt::white); - painter.drawRect(rect()); - - struct branch - { - int src,dst,srcAddr; - bool conditional; - }; - branch branches[256]; - int numBranches=0; - - int width = rect().width(); - int numRows=(rect().height()/rowHeight); - - QColor bgColor(0xFFFFFFFF); - QPen nullPen(bgColor); - QPen currentPen(QColor(0,0,0)); - QPen selPen(QColor(0xFF808080)); - QPen condPen(QColor(0xFFFF3020)); - - QBrush lbr; - lbr.setColor(bgColor); - QBrush currentBrush(QColor(0xFFFFEfE8)); - QBrush pcBrush(QColor(0xFF70FF70)); - - QFont normalFont("Arial", 10); - QFont boldFont("Arial", 10); - QFont alignedFont("Monospace", 10); - alignedFont.setStyleHint(QFont::Monospace); - boldFont.setBold(true); - painter.setFont(normalFont); - - - QImage breakPoint(":/resources/breakpoint.ico"); - int i; - curAddress&=~(align-1); - - align=(debugger->getInstructionSize(0)); - for (i=0; i<=numRows; i++) - { - unsigned int address=curAddress + (i-(numRows/2))*align; - - int rowY1 = rect().top() + rowHeight*i; - int rowY2 = rect().top() + rowHeight*i + rowHeight - 1; - - lbr.setColor((unsigned int)marker == address ? QColor(0xFFFFEEE0) : QColor(debugger->getColor(address))); - QColor bg = lbr.color(); - painter.setBrush(currentBrush); - painter.setPen(nullPen); - painter.drawRect(0,rowY1,16-1,rowY2-rowY1); - - if (selecting && address == (unsigned int)selection) - painter.setPen(selPen); - else - { - if(i==numRows/2) - painter.setPen(currentPen); - else - painter.setPen(bg); - } - painter.setBrush(QBrush(bg)); - - if (address == debugger->getPC()) - { - painter.setBrush(pcBrush); - } - - painter.drawRect(16,rowY1,width-16-1,rowY2-rowY1); - painter.setBrush(currentBrush); - QPen textPen(QColor(halfAndHalf(bg.rgba(),0))); - painter.setPen(textPen); - painter.setFont(alignedFont); - painter.drawText(17,rowY1-3+rowHeight,QString("%1").arg(address,8,16,QChar('0'))); - painter.setFont(normalFont); - textPen.setColor(QColor(0xFF000000)); - painter.setPen(textPen); - if (debugger->isAlive()) - { - const char *dizz = debugger->disasm(address, align); - char dis[512]; - strcpy(dis, dizz); - char *dis2 = strchr(dis,'\t'); - char desc[256]=""; - if (dis2) - { - *dis2=0; - dis2++; - const char *mojs=strstr(dis2,"->$"); - if (mojs) - { - for (int i=0; i<8; i++) - { - bool found=false; - for (int j=0; j<22; j++) - { - if (mojs[i+3]=="0123456789ABCDEFabcdef"[j]) - found=true; - } - if (!found) - { - mojs=0; - break; - } - } - } - if (mojs) - { - unsigned int offs; - sscanf(mojs+3,"%08x",&offs); - branches[numBranches].src=rowY1 + rowHeight/2; - branches[numBranches].srcAddr=address/align; - branches[numBranches].dst=(int)(rowY1+((s64)offs-(s64)address)*rowHeight/align + rowHeight/2); - branches[numBranches].conditional = (dis[1]!=0); //unconditional 'b' branch - numBranches++; - const char *t = debugger->getDescription(offs).c_str(); - if (memcmp(t,"z_",2)==0) - t+=2; - if (memcmp(t,"zz_",3)==0) - t+=3; - sprintf(desc,"-->%s", t); - textPen.setColor(QColor(0xFF600060)); - painter.setPen(textPen); - } - else - { - textPen.setColor(QColor(0xFF000000)); - painter.setPen(textPen); - } - painter.drawText(149,rowY1-3+rowHeight,QString(dis2)); - } - textPen.setColor(QColor(0xFF007000)); - painter.setPen(textPen); - painter.setFont(boldFont); - painter.drawText(84,rowY1-3+rowHeight,QString(dis)); - painter.setFont(normalFont); - if (desc[0]==0) - { - const char *t = debugger->getDescription(address).c_str(); - if (memcmp(t,"z_",2)==0) - t+=2; - if (memcmp(t,"zz_",3)==0) - t+=3; - strcpy(desc,t); - } - if (memcmp(desc,"-->",3) == 0) - { - textPen.setColor(QColor(0xFF0000FF)); - painter.setPen(textPen); - } - else - { - textPen.setColor(halfAndHalf(halfAndHalf(bg.rgba(),0),bg.rgba())); - painter.setPen(textPen); - } - if (strlen(desc)) - painter.drawText(std::max(280,width/3+190),rowY1-3+rowHeight,QString(desc)); - if (debugger->isBreakpoint(address)) - { - painter.drawImage(2,rowY1+2,breakPoint); - } - } - } - for (i=0; i-200) - { - painter.drawLine(curPos, QPoint(x+2,branches[i].src)); - curPos = QPoint(x+2,branches[i].src); - painter.drawLine(curPos, QPoint(x+2,branches[i].dst)); - curPos = QPoint(x+2,branches[i].dst); - painter.drawLine(curPos, QPoint(x-4,branches[i].dst)); - - curPos = QPoint(x,branches[i].dst-4); - painter.drawLine(curPos, QPoint(x-4,branches[i].dst)); - curPos = QPoint(x-4,branches[i].dst); - painter.drawLine(curPos, QPoint(x+1,branches[i].dst+5)); - } - else - { - painter.drawLine(curPos, QPoint(x+4,branches[i].src)); - } - } -} - -int CtrlDisAsmView::yToAddress(int y) -{ - //int ydiff=y - rect().bottom()/2;//-rowHeight/2; - int ydiff=(int)(floor((float)y / (float)rowHeight)); - ydiff -= (rect().height()/rowHeight)/2; - return curAddress + ydiff *align; -} diff --git a/Qt/Debugger/ctrldisasmview.h b/Qt/Debugger/ctrldisasmview.h deleted file mode 100644 index 8846ad40db..0000000000 --- a/Qt/Debugger/ctrldisasmview.h +++ /dev/null @@ -1,100 +0,0 @@ -#ifndef CTRLDISASMVIEW_H -#define CTRLDISASMVIEW_H - -#include -#include "Core/Debugger/DebugInterface.h" - -class Debugger_Disasm; - -class CtrlDisAsmView : public QWidget -{ - Q_OBJECT -public: - explicit CtrlDisAsmView(QWidget *parent = 0); - - void redraw(); - void setAlign(int l) - { - align=l; - } - - void setParentWindow(Debugger_Disasm* win) - { - parentWindow = win; - } - - void setDebugger(DebugInterface *deb) - { - debugger=deb; - curAddress=debugger->getPC(); - align=debugger->getInstructionSize(0); - } - DebugInterface *getDebugger() - { - return debugger; - } - void gotoAddr(unsigned int addr) - { - curAddress=addr&(~(align-1)); - redraw(); - } - void gotoPC() - { - curAddress=debugger->getPC()&(~(align-1)); - redraw(); - } - unsigned int getSelection() - { - return curAddress; - } - - void setShowMode(bool s) - { - showHex=s; - } - - void toggleBreakpoint() - { - debugger->toggleBreakpoint(curAddress); - redraw(); - } - - void contextMenu(const QPoint& pos); -protected: - void paintEvent(QPaintEvent *); - void mousePressEvent(QMouseEvent *e); - void keyPressEvent(QKeyEvent *); - void wheelEvent(QWheelEvent *e); - -public slots: - void CopyAddress(); - void CopyInstrDisAsm(); - void SetNextStatement(); - void FollowBranch(); - void CopyInstrHex(); - void RunToHere(); - void ToggleBreakpoint(); - void GoToMemoryView(); - void RenameFunction(); - -private: - int yToAddress(int y); - - int curAddress; - int align; - - int rowHeight; - int selection; - int marker; - int oldSelection; - bool selectionChanged; - bool selecting; - bool hasFocus; - bool showHex; - - DebugInterface *debugger; - Debugger_Disasm* parentWindow; - -}; - -#endif // CTRLDISASMVIEW_H diff --git a/Qt/Debugger/ctrlmemview.cpp b/Qt/Debugger/ctrlmemview.cpp deleted file mode 100644 index 65079a0a04..0000000000 --- a/Qt/Debugger/ctrlmemview.cpp +++ /dev/null @@ -1,259 +0,0 @@ -#include "ctrlmemview.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "math.h" - -#include "Core/MemMap.h" -#include "Core/Debugger/SymbolMap.h" - -CtrlMemView::CtrlMemView(QWidget *parent) : - QWidget(parent) -{ - curAddress=0; - rowHeight=14; - align=4; - alignMul=4; - selecting=false; - mode=MV_NORMAL; - debugger = 0; - - setMinimumWidth(500); -} - -void CtrlMemView::redraw() -{ - update(); -} - -void CtrlMemView::wheelEvent(QWheelEvent* e) -{ - int numDegrees = e->delta() / 8; - int numSteps = numDegrees / 15; - if (e->orientation() == Qt::Vertical) - { - curAddress -= numSteps*align*alignMul; - redraw(); - } -} - -void CtrlMemView::keyPressEvent(QKeyEvent *e) -{ - int page=(rect().bottom()/rowHeight)/2-1; - - switch (e->key()) - { - case Qt::Key_Up: curAddress -= align*alignMul; break; - case Qt::Key_Down: curAddress += align*alignMul; break; - case Qt::Key_PageUp: curAddress -= page*align*alignMul; break; - case Qt::Key_PageDown: curAddress += page*align*alignMul; break; - default: QWidget::keyPressEvent(e); break; - } - - redraw(); -} - -void CtrlMemView::paintEvent(QPaintEvent *) -{ - QPainter painter(this); - painter.setBrush(Qt::white); - painter.setPen(Qt::white); - painter.drawRect(rect()); - - if (!debugger) - return; - - int width = rect().width(); - int numRows=(rect().bottom()/rowHeight)/2+1; - - QPen nullPen(0xFFFFFF); - QPen currentPen(0xFF000000); - QPen selPen(0x808080); - QBrush lbr(0xFFFFFF); - QBrush nullBrush(0xFFFFFF); - QBrush currentBrush(0xFFEFE8); - QBrush pcBrush(0x70FF70); - QPen textPen; - - QFont normalFont("Arial", 10); - QFont alignedFont("Monospace", 10); - alignedFont.setStyleHint(QFont::Monospace); - painter.setFont(normalFont); - - int i; - curAddress&=~(align-1); - for (i=-numRows; i<=numRows; i++) - { - unsigned int address=curAddress + i*align*alignMul; - - int rowY1 = rect().bottom()/2 + rowHeight*i - rowHeight/2; - int rowY2 = rect().bottom()/2 + rowHeight*i + rowHeight/2; - - char temp[256]; - - painter.setBrush(currentBrush); - - if (selecting && address == (unsigned int)selection) - painter.setPen(selPen); - else - painter.setPen(i==0 ? currentPen : nullPen); - painter.drawRect(0, rowY1, 16-1, rowY2 - rowY1 - 1); - - painter.drawRect(16, rowY1, width - 16 -1, rowY2 - rowY1 - 1); - painter.setBrush(nullBrush); - textPen.setColor(0x600000); - painter.setPen(textPen); - painter.setFont(alignedFont); - painter.drawText(17,rowY1-2+rowHeight, QString("%1").arg(address,8,16,QChar('0'))); - textPen.setColor(0xFF000000); - painter.setPen(textPen); - if (debugger->isAlive()) - { - - switch(mode) { - case MV_NORMAL: - { - const char *m = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; - if (Memory::IsValidAddress(address)) - { - u32 memory[4] = { - debugger->readMemory(address), - debugger->readMemory(address+4), - debugger->readMemory(address+8), - debugger->readMemory(address+12) - }; - m = (const char*)memory; - sprintf(temp, "%08x %08x %08x %08x ................", - memory[0],memory[1],memory[2],memory[3]); - } - for (int i=0; i<16; i++) - { - int c = (unsigned char)m[i]; - if (c>=32 && c<255) - temp[i+37]=c; - } - } - painter.setFont(alignedFont); - painter.drawText(85,rowY1 - 2 + rowHeight, temp); - break; - - case MV_SYMBOLS: - { -/* textPen.setColor(0x0000FF); - painter.setPen(textPen); - int fn = g_symbolMap->GetSymbolNum(address); - if (fn==-1) - { - sprintf(temp, "%s (ns)", Memory::GetAddressName(address)); - } - else - sprintf(temp, "%s (0x%x b)", g_symbolMap->GetSymbolName(fn),g_symbolMap->GetSymbolSize(fn)); - painter.drawText(205,rowY1 - 2 + rowHeight, temp); - - textPen.setColor(0xFF000000); - painter.setPen(textPen); - - if (align==4) - { - u32 value = Memory::ReadUnchecked_U32(address); - int symbolnum = g_symbolMap->GetSymbolNum(value); - if(symbolnum>=0) - sprintf(temp, "%08x [%s]", value, g_symbolMap->GetSymbolName(symbolnum)); - } - else if (align==2) - { - u16 value = Memory::ReadUnchecked_U16(address); - int symbolnum = g_symbolMap->GetSymbolNum(value); - if(symbolnum>=0) - sprintf(temp, "%04x [%s]", value, g_symbolMap->GetSymbolName(symbolnum)); - } - - painter.drawText(85,rowY1 - 2 + rowHeight, temp);*/ - break; - } - case MV_MAX: break; - } - } - } -} - -void CtrlMemView::mousePressEvent(QMouseEvent *e) -{ - int x = e->pos().x(); - int y = e->pos().y(); - if (x>16) - { - oldSelection=selection; - selection=yToAddress(y); - bool oldselecting=selecting; - selecting=true; - if (!oldselecting || (selection!=oldSelection)) - redraw(); - } -} - -void CtrlMemView::contextMenu(const QPoint &pos) -{ - QMenu menu(this); - - QAction *gotoDisAsm = new QAction(tr("Go to in &disasm"), this); - //connect(gotoDisAsm, SIGNAL(triggered()), this, SLOT(GotoDisAsm())); - menu.addAction(gotoDisAsm); - - menu.addSeparator(); - - QAction *copyValue = new QAction(tr("&Copy value"), this); - connect(copyValue, SIGNAL(triggered()), this, SLOT(CopyValue())); - menu.addAction(copyValue); - - QAction *changeValue = new QAction(tr("C&hange value"), this); - connect(changeValue, SIGNAL(triggered()), this, SLOT(Change())); - menu.addAction(changeValue); - - QAction *dump = new QAction(tr("Dump..."), this); - connect(dump, SIGNAL(triggered()), this, SLOT(Dump())); - menu.addAction(dump); - - menu.exec( mapToGlobal(pos)); -} - -void CtrlMemView::CopyValue() -{ - QApplication::clipboard()->setText(QString("%1").arg(Memory::ReadUnchecked_U32(selection),8,16,QChar('0'))); -} - -void CtrlMemView::Dump() -{ - QMessageBox::information(this,"Sorry","This feature has not been implemented.",QMessageBox::Ok); -} - - -void CtrlMemView::Change() -{ - QString curVal = QString("%1").arg(Memory::ReadUnchecked_U32(selection),8,16,QChar('0')); - - bool ok; - QString text = QInputDialog::getText(this, tr("Set new value"), - tr("Set new value:"), QLineEdit::Normal, - curVal, &ok); - if (ok && !text.isEmpty()) - { - Memory::WriteUnchecked_U32(text.toUInt(0,16),selection); - redraw(); - } -} - - -int CtrlMemView::yToAddress(int y) -{ - int ydiff=y-rect().bottom()/2-rowHeight/2; - ydiff=(int)(floor((float)ydiff / (float)rowHeight))+1; - return curAddress + ydiff * align*alignMul; -} - diff --git a/Qt/Debugger/ctrlmemview.h b/Qt/Debugger/ctrlmemview.h deleted file mode 100644 index e4b52cfe63..0000000000 --- a/Qt/Debugger/ctrlmemview.h +++ /dev/null @@ -1,91 +0,0 @@ -#ifndef CTRLMEMVIEW_H -#define CTRLMEMVIEW_H - -#include "Core/Debugger/DebugInterface.h" -#include - -enum MemViewMode -{ - MV_NORMAL, - MV_SYMBOLS, - MV_MAX -}; - -class CtrlMemView : public QWidget -{ - Q_OBJECT -public: - explicit CtrlMemView(QWidget *parent = 0); - - void setDebugger(DebugInterface *deb) - { - debugger=deb; - if (debugger) - align=debugger->getInstructionSize(0); - } - DebugInterface *getDebugger() - { - return debugger; - } - void redraw(); - - void setMode(MemViewMode m) - { - mode=m; - switch(mode) { - case MV_NORMAL: - alignMul=4; - break; - case MV_SYMBOLS: - alignMul=1; - break; - default: - break; - } - redraw(); - } - - void setAlign(int l) - { - align=l; - } - int yToAddress(int y); - - void gotoAddr(unsigned int addr) - { - curAddress=addr&(~(align-1)); - redraw(); - } - - unsigned int getSelection() - { - return curAddress; - } - void contextMenu(const QPoint &pos); -protected: - void paintEvent(QPaintEvent *); - void keyPressEvent(QKeyEvent *e); - void wheelEvent(QWheelEvent *e); - void mousePressEvent(QMouseEvent *e); - -public slots: - void CopyValue(); - void Dump(); - void Change(); -private: - int curAddress; - int align; - int alignMul; - int rowHeight; - - int selection; - int oldSelection; - bool selectionChanged; - bool selecting; - bool hasFocus; - - DebugInterface *debugger; - MemViewMode mode; -}; - -#endif // CTRLMEMVIEW_H diff --git a/Qt/Debugger/ctrlregisterlist.cpp b/Qt/Debugger/ctrlregisterlist.cpp deleted file mode 100644 index 29184ee1ab..0000000000 --- a/Qt/Debugger/ctrlregisterlist.cpp +++ /dev/null @@ -1,335 +0,0 @@ -#include "ctrlregisterlist.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include "debugger_disasm.h" - -CtrlRegisterList::CtrlRegisterList(QWidget *parent) : - QWidget(parent) -{ - rowHeight = 14; - selecting=false; - selection=0; - category=0; - showHex=false; - cpu=0; - lastPC = 0; - lastCat0Values = NULL; - changedCat0Regs = NULL; - curVertOffset = 0; - -} - -CtrlRegisterList::~CtrlRegisterList() -{ - if (lastCat0Values != NULL) - delete [] lastCat0Values; - if (changedCat0Regs != NULL) - delete [] changedCat0Regs; -} - -void CtrlRegisterList::redraw() -{ - update(); -} - -void CtrlRegisterList::scrollChanged(int action) -{ - QScrollBar *bar = findChild("RegListScroll"); - switch(action) - { - case QScrollBar::SliderSingleStepAdd: - curVertOffset++; - break; - case QScrollBar::SliderSingleStepSub: - curVertOffset--; - break; - case QScrollBar::SliderPageStepAdd: - curVertOffset+= 4; - break; - case QScrollBar::SliderPageStepSub: - curVertOffset-= 4; - break; - case QScrollBar::SliderMove: - curVertOffset = bar->sliderPosition(); - break; - default: - break; - } - redraw(); -} - - -void CtrlRegisterList::keyPressEvent(QKeyEvent *e) -{ - switch (e->key()) - { - case Qt::Key_Down: selection += 1; break; - case Qt::Key_Up: selection -= 1; break; - case Qt::Key_PageDown: selection += 4; break; - case Qt::Key_PageUp: selection -= 4; break; - default: QWidget::keyPressEvent(e); break; - } - - int maxRowsDisplay =rect().bottom()/rowHeight - 1; - curVertOffset = std::min(std::max(curVertOffset, selection-maxRowsDisplay),selection); - update(); -} - -void CtrlRegisterList::mousePressEvent(QMouseEvent *e) -{ - int x = e->pos().x(); - int y = e->pos().y(); - if (x>16) - { - oldSelection=selection; - - if (y>rowHeight) - { - selection=yToIndex(y); - bool oldselecting=selecting; - selecting=true; - if (!oldselecting || (selection!=oldSelection)) - redraw(); - } - else - { - int lastCat = category; - category = (x*cpu->GetNumCategories())/(rect().width()); - if (category<0) category=0; - if (category>=cpu->GetNumCategories()) - category=cpu->GetNumCategories()-1; - if (category!=lastCat) - { - curVertOffset = 0; - redraw(); - } - } - } - else - { - redraw(); - } -} - -void CtrlRegisterList::wheelEvent(QWheelEvent* e) -{ - int numDegrees = e->delta() / 8; - int numSteps = numDegrees / 15; - if (e->orientation() == Qt::Vertical) - { - curVertOffset -= numSteps; - update(); - } -} - - -void CtrlRegisterList::paintEvent(QPaintEvent *) -{ - - int numRowsTotal = cpu->GetNumRegsInCategory(category); - int maxRowsDisplay =rect().bottom()/rowHeight - 1; - - selection = std::min(std::max(selection,0),numRowsTotal); - curVertOffset = std::max(std::min(curVertOffset,numRowsTotal - maxRowsDisplay),0); - - QScrollBar *bar = findChild("RegListScroll"); - if(bar) - { - bar->setMinimum(0); - bar->setMaximum(numRowsTotal - maxRowsDisplay); - bar->setPageStep(1); - bar->setValue(curVertOffset); - } - - - QPainter painter(this); - painter.setBrush(Qt::white); - painter.setPen(Qt::white); - painter.drawRect(rect()); - - if (!cpu) - return; - - QFont normalFont = QFont("Arial", 10); - painter.setFont(normalFont); - - int width = rect().width(); - - QColor bgColor(0xffffff); - QPen nullPen(bgColor); - QPen currentPen(QColor(0xFF000000)); - QPen selPen(0x808080); - QPen textPen; - - QBrush lbr; - lbr.setColor(bgColor); - QBrush nullBrush(bgColor); - QBrush currentBrush(0xFFEfE8); - QBrush pcBrush(0x70FF70); - - int nc = cpu->GetNumCategories(); - for (int i=0; iGetCategoryName(i); - painter.setPen(currentPen); - painter.drawText(width*i/nc+1,-3+rowHeight,name); - } - - int numRows=rect().bottom()/rowHeight; - - for (int i=curVertOffset; iGetPC()) - { - for (int i = 0, n = cpu->GetNumRegsInCategory(0); i < n; ++i) - { - u32 v = cpu->GetRegValue(0, i); - changedCat0Regs[i] = v != lastCat0Values[i]; - lastCat0Values[i] = v; - } - lastPC = cpu->GetPC(); - } - - painter.setBrush(currentBrush); - if (iGetNumRegsInCategory(category)) - { - QString regName = cpu->GetRegName(category,i); - textPen.setColor(0x600000); - painter.setPen(textPen); - painter.drawText(17,rowY1-3+rowHeight,regName); - textPen.setColor(0xFF000000); - painter.setPen(textPen); - - char temp[256]; - cpu->PrintRegValue(category,i,temp); - if (category == 0 && changedCat0Regs[i]) - { - textPen.setColor(0x0000FF); - painter.setPen(textPen); - } - else - { - textPen.setColor(0x004000); - painter.setPen(textPen); - } - painter.drawText(77,rowY1-3+rowHeight,temp); - } - } -} - -int CtrlRegisterList::yToIndex(int y) -{ - int n = (y/rowHeight) - 1 + curVertOffset; - if (n<0) n=0; - return n; -} - -void CtrlRegisterList::contextMenu(const QPoint &pos) -{ - QMenu menu(this); - - QAction *gotoMemory = new QAction(tr("Go to in &memory view"), this); - connect(gotoMemory, SIGNAL(triggered()), this, SLOT(GotoMemory())); - menu.addAction(gotoMemory); - - QAction *gotoDisAsm = new QAction(tr("Go to in &disasm"), this); - connect(gotoDisAsm, SIGNAL(triggered()), this, SLOT(GotoDisAsm())); - menu.addAction(gotoDisAsm); - - menu.addSeparator(); - - QAction *copyValue = new QAction(tr("&Copy value"), this); - connect(copyValue, SIGNAL(triggered()), this, SLOT(CopyValue())); - menu.addAction(copyValue); - - QAction *change = new QAction(tr("C&hange..."), this); - connect(change, SIGNAL(triggered()), this, SLOT(Change())); - menu.addAction(change); - - menu.exec( mapToGlobal(pos)); -} - -void CtrlRegisterList::GotoMemory() -{ - int cat = category; - int reg = selection; - if (selection >= cpu->GetNumRegsInCategory(cat)) - return; - - u32 val = cpu->GetRegValue(cat,reg); - - parentWindow->ShowMemory(val); -} - -void CtrlRegisterList::GotoDisAsm() -{ - int cat = category; - int reg = selection; - if (selection >= cpu->GetNumRegsInCategory(cat)) - return; - - u32 val = cpu->GetRegValue(cat,reg); - - emit GotoDisasm(val); -} - -void CtrlRegisterList::CopyValue() -{ - int cat = category; - int reg = selection; - if (selection >= cpu->GetNumRegsInCategory(cat)) - return; - - u32 val = cpu->GetRegValue(cat,reg); - - QApplication::clipboard()->setText(QString("%1").arg(val,8,16,QChar('0'))); -} - -void CtrlRegisterList::Change() -{ - int cat = category; - int reg = selection; - if (selection >= cpu->GetNumRegsInCategory(cat)) - return; - - u32 val = cpu->GetRegValue(cat,reg); - - bool ok; - QString text = QInputDialog::getText(this, tr("Set new value"), - tr("Set new value:"), QLineEdit::Normal, - QString::number(val), &ok); - if (ok && !text.isEmpty()) - { - cpu->SetRegValue(cat,reg,text.toInt()); - redraw(); - } -} diff --git a/Qt/Debugger/ctrlregisterlist.h b/Qt/Debugger/ctrlregisterlist.h deleted file mode 100644 index ca3c498d9a..0000000000 --- a/Qt/Debugger/ctrlregisterlist.h +++ /dev/null @@ -1,77 +0,0 @@ -#ifndef CTRLREGISTERLIST_H -#define CTRLREGISTERLIST_H - -#include -#include "Core/Debugger/DebugInterface.h" - -class Debugger_Disasm; -class CtrlRegisterList : public QWidget -{ - Q_OBJECT -public: - explicit CtrlRegisterList(QWidget *parent = 0); - - - void setParentWindow(Debugger_Disasm* win) - { - parentWindow = win; - } - - void setCPU(DebugInterface *deb) - { - cpu = deb; - - int regs = cpu->GetNumRegsInCategory(0); - lastCat0Values = new u32[regs]; - changedCat0Regs = new bool[regs]; - memset(lastCat0Values, 0, regs * sizeof(u32)); - memset(changedCat0Regs, 0, regs * sizeof(bool)); - } - DebugInterface *getCPU() - { - return cpu; - } - ~CtrlRegisterList(); - void contextMenu(const QPoint &pos); -protected: - void paintEvent(QPaintEvent *); - void keyPressEvent(QKeyEvent *e); - void mousePressEvent(QMouseEvent *e); - void wheelEvent(QWheelEvent *e); -signals: - void GotoDisasm(u32); - -public slots: - void redraw(); - void scrollChanged(int); - - void GotoDisAsm(); - void CopyValue(); - void Change(); - void GotoMemory(); - -private: - int yToIndex(int y); - - int rowHeight; - int selection; - int marker; - int category; - - int oldSelection; - - bool selectionChanged; - bool selecting; - bool hasFocus; - bool showHex; - DebugInterface *cpu; - int curVertOffset; - - u32 lastPC; - u32 *lastCat0Values; - bool *changedCat0Regs; - - Debugger_Disasm* parentWindow; -}; - -#endif // CTRLREGISTERLIST_H diff --git a/Qt/Debugger/ctrlvfpuview.cpp b/Qt/Debugger/ctrlvfpuview.cpp deleted file mode 100644 index f7622d03ef..0000000000 --- a/Qt/Debugger/ctrlvfpuview.cpp +++ /dev/null @@ -1,93 +0,0 @@ -#include "ctrlvfpuview.h" -#include - -#include "Core/MIPS/MIPS.h" // BAD - -CtrlVfpuView::CtrlVfpuView(QWidget *parent) : - QWidget(parent) -{ - mode = 0; -} - -void CtrlVfpuView::setMode(int newMode) -{ - mode = newMode; - redraw(); -} - -void CtrlVfpuView::paintEvent(QPaintEvent *) -{ - QPainter painter(this); - painter.setBrush(Qt::white); - painter.setPen(Qt::white); - painter.drawRect(rect()); - - if (!cpu) - return; - - QFont normalFont("Arial", 10); - painter.setFont(normalFont); - - QPen currentPen(0xFF000000); - QBrush nullBrush(0xFFEfE8); - painter.setPen(currentPen); - painter.setBrush(nullBrush); - - enum - { - rowHeight = 15, - columnWidth = 80, - xStart = columnWidth/2, - yStart = 0 - }; - - for (int matrix = 0; matrix<8; matrix++) - { - int my = (int)(yStart + matrix * rowHeight * 5.5f); - painter.drawRect(0, my, xStart-1, rowHeight-1); - painter.drawText(3, my+rowHeight-3, QString("M%1").arg(matrix)+"00"); - painter.drawRect(xStart, my+rowHeight, columnWidth*4-1, 4*rowHeight-1); - - for (int column = 0; column<4; column++) - { - int y = my; - int x = column * columnWidth + xStart; - - painter.drawRect(x, y, columnWidth-1, rowHeight - 1); - painter.drawText(x+3, y-3+rowHeight, QString("R%1").arg(matrix)+QString("0%1").arg(column)); - - painter.drawRect(0, y+rowHeight*(column+1), xStart - 1, rowHeight - 1); - painter.drawText(3, y+rowHeight*(column+2)-3, QString("C%1").arg(matrix)+QString("%1").arg(column)+"0"); - - y+=rowHeight; - - for (int row = 0; row<4; row++) - { - float val = mipsr4k.v[column*32+row+matrix*4]; - u32 hex = *((u32*)&val); - char temp[256]; - switch (mode) - { - case 0: sprintf(temp,"%f",val); break; -// case 1: sprintf(temp,"??"); break; - case 2: sprintf(temp,"0x%08x",hex); break; - default:sprintf(temp,"%f",val); break; - } - - painter.drawText(x+3, y-3 + rowHeight, temp); - y+=rowHeight; - } - } - } - - setMinimumHeight((int)(yStart + 8 * rowHeight * 5.5f)); - setMinimumWidth(4*columnWidth+xStart); - -} - - -void CtrlVfpuView::redraw() -{ - update(); -} - diff --git a/Qt/Debugger/ctrlvfpuview.h b/Qt/Debugger/ctrlvfpuview.h deleted file mode 100644 index 5fabb3dd66..0000000000 --- a/Qt/Debugger/ctrlvfpuview.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef CTRLVFPUVIEW_H -#define CTRLVFPUVIEW_H - -#include -#include "Core/Debugger/DebugInterface.h" - -class Debugger_Vfpu; -class CtrlVfpuView : public QWidget -{ - Q_OBJECT -public: - explicit CtrlVfpuView(QWidget *parent = 0); - - void setParentWindow(Debugger_Vfpu* win) - { - parentWindow = win; - } - - void setCPU(DebugInterface *deb) - { - cpu = deb; - } - DebugInterface *getCPU() - { - return cpu; - } - void setMode(int newMode); - void redraw(); -protected: - void paintEvent(QPaintEvent *); - -private: - DebugInterface *cpu; - Debugger_Vfpu* parentWindow; - int mode; -}; - -#endif // CTRLVFPUVIEW_H diff --git a/Qt/Debugger/debugger_disasm.cpp b/Qt/Debugger/debugger_disasm.cpp deleted file mode 100644 index f8e585e23d..0000000000 --- a/Qt/Debugger/debugger_disasm.cpp +++ /dev/null @@ -1,607 +0,0 @@ -#include -#include - -#include - -#include "debugger_disasm.h" -#include "ui_debugger_disasm.h" -#include "Core/Debugger/DebugInterface.h" -#include "Core/Debugger/SymbolMap.h" -#include "ctrldisasmview.h" -#include "Core/Debugger/Breakpoints.h" -#include "Core/HLE/HLE.h" -#include "Core/CoreTiming.h" -#include "mainwindow.h" -#include "ctrlregisterlist.h" -#include "base/stringutil.h" -#include "Core/Debugger/SymbolMap.h" -#include "GPU/GPUState.h" -#include "GPU/GPUInterface.h" -#include "GPU/GeDisasm.h" -#include "GPU/Common/GPUDebugInterface.h" -#include "Core/Host.h" - -Debugger_Disasm::Debugger_Disasm(DebugInterface *_cpu, MainWindow* mainWindow_, QWidget *parent) : - QDialog(parent), - ui(new Ui::Debugger_Disasm), - cpu(_cpu), - mainWindow(mainWindow_) -{ - ui->setupUi(this); - - vfpudlg = new Debugger_VFPU(_cpu, mainWindow, this); - - ui->DisasmView->setWindowTitle(_cpu->GetName()); - - QObject::connect(ui->RegListScroll,SIGNAL(actionTriggered(int)), ui->RegList, SLOT(scrollChanged(int))); - QObject::connect(ui->RegList,SIGNAL(GotoDisasm(u32)),this,SLOT(Goto(u32))); - QObject::connect(this, SIGNAL(UpdateCallstack_()), this, SLOT(UpdateCallstackGUI())); - QObject::connect(this, SIGNAL(UpdateDisplayList_()), this, SLOT(UpdateDisplayListGUI())); - QObject::connect(this, SIGNAL(UpdateBreakpoints_()), this, SLOT(UpdateBreakpointsGUI())); - QObject::connect(this, SIGNAL(UpdateThread_()), this, SLOT(UpdateThreadGUI())); - - CtrlDisAsmView *ptr = ui->DisasmView; - ptr->setDebugger(cpu); - ptr->setParentWindow(this); - ptr->gotoAddr(0x00000000); - - CtrlRegisterList *rl = ui->RegList; - rl->setParentWindow(this); - rl->setCPU(cpu); - - FillFunctions(); - -} - -Debugger_Disasm::~Debugger_Disasm() -{ - delete ui; -} - -void Debugger_Disasm::ShowVFPU() -{ - vfpudlg->show(); -} - -void Debugger_Disasm::Update() -{ - ui->RegList->redraw(); - mainWindow->updateMenus(); - UpdateDialog(); -} - -void Debugger_Disasm::Go() -{ - SetDebugMode(false); - Core_EnableStepping(false); - mainWindow->updateMenus(); -} - -void Debugger_Disasm::Step() -{ - Core_DoSingleStep(); - _dbg_update_(); -} - -void Debugger_Disasm::StepOver() -{ - SetDebugMode(false); - CBreakPoints::AddBreakPoint(cpu->GetPC()+cpu->getInstructionSize(0),true); - _dbg_update_(); - Core_EnableStepping(false); - mainWindow->updateMenus(); -} - -void Debugger_Disasm::StepHLE() -{ - hleDebugBreak(); - SetDebugMode(false); - _dbg_update_(); - Core_EnableStepping(false); - mainWindow->updateMenus(); -} - -void Debugger_Disasm::UpdateDialog() -{ - if(!isVisible()) - return; - ui->DisasmView->setAlign(cpu->getInstructionSize(0)); - ui->DisasmView->redraw(); - ui->RegList->redraw(); - vfpudlg->Update(); - UpdateBreakpoints(); - UpdateThread(); - UpdateDisplayList(); - UpdateCallstack(); - - char tempTicks[24]; - sprintf(tempTicks, "%lld", CoreTiming::GetTicks()); - ui->debugCount->setText(QString("Ctr : ") + tempTicks); - - if(mainWindow->GetDialogMemory()) - mainWindow->GetDialogMemory()->Update(); - -} - -void Debugger_Disasm::Stop() -{ - SetDebugMode(true); - Core_EnableStepping(true); - _dbg_update_(); - mainWindow->updateMenus(); - UpdateDialog(); -} - -void Debugger_Disasm::Skip() -{ - CtrlDisAsmView *ptr = ui->DisasmView; - - cpu->SetPC(cpu->GetPC() + cpu->getInstructionSize(0)); - ptr->gotoPC(); - UpdateDialog(); -} - -void Debugger_Disasm::GotoPC() -{ - CtrlDisAsmView *ptr = ui->DisasmView; - - ptr->gotoPC(); - UpdateDialog(); -} - -void Debugger_Disasm::GotoLR() -{ - CtrlDisAsmView *ptr = ui->DisasmView; - - ptr->gotoAddr(cpu->GetLR()); -} - -void Debugger_Disasm::SetDebugMode(bool _bDebug) -{ - if (_bDebug) - { - ui->Go->setEnabled(true); - ui->StepInto->setEnabled(true); - ui->StepOver->setEnabled(false); // Crash, so disable for now - ui->NextHLE->setEnabled(true); - ui->Stop->setEnabled(false); - ui->Skip->setEnabled(true); - CtrlDisAsmView *ptr = ui->DisasmView; - ptr->gotoPC(); - UpdateDialog(); - } - else - { - ui->Go->setEnabled(false); - ui->StepInto->setEnabled(false); - ui->StepOver->setEnabled(false); - ui->NextHLE->setEnabled(false); - ui->Stop->setEnabled(true); - ui->Skip->setEnabled(false); - CtrlRegisterList *reglist = ui->RegList; - reglist->redraw(); - } -} - -void Debugger_Disasm::Goto(u32 addr) -{ - CtrlDisAsmView *ptr = ui->DisasmView; - ptr->gotoAddr(addr); - ptr->redraw(); -} - -void Debugger_Disasm::on_GotoPc_clicked() -{ - GotoPC(); -} - -void Debugger_Disasm::on_Go_clicked() -{ - Go(); -} - -void Debugger_Disasm::on_Stop_clicked() -{ - Stop(); -} - -void Debugger_Disasm::on_StepInto_clicked() -{ - Step(); -} - -void Debugger_Disasm::on_StepOver_clicked() -{ - StepOver(); -} - -void Debugger_Disasm::on_Skip_clicked() -{ - Skip(); -} - -void Debugger_Disasm::on_NextHLE_clicked() -{ - StepHLE(); -} - -void Debugger_Disasm::on_GotoLr_clicked() -{ - GotoLR(); -} - -void Debugger_Disasm::on_GotoInt_currentIndexChanged(int index) -{ - CtrlDisAsmView *ptr = ui->DisasmView; - u32 addr = ui->GotoInt->itemData(index,Qt::UserRole).toInt(); - if (addr != 0xFFFFFFFF) - ptr->gotoAddr(addr); -} - -void Debugger_Disasm::on_Address_textChanged(const QString &arg1) -{ - CtrlDisAsmView *ptr = ui->DisasmView; - ptr->gotoAddr(parseHex(ui->Address->text().toStdString().c_str())); - UpdateDialog(); -} - -void Debugger_Disasm::on_DisasmView_customContextMenuRequested(const QPoint &pos) -{ - ui->DisasmView->contextMenu(pos); -} - -void Debugger_Disasm::NotifyMapLoaded() -{ - FillFunctions(); - CtrlDisAsmView *ptr = ui->DisasmView; - ptr->redraw(); -} - -void Debugger_Disasm::on_RegList_customContextMenuRequested(const QPoint &pos) -{ - ui->RegList->contextMenu(pos); -} - -void Debugger_Disasm::ShowMemory(u32 addr) -{ - mainWindow->ShowMemory(addr); -} - -void Debugger_Disasm::on_vfpu_clicked() -{ - ShowVFPU(); -} - -void Debugger_Disasm::on_FuncList_itemClicked(QListWidgetItem *item) -{ - u32 addr = item->data(Qt::UserRole).toInt(); - - ui->DisasmView->gotoAddr(addr); -} - -void Debugger_Disasm::FillFunctions() -{ - QListWidgetItem* item = new QListWidgetItem(); - item->setText("(0x02000000)"); - item->setData(Qt::UserRole, 0x02000000); - ui->FuncList->addItem(item); - - std::vector symbols = g_symbolMap->GetAllSymbols(ST_FUNCTION); - for(int i = 0; i < (int)symbols.size(); i++) - { - QListWidgetItem* item = new QListWidgetItem(); - item->setText(QString("%1 (%2)").arg(QString::fromStdString(symbols[i].name)).arg(symbols[i].size)); - item->setData(Qt::UserRole, symbols[i].address); - ui->FuncList->addItem(item); - } -} - -void Debugger_Disasm::UpdateCallstack() -{ - emit UpdateCallstack_(); -} - -void Debugger_Disasm::UpdateCallstackGUI() -{ - - auto threads = GetThreadsInfo(); - - u32 entry = 0, stackTop = 0; - for (size_t i = 0; i < threads.size(); i++) - { - if (threads[i].isCurrent) - { - entry = threads[i].entrypoint; - stackTop = threads[i].initialStack; - break; - } - } - if (entry != 0) { - stackTraceModel = MIPSStackWalk::Walk( - cpu->GetPC(), - cpu->GetRegValue(0,MIPS_REG_RA), - cpu->GetRegValue(0,MIPS_REG_SP), - entry, - stackTop); - } else { - stackTraceModel.clear(); - } - - ui->callStack->clear(); - - QTreeWidgetItem* item; - for(auto it=stackTraceModel.begin();it!=stackTraceModel.end();it++) - { - item = new QTreeWidgetItem(); - item->setText(0,QString("%1").arg(it->pc,8,16,QChar('0')).prepend("0x")); - item->setData(0,Qt::UserRole,it->pc); - item->setText(1,QString("%1").arg(it->entry,8,16,QChar('0')).prepend("0x")); - item->setData(1,Qt::UserRole,it->entry); - item->setText(2,QString("%1").arg(it->sp,8,16,QChar('0')).prepend("0x")); - item->setText(3,QString("%1").arg(it->stackSize,8,16,QChar('0')).prepend("0x")); - ui->callStack->addTopLevelItem(item); - } -} - -void Debugger_Disasm::UpdateBreakpoints() -{ - emit UpdateBreakpoints_(); -} - -void Debugger_Disasm::UpdateBreakpointsGUI() -{ - u32 curBpAddr = 0; - QTreeWidgetItem* curItem = ui->breakpointsList->currentItem(); - if(curItem) - curBpAddr = ui->breakpointsList->currentItem()->data(0,Qt::UserRole).toInt(); - - ui->breakpointsList->clear(); - - auto breakpoints = CBreakPoints::GetBreakpoints(); - for(size_t i = 0; i < breakpoints.size(); i++) - { - u32 addr_ = breakpoints[i].addr; - if(!CBreakPoints::IsTempBreakPoint(addr_)) - { - QTreeWidgetItem* item = new QTreeWidgetItem(); - item->setText(0,QString("%1").arg(addr_,8,16,QChar('0'))); - item->setData(0,Qt::UserRole,addr_); - ui->breakpointsList->addTopLevelItem(item); - if(curBpAddr == addr_) - ui->breakpointsList->setCurrentItem(item); - } - } -} - -void Debugger_Disasm::on_breakpointsList_itemClicked(QTreeWidgetItem *item, int column) -{ - ui->DisasmView->gotoAddr(item->data(column,Qt::UserRole).toInt()); -} - -void Debugger_Disasm::on_breakpointsList_customContextMenuRequested(const QPoint &pos) -{ - QTreeWidgetItem* item = ui->breakpointsList->itemAt(pos); - if(item) - { - breakpointAddr = item->data(0,Qt::UserRole).toInt(); - - QMenu menu(this); - - QAction *removeBP = new QAction(tr("Remove breakpoint"), this); - connect(removeBP, SIGNAL(triggered()), this, SLOT(RemoveBreakpoint())); - menu.addAction(removeBP); - - menu.exec( ui->breakpointsList->mapToGlobal(pos)); - } -} - -void Debugger_Disasm::RemoveBreakpoint() -{ - CBreakPoints::RemoveBreakPoint(breakpointAddr); - Update(); -} - -void Debugger_Disasm::on_clearAllBP_clicked() -{ - CBreakPoints::ClearAllBreakPoints(); - Update(); -} - -void Debugger_Disasm::UpdateThread() -{ - emit UpdateThread_(); -} - -void Debugger_Disasm::UpdateThreadGUI() -{ - ui->threadList->clear(); - - std::vector threads = GetThreadsInfo(); - - for(size_t i = 0; i < threads.size(); i++) - { - QTreeWidgetItem* item = new QTreeWidgetItem(); - item->setText(0,QString::number(threads[i].id)); - item->setData(0,Qt::UserRole,threads[i].id); - item->setText(1,threads[i].name); - QString status = ""; - if(threads[i].status & THREADSTATUS_RUNNING) status += "Running "; - if(threads[i].status & THREADSTATUS_WAIT) status += "Wait "; - if(threads[i].status & THREADSTATUS_READY) status += "Ready "; - if(threads[i].status & THREADSTATUS_SUSPEND) status += "Suspend "; - if(threads[i].status & THREADSTATUS_DORMANT) status += "Dormant "; - if(threads[i].status & THREADSTATUS_DEAD) status += "Dead "; - item->setText(2,status); - item->setText(3,QString("%1").arg(threads[i].curPC,8,16,QChar('0'))); - item->setData(3,Qt::UserRole,threads[i].curPC); - item->setText(4,QString("%1").arg(threads[i].entrypoint,8,16,QChar('0'))); - item->setData(4,Qt::UserRole,threads[i].entrypoint); - - if(threads[i].isCurrent) - { - for(int j = 0; j < 5; j++) - item->setTextColor(j,Qt::green); - } - - ui->threadList->addTopLevelItem(item); - } - for(int i = 0; i < ui->threadList->columnCount(); i++) - ui->threadList->resizeColumnToContents(i); -} - -void Debugger_Disasm::on_threadList_itemClicked(QTreeWidgetItem *item, int column) -{ - ui->DisasmView->gotoAddr(item->data(3,Qt::UserRole).toInt()); -} - -void Debugger_Disasm::on_threadList_customContextMenuRequested(const QPoint &pos) -{ - QTreeWidgetItem* item = ui->threadList->itemAt(pos); - if(item) - { - threadRowSelected = item; - - QMenu menu(this); - - QAction *gotoEntryPoint = new QAction(tr("Go to entry point"), this); - connect(gotoEntryPoint, SIGNAL(triggered()), this, SLOT(GotoThreadEntryPoint())); - menu.addAction(gotoEntryPoint); - - QMenu* changeStatus = menu.addMenu(tr("Change status")); - - QAction *statusRunning = new QAction(tr("Running"), this); - connect(statusRunning, SIGNAL(triggered()), this, SLOT(SetThreadStatusRun())); - changeStatus->addAction(statusRunning); - - QAction *statusWait = new QAction(tr("Wait"), this); - connect(statusWait, SIGNAL(triggered()), this, SLOT(SetThreadStatusWait())); - changeStatus->addAction(statusWait); - - QAction *statusSuspend = new QAction(tr("Suspend"), this); - connect(statusSuspend, SIGNAL(triggered()), this, SLOT(SetThreadStatusSuspend())); - changeStatus->addAction(statusSuspend); - - menu.exec( ui->threadList->mapToGlobal(pos)); - } -} - -void Debugger_Disasm::GotoThreadEntryPoint() -{ - ui->DisasmView->gotoAddr(threadRowSelected->data(4,Qt::UserRole).toInt()); - Update(); -} - -void Debugger_Disasm::SetThreadStatus(ThreadStatus status) -{ - __KernelChangeThreadState(threadRowSelected->data(0,Qt::UserRole).toInt(), status); - - UpdateThread(); -} - -void Debugger_Disasm::SetThreadStatusRun() -{ - SetThreadStatus(THREADSTATUS_RUNNING); -} - -void Debugger_Disasm::SetThreadStatusWait() -{ - SetThreadStatus(THREADSTATUS_WAIT); -} - -void Debugger_Disasm::SetThreadStatusSuspend() -{ - SetThreadStatus(THREADSTATUS_SUSPEND); -} - -void Debugger_Disasm::UpdateDisplayList() -{ - emit UpdateDisplayList_(); -} - -void Debugger_Disasm::UpdateDisplayListGUI() -{ - u32 curDlId = 0; - QTreeWidgetItem* curItem = ui->displayList->currentItem(); - if(curItem) - curDlId = ui->displayList->currentItem()->data(0,Qt::UserRole).toInt(); - - ui->displayList->clear(); - - const std::list& dlQueue = gpu->GetDisplayLists(); - - DisplayList dlist; - DisplayList* dl = &dlist; - if(gpuDebug->GetCurrentDisplayList(dlist)) - { - QTreeWidgetItem* item = new QTreeWidgetItem(); - item->setText(0,QString::number(dl->id)); - item->setData(0, Qt::UserRole, dl->id); - switch(dl->state) - { - case PSP_GE_DL_STATE_NONE: item->setText(1,"None"); break; - case PSP_GE_DL_STATE_QUEUED: item->setText(1,"Queued"); break; - case PSP_GE_DL_STATE_RUNNING: item->setText(1,"Running"); break; - case PSP_GE_DL_STATE_COMPLETED: item->setText(1,"Completed"); break; - case PSP_GE_DL_STATE_PAUSED: item->setText(1,"Paused"); break; - default: break; - } - item->setText(2,QString("%1").arg(dl->startpc,8,16,QChar('0'))); - item->setData(2, Qt::UserRole, dl->startpc); - item->setText(3,QString("%1").arg(dl->pc,8,16,QChar('0'))); - item->setData(3, Qt::UserRole, dl->pc); - ui->displayList->addTopLevelItem(item); - if(curDlId == (u32)dl->id) - { - ui->displayList->setCurrentItem(item); - displayListRowSelected = item; - } - } - - for(auto listIdIt = dlQueue.begin(); listIdIt != dlQueue.end(); ++listIdIt) - { - DisplayList *it = gpu->getList(*listIdIt); - if(dl && it->id == dl->id) - continue; - QTreeWidgetItem* item = new QTreeWidgetItem(); - item->setText(0,QString::number(it->id)); - item->setData(0, Qt::UserRole, it->id); - switch(it->state) - { - case PSP_GE_DL_STATE_NONE: item->setText(1,"None"); break; - case PSP_GE_DL_STATE_QUEUED: item->setText(1,"Queued"); break; - case PSP_GE_DL_STATE_RUNNING: item->setText(1,"Running"); break; - case PSP_GE_DL_STATE_COMPLETED: item->setText(1,"Completed"); break; - case PSP_GE_DL_STATE_PAUSED: item->setText(1,"Paused"); break; - default: break; - } - item->setText(2,QString("%1").arg(it->startpc,8,16,QChar('0'))); - item->setData(2, Qt::UserRole, it->startpc); - item->setText(3,QString("%1").arg(it->pc,8,16,QChar('0'))); - item->setData(3, Qt::UserRole, it->pc); - ui->displayList->addTopLevelItem(item); - if(curDlId == (u32)it->id) - { - ui->displayList->setCurrentItem(item); - displayListRowSelected = item; - } - } - for(int i = 0; i < ui->displayList->columnCount(); i++) - ui->displayList->resizeColumnToContents(i); -} - -void Debugger_Disasm::on_displayList_customContextMenuRequested(const QPoint &pos) -{ - QTreeWidgetItem* item = ui->displayList->itemAt(pos); - if(item) - { - displayListRowSelected = item; - - /*QMenu menu(this); - - QAction *showCode = new QAction(tr("Show code"), this); - connect(showCode, SIGNAL(triggered()), this, SLOT(ShowDLCode())); - menu.addAction(showCode);*/ - - //menu.exec( ui->displayList->mapToGlobal(pos)); - } -} diff --git a/Qt/Debugger/debugger_disasm.h b/Qt/Debugger/debugger_disasm.h deleted file mode 100644 index aa06dc5bc8..0000000000 --- a/Qt/Debugger/debugger_disasm.h +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef DEBUGGER_DISASM_H -#define DEBUGGER_DISASM_H - -#include "Core/MIPS/MIPSStackWalk.h" -#include "Core/HLE/sceKernelThread.h" -#include "Core/Debugger/DebugInterface.h" -#include "debugger_vfpu.h" -#include -#include -#include -#include - -class MainWindow; -namespace Ui { -class Debugger_Disasm; -} - -class Debugger_Disasm : public QDialog -{ - Q_OBJECT - -public: - explicit Debugger_Disasm(DebugInterface *_cpu, MainWindow* mainWindow_, QWidget *parent = 0); - ~Debugger_Disasm(); - void SetDebugMode(bool _bDebug); - - void ShowVFPU(); - void Go(); - void Step(); - void StepOver(); - void StepHLE(); - void Stop(); - void Skip(); - void GotoPC(); - void GotoLR(); - void UpdateDialog(); - void NotifyMapLoaded(); - void Update(); - void ShowMemory(u32 addr); - void FillFunctions(); - void UpdateCallstack(); - void UpdateBreakpoints(); - void UpdateThread(); - void UpdateDisplayList(); - -signals: - void UpdateCallstack_(); - void UpdateDisplayList_(); - void UpdateBreakpoints_(); - void UpdateThread_(); - -public slots: - void Goto(u32 addr); - void RemoveBreakpoint(); - void GotoThreadEntryPoint(); - -private slots: - void UpdateCallstackGUI(); - void UpdateDisplayListGUI(); - void UpdateBreakpointsGUI(); - void UpdateThreadGUI(); - - void on_GotoPc_clicked(); - void on_Go_clicked(); - void on_Stop_clicked(); - void on_StepInto_clicked(); - void on_StepOver_clicked(); - void on_Skip_clicked(); - void on_NextHLE_clicked(); - void on_GotoLr_clicked(); - void on_GotoInt_currentIndexChanged(int index); - void on_Address_textChanged(const QString &arg1); - void on_DisasmView_customContextMenuRequested(const QPoint &pos); - - void on_RegList_customContextMenuRequested(const QPoint &pos); - void on_vfpu_clicked(); - void on_FuncList_itemClicked(QListWidgetItem *item); - void on_breakpointsList_itemClicked(QTreeWidgetItem *item, int column); - void on_breakpointsList_customContextMenuRequested(const QPoint &pos); - void on_clearAllBP_clicked(); - void on_threadList_itemClicked(QTreeWidgetItem *item, int column); - void on_threadList_customContextMenuRequested(const QPoint &pos); - - void SetThreadStatusRun(); - void SetThreadStatusWait(); - void SetThreadStatusSuspend(); - void on_displayList_customContextMenuRequested(const QPoint &pos); - -private: - void SetThreadStatus(ThreadStatus status); - - Ui::Debugger_Disasm *ui; - DebugInterface* cpu; - MainWindow* mainWindow; - Debugger_VFPU* vfpudlg; - u32 breakpointAddr; - QTreeWidgetItem* threadRowSelected; - QTreeWidgetItem* displayListRowSelected; - std::vector stackTraceModel; -}; - -#endif // DEBUGGER_DISASM_H diff --git a/Qt/Debugger/debugger_disasm.ui b/Qt/Debugger/debugger_disasm.ui deleted file mode 100644 index fb3b7e6e17..0000000000 --- a/Qt/Debugger/debugger_disasm.ui +++ /dev/null @@ -1,530 +0,0 @@ - - - Debugger_Disasm - - - - 0 - 0 - 837 - 492 - - - - Qt::ClickFocus - - - Disassembler - - - - - - Qt::Horizontal - - - - - - - Ctr: - - - - - - - - 0 - 0 - - - - &Go to - - - - 0 - - - 4 - - - 4 - - - 0 - - - 0 - - - - - - - - QLayout::SetDefaultConstraint - - - - - - 0 - 0 - - - - &PC - - - - - - - - 0 - 0 - - - - &LR - - - - - - - - 0 - 0 - - - - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Show VFPU - - - - - - - - - 0 - - - - - 0 - 0 - - - - Regs - - - - - - - 0 - 0 - - - - Qt::ClickFocus - - - Qt::CustomContextMenu - - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 120 - 20 - - - - - - - - Qt::Vertical - - - - - - - - - - - Funcs - - - - - - - - - - - - - - - - - - - - 0 - 0 - - - - &Go - - - - - - - - 0 - 0 - - - - Stop - - - - - - - - 0 - 0 - - - - Step &Into - - - - - - - false - - - - 0 - 0 - - - - Step &Over - - - - - - - - 0 - 0 - - - - S&kip - - - - - - - - 0 - 0 - - - - Next &HLE - - - - - - - - - - 0 - 0 - - - - Qt::ClickFocus - - - Qt::CustomContextMenu - - - - - - - 0 - - - - Breakpoints - - - - - - Qt::CustomContextMenu - - - false - - - false - - - 30 - - - - Address - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Clear All - - - - - - - - - - Callstack - - - - - - 4 - - - - Address - - - - - Entry Point - - - - - SP - - - - - length - - - - - - - - - Display Lists - - - - - - Qt::Horizontal - - - - Qt::CustomContextMenu - - - false - - - false - - - false - - - 30 - - - - Id - - - - - Status - - - - - Start Address - - - - - Current Address - - - - - - - - - - Threads - - - - - - Qt::CustomContextMenu - - - false - - - false - - - false - - - 50 - - - - Id - - - - - Name - - - - - Status - - - - - Current PC - - - - - Entry point - - - - - - - - - - - - - - - - - CtrlDisAsmView - QWidget -
ctrldisasmview.h
- 1 -
- - CtrlRegisterList - QWidget -
ctrlregisterlist.h
- 1 -
-
- - -
diff --git a/Qt/Debugger/debugger_displaylist.cpp b/Qt/Debugger/debugger_displaylist.cpp deleted file mode 100644 index 6089c64003..0000000000 --- a/Qt/Debugger/debugger_displaylist.cpp +++ /dev/null @@ -1,1666 +0,0 @@ -#include "debugger_displaylist.h" - -#include -#include -#include - -#include "ui_debugger_displaylist.h" -#include "GPU/GPUInterface.h" -#include "GPU/GeDisasm.h" -#include "Core/Host.h" -#include "base/display.h" -#include "mainwindow.h" -#include "Qt/QtHost.h" - -//commented out until someone bothers to maintain it (see below) -//#include "GPU/GLES/VertexDecoder.h" - -Debugger_DisplayList::Debugger_DisplayList(DebugInterface *_cpu, Draw::DrawContext *draw, MainWindow* mainWindow_, QWidget *parent) : - QDialog(parent), - ui(new Ui::Debugger_DisplayList), - cpu(_cpu), - draw_(draw), - mainWindow(mainWindow_), - currentRenderFrameDisplay(0), - currentTextureDisplay(0), - fboZoomFactor(1), - maxVtxDisplay(20), - maxIdxDisplay(20) -{ - ui->setupUi(this); - - QObject::connect(this, SIGNAL(updateDisplayList_()), this, SLOT(UpdateDisplayListGUI())); - QObject::connect(this, SIGNAL(updateRenderBufferList_()), this, SLOT(UpdateRenderBufferListGUI())); - QObject::connect(this, SIGNAL(updateRenderBuffer_()), this, SLOT(UpdateRenderBufferGUI())); - -} - -Debugger_DisplayList::~Debugger_DisplayList() -{ - delete ui; -} - -void Debugger_DisplayList::UpdateDisplayList() -{ - emit updateDisplayList_(); -} - - -void Debugger_DisplayList::UpdateDisplayListGUI() -{ - u32 curDlId = 0; - QTreeWidgetItem* curItem = ui->displayList->currentItem(); - if(curItem) - curDlId = ui->displayList->currentItem()->data(0,Qt::UserRole).toInt(); - - displayListRowSelected = 0; - ui->displayList->clear(); - ui->displayListData->clear(); - - const std::list& dlQueue = gpu->GetDisplayLists(); - - for(auto listIdIt = dlQueue.begin(); listIdIt != dlQueue.end(); ++listIdIt) - { - DisplayList *it = gpu->getList(*listIdIt); - QTreeWidgetItem* item = new QTreeWidgetItem(); - item->setText(0,QString::number(it->id)); - item->setData(0, Qt::UserRole, it->id); - switch(it->state) - { - case PSP_GE_DL_STATE_NONE: item->setText(1,"None"); break; - case PSP_GE_DL_STATE_QUEUED: item->setText(1,"Queued"); break; - case PSP_GE_DL_STATE_RUNNING: item->setText(1,"Running"); break; - case PSP_GE_DL_STATE_COMPLETED: item->setText(1,"Completed"); break; - case PSP_GE_DL_STATE_PAUSED: item->setText(1,"Paused"); break; - default: break; - } - item->setText(2,QString("%1").arg(it->startpc,8,16,QChar('0'))); - item->setData(2, Qt::UserRole, it->startpc); - item->setText(3,QString("%1").arg(it->pc,8,16,QChar('0'))); - item->setData(3, Qt::UserRole, it->pc); - ui->displayList->addTopLevelItem(item); - if(curDlId == (u32)it->id) - { - ui->displayList->setCurrentItem(item); - displayListRowSelected = item; - ShowDLCode(); - } - } - for(int i = 0; i < ui->displayList->columnCount(); i++) - ui->displayList->resizeColumnToContents(i); - - if (ui->displayList->selectedItems().size() == 0 && ui->displayList->topLevelItemCount() != 0) - { - ui->displayList->setCurrentItem(ui->displayList->topLevelItem(0)); - displayListRowSelected = ui->displayList->topLevelItem(0); - ShowDLCode(); - } -} - - -void Debugger_DisplayList::ShowDLCode() -{ - ui->displayListData->clear(); - ui->displayListData->setColumnCount(4); - - u32 startPc = displayListRowSelected->data(2,Qt::UserRole).toInt(); - u32 curPc = displayListRowSelected->data(3,Qt::UserRole).toInt(); - - std::map data; - GPUgstate listState; - memset(&listState,0,sizeof(GPUgstate)); - drawGPUState.clear(); - vtxBufferSize.clear(); - idxBufferSize.clear(); - - FillDisplayListCmd(data, startPc,0, listState); - - u32 curTexAddr = 0; - u32 curVtxAddr = 0; - u32 curIdxAddr = 0; - - for(std::map::iterator it = data.begin(); it != data.end(); it++) - { - QTreeWidgetItem* item = new QTreeWidgetItem(); - item->setText(0,QString("%1").arg(it->first,8,16,QChar('0'))); - item->setData(0, Qt::UserRole, it->first); - item->setText(1,QString("%1").arg(it->second.cmd,2,16,QChar('0'))); - item->setText(2,QString("%1").arg(it->second.data,6,16,QChar('0'))); - item->setText(3,it->second.comment); - if(curPc == (u32)it->first) - { - curTexAddr = it->second.texAddr; - curVtxAddr = it->second.vtxAddr; - curIdxAddr = it->second.idxAddr; - setCurrentFBO(it->second.fboAddr); - for(int j = 0; j < ui->displayListData->columnCount(); j++) - item->setTextColor(j, Qt::green); - } - if(it->second.implementationNotFinished) - { - for(int j = 0; j < ui->displayListData->columnCount(); j++) - item->setBackgroundColor(j, Qt::red); - } - ui->displayListData->addTopLevelItem(item); - - if(curPc == (u32)it->first) - { - ui->displayListData->setCurrentItem(item); - } - } - for(int j = 0; j < ui->displayListData->columnCount(); j++) - ui->displayListData->resizeColumnToContents(j); - - ui->texturesList->clear(); - ui->vertexData->clear(); - ui->vertexList->clear(); - ui->indexData->clear(); - ui->indexList->clear(); - - std::set usedTexAddr; - std::set usedVtxAddr; - std::set usedIdxAddr; - for(int i = 0; i < (int)drawGPUState.size(); i++) - { - // Textures - QTreeWidgetItem* item = new QTreeWidgetItem(); - u32 texaddr = (drawGPUState[i].texaddr[0] & 0xFFFFF0) | ((drawGPUState[i].texbufwidth[0]<<8) & 0x0F000000); - if(usedTexAddr.find(texaddr) == usedTexAddr.end() && Memory::IsValidAddress(texaddr)) - { - u32 format = drawGPUState[i].texformat & 0xF; - int w = 1 << (drawGPUState[i].texsize[0] & 0xf); - int h = 1 << ((drawGPUState[i].texsize[0]>>8) & 0xf); - - item->setText(0,QString("%1").arg(texaddr,8,16,QChar('0'))); - item->setData(0, Qt::UserRole, i); - item->setText(1,QString::number(w)); - item->setText(2,QString::number(h)); - item->setText(3,QString::number(format,16)); - ui->texturesList->addTopLevelItem(item); - if(curTexAddr == texaddr) - { - ui->texturesList->setCurrentItem(item); - for(int j = 0; j < ui->texturesList->columnCount(); j++) - item->setTextColor(j,Qt::green); - } - usedTexAddr.insert(texaddr); - } - - // Vertex - QTreeWidgetItem* vertexItem = new QTreeWidgetItem(); - u32 baseExtended = ((drawGPUState[i].base & 0x0F0000) << 8) | (drawGPUState[i].vaddr & 0xFFFFFF); - u32 vaddr = ((drawGPUState[i].offsetAddr & 0xFFFFFF) + baseExtended) & 0x0FFFFFFF; - if(drawGPUState[i].vaddr != 0 && Memory::IsValidAddress(vaddr) && usedVtxAddr.find(vaddr) == usedVtxAddr.end()) - { - vertexItem->setText(0, QString("%1").arg(vaddr,8,16,QChar('0'))); - vertexItem->setData(0,Qt::UserRole, i); - if((drawGPUState[i].vertType & GE_VTYPE_THROUGH_MASK) == GE_VTYPE_TRANSFORM) - vertexItem->setText(1, "Transform"); - else - vertexItem->setText(1, "Raw"); - vertexItem->setText(2, QString::number((drawGPUState[i].vertType & GE_VTYPE_MORPHCOUNT_MASK) >> GE_VTYPE_MORPHCOUNT_SHIFT)); - vertexItem->setText(3, QString::number((drawGPUState[i].vertType & GE_VTYPE_WEIGHTCOUNT_MASK) >> GE_VTYPE_WEIGHTCOUNT_SHIFT)); - switch(drawGPUState[i].vertType & GE_VTYPE_WEIGHT_MASK) - { - case GE_VTYPE_WEIGHT_8BIT: vertexItem->setText(4, "8bit"); break; - case GE_VTYPE_WEIGHT_16BIT: vertexItem->setText(4, "16bit"); break; - case GE_VTYPE_WEIGHT_FLOAT: vertexItem->setText(4, "float"); break; - default: vertexItem->setText(4, "No"); break; - } - switch(drawGPUState[i].vertType & GE_VTYPE_POS_MASK) - { - case GE_VTYPE_POS_8BIT: vertexItem->setText(5, "8bit"); break; - case GE_VTYPE_POS_16BIT: vertexItem->setText(5, "16bit"); break; - case GE_VTYPE_POS_FLOAT: vertexItem->setText(5, "float"); break; - default: vertexItem->setText(5, "No"); break; - } - switch(drawGPUState[i].vertType & GE_VTYPE_NRM_MASK) - { - case GE_VTYPE_NRM_8BIT: vertexItem->setText(6, "8bit"); break; - case GE_VTYPE_NRM_16BIT: vertexItem->setText(6, "16bit"); break; - case GE_VTYPE_NRM_FLOAT: vertexItem->setText(6, "float"); break; - default: vertexItem->setText(6, "No"); break; - } - switch(drawGPUState[i].vertType & GE_VTYPE_COL_MASK) - { - case GE_VTYPE_COL_4444: vertexItem->setText(7, "4444"); break; - case GE_VTYPE_COL_5551: vertexItem->setText(7, "5551"); break; - case GE_VTYPE_COL_565: vertexItem->setText(7, "565"); break; - case GE_VTYPE_COL_8888: vertexItem->setText(7, "8888"); break; - default: vertexItem->setText(7, "No"); break; - } - switch(drawGPUState[i].vertType & GE_VTYPE_TC_MASK) - { - case GE_VTYPE_TC_8BIT: vertexItem->setText(8, "8bit"); break; - case GE_VTYPE_TC_16BIT: vertexItem->setText(8, "16bit"); break; - case GE_VTYPE_TC_FLOAT: vertexItem->setText(8, "float"); break; - default: vertexItem->setText(8, "No"); break; - } - - ui->vertexList->addTopLevelItem(vertexItem); - if(curVtxAddr == vaddr) - { - ui->vertexList->setCurrentItem(vertexItem); - for(int j = 0; j < ui->vertexList->columnCount(); j++) - vertexItem->setTextColor(j,Qt::green); - } - usedVtxAddr.insert(vaddr); - } - - - // Index - QTreeWidgetItem* indexItem = new QTreeWidgetItem(); - baseExtended = ((drawGPUState[i].base & 0x0F0000) << 8) | (drawGPUState[i].iaddr & 0xFFFFFF); - u32 iaddr = ((drawGPUState[i].offsetAddr & 0xFFFFFF) + baseExtended) & 0x0FFFFFFF; - if((drawGPUState[i].iaddr & 0xFFFFFF) != 0 && Memory::IsValidAddress(iaddr) && usedIdxAddr.find(iaddr) == usedIdxAddr.end()) - { - indexItem->setText(0, QString("%1").arg(iaddr,8,16,QChar('0'))); - indexItem->setData(0,Qt::UserRole, i); - - ui->indexList->addTopLevelItem(indexItem); - if(curIdxAddr == iaddr) - { - ui->indexList->setCurrentItem(indexItem); - for(int j = 0; j < ui->indexList->columnCount(); j++) - indexItem->setTextColor(j,Qt::green); - } - usedIdxAddr.insert(iaddr); - } - } - - - for(int i = 0; i < ui->texturesList->columnCount(); i++) - ui->texturesList->resizeColumnToContents(i); - for(int i = 0; i < ui->vertexList->columnCount(); i++) - ui->vertexList->resizeColumnToContents(i); - for(int i = 0; i < ui->indexList->columnCount(); i++) - ui->indexList->resizeColumnToContents(i); - - UpdateVertexInfo(); - UpdateIndexInfo(); -} - - -QString Debugger_DisplayList::DisassembleOp(u32 pc, u32 op, u32 prev, const GPUgstate& state) { - u32 cmd = op >> 24; - u32 data = op & 0xFFFFFF; - - // Handle control and drawing commands here directly. The others we delegate. - switch (cmd) - { - case GE_CMD_BASE: - return QString("BASE: %1").arg(data & 0xFFFFFF,6,16,QChar('0')); - - case GE_CMD_VADDR: /// <<8???? - { - u32 baseExtended = ((state.base & 0x0F0000) << 8) | (data & 0xFFFFFF); - baseExtended = (state.offsetAddr + baseExtended) & 0x0FFFFFFF; - return QString("VADDR: %1").arg(baseExtended,6,16,QChar('0')); - } - - case GE_CMD_IADDR: - { - u32 baseExtended = ((state.base & 0x0F0000) << 8) | (data & 0xFFFFFF); - baseExtended = (state.offsetAddr + baseExtended) & 0x0FFFFFFF; - return QString("IADDR: %1").arg(baseExtended,6,16,QChar('0')); - } - - case GE_CMD_PRIM: - { - u32 count = data & 0xFFFF; - u32 type = data >> 16; - static const char* types[7] = { - "POINTS", - "LINES", - "LINE_STRIP", - "TRIANGLES", - "TRIANGLE_STRIP", - "TRIANGLE_FAN", - "RECTANGLES", - }; - return QString("DrawPrim type: %1 count: %2").arg(type < 7 ? types[type] : "INVALID").arg(count); - } - - // The arrow and other rotary items in Puzbob are bezier patches, strangely enough. - case GE_CMD_BEZIER: - { - int bz_ucount = data & 0xFF; - int bz_vcount = (data >> 8) & 0xFF; - return QString("DRAW BEZIER: U=%1 x V=%2").arg(bz_ucount).arg(bz_vcount); - } - - case GE_CMD_SPLINE: - { - int sp_ucount = data & 0xFF; - int sp_vcount = (data >> 8) & 0xFF; - static const char* type[4] = { - "Close/Close", - "Open/Close", - "Close/Open", - "Open/Open" - }; - int sp_utype = (data >> 16) & 0x3; - int sp_vtype = (data >> 18) & 0x3; - return QString("DRAW SPLINE: U=%1 x V=%2, U Type = %3 , V Type = %4").arg(sp_ucount).arg(sp_vcount).arg(type[sp_utype]).arg(type[sp_vtype]); - } - - case GE_CMD_JUMP: - { - u32 target = (((state.base & 0x00FF0000) << 8) | (op & 0xFFFFFC)) & 0x0FFFFFFF; - return QString("CMD JUMP - %1 to %2").arg(pc,8,16,QChar('0')).arg(target,8,16,QChar('0')); - } - - case GE_CMD_CALL: - { - u32 retval = pc + 4; - u32 baseExtended = ((state.base & 0x0F0000) << 8) | (op & 0xFFFFFF); - u32 target = (state.offsetAddr + baseExtended) & 0x0FFFFFFF; - return QString("CMD CALL - %1 to %2, ret=%3").arg(pc,8,16,QChar('0')).arg(target,8,16,QChar('0')).arg(retval,8,16,QChar('0')); - } - - case GE_CMD_RET: - return QString("CMD RET"); - - case GE_CMD_SIGNAL: - return QString("GE_CMD_SIGNAL %1").arg(data,6,16,QChar('0')); - - case GE_CMD_FINISH: - return QString("CMD FINISH %1").arg(data,6,16,QChar('0')); - - case GE_CMD_END: - switch (prev >> 24) - { - case GE_CMD_SIGNAL: - { - // TODO: see http://code.google.com/p/jpcsp/source/detail?r=2935# - int behaviour = (prev >> 16) & 0xFF; - int signal = prev & 0xFFFF; - int enddata = data & 0xFFFF; - // We should probably defer to sceGe here, no sense in implementing this stuff in every GPU - switch (behaviour) { - case 1: // Signal with Wait - return QString("Signal with Wait UNIMPLEMENTED! signal/end: %1 %2").arg(signal,4,16,QChar('0')).arg(enddata,4,16,QChar('0')); - case 2: - return QString("Signal without wait. signal/end: %1 %2").arg(signal,4,16,QChar('0')).arg(enddata,4,16,QChar('0')); - case 3: - return QString("Signal with Pause UNIMPLEMENTED! signal/end: %1 %2").arg(signal,4,16,QChar('0')).arg(enddata,4,16,QChar('0')); - case 0x10: - return QString("Signal with Jump UNIMPLEMENTED! signal/end: %1 %2").arg(signal,4,16,QChar('0')).arg(enddata,4,16,QChar('0')); - case 0x11: - return QString("Signal with Call UNIMPLEMENTED! signal/end: %1 %2").arg(signal,4,16,QChar('0')).arg(enddata,4,16,QChar('0')); - case 0x12: - return QString("Signal with Return UNIMPLEMENTED! signal/end: %1 %2").arg(signal,4,16,QChar('0')).arg(enddata,4,16,QChar('0')); - default: - return QString("UNKNOWN Signal UNIMPLEMENTED %1 ! signal/end: %1 %2").arg(behaviour).arg(signal,4,16,QChar('0')).arg(enddata,4,16,QChar('0')); - } - } - break; - case GE_CMD_FINISH: - break; - default: - return QString("Ah, not finished: %1").arg(prev & 0xFFFFFF,6,16,QChar('0')); - } - return "CMD END"; - - case GE_CMD_BJUMP: - { - // bounding box jump. Let's just not jump, for now. - u32 target = (((state.base & 0x00FF0000) << 8) | (op & 0xFFFFFC)) & 0x0FFFFFFF; - return QString("BBOX JUMP - %1 to %2").arg(pc,8,16,QChar('0')).arg(target,8,16,QChar('0')); - } - case GE_CMD_BOUNDINGBOX: - // bounding box test. Let's do nothing. - return QString("BBOX TEST - number : %1").arg(data & 0xFFFF,4,16,QChar('0')); - - case GE_CMD_ORIGIN: - return QString("Origin: %1").arg(data,6,16,QChar('0')); - - case GE_CMD_VERTEXTYPE: - { - const char* format[4] = - { - "No", - "8 Bits fixed", - "16 Bits fixed", - "Float" - }; - const char* colFormat[8] = - { - "No", - "", - "", - "", - "16-bit BGR-5650", - "16-bit ABGR-5551", - "16-bit ABGR-4444", - "32-bit ABGR-8888" - }; - QString retString = "SetVertexType:"; - - u32 transform = data & GE_VTYPE_THROUGH_MASK; - retString += QString(" Transform : %1").arg(transform==0?"Transformed":"Raw"); - - u32 numVertMorph = (data & GE_VTYPE_MORPHCOUNT_MASK) >> GE_VTYPE_MORPHCOUNT_SHIFT; - retString += QString(", Num Vtx Morph : %1").arg(numVertMorph); - - u32 numWeight = (data & GE_VTYPE_WEIGHTCOUNT_MASK) >> GE_VTYPE_WEIGHTCOUNT_SHIFT; - retString += QString(", Num Weights : %1").arg(numWeight); - - u32 indexFmt = (data & GE_VTYPE_IDX_MASK) >> GE_VTYPE_IDX_SHIFT; - retString += QString(", Index Format : %1").arg(format[indexFmt]); - - u32 weightFmt = (data & GE_VTYPE_WEIGHT_MASK) >> GE_VTYPE_WEIGHT_SHIFT; - retString += QString(", Weight Format : %1").arg(format[weightFmt]); - - u32 posFmt = (data & GE_VTYPE_POS_MASK) >> GE_VTYPE_POS_SHIFT; - retString += QString(", Position Format : %1").arg(format[posFmt]); - - u32 nrmFmt = (data & GE_VTYPE_NRM_MASK) >> GE_VTYPE_NRM_SHIFT; - retString += QString(", Normal Format : %1").arg(format[nrmFmt]); - - u32 colFmt = (data & GE_VTYPE_COL_MASK) >> GE_VTYPE_COL_SHIFT; - retString += QString(", Color Format : %1").arg(colFormat[colFmt]); - - u32 tcFmt = (data & GE_VTYPE_TC_MASK) >> GE_VTYPE_TC_SHIFT; - retString += QString(", Texture UV Format : %1").arg(format[tcFmt]); - - return retString; - } - case GE_CMD_OFFSETADDR: - return QString("OffsetAddr: %1").arg(data,6,16,QChar('0')); - break; - - case GE_CMD_REGION1: - { - int x1 = data & 0x3ff; - int y1 = data >> 10; - //topleft - return QString("Region TL: %1 %2").arg(x1).arg(y1); - } - - case GE_CMD_REGION2: - { - int x2 = data & 0x3ff; - int y2 = data >> 10; - return QString("Region BR: %1 %2").arg(x2).arg(y2); - } - - case GE_CMD_CLIPENABLE: - return QString("Clip Enable: %1").arg(data); - - case GE_CMD_CULLFACEENABLE: - return QString("CullFace Enable: %1").arg(data); - - case GE_CMD_TEXTUREMAPENABLE: - return QString("Texture map enable: %1").arg(data); - - case GE_CMD_LIGHTINGENABLE: - return QString("Lighting enable: %1").arg(data); - - case GE_CMD_FOGENABLE: - return QString("Fog Enable: %1").arg(data); - - case GE_CMD_DITHERENABLE: - return QString("Dither Enable: %1").arg(data); - - case GE_CMD_OFFSETX: - return QString("Offset X: %1").arg(data); - - case GE_CMD_OFFSETY: - return QString("Offset Y: %1").arg(data); - - case GE_CMD_TEXSCALEU: - return QString("Texture U Scale: %1").arg(getFloat24(data)); - - case GE_CMD_TEXSCALEV: - return QString("Texture V Scale: %1").arg(getFloat24(data)); - - case GE_CMD_TEXOFFSETU: - return QString("Texture U Offset: %1").arg(getFloat24(data)); - - case GE_CMD_TEXOFFSETV: - return QString("Texture V Offset: %1").arg(getFloat24(data)); - - case GE_CMD_SCISSOR1: - { - int x1 = data & 0x3ff; - int y1 = data >> 10; - return QString("Scissor TL: %1, %2").arg(x1).arg(y1); - } - case GE_CMD_SCISSOR2: - { - int x2 = data & 0x3ff; - int y2 = data >> 10; - return QString("Scissor BR: %1, %2").arg(x2).arg(y2); - } - - case GE_CMD_MINZ: - { - float zMin = getFloat24(data) / 65535.f; - return QString("MinZ: %1").arg(zMin); - } - - case GE_CMD_MAXZ: - { - float zMax = getFloat24(data) / 65535.f; - return QString("MaxZ: %1").arg(zMax); - } - - case GE_CMD_FRAMEBUFPTR: - { - return QString("FramebufPtr: %1").arg(data,8,16,QChar('0')); - } - - case GE_CMD_FRAMEBUFWIDTH: - { - return QString("FramebufWidth: %1").arg(data); - } - - case GE_CMD_FRAMEBUFPIXFORMAT: - { - const char* fmt[4] = - { - "16-bit BGR 5650", - "16-bit ABGR 5551", - "16-bit ABGR 4444", - "32-bit ABGR 8888" - }; - return QString("FramebufPixeFormat: %1").arg(fmt[data]); - } - case GE_CMD_TEXADDR0: - case GE_CMD_TEXADDR1: - case GE_CMD_TEXADDR2: - case GE_CMD_TEXADDR3: - case GE_CMD_TEXADDR4: - case GE_CMD_TEXADDR5: - case GE_CMD_TEXADDR6: - case GE_CMD_TEXADDR7: - return QString("Texture address %1: %2").arg(cmd-GE_CMD_TEXADDR0).arg(data,6,16,QChar('0')); - - case GE_CMD_TEXBUFWIDTH0: - case GE_CMD_TEXBUFWIDTH1: - case GE_CMD_TEXBUFWIDTH2: - case GE_CMD_TEXBUFWIDTH3: - case GE_CMD_TEXBUFWIDTH4: - case GE_CMD_TEXBUFWIDTH5: - case GE_CMD_TEXBUFWIDTH6: - case GE_CMD_TEXBUFWIDTH7: - return QString("Texture BUFWIDTHess %1: %2 width : %3").arg(cmd-GE_CMD_TEXBUFWIDTH0).arg(data,6,16,QChar('0')).arg(data & 0xFFFF); - - case GE_CMD_CLUTADDR: - return QString("CLUT base addr: %1").arg(data,6,16,QChar('0')); - - case GE_CMD_CLUTADDRUPPER: - return QString("CLUT addr upper %1").arg(data,8,16,QChar('0')); - - case GE_CMD_LOADCLUT: - // This could be used to "dirty" textures with clut. - return QString("Clut load, numColor : %1").arg(data*8); - - case GE_CMD_TEXMAPMODE: - { - const char* texMapMode[3] = - { - "Texture Coordinates (UV)", - "Texture Matrix", - "Environment Map" - }; - const char* texProjMode[4] = - { - "Position", - "Texture Coordinates", - "Normalized Normal", - "Normal" - }; - return QString("Tex map mode: Map mode : %1, Proj Mode : %2").arg(texMapMode[data & 0x3]).arg(texProjMode[(data >> 8) & 0x3]); - } - case GE_CMD_TEXSHADELS: - return QString("Tex shade light sources: %1").arg(data,6,16,QChar('0')); - - case GE_CMD_CLUTFORMAT: - { - const char* fmt[4] = - { - "16-bit BGR 5650", - "16-bit ABGR 5551", - "16-bit ABGR 4444", - "32-bit ABGR 8888" - }; - return QString("Clut format: %1 , Fmt : %2").arg(data,6,16,QChar('0')).arg(fmt[data & 0x3]); - } - - case GE_CMD_TRANSFERSRC: - { - return QString("Block Transfer Src: %1").arg(data,6,16,QChar('0')); - // Nothing to do, the next one prints - } - - case GE_CMD_TRANSFERSRCW: - { - u32 xferSrc = state.transfersrc | ((data&0xFF0000)<<8); - u32 xferSrcW = state.transfersrcw & 1023; - return QString("Block Transfer Src: %1 W: %2").arg(xferSrc,8,16,QChar('0')).arg(xferSrcW); - } - - case GE_CMD_TRANSFERDST: - { - // Nothing to do, the next one prints - return QString("Block Transfer Dst: %1").arg(data,6,16,QChar('0')); - } - - case GE_CMD_TRANSFERDSTW: - { - u32 xferDst= state.transferdst | ((data&0xFF0000)<<8); - u32 xferDstW = state.transferdstw & 1023; - return QString("Block Transfer Dest: %1 W: %2").arg(xferDst,8,16,QChar('0')).arg(xferDstW); - } - - case GE_CMD_TRANSFERSRCPOS: - { - u32 x = (data & 1023)+1; - u32 y = ((data>>10) & 1023)+1; - return QString("Block Transfer Src Rect TL: %1, %2").arg(x).arg(y); - } - - case GE_CMD_TRANSFERDSTPOS: - { - u32 x = (data & 1023)+1; - u32 y = ((data>>10) & 1023)+1; - return QString("Block Transfer Dest Rect TL: %1, %2").arg(x).arg(y); - } - - case GE_CMD_TRANSFERSIZE: - { - u32 w = (data & 1023)+1; - u32 h = ((data>>10) & 1023)+1; - return QString("Block Transfer Rect Size: %1 x %2").arg(w).arg(h); - } - - case GE_CMD_TRANSFERSTART: - { - return QString("Block Transfer Start : %1").arg(data ? "32-bit texel size" : "16-bit texel size"); - } - - case GE_CMD_TEXSIZE0: - case GE_CMD_TEXSIZE1: - case GE_CMD_TEXSIZE2: - case GE_CMD_TEXSIZE3: - case GE_CMD_TEXSIZE4: - case GE_CMD_TEXSIZE5: - case GE_CMD_TEXSIZE6: - case GE_CMD_TEXSIZE7: - { - int w = 1 << (data & 0xf); - int h = 1 << ((data>>8) & 0xf); - return QString("Texture Size %1: %2, width : %3, height : %4").arg(cmd - GE_CMD_TEXSIZE0).arg(data,6,16,QChar('0')).arg(w).arg(h); - } - - case GE_CMD_ZBUFPTR: - { - return QString("Zbuf Ptr: %1").arg(data,6,16,QChar('0')); - } - - case GE_CMD_ZBUFWIDTH: - { - return QString("Zbuf Width: %1").arg(data,6,16,QChar('0')); - } - - case GE_CMD_AMBIENTCOLOR: - return QString("Ambient Color: %1").arg(data,6,16,QChar('0')); - - case GE_CMD_AMBIENTALPHA: - return QString("Ambient Alpha: %1").arg(data,6,16,QChar('0')); - - case GE_CMD_MATERIALAMBIENT: - return QString("Material Ambient Color: %1").arg(data,6,16,QChar('0')); - - case GE_CMD_MATERIALDIFFUSE: - return QString("Material Diffuse Color: %1").arg(data,6,16,QChar('0')); - - case GE_CMD_MATERIALEMISSIVE: - return QString("Material Emissive Color: %1").arg(data,6,16,QChar('0')); - - case GE_CMD_MATERIALSPECULAR: - return QString("Material Specular Color: %1").arg(data,6,16,QChar('0')); - - case GE_CMD_MATERIALALPHA: - return QString("Material Alpha Color: %1").arg(data,6,16,QChar('0')); - - case GE_CMD_MATERIALSPECULARCOEF: - return QString("Material specular coef: %1").arg(getFloat24(data)); - - case GE_CMD_SHADEMODE: - return QString("Shade: %1 (%2)").arg(data,6,16,QChar('0')).arg(data ? "gouraud" : "flat"); - - case GE_CMD_LIGHTMODE: - return QString("Lightmode: %1 (%2)").arg(data,6,16,QChar('0')).arg(data ? "separate spec" : "single color"); - - case GE_CMD_LIGHTTYPE0: - case GE_CMD_LIGHTTYPE1: - case GE_CMD_LIGHTTYPE2: - case GE_CMD_LIGHTTYPE3: - { - const char* lightType[3] = - { - "Directional Light", - "Point Light", - "Spot Light" - }; - const char* lightComp[3] = - { - "Ambient & Diffuse", - "Diffuse & Specular", - "Unknown (diffuse color, affected by specular power)" - }; - return QString("Light %1 type: %2 %3").arg(cmd-GE_CMD_LIGHTTYPE0).arg(lightType[(data) >> 8 & 0x3]).arg(lightComp[data & 0x3]); - } - case GE_CMD_LX0:case GE_CMD_LY0:case GE_CMD_LZ0: - case GE_CMD_LX1:case GE_CMD_LY1:case GE_CMD_LZ1: - case GE_CMD_LX2:case GE_CMD_LY2:case GE_CMD_LZ2: - case GE_CMD_LX3:case GE_CMD_LY3:case GE_CMD_LZ3: - { - int n = cmd - GE_CMD_LX0; - int l = n / 3; - int c = n % 3; - float val = getFloat24(data); - return QString("Light %1 %2 pos: %3").arg(l).arg(QChar('X')+c).arg(val); - } - - case GE_CMD_LDX0:case GE_CMD_LDY0:case GE_CMD_LDZ0: - case GE_CMD_LDX1:case GE_CMD_LDY1:case GE_CMD_LDZ1: - case GE_CMD_LDX2:case GE_CMD_LDY2:case GE_CMD_LDZ2: - case GE_CMD_LDX3:case GE_CMD_LDY3:case GE_CMD_LDZ3: - { - int n = cmd - GE_CMD_LDX0; - int l = n / 3; - int c = n % 3; - float val = getFloat24(data); - return QString("Light %1 %2 dir: %3").arg(l).arg(QChar('X')+c).arg(val); - } - - case GE_CMD_LKA0:case GE_CMD_LKB0:case GE_CMD_LKC0: - case GE_CMD_LKA1:case GE_CMD_LKB1:case GE_CMD_LKC1: - case GE_CMD_LKA2:case GE_CMD_LKB2:case GE_CMD_LKC2: - case GE_CMD_LKA3:case GE_CMD_LKB3:case GE_CMD_LKC3: - { - int n = cmd - GE_CMD_LKA0; - int l = n / 3; - int c = n % 3; - float val = getFloat24(data); - return QString("Light %1 %2 att: %3").arg(l).arg(QChar('X')+c).arg(val); - } - - case GE_CMD_LAC0:case GE_CMD_LAC1:case GE_CMD_LAC2:case GE_CMD_LAC3: - case GE_CMD_LDC0:case GE_CMD_LDC1:case GE_CMD_LDC2:case GE_CMD_LDC3: - case GE_CMD_LSC0:case GE_CMD_LSC1:case GE_CMD_LSC2:case GE_CMD_LSC3: - { - float r = (float)(data & 0xff)/255.0f; - float g = (float)((data>>8) & 0xff)/255.0f; - float b = (float)(data>>16)/255.0f; - - int l = (cmd - GE_CMD_LAC0) / 3; - int t = (cmd - GE_CMD_LAC0) % 3; - return QString("Light %1 color %2: %3 %4 %5").arg(l).arg(t).arg(r).arg(g).arg(b); - } - - case GE_CMD_VIEWPORTXSCALE: - case GE_CMD_VIEWPORTYSCALE: - case GE_CMD_VIEWPORTXCENTER: - case GE_CMD_VIEWPORTYCENTER: - return QString("Viewport param %1: %2").arg(cmd-GE_CMD_VIEWPORTXSCALE).arg(getFloat24(data)); - case GE_CMD_VIEWPORTZSCALE: - { - float zScale = getFloat24(data) / 65535.f; - return QString("Viewport Z scale: %1").arg(zScale); - } - case GE_CMD_VIEWPORTZCENTER: - { - float zOff = getFloat24(data) / 65535.f; - return QString("Viewport Z pos: %1").arg(zOff); - } - - case GE_CMD_LIGHTENABLE0: - case GE_CMD_LIGHTENABLE1: - case GE_CMD_LIGHTENABLE2: - case GE_CMD_LIGHTENABLE3: - return QString("Light %1 enable: %2").arg(cmd-GE_CMD_LIGHTENABLE0).arg(data); - - case GE_CMD_CULL: - { - const char* cull[2] = - { - "Clockwise visible", - "Counter-clockwise visible" - }; - return QString("cull: %1").arg(cull[data & 0x1]); - } - case GE_CMD_PATCHDIVISION: - { - int patch_div_s = data & 0xFF; - int patch_div_t = (data >> 8) & 0xFF; - return QString("Patch subdivision: S=%1 x T=%2").arg(patch_div_s).arg(patch_div_t); - } - - case GE_CMD_PATCHPRIMITIVE: - { - const char* type[3] = - { - "Triangles", - "Lines", - "Points" - }; - return QString("Patch Primitive: %1").arg(type[data]); - } - case GE_CMD_PATCHFACING: - { - const char* val[2] = - { - "Clockwise", - "Counter-Clockwise" - }; - return QString( "Patch Facing: %1").arg(val[data]); - } - case GE_CMD_REVERSENORMAL: - return QString("Reverse normal: %1").arg(data); - break; - - case GE_CMD_MATERIALUPDATE: - { - QString txt = ""; - if(data & 1) txt += " Ambient"; - if(data & 2) txt += " Diffuse"; - if(data & 4) txt += " Specular"; - return QString("Material Update: %1").arg(txt); - } - - ////////////////////////////////////////////////////////////////// - // CLEARING - ////////////////////////////////////////////////////////////////// - case GE_CMD_CLEARMODE: - { - // If it becomes a performance problem, check diff&1 - const char* clearMode[8] = - { - "", - "Clear Color Buffer", - "Clear Stencil/Alpha Buffer", - "", - "Clear Depth Buffer", - "", - "", - "" - }; - return QString("Clear mode: %1, enabled : %2").arg(clearMode[(data >> 8) & 0xF]).arg(data & 0x1); - } - - ////////////////////////////////////////////////////////////////// - // ALPHA BLENDING - ////////////////////////////////////////////////////////////////// - case GE_CMD_ALPHABLENDENABLE: - return QString("Alpha blend enable: %1").arg(data); - - case GE_CMD_BLENDMODE: - { - const char* func[9] = - { - "Source Color", - "One Minus Source Color", - "Source Alpha", - "One Minus Source Alpha", - "Destination Color", - "One Minus Destination Color", - "Destination Alpha", - "One Minus Destination Alpha", - "Fix" - }; - const char* op[6] = - { - "Add", - "Subtract", - "Reverse Subtract", - "Minimum Value", - "Maximum Value", - "Absolute Value" - }; - return QString("Blend mode: Src : %1, Dest : %2, Op : %3").arg(func[(data >> 4) & 0xF]).arg(func[(data >> 8) & 0xF]).arg(op[(data) & 0x7]); - } - case GE_CMD_BLENDFIXEDA: - return QString("Blend fix A: %1").arg(data,6,16,QChar('0')); - - case GE_CMD_BLENDFIXEDB: - return QString("Blend fix B: %1").arg(data,6,16,QChar('0')); - - case GE_CMD_ALPHATESTENABLE: - return QString("Alpha test enable: %1").arg(data); - - case GE_CMD_ALPHATEST: - { - const char* testFunc[8] = - { - "Never pass pixel", - "Always pass pixel", - "Pass pixel if match", - "Pass pixel if difference", - "Pass pixel if less", - "Pass pixel if less or equal", - "Pass pixel if greater", - "Pass pixel if greater or equal" - }; - return QString("Alpha test settings, Mask : %1, Ref : %2, Test : %3").arg((data >> 8) & 0xFF).arg((data >> 16) & 0xFF).arg(testFunc[data & 0x7]); - } - case GE_CMD_ANTIALIASENABLE: - return QString("Antialias enable: %1").arg(data); - - case GE_CMD_PATCHCULLENABLE: - return QString("Antialias enable: %1").arg(data); - - case GE_CMD_COLORTESTENABLE: - { - const char* colorTest[4] = - { - "Never pass pixel", - "Always pass pixel", - "Pass pixel if color matches", - "Pass pixel if color differs" - }; - return QString("Color Test enable: %1").arg(colorTest[data]); - } - case GE_CMD_LOGICOPENABLE: - { - const char* logicOp[16] = - { - "Clear", - "And", - "Reverse And", - "Copy", - "Inverted And", - "No Operation", - "Exclusive Or", - "Or", - "Negated Or", - "Equivalence", - "Inverted", - "Reverse Or", - "Inverted Copy", - "Inverted Or", - "Negated And", - "Set" - }; - return QString("Logic op enable: %1").arg(logicOp[data]); - } - case GE_CMD_TEXFUNC: - { - const char* effect[8] = - { - "Modulate", - "Decal", - "Blend", - "Replace", - "Add", - "","","" - }; - return QString("TexFunc %1 / %2 / %3").arg((data&0x100)?"Texture alpha is read":"Texture alpha is ignored").arg((data & 0x10000)?"Fragment color is doubled":"Fragment color is untouched").arg(effect[data & 0x7]); - } - case GE_CMD_TEXFILTER: - { - const char* filter[8]= - { - "Nearest", - "Linear", - "", - "", - "Nearest; Mipmap Nearest", - "Linear; Mipmap Nearest", - "Nearest; Mipmap Linear", - "Linear; Mipmap Linear" - }; - int min = data & 0x7; - int mag = (data >> 8) & 0x7; - return QString("TexFilter min: %1 mag: %2").arg(filter[min]).arg(filter[mag]); - } - - case GE_CMD_TEXENVCOLOR: - return QString("TexEnvColor %1").arg(data,6,16,QChar('0')); - - case GE_CMD_TEXMODE: - { - u32 maxMipMap = (data >> 16) & 0xF; - return QString("TexMode MaxmipMap : %1, Swizzle : %2").arg(maxMipMap).arg(data & 0x1); - } - case GE_CMD_TEXFORMAT: - { - const char* texFmt[11] = - { - "16-bit BGR 5650", - "16-bit ABGR 5551", - "16-bit ABGR 4444", - "32-bit ABGR 8888", - "4-bit indexed", - "8-bit indexed", - "16-bit indexed", - "32-bit indexed", - "DXT1", - "DXT3", - "DXT5" - }; - return QString("TexFormat %1").arg(texFmt[data]); - } - case GE_CMD_TEXFLUSH: - return QString("TexFlush"); - - case GE_CMD_TEXSYNC: - return QString("TexSync"); - - case GE_CMD_TEXWRAP: - { - const char* wrapMode[2] = - { - "Repeat", - "Clamp" - }; - return QString("TexWrap U : %1, V : %2").arg(wrapMode[data & 0x1]).arg(wrapMode[(data >> 8) & 0x1]); - } - case GE_CMD_TEXLEVEL: - return QString("TexWrap Mode: %1 Offset: %2").arg(data&3).arg(data >> 16); - - case GE_CMD_FOG1: - return QString("Fog1 %1").arg(getFloat24(data)); - - case GE_CMD_FOG2: - return QString( "Fog2 %1").arg(getFloat24(data)); - - case GE_CMD_FOGCOLOR: - return QString("FogColor %1").arg(data,6,16,QChar('0')); - - case GE_CMD_TEXLODSLOPE: - return QString( "TexLodSlope %1").arg(data,6,16,QChar('0')); - - ////////////////////////////////////////////////////////////////// - // Z/STENCIL TESTING - ////////////////////////////////////////////////////////////////// - - case GE_CMD_ZTESTENABLE: - return QString( "Z test enable: %1").arg(data&1); - - case GE_CMD_STENCILOP: - { - const char* stencilOp[8] = - { - "Keep stencil value", - "Zero stencil value", - "Replace stencil value", - "Invert stencil value", - "Increment stencil value", - "Decrement stencil value", - "","" - }; - return QString("Stencil op: ZFail : %1, Fail : %2, Pass : %3").arg(stencilOp[(data >> 16) & 0x7]).arg(stencilOp[(data >> 8) & 0x7]).arg(stencilOp[(data) & 0x7]); - } - case GE_CMD_STENCILTEST: - { - const char* testFunc[8] = - { - "Never pass stencil pixel", - "Always pass stencil pixel", - "Pass test if match", - "Pass test if difference", - "Pass test if less", - "Pass test if less or equal", - "Pass test if greater", - "Pass test if greater or equal" - }; - return QString("Stencil test, Mask : %1, Ref : %2, Test : %3").arg((data >> 8) & 0xFF).arg((data >> 16) & 0xFF).arg(testFunc[data & 0x7]); - } - - case GE_CMD_STENCILTESTENABLE: - return QString("Stencil test enable: %1").arg(data); - - case GE_CMD_ZTEST: - { - const char* testFunc[8] = - { - "Never pass stencil pixel", - "Always pass stencil pixel", - "Pass pixel if match", - "Pass pixel if difference", - "Pass pixel if less", - "Pass pixel if less or equal", - "Pass pixel if greater", - "Pass pixel if greater or equal" - }; - return QString("Z test mode: %1").arg(testFunc[data & 0x7]); - } - case GE_CMD_MORPHWEIGHT0: - case GE_CMD_MORPHWEIGHT1: - case GE_CMD_MORPHWEIGHT2: - case GE_CMD_MORPHWEIGHT3: - case GE_CMD_MORPHWEIGHT4: - case GE_CMD_MORPHWEIGHT5: - case GE_CMD_MORPHWEIGHT6: - case GE_CMD_MORPHWEIGHT7: - { - int index = cmd - GE_CMD_MORPHWEIGHT0; - float weight = getFloat24(data); - return QString("MorphWeight %1 = %2").arg(index).arg(weight); - } - - case GE_CMD_DITH0: - case GE_CMD_DITH1: - case GE_CMD_DITH2: - case GE_CMD_DITH3: - return QString("DitherMatrix %1 = %2").arg(cmd-GE_CMD_DITH0).arg(data,6,16,QChar('0')); - - case GE_CMD_LOGICOP: - return QString("LogicOp: %1").arg(data,6,16,QChar('0')); - - case GE_CMD_ZWRITEDISABLE: - return QString("ZMask: %1").arg(data,6,16,QChar('0')); - - case GE_CMD_COLORTEST: - return QString("ColorTest: %1").arg(data,6,16,QChar('0')); - - case GE_CMD_COLORREF: - return QString("ColorRef: %1").arg(data,6,16,QChar('0')); - - case GE_CMD_COLORTESTMASK: - return QString( "ColorTestMask: %1").arg(data,6,16,QChar('0')); - - case GE_CMD_MASKRGB: - return QString("MaskRGB: %1").arg(data,6,16,QChar('0')); - - case GE_CMD_MASKALPHA: - return QString("MaskAlpha: %1").arg(data,6,16,QChar('0')); - - case GE_CMD_WORLDMATRIXNUMBER: - return QString("World # %1").arg(data & 0xF); - - case GE_CMD_WORLDMATRIXDATA: - return QString("World data # %1").arg(getFloat24(data)); - - case GE_CMD_VIEWMATRIXNUMBER: - return QString("VIEW # %1").arg(data & 0xF); - - case GE_CMD_VIEWMATRIXDATA: - return QString("VIEW data # %1").arg(getFloat24(data)); - - case GE_CMD_PROJMATRIXNUMBER: - return QString("PROJECTION # %1").arg(data & 0xF); - - case GE_CMD_PROJMATRIXDATA: - return QString("PROJECTION matrix data # %1").arg(getFloat24(data)); - - case GE_CMD_TGENMATRIXNUMBER: - return QString("TGEN # %1").arg(data & 0xF); - - case GE_CMD_TGENMATRIXDATA: - return QString("TGEN data # %1").arg(getFloat24(data)); - - case GE_CMD_BONEMATRIXNUMBER: - return QString("BONE #%1").arg(data); - - case GE_CMD_BONEMATRIXDATA: - return QString("BONE data #%1 %2").arg(state.boneMatrixNumber & 0x7f).arg(getFloat24(data)); - - default: - return QString("Unknown: %1").arg(op,8,16,QChar('0')); - } -} - - -void Debugger_DisplayList::FillDisplayListCmd(std::map& data, u32 pc, u32 prevAddr, GPUgstate& state) -{ - u32 curPc = pc; - int debugLimit = 10000; // Anti crash if this code is bugged - while(Memory::IsValidAddress(curPc) && debugLimit > 0) - { - if(data.find(curPc) != data.end()) - return; - - u32 op = Memory::ReadUnchecked_U32(curPc); //read from memory - u32 cmd = op >> 24; - u32 data_ = op & 0xFFFFFF; - u32 diff = op ^ gstate.cmdmem[cmd]; - state.cmdmem[cmd] = op; - u32 prevOp = 0; - if(Memory::IsValidAddress(prevAddr)) - Memory::ReadUnchecked_U32(prevAddr); - data[curPc].comment = DisassembleOp(curPc, op, prevOp, state); - data[curPc].addr = curPc; - data[curPc].cmd = cmd; - data[curPc].data = data_; - data[curPc].implementationNotFinished = false; - data[curPc].texAddr = (gstate.texaddr[0] & 0xFFFFF0) | ((gstate.texbufwidth[0]<<8) & 0x0F000000); - data[curPc].fboAddr = state.fbptr & 0xFFFFFF; - u32 baseExtended = ((state.base & 0x0F0000) << 8) | (state.vaddr & 0xFFFFFF); - data[curPc].vtxAddr = ((state.offsetAddr & 0xFFFFFF) + baseExtended) & 0x0FFFFFFF; - baseExtended = ((state.base & 0x0F0000) << 8) | (state.iaddr & 0xFFFFFF); - data[curPc].idxAddr = ((state.offsetAddr & 0xFFFFFF) + baseExtended) & 0x0FFFFFFF; - // Add or remove bugged functions for highlight - if(cmd == GE_CMD_BEZIER || - cmd == GE_CMD_SPLINE || - cmd == GE_CMD_BJUMP || - cmd == GE_CMD_BOUNDINGBOX) - { - data[curPc].implementationNotFinished = true; - } - - // We are drawing, save the GPU state for texture, vertex and index list - if(cmd == GE_CMD_PRIM || cmd == GE_CMD_BEZIER || cmd == GE_CMD_SPLINE) - { - drawGPUState.push_back(state); - } - - if(cmd == GE_CMD_JUMP) - { - u32 baseExtended = ((state.base & 0x0F0000) << 8) | (data_ & 0xFFFFFF); - u32 target = (((state.offsetAddr & 0xFFFFFF) << 8) + baseExtended) & 0x0FFFFFFF; - FillDisplayListCmd(data, target, prevAddr, state); - return; - } - else if(cmd == GE_CMD_CALL) - { - u32 baseExtended = ((state.base & 0x0F0000) << 8) | (data_ & 0xFFFFFF); - u32 target = (((state.offsetAddr & 0xFFFFFF) << 8) + baseExtended) & 0x0FFFFFFF; - FillDisplayListCmd(data, target, prevAddr, state); - } - else if(cmd == GE_CMD_RET) - { - return; - } - else if(cmd == GE_CMD_FINISH) - { - return; - } - else if(cmd == GE_CMD_END) - { - if(prevOp >> 24 == GE_CMD_FINISH) - return; - } - prevAddr = curPc; - curPc += 4; - debugLimit--; - } -} - -void Debugger_DisplayList::Update() -{ - if(!isVisible()) - return; - UpdateRenderBuffer(); - UpdateRenderBufferList(); - UpdateDisplayList(); -} - - -void Debugger_DisplayList::on_displayList_itemClicked(QTreeWidgetItem *item, int column) -{ - displayListRowSelected = item; - ShowDLCode(); -} - -void Debugger_DisplayList::on_stepBtn_clicked() -{ - ((QtHost*)host)->SetGPUStep(true); -} - -void Debugger_DisplayList::on_runBtn_clicked() -{ - ui->displayList->clear(); - ui->displayListData->clear(); - ((QtHost*)host)->SetGPUStep(false); -} - -void Debugger_DisplayList::on_stopBtn_clicked() -{ - ((QtHost*)host)->SetGPUStep(true); -} - -void Debugger_DisplayList::UpdateRenderBuffer() -{ - emit updateRenderBuffer_(); -} - -void Debugger_DisplayList::UpdateRenderBufferGUI() -{ - //gpu->Flush(); - - int FRAME_WIDTH; - int FRAME_HEIGHT; - u8 *data = 0; - int curTex; - glGetIntegerv(GL_TEXTURE_BINDING_2D, &curTex); - // TODO: GLES2 compatible -#ifndef USING_GLES2 - if(currentTextureDisplay == 0) - { - FRAME_WIDTH = pixel_xres; - FRAME_HEIGHT = pixel_yres; - data = new u8[FRAME_WIDTH * FRAME_HEIGHT * 4]; - memset(data,0,FRAME_WIDTH * FRAME_HEIGHT * 4); - if(currentRenderFrameDisplay == 0) - { - glReadBuffer(GL_COLOR_ATTACHMENT0); - glReadPixels(0, 0, FRAME_WIDTH, FRAME_HEIGHT, GL_BGRA, GL_UNSIGNED_BYTE, data); - } - else - { - glReadBuffer(GL_DEPTH_ATTACHMENT); - glReadPixels(0, 0, FRAME_WIDTH, FRAME_HEIGHT, GL_DEPTH_COMPONENT, GL_FLOAT, data); - } - } - else - { - draw_->GetFramebufferDimensions(currentTextureDisplay, &FRAME_WIDTH, &FRAME_HEIGHT); - data = new u8[FRAME_WIDTH * FRAME_HEIGHT * 4]; - memset(data,0,FRAME_WIDTH * FRAME_HEIGHT * 4); - if(currentRenderFrameDisplay == 0) - { - draw_->BindFramebufferAsTexture(currentTextureDisplay, 0, Draw::FB_COLOR_BIT, 0); - glGetTexImage(GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_BYTE, data); - } - } -#endif - glBindTexture(GL_TEXTURE_2D, curTex); - - QImage img = QImage(data, FRAME_WIDTH, FRAME_HEIGHT, FRAME_WIDTH*4, QImage::Format_ARGB32).mirrored(false,true); - QPixmap pixmap = QPixmap::fromImage(img); - ui->fboImg->setPixmap(pixmap); - ui->fboImg->setMinimumWidth(pixmap.width() * fboZoomFactor); - ui->fboImg->setMinimumHeight(pixmap.height() * fboZoomFactor); - ui->fboImg->setMaximumWidth(pixmap.width() * fboZoomFactor); - ui->fboImg->setMaximumHeight(pixmap.height() * fboZoomFactor); - - delete[] data; -} - -void Debugger_DisplayList::on_nextDrawBtn_clicked() -{ - ((QtHost*)host)->SetGPUStep(true, 1); -} - -void Debugger_DisplayList::on_gotoPCBtn_clicked() -{ - if(!displayListRowSelected) - return; - u32 currentPC = displayListRowSelected->data(3, Qt::UserRole).toInt(); - - for(int i = 0; i < ui->displayListData->topLevelItemCount(); i++) - { - if((u32)ui->displayListData->topLevelItem(i)->data(0, Qt::UserRole).toInt() == currentPC) - { - ui->displayListData->setCurrentItem(ui->displayListData->topLevelItem(i)); - } - } -} - -void Debugger_DisplayList::on_texturesList_itemDoubleClicked(QTreeWidgetItem *item, int column) -{ - mainWindow->GetDialogMemoryTex()->ShowTex(drawGPUState[item->data(0,Qt::UserRole).toInt()]); -} - -void Debugger_DisplayList::on_comboBox_currentIndexChanged(int index) -{ - currentRenderFrameDisplay = index; - UpdateRenderBufferGUI(); -} - -void Debugger_DisplayList::UpdateRenderBufferList() -{ - emit updateRenderBufferList_(); -} - -void Debugger_DisplayList::UpdateRenderBufferListGUI() -{ - ui->fboList->clear(); - - QTreeWidgetItem* item = new QTreeWidgetItem(); - item->setText(0,"Framebuffer"); - item->setData(0,Qt::UserRole, 0); - item->setText(1,QString::number(pixel_xres)); - item->setText(2,QString::number(pixel_yres)); - item->setText(3,QString::number(4)); - ui->fboList->addTopLevelItem(item); - - std::vector fboList = gpu->GetFramebufferList(); - - for(size_t i = 0; i < fboList.size(); i++) - { - QTreeWidgetItem* item = new QTreeWidgetItem(); - item->setText(0,QString("%1").arg(fboList[i].fb_address,8,16,QChar('0'))); - u64 addr = (u64)fboList[i].fbo; - item->setData(0,Qt::UserRole, addr); - item->setData(0,Qt::UserRole+1, fboList[i].fb_address); - item->setText(1,QString::number(fboList[i].width)); - item->setText(2,QString::number(fboList[i].height)); - item->setText(3,QString::number(fboList[i].format)); - - ui->fboList->addTopLevelItem(item); - } -} - -void Debugger_DisplayList::on_fboList_itemClicked(QTreeWidgetItem *item, int column) -{ - u64 addr = item->data(0,Qt::UserRole).toULongLong(); - Draw::Framebuffer * fbo = (Draw::Framebuffer *)addr; - currentTextureDisplay = fbo; - UpdateRenderBufferGUI(); -} - -void Debugger_DisplayList::on_nextDLBtn_clicked() -{ - ((QtHost*)host)->SetGPUStep(true,-1); -} - -void Debugger_DisplayList::setCurrentFBO(u32 addr) -{ - for(int i = 0; i < ui->fboList->topLevelItemCount(); i++) - { - if((u32)ui->fboList->topLevelItem(i)->data(0,Qt::UserRole+1).toInt() == addr) - { - for(int j = 0; j < ui->fboList->colorCount(); j++) - ui->fboList->topLevelItem(i)->setTextColor(j,Qt::green); - } - else - { - for(int j = 0; j < ui->fboList->colorCount(); j++) - ui->fboList->topLevelItem(i)->setTextColor(j,Qt::black); - } - } -} - -void Debugger_DisplayList::on_zoommBtn_clicked() -{ - fboZoomFactor *= 0.5; - ui->fboImg->setMinimumWidth(ui->fboImg->minimumWidth()*0.5); - ui->fboImg->setMinimumHeight(ui->fboImg->minimumHeight()*0.5); - ui->fboImg->setMaximumWidth(ui->fboImg->minimumWidth()*0.5); - ui->fboImg->setMaximumHeight(ui->fboImg->minimumHeight()*0.5); -} - -void Debugger_DisplayList::on_zoompBtn_clicked() -{ - fboZoomFactor *= 2; - ui->fboImg->setMinimumWidth(ui->fboImg->minimumWidth()*2); - ui->fboImg->setMinimumHeight(ui->fboImg->minimumHeight()*2); - ui->fboImg->setMaximumWidth(ui->fboImg->minimumWidth()*2); - ui->fboImg->setMaximumHeight(ui->fboImg->minimumHeight()*2); -} - -void Debugger_DisplayList::UpdateVertexInfo() -{ - //commented out until someone bothers to maintain it -#if 0 - ui->vertexData->clear(); - - QTreeWidgetItem* item = ui->vertexList->currentItem(); - if(item == 0) - return; - - GPUgstate state = drawGPUState[item->data(0,Qt::UserRole).toInt()]; - u32 baseExtended = ((state.base & 0x0F0000) << 8) | (state.vaddr & 0xFFFFFF); - u32 vaddr = ((state.offsetAddr & 0xFFFFFF) + baseExtended) & 0x0FFFFFFF; - - VertexDecoder vtcDec; - vtcDec.SetVertexType(state.vertType); - u8* tmp = new u8[20*vtcDec.GetDecVtxFmt().stride]; - vtcDec.DecodeVerts(tmp,Memory::GetPointer(vaddr),0,19); - VertexReader vtxRead(tmp,vtcDec.GetDecVtxFmt(),state.vertType); - - for(int i = 0; i < maxVtxDisplay; i++) - { - vtxRead.Goto(i); - QTreeWidgetItem* itemTop = new QTreeWidgetItem(); - itemTop->setText(0,QString::number(i)); - itemTop->setText(1,QString("%1").arg(vaddr+i*vtcDec.GetDecVtxFmt().stride,8,16,QChar('0'))); - ui->vertexData->addTopLevelItem(itemTop); - - if (vtxRead.hasNormal()) - { - float nrm[3]; - vtxRead.ReadNrm(nrm); - QTreeWidgetItem* item = new QTreeWidgetItem(); - item->setText(1,"Normal"); - item->setText(2,QString("X: %1, Y: %2, Z: %3").arg(nrm[0]).arg(nrm[1]).arg(nrm[2])); - itemTop->addChild(item); - } - if (vtxRead.hasUV()) { - float uv[2]; - vtxRead.ReadUV(uv); - QTreeWidgetItem* item = new QTreeWidgetItem(); - item->setText(1,"Uv"); - item->setText(2,QString("X: %1, Y: %2").arg(uv[0]).arg(uv[1])); - itemTop->addChild(item); - } - if (vtxRead.hasColor0()) { - float col0[4]; - vtxRead.ReadColor0(col0); - QTreeWidgetItem* item = new QTreeWidgetItem(); - item->setText(1,"Color0"); - item->setText(2,QString("X: %1, Y: %2, Z: %3").arg(col0[0]).arg(col0[1]).arg(col0[2])); - itemTop->addChild(item); - } - if (vtxRead.hasColor0()) { - float col1[3]; - vtxRead.ReadColor1(col1); - QTreeWidgetItem* item = new QTreeWidgetItem(); - item->setText(1,"Color1"); - item->setText(2,QString("X: %1, Y: %2, Z: %3").arg(col1[0]).arg(col1[1]).arg(col1[2])); - itemTop->addChild(item); - } - float pos[3]; - vtxRead.ReadPos(pos); - QTreeWidgetItem* item = new QTreeWidgetItem(); - item->setText(1,"Position"); - item->setText(2,QString("X: %1, Y: %2, Z: %3").arg(pos[0]).arg(pos[1]).arg(pos[2])); - itemTop->addChild(item); - } - delete [] tmp; - for(int i = 0; i < ui->vertexData->columnCount(); i++) - { - ui->vertexData->resizeColumnToContents(i); - } -#endif -} - -void Debugger_DisplayList::on_vertexList_itemClicked(QTreeWidgetItem *item, int column) -{ - UpdateVertexInfo(); -} - -void Debugger_DisplayList::on_pushButton_clicked() -{ - maxVtxDisplay += 20; - UpdateVertexInfo(); -} - - -void Debugger_DisplayList::UpdateIndexInfo() -{ - ui->indexData->clear(); - - QTreeWidgetItem* item = ui->indexList->currentItem(); - if(item == 0) - return; - - GPUgstate state = drawGPUState[item->data(0,Qt::UserRole).toInt()]; - u32 baseExtended = ((state.base & 0x0F0000) << 8) | (state.iaddr & 0xFFFFFF); - u32 iaddr = ((state.offsetAddr & 0xFFFFFF) + baseExtended) & 0x0FFFFFFF; - - int sizeIdx = 1; - if((state.vertType & GE_VTYPE_IDX_MASK) == GE_VTYPE_IDX_16BIT) - sizeIdx = 2; - - for(int i = 0; i < maxIdxDisplay; i++) - { - QTreeWidgetItem* itemTop = new QTreeWidgetItem(); - itemTop->setText(0,QString::number(i)); - itemTop->setText(1,QString("%1").arg(iaddr+i*sizeIdx,8,16,QChar('0'))); - int idx = 0; - if(sizeIdx == 1) - idx = Memory::Read_U8(iaddr+i*sizeIdx); - else - idx = Memory::Read_U16(iaddr+i*sizeIdx); - itemTop->setText(2,QString::number(idx)); - ui->indexData->addTopLevelItem(itemTop); - } - for(int i = 0; i < ui->indexData->columnCount(); i++) - { - ui->indexData->resizeColumnToContents(i); - } -} - - -void Debugger_DisplayList::on_nextIdx_clicked() -{ - maxIdxDisplay += 20; - UpdateIndexInfo(); -} - -void Debugger_DisplayList::on_indexList_itemClicked(QTreeWidgetItem *item, int column) -{ - UpdateIndexInfo(); -} - -void Debugger_DisplayList::on_displayListData_customContextMenuRequested(const QPoint &pos) -{ - QTreeWidgetItem* item = ui->displayListData->itemAt(pos); - if(!item) - return; - displayListDataSelected = item; - - QMenu menu(this); - - QAction *runToHere = new QAction(tr("Run to here"), this); - connect(runToHere, SIGNAL(triggered()), this, SLOT(RunToDLPC())); - menu.addAction(runToHere); - - menu.exec( ui->displayListData->mapToGlobal(pos)); -} - -void Debugger_DisplayList::RunToDLPC() -{ - u32 addr = displayListDataSelected->text(0).toUInt(0,16); - ((QtHost*)host)->SetGPUStep(true, 2, addr); -} - -void Debugger_DisplayList::on_texturesList_customContextMenuRequested(const QPoint &pos) -{ - QTreeWidgetItem* item = ui->texturesList->itemAt(pos); - if(!item) - return; - textureDataSelected = item; - - QMenu menu(this); - - QAction *runToDraw = new QAction(tr("Run to draw using this texture"), this); - connect(runToDraw, SIGNAL(triggered()), this, SLOT(RunToDrawTex())); - menu.addAction(runToDraw); - - menu.exec( ui->texturesList->mapToGlobal(pos)); -} - -void Debugger_DisplayList::RunToDrawTex() -{ - u32 addr = textureDataSelected->text(0).toUInt(0,16); - ((QtHost*)host)->SetGPUStep(true, 3, addr); -} diff --git a/Qt/Debugger/debugger_displaylist.h b/Qt/Debugger/debugger_displaylist.h deleted file mode 100644 index 2c88384269..0000000000 --- a/Qt/Debugger/debugger_displaylist.h +++ /dev/null @@ -1,104 +0,0 @@ -#pragma once - -#include "Core/Debugger/DebugInterface.h" - -#include -#include - -#include "GPU/GPUState.h" -#include "thin3d/thin3d.h" - -class MainWindow; -namespace Ui { -class Debugger_DisplayList; -} - - -class DListLine -{ -public: - u32 addr; - u32 cmd; - u32 data; - QString comment; - bool implementationNotFinished; - u32 texAddr; - u32 fboAddr; - u32 vtxAddr; - int vtxStart; - int vtxCount; - u32 idxAddr; - int idxStart; - int idxCount; -}; - -class Debugger_DisplayList : public QDialog -{ - Q_OBJECT - -public: - Debugger_DisplayList(DebugInterface *_cpu, Draw::DrawContext *draw, MainWindow *mainWindow_, QWidget *parent = 0); - ~Debugger_DisplayList(); - - void UpdateDisplayList(); - - void ShowDLCode(); - void FillDisplayListCmd(std::map &data, u32 pc, u32 prev, GPUgstate &state); - void Update(); - void UpdateRenderBuffer(); - void UpdateRenderBufferList(); - void UpdateVertexInfo(); - void UpdateIndexInfo(); - -signals: - void updateDisplayList_(); - void updateRenderBufferList_(); - void updateRenderBuffer_(); - -private slots: - void UpdateDisplayListGUI(); - void UpdateRenderBufferListGUI(); - void UpdateRenderBufferGUI(); - - void on_displayList_itemClicked(QTreeWidgetItem *item, int column); - void on_stepBtn_clicked(); - void on_runBtn_clicked(); - void on_stopBtn_clicked(); - void on_nextDrawBtn_clicked(); - void on_gotoPCBtn_clicked(); - void on_texturesList_itemDoubleClicked(QTreeWidgetItem *item, int column); - void on_comboBox_currentIndexChanged(int index); - void on_fboList_itemClicked(QTreeWidgetItem *item, int column); - void on_nextDLBtn_clicked(); - void setCurrentFBO(u32 addr); - void on_zoommBtn_clicked(); - void on_zoompBtn_clicked(); - void on_vertexList_itemClicked(QTreeWidgetItem *item, int column); - void on_pushButton_clicked(); - void on_nextIdx_clicked(); - void on_indexList_itemClicked(QTreeWidgetItem *item, int column); - void on_displayListData_customContextMenuRequested(const QPoint &pos); - void on_texturesList_customContextMenuRequested(const QPoint &pos); - void RunToDLPC(); - void RunToDrawTex(); - -private: - QString DisassembleOp(u32 pc, u32 op, u32 prev, const GPUgstate &state); - - Ui::Debugger_DisplayList *ui; - DebugInterface* cpu; - Draw::DrawContext* draw_; - MainWindow* mainWindow; - QTreeWidgetItem* displayListRowSelected; - QTreeWidgetItem* displayListDataSelected; - QTreeWidgetItem* textureDataSelected; - int currentRenderFrameDisplay; - Draw::Framebuffer *currentTextureDisplay; - float fboZoomFactor; - int maxVtxDisplay; - int maxIdxDisplay; - - std::vector drawGPUState; - std::map vtxBufferSize; - std::map idxBufferSize; -}; diff --git a/Qt/Debugger/debugger_displaylist.ui b/Qt/Debugger/debugger_displaylist.ui deleted file mode 100644 index 894a479ad2..0000000000 --- a/Qt/Debugger/debugger_displaylist.ui +++ /dev/null @@ -1,533 +0,0 @@ - - - Debugger_DisplayList - - - - 0 - 0 - 795 - 506 - - - - Dialog - - - - - - Qt::Horizontal - - - - Qt::Vertical - - - - DisplayList - - - - - - Qt::CustomContextMenu - - - false - - - false - - - false - - - 30 - - - - Id - - - - - Status - - - - - Start Address - - - - - Current Address - - - - - - - - - - Run - - - - - - - Stop - - - - - - - Next DL - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - 0 - - - - Commands - - - - - - Qt::CustomContextMenu - - - false - - - false - - - false - - - 2 - - - false - - - - 1 - - - - - 2 - - - - - - - - - - Step - - - - - - - Next Draw - - - - - - - Goto PC - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - Textures - - - - - - Qt::CustomContextMenu - - - - Address - - - - - Width - - - - - Height - - - - - Format - - - - - - - - - Vertex Buffer - - - - - - 30 - - - - Address - - - - - Coord Type - - - - - Number Morph - - - - - Number Weights - - - - - Has Weight - - - - - Has Position - - - - - Has Normal - - - - - Has Color - - - - - Has UV - - - - - - - - 30 - - - - Idx - - - - - Address - - - - - Values - - - - - - - - - - Next 20 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - Index Buffer - - - - - - Qt::Vertical - - - - - Address - - - - - - 30 - - - - Idx - - - - - Adress - - - - - Value - - - - - - - - - - - Next 20 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - Framebuffer - - - - - - Qt::Vertical - - - - - 0 - 0 - - - - - VAddress - - - - - Width - - - - - Height - - - - - Format - - - - - - - 0 - 0 - - - - true - - - - - 0 - 0 - 375 - 76 - - - - - - - QFrame::Box - - - - - - true - - - - - - - - - - - - - - Display : - - - - - - - - Color - - - - - Depth - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Zoom- - - - - - - - Zoom+ - - - - - - - - - - - - - - diff --git a/Qt/Debugger/debugger_memory.cpp b/Qt/Debugger/debugger_memory.cpp deleted file mode 100644 index 706fd8dcfd..0000000000 --- a/Qt/Debugger/debugger_memory.cpp +++ /dev/null @@ -1,89 +0,0 @@ -#include "debugger_memory.h" -#include "ui_debugger_memory.h" -#include "Core/Debugger/SymbolMap.h" -#include - -Debugger_Memory::Debugger_Memory(DebugInterface *_cpu, MainWindow* mainWindow_, QWidget *parent) : - QDialog(parent), - ui(new Ui::Debugger_Memory), - cpu(_cpu), - mainWindow(mainWindow_) -{ - ui->setupUi(this); - - setWindowTitle(tr("Memory Viewer - %1").arg(cpu->GetName())); - - ui->memView->setDebugger(_cpu); -} - -Debugger_Memory::~Debugger_Memory() -{ - delete ui; -} - -void Debugger_Memory::Update() -{ - ui->memView->redraw(); -} - -void Debugger_Memory::Goto(u32 addr) -{ - show(); - ui->memView->gotoAddr(addr & ~3); -} - -void Debugger_Memory::on_editAddress_textChanged(const QString &arg1) -{ - ui->memView->gotoAddr(arg1.toUInt(0,16) & ~3); -} - -void Debugger_Memory::on_normalBtn_clicked() -{ - ui->memView->setMode(MV_NORMAL); -} - -void Debugger_Memory::on_symbolsBtn_clicked() -{ - ui->memView->setMode(MV_SYMBOLS); -} - -void Debugger_Memory::on_memView_customContextMenuRequested(const QPoint &pos) -{ - ui->memView->contextMenu(pos); -} - -void Debugger_Memory::NotifyMapLoaded() -{ - QListWidgetItem* item = new QListWidgetItem(); - item->setText("(0x80000000)"); - item->setData(Qt::UserRole, 0x80000000); - ui->symbols->addItem(item); - - std::vector symbols = g_symbolMap->GetAllSymbols(ST_DATA); - for(int i = 0; i < (int)symbols.size(); i++) - { - QListWidgetItem* item = new QListWidgetItem(); - item->setText(QString("%1 (%2)").arg(QString::fromStdString(symbols[i].name)).arg(symbols[i].size)); - item->setData(Qt::UserRole, symbols[i].address); - ui->symbols->addItem(item); - } - - ui->regions->clear(); - /* - for (int i = 0; i < cpu->getMemMap()->numRegions; i++) - { - int n = ComboBox_AddString(lb,cpu->getMemMap()->regions[i].name); - ComboBox_SetItemData(lb,n,cpu->getMemMap()->regions[i].start); - } - */ -} - -void Debugger_Memory::on_regions_currentIndexChanged(int index) -{ - ui->memView->gotoAddr(ui->regions->itemData(index,Qt::UserRole).toInt()); -} - -void Debugger_Memory::on_symbols_itemClicked(QListWidgetItem *item) -{ - ui->memView->gotoAddr(item->data(Qt::UserRole).toInt()); -} diff --git a/Qt/Debugger/debugger_memory.h b/Qt/Debugger/debugger_memory.h deleted file mode 100644 index c4593a5fef..0000000000 --- a/Qt/Debugger/debugger_memory.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef DEBUGGER_MEMORY_H -#define DEBUGGER_MEMORY_H - -#include "Core/Debugger/DebugInterface.h" -#include -#include - -class MainWindow; -namespace Ui { -class Debugger_Memory; -} - -class Debugger_Memory : public QDialog -{ - Q_OBJECT - -public: - explicit Debugger_Memory(DebugInterface *_cpu, MainWindow* mainWindow_, QWidget *parent = 0); - ~Debugger_Memory(); - - void Update(); - void Goto(u32 addr); - - void NotifyMapLoaded(); -private slots: - void on_editAddress_textChanged(const QString &arg1); - - void on_normalBtn_clicked(); - - void on_symbolsBtn_clicked(); - - void on_memView_customContextMenuRequested(const QPoint &pos); - - void on_regions_currentIndexChanged(int index); - - void on_symbols_itemClicked(QListWidgetItem *item); - -private: - Ui::Debugger_Memory *ui; - DebugInterface* cpu; - MainWindow* mainWindow; - -}; - -#endif // DEBUGGER_MEMORY_H diff --git a/Qt/Debugger/debugger_memory.ui b/Qt/Debugger/debugger_memory.ui deleted file mode 100644 index abb94dcf32..0000000000 --- a/Qt/Debugger/debugger_memory.ui +++ /dev/null @@ -1,92 +0,0 @@ - - - Debugger_Memory - - - - 0 - 0 - 611 - 384 - - - - Dialog - - - - - - - - Goto: - - - - - - - - - - - - - Mode - - - - - - Normal - - - - - - - Symbols - - - - - - - - - - - - - - - - - - 0 - 0 - - - - Qt::StrongFocus - - - Qt::CustomContextMenu - - - - - - - - - - CtrlMemView - QWidget -
ctrlmemview.h
- 1 -
-
- - -
diff --git a/Qt/Debugger/debugger_memorytex.cpp b/Qt/Debugger/debugger_memorytex.cpp deleted file mode 100644 index 238c1371af..0000000000 --- a/Qt/Debugger/debugger_memorytex.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include "debugger_memorytex.h" -#include "gfx/gl_common.h" -#include "ui_debugger_memorytex.h" -#include "Core/MemMap.h" -#include -#include -#include "Core/HLE/sceDisplay.h" -#include "GPU/GPUInterface.h" -#include "base/display.h" - -Debugger_MemoryTex::Debugger_MemoryTex(QWidget *parent) : - QDialog(parent), - ui(new Ui::Debugger_MemoryTex) -{ - ui->setupUi(this); -} - -Debugger_MemoryTex::~Debugger_MemoryTex() -{ - delete ui; -} - -void Debugger_MemoryTex::ShowTex(const GPUgstate &state) -{ - ui->texaddr->setText(QString("%1").arg(state.texaddr[0] & 0xFFFFFF,8,16,QChar('0'))); - ui->texbufwidth0->setText(QString("%1").arg(state.texbufwidth[0] & 0xFFFFFF,8,16,QChar('0'))); - ui->texformat->setText(QString("%1").arg(state.texformat & 0xFFFFFF,8,16,QChar('0'))); - ui->texsize->setText(QString("%1").arg(state.texsize[0] & 0xFFFFFF,8,16,QChar('0'))); - ui->texmode->setText(QString("%1").arg(state.texmode & 0xFFFFFF,8,16,QChar('0'))); - ui->clutformat->setText(QString("%1").arg(state.clutformat & 0xFFFFFF,8,16,QChar('0'))); - ui->clutaddr->setText(QString("%1").arg(state.clutaddr & 0xFFFFFF,8,16,QChar('0'))); - ui->clutaddrupper->setText(QString("%1").arg(state.clutaddrupper & 0xFFFFFF,8,16,QChar('0'))); - ui->loadclut->setText(QString("%1").arg(state.loadclut & 0xFFFFFF,8,16,QChar('0'))); - on_readBtn_clicked(); - - show(); -} - -void Debugger_MemoryTex::on_readBtn_clicked() -{ - GPUgstate state; - state.texaddr[0] = ui->texaddr->text().toUInt(0,16); - state.texbufwidth[0] = ui->texbufwidth0->text().toUInt(0,16); - state.texformat = ui->texformat->text().toUInt(0,16); - state.texsize[0] = ui->texsize->text().toUInt(0,16); - state.texmode = ui->texmode->text().toUInt(0,16); - state.clutformat = ui->clutformat->text().toUInt(0,16); - state.clutaddr = ui->clutaddr->text().toUInt(0,16); - state.clutaddrupper = ui->clutaddrupper->text().toUInt(0,16); - state.loadclut = ui->loadclut->text().toUInt(0,16); - int bufW = state.texbufwidth[0] & 0x3ff; - int w = 1 << (state.texsize[0] & 0xf); - int h = 1 << ((state.texsize[0]>>8) & 0xf); - w = std::max(bufW,w); - uchar* newData = new uchar[w*h*4]; - - // TODO: Use GPU_GetCurrentTexture(). Warning: blocks. - - delete[] newData; -} diff --git a/Qt/Debugger/debugger_memorytex.h b/Qt/Debugger/debugger_memorytex.h deleted file mode 100644 index a39d3ed53b..0000000000 --- a/Qt/Debugger/debugger_memorytex.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef DEBUGGER_MEMORYTEX_H -#define DEBUGGER_MEMORYTEX_H - -#include -#include "GPU/GPUState.h" - -namespace Ui { -class Debugger_MemoryTex; -} - -class Debugger_MemoryTex : public QDialog -{ - Q_OBJECT - -public: - explicit Debugger_MemoryTex(QWidget *parent = 0); - ~Debugger_MemoryTex(); - - void ShowTex(const GPUgstate& state); -private slots: - void on_readBtn_clicked(); - -private: - Ui::Debugger_MemoryTex *ui; -}; - -#endif // DEBUGGER_MEMORYTEX_H diff --git a/Qt/Debugger/debugger_memorytex.ui b/Qt/Debugger/debugger_memorytex.ui deleted file mode 100644 index b163010222..0000000000 --- a/Qt/Debugger/debugger_memorytex.ui +++ /dev/null @@ -1,177 +0,0 @@ - - - Debugger_MemoryTex - - - - 0 - 0 - 768 - 546 - - - - Dialog - - - - - - Qt::Horizontal - - - - - - - - - TexAddr - - - - - - - TexBufWidth0 - - - - - - - TexFormat - - - - - - - TexSize - - - - - - - ClutFormat - - - - - - - ClutAddr - - - - - - - ClutAddrUpper - - - - - - - LoadClut - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - TexMode - - - - - - - - - - - - - - Read - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - true - - - - - 0 - 0 - 76 - 526 - - - - - - - QFrame::Box - - - - - - - - - - - - - - - - diff --git a/Qt/Debugger/debugger_vfpu.cpp b/Qt/Debugger/debugger_vfpu.cpp deleted file mode 100644 index af10c2e058..0000000000 --- a/Qt/Debugger/debugger_vfpu.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "debugger_vfpu.h" -#include "ui_debugger_vfpu.h" -#include "mainwindow.h" -#include - -Debugger_VFPU::Debugger_VFPU(DebugInterface *_cpu, MainWindow* mainWindow_, QWidget *parent) : - QDialog(parent), - ui(new Ui::Debugger_VFPU), - cpu(_cpu), - mainWindow(mainWindow_) -{ - ui->setupUi(this); - - setWindowTitle(QString("VFPU - %1").arg(cpu->GetName())); - - ui->vfpu->setCPU(_cpu); -} - -Debugger_VFPU::~Debugger_VFPU() -{ - delete ui; -} - -void Debugger_VFPU::Update() -{ - ui->vfpu->redraw(); -} - -void Debugger_VFPU::Goto(u32 addr) -{ - show(); - mainWindow->GetDialogMemory()->Goto(addr & ~3); -} - -void Debugger_VFPU::on_comboBox_currentIndexChanged(int index) -{ - ui->vfpu->setMode(index); -} diff --git a/Qt/Debugger/debugger_vfpu.h b/Qt/Debugger/debugger_vfpu.h deleted file mode 100644 index e14fc73713..0000000000 --- a/Qt/Debugger/debugger_vfpu.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef DEBUGGER_VFPU_H -#define DEBUGGER_VFPU_H - -#include "Core/Debugger/DebugInterface.h" -#include - -class MainWindow; -namespace Ui { -class Debugger_VFPU; -} - -class Debugger_VFPU : public QDialog -{ - Q_OBJECT - -public: - explicit Debugger_VFPU(DebugInterface *_cpu, MainWindow *mainWindow_, QWidget *parent = 0); - ~Debugger_VFPU(); - - void Update(); - void Goto(u32 addr); -private slots: - void on_comboBox_currentIndexChanged(int index); - -private: - Ui::Debugger_VFPU *ui; - DebugInterface* cpu; - MainWindow* mainWindow; -}; - -#endif // DEBUGGER_VFPU_H diff --git a/Qt/Debugger/debugger_vfpu.ui b/Qt/Debugger/debugger_vfpu.ui deleted file mode 100644 index acf1f04dba..0000000000 --- a/Qt/Debugger/debugger_vfpu.ui +++ /dev/null @@ -1,87 +0,0 @@ - - - Debugger_VFPU - - - - 0 - 0 - 400 - 300 - - - - VFPU - - - - - - - - - Float - - - - - HalfFloat - - - - - Hex - - - - - Bytes - - - - - Shorts - - - - - Ints - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Qt::StrongFocus - - - - - - - - CtrlVfpuView - QWidget -
ctrlvfpuview.h
- 1 -
-
- - -
diff --git a/Qt/QtHost.h b/Qt/QtHost.h index 2df6155bb0..fbad0aa7ea 100644 --- a/Qt/QtHost.h +++ b/Qt/QtHost.h @@ -28,8 +28,6 @@ public: QtHost(MainWindow *mainWindow_) { mainWindow = mainWindow_; - m_GPUStep = false; - m_GPUFlag = 0; } virtual void UpdateUI() override { @@ -37,19 +35,12 @@ public: } virtual void UpdateMemView() override { - if(mainWindow->GetDialogMemory()) - mainWindow->GetDialogMemory()->Update(); } virtual void UpdateDisassembly() override { - if(mainWindow->GetDialogDisasm()) - mainWindow->GetDialogDisasm()->Update(); - if(mainWindow->GetDialogDisplaylist()) - mainWindow->GetDialogDisplaylist()->Update(); + mainWindow->updateMenus(); } virtual void SetDebugMode(bool mode) override { - if(mainWindow->GetDialogDisasm()) - mainWindow->GetDialogDisasm()->SetDebugMode(mode); } virtual bool InitGraphics(std::string *error_message, GraphicsContext **ctx) override { return true; } @@ -100,24 +91,9 @@ public: } bool GPUDebuggingActive() override { - auto dialogDisplayList = mainWindow->GetDialogDisplaylist(); - if (dialogDisplayList && dialogDisplayList->isVisible()) { - if (m_GPUStep && m_GPUFlag == -1) - m_GPUFlag = 0; - - return true; - } return false; } - void SetGPUStep(bool value, int flag = 0, u32 data = 0) { - m_GPUStep = value; - m_GPUFlag = flag; - m_GPUData = data; - } private: const char* SymbolMapFilename(std::string currentFilename); MainWindow* mainWindow; - bool m_GPUStep; - int m_GPUFlag; - u32 m_GPUData; }; diff --git a/Qt/QtMain.cpp b/Qt/QtMain.cpp index 5112c20435..ada30411ad 100644 --- a/Qt/QtMain.cpp +++ b/Qt/QtMain.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "ext/glslang/glslang/Public/ShaderLang.h" @@ -30,10 +31,13 @@ #include "gfx_es2/gpu_features.h" #include "math/math_util.h" #include "thread/threadutil.h" +#include "Core/Config.h" +#include "Core/ConfigValues.h" #include MainUI *emugl = NULL; +static int refreshRate = 60000; #ifdef SDL extern void mixaudio(void *userdata, Uint8 *stream, int len) { @@ -67,7 +71,7 @@ int System_GetPropertyInt(SystemProperty prop) { case SYSPROP_AUDIO_SAMPLE_RATE: return 44100; case SYSPROP_DISPLAY_REFRESH_RATE: - return 60000; + return refreshRate; case SYSPROP_DEVICE_TYPE: #if defined(__ANDROID__) return DEVICE_TYPE_MOBILE; @@ -491,6 +495,13 @@ Q_DECL_EXPORT #endif int main(int argc, char *argv[]) { + for (int i = 1; i < argc; i++) { + if (!strcmp(argv[i], "--version")) { + printf("%s\n", PPSSPP_GIT_VERSION); + return 0; + } + } + glslang::InitializeProcess(); #if defined(Q_OS_LINUX) QApplication::setAttribute(Qt::AA_X11InitThreads, true); @@ -507,6 +518,9 @@ int main(int argc, char *argv[]) g_dpi_scale_real_y = g_dpi_scale_y; dp_xres = (int)(pixel_xres * g_dpi_scale_x); dp_yres = (int)(pixel_yres * g_dpi_scale_y); + + refreshRate = (int)(a.primaryScreen()->refreshRate() * 1000); + std::string savegame_dir = "."; std::string external_dir = "."; #if QT_VERSION > QT_VERSION_CHECK(5, 0, 0) @@ -516,12 +530,7 @@ int main(int argc, char *argv[]) savegame_dir += "/"; external_dir += "/"; - bool fullscreenCLI=false; - for (int i = 1; i < argc; i++) { - if (!strcmp(argv[i],"--fullscreen")) - fullscreenCLI=true; - } - NativeInit(argc, (const char **)argv, savegame_dir.c_str(), external_dir.c_str(), nullptr, fullscreenCLI); + NativeInit(argc, (const char **)argv, savegame_dir.c_str(), external_dir.c_str(), nullptr); // TODO: Support other backends than GL, like Vulkan, in the Qt backend. g_Config.iGPUBackend = (int)GPUBackend::OPENGL; diff --git a/Qt/QtMain.h b/Qt/QtMain.h index 7f67794487..58fefb6cfe 100644 --- a/Qt/QtMain.h +++ b/Qt/QtMain.h @@ -38,6 +38,7 @@ QTM_USE_NAMESPACE #include "Common/GraphicsContext.h" #include "Core/Core.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/System.h" #include "thin3d/thin3d_create.h" #include "thin3d/GLRenderManager.h" @@ -110,6 +111,8 @@ public: explicit MainUI(QWidget *parent = 0); ~MainUI(); + void resizeGL(int w, int h); + public slots: QString InputBoxGetQString(QString title, QString defaultValue); @@ -123,7 +126,6 @@ protected: bool event(QEvent *e); void initializeGL(); - void resizeGL(int w, int h); void paintGL(); void updateAccelerometer(); diff --git a/Qt/desktop_assets.qrc b/Qt/desktop_assets.qrc deleted file mode 100644 index c65de91f71..0000000000 --- a/Qt/desktop_assets.qrc +++ /dev/null @@ -1,16 +0,0 @@ - - - ../assets/ui_atlas.zim - ../assets/ppge_atlas.zim - ../assets/lang - ../assets/compat.ini - ../assets/langregion.ini - ../assets/unknown.png - ../assets/rargray.png - ../assets/zip.png - ../assets/shaders - ../flash0/font - resources/breakpointDisable.ico - resources/breakpoint.ico - - diff --git a/Qt/mainwindow.cpp b/Qt/mainwindow.cpp index 116b7f084a..123f6605c8 100644 --- a/Qt/mainwindow.cpp +++ b/Qt/mainwindow.cpp @@ -20,11 +20,7 @@ MainWindow::MainWindow(QWidget *parent, bool fullscreen) : QMainWindow(parent), currentLanguage("en"), nextState(CORE_POWERDOWN), - lastUIState(UISTATE_MENU), - dialogDisasm(0), - memoryWindow(0), - memoryTexWindow(0), - displaylistWindow(0) + lastUIState(UISTATE_MENU) { QDesktopWidget *desktop = QApplication::desktop(); int screenNum = QProcessEnvironment::systemEnvironment().value("SDL_VIDEO_FULLSCREEN_HEAD", "0").toInt(); @@ -40,21 +36,12 @@ MainWindow::MainWindow(QWidget *parent, bool fullscreen) : createMenus(); updateMenus(); - SetWindowScale(-1); - - if(fullscreen) - fullscrAct(); + SetFullScreen(fullscreen); QObject::connect(emugl, SIGNAL(doubleClick()), this, SLOT(fullscrAct())); QObject::connect(emugl, SIGNAL(newFrame()), this, SLOT(newFrame())); } -void MainWindow::ShowMemory(u32 addr) -{ - if(memoryWindow) - memoryWindow->Goto(addr); -} - inline float clamp1(float x) { if (x > 1.0f) return 1.0f; if (x < -1.0f) return -1.0f; @@ -109,9 +96,6 @@ void MainWindow::updateMenus() foreach(QAction * action, displayLayoutGroup->actions()) { if (g_Config.iSmallDisplayZoomType == action->data().toInt()) { - - NativeMessageReceived("gpu_resized", ""); - action->setChecked(true); break; } @@ -145,18 +129,8 @@ void MainWindow::updateMenus() void MainWindow::bootDone() { - dialogDisasm = new Debugger_Disasm(currentDebugMIPS, this, this); - if(g_Config.bShowDebuggerOnLoad) - dialogDisasm->show(); - - if(g_Config.bFullScreen != isFullScreen()) - fullscrAct(); - - memoryWindow = new Debugger_Memory(currentDebugMIPS, this, this); - memoryTexWindow = new Debugger_MemoryTex(this); - displaylistWindow = new Debugger_DisplayList(currentDebugMIPS, gpu->GetDrawContext(), this, this); - - notifyMapsLoaded(); + if (g_Config.bFullScreen != isFullScreen()) + SetFullScreen(g_Config.bFullScreen); if (nextState == CORE_RUNNING) runAct(); @@ -177,26 +151,16 @@ void MainWindow::openAct() void MainWindow::closeAct() { - if(dialogDisasm) - dialogDisasm->Stop(); - - if(dialogDisasm && dialogDisasm->isVisible()) - dialogDisasm->close(); - if(memoryWindow && memoryWindow->isVisible()) - memoryWindow->close(); - if(memoryTexWindow && memoryTexWindow->isVisible()) - memoryTexWindow->close(); - if(displaylistWindow && displaylistWindow->isVisible()) - displaylistWindow->close(); + updateMenus(); NativeMessageReceived("stop", ""); SetGameTitle(""); } -void SaveStateActionFinished(bool result, const std::string &message, void *userdata) +void SaveStateActionFinished(SaveState::Status status, const std::string &message, void *userdata) { // TODO: Improve messaging? - if (!result) + if (status == SaveState::Status::FAILURE) { QMessageBox msgBox; msgBox.setWindowTitle("Load Save State"); @@ -266,17 +230,7 @@ void MainWindow::pauseAct() void MainWindow::resetAct() { - if(dialogDisasm) - dialogDisasm->Stop(); - - if(dialogDisasm) - dialogDisasm->close(); - if(memoryWindow) - memoryWindow->close(); - if(memoryTexWindow) - memoryTexWindow->close(); - if(displaylistWindow) - displaylistWindow->close(); + updateMenus(); NativeMessageReceived("reset", ""); } @@ -302,7 +256,6 @@ void MainWindow::lmapAct() { QString fileName = QFileInfo(fileNames[0]).absoluteFilePath(); g_symbolMap->LoadSymbolMap(fileName.toStdString().c_str()); - notifyMapsLoaded(); } } @@ -325,7 +278,6 @@ void MainWindow::smapAct() void MainWindow::resetTableAct() { g_symbolMap->Clear(); - notifyMapsLoaded(); } void MainWindow::dumpNextAct() @@ -333,74 +285,52 @@ void MainWindow::dumpNextAct() gpu->DumpNextFrame(); } -void MainWindow::disasmAct() -{ - if(dialogDisasm) - dialogDisasm->show(); -} - -void MainWindow::dpyListAct() -{ - if(displaylistWindow) - displaylistWindow->show(); -} - void MainWindow::consoleAct() { LogManager::GetInstance()->GetConsoleListener()->Show(LogManager::GetInstance()->GetConsoleListener()->Hidden()); } -void MainWindow::memviewAct() -{ - if (memoryWindow) - memoryWindow->show(); -} - -void MainWindow::memviewTexAct() -{ - if(memoryTexWindow) - memoryTexWindow->show(); -} - void MainWindow::raiseTopMost() { - setWindowState( (windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); raise(); - activateWindow(); - + activateWindow(); } -void MainWindow::fullscrAct() -{ - if(isFullScreen()) { - g_Config.bFullScreen = false; +void MainWindow::SetFullScreen(bool fullscreen) { + if (fullscreen) { + menuBar()->hide(); + + emugl->setFixedSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); + emugl->resizeGL(emugl->size().width(), emugl->size().height()); + // TODO: Won't showFullScreen do this for us? + setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); + setFixedSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); + + showFullScreen(); + InitPadLayout(dp_xres, dp_yres); + + if (GetUIState() == UISTATE_INGAME && !g_Config.bShowTouchControls) + QApplication::setOverrideCursor(QCursor(Qt::BlankCursor)); + } else { menuBar()->show(); updateMenus(); showNormal(); SetWindowScale(-1); InitPadLayout(dp_xres, dp_yres); + if (GetUIState() == UISTATE_INGAME && QApplication::overrideCursor()) QApplication::restoreOverrideCursor(); } - else { - g_Config.bFullScreen = true; - menuBar()->hide(); +} - emugl->setFixedSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); - setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); - setFixedSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); +void MainWindow::fullscrAct() +{ + // Toggle the current state. + g_Config.bFullScreen = !isFullScreen(); + SetFullScreen(g_Config.bFullScreen); - showFullScreen(); - - NativeMessageReceived("gpu_resized", ""); - InitPadLayout(dp_xres, dp_yres); - if (GetUIState() == UISTATE_INGAME && !g_Config.bShowTouchControls) - QApplication::setOverrideCursor(QCursor(Qt::BlankCursor)); - - } - QTimer::singleShot(1000, this, SLOT(raiseTopMost())); } @@ -452,8 +382,8 @@ void MainWindow::SetWindowScale(int zoom) { // Update to the specified factor. Let's clamp first. if (zoom < 1) zoom = 1; - if (zoom > 4) - zoom = 4; + if (zoom > 10) + zoom = 10; width = (g_Config.IsPortrait() ? 272 : 480) * zoom; height = (g_Config.IsPortrait() ? 480 : 272) * zoom; @@ -463,6 +393,7 @@ void MainWindow::SetWindowScale(int zoom) { g_Config.iWindowHeight = height; emugl->setFixedSize(g_Config.iWindowWidth, g_Config.iWindowHeight); + emugl->resizeGL(g_Config.iWindowWidth, g_Config.iWindowHeight); setFixedSize(sizeHint()); } @@ -537,17 +468,8 @@ void MainWindow::createMenus() debugMenu->add(new MenuAction(this, SLOT(takeScreen()), QT_TR_NOOP("Take Screenshot"), Qt::Key_F12)) ->addDisableState(UISTATE_MENU); debugMenu->addSeparator(); - debugMenu->add(new MenuAction(this, SLOT(disasmAct()), QT_TR_NOOP("Disassembly"), Qt::CTRL + Qt::Key_D)) - ->addDisableState(UISTATE_MENU); - //commented out until someone bothers to maintain it - //debugMenu->add(new MenuAction(this, SLOT(dpyListAct()), QT_TR_NOOP("Display List..."))) - // ->addDisableState(UISTATE_MENU); debugMenu->add(new MenuAction(this, SLOT(consoleAct()), QT_TR_NOOP("Log Console"))) ->addDisableState(UISTATE_MENU); - debugMenu->add(new MenuAction(this, SLOT(memviewAct()), QT_TR_NOOP("Memory View"))) - ->addDisableState(UISTATE_MENU); - debugMenu->add(new MenuAction(this, SLOT(memviewTexAct()),QT_TR_NOOP("Memory View Texture"))) - ->addDisableState(UISTATE_MENU); // Options MenuTree* optionsMenu = new MenuTree(this, menuBar(), QT_TR_NOOP("&Options")); @@ -574,8 +496,8 @@ void MainWindow::createMenus() // - Screen Size MenuTree* screenMenu = new MenuTree(this, videoMenu, QT_TR_NOOP("&Screen Size")); screenGroup = new MenuActionGroup(this, screenMenu, SLOT(screenGroup_triggered(QAction *)), - QStringList() << "1x" << "2x" << "3x" << "4x", - QList() << 1 << 2 << 3 << 4, + QStringList() << "1x" << "2x" << "3x" << "4x" << "5x" << "6x" << "7x" << "8x" << "9x" << "10x", + QList() << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9 << 10, QList() << Qt::CTRL + Qt::Key_1 << Qt::CTRL + Qt::Key_2 << Qt::CTRL + Qt::Key_3 << Qt::CTRL + Qt::Key_4); MenuTree* displayLayoutMenu = new MenuTree(this, videoMenu, QT_TR_NOOP("&Display Layout Options")); @@ -668,11 +590,3 @@ void MainWindow::createMenus() retranslate(); } - -void MainWindow::notifyMapsLoaded() -{ - if (dialogDisasm) - dialogDisasm->NotifyMapLoaded(); - if (memoryWindow) - memoryWindow->NotifyMapLoaded(); -} diff --git a/Qt/mainwindow.h b/Qt/mainwindow.h index ba2d3ff7dc..90ab051b91 100644 --- a/Qt/mainwindow.h +++ b/Qt/mainwindow.h @@ -12,10 +12,6 @@ #include "Core/Core.h" #include "Core/Config.h" #include "Core/System.h" -#include "Debugger/debugger_disasm.h" -#include "Debugger/debugger_memory.h" -#include "Debugger/debugger_memorytex.h" -#include "Debugger/debugger_displaylist.h" #include "Qt/QtMain.h" extern bool g_TakeScreenshot; @@ -34,16 +30,11 @@ class MainWindow : public QMainWindow Q_OBJECT public: - explicit MainWindow(QWidget *parent = 0, bool fullscreen=false); + explicit MainWindow(QWidget *parent = nullptr, bool fullscreen = false); ~MainWindow() { }; - Debugger_Disasm* GetDialogDisasm() { return dialogDisasm; } - Debugger_Memory* GetDialogMemory() { return memoryWindow; } - Debugger_MemoryTex* GetDialogMemoryTex() { return memoryTexWindow; } - Debugger_DisplayList* GetDialogDisplaylist() { return displaylistWindow; } CoreState GetNextState() { return nextState; } - void ShowMemory(u32 addr); void updateMenus(); void Notify(MainWindowMsg msg) { @@ -97,11 +88,7 @@ private slots: void resetTableAct(); void dumpNextAct(); void takeScreen() { g_TakeScreenshot = true; } - void disasmAct(); - void dpyListAct(); void consoleAct(); - void memviewAct(); - void memviewTexAct(); // Options // Core @@ -112,12 +99,18 @@ private slots: // Video void anisotropicGroup_triggered(QAction *action) { g_Config.iAnisotropyLevel = action->data().toInt(); } - void bufferRenderAct() { g_Config.iRenderingMode = !g_Config.iRenderingMode; } + void bufferRenderAct() { + g_Config.iRenderingMode = !g_Config.iRenderingMode; + NativeMessageReceived("gpu_resized", ""); + } void linearAct() { g_Config.iTexFiltering = (g_Config.iTexFiltering != 0) ? 0 : 3; } void screenGroup_triggered(QAction *action) { SetWindowScale(action->data().toInt()); } - void displayLayoutGroup_triggered(QAction *action) { g_Config.iSmallDisplayZoomType = action->data().toInt(); } + void displayLayoutGroup_triggered(QAction *action) { + g_Config.iSmallDisplayZoomType = action->data().toInt(); + NativeMessageReceived("gpu_resized", ""); + } void transformAct() { g_Config.bHardwareTransform = !g_Config.bHardwareTransform; } void vertexCacheAct() { g_Config.bVertexCache = !g_Config.bVertexCache; } void frameskipAct() { g_Config.iFrameSkip = !g_Config.iFrameSkip; } @@ -127,7 +120,10 @@ private slots: void fullscrAct(); void raiseTopMost(); - void statsAct() { g_Config.bShowDebugStats = !g_Config.bShowDebugStats; } + void statsAct() { + g_Config.bShowDebugStats = !g_Config.bShowDebugStats; + NativeMessageReceived("clear jit", ""); + } void showFPSAct() { g_Config.iShowFPSCounter = !g_Config.iShowFPSCounter; } // Logs @@ -157,9 +153,9 @@ private: void bootDone(); void SetWindowScale(int zoom); void SetGameTitle(QString text); + void SetFullScreen(bool fullscreen); void loadLanguage(const QString &language, bool retranslate); void createMenus(); - void notifyMapsLoaded(); QTranslator translator; QString currentLanguage; @@ -167,11 +163,6 @@ private: CoreState nextState; GlobalUIState lastUIState; - Debugger_Disasm *dialogDisasm; - Debugger_Memory *memoryWindow; - Debugger_MemoryTex *memoryTexWindow; - Debugger_DisplayList *displaylistWindow; - QActionGroup *anisotropicGroup, *screenGroup, *displayLayoutGroup, *defaultLogGroup, *g3dLogGroup, *hleLogGroup; diff --git a/SDL/SDLGLGraphicsContext.cpp b/SDL/SDLGLGraphicsContext.cpp index 3693c77c5a..42ed9a261b 100644 --- a/SDL/SDLGLGraphicsContext.cpp +++ b/SDL/SDLGLGraphicsContext.cpp @@ -1,13 +1,221 @@ +#include #include "SDLGLGraphicsContext.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/System.h" #include "base/NativeApp.h" #include "base/display.h" #include "gfx_es2/gpu_features.h" #include "thin3d/thin3d_create.h" +#if defined(USING_EGL) +#include "EGL/egl.h" +#endif + class GLRenderManager; +#if defined(USING_EGL) + +// TODO: Move these into the class. +static EGLDisplay g_eglDisplay = NULL; +static EGLContext g_eglContext = NULL; +static EGLSurface g_eglSurface = NULL; +#ifdef USING_FBDEV +static EGLNativeDisplayType g_Display = NULL; +#else +static Display* g_Display = NULL; +#endif +static NativeWindowType g_Window = (NativeWindowType)NULL; + +int8_t CheckEGLErrors(const std::string& file, uint16_t line) { + EGLenum error; + std::string errortext; + + error = eglGetError(); + switch (error) + { + case EGL_SUCCESS: case 0: return 0; + case EGL_NOT_INITIALIZED: errortext = "EGL_NOT_INITIALIZED"; break; + case EGL_BAD_ACCESS: errortext = "EGL_BAD_ACCESS"; break; + case EGL_BAD_ALLOC: errortext = "EGL_BAD_ALLOC"; break; + case EGL_BAD_ATTRIBUTE: errortext = "EGL_BAD_ATTRIBUTE"; break; + case EGL_BAD_CONTEXT: errortext = "EGL_BAD_CONTEXT"; break; + case EGL_BAD_CONFIG: errortext = "EGL_BAD_CONFIG"; break; + case EGL_BAD_CURRENT_SURFACE: errortext = "EGL_BAD_CURRENT_SURFACE"; break; + case EGL_BAD_DISPLAY: errortext = "EGL_BAD_DISPLAY"; break; + case EGL_BAD_SURFACE: errortext = "EGL_BAD_SURFACE"; break; + case EGL_BAD_MATCH: errortext = "EGL_BAD_MATCH"; break; + case EGL_BAD_PARAMETER: errortext = "EGL_BAD_PARAMETER"; break; + case EGL_BAD_NATIVE_PIXMAP: errortext = "EGL_BAD_NATIVE_PIXMAP"; break; + case EGL_BAD_NATIVE_WINDOW: errortext = "EGL_BAD_NATIVE_WINDOW"; break; + default: errortext = "unknown"; break; + } + printf( "ERROR: EGL Error detected in file %s at line %d: %s (0x%X)\n", file.c_str(), line, errortext.c_str(), error ); + return 1; +} + +#define EGL_ERROR(str, check) { \ + if (check) CheckEGLErrors( __FILE__, __LINE__ ); \ + printf("EGL ERROR: " str "\n"); \ + return 1; \ + } + +int8_t EGL_Open() { +#ifdef USING_FBDEV + g_Display = ((EGLNativeDisplayType)0); +#else + if ((g_Display = XOpenDisplay(NULL)) == NULL) + EGL_ERROR("Unable to get display!", false); +#endif + if ((g_eglDisplay = eglGetDisplay((NativeDisplayType)g_Display)) == EGL_NO_DISPLAY) + EGL_ERROR("Unable to create EGL display.", true); + if (eglInitialize(g_eglDisplay, NULL, NULL) != EGL_TRUE) + EGL_ERROR("Unable to initialize EGL display.", true); + return 0; +} + +#ifndef EGL_OPENGL_ES3_BIT_KHR +#define EGL_OPENGL_ES3_BIT_KHR (1 << 6) +#endif + +EGLConfig EGL_FindConfig(int *contextVersion) { + std::vector configs; + EGLint numConfigs = 0; + + EGLBoolean result = eglGetConfigs(g_eglDisplay, nullptr, 0, &numConfigs); + if (result != EGL_TRUE || numConfigs == 0) { + return nullptr; + } + + configs.resize(numConfigs); + result = eglGetConfigs(g_eglDisplay, &configs[0], numConfigs, &numConfigs); + if (result != EGL_TRUE || numConfigs == 0) { + return nullptr; + } + + // Mali (ARM) seems to have compositing issues with alpha backbuffers. + // EGL_TRANSPARENT_TYPE doesn't help. + const char *vendorName = eglQueryString(g_eglDisplay, EGL_VENDOR); + const bool avoidAlphaGLES = vendorName && !strcmp(vendorName, "ARM"); + + EGLConfig best = nullptr; + int bestScore = 0; + int bestContextVersion = 0; + for (const EGLConfig &config : configs) { + auto readConfig = [&](EGLint attr) -> EGLint { + EGLint val = 0; + eglGetConfigAttrib(g_eglDisplay, config, attr, &val); + return val; + }; + + int colorScore = readConfig(EGL_RED_SIZE) + readConfig(EGL_BLUE_SIZE) + readConfig(EGL_GREEN_SIZE); + int alphaScore = readConfig(EGL_ALPHA_SIZE); + int depthScore = readConfig(EGL_DEPTH_SIZE); + int levelScore = readConfig(EGL_LEVEL) == 0 ? 100 : 0; + int samplesScore = readConfig(EGL_SAMPLES) == 0 ? 100 : 0; + int sampleBufferScore = readConfig(EGL_SAMPLE_BUFFERS) == 0 ? 100 : 0; + int stencilScore = readConfig(EGL_STENCIL_SIZE); + int transparentScore = readConfig(EGL_TRANSPARENT_TYPE) == EGL_NONE ? 50 : 0; + + EGLint caveat = readConfig(EGL_CONFIG_CAVEAT); + int caveatScore = caveat == EGL_NONE ? 100 : (caveat == EGL_NON_CONFORMANT_CONFIG ? 50 : 0); + + EGLint renderable = readConfig(EGL_RENDERABLE_TYPE); + bool renderableGLES3 = (renderable & EGL_OPENGL_ES3_BIT_KHR) != 0; + bool renderableGLES2 = (renderable & EGL_OPENGL_ES2_BIT) != 0; + bool renderableGL = (renderable & EGL_OPENGL_BIT) != 0; +#ifdef USING_GLES2 + int renderableScoreGLES = renderableGLES3 ? 100 : (renderableGLES2 ? 80 : 0); + int renderableScoreGL = 0; +#else + int renderableScoreGLES = 0; + int renderableScoreGL = renderableGL ? 100 : (renderableGLES3 ? 80 : 0); +#endif + + if (avoidAlphaGLES && renderableScoreGLES > 0) { + alphaScore = 8 - alphaScore; + } + + int score = 0; + // Here's a good place to play with the weights to pick a better config. + score += (colorScore + alphaScore) * 10; + score += depthScore * 5 + stencilScore; + score += levelScore + samplesScore + sampleBufferScore + transparentScore; + score += caveatScore + renderableScoreGLES + renderableScoreGL; + + if (score > bestScore) { + bestScore = score; + best = config; + bestContextVersion = renderableGLES3 ? 3 : (renderableGLES2 ? 2 : 0); + } + } + + *contextVersion = bestContextVersion; + return best; +} + +int8_t EGL_Init() { + int contextVersion = 0; + EGLConfig eglConfig = EGL_FindConfig(&contextVersion); + if (!eglConfig) { + EGL_ERROR("Unable to find a usable EGL config.", true); + } + + EGLint contextAttributes[] = { + EGL_CONTEXT_CLIENT_VERSION, contextVersion, + EGL_NONE, + }; + if (contextVersion == 0) { + contextAttributes[0] = EGL_NONE; + } + + g_eglContext = eglCreateContext(g_eglDisplay, eglConfig, nullptr, contextAttributes); + if (g_eglContext == EGL_NO_CONTEXT) { + EGL_ERROR("Unable to create GLES context!", true); + } + +#if !defined(USING_FBDEV) && !defined(__APPLE__) + //Get the SDL window handle + SDL_SysWMinfo sysInfo; //Will hold our Window information + SDL_VERSION(&sysInfo.version); //Set SDL version + g_Window = (NativeWindowType)sysInfo.info.x11.window; +#else + g_Window = (NativeWindowType)NULL; +#endif + g_eglSurface = eglCreateWindowSurface(g_eglDisplay, eglConfig, g_Window, nullptr); + if (g_eglSurface == EGL_NO_SURFACE) + EGL_ERROR("Unable to create EGL surface!", true); + + if (eglMakeCurrent(g_eglDisplay, g_eglSurface, g_eglSurface, g_eglContext) != EGL_TRUE) + EGL_ERROR("Unable to make GLES context current.", true); + + return 0; +} + +void EGL_Close() { + if (g_eglDisplay != NULL) { + eglMakeCurrent(g_eglDisplay, NULL, NULL, EGL_NO_CONTEXT); + if (g_eglContext != NULL) { + eglDestroyContext(g_eglDisplay, g_eglContext); + } + if (g_eglSurface != NULL) { + eglDestroySurface(g_eglDisplay, g_eglSurface); + } + eglTerminate(g_eglDisplay); + g_eglDisplay = NULL; + } + if (g_Display != NULL) { +#if !defined(USING_FBDEV) + XCloseDisplay(g_Display); +#endif + g_Display = NULL; + } + g_eglSurface = NULL; + g_eglContext = NULL; +} + +#endif // USING_EGL + // Returns 0 on success. int SDLGLGraphicsContext::Init(SDL_Window *&window, int x, int y, int mode, std::string *error_message) { struct GLVersionPair { @@ -23,11 +231,10 @@ int SDLGLGraphicsContext::Init(SDL_Window *&window, int x, int y, int mode, std: #endif }; -#ifdef USING_GLES2 - mode |= SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN; -#else - mode |= SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE; -#endif + // We start hidden because we have to try several windows. + // On Mac, full screen animates so each attempt is slow. + mode |= SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN; + SDL_GLContext glContext = nullptr; for (size_t i = 0; i < ARRAY_SIZE(attemptVersions); ++i) { const auto &ver = attemptVersions[i]; @@ -45,8 +252,9 @@ int SDLGLGraphicsContext::Init(SDL_Window *&window, int x, int y, int mode, std: window = SDL_CreateWindow("PPSSPP", x,y, pixel_xres, pixel_yres, mode); if (!window) { - NativeShutdown(); - fprintf(stderr, "SDL_CreateWindow failed: %s\n", SDL_GetError()); + // Definitely don't shutdown here: we'll keep trying more GL versions. + fprintf(stderr, "SDL_CreateWindow failed for GL %d.%d: %s\n", ver.major, ver.minor, SDL_GetError()); + // Skip the DestroyWindow. continue; } @@ -84,6 +292,9 @@ int SDLGLGraphicsContext::Init(SDL_Window *&window, int x, int y, int mode, std: } } + // At this point, we have a window that we can show finally. + SDL_ShowWindow(window); + #ifdef USING_EGL EGL_Init(); #endif diff --git a/SDL/SDLGLGraphicsContext.h b/SDL/SDLGLGraphicsContext.h index 2695ebf64d..e0cb354701 100644 --- a/SDL/SDLGLGraphicsContext.h +++ b/SDL/SDLGLGraphicsContext.h @@ -7,6 +7,11 @@ #include "gfx/gl_common.h" #include "Common/GraphicsContext.h" +// TODO: Move this to a better place. +#if defined(USING_EGL) +int8_t EGL_Open(); +#endif + class SDLGLGraphicsContext : public DummyGraphicsContext { public: SDLGLGraphicsContext() { diff --git a/SDL/SDLMain.cpp b/SDL/SDLMain.cpp index 5e814d05dc..85b49825a1 100644 --- a/SDL/SDLMain.cpp +++ b/SDL/SDLMain.cpp @@ -48,13 +48,10 @@ SDLJoystick *joystick = NULL; #include #endif -#if defined(USING_EGL) -#include "EGL/egl.h" -#endif - #include "Core/System.h" #include "Core/Core.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Common/GraphicsContext.h" #include "SDLGLGraphicsContext.h" #include "SDLVulkanGraphicsContext.h" @@ -70,135 +67,6 @@ static int g_QuitRequested = 0; static int g_DesktopWidth = 0; static int g_DesktopHeight = 0; -#if defined(USING_EGL) - -static EGLDisplay g_eglDisplay = NULL; -static EGLContext g_eglContext = NULL; -static EGLSurface g_eglSurface = NULL; -#ifdef USING_FBDEV -static EGLNativeDisplayType g_Display = NULL; -#else -static Display* g_Display = NULL; -#endif -static NativeWindowType g_Window = (NativeWindowType)NULL; - -int8_t CheckEGLErrors(const std::string& file, uint16_t line) { - EGLenum error; - std::string errortext; - - error = eglGetError(); - switch (error) - { - case EGL_SUCCESS: case 0: return 0; - case EGL_NOT_INITIALIZED: errortext = "EGL_NOT_INITIALIZED"; break; - case EGL_BAD_ACCESS: errortext = "EGL_BAD_ACCESS"; break; - case EGL_BAD_ALLOC: errortext = "EGL_BAD_ALLOC"; break; - case EGL_BAD_ATTRIBUTE: errortext = "EGL_BAD_ATTRIBUTE"; break; - case EGL_BAD_CONTEXT: errortext = "EGL_BAD_CONTEXT"; break; - case EGL_BAD_CONFIG: errortext = "EGL_BAD_CONFIG"; break; - case EGL_BAD_CURRENT_SURFACE: errortext = "EGL_BAD_CURRENT_SURFACE"; break; - case EGL_BAD_DISPLAY: errortext = "EGL_BAD_DISPLAY"; break; - case EGL_BAD_SURFACE: errortext = "EGL_BAD_SURFACE"; break; - case EGL_BAD_MATCH: errortext = "EGL_BAD_MATCH"; break; - case EGL_BAD_PARAMETER: errortext = "EGL_BAD_PARAMETER"; break; - case EGL_BAD_NATIVE_PIXMAP: errortext = "EGL_BAD_NATIVE_PIXMAP"; break; - case EGL_BAD_NATIVE_WINDOW: errortext = "EGL_BAD_NATIVE_WINDOW"; break; - default: errortext = "unknown"; break; - } - printf( "ERROR: EGL Error detected in file %s at line %d: %s (0x%X)\n", file.c_str(), line, errortext.c_str(), error ); - return 1; -} -#define EGL_ERROR(str, check) { \ - if (check) CheckEGLErrors( __FILE__, __LINE__ ); \ - printf("EGL ERROR: " str "\n"); \ - return 1; \ - } - -int8_t EGL_Open() { -#ifdef USING_FBDEV - g_Display = ((EGLNativeDisplayType)0); -#else - if ((g_Display = XOpenDisplay(NULL)) == NULL) - EGL_ERROR("Unable to get display!", false); -#endif - if ((g_eglDisplay = eglGetDisplay((NativeDisplayType)g_Display)) == EGL_NO_DISPLAY) - EGL_ERROR("Unable to create EGL display.", true); - if (eglInitialize(g_eglDisplay, NULL, NULL) != EGL_TRUE) - EGL_ERROR("Unable to initialize EGL display.", true); - return 0; -} - -int8_t EGL_Init() { - EGLConfig g_eglConfig; - EGLint g_numConfigs = 0; - EGLint attrib_list[]= { - // TODO: Should cycle through fallbacks, like on Android -#ifdef USING_FBDEV - EGL_RED_SIZE, 5, - EGL_GREEN_SIZE, 6, - EGL_BLUE_SIZE, 5, -#endif - EGL_DEPTH_SIZE, 16, - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, -#ifdef USING_GLES2 - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, -#else - EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, -#endif - EGL_SAMPLE_BUFFERS, 0, - EGL_SAMPLES, 0, - EGL_NONE}; - - const EGLint attributes[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; - - EGLBoolean result = eglChooseConfig(g_eglDisplay, attrib_list, &g_eglConfig, 1, &g_numConfigs); - if (result != EGL_TRUE || g_numConfigs == 0) EGL_ERROR("Unable to query for available configs.", true); - - g_eglContext = eglCreateContext(g_eglDisplay, g_eglConfig, NULL, attributes ); - if (g_eglContext == EGL_NO_CONTEXT) EGL_ERROR("Unable to create GLES context!", true); - -#if !defined(USING_FBDEV) && !defined(__APPLE__) - //Get the SDL window handle - SDL_SysWMinfo sysInfo; //Will hold our Window information - SDL_VERSION(&sysInfo.version); //Set SDL version - g_Window = (NativeWindowType)sysInfo.info.x11.window; -#else - g_Window = (NativeWindowType)NULL; -#endif - g_eglSurface = eglCreateWindowSurface(g_eglDisplay, g_eglConfig, g_Window, 0); - if (g_eglSurface == EGL_NO_SURFACE) - EGL_ERROR("Unable to create EGL surface!", true); - - if (eglMakeCurrent(g_eglDisplay, g_eglSurface, g_eglSurface, g_eglContext) != EGL_TRUE) - EGL_ERROR("Unable to make GLES context current.", true); - - return 0; -} - -void EGL_Close() { - if (g_eglDisplay != NULL) { - eglMakeCurrent(g_eglDisplay, NULL, NULL, EGL_NO_CONTEXT); - if (g_eglContext != NULL) { - eglDestroyContext(g_eglDisplay, g_eglContext); - } - if (g_eglSurface != NULL) { - eglDestroySurface(g_eglDisplay, g_eglSurface); - } - eglTerminate(g_eglDisplay); - g_eglDisplay = NULL; - } - if (g_Display != NULL) { -#if !defined(USING_FBDEV) - XCloseDisplay(g_Display); -#endif - g_Display = NULL; - } - g_eglSurface = NULL; - g_eglContext = NULL; -} -#endif - - int getDisplayNumber(void) { int displayNumber = 0; char * displayNumberStr; @@ -448,6 +316,13 @@ static void EmuThreadJoin() { #undef main #endif int main(int argc, char *argv[]) { + for (int i = 1; i < argc; i++) { + if (!strcmp(argv[i], "--version")) { + printf("%s\n", PPSSPP_GIT_VERSION); + return 0; + } + } + glslang::InitializeProcess(); #if PPSSPP_PLATFORM(RPI) @@ -548,6 +423,13 @@ int main(int argc, char *argv[]) { mode |= SDL_WINDOW_FULLSCREEN_DESKTOP; } + // If we're on mobile, don't try for windowed either. +#if defined(USING_GLES2) || defined(MOBILE_DEVICE) + mode |= SDL_WINDOW_FULLSCREEN; +#else + mode |= SDL_WINDOW_RESIZABLE; +#endif + if (mode & SDL_WINDOW_FULLSCREEN_DESKTOP) { pixel_xres = g_DesktopWidth; pixel_yres = g_DesktopHeight; @@ -721,11 +603,9 @@ int main(int argc, char *argv[]) { Uint32 window_flags = SDL_GetWindowFlags(window); bool fullscreen = (window_flags & SDL_WINDOW_FULLSCREEN); - pixel_xres = event.window.data1; - pixel_yres = event.window.data2; - dp_xres = (float)pixel_xres * dpi_scale; - dp_yres = (float)pixel_yres * dpi_scale; - NativeResized(); + if (UpdateScreenScale(event.window.data1, event.window.data2)) { + NativeMessageReceived("gpu_resized", ""); + } // Set variable here in case fullscreen was toggled by hotkey g_Config.bFullScreen = fullscreen; @@ -879,7 +759,7 @@ int main(int argc, char *argv[]) { lastUIState = GetUIState(); if (lastUIState == UISTATE_INGAME && g_Config.bFullScreen && !g_Config.bShowTouchControls) SDL_ShowCursor(SDL_DISABLE); - if (lastUIState != UISTATE_INGAME && g_Config.bFullScreen) + if (lastUIState != UISTATE_INGAME || !g_Config.bFullScreen) SDL_ShowCursor(SDL_ENABLE); } #endif diff --git a/SDL/SDLVulkanGraphicsContext.cpp b/SDL/SDLVulkanGraphicsContext.cpp index 898d2c3a2a..a29d41ac3e 100644 --- a/SDL/SDLVulkanGraphicsContext.cpp +++ b/SDL/SDLVulkanGraphicsContext.cpp @@ -1,4 +1,5 @@ #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "base/NativeApp.h" #include "base/display.h" #include "thin3d/thin3d.h" diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index 27e7a4f4d2..db2651e747 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -30,6 +30,7 @@ #include "Core/MemMap.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/System.h" #include "Core/CoreParameter.h" #include "Core/MIPS/MIPSTables.h" diff --git a/UI/DisplayLayoutScreen.cpp b/UI/DisplayLayoutScreen.cpp index 4e719533ed..0ba1901811 100644 --- a/UI/DisplayLayoutScreen.cpp +++ b/UI/DisplayLayoutScreen.cpp @@ -26,6 +26,7 @@ #include "DisplayLayoutScreen.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/System.h" #include "DisplayLayoutEditor.h" #include "GPU/Common/FramebufferCommon.h" @@ -72,7 +73,7 @@ bool DisplayLayoutScreen::touch(const TouchInput &touch) { using namespace UI; int mode = mode_ ? mode_->GetSelection() : 0; - if (g_Config.iSmallDisplayZoomType == 2) { mode = -1; } + if (g_Config.iSmallDisplayZoomType == (int)SmallDisplayZoom::AUTO) { mode = -1; } const Bounds &screen_bounds = screenManager()->getUIContext()->GetBounds(); if ((touch.flags & TOUCH_MOVE) && picked_ != 0) { @@ -185,7 +186,7 @@ UI::EventReturn DisplayLayoutScreen::OnCenter(UI::EventParams &e) { }; UI::EventReturn DisplayLayoutScreen::OnZoomTypeChange(UI::EventParams &e) { - if (g_Config.iSmallDisplayZoomType < 3) { + if (g_Config.iSmallDisplayZoomType < (int)SmallDisplayZoom::MANUAL) { const Bounds &bounds = screenManager()->getUIContext()->GetBounds(); float autoBound = bounds.w / 480.0f; g_Config.fSmallDisplayZoomLevel = autoBound; @@ -268,8 +269,8 @@ void DisplayLayoutScreen::CreateViews() { HighlightLabel *label = nullptr; mode_ = nullptr; - if (g_Config.iSmallDisplayZoomType > 1) { // Scaling - if (g_Config.iSmallDisplayZoomType == 2) { // Auto Scaling + if (g_Config.iSmallDisplayZoomType >= (int)SmallDisplayZoom::AUTO) { // Scaling + if (g_Config.iSmallDisplayZoomType == (int)SmallDisplayZoom::AUTO) { label = new HighlightLabel(gr->T("Auto Scaling"), new AnchorLayoutParams(WRAP_CONTENT, 64.0f, local_dp_xres / 2.0f, local_dp_yres / 2.0f, NONE, NONE, true)); float autoBound = local_dp_yres / 270.0f; // Case of screen rotated ~ only works with buffered rendering @@ -311,7 +312,7 @@ void DisplayLayoutScreen::CreateViews() { displayRepresentation_->SetVisibility(V_INVISIBLE); float width = previewWidth; float height = previewHeight; - if (g_Config.iSmallDisplayZoomType == 0) { // Stretched + if (g_Config.iSmallDisplayZoomType == (int)SmallDisplayZoom::STRETCH) { Choice *stretched = new Choice("", "", false, new AnchorLayoutParams(width, height, width - width / 2.0f, NONE, NONE, height - height / 2.0f)); stretched->SetEnabled(false); root_->Add(stretched); @@ -320,12 +321,12 @@ void DisplayLayoutScreen::CreateViews() { float frameRatio = width / height; if (origRatio > frameRatio) { height = width / origRatio; - if (!bRotated && g_Config.iSmallDisplayZoomType == 1) { + if (!bRotated && g_Config.iSmallDisplayZoomType == (int)SmallDisplayZoom::PARTIAL_STRETCH) { height = (272.0f + height) / 2.0f; } } else { width = height * origRatio; - if (bRotated && g_Config.iSmallDisplayZoomType == 1) { + if (bRotated && g_Config.iSmallDisplayZoomType == (int)SmallDisplayZoom::PARTIAL_STRETCH) { width = (272.0f + height) / 2.0f; } } diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index db790eb516..b84a2e7efc 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -41,6 +41,7 @@ #include "Core/AVIDump.h" #endif #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/CoreTiming.h" #include "Core/CoreParameter.h" #include "Core/Core.h" @@ -224,7 +225,7 @@ void EmuScreen::bootGame(const std::string &filename) { coreParam.fileToStart = filename; coreParam.mountIso = ""; coreParam.mountRoot = ""; - coreParam.startPaused = false; + coreParam.startBreak = !g_Config.bAutoRun; coreParam.printfEmuLog = false; coreParam.headLess = false; @@ -346,14 +347,14 @@ void EmuScreen::dialogFinished(const Screen *dialog, DialogResult result) { RecreateViews(); } -static void AfterSaveStateAction(bool success, const std::string &message, void *) { +static void AfterSaveStateAction(SaveState::Status status, const std::string &message, void *) { if (!message.empty()) { - osm.Show(message, 2.0); + osm.Show(message, status == SaveState::Status::SUCCESS ? 2.0 : 5.0); } } -static void AfterStateBoot(bool success, const std::string &message, void *ignored) { - AfterSaveStateAction(success, message, ignored); +static void AfterStateBoot(SaveState::Status status, const std::string &message, void *ignored) { + AfterSaveStateAction(status, message, ignored); Core_EnableStepping(false); host->UpdateDisassembly(); } @@ -399,15 +400,15 @@ void EmuScreen::sendMessage(const char *message, const char *value) { // In case we need to position touch controls differently. RecreateViews(); } else if (!strcmp(message, "control mapping") && screenManager()->topScreen() == this) { - UpdateUIState(UISTATE_MENU); + UpdateUIState(UISTATE_PAUSEMENU); releaseButtons(); screenManager()->push(new ControlMappingScreen()); } else if (!strcmp(message, "display layout editor") && screenManager()->topScreen() == this) { - UpdateUIState(UISTATE_MENU); + UpdateUIState(UISTATE_PAUSEMENU); releaseButtons(); screenManager()->push(new DisplayLayoutScreen()); } else if (!strcmp(message, "settings") && screenManager()->topScreen() == this) { - UpdateUIState(UISTATE_MENU); + UpdateUIState(UISTATE_PAUSEMENU); releaseButtons(); screenManager()->push(new GameSettingsScreen(gamePath_)); } else if (!strcmp(message, "gpu dump next frame")) { @@ -464,20 +465,35 @@ void EmuScreen::onVKeyDown(int virtualKeyCode) { switch (virtualKeyCode) { case VIRTKEY_UNTHROTTLE: + if (coreState == CORE_STEPPING) { + Core_EnableStepping(false); + } PSP_CoreParameter().unthrottle = true; break; case VIRTKEY_SPEED_TOGGLE: - if (!g_Config.bUnToggleFpsLimit) { - if (PSP_CoreParameter().fpsLimit == 0) { - PSP_CoreParameter().fpsLimit = 1; - osm.Show(sc->T("fixed", "Speed: alternate"), 1.0); - } else if (PSP_CoreParameter().fpsLimit == 1) { - PSP_CoreParameter().fpsLimit = 0; - osm.Show(sc->T("standard", "Speed: standard"), 1.0); - } - } else { - PSP_CoreParameter().fpsLimit = 1; + // Cycle through enabled speeds. + if (PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL && g_Config.iFpsLimit1 >= 0) { + PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM1; + } else if (PSP_CoreParameter().fpsLimit != FPSLimit::CUSTOM2 && g_Config.iFpsLimit2 >= 0) { + PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM2; + osm.Show(sc->T("SpeedCustom2", "Speed: alternate 2"), 1.0); + } else if (PSP_CoreParameter().fpsLimit != FPSLimit::NORMAL) { + PSP_CoreParameter().fpsLimit = FPSLimit::NORMAL; + osm.Show(sc->T("standard", "Speed: standard"), 1.0); + } + break; + + case VIRTKEY_SPEED_CUSTOM1: + if (PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL) { + PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM1; + osm.Show(sc->T("fixed", "Speed: alternate"), 1.0); + } + break; + case VIRTKEY_SPEED_CUSTOM2: + if (PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL) { + PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM2; + osm.Show(sc->T("SpeedCustom2", "Speed: alternate 2"), 1.0); } break; @@ -580,14 +596,23 @@ void EmuScreen::onVKeyDown(int virtualKeyCode) { } void EmuScreen::onVKeyUp(int virtualKeyCode) { + I18NCategory *sc = GetI18NCategory("Screen"); + switch (virtualKeyCode) { case VIRTKEY_UNTHROTTLE: PSP_CoreParameter().unthrottle = false; break; - case VIRTKEY_SPEED_TOGGLE: - if (g_Config.bUnToggleFpsLimit) { - PSP_CoreParameter().fpsLimit = 0; + case VIRTKEY_SPEED_CUSTOM1: + if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM1) { + PSP_CoreParameter().fpsLimit = FPSLimit::NORMAL; + osm.Show(sc->T("standard", "Speed: standard"), 1.0); + } + break; + case VIRTKEY_SPEED_CUSTOM2: + if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM2) { + PSP_CoreParameter().fpsLimit = FPSLimit::NORMAL; + osm.Show(sc->T("standard", "Speed: standard"), 1.0); } break; @@ -888,13 +913,16 @@ public: // PIC1 is the loading image, so let's only draw if it's available. if (ginfo && ginfo->pic1.texture) { - dc.GetDrawContext()->BindTexture(0, ginfo->pic1.texture->GetTexture()); + Draw::Texture *texture = ginfo->pic1.texture->GetTexture(); + if (texture) { + dc.GetDrawContext()->BindTexture(0, texture); - double loadTime = ginfo->pic1.timeLoaded; - uint32_t color = alphaMul(color_, ease((time_now_d() - loadTime) * 3)); - dc.Draw()->DrawTexRect(dc.GetBounds(), 0, 0, 1, 1, color); - dc.Flush(); - dc.RebindTexture(); + double loadTime = ginfo->pic1.timeLoaded; + uint32_t color = alphaMul(color_, ease((time_now_d() - loadTime) * 3)); + dc.Draw()->DrawTexRect(dc.GetBounds(), 0, 0, 1, 1, color); + dc.Flush(); + dc.RebindTexture(); + } } } @@ -929,9 +957,7 @@ void EmuScreen::CreateViews() { GameInfoBGView *loadingBG = root_->Add(new GameInfoBGView(gamePath_, new AnchorLayoutParams(FILL_PARENT, FILL_PARENT))); TextView *loadingTextView = root_->Add(new TextView(sc->T(PSP_GetLoading()), new AnchorLayoutParams(bounds.centerX(), NONE, NONE, 40, true))); - - // Don't really need this, and it creates a lot of strings to translate... - loadingTextView->SetVisibility(V_INVISIBLE); + loadingTextView_ = loadingTextView; static const int symbols[4] = { I_CROSS, @@ -941,10 +967,17 @@ void EmuScreen::CreateViews() { }; Spinner *loadingSpinner = root_->Add(new Spinner(symbols, ARRAY_SIZE(symbols), new AnchorLayoutParams(NONE, NONE, 45, 45, true))); loadingSpinner_ = loadingSpinner; - loadingTextView->SetShadow(true); - loadingTextView_ = loadingTextView; - loadingViewColor_ = loadingTextView->AddTween(new CallbackColorTween(0x00FFFFFF, 0x00FFFFFF, 0.2f, &bezierEaseInOut)); + loadingBG->SetTag("LoadingBG"); + loadingTextView->SetTag("LoadingText"); + loadingSpinner->SetTag("LoadingSpinner"); + + // Don't really need this, and it creates a lot of strings to translate... + // Maybe just show "Loading game..." only? + loadingTextView->SetVisibility(V_GONE); + loadingTextView->SetShadow(true); + + loadingViewColor_ = loadingSpinner->AddTween(new CallbackColorTween(0x00FFFFFF, 0x00FFFFFF, 0.2f, &bezierEaseInOut)); loadingViewColor_->SetCallback([loadingBG, loadingTextView, loadingSpinner](View *v, uint32_t c) { loadingBG->SetColor(c & 0xFFC0C0C0); loadingTextView->SetTextColor(c); @@ -953,15 +986,18 @@ void EmuScreen::CreateViews() { loadingViewColor_->Persist(); // We start invisible here, in case of recreated views. - loadingViewVisible_ = loadingTextView->AddTween(new VisibilityTween(UI::V_INVISIBLE, UI::V_INVISIBLE, 0.2f, &bezierEaseInOut)); + loadingViewVisible_ = loadingSpinner->AddTween(new VisibilityTween(UI::V_INVISIBLE, UI::V_INVISIBLE, 0.2f, &bezierEaseInOut)); loadingViewVisible_->Persist(); - loadingViewVisible_->Finish.Add([loadingBG](EventParams &p) { + loadingViewVisible_->Finish.Add([loadingBG, loadingSpinner](EventParams &p) { loadingBG->SetVisibility(p.v->GetVisibility()); // If we just became invisible, flush BGs since we don't need them anymore. // Saves some VRAM for the game, but don't do it before we fade out... if (p.v->GetVisibility() == V_INVISIBLE) { g_gameInfoCache->FlushBGs(); + // And we can go away too. This means the tween will never run again. + loadingBG->SetVisibility(V_GONE); + loadingSpinner->SetVisibility(V_GONE); } return EVENT_DONE; }); @@ -1024,11 +1060,6 @@ void EmuScreen::update() { // Virtual keys. __CtrlSetRapidFire(virtKeys[VIRTKEY_RAPID_FIRE - VIRTKEY_FIRST]); - // Make sure fpsLimit starts at 0 - if (PSP_CoreParameter().fpsLimit != 0 && PSP_CoreParameter().fpsLimit != 1) { - PSP_CoreParameter().fpsLimit = 0; - } - // This is here to support the iOS on screen back button. if (pauseTrigger_) { pauseTrigger_ = false; @@ -1137,6 +1168,11 @@ void EmuScreen::preRender() { using namespace Draw; DrawContext *draw = screenManager()->getDrawContext(); draw->BeginFrame(); + // Let's be consistent for the entire frame. We skip the UI texture if we don't need it. + hasVisibleUI_ = hasVisibleUI(); + if (hasVisibleUI_) { + screenManager()->getUIContext()->BeginFrame(); + } // Here we do NOT bind the backbuffer or clear the screen, unless non-buffered. // The emuscreen is different than the others - we really want to allow the game to render to framebuffers // before we ever bind the backbuffer for rendering. On mobile GPUs, switching back and forth between render @@ -1178,6 +1214,8 @@ void EmuScreen::render() { using namespace Draw; DrawContext *thin3d = screenManager()->getDrawContext(); + if (!thin3d) + return; // shouldn't really happen but I've seen a suspicious stack trace.. if (invalid_) { // Loading, or after shutdown? @@ -1207,22 +1245,20 @@ void EmuScreen::render() { PSP_BeginHostFrame(); - // We just run the CPU until we get to vblank. This will quickly sync up pretty nicely. - // The actual number of cycles doesn't matter so much here as we will break due to CORE_NEXTFRAME, most of the time hopefully... - int blockTicks = usToCycles(1000000 / 10); - - // Run until CORE_NEXTFRAME - while (coreState == CORE_RUNNING) { - PSP_RunLoopFor(blockTicks); - } + PSP_RunLoopWhileState(); // Hopefully coreState is now CORE_NEXTFRAME if (coreState == CORE_NEXTFRAME) { // set back to running for the next frame coreState = CORE_RUNNING; } else if (coreState == CORE_STEPPING) { - // If we're stepping, it's convenient not to clear the screen. - thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::KEEP, RPAction::DONT_CARE, RPAction::DONT_CARE }); + // If we're stepping, it's convenient not to clear the screen entirely, so we copy display to output. + // This won't work in non-buffered, but that's fine. + thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::DONT_CARE, RPAction::DONT_CARE }); + // Just to make sure. + if (PSP_IsInited()) { + gpu->CopyDisplayToOutput(); + } } else { // Didn't actually reach the end of the frame, ran out of the blockTicks cycles. // In this case we need to bind and wipe the backbuffer, at least. @@ -1235,9 +1271,7 @@ void EmuScreen::render() { if (invalid_) return; - const bool hasVisibleUI = !osm.IsEmpty() || saveStatePreview_->GetVisibility() != UI::V_GONE || g_Config.bShowTouchControls || loadingTextView_->GetVisibility() == UI::V_VISIBLE; - const bool showDebugUI = g_Config.bShowDebugStats || g_Config.bShowDeveloperMenu || g_Config.bShowAudioDebug || g_Config.bShowFrameProfiler || g_Config.bSimpleFrameStats; - if (hasVisibleUI || showDebugUI || g_Config.iShowFPSCounter != 0) { + if (hasVisibleUI_) { renderUI(); } @@ -1259,6 +1293,20 @@ void EmuScreen::render() { */ } +bool EmuScreen::hasVisibleUI() { + // Regular but uncommon UI. + if (saveStatePreview_->GetVisibility() != UI::V_GONE || loadingSpinner_->GetVisibility() == UI::V_VISIBLE) + return true; + if (!osm.IsEmpty() || g_Config.bShowTouchControls || g_Config.iShowFPSCounter != 0) + return true; + + // Debug UI. + if (g_Config.bShowDebugStats || g_Config.bShowDeveloperMenu || g_Config.bShowAudioDebug || g_Config.bShowFrameProfiler || g_Config.bSimpleFrameStats) + return true; + + return false; +} + void EmuScreen::renderUI() { using namespace Draw; @@ -1309,11 +1357,26 @@ void EmuScreen::renderUI() { } void EmuScreen::autoLoad() { + int autoSlot = -1; + //check if save state has save, if so, load - int lastSlot = SaveState::GetNewestSlot(gamePath_); - if (g_Config.bEnableAutoLoad && lastSlot != -1) { - SaveState::LoadSlot(gamePath_, lastSlot, &AfterSaveStateAction); - g_Config.iCurrentStateSlot = lastSlot; + switch (g_Config.iAutoLoadSaveState) { + case (int)AutoLoadSaveState::OFF: // "AutoLoad Off" + return; + case (int)AutoLoadSaveState::OLDEST: // "Oldest Save" + autoSlot = SaveState::GetOldestSlot(gamePath_); + break; + case (int)AutoLoadSaveState::NEWEST: // "Newest Save" + autoSlot = SaveState::GetNewestSlot(gamePath_); + break; + default: // try the specific save state slot specified + autoSlot = (SaveState::HasSaveInSlot(gamePath_, g_Config.iAutoLoadSaveState - 3)) ? (g_Config.iAutoLoadSaveState - 3) : -1; + break; + } + + if (g_Config.iAutoLoadSaveState && autoSlot != -1) { + SaveState::LoadSlot(gamePath_, autoSlot, &AfterSaveStateAction); + g_Config.iCurrentStateSlot = autoSlot; } } diff --git a/UI/EmuScreen.h b/UI/EmuScreen.h index dd1fd0ee5c..0dd44e614a 100644 --- a/UI/EmuScreen.h +++ b/UI/EmuScreen.h @@ -56,6 +56,7 @@ private: void bootGame(const std::string &filename); bool bootAllowStorage(const std::string &filename); void bootComplete(); + bool hasVisibleUI(); void renderUI(); void processAxis(const AxisInput &axis, int direction); @@ -79,6 +80,7 @@ private: bool invalid_; bool quit_; bool stopRender_ = false; + bool hasVisibleUI_ = true; std::string errorMessage_; // If set, pauses at the end of the frame. diff --git a/UI/GameScreen.cpp b/UI/GameScreen.cpp index 740ef7467d..cf0f8c0ba5 100644 --- a/UI/GameScreen.cpp +++ b/UI/GameScreen.cpp @@ -216,12 +216,12 @@ void GameScreen::update() { if (info->gameSize) { char temp[256]; - sprintf(temp, "%s: %1.1f %s", ga->T("Game"), (float) (info->gameSize) / 1024.f / 1024.f, ga->T("MB")); + snprintf(temp, sizeof(temp), "%s: %1.1f %s", ga->T("Game"), (float) (info->gameSize) / 1024.f / 1024.f, ga->T("MB")); tvGameSize_->SetText(temp); - sprintf(temp, "%s: %1.2f %s", ga->T("SaveData"), (float) (info->saveDataSize) / 1024.f / 1024.f, ga->T("MB")); + snprintf(temp, sizeof(temp), "%s: %1.2f %s", ga->T("SaveData"), (float) (info->saveDataSize) / 1024.f / 1024.f, ga->T("MB")); tvSaveDataSize_->SetText(temp); if (info->installDataSize > 0) { - sprintf(temp, "%s: %1.2f %s", ga->T("InstallData"), (float) (info->installDataSize) / 1024.f / 1024.f, ga->T("MB")); + snprintf(temp, sizeof(temp), "%s: %1.2f %s", ga->T("InstallData"), (float) (info->installDataSize) / 1024.f / 1024.f, ga->T("MB")); tvInstallDataSize_->SetText(temp); tvInstallDataSize_->SetVisibility(UI::V_VISIBLE); } diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 7ed812fba4..756f3ebc59 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -49,9 +49,11 @@ #include "Common/FileUtil.h" #include "Common/OSVersion.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/Host.h" #include "Core/System.h" #include "Core/Reporting.h" +#include "Core/WebServer.h" #include "GPU/Common/PostShader.h" #include "android/jni/TestRunner.h" #include "GPU/GPUInterface.h" @@ -103,8 +105,9 @@ bool DoesBackendSupportHWTess() { case GPUBackend::VULKAN: case GPUBackend::DIRECT3D11: return true; + default: + return false; } - return false; } static std::string PostShaderTranslateName(const char *value) { @@ -124,7 +127,8 @@ void GameSettingsScreen::CreateViews() { cap60FPS_ = g_Config.iForceMaxEmulatedFPS == 60; - iAlternateSpeedPercent_ = (g_Config.iFpsLimit * 100) / 60; + iAlternateSpeedPercent1_ = g_Config.iFpsLimit1 < 0 ? -1 : (g_Config.iFpsLimit1 * 100) / 60; + iAlternateSpeedPercent2_ = g_Config.iFpsLimit2 < 0 ? -1 : (g_Config.iFpsLimit2 * 100) / 60; bool vertical = UseVerticalLayout(); @@ -198,8 +202,38 @@ void GameSettingsScreen::CreateViews() { #ifndef IOS vulkanAvailable = VulkanMayBeAvailable(); #endif - if (!vulkanAvailable) { - renderingBackendChoice->HideChoice(3); + if (!vulkanAvailable) { + renderingBackendChoice->HideChoice(3); + } +#endif + Draw::DrawContext *draw = screenManager()->getDrawContext(); + + // Backends that don't allow a device choice will only expose one device. + if (draw->GetDeviceList().size() > 1) { + std::string *deviceNameSetting = nullptr; + if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN) { + deviceNameSetting = &g_Config.sVulkanDevice; + } +#ifdef _WIN32 + if (g_Config.iGPUBackend == (int)GPUBackend::DIRECT3D11) { + deviceNameSetting = &g_Config.sD3D11Device; + } +#endif + if (deviceNameSetting) { + PopupMultiChoiceDynamic *deviceChoice = graphicsSettings->Add(new PopupMultiChoiceDynamic(deviceNameSetting, gr->T("Device"), draw->GetDeviceList(), nullptr, screenManager())); + deviceChoice->OnChoice.Handle(this, &GameSettingsScreen::OnRenderingBackend); + } + } + + static const char *renderingMode[] = { "Non-Buffered Rendering", "Buffered Rendering"}; + PopupMultiChoice *renderingModeChoice = graphicsSettings->Add(new PopupMultiChoice(&g_Config.iRenderingMode, gr->T("Mode"), renderingMode, 0, ARRAY_SIZE(renderingMode), gr->GetName(), screenManager())); + renderingModeChoice->OnChoice.Add([=](EventParams &e) { + switch (g_Config.iRenderingMode) { + case FB_NON_BUFFERED_MODE: + settingInfo_->Show(gr->T("RenderingMode NonBuffered Tip", "Faster, but graphics may be missing in some games"), e.v); + break; + case FB_BUFFERED_MODE: + break; } #endif @@ -250,11 +284,16 @@ void GameSettingsScreen::CreateViews() { frameSkipAuto_->OnClick.Handle(this, &GameSettingsScreen::OnAutoFrameskip); graphicsSettings->Add(new CheckBox(&cap60FPS_, gr->T("Force max 60 FPS (helps GoW)"))); - PopupSliderChoice *altSpeed = graphicsSettings->Add(new PopupSliderChoice(&iAlternateSpeedPercent_, 0, 1000, gr->T("Alternative Speed", "Alternative speed"), 5, screenManager(), gr->T("%, 0:unlimited"))); - altSpeed->SetFormat("%i%%"); - altSpeed->SetZeroLabel(gr->T("Unlimited")); - CheckBox *untoggleFpsLimit = graphicsSettings->Add(new CheckBox(&g_Config.bUnToggleFpsLimit, gr->T("Unthrottle-like Alternative Speed"))); - } + PopupSliderChoice *altSpeed1 = graphicsSettings->Add(new PopupSliderChoice(&iAlternateSpeedPercent1_, 0, 1000, gr->T("Alternative Speed", "Alternative speed"), 5, screenManager(), gr->T("%, 0:unlimited"))); + altSpeed1->SetFormat("%i%%"); + altSpeed1->SetZeroLabel(gr->T("Unlimited")); + altSpeed1->SetNegativeDisable(gr->T("Disabled")); + + PopupSliderChoice *altSpeed2 = graphicsSettings->Add(new PopupSliderChoice(&iAlternateSpeedPercent2_, 0, 1000, gr->T("Alternative Speed 2", "Alternative speed 2 (in %, 0 = unlimited)"), 5, screenManager(), gr->T("%, 0:unlimited"))); + altSpeed2->SetFormat("%i%%"); + altSpeed2->SetZeroLabel(gr->T("Unlimited")); + altSpeed2->SetNegativeDisable(gr->T("Disabled")); + graphicsSettings->Add(new ItemHeader(gr->T("Features"))); // Hide postprocess option on unsupported backends to avoid confusion. if (GetGPUBackend() != GPUBackend::DIRECT3D9) { @@ -749,8 +788,8 @@ void GameSettingsScreen::CreateViews() { systemSettings->Add(new Choice(sy->T("Restore Default Settings")))->OnClick.Handle(this, &GameSettingsScreen::OnRestoreDefaultSettings); if (!g_Config.bSimpleUI) { systemSettings->Add(new CheckBox(&g_Config.bEnableStateUndo, sy->T("Savestate slot backups"))); - systemSettings->Add(new CheckBox(&g_Config.bEnableAutoLoad, sy->T("Auto Load Newest Savestate"))); - + static const char *autoLoadSaveStateChoices[] = { "Off", "Oldest Save", "Newest Save", "Slot 1", "Slot 2", "Slot 3", "Slot 4", "Slot 5" }; + systemSettings->Add(new PopupMultiChoice(&g_Config.iAutoLoadSaveState, sy->T("Auto Load Savestate"), autoLoadSaveStateChoices, 0, ARRAY_SIZE(autoLoadSaveStateChoices), sy->GetName(), screenManager())); #if defined(USING_WIN_UI) systemSettings->Add(new CheckBox(&g_Config.bBypassOSKWithKeyboard, sy->T("Enable Windows native keyboard", "Enable Windows native keyboard"))); #endif @@ -1067,7 +1106,8 @@ void GameSettingsScreen::update() { else cap60FPS_ = false; - g_Config.iFpsLimit = (iAlternateSpeedPercent_ * 60) / 100; + g_Config.iFpsLimit1 = iAlternateSpeedPercent1_ < 0 ? -1 : (iAlternateSpeedPercent1_ * 60) / 100; + g_Config.iFpsLimit2 = iAlternateSpeedPercent2_ < 0 ? -1 : (iAlternateSpeedPercent2_ * 60) / 100; bool vertical = UseVerticalLayout(); if (vertical != lastVertical_) { @@ -1313,6 +1353,12 @@ void DeveloperToolsScreen::CreateViews() { createTextureIni->SetEnabled(false); } #endif + + allowDebugger_ = !WebServerStopped(WebServerFlags::DEBUGGER); + canAllowDebugger_ = !WebServerStopping(WebServerFlags::DEBUGGER); + CheckBox *allowDebugger = new CheckBox(&allowDebugger_, dev->T("Allow remote debugger")); + list->Add(allowDebugger)->OnClick.Handle(this, &DeveloperToolsScreen::OnRemoteDebugger); + allowDebugger->SetEnabledPtr(&canAllowDebugger_); } void DeveloperToolsScreen::onFinish(DialogResult result) { @@ -1447,6 +1493,23 @@ UI::EventReturn DeveloperToolsScreen::OnJitAffectingSetting(UI::EventParams &e) return UI::EVENT_DONE; } +UI::EventReturn DeveloperToolsScreen::OnRemoteDebugger(UI::EventParams &e) { + if (allowDebugger_) { + StartWebServer(WebServerFlags::DEBUGGER); + } else { + StopWebServer(WebServerFlags::DEBUGGER); + } + // Persist the setting. Maybe should separate? + g_Config.bRemoteDebuggerOnStartup = allowDebugger_; + return UI::EVENT_CONTINUE; +} + +void DeveloperToolsScreen::update() { + UIDialogScreenWithBackground::update(); + allowDebugger_ = !WebServerStopped(WebServerFlags::DEBUGGER); + canAllowDebugger_ = !WebServerStopping(WebServerFlags::DEBUGGER); +} + void ProAdhocServerScreen::CreateViews() { using namespace UI; I18NCategory *sy = GetI18NCategory("System"); diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h index a066349b16..384d23ee2e 100644 --- a/UI/GameSettingsScreen.h +++ b/UI/GameSettingsScreen.h @@ -107,7 +107,8 @@ private: // Temporaries to convert bools to int settings bool cap60FPS_; - int iAlternateSpeedPercent_; + int iAlternateSpeedPercent1_; + int iAlternateSpeedPercent2_; int iAlternateSpeedPercent2_; bool enableReports_; @@ -142,13 +143,13 @@ private: class DeveloperToolsScreen : public UIDialogScreenWithBackground { public: DeveloperToolsScreen() {} + void update() override; void onFinish(DialogResult result) override; protected: void CreateViews() override; private: - UI::EventReturn OnBack(UI::EventParams &e); UI::EventReturn OnRunCPUTests(UI::EventParams &e); UI::EventReturn OnLoggingChanged(UI::EventParams &e); UI::EventReturn OnLoadLanguageIni(UI::EventParams &e); @@ -156,6 +157,10 @@ private: UI::EventReturn OnOpenTexturesIniFile(UI::EventParams &e); UI::EventReturn OnLogConfig(UI::EventParams &e); UI::EventReturn OnJitAffectingSetting(UI::EventParams &e); + UI::EventReturn OnRemoteDebugger(UI::EventParams &e); + + bool allowDebugger_ = false; + bool canAllowDebugger_ = true; }; class OtherSettingsScreen : public UIDialogScreenWithBackground { diff --git a/UI/GamepadEmu.cpp b/UI/GamepadEmu.cpp index 42edfa2979..96253b2727 100644 --- a/UI/GamepadEmu.cpp +++ b/UI/GamepadEmu.cpp @@ -18,6 +18,7 @@ #include #include "Common/Log.h" #include "Core/Config.h" +#include "Core/Core.h" #include "Core/System.h" #include "Core/HLE/sceCtrl.h" #include "UI/GamepadEmu.h" @@ -107,7 +108,7 @@ void MultiTouchButton::Draw(UIContext &dc) { uint32_t colorBg = colorAlpha(GetButtonColor(), opacity); uint32_t color = colorAlpha(0xFFFFFF, opacity); - dc.Draw()->DrawImageRotated(bgImg_, bounds_.centerX(), bounds_.centerY(), scale, angle_ * (M_PI * 2 / 360.0f), colorBg, flipImageH_); + dc.Draw()->DrawImageRotated(bgImg_, bounds_.centerX(), bounds_.centerY(), scale, bgAngle_ * (M_PI * 2 / 360.0f), colorBg, flipImageH_); int y = bounds_.centerY(); // Hack round the fact that the center of the rectangular picture the triangle is contained in @@ -124,9 +125,32 @@ void BoolButton::Touch(const TouchInput &input) { if (down != lastDown) { *value_ = down; + UI::EventParams params{ this }; + params.a = down; + OnChange.Trigger(params); } } +void FPSLimitButton::Touch(const TouchInput &input) { + bool lastDown = pointerDownMask_ != 0; + MultiTouchButton::Touch(input); + bool down = pointerDownMask_ != 0; + + if (!down && lastDown && IsDown()) { + PSP_CoreParameter().fpsLimit = FPSLimit::NORMAL; + } else if (down && !lastDown && PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL) { + int limit = limit_ == FPSLimit::CUSTOM1 ? g_Config.iFpsLimit1 : g_Config.iFpsLimit2; + // Validate it actually has a setting (may this should override visible?) + if (limit >= 0) { + PSP_CoreParameter().fpsLimit = limit_; + } + } +} + +bool FPSLimitButton::IsDown() { + return PSP_CoreParameter().fpsLimit == limit_; +} + void PSPButton::Touch(const TouchInput &input) { bool lastDown = pointerDownMask_ != 0; MultiTouchButton::Touch(input); @@ -387,6 +411,15 @@ void PSPStick::ProcessTouch(float x, float y, bool down) { void InitPadLayout(float xres, float yres, float globalScale) { const float scale = globalScale; + const int halfW = xres / 2; + + auto initTouchPos = [=](ConfigTouchPos &touch, float x, float y) { + if (touch.x == -1.0f || touch.y == -1.0f) { + touch.x = x / xres; + touch.y = y / yres; + touch.scale = scale; + } + }; // PSP buttons (triangle, circle, square, cross)--------------------- // space between the PSP buttons (triangle, circle, square and cross) @@ -398,12 +431,7 @@ void InitPadLayout(float xres, float yres, float globalScale) { float Action_button_spacing = g_Config.fActionButtonSpacing * baseActionButtonSpacing; int Action_button_center_X = xres - Action_button_spacing * 2; int Action_button_center_Y = yres - Action_button_spacing * 2; - - if (g_Config.fActionButtonCenterX == -1.0 || g_Config.fActionButtonCenterY == -1.0) { - // Setup defaults - g_Config.fActionButtonCenterX = (float)Action_button_center_X / xres; - g_Config.fActionButtonCenterY = (float)Action_button_center_Y / yres; - } + initTouchPos(g_Config.touchActionButtonCenter, Action_button_center_X, Action_button_center_Y); //D-PAD (up down left right) (aka PSP cross)---------------------------- //radius to the D-pad @@ -411,26 +439,16 @@ void InitPadLayout(float xres, float yres, float globalScale) { int D_pad_X = 2.5 * D_pad_Radius * scale; int D_pad_Y = yres - D_pad_Radius * scale; - if (g_Config.bShowTouchAnalogStick) { + if (g_Config.touchAnalogStick.show) { D_pad_Y -= 200 * scale; } - - if (g_Config.fDpadX == -1.0 || g_Config.fDpadY == -1.0 ) { - //setup defaults - g_Config.fDpadX = (float)D_pad_X / xres; - g_Config.fDpadY = (float)D_pad_Y / yres; - } + initTouchPos(g_Config.touchDpad, D_pad_X, D_pad_Y); //analog stick------------------------------------------------------- //keep the analog stick right below the D pad int analog_stick_X = D_pad_X; int analog_stick_Y = yres - 80 * scale; - - if (g_Config.fAnalogStickX == -1.0 || g_Config.fAnalogStickY == -1.0 ) { - g_Config.fAnalogStickX = (float)analog_stick_X / xres; - g_Config.fAnalogStickY = (float)analog_stick_Y / yres; - g_Config.fAnalogStickScale = scale; - } + initTouchPos(g_Config.touchAnalogStick, analog_stick_X, analog_stick_Y); //select, start, throttle-------------------------------------------- //space between the bottom keys (space between select, start and un-throttle) @@ -439,32 +457,20 @@ void InitPadLayout(float xres, float yres, float globalScale) { bottom_key_spacing *= 0.8f; } - int start_key_X = xres / 2 + (bottom_key_spacing) * scale; + int start_key_X = halfW + bottom_key_spacing * scale; int start_key_Y = yres - 60 * scale; + initTouchPos(g_Config.touchStartKey, start_key_X, start_key_Y); - if (g_Config.fStartKeyX == -1.0 || g_Config.fStartKeyY == -1.0 ) { - g_Config.fStartKeyX = (float)start_key_X / xres; - g_Config.fStartKeyY = (float)start_key_Y / yres; - g_Config.fStartKeyScale = scale; - } - - int select_key_X = xres / 2; + int select_key_X = halfW; int select_key_Y = yres - 60 * scale; + initTouchPos(g_Config.touchSelectKey, select_key_X, select_key_Y); - if (g_Config.fSelectKeyX == -1.0 || g_Config.fSelectKeyY == -1.0 ) { - g_Config.fSelectKeyX = (float)select_key_X / xres; - g_Config.fSelectKeyY = (float)select_key_Y / yres; - g_Config.fSelectKeyScale = scale; - } - - int unthrottle_key_X = xres / 2 - (bottom_key_spacing) * scale; + int unthrottle_key_X = halfW - bottom_key_spacing * scale; int unthrottle_key_Y = yres - 60 * scale; + initTouchPos(g_Config.touchUnthrottleKey, unthrottle_key_X, unthrottle_key_Y); - if (g_Config.fUnthrottleKeyX == -1.0 || g_Config.fUnthrottleKeyY == -1.0 ) { - g_Config.fUnthrottleKeyX = (float)unthrottle_key_X / xres; - g_Config.fUnthrottleKeyY = (float)unthrottle_key_Y / yres; - g_Config.fUnthrottleKeyScale = scale; - } + initTouchPos(g_Config.touchSpeed1Key, unthrottle_key_X, unthrottle_key_Y - 60 * scale); + initTouchPos(g_Config.touchSpeed2Key, unthrottle_key_X + bottom_key_spacing * scale, unthrottle_key_Y - 60 * scale); // L and R------------------------------------------------------------ // Put them above the analog stick / above the buttons to the right. @@ -472,214 +478,145 @@ void InitPadLayout(float xres, float yres, float globalScale) { int l_key_X = 60 * scale; int l_key_Y = yres - 380 * scale; - - if (g_Config.fLKeyX == -1.0 || g_Config.fLKeyY == -1.0 ) { - g_Config.fLKeyX = (float)l_key_X / xres; - g_Config.fLKeyY = (float)l_key_Y / yres; - g_Config.fLKeyScale = scale; - } + initTouchPos(g_Config.touchLKey, l_key_X, l_key_Y); int r_key_X = xres - 60 * scale; int r_key_Y = l_key_Y; - - if (g_Config.fRKeyX == -1.0 || g_Config.fRKeyY == -1.0 ) { - g_Config.fRKeyX = (float)r_key_X / xres; - g_Config.fRKeyY = (float)r_key_Y / yres; - g_Config.fRKeyScale = scale; - } + initTouchPos(g_Config.touchRKey, r_key_X, r_key_Y); //Combo key - int combo_key_X = xres / 2 + (bottom_key_spacing)* scale*1.2f; + int combo_key_X = halfW + bottom_key_spacing * scale * 1.2f; int combo_key_Y = yres / 2; + initTouchPos(g_Config.touchCombo0, combo_key_X, combo_key_Y); - if (g_Config.fcombo0X == -1.0 || g_Config.fcombo0Y == -1.0) { - g_Config.fcombo0X = (float)combo_key_X / xres; - g_Config.fcombo0Y = (float)combo_key_Y / yres; - g_Config.fcomboScale0 = scale; - } - - int combo1_key_X = xres / 2 + (bottom_key_spacing)* scale * 2.2; + int combo1_key_X = halfW + bottom_key_spacing * scale * 2.2f; int combo1_key_Y = yres / 2; + initTouchPos(g_Config.touchCombo1, combo1_key_X, combo1_key_Y); - if (g_Config.fcombo1X == -1.0 || g_Config.fcombo1Y == -1.0) { - g_Config.fcombo1X = (float)combo1_key_X / xres; - g_Config.fcombo1Y = (float)combo1_key_Y / yres; - g_Config.fcomboScale1 = scale; - } - - int combo2_key_X = xres / 2 + (bottom_key_spacing)* scale * 3.2; + int combo2_key_X = halfW + bottom_key_spacing * scale * 3.2f; int combo2_key_Y = yres / 2; + initTouchPos(g_Config.touchCombo2, combo2_key_X, combo2_key_Y); - if (g_Config.fcombo2X == -1.0 || g_Config.fcombo2Y == -1.0) { - g_Config.fcombo2X = (float)combo2_key_X / xres; - g_Config.fcombo2Y = (float)combo2_key_Y / yres; - g_Config.fcomboScale2 = scale; - } - - int combo3_key_X = xres / 2 + (bottom_key_spacing)* scale * 1.2; + int combo3_key_X = halfW + bottom_key_spacing * scale * 1.2f; int combo3_key_Y = yres / 3; + initTouchPos(g_Config.touchCombo3, combo3_key_X, combo3_key_Y); - if (g_Config.fcombo3X == -1.0 || g_Config.fcombo3Y == -1.0) { - g_Config.fcombo3X = (float)combo3_key_X / xres; - g_Config.fcombo3Y = (float)combo3_key_Y / yres; - g_Config.fcomboScale3 = scale; - } - - int combo4_key_X = xres / 2 + (bottom_key_spacing)* scale * 2.2; + int combo4_key_X = halfW + bottom_key_spacing * scale * 2.2f; int combo4_key_Y = yres / 3; - - if (g_Config.fcombo4X == -1.0 || g_Config.fcombo4Y == -1.0) { - g_Config.fcombo4X = (float)combo4_key_X / xres; - g_Config.fcombo4Y = (float)combo4_key_Y / yres; - g_Config.fcomboScale4 = scale; - } - -}; + initTouchPos(g_Config.touchCombo4, combo4_key_X, combo4_key_Y); +} UI::ViewGroup *CreatePadLayout(float xres, float yres, bool *pause) { - //standard coord system - using namespace UI; AnchorLayout *root = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT)); + if (!g_Config.bShowTouchControls) { + return root; + } - //PSP buttons (triangle, circle, square, cross)--------------------- - //space between the PSP buttons (traingle, circle, square and cross) - const float Action_button_scale = g_Config.fActionButtonScale; - const float Action_button_spacing = g_Config.fActionButtonSpacing * baseActionButtonSpacing; - //position of the circle button (the PSP circle button). It is the farthest to the left - float Action_button_center_X = g_Config.fActionButtonCenterX * xres; - float Action_button_center_Y = g_Config.fActionButtonCenterY * yres; + struct ButtonOffset { + float x; + float y; + }; + auto buttonLayoutParams = [=](const ConfigTouchPos &touch, ButtonOffset off = { 0, 0 }) { + return new AnchorLayoutParams(touch.x * xres + off.x, touch.y * yres + off.y, NONE, NONE, true); + }; - const float Action_circle_button_X = Action_button_center_X + Action_button_spacing; - const float Action_circle_button_Y = Action_button_center_Y; - - const float Action_cross_button_X = Action_button_center_X; - const float Action_cross_button_Y = Action_button_center_Y + Action_button_spacing; - - const float Action_triangle_button_X = Action_button_center_X; - const float Action_triangle_button_Y = Action_button_center_Y - Action_button_spacing; - - const float Action_square_button_X = Action_button_center_X - Action_button_spacing; - const float Action_square_button_Y = Action_button_center_Y; - - //D-PAD (up down left right) (aka PSP cross)-------------------------------------------------------------- - //radius to the D-pad - - float D_pad_X = g_Config.fDpadX * xres; - float D_pad_Y = g_Config.fDpadY * yres; - float D_pad_scale = g_Config.fDpadScale; - float D_pad_spacing = g_Config.fDpadSpacing; - - //select, start, throttle-------------------------------------------- - //space between the bottom keys (space between select, start and un-throttle) - float start_key_X = g_Config.fStartKeyX * xres; - float start_key_Y = g_Config.fStartKeyY * yres; - float start_key_scale = g_Config.fStartKeyScale; - - float select_key_X = g_Config.fSelectKeyX * xres; - float select_key_Y = g_Config.fSelectKeyY * yres; - float select_key_scale = g_Config.fSelectKeyScale; - - float unthrottle_key_X = g_Config.fUnthrottleKeyX * xres; - float unthrottle_key_Y = g_Config.fUnthrottleKeyY * yres; - float unthrottle_key_scale = g_Config.fUnthrottleKeyScale; - - //L and R------------------------------------------------------------ - float l_key_X = g_Config.fLKeyX * xres; - float l_key_Y = g_Config.fLKeyY * yres; - float l_key_scale = g_Config.fLKeyScale; - - float r_key_X = g_Config.fRKeyX * xres; - float r_key_Y = g_Config.fRKeyY * yres; - float r_key_scale = g_Config.fRKeyScale; - - //analog stick------------------------------------------------------- - float analog_stick_X = g_Config.fAnalogStickX * xres; - float analog_stick_Y = g_Config.fAnalogStickY * yres; - float analog_stick_scale = g_Config.fAnalogStickScale; - - //combo key ------------------------------------------------------- - float combo0_key_X = g_Config.fcombo0X * xres; - float combo0_key_Y = g_Config.fcombo0Y * yres; - float combo_key_scale = g_Config.fcomboScale0; - float combo1_key_X = g_Config.fcombo1X * xres; - float combo1_key_Y = g_Config.fcombo1Y * yres; - float combo1_key_scale = g_Config.fcomboScale1; - float combo2_key_X = g_Config.fcombo2X * xres; - float combo2_key_Y = g_Config.fcombo2Y * yres; - float combo2_key_scale = g_Config.fcomboScale2; - float combo3_key_X = g_Config.fcombo3X * xres; - float combo3_key_Y = g_Config.fcombo3Y * yres; - float combo3_key_scale = g_Config.fcomboScale3; - float combo4_key_X = g_Config.fcombo4X * xres; - float combo4_key_Y = g_Config.fcombo4Y * yres; - float combo4_key_scale = g_Config.fcomboScale4; + // Space between the PSP buttons (traingle, circle, square and cross) + const float actionButtonSpacing = g_Config.fActionButtonSpacing * baseActionButtonSpacing; + // Position of the circle button (the PSP circle button). It is the farthest to the right. + ButtonOffset circleOffset{ actionButtonSpacing, 0.0f }; + ButtonOffset crossOffset{ 0.0f, actionButtonSpacing }; + ButtonOffset triangleOffset{ 0.0f, -actionButtonSpacing }; + ButtonOffset squareOffset{ -actionButtonSpacing, 0.0f }; const int halfW = xres / 2; const int roundImage = g_Config.iTouchButtonStyle ? I_ROUND_LINE : I_ROUND; - if (g_Config.bShowTouchControls) { - const int rectImage = g_Config.iTouchButtonStyle ? I_RECT_LINE : I_RECT; - const int shoulderImage = g_Config.iTouchButtonStyle ? I_SHOULDER_LINE : I_SHOULDER; - const int dirImage = g_Config.iTouchButtonStyle ? I_DIR_LINE : I_DIR; - const int stickImage = g_Config.iTouchButtonStyle ? I_STICK_LINE : I_STICK; - const int stickBg = g_Config.iTouchButtonStyle ? I_STICK_BG_LINE : I_STICK_BG; - static const int comboKeyImages[5] = { I_1, I_2, I_3, I_4, I_5 }; + const int rectImage = g_Config.iTouchButtonStyle ? I_RECT_LINE : I_RECT; + const int shoulderImage = g_Config.iTouchButtonStyle ? I_SHOULDER_LINE : I_SHOULDER; + const int dirImage = g_Config.iTouchButtonStyle ? I_DIR_LINE : I_DIR; + const int stickImage = g_Config.iTouchButtonStyle ? I_STICK_LINE : I_STICK; + const int stickBg = g_Config.iTouchButtonStyle ? I_STICK_BG_LINE : I_STICK_BG; + static const int comboKeyImages[5] = { I_1, I_2, I_3, I_4, I_5 }; - if (!System_GetPropertyBool(SYSPROP_HAS_BACK_BUTTON) || g_Config.bShowTouchPause) { - root->Add(new BoolButton(pause, roundImage, I_ARROW, 1.0f, new AnchorLayoutParams(halfW, 20, NONE, NONE, true)))->SetAngle(90); + auto addPSPButton = [=](int buttonBit, int bgImg, int img, const ConfigTouchPos &touch, ButtonOffset off = { 0, 0 }) -> PSPButton * { + if (touch.show) { + return root->Add(new PSPButton(buttonBit, bgImg, img, touch.scale, buttonLayoutParams(touch, off))); } + return nullptr; + }; + auto addComboKey = [=](int buttonBit, int bgImg, int img, const ConfigTouchPos &touch) -> ComboKey * { + if (touch.show) { + return root->Add(new ComboKey(buttonBit, bgImg, img, touch.scale, buttonLayoutParams(touch))); + } + return nullptr; + }; + auto addBoolButton = [=](bool *value, int bgImg, int img, const ConfigTouchPos &touch) -> BoolButton * { + if (touch.show) { + return root->Add(new BoolButton(value, bgImg, img, touch.scale, buttonLayoutParams(touch))); + } + return nullptr; + }; + auto addFPSLimitButton = [=](FPSLimit value, int bgImg, int img, const ConfigTouchPos &touch) -> FPSLimitButton * { + if (touch.show) { + return root->Add(new FPSLimitButton(value, bgImg, img, touch.scale, buttonLayoutParams(touch))); + } + return nullptr; + }; - if (g_Config.bShowTouchCircle) - root->Add(new PSPButton(CTRL_CIRCLE, roundImage, I_CIRCLE, Action_button_scale, new AnchorLayoutParams(Action_circle_button_X, Action_circle_button_Y, NONE, NONE, true))); - - if (g_Config.bShowTouchCross) - root->Add(new PSPButton(CTRL_CROSS, roundImage, I_CROSS, Action_button_scale, new AnchorLayoutParams(Action_cross_button_X, Action_cross_button_Y, NONE, NONE, true))); - - if (g_Config.bShowTouchTriangle) - root->Add(new PSPButton(CTRL_TRIANGLE, roundImage, I_TRIANGLE, Action_button_scale, new AnchorLayoutParams(Action_triangle_button_X, Action_triangle_button_Y, NONE, NONE, true))); - - if (g_Config.bShowTouchSquare) - root->Add(new PSPButton(CTRL_SQUARE, roundImage, I_SQUARE, Action_button_scale, new AnchorLayoutParams(Action_square_button_X, Action_square_button_Y, NONE, NONE, true))); - - if (g_Config.bShowTouchStart) - root->Add(new PSPButton(CTRL_START, rectImage, I_START, start_key_scale, new AnchorLayoutParams(start_key_X, start_key_Y, NONE, NONE, true))); - - if (g_Config.bShowTouchSelect) - root->Add(new PSPButton(CTRL_SELECT, rectImage, I_SELECT, select_key_scale, new AnchorLayoutParams(select_key_X, select_key_Y, NONE, NONE, true))); - - if (g_Config.bShowTouchUnthrottle) - root->Add(new BoolButton(&PSP_CoreParameter().unthrottle, rectImage, I_ARROW, unthrottle_key_scale, new AnchorLayoutParams(unthrottle_key_X, unthrottle_key_Y, NONE, NONE, true)))->SetAngle(180); - - if (g_Config.bShowTouchLTrigger) - root->Add(new PSPButton(CTRL_LTRIGGER, shoulderImage, I_L, l_key_scale, new AnchorLayoutParams(l_key_X, l_key_Y, NONE, NONE, true))); - - if (g_Config.bShowTouchRTrigger) - root->Add(new PSPButton(CTRL_RTRIGGER, shoulderImage, I_R, r_key_scale, new AnchorLayoutParams(r_key_X,r_key_Y, NONE, NONE, true)))->FlipImageH(true); - - if (g_Config.bShowTouchDpad) - root->Add(new PSPDpad(dirImage, I_ARROW, D_pad_scale, D_pad_spacing, new AnchorLayoutParams(D_pad_X, D_pad_Y, NONE, NONE, true))); - - if (g_Config.bShowTouchAnalogStick) - root->Add(new PSPStick(stickBg, stickImage, 0, analog_stick_scale, new AnchorLayoutParams(analog_stick_X, analog_stick_Y, NONE, NONE, true))); - - if (g_Config.bShowComboKey0) - root->Add(new ComboKey(g_Config.iCombokey0, roundImage, comboKeyImages[0], combo_key_scale, new AnchorLayoutParams(combo0_key_X, combo0_key_Y, NONE, NONE, true))); - - if (g_Config.bShowComboKey1) - root->Add(new ComboKey(g_Config.iCombokey1, roundImage, comboKeyImages[1], combo1_key_scale, new AnchorLayoutParams(combo1_key_X, combo1_key_Y, NONE, NONE, true))); - - if (g_Config.bShowComboKey2) - root->Add(new ComboKey(g_Config.iCombokey2, roundImage, comboKeyImages[2], combo2_key_scale, new AnchorLayoutParams(combo2_key_X, combo2_key_Y, NONE, NONE, true))); - - if (g_Config.bShowComboKey3) - root->Add(new ComboKey(g_Config.iCombokey3, roundImage, comboKeyImages[3], combo3_key_scale, new AnchorLayoutParams(combo3_key_X, combo3_key_Y, NONE, NONE, true))); - - if (g_Config.bShowComboKey4) - root->Add(new ComboKey(g_Config.iCombokey4, roundImage, comboKeyImages[4], combo4_key_scale, new AnchorLayoutParams(combo4_key_X, combo4_key_Y, NONE, NONE, true))); + if (!System_GetPropertyBool(SYSPROP_HAS_BACK_BUTTON) || g_Config.bShowTouchPause) { + root->Add(new BoolButton(pause, roundImage, I_ARROW, 1.0f, new AnchorLayoutParams(halfW, 20, NONE, NONE, true)))->SetAngle(90); } + // touchActionButtonCenter.show will always be true, since that's the default. + if (g_Config.bShowTouchCircle) + addPSPButton(CTRL_CIRCLE, roundImage, I_CIRCLE, g_Config.touchActionButtonCenter, circleOffset); + if (g_Config.bShowTouchCross) + addPSPButton(CTRL_CROSS, roundImage, I_CROSS, g_Config.touchActionButtonCenter, crossOffset); + if (g_Config.bShowTouchTriangle) + addPSPButton(CTRL_TRIANGLE, roundImage, I_TRIANGLE, g_Config.touchActionButtonCenter, triangleOffset); + if (g_Config.bShowTouchSquare) + addPSPButton(CTRL_SQUARE, roundImage, I_SQUARE, g_Config.touchActionButtonCenter, squareOffset); + + addPSPButton(CTRL_START, rectImage, I_START, g_Config.touchStartKey); + addPSPButton(CTRL_SELECT, rectImage, I_SELECT, g_Config.touchSelectKey); + + BoolButton *unthrottle = addBoolButton(&PSP_CoreParameter().unthrottle, rectImage, I_ARROW, g_Config.touchUnthrottleKey); + if (unthrottle) { + unthrottle->SetAngle(180.0f); + unthrottle->OnChange.Add([](UI::EventParams &e) { + if (e.a) { + Core_EnableStepping(false); + } + return UI::EVENT_DONE; + }); + } + + FPSLimitButton *speed1 = addFPSLimitButton(FPSLimit::CUSTOM1, rectImage, I_ARROW, g_Config.touchSpeed1Key); + if (speed1) + speed1->SetAngle(170.0f, 180.0f); + FPSLimitButton *speed2 = addFPSLimitButton(FPSLimit::CUSTOM2, rectImage, I_ARROW, g_Config.touchSpeed2Key); + if (speed2) + speed2->SetAngle(190.0f, 180.0f); + + addPSPButton(CTRL_LTRIGGER, shoulderImage, I_L, g_Config.touchLKey); + PSPButton *rTrigger = addPSPButton(CTRL_RTRIGGER, shoulderImage, I_R, g_Config.touchRKey); + if (rTrigger) + rTrigger->FlipImageH(true); + + if (g_Config.touchDpad.show) + root->Add(new PSPDpad(dirImage, I_ARROW, g_Config.touchDpad.scale, g_Config.fDpadSpacing, buttonLayoutParams(g_Config.touchDpad))); + + if (g_Config.touchAnalogStick.show) + root->Add(new PSPStick(stickBg, stickImage, 0, g_Config.touchAnalogStick.scale, buttonLayoutParams(g_Config.touchAnalogStick))); + + addComboKey(g_Config.iCombokey0, roundImage, comboKeyImages[0], g_Config.touchCombo0); + addComboKey(g_Config.iCombokey1, roundImage, comboKeyImages[1], g_Config.touchCombo1); + addComboKey(g_Config.iCombokey2, roundImage, comboKeyImages[2], g_Config.touchCombo2); + addComboKey(g_Config.iCombokey3, roundImage, comboKeyImages[3], g_Config.touchCombo3); + addComboKey(g_Config.iCombokey4, roundImage, comboKeyImages[4], g_Config.touchCombo4); + return root; } diff --git a/UI/GamepadEmu.h b/UI/GamepadEmu.h index 79e72bcf43..087fa7da8d 100644 --- a/UI/GamepadEmu.h +++ b/UI/GamepadEmu.h @@ -22,6 +22,7 @@ #include "ui/view.h" #include "ui/viewgroup.h" +#include "Core/CoreParameter.h" class GamepadView : public UI::View { public: @@ -43,7 +44,7 @@ protected: class MultiTouchButton : public GamepadView { public: MultiTouchButton(int bgImg, int img, float scale, UI::LayoutParams *layoutParams) - : GamepadView(layoutParams), pointerDownMask_(0), scale_(scale), bgImg_(bgImg), img_(img), angle_(0.0f), flipImageH_(false) { + : GamepadView(layoutParams), scale_(scale), bgImg_(bgImg), img_(img) { } void Touch(const TouchInput &input) override; @@ -52,17 +53,19 @@ public: virtual bool IsDown() { return pointerDownMask_ != 0; } // chainable MultiTouchButton *FlipImageH(bool flip) { flipImageH_ = flip; return this; } - MultiTouchButton *SetAngle(float angle) { angle_ = angle; return this; } + MultiTouchButton *SetAngle(float angle) { angle_ = angle; bgAngle_ = angle; return this; } + MultiTouchButton *SetAngle(float angle, float bgAngle) { angle_ = angle; bgAngle_ = bgAngle; return this; } protected: - uint32_t pointerDownMask_; + uint32_t pointerDownMask_ = 0; float scale_; private: int bgImg_; int img_; - float angle_; - bool flipImageH_; + float bgAngle_ = 0.0f; + float angle_ = 0.0f; + bool flipImageH_ = false; }; class BoolButton : public MultiTouchButton { @@ -74,10 +77,25 @@ public: void Touch(const TouchInput &input) override; bool IsDown() override { return *value_; } + UI::Event OnChange; + private: bool *value_; }; +class FPSLimitButton : public MultiTouchButton { +public: + FPSLimitButton(FPSLimit limit, int bgImg, int img, float scale, UI::LayoutParams *layoutParams) + : MultiTouchButton(bgImg, img, scale, layoutParams), limit_(limit) { + + } + void Touch(const TouchInput &input) override; + bool IsDown() override; + +private: + FPSLimit limit_; +}; + class PSPButton : public MultiTouchButton { public: PSPButton(int pspButtonBit, int bgImg, int img, float scale, UI::LayoutParams *layoutParams) diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index 254c8151ff..45665816e0 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -29,6 +29,7 @@ #include "ui/ui_context.h" #include "ui/view.h" #include "ui/viewgroup.h" +#include "util/text/utf8.h" #include "Common/FileUtil.h" #include "Core/System.h" @@ -76,6 +77,47 @@ bool MainScreen::showHomebrewTab = false; +static bool IsTempPath(const std::string &str) { + std::string item = str; + + const auto testPath = [&](std::string temp) { +#ifdef _WIN32 + temp = ReplaceAll(temp, "/", "\\"); + if (!temp.empty() && temp[temp.size() - 1] != '\\') + temp += "\\"; +#else + if (!temp.empty() && temp[temp.size() - 1] != '/') + temp += "/"; +#endif + return startsWith(item, temp); + }; + + const auto testCPath = [&](const char *temp) { + if (temp && temp[0]) + return testPath(temp); + return false; + }; + +#ifdef _WIN32 + // Normalize slashes. + item = ReplaceAll(str, "/", "\\"); + + wchar_t tempPath[MAX_PATH]; + GetTempPath(MAX_PATH, tempPath); + if (testPath(ConvertWStringToUTF8(tempPath))) + return true; +#endif + + if (testCPath(getenv("TMPDIR"))) + return true; + if (testCPath(getenv("TMP"))) + return true; + if (testCPath(getenv("TEMP"))) + return true; + + return false; +} + class GameButton : public UI::Clickable { public: GameButton(const std::string &gamePath, bool gridStyle, UI::LayoutParams *layoutParams = 0) @@ -743,6 +785,7 @@ void MainScreen::CreateViews() { bool showRecent = g_Config.iMaxRecent > 0; bool hasStorageAccess = System_GetPermissionStatus(SYSTEM_PERMISSION_STORAGE) == PERMISSION_STATUS_GRANTED; + bool storageIsTemporary = IsTempPath(GetSysDirectory(DIRECTORY_SAVEDATA)) && !confirmedTemporary_; if (showRecent && !hasStorageAccess) { showRecent = !g_Config.recentIsos.empty(); } @@ -762,7 +805,7 @@ void MainScreen::CreateViews() { tabRecentGames->OnHighlight.Handle(this, &MainScreen::OnGameHighlight); } - Button *grantStorageButton = nullptr; + Button *focusButton = nullptr; if (hasStorageAccess) { ScrollView *scrollAllGames = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); scrollAllGames->SetTag("MainScreenAllGames"); @@ -810,6 +853,26 @@ void MainScreen::CreateViews() { backFromStore_ = false; showHomebrewTab = false; } + + if (storageIsTemporary) { + LinearLayout *buttonHolder = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT)); + buttonHolder->Add(new Spacer(new LinearLayoutParams(1.0f))); + focusButton = new Button(mm->T("SavesAreTemporaryIgnore", "Ignore warning"), new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT)); + focusButton->SetPadding(32, 16); + buttonHolder->Add(focusButton)->OnClick.Add([this](UI::EventParams &e) { + confirmedTemporary_ = true; + RecreateViews(); + return UI::EVENT_DONE; + }); + buttonHolder->Add(new Spacer(new LinearLayoutParams(1.0f))); + + leftColumn->Add(new Spacer(new LinearLayoutParams(0.1f))); + leftColumn->Add(new TextView(mm->T("SavesAreTemporary", "PPSSPP saving in temporary storage"), ALIGN_HCENTER, false)); + leftColumn->Add(new TextView(mm->T("SavesAreTemporaryGuidance", "Extract PPSSPP somewhere to save permanently"), ALIGN_HCENTER, false)); + leftColumn->Add(new Spacer(10.0f)); + leftColumn->Add(buttonHolder); + leftColumn->Add(new Spacer(new LinearLayoutParams(0.1f))); + } } else { if (!showRecent) { leftColumn = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0f)); @@ -820,8 +883,9 @@ void MainScreen::CreateViews() { LinearLayout *buttonHolder = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT)); buttonHolder->Add(new Spacer(new LinearLayoutParams(1.0f))); - grantStorageButton = new Button(mm->T("Give PPSSPP permission to access storage"), new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT)); - buttonHolder->Add(grantStorageButton)->OnClick.Handle(this, &MainScreen::OnAllowStorage); + focusButton = new Button(mm->T("Give PPSSPP permission to access storage"), new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT)); + focusButton->SetPadding(32, 16); + buttonHolder->Add(focusButton)->OnClick.Handle(this, &MainScreen::OnAllowStorage); buttonHolder->Add(new Spacer(new LinearLayoutParams(1.0f))); leftColumn->Add(new Spacer(new LinearLayoutParams(0.1f))); @@ -883,8 +947,8 @@ void MainScreen::CreateViews() { root_->Add(rightColumn); } - if (grantStorageButton) { - root_->SetDefaultFocusView(grantStorageButton); + if (focusButton) { + root_->SetDefaultFocusView(focusButton); } else if (tabHolder_->GetVisibility() != V_GONE) { root_->SetDefaultFocusView(tabHolder_); } diff --git a/UI/MainScreen.h b/UI/MainScreen.h index b80bfae5e9..d4957e1d7a 100644 --- a/UI/MainScreen.h +++ b/UI/MainScreen.h @@ -119,6 +119,7 @@ protected: bool backFromStore_; bool lockBackgroundAudio_; bool lastVertical_; + bool confirmedTemporary_ = false; friend class RemoteISOBrowseScreen; }; diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index a4da5adbe6..0a7673e1ce 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -71,6 +71,7 @@ #include "Common/MemArena.h" #include "Common/GraphicsContext.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/Core.h" #include "Core/FileLoaders/DiskCachingFileLoader.h" #include "Core/Host.h" @@ -84,6 +85,7 @@ #include "Core/HLE/sceUsbGps.h" #include "Core/Util/GameManager.h" #include "Core/Util/AudioFormat.h" +#include "Core/WebServer.h" #include "GPU/GPUInterface.h" #include "ui_atlas.h" @@ -104,6 +106,9 @@ #if !defined(MOBILE_DEVICE) && defined(USING_QT_UI) #include "Qt/QtHost.h" #endif +#if defined(USING_QT_UI) +#include +#endif // The new UI framework, for initialization @@ -275,8 +280,8 @@ void NativeGetAppInfo(std::string *app_dir_name, std::string *app_nice_name, boo #endif } -#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) -bool CheckFontIsUsable(const wchar_t *fontFace) { +#if defined(USING_WIN_UI) && !PPSSPP_PLATFORM(UWP) +static bool CheckFontIsUsable(const wchar_t *fontFace) { wchar_t actualFontFace[1024] = { 0 }; HFONT f = CreateFont(0, 0, 0, 0, FW_LIGHT, 0, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, VARIABLE_PITCH, fontFace); @@ -298,7 +303,50 @@ bool CheckFontIsUsable(const wchar_t *fontFace) { } #endif -void NativeInit(int argc, const char *argv[], const char *savegame_dir, const char *external_dir, const char *cache_dir, bool fs) { +static void PostLoadConfig() { + // On Windows, we deal with currentDirectory in InitSysDirectories(). +#ifndef _WIN32 + if (g_Config.currentDirectory.empty()) { +#if defined(__ANDROID__) + g_Config.currentDirectory = g_Config.externalDirectory; +#elif defined(IOS) + g_Config.currentDirectory = g_Config.internalDataDirectory; +#else + if (getenv("HOME") != nullptr) + g_Config.currentDirectory = getenv("HOME"); + else + g_Config.currentDirectory = "./"; +#endif + } +#endif + + // Allow the lang directory to be overridden for testing purposes (e.g. Android, where it's hard to + // test new languages without recompiling the entire app, which is a hassle). + const std::string langOverridePath = g_Config.memStickDirectory + "PSP/SYSTEM/lang/"; + + // If we run into the unlikely case that "lang" is actually a file, just use the built-in translations. + if (!File::Exists(langOverridePath) || !File::IsDirectory(langOverridePath)) + i18nrepo.LoadIni(g_Config.sLanguageIni); + else + i18nrepo.LoadIni(g_Config.sLanguageIni, langOverridePath); +} + +void CreateDirectoriesAndroid() { + // On Android, create a PSP directory tree in the external_dir, + // to hopefully reduce confusion a bit. + ILOG("Creating %s", (g_Config.memStickDirectory + "PSP").c_str()); + File::CreateDir(g_Config.memStickDirectory + "PSP"); + File::CreateDir(g_Config.memStickDirectory + "PSP/SAVEDATA"); + File::CreateDir(g_Config.memStickDirectory + "PSP/PPSSPP_STATE"); + File::CreateDir(g_Config.memStickDirectory + "PSP/GAME"); + File::CreateDir(g_Config.memStickDirectory + "PSP/SYSTEM"); + + // Avoid media scanners in PPSSPP_STATE and SAVEDATA directories + File::CreateEmptyFile(g_Config.memStickDirectory + "PSP/PPSSPP_STATE/.nomedia"); + File::CreateEmptyFile(g_Config.memStickDirectory + "PSP/SAVEDATA/.nomedia"); +} + +void NativeInit(int argc, const char *argv[], const char *savegame_dir, const char *external_dir, const char *cache_dir) { 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. InitFastMath(cpu_info.bNEON); @@ -316,9 +364,7 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch #endif // We want this to be FIRST. -#ifdef USING_QT_UI - VFSRegister("", new AssetsAssetReader()); -#elif defined(IOS) +#if defined(IOS) // Packed assets are included in app VFSRegister("", new DirectoryAssetReader(external_dir)); #endif @@ -336,8 +382,10 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch } #endif -#if defined(__ANDROID__) g_Config.internalDataDirectory = savegame_dir; + g_Config.externalDirectory = external_dir; + +#if defined(__ANDROID__) // Maybe there should be an option to use internal memory instead, but I think // that for most people, using external memory (SDCard/USB Storage) makes the // most sense. @@ -375,22 +423,13 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch // Note that if we don't have storage permission here, loading the config will // fail and it will be set to the default. Later, we load again when we get permission. g_Config.Load(); - g_Config.externalDirectory = external_dir; #endif LogManager *logman = LogManager::GetInstance(); #ifdef __ANDROID__ - // TODO: This is also done elsewhere. Remove? - // On Android, create a PSP directory tree in the external_dir, - // to hopefully reduce confusion a bit. - ILOG("Creating %s", (g_Config.memStickDirectory + "PSP").c_str()); - File::CreateDir((g_Config.memStickDirectory + "PSP").c_str()); - File::CreateDir((g_Config.memStickDirectory + "PSP/SAVEDATA").c_str()); - File::CreateDir((g_Config.memStickDirectory + "PSP/GAME").c_str()); + CreateDirectoriesAndroid(); #endif - - const char *fileToLog = 0; const char *stateToLoad = 0; @@ -436,6 +475,8 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch #endif if (!strncmp(argv[i], "--pause-menu-exit", strlen("--pause-menu-exit"))) g_Config.bPauseMenuExitsEmulator = true; + if (!strcmp(argv[i], "--fullscreen")) + g_Config.bFullScreen = true; break; } } else { @@ -471,12 +512,22 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch std::unique_ptr fileLoader(ConstructFileLoader(boot_filename)); if (!fileLoader->Exists()) { fprintf(stderr, "File not found: %s\n", boot_filename.c_str()); +#ifdef _WIN32 + // Ignore and proceed. +#else + // Bail. exit(1); +#endif } } } else { fprintf(stderr, "Can only boot one file"); +#ifdef _WIN32 + // Ignore and proceed. +#else + // Bail. exit(1); +#endif } } } @@ -484,20 +535,7 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch if (fileToLog) LogManager::GetInstance()->ChangeFileLog(fileToLog); -#ifndef _WIN32 - if (g_Config.currentDirectory == "") { -#if defined(__ANDROID__) - g_Config.currentDirectory = external_dir; -#elif defined(IOS) || defined(_WIN32) - g_Config.currentDirectory = savegame_dir; -#else - if (getenv("HOME") != NULL) - g_Config.currentDirectory = getenv("HOME"); - else - g_Config.currentDirectory = "./"; -#endif - } -#endif + PostLoadConfig(); // Hard reset the logs. TODO: Get rid of this and read from config. #ifndef _WIN32 @@ -515,16 +553,6 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch logman->AddListener(logger); #endif - // Allow the lang directory to be overridden for testing purposes (e.g. Android, where it's hard to - // test new languages without recompiling the entire app, which is a hassle). - const std::string langOverridePath = g_Config.memStickDirectory + "PSP/SYSTEM/lang/"; - - // If we run into the unlikely case that "lang" is actually a file, just use the built-in translations. - if (!File::Exists(langOverridePath) || !File::IsDirectory(langOverridePath)) - i18nrepo.LoadIni(g_Config.sLanguageIni); - else - i18nrepo.LoadIni(g_Config.sLanguageIni, langOverridePath); - if (System_GetPropertyBool(SYSPROP_SUPPORTS_PERMISSIONS)) { if (System_GetPermissionStatus(SYSTEM_PERMISSION_STORAGE) != PERMISSION_STATUS_GRANTED) { System_AskForPermission(SYSTEM_PERMISSION_STORAGE); @@ -536,7 +564,7 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch // Note to translators: do not translate this/add this to PPSSPP-lang's files. // It's intended to be custom for every user. // Only add it to your own personal copies of PPSSPP. -#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) +#if defined(USING_WIN_UI) && !PPSSPP_PLATFORM(UWP) // TODO: Could allow a setting to specify a font file to load? // TODO: Make this a constant if we can sanely load the font on other systems? AddFontResourceEx(L"assets/Roboto-Condensed.ttf", FR_PRIVATE, NULL); @@ -546,12 +574,28 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch } else { g_Config.sFont = des->T("Font", "Roboto"); } +#elif defined(USING_QT_UI) + size_t fontSize = 0; + uint8_t *fontData = VFSReadFile("Roboto-Condensed.ttf", &fontSize); + if (fontData) { + int fontID = QFontDatabase::addApplicationFontFromData(QByteArray((const char *)fontData, fontSize)); + delete [] fontData; + + QStringList fontsFound = QFontDatabase::applicationFontFamilies(fontID); + if (fontsFound.size() >= 1) { + // Might be "Roboto" or "Roboto Condensed". + g_Config.sFont = des->T("Font", fontsFound.at(0).toUtf8().constData()); + } + } else { + // Let's try for it being a system font. + g_Config.sFont = des->T("Font", "Roboto Condensed"); + } #endif if (!boot_filename.empty() && stateToLoad != NULL) { - SaveState::Load(stateToLoad, [](bool status, const std::string &message, void *) { + SaveState::Load(stateToLoad, [](SaveState::Status status, const std::string &message, void *) { if (!message.empty()) { - osm.Show(message, 2.0); + osm.Show(message, status == SaveState::Status::SUCCESS ? 2.0 : 5.0); } }); } @@ -563,15 +607,18 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch screenManager->switchScreen(new LogoScreen()); } - if (g_Config.bRemoteShareOnStartup) { - StartRemoteISOSharing(); - } + if (g_Config.bRemoteShareOnStartup && g_Config.bRemoteDebuggerOnStartup) + StartWebServer(WebServerFlags::ALL); + else if (g_Config.bRemoteShareOnStartup) + StartWebServer(WebServerFlags::DISCS); + else if (g_Config.bRemoteDebuggerOnStartup) + StartWebServer(WebServerFlags::DEBUGGER); std::string sysName = System_GetProperty(SYSPROP_NAME); isOuya = KeyMap::IsOuya(sysName); #if !defined(MOBILE_DEVICE) && defined(USING_QT_UI) - MainWindow* mainWindow = new MainWindow(0,fs); + MainWindow *mainWindow = new MainWindow(nullptr, g_Config.bFullScreen); mainWindow->show(); if (host == nullptr) { host = new QtHost(mainWindow); @@ -595,7 +642,7 @@ static UI::Style MakeStyle(uint32_t fg, uint32_t bg) { } static void UIThemeInit() { -#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) +#if (defined(USING_WIN_UI) && !PPSSPP_PLATFORM(UWP)) || defined(USING_QT_UI) ui_theme.uiFont = UI::FontStyle(UBUNTU24, g_Config.sFont.c_str(), 22); ui_theme.uiFontSmall = UI::FontStyle(UBUNTU24, g_Config.sFont.c_str(), 15); ui_theme.uiFontSmaller = UI::FontStyle(UBUNTU24, g_Config.sFont.c_str(), 12); @@ -668,6 +715,9 @@ bool NativeInitGraphics(GraphicsContext *graphicsContext) { colorPipeline = g_draw->CreateGraphicsPipeline(colorDesc); texColorPipeline = g_draw->CreateGraphicsPipeline(texColorDesc); + _assert_(colorPipeline); + _assert_(texColorPipeline); + // Release these now, reference counting should ensure that they get completely released // once we delete both pipelines. inputLayout->Release(); @@ -953,14 +1003,17 @@ void HandleGlobalMessage(const std::string &msg, const std::string &value) { osm.Show(sy->T("WARNING: Battery save mode is on"), 2.0f, 0xFFFFFF, -1, true, "core_powerSaving"); #endif } - Core_SetPowerSaving(value != "false"); } if (msg == "permission_granted" && value == "storage") { +#ifdef __ANDROID__ + CreateDirectoriesAndroid(); +#endif // We must have failed to load the config before, so load it now to avoid overwriting the old config // with a freshly generated one. ILOG("Reloading config after storage permission grant."); g_Config.Load(); + PostLoadConfig(); } } @@ -984,6 +1037,11 @@ void NativeUpdate() { } bool NativeIsAtTopLevel() { + // This might need some synchronization? + if (!screenManager) { + ELOG("No screen manager active"); + return false; + } Screen *currentScreen = screenManager->topScreen(); if (currentScreen) { bool top = currentScreen->isTopLevel(); @@ -1134,7 +1192,8 @@ bool NativeIsRestarting() { } void NativeShutdown() { - screenManager->shutdown(); + if (screenManager) + screenManager->shutdown(); delete screenManager; screenManager = nullptr; diff --git a/UI/PauseScreen.cpp b/UI/PauseScreen.cpp index c643d2b18a..9aabb3a2f7 100644 --- a/UI/PauseScreen.cpp +++ b/UI/PauseScreen.cpp @@ -238,9 +238,9 @@ void SaveSlotView::Draw(UIContext &dc) { UI::LinearLayout::Draw(dc); } -static void AfterSaveStateAction(bool status, const std::string &message, void *) { +static void AfterSaveStateAction(SaveState::Status status, const std::string &message, void *) { if (!message.empty()) { - osm.Show(message, 2.0); + osm.Show(message, status == SaveState::Status::SUCCESS ? 2.0 : 5.0); } } @@ -373,7 +373,7 @@ void GamePauseScreen::dialogFinished(const Screen *dialog, DialogResult dr) { ScreenshotViewScreen *s = (ScreenshotViewScreen *)dialog; int slot = s->GetSlot(); g_Config.iCurrentStateSlot = slot; - SaveState::LoadSlot(gamePath_, slot, SaveState::Callback(), 0); + SaveState::LoadSlot(gamePath_, slot, &AfterSaveStateAction); finishNextFrame_ = true; } else { diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index 2767e72761..31d8f9f966 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -18,20 +18,15 @@ #include #include #include -#include #include "base/timeutil.h" -#include "ext/vjson/json.h" -#include "file/fd_util.h" #include "i18n/i18n.h" +#include "json/json_reader.h" #include "net/http_client.h" -#include "net/http_server.h" #include "net/resolve.h" -#include "net/sinks.h" -#include "thread/threadutil.h" #include "Common/Common.h" -#include "Common/FileUtil.h" #include "Core/Config.h" +#include "Core/WebServer.h" #include "UI/RemoteISOScreen.h" using namespace UI; @@ -39,169 +34,9 @@ using namespace UI; static const char *REPORT_HOSTNAME = "report.ppsspp.org"; static const int REPORT_PORT = 80; -enum class ServerStatus { - STOPPED, - STARTING, - RUNNING, - STOPPING, -}; - -static std::thread *serverThread = nullptr; -static ServerStatus serverStatus; -static std::mutex serverStatusLock; -static std::condition_variable serverStatusCond; - static bool scanCancelled = false; static bool scanAborted = false; -static void UpdateStatus(ServerStatus s) { - std::lock_guard guard(serverStatusLock); - serverStatus = s; - serverStatusCond.notify_one(); -} - -static ServerStatus RetrieveStatus() { - std::lock_guard guard(serverStatusLock); - return serverStatus; -} - -// This reports the local IP address to report.ppsspp.org, which can then -// relay that address to a mobile device searching for the server. -static void RegisterServer(int port) { - http::Client http; - Buffer theVoid; - - if (http.Resolve(REPORT_HOSTNAME, REPORT_PORT)) { - if (http.Connect(2, 20.0, &scanCancelled)) { - char resource[1024] = {}; - std::string ip = fd_util::GetLocalIP(http.sock()); - snprintf(resource, sizeof(resource) - 1, "/match/update?local=%s&port=%d", ip.c_str(), port); - - http.GET(resource, &theVoid); - http.Disconnect(); - } - } -} - -static void ExecuteServer() { - setCurrentThreadName("HTTPServer"); - - auto http = new http::Server(new threading::SameThreadExecutor()); - - std::map paths; - for (std::string filename : g_Config.recentIsos) { -#ifdef _WIN32 - static const std::string sep = "\\/"; -#else - static const std::string sep = "/"; -#endif - size_t basepos = filename.find_last_of(sep); - std::string basename = "/" + (basepos == filename.npos ? filename : filename.substr(basepos + 1)); - - // Let's not serve directories, since they won't work. Only single files. - // Maybe can do PBPs and other files later. Would be neat to stream virtual disc filesystems. - if (endsWithNoCase(basename, ".cso") || endsWithNoCase(basename, ".iso")) { - paths[ReplaceAll(basename, " ", "%20")] = filename; - } - } - - auto handler = [&](const http::Request &request) { - std::string filename = paths[request.resource()]; - s64 sz = File::GetFileSize(filename); - - std::string range; - if (request.Method() == http::RequestHeader::HEAD) { - request.WriteHttpResponseHeader(200, sz, "application/octet-stream", "Accept-Ranges: bytes\r\n"); - } else if (request.GetHeader("range", &range)) { - s64 begin = 0, last = 0; - if (sscanf(range.c_str(), "bytes=%lld-%lld", &begin, &last) != 2) { - request.WriteHttpResponseHeader(400, -1, "text/plain"); - request.Out()->Push("Could not understand range request."); - return; - } - - if (begin < 0 || begin > last || last >= sz) { - request.WriteHttpResponseHeader(416, -1, "text/plain"); - request.Out()->Push("Range goes outside of file."); - return; - } - - FILE *fp = File::OpenCFile(filename, "rb"); - if (!fp || fseek(fp, begin, SEEK_SET) != 0) { - request.WriteHttpResponseHeader(500, -1, "text/plain"); - request.Out()->Push("File access failed."); - if (fp) { - fclose(fp); - } - return; - } - - s64 len = last - begin + 1; - char contentRange[1024]; - sprintf(contentRange, "Content-Range: bytes %lld-%lld/%lld\r\n", begin, last, sz); - request.WriteHttpResponseHeader(206, len, "application/octet-stream", contentRange); - - const size_t CHUNK_SIZE = 16 * 1024; - char *buf = new char[CHUNK_SIZE]; - for (s64 pos = 0; pos < len; pos += CHUNK_SIZE) { - s64 chunklen = std::min(len - pos, (s64)CHUNK_SIZE); - fread(buf, chunklen, 1, fp); - request.Out()->Push(buf, chunklen); - } - fclose(fp); - delete [] buf; - request.Out()->Flush(); - } else { - request.WriteHttpResponseHeader(418, -1, "text/plain"); - request.Out()->Push("This server only supports range requests."); - } - }; - - for (auto pair : paths) { - http->RegisterHandler(pair.first.c_str(), handler); - } - - if (!http->Listen(g_Config.iRemoteISOPort)) { - if (!http->Listen(0)) { - ERROR_LOG(FILESYS, "Unable to listen on any port"); - UpdateStatus(ServerStatus::STOPPED); - return; - } - } - UpdateStatus(ServerStatus::RUNNING); - - g_Config.iRemoteISOPort = http->Port(); - RegisterServer(http->Port()); - double lastRegister = real_time_now(); - while (RetrieveStatus() == ServerStatus::RUNNING) { - http->RunSlice(5.0); - - double now = real_time_now(); - if (now > lastRegister + 540.0) { - RegisterServer(http->Port()); - lastRegister = now; - } - } - - http->Stop(); - - UpdateStatus(ServerStatus::STOPPED); -} - -bool StartRemoteISOSharing() { - std::lock_guard guard(serverStatusLock); - - if (serverStatus != ServerStatus::STOPPED) { - return false; - } - - serverStatus = ServerStatus::STARTING; - serverThread = new std::thread(&ExecuteServer); - serverThread->detach(); - - return true; -} - static bool FindServer(std::string &resultHost, int &resultPort) { http::Client http; Buffer result; @@ -252,16 +87,19 @@ static bool FindServer(std::string &resultHost, int &resultPort) { return false; } - const json_value *entries = reader.root(); - if (!entries) { + const JsonValue entries = reader.rootArray(); + if (entries.getTag() != JSON_ARRAY) { return false; } std::vector servers; - const json_value *entry = entries->first_child; - while (entry && !scanCancelled) { - const char *host = entry->getString("ip", ""); - int port = entry->getInt("p", 0); + for (const auto pentry : entries) { + JsonGet entry = pentry->value; + if (scanCancelled) + return false; + + const char *host = entry.getString("ip", ""); + int port = entry.getInt("p", 0); char url[1024] = {}; snprintf(url, sizeof(url), "http://%s:%d", host, port); @@ -270,8 +108,6 @@ static bool FindServer(std::string &resultHost, int &resultPort) { if (TryServer(host, port)) { return true; } - - entry = entry->next_sibling; } // None of the local IPs were reachable. @@ -371,11 +207,8 @@ RemoteISOScreen::RemoteISOScreen() : serverRunning_(false), serverStopping_(fals void RemoteISOScreen::update() { UIScreenWithBackground::update(); - bool nowRunning = RetrieveStatus() != ServerStatus::STOPPED; + bool nowRunning = !WebServerStopped(WebServerFlags::DISCS); if (serverStopping_ && !nowRunning) { - // Server stopped, delete the thread. - delete serverThread; - serverThread = nullptr; serverStopping_ = false; } @@ -402,11 +235,10 @@ void RemoteISOScreen::CreateViews() { rightColumnItems->SetSpacing(0.0f); Choice *browseChoice = new Choice(ri->T("Browse Games")); rightColumnItems->Add(browseChoice)->OnClick.Handle(this, &RemoteISOScreen::HandleBrowse); - ServerStatus status = RetrieveStatus(); - if (status == ServerStatus::STOPPING) { + if (WebServerStopping(WebServerFlags::DISCS)) { rightColumnItems->Add(new Choice(ri->T("Stopping..")))->SetDisabledPtr(&serverStopping_); browseChoice->SetEnabled(false); - } else if (status != ServerStatus::STOPPED) { + } else if (!WebServerStopped(WebServerFlags::DISCS)) { rightColumnItems->Add(new Choice(ri->T("Stop Sharing")))->OnClick.Handle(this, &RemoteISOScreen::HandleStopServer); browseChoice->SetEnabled(false); } else { @@ -428,7 +260,7 @@ void RemoteISOScreen::CreateViews() { } UI::EventReturn RemoteISOScreen::HandleStartServer(UI::EventParams &e) { - if (!StartRemoteISOSharing()) { + if (!StartWebServer(WebServerFlags::DISCS)) { return EVENT_SKIPPED; } @@ -436,13 +268,10 @@ UI::EventReturn RemoteISOScreen::HandleStartServer(UI::EventParams &e) { } UI::EventReturn RemoteISOScreen::HandleStopServer(UI::EventParams &e) { - std::lock_guard guard(serverStatusLock); - - if (serverStatus != ServerStatus::RUNNING) { + if (!StopWebServer(WebServerFlags::DISCS)) { return EVENT_SKIPPED; } - serverStatus = ServerStatus::STOPPING; serverStopping_ = true; RecreateViews(); @@ -661,13 +490,13 @@ void RemoteISOBrowseScreen::CreateViews() { } RemoteISOSettingsScreen::RemoteISOSettingsScreen() { - serverRunning_ = RetrieveStatus() != ServerStatus::STOPPED; + serverRunning_ = !WebServerStopped(WebServerFlags::DISCS); } void RemoteISOSettingsScreen::update() { UIDialogScreenWithBackground::update(); - bool nowRunning = RetrieveStatus() != ServerStatus::STOPPED; + bool nowRunning = !WebServerStopped(WebServerFlags::DISCS); if (serverRunning_ != nowRunning) { RecreateViews(); } diff --git a/UI/RemoteISOScreen.h b/UI/RemoteISOScreen.h index a4384d793e..b7f7ff0f5c 100644 --- a/UI/RemoteISOScreen.h +++ b/UI/RemoteISOScreen.h @@ -25,8 +25,6 @@ #include "UI/MiscScreens.h" #include "UI/MainScreen.h" -bool StartRemoteISOSharing(); - class RemoteISOScreen : public UIScreenWithBackground { public: RemoteISOScreen(); diff --git a/UI/ReportScreen.cpp b/UI/ReportScreen.cpp index 549a86f604..8d8fb86100 100644 --- a/UI/ReportScreen.cpp +++ b/UI/ReportScreen.cpp @@ -17,6 +17,8 @@ #include #include "base/display.h" +// TODO: For text align flags, probably shouldn';t be in gfx_es2/... +#include "gfx_es2/draw_buffer.h" #include "i18n/i18n.h" #include "thin3d/thin3d.h" #include "ui/ui_context.h" @@ -66,7 +68,7 @@ RatingChoice::RatingChoice(const char *captionKey, int *value, LayoutParams *lay I18NCategory *rp = GetI18NCategory("Reporting"); group_ = new LinearLayout(ORIENT_HORIZONTAL); - Add(new TextView(rp->T(captionKey)))->SetShadow(true); + Add(new TextView(rp->T(captionKey), FLAG_WRAP_TEXT, false))->SetShadow(true); Add(group_); group_->SetSpacing(0.0f); @@ -170,6 +172,11 @@ void ReportScreen::update() { UIDialogScreenWithGameBackground::update(); } +void ReportScreen::resized() { + UIDialogScreenWithGameBackground::resized(); + RecreateViews(); +} + EventReturn ReportScreen::HandleChoice(EventParams &e) { if (overall_ == ReportingOverallScore::NONE) { graphics_ = 0; @@ -218,14 +225,15 @@ void ReportScreen::CreateViews() { Margins actionMenuMargins(0, 20, 15, 0); Margins contentMargins(0, 20, 5, 5); + float leftColumnWidth = dp_xres - actionMenuMargins.horiz() - contentMargins.horiz() - 300.0f; ViewGroup *leftColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(WRAP_CONTENT, FILL_PARENT, 0.4f, contentMargins)); LinearLayout *leftColumnItems = new LinearLayout(ORIENT_VERTICAL, new LayoutParams(WRAP_CONTENT, FILL_PARENT)); ViewGroup *rightColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(300, FILL_PARENT, actionMenuMargins)); LinearLayout *rightColumnItems = new LinearLayout(ORIENT_VERTICAL); - leftColumnItems->Add(new TextView(rp->T("FeedbackDesc", "How's the emulation? Let us and the community know!"), new LinearLayoutParams(Margins(12, 5, 0, 5))))->SetShadow(true); + leftColumnItems->Add(new TextView(rp->T("FeedbackDesc", "How's the emulation? Let us and the community know!"), FLAG_WRAP_TEXT, false, new LinearLayoutParams(Margins(12, 5, 0, 5))))->SetShadow(true); if (!Reporting::IsEnabled()) { - reportingNotice_ = leftColumnItems->Add(new TextView(rp->T("FeedbackDisabled", "Compatibility server reports must be enabled."), new LinearLayoutParams(Margins(12, 5, 0, 5)))); + reportingNotice_ = leftColumnItems->Add(new TextView(rp->T("FeedbackDisabled", "Compatibility server reports must be enabled."), FLAG_WRAP_TEXT, false, new LinearLayoutParams(Margins(12, 5, 0, 5)))); reportingNotice_->SetShadow(true); reportingNotice_->SetTextColor(0xFF3030FF); CheckBox *reporting = leftColumnItems->Add(new CheckBox(&enableReporting_, sy->T("Enable Compatibility Server Reports"))); @@ -237,7 +245,10 @@ void ReportScreen::CreateViews() { #ifdef MOBILE_DEVICE if (!Core_GetPowerSaving()) { - leftColumnItems->Add(new TextView(rp->T("FeedbackIncludeCRC", "Note: Battery will be used to send a disc CRC"), new LinearLayoutParams(Margins(12, 5, 0, 5))))->SetEnabledPtr(&enableReporting_); + auto crcWarning = new TextView(rp->T("FeedbackIncludeCRC", "Note: Battery will be used to send a disc CRC"), FLAG_WRAP_TEXT, false, new LinearLayoutParams(Margins(12, 5, 0, 5))); + crcWarning->SetShadow(true); + crcWarning->SetEnabledPtr(&enableReporting_); + leftColumnItems->Add(crcWarning); } #endif @@ -257,11 +268,15 @@ void ReportScreen::CreateViews() { } leftColumnItems->Add(new CompatRatingChoice("Overall", (int *)&overall_))->SetEnabledPtr(&enableReporting_)->OnChoice.Handle(this, &ReportScreen::HandleChoice); - overallDescription_ = leftColumnItems->Add(new TextView("", new LinearLayoutParams(Margins(10, 0)))); + overallDescription_ = leftColumnItems->Add(new TextView("", FLAG_WRAP_TEXT, false, new LinearLayoutParams(Margins(10, 0)))); overallDescription_->SetShadow(true); - leftColumnItems->Add(new RatingChoice("Graphics", &graphics_))->SetEnabledPtr(&ratingEnabled_)->OnChoice.Handle(this, &ReportScreen::HandleChoice); - leftColumnItems->Add(new RatingChoice("Speed", &speed_))->SetEnabledPtr(&ratingEnabled_)->OnChoice.Handle(this, &ReportScreen::HandleChoice); - leftColumnItems->Add(new RatingChoice("Gameplay", &gameplay_))->SetEnabledPtr(&ratingEnabled_)->OnChoice.Handle(this, &ReportScreen::HandleChoice); + + UI::Orientation ratingsOrient = leftColumnWidth >= 750.0f ? ORIENT_HORIZONTAL : ORIENT_VERTICAL; + UI::LinearLayout *ratingsHolder = new LinearLayout(ratingsOrient, new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT)); + leftColumnItems->Add(ratingsHolder); + ratingsHolder->Add(new RatingChoice("Graphics", &graphics_))->SetEnabledPtr(&ratingEnabled_)->OnChoice.Handle(this, &ReportScreen::HandleChoice); + ratingsHolder->Add(new RatingChoice("Speed", &speed_))->SetEnabledPtr(&ratingEnabled_)->OnChoice.Handle(this, &ReportScreen::HandleChoice); + ratingsHolder->Add(new RatingChoice("Gameplay", &gameplay_))->SetEnabledPtr(&ratingEnabled_)->OnChoice.Handle(this, &ReportScreen::HandleChoice); rightColumnItems->SetSpacing(0.0f); rightColumnItems->Add(new Choice(rp->T("Open Browser")))->OnClick.Handle(this, &ReportScreen::HandleBrowser); @@ -288,16 +303,18 @@ void ReportScreen::UpdateSubmit() { void ReportScreen::UpdateOverallDescription() { I18NCategory *rp = GetI18NCategory("Reporting"); const char *desc; + uint32_t c = 0xFFFFFFFF; switch (overall_) { case ReportingOverallScore::PERFECT: desc = rp->T("Perfect Description", "Flawless emulation for the entire game - great!"); break; case ReportingOverallScore::PLAYABLE: desc = rp->T("Plays Description", "Fully playable but might be with glitches"); break; - case ReportingOverallScore::INGAME: desc = rp->T("In-game Description", "Gets into gameplay, but too buggy too complete"); break; + case ReportingOverallScore::INGAME: desc = rp->T("In-game Description", "Gets into gameplay, but too buggy to complete"); break; case ReportingOverallScore::MENU: desc = rp->T("Menu/Intro Description", "Can't get into the game itself"); break; - case ReportingOverallScore::NONE: desc = rp->T("Nothing Description", "Completely broken"); break; + case ReportingOverallScore::NONE: desc = rp->T("Nothing Description", "Completely broken"); c = 0xFF0000FF; break; default: desc = rp->T("Unselected Overall Description", "How well does this game emulate?"); break; } overallDescription_->SetText(desc); + overallDescription_->SetTextColor(c); } EventReturn ReportScreen::HandleSubmit(EventParams &e) { @@ -319,7 +336,7 @@ EventReturn ReportScreen::HandleSubmit(EventParams &e) { std::string filename = includeScreenshot_ ? screenshotFilename_ : ""; Reporting::ReportCompatibility(compat, graphics_ + 1, speed_ + 1, gameplay_ + 1, filename); TriggerFinish(DR_OK); - screenManager()->push(new ReportFinishScreen(gamePath_)); + screenManager()->push(new ReportFinishScreen(gamePath_, overall_)); return EVENT_DONE; } @@ -329,8 +346,8 @@ EventReturn ReportScreen::HandleBrowser(EventParams &e) { return EVENT_DONE; } -ReportFinishScreen::ReportFinishScreen(const std::string &gamePath) - : UIDialogScreenWithGameBackground(gamePath), resultNotice_(nullptr), setStatus_(false) { +ReportFinishScreen::ReportFinishScreen(const std::string &gamePath, ReportingOverallScore score) + : UIDialogScreenWithGameBackground(gamePath), score_(score) { } void ReportFinishScreen::CreateViews() { @@ -344,8 +361,15 @@ void ReportFinishScreen::CreateViews() { ViewGroup *rightColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(300, FILL_PARENT, actionMenuMargins)); LinearLayout *rightColumnItems = new LinearLayout(ORIENT_VERTICAL); - leftColumnItems->Add(new TextView(rp->T("FeedbackThanks", "Thanks for your feedback."), new LinearLayoutParams(Margins(12, 5, 0, 5)))); - resultNotice_ = leftColumnItems->Add(new TextView(rp->T("FeedbackDelayInfo", "Your data is being submitted in the background."), new LinearLayoutParams(Margins(12, 5, 0, 5)))); + leftColumnItems->Add(new TextView(rp->T("FeedbackThanks", "Thanks for your feedback."), FLAG_WRAP_TEXT, false, new LinearLayoutParams(Margins(12, 5, 0, 5))))->SetShadow(true); + if (score_ == ReportingOverallScore::PERFECT || score_ == ReportingOverallScore::PLAYABLE) { + resultNotice_ = leftColumnItems->Add(new TextView(rp->T("FeedbackDelayInfo", "Your data is being submitted in the background."), FLAG_WRAP_TEXT, false, new LinearLayoutParams(Margins(12, 5, 0, 5)))); + } else { + resultNotice_ = leftColumnItems->Add(new TextView(rp->T("SuggestionsWaiting", "Submitting and checking other user feedback.."), FLAG_WRAP_TEXT, false, new LinearLayoutParams(Margins(12, 5, 0, 5)))); + } + resultNotice_->SetShadow(true); + resultItems_ = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, Margins(12, 5, 0, 5))); + leftColumnItems->Add(resultItems_); rightColumnItems->SetSpacing(0.0f); rightColumnItems->Add(new Choice(rp->T("View Feedback")))->OnClick.Handle(this, &ReportFinishScreen::HandleViewFeedback); @@ -368,11 +392,13 @@ void ReportFinishScreen::update() { Reporting::ReportStatus status = Reporting::GetStatus(); switch (status) { case Reporting::ReportStatus::WORKING: - resultNotice_->SetText(rp->T("FeedbackSubmitDone", "Your data has been submitted.")); + ShowSuggestions(); + setStatus_ = true; break; case Reporting::ReportStatus::FAILING: resultNotice_->SetText(rp->T("FeedbackSubmitFail", "Could not submit data to server. Try updating PPSSPP.")); + setStatus_ = true; break; case Reporting::ReportStatus::BUSY: @@ -385,6 +411,52 @@ void ReportFinishScreen::update() { UIDialogScreenWithGameBackground::update(); } +void ReportFinishScreen::ShowSuggestions() { + I18NCategory *rp = GetI18NCategory("Reporting"); + + auto suggestions = Reporting::CompatibilitySuggestions(); + if (score_ == ReportingOverallScore::PERFECT || score_ == ReportingOverallScore::PLAYABLE) { + resultNotice_->SetText(rp->T("FeedbackSubmitDone", "Your data has been submitted.")); + } else if (suggestions.empty()) { + resultNotice_->SetText(rp->T("SuggestionsNone", "This game isn't working for other users too.")); + } else { + resultNotice_->SetText(rp->T("SuggestionsFound", "Other users have reported better results. Tap View Feedback for more detail.")); + + resultItems_->Clear(); + bool shownConfig = false; + bool valid = false; + for (auto item : suggestions) { + const char *suggestion = nullptr; + if (item == "Upgrade") { + suggestion = rp->T("SuggestionUpgrade", "Upgrade to a newer PPSSPP build"); + } if (item == "Downgrade") { + suggestion = rp->T("SuggestionDowngrade", "Downgrade to an older PPSSPP version (please report this bug)"); + } else if (item == "VerifyDisc") { + suggestion = rp->T("SuggestionVerifyDisc", "Check your ISO is a good copy of your disc"); + } else if (item == "Config:CPUSpeed:0") { + suggestion = rp->T("SuggestionCPUSpeed0", "Disable locked CPU speed setting"); + } else { + bool isConfig = startsWith(item, "Config:"); + if (isConfig && !shownConfig) { + suggestion = rp->T("SuggestionConfig", "See reports on website for good settings"); + shownConfig = true; + } + // Ignore unknown configs, hopefully we recognized "Upgrade" at least. + } + + if (suggestion) { + valid = true; + resultItems_->Add(new TextView(std::string(" - ") + suggestion, FLAG_WRAP_TEXT, false))->SetShadow(true); + } + } + + if (!valid) { + // No actual valid versions. Let's just say upgrade and hope the server's not broken. + resultItems_->Add(new TextView(std::string(" - ") + rp->T("SuggestionUpgrade", "Upgrade to a newer PPSSPP build"), FLAG_WRAP_TEXT, false))->SetShadow(true); + } + } +} + UI::EventReturn ReportFinishScreen::HandleViewFeedback(UI::EventParams &e) { const std::string url = "http://" + Reporting::ServerHost() + "/game/" + Reporting::CurrentGameID(); LaunchBrowser(url.c_str()); diff --git a/UI/ReportScreen.h b/UI/ReportScreen.h index f43b2e9b19..18c0d2b930 100644 --- a/UI/ReportScreen.h +++ b/UI/ReportScreen.h @@ -38,6 +38,7 @@ public: protected: void update() override; + void resized() override; void CreateViews() override; void UpdateSubmit(); void UpdateOverallDescription(); @@ -64,14 +65,17 @@ protected: class ReportFinishScreen : public UIDialogScreenWithGameBackground { public: - ReportFinishScreen(const std::string &gamePath); + ReportFinishScreen(const std::string &gamePath, ReportingOverallScore score); protected: void update() override; void CreateViews() override; + void ShowSuggestions(); UI::EventReturn HandleViewFeedback(UI::EventParams &e); - UI::TextView *resultNotice_; - bool setStatus_; + UI::TextView *resultNotice_ = nullptr; + UI::LinearLayout *resultItems_ = nullptr; + ReportingOverallScore score_; + bool setStatus_ = false; }; diff --git a/UI/SavedataScreen.cpp b/UI/SavedataScreen.cpp index fbce3e7862..37f767a82d 100644 --- a/UI/SavedataScreen.cpp +++ b/UI/SavedataScreen.cpp @@ -16,12 +16,14 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include +#include #include "base/colorutil.h" #include "base/timeutil.h" #include "gfx_es2/draw_buffer.h" #include "i18n/i18n.h" #include "math/curves.h" +#include "thread/prioritizedworkqueue.h" #include "util/text/utf8.h" #include "ui/ui_context.h" #include "ui/view.h" @@ -121,10 +123,42 @@ private: std::string savePath_; }; +class SortedLinearLayout : public UI::LinearLayout { +public: + typedef std::function CompareFunc; + typedef std::function DoneFunc; + + SortedLinearLayout(UI::Orientation orientation, UI::LayoutParams *layoutParams = nullptr) + : UI::LinearLayout(orientation, layoutParams) { + } + + void SetCompare(CompareFunc lessFunc, DoneFunc) { + lessFunc_ = lessFunc; + } + + void Update() override; + +private: + CompareFunc lessFunc_; + DoneFunc doneFunc_; +}; + +void SortedLinearLayout::Update() { + if (lessFunc_) { + std::stable_sort(views_.begin(), views_.end(), lessFunc_); + } + if (doneFunc_ && doneFunc_()) { + lessFunc_ = CompareFunc(); + } + UI::LinearLayout::Update(); +} + class SavedataButton : public UI::Clickable { public: SavedataButton(const std::string &gamePath, UI::LayoutParams *layoutParams = 0) - : UI::Clickable(layoutParams), savePath_(gamePath) {} + : UI::Clickable(layoutParams), savePath_(gamePath) { + SetTag(gamePath); + } void Draw(UIContext &dc) override; void GetContentDimensions(const UIContext &dc, float &w, float &h) const override { @@ -134,8 +168,6 @@ public: const std::string &GamePath() const { return savePath_; } - std::string GetGamePath() const { return savePath_; } - private: std::string savePath_; std::string title_; @@ -279,10 +311,45 @@ void SavedataButton::Draw(UIContext &dc) { } SavedataBrowser::SavedataBrowser(std::string path, UI::LayoutParams *layoutParams) - : LinearLayout(UI::ORIENT_VERTICAL, layoutParams), gameList_(0), path_(path) { + : LinearLayout(UI::ORIENT_VERTICAL, layoutParams), path_(path) { Refresh(); } +void SavedataBrowser::SetSortOption(SavedataSortOption opt) { + sortOption_ = opt; + if (gameList_) { + SortedLinearLayout *gl = static_cast(gameList_); + if (sortOption_ == SavedataSortOption::FILENAME) { + gl->SetCompare(&ByFilename, &SortDone); + } else if (sortOption_ == SavedataSortOption::SIZE) { + gl->SetCompare(&BySize, &SortDone); + } + } +} + +bool SavedataBrowser::ByFilename(const UI::View *v1, const UI::View *v2) { + const SavedataButton *b1 = static_cast(v1); + const SavedataButton *b2 = static_cast(v2); + + return strcmp(b1->GamePath().c_str(), b2->GamePath().c_str()) < 0; +} + +bool SavedataBrowser::BySize(const UI::View *v1, const UI::View *v2) { + const SavedataButton *b1 = static_cast(v1); + const SavedataButton *b2 = static_cast(v2); + + std::shared_ptr g1info = g_gameInfoCache->GetInfo(nullptr, b1->GamePath(), GAMEINFO_WANTSIZE); + std::shared_ptr g2info = g_gameInfoCache->GetInfo(nullptr, b2->GamePath(), GAMEINFO_WANTSIZE); + + // They might be zero, but that's fine. + return g1info->gameSize > g2info->gameSize; +} + +bool SavedataBrowser::SortDone() { + PrioritizedWorkQueue *wq = g_gameInfoCache->WorkQueue(); + return wq->Done(); +} + void SavedataBrowser::Refresh() { using namespace UI; @@ -293,7 +360,7 @@ void SavedataBrowser::Refresh() { I18NCategory *mm = GetI18NCategory("MainMenu"); I18NCategory *sa = GetI18NCategory("Savedata"); - UI::LinearLayout *gl = new UI::LinearLayout(UI::ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); + SortedLinearLayout *gl = new SortedLinearLayout(UI::ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); gl->SetSpacing(4.0f); gameList_ = gl; Add(gameList_); @@ -327,6 +394,9 @@ void SavedataBrowser::Refresh() { group->Add(new TextView(sa->T("None yet. Things will appear here after you save."))); gameList_->Add(group); } + + // Reapply. + SetSortOption(sortOption_); } UI::EventReturn SavedataBrowser::SavedataButtonClick(UI::EventParams &e) { @@ -357,25 +427,47 @@ void SavedataScreen::CreateViews() { std::string savestate_dir = GetSysDirectory(DIRECTORY_SAVESTATE); gridStyle_ = false; - root_ = new LinearLayout(ORIENT_VERTICAL); + root_ = new AnchorLayout(); + + LinearLayout *main = new LinearLayout(ORIENT_VERTICAL, new AnchorLayoutParams(FILL_PARENT, FILL_PARENT)); TabHolder *tabs = new TabHolder(ORIENT_HORIZONTAL, 64, new LinearLayoutParams(FILL_PARENT, FILL_PARENT, 1.0f)); tabs->SetTag("Savedata"); ScrollView *scroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); scroll->SetTag("SavedataBrowser"); - browser_ = scroll->Add(new SavedataBrowser(savedata_dir, new LayoutParams(FILL_PARENT, FILL_PARENT))); - browser_->OnChoice.Handle(this, &SavedataScreen::OnSavedataButtonClick); + dataBrowser_ = scroll->Add(new SavedataBrowser(savedata_dir, new LayoutParams(FILL_PARENT, FILL_PARENT))); + dataBrowser_->SetSortOption(sortOption_); + dataBrowser_->OnChoice.Handle(this, &SavedataScreen::OnSavedataButtonClick); tabs->AddTab(sa->T("Save Data"), scroll); ScrollView *scroll2 = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); scroll2->SetTag("SavedataStatesBrowser"); - SavedataBrowser *browser2 = scroll2->Add(new SavedataBrowser(savestate_dir)); - browser2->OnChoice.Handle(this, &SavedataScreen::OnSavedataButtonClick); + stateBrowser_ = scroll2->Add(new SavedataBrowser(savestate_dir)); + stateBrowser_->SetSortOption(sortOption_); + stateBrowser_->OnChoice.Handle(this, &SavedataScreen::OnSavedataButtonClick); tabs->AddTab(sa->T("Save States"), scroll2); - root_->Add(tabs); - root_->Add(new Button(di->T("Back")))->OnClick.Handle(this, &UIScreen::OnBack); + main->Add(tabs); + main->Add(new Button(di->T("Back"), new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 0.0f)))->OnClick.Handle(this, &UIScreen::OnBack); + + ChoiceStrip *sortStrip = new ChoiceStrip(ORIENT_HORIZONTAL, new AnchorLayoutParams(NONE, 0, 0, NONE)); + sortStrip->AddChoice(sa->T("Filename")); + sortStrip->AddChoice(sa->T("Size")); + sortStrip->SetSelection((int)sortOption_); + sortStrip->OnChoice.Handle(this, &SavedataScreen::OnSortClick); + + root_->Add(main); + root_->Add(sortStrip); +} + +UI::EventReturn SavedataScreen::OnSortClick(UI::EventParams &e) { + sortOption_ = SavedataSortOption(e.a); + + dataBrowser_->SetSortOption(sortOption_); + stateBrowser_->SetSortOption(sortOption_); + + return UI::EVENT_DONE; } UI::EventReturn SavedataScreen::OnSavedataButtonClick(UI::EventParams &e) { diff --git a/UI/SavedataScreen.h b/UI/SavedataScreen.h index adf688013c..0ab3e6ed56 100644 --- a/UI/SavedataScreen.h +++ b/UI/SavedataScreen.h @@ -26,17 +26,29 @@ #include "UI/MiscScreens.h" +enum class SavedataSortOption { + FILENAME, + SIZE, +}; + class SavedataBrowser : public UI::LinearLayout { public: SavedataBrowser(std::string path, UI::LayoutParams *layoutParams = 0); + void SetSortOption(SavedataSortOption opt); + UI::Event OnChoice; private: + static bool ByFilename(const UI::View *, const UI::View *); + static bool BySize(const UI::View *, const UI::View *); + static bool SortDone(); + void Refresh(); UI::EventReturn SavedataButtonClick(UI::EventParams &e); - UI::ViewGroup *gameList_; + SavedataSortOption sortOption_ = SavedataSortOption::FILENAME; + UI::ViewGroup *gameList_ = nullptr; std::string path_; }; @@ -50,7 +62,10 @@ public: protected: UI::EventReturn OnSavedataButtonClick(UI::EventParams &e); + UI::EventReturn OnSortClick(UI::EventParams &e); void CreateViews() override; bool gridStyle_; - SavedataBrowser *browser_; + SavedataSortOption sortOption_ = SavedataSortOption::FILENAME; + SavedataBrowser *dataBrowser_; + SavedataBrowser *stateBrowser_; }; diff --git a/UI/Store.cpp b/UI/Store.cpp index 99c79d2213..da2d2f1a4f 100644 --- a/UI/Store.cpp +++ b/UI/Store.cpp @@ -18,7 +18,7 @@ #include #include "base/basictypes.h" -#include "ext/vjson/json.h" +#include "json/json_reader.h" #include "i18n/i18n.h" #include "ui/screen.h" @@ -392,33 +392,32 @@ void StoreScreen::update() { void StoreScreen::ParseListing(std::string json) { JsonReader reader(json.c_str(), json.size()); - if (!reader.ok()) { + if (!reader.ok() || !reader.root()) { ELOG("Error parsing JSON from store"); connectionError_ = true; RecreateViews(); return; } - json_value *root = reader.root(); - const json_value *entries = root->getArray("entries"); + const JsonGet root = reader.root(); + const JsonNode *entries = root.getArray("entries"); if (entries) { entries_.clear(); - const json_value *game = entries->first_child; - while (game) { + for (const JsonNode *pgame : entries->value) { + JsonGet game = pgame->value; StoreEntry e; e.type = ENTRY_PBPZIP; e.name = GetTranslatedString(game, "name"); e.description = GetTranslatedString(game, "description", ""); - e.author = game->getString("author", "?"); - e.size = game->getInt("size"); - e.downloadURL = game->getString("download-url", ""); - e.iconURL = game->getString("icon-url", ""); - e.hidden = game->getBool("hidden", false); - const char *file = game->getString("file", 0); + e.author = game.getString("author", "?"); + e.size = game.getInt("size"); + e.downloadURL = game.getString("download-url", ""); + e.iconURL = game.getString("icon-url", ""); + e.hidden = game.getBool("hidden", false); + const char *file = game.getString("file", nullptr); if (!file) continue; e.file = file; entries_.push_back(e); - game = game->next_sibling; } } } @@ -541,16 +540,16 @@ std::string StoreScreen::GetStoreJsonURL(std::string storePath) const { return path; } -std::string StoreScreen::GetTranslatedString(const json_value *json, std::string key, const char *fallback) const { - const json_value *dict = json->getDict("en_US"); - if (dict && json->hasChild(lang_.c_str(), JSON_OBJECT)) { - if (json->getDict(lang_.c_str())->hasChild(key.c_str(), JSON_STRING)) { - dict = json->getDict(lang_.c_str()); +std::string StoreScreen::GetTranslatedString(const JsonGet json, std::string key, const char *fallback) const { + JsonGet dict = json.getDict("en_US"); + if (dict && json.hasChild(lang_.c_str(), JSON_OBJECT)) { + if (json.getDict(lang_.c_str()).hasChild(key.c_str(), JSON_STRING)) { + dict = json.getDict(lang_.c_str()); } } - const char *str = 0; + const char *str = nullptr; if (dict) { - str = dict->getString(key.c_str(), 0); + str = dict.getString(key.c_str(), nullptr); } if (str) { return std::string(str); diff --git a/UI/Store.h b/UI/Store.h index 1fff85a4c9..e7786c7c9c 100644 --- a/UI/Store.h +++ b/UI/Store.h @@ -29,7 +29,7 @@ // set game specific settings, etc. // Uses GameInfoCache heavily to implement the functionality. -struct json_value; +struct JsonGet; class ProductItemView; enum EntryType { @@ -79,7 +79,7 @@ private: std::vector FilterEntries(); std::string GetStoreJsonURL(std::string storePath) const; - std::string GetTranslatedString(const json_value *json, std::string key, const char *fallback = 0) const; + std::string GetTranslatedString(const JsonGet json, std::string key, const char *fallback = nullptr) const; std::shared_ptr listing_; std::shared_ptr image_; diff --git a/UI/TouchControlLayoutScreen.cpp b/UI/TouchControlLayoutScreen.cpp index 87dee45e31..fc841f4e8c 100644 --- a/UI/TouchControlLayoutScreen.cpp +++ b/UI/TouchControlLayoutScreen.cpp @@ -42,9 +42,9 @@ static u32 GetButtonColor() { class DragDropButton : public MultiTouchButton { public: - DragDropButton(float &x, float &y, int bgImg, int img, float &scale) - : MultiTouchButton(bgImg, img, scale, new UI::AnchorLayoutParams(fromFullscreenCoord(x), y*local_dp_yres, UI::NONE, UI::NONE, true)), - x_(x), y_(y), theScale_(scale) { + DragDropButton(ConfigTouchPos &pos, int bgImg, int img) + : MultiTouchButton(bgImg, img, pos.scale, new UI::AnchorLayoutParams(fromFullscreenCoord(pos.x), pos.y * local_dp_yres, UI::NONE, UI::NONE, true)), + x_(pos.x), y_(pos.y), theScale_(pos.scale) { scale_ = theScale_; } @@ -89,8 +89,8 @@ private: class PSPActionButtons : public DragDropButton { public: - PSPActionButtons(float &x, float &y, float &scale, float &spacing) - : DragDropButton(x, y, -1, -1, scale), spacing_(spacing) { + PSPActionButtons(ConfigTouchPos &pos, float &spacing) + : DragDropButton(pos, -1, -1), spacing_(spacing) { using namespace UI; roundId_ = g_Config.iTouchButtonStyle ? I_ROUND_LINE : I_ROUND; @@ -172,8 +172,8 @@ private: class PSPDPadButtons : public DragDropButton { public: - PSPDPadButtons(float &x, float &y, float &scale, float &spacing) - : DragDropButton(x, y, -1, -1, scale), spacing_(spacing) { + PSPDPadButtons(ConfigTouchPos &pos, float &spacing) + : DragDropButton(pos, -1, -1), spacing_(spacing) { } void Draw(UIContext &dc) override { @@ -361,7 +361,7 @@ void TouchControlLayoutScreen::CreateViews() { controls_.clear(); - PSPActionButtons *actionButtons = new PSPActionButtons(g_Config.fActionButtonCenterX, g_Config.fActionButtonCenterY, g_Config.fActionButtonScale, g_Config.fActionButtonSpacing); + PSPActionButtons *actionButtons = new PSPActionButtons(g_Config.touchActionButtonCenter, g_Config.fActionButtonSpacing); actionButtons->setCircleVisibility(g_Config.bShowTouchCircle); actionButtons->setCrossVisibility(g_Config.bShowTouchCross); actionButtons->setTriangleVisibility(g_Config.bShowTouchTriangle); @@ -378,51 +378,63 @@ void TouchControlLayoutScreen::CreateViews() { const int comboKeyImages[5] = { I_1, I_2, I_3, I_4, I_5 }; - if (g_Config.bShowTouchDpad) { - controls_.push_back(new PSPDPadButtons(g_Config.fDpadX, g_Config.fDpadY, g_Config.fDpadScale, g_Config.fDpadSpacing)); + if (g_Config.touchDpad.show) { + controls_.push_back(new PSPDPadButtons(g_Config.touchDpad, g_Config.fDpadSpacing)); } - if (g_Config.bShowTouchSelect) { - controls_.push_back(new DragDropButton(g_Config.fSelectKeyX, g_Config.fSelectKeyY, rectImage, I_SELECT, g_Config.fSelectKeyScale)); + if (g_Config.touchSelectKey.show) { + controls_.push_back(new DragDropButton(g_Config.touchSelectKey, rectImage, I_SELECT)); } - if (g_Config.bShowTouchStart) { - controls_.push_back(new DragDropButton(g_Config.fStartKeyX, g_Config.fStartKeyY, rectImage, I_START, g_Config.fStartKeyScale)); + if (g_Config.touchStartKey.show) { + controls_.push_back(new DragDropButton(g_Config.touchStartKey, rectImage, I_START)); } - if (g_Config.bShowTouchUnthrottle) { - DragDropButton *unthrottle = new DragDropButton(g_Config.fUnthrottleKeyX, g_Config.fUnthrottleKeyY, rectImage, I_ARROW, g_Config.fUnthrottleKeyScale); + if (g_Config.touchUnthrottleKey.show) { + DragDropButton *unthrottle = new DragDropButton(g_Config.touchUnthrottleKey, rectImage, I_ARROW); unthrottle->SetAngle(180.0f); controls_.push_back(unthrottle); } - if (g_Config.bShowTouchLTrigger) { - controls_.push_back(new DragDropButton(g_Config.fLKeyX, g_Config.fLKeyY, shoulderImage, I_L, g_Config.fLKeyScale)); + if (g_Config.touchSpeed1Key.show) { + DragDropButton *speed1 = new DragDropButton(g_Config.touchSpeed1Key, rectImage, I_ARROW); + speed1->SetAngle(170.0f, 180.0f); + controls_.push_back(speed1); } - if (g_Config.bShowTouchRTrigger) { - DragDropButton *rbutton = new DragDropButton(g_Config.fRKeyX, g_Config.fRKeyY, shoulderImage, I_R, g_Config.fRKeyScale); + if (g_Config.touchSpeed2Key.show) { + DragDropButton *speed2 = new DragDropButton(g_Config.touchSpeed2Key, rectImage, I_ARROW); + speed2->SetAngle(190.0f, 180.0f); + controls_.push_back(speed2); + } + + if (g_Config.touchLKey.show) { + controls_.push_back(new DragDropButton(g_Config.touchLKey, shoulderImage, I_L)); + } + + if (g_Config.touchRKey.show) { + DragDropButton *rbutton = new DragDropButton(g_Config.touchRKey, shoulderImage, I_R); rbutton->FlipImageH(true); controls_.push_back(rbutton); } - if (g_Config.bShowTouchAnalogStick) { - controls_.push_back(new DragDropButton(g_Config.fAnalogStickX, g_Config.fAnalogStickY, stickBg, stickImage, g_Config.fAnalogStickScale)); + if (g_Config.touchAnalogStick.show) { + controls_.push_back(new DragDropButton(g_Config.touchAnalogStick, stickBg, stickImage)); } - if (g_Config.bShowComboKey0) { - controls_.push_back(new DragDropButton(g_Config.fcombo0X, g_Config.fcombo0Y, roundImage, comboKeyImages[0], g_Config.fcomboScale0)); + if (g_Config.touchCombo0.show) { + controls_.push_back(new DragDropButton(g_Config.touchCombo0, roundImage, comboKeyImages[0])); } - if (g_Config.bShowComboKey1) { - controls_.push_back(new DragDropButton(g_Config.fcombo1X, g_Config.fcombo1Y, roundImage, comboKeyImages[1], g_Config.fcomboScale1)); + if (g_Config.touchCombo1.show) { + controls_.push_back(new DragDropButton(g_Config.touchCombo1, roundImage, comboKeyImages[1])); } - if (g_Config.bShowComboKey2) { - controls_.push_back(new DragDropButton(g_Config.fcombo2X, g_Config.fcombo2Y, roundImage, comboKeyImages[2], g_Config.fcomboScale2)); + if (g_Config.touchCombo2.show) { + controls_.push_back(new DragDropButton(g_Config.touchCombo2, roundImage, comboKeyImages[2])); } - if (g_Config.bShowComboKey3) { - controls_.push_back(new DragDropButton(g_Config.fcombo3X, g_Config.fcombo3Y, roundImage, comboKeyImages[3], g_Config.fcomboScale3)); + if (g_Config.touchCombo3.show) { + controls_.push_back(new DragDropButton(g_Config.touchCombo3, roundImage, comboKeyImages[3])); } - if (g_Config.bShowComboKey4) { - controls_.push_back(new DragDropButton(g_Config.fcombo4X, g_Config.fcombo4Y, roundImage, comboKeyImages[4], g_Config.fcomboScale4)); + if (g_Config.touchCombo4.show) { + controls_.push_back(new DragDropButton(g_Config.touchCombo4, roundImage, comboKeyImages[4])); }; for (size_t i = 0; i < controls_.size(); i++) { diff --git a/UI/TouchControlVisibilityScreen.cpp b/UI/TouchControlVisibilityScreen.cpp index 2d0381ede5..4e67d1cc5c 100644 --- a/UI/TouchControlVisibilityScreen.cpp +++ b/UI/TouchControlVisibilityScreen.cpp @@ -20,84 +20,91 @@ #include "UI/ui_atlas.h" #include "i18n/i18n.h" +static const int leftColumnWidth = 140; + +class CheckBoxChoice : public UI::Choice { +public: + CheckBoxChoice(const std::string &text, UI::CheckBox *checkbox, UI::LayoutParams *lp) + : Choice(text, lp), checkbox_(checkbox) { + OnClick.Handle(this, &CheckBoxChoice::HandleClick); + } + CheckBoxChoice(ImageID imgID, UI::CheckBox *checkbox, UI::LayoutParams *lp) + : Choice(imgID, lp), checkbox_(checkbox) { + OnClick.Handle(this, &CheckBoxChoice::HandleClick); + } + +private: + UI::EventReturn HandleClick(UI::EventParams &e); + + UI::CheckBox *checkbox_; +}; + void TouchControlVisibilityScreen::CreateViews() { using namespace UI; - root_ = new ScrollView(ORIENT_VERTICAL); - LinearLayout *vert = root_->Add(new LinearLayout(ORIENT_VERTICAL, new LayoutParams(FILL_PARENT, FILL_PARENT))); + I18NCategory *di = GetI18NCategory("Dialog"); + I18NCategory *co = GetI18NCategory("Controls"); + + root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT)); + + Choice *back = new Choice(di->T("Back"), "", false, new AnchorLayoutParams(leftColumnWidth - 10, WRAP_CONTENT, 10, NONE, NONE, 10)); + root_->Add(back)->OnClick.Handle(this, &UIScreen::OnBack); + Choice *toggleAll = new Choice(di->T("Toggle All"), "", false, new AnchorLayoutParams(leftColumnWidth - 10, WRAP_CONTENT, 10, NONE, NONE, 84)); + root_->Add(toggleAll)->OnClick.Handle(this, &TouchControlVisibilityScreen::OnToggleAll); + + TabHolder *tabHolder = new TabHolder(ORIENT_VERTICAL, leftColumnWidth, new AnchorLayoutParams(10, 0, 10, 0, false)); + tabHolder->SetTag("TouchControlVisibility"); + root_->Add(tabHolder); + ScrollView *rightPanel = new ScrollView(ORIENT_VERTICAL); + tabHolder->AddTab(co->T("Visibility"), rightPanel); + + LinearLayout *vert = rightPanel->Add(new LinearLayout(ORIENT_VERTICAL, new LayoutParams(FILL_PARENT, FILL_PARENT))); vert->SetSpacing(0); - LinearLayout *topBar = new LinearLayout(ORIENT_HORIZONTAL); - I18NCategory *di = GetI18NCategory("Dialog"); - topBar->Add(new Choice(di->T("Back")))->OnClick.Handle(this, &UIScreen::OnBack); - topBar->Add(new Choice(di->T("Toggle All")))->OnClick.Handle(this, &TouchControlVisibilityScreen::OnToggleAll); - - vert->Add(topBar); - I18NCategory *co = GetI18NCategory("Controls"); vert->Add(new ItemHeader(co->T("Touch Control Visibility"))); - const int cellSize = 400; + const int cellSize = 380; UI::GridLayoutSettings gridsettings(cellSize, 64, 5); gridsettings.fillCells = true; GridLayout *grid = vert->Add(new GridLayout(gridsettings, new LayoutParams(FILL_PARENT, WRAP_CONTENT))); - std::map keyImages; - keyImages["Circle"] = I_CIRCLE; - keyImages["Cross"] = I_CROSS; - keyImages["Square"] = I_SQUARE; - keyImages["Triangle"] = I_TRIANGLE; - keyImages["Start"] = I_START; - keyImages["Select"] = I_SELECT; - keyImages["L"] = I_L; - keyImages["R"] = I_R; - keyImages["Combo0"] = I_1; - keyImages["Combo1"] = I_2; - keyImages["Combo2"] = I_3; - keyImages["Combo3"] = I_4; - keyImages["Combo4"] = I_5; - - keyToggles.clear(); - keyToggles["Circle"] = &g_Config.bShowTouchCircle; - keyToggles["Cross"] = &g_Config.bShowTouchCross; - keyToggles["Square"] = &g_Config.bShowTouchSquare; - keyToggles["Triangle"] = &g_Config.bShowTouchTriangle; - keyToggles["L"] = &g_Config.bShowTouchLTrigger; - keyToggles["R"] = &g_Config.bShowTouchRTrigger; - keyToggles["Start"] = &g_Config.bShowTouchStart; - keyToggles["Select"] = &g_Config.bShowTouchSelect; - keyToggles["Dpad"] = &g_Config.bShowTouchDpad; - keyToggles["Analog Stick"] = &g_Config.bShowTouchAnalogStick; - keyToggles["Unthrottle"] = &g_Config.bShowTouchUnthrottle; - keyToggles["Combo0"] = &g_Config.bShowComboKey0; - keyToggles["Combo1"] = &g_Config.bShowComboKey1; - keyToggles["Combo2"] = &g_Config.bShowComboKey2; - keyToggles["Combo3"] = &g_Config.bShowComboKey3; - keyToggles["Combo4"] = &g_Config.bShowComboKey4; - - std::map::iterator imageFinder; + toggles_.clear(); + toggles_.push_back({ "Circle", &g_Config.bShowTouchCircle, I_CIRCLE }); + toggles_.push_back({ "Cross", &g_Config.bShowTouchCross, I_CROSS }); + toggles_.push_back({ "Square", &g_Config.bShowTouchSquare, I_SQUARE }); + toggles_.push_back({ "Triangle", &g_Config.bShowTouchTriangle, I_TRIANGLE }); + toggles_.push_back({ "L", &g_Config.touchLKey.show, I_L }); + toggles_.push_back({ "R", &g_Config.touchRKey.show, I_R }); + toggles_.push_back({ "Start", &g_Config.touchStartKey.show, I_START }); + toggles_.push_back({ "Select", &g_Config.touchSelectKey.show, I_SELECT }); + toggles_.push_back({ "Dpad", &g_Config.touchDpad.show, -1 }); + toggles_.push_back({ "Analog Stick", &g_Config.touchAnalogStick.show, -1 }); + toggles_.push_back({ "Unthrottle", &g_Config.touchUnthrottleKey.show, -1 }); + toggles_.push_back({ "Combo0", &g_Config.touchCombo0.show, I_1 }); + toggles_.push_back({ "Combo1", &g_Config.touchCombo1.show, I_2 }); + toggles_.push_back({ "Combo2", &g_Config.touchCombo2.show, I_3 }); + toggles_.push_back({ "Combo3", &g_Config.touchCombo3.show, I_4 }); + toggles_.push_back({ "Combo4", &g_Config.touchCombo4.show, I_5 }); + toggles_.push_back({ "Alt speed 1", &g_Config.touchSpeed1Key.show, -1 }); + toggles_.push_back({ "Alt speed 2", &g_Config.touchSpeed2Key.show, -1 }); I18NCategory *mc = GetI18NCategory("MappableControls"); - for (auto i = keyToggles.begin(); i != keyToggles.end(); ++i) { + for (auto toggle : toggles_) { LinearLayout *row = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); row->SetSpacing(0); - CheckBox *checkbox = new CheckBox(i->second, "", "", new LinearLayoutParams(50, WRAP_CONTENT)); + CheckBox *checkbox = new CheckBox(toggle.show, "", "", new LinearLayoutParams(50, WRAP_CONTENT)); row->Add(checkbox); - imageFinder = keyImages.find(i->first); Choice *choice; - - if (imageFinder != keyImages.end()) { - choice = new Choice(keyImages[imageFinder->first], new LinearLayoutParams(1.0f)); + if (toggle.img != -1) { + choice = new CheckBoxChoice(toggle.img, checkbox, new LinearLayoutParams(1.0f)); } else { - choice = new Choice(mc->T(i->first.c_str()), new LinearLayoutParams(1.0f)); + choice = new CheckBoxChoice(mc->T(toggle.key), checkbox, new LinearLayoutParams(1.0f)); } - ChoiceEventHandler *choiceEventHandler = new ChoiceEventHandler(checkbox); - choice->OnClick.Handle(choiceEventHandler, &ChoiceEventHandler::onChoiceClick); - choice->SetCentered(true); row->Add(choice); @@ -110,16 +117,15 @@ void TouchControlVisibilityScreen::onFinish(DialogResult result) { } UI::EventReturn TouchControlVisibilityScreen::OnToggleAll(UI::EventParams &e) { - for (auto i = keyToggles.begin(); i != keyToggles.end(); ++i) { - *i->second = toggleSwitch; + for (auto toggle : toggles_) { + *toggle.show = nextToggleAll_; } - - toggleSwitch = !toggleSwitch; + nextToggleAll_ = !nextToggleAll_; return UI::EVENT_DONE; } -UI::EventReturn TouchControlVisibilityScreen::ChoiceEventHandler::onChoiceClick(UI::EventParams &e){ +UI::EventReturn CheckBoxChoice::HandleClick(UI::EventParams &e) { checkbox_->Toggle(); return UI::EVENT_DONE; diff --git a/UI/TouchControlVisibilityScreen.h b/UI/TouchControlVisibilityScreen.h index d7f98885f0..88455a343e 100644 --- a/UI/TouchControlVisibilityScreen.h +++ b/UI/TouchControlVisibilityScreen.h @@ -19,16 +19,18 @@ #include "MiscScreens.h" -#include - namespace UI { class CheckBox; } +struct TouchButtonToggle { + const char *key; + bool *show; + int img; +}; + class TouchControlVisibilityScreen : public UIDialogScreenWithBackground { public: - TouchControlVisibilityScreen() : toggleSwitch(true){ } - void CreateViews() override; void onFinish(DialogResult result) override; @@ -36,14 +38,6 @@ protected: UI::EventReturn OnToggleAll(UI::EventParams &e); private: - std::map keyToggles; - bool toggleSwitch; - - class ChoiceEventHandler{ - public: - ChoiceEventHandler(UI::CheckBox *checkbox) : checkbox_(checkbox) {} - UI::EventReturn onChoiceClick(UI::EventParams &e); - private: - UI::CheckBox *checkbox_; - }; + std::vector toggles_; + bool nextToggleAll_ = true; }; diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj index 04eba6a66f..7cd70cfa70 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj +++ b/UWP/CoreUWP/CoreUWP.vcxproj @@ -463,6 +463,7 @@ + @@ -673,6 +674,7 @@ + diff --git a/UWP/NativeUWP/NativeUWP.vcxproj b/UWP/NativeUWP/NativeUWP.vcxproj index e6e8d6a89f..da5942bbfb 100644 --- a/UWP/NativeUWP/NativeUWP.vcxproj +++ b/UWP/NativeUWP/NativeUWP.vcxproj @@ -328,8 +328,6 @@ - - @@ -338,6 +336,8 @@ + + @@ -1160,8 +1160,6 @@ NotUsing NotUsing - - @@ -1169,6 +1167,8 @@ + + diff --git a/UWP/NativeUWP/NativeUWP.vcxproj.filters b/UWP/NativeUWP/NativeUWP.vcxproj.filters index 6b58b4d5c1..ce9603f332 100644 --- a/UWP/NativeUWP/NativeUWP.vcxproj.filters +++ b/UWP/NativeUWP/NativeUWP.vcxproj.filters @@ -37,9 +37,6 @@ {2be24387-0b6a-4253-97fa-b8b6f75a8a4c} - - {7fdd3320-a8e0-42ed-b08b-2d86ba2ff414} - {1a486fc4-bac0-4b33-9139-262272690c74} @@ -214,12 +211,6 @@ i18n - - ext\vjson - - - ext\vjson - ext\libzip @@ -656,12 +647,6 @@ i18n - - ext\vjson - - - ext\vjson - ext\libzip diff --git a/Windows/DSoundStream.cpp b/Windows/DSoundStream.cpp index 79d8d2699e..ca15e20856 100644 --- a/Windows/DSoundStream.cpp +++ b/Windows/DSoundStream.cpp @@ -12,6 +12,7 @@ #include "thread/threadutil.h" #include "Common/OSVersion.h" +#include "Core/ConfigValues.h" #include "Core/Reporting.h" #include "Core/Util/AudioFormat.h" #include "Windows/W32Util/Misc.h" diff --git a/Windows/DSoundStream.h b/Windows/DSoundStream.h index 95aee96326..fa101f4b71 100644 --- a/Windows/DSoundStream.h +++ b/Windows/DSoundStream.h @@ -2,6 +2,7 @@ #include "Common/CommonWindows.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" typedef int (*StreamCallback)(short *buffer, int numSamples, int bits, int rate, int channels); diff --git a/Windows/Debugger/CtrlDisAsmView.cpp b/Windows/Debugger/CtrlDisAsmView.cpp index fecc7de397..93d4ed6178 100644 --- a/Windows/Debugger/CtrlDisAsmView.cpp +++ b/Windows/Debugger/CtrlDisAsmView.cpp @@ -183,6 +183,7 @@ CtrlDisAsmView::~CtrlDisAsmView() { DeleteObject(font); DeleteObject(boldfont); + manager.clear(); } COLORREF scaleColor(COLORREF color, float factor) @@ -200,7 +201,6 @@ COLORREF scaleColor(COLORREF color, float factor) bool CtrlDisAsmView::getDisasmAddressText(u32 address, char* dest, bool abbreviateLabels, bool showData) { - auto memLock = Memory::Lock(); if (!PSP_IsInited()) return false; @@ -468,6 +468,7 @@ void CtrlDisAsmView::drawArguments(HDC hdc, const DisassemblyLineInfo &line, int void CtrlDisAsmView::onPaint(WPARAM wParam, LPARAM lParam) { + auto memLock = Memory::Lock(); if (!debugger->isAlive()) return; PAINTSTRUCT ps; @@ -1179,6 +1180,7 @@ void CtrlDisAsmView::calculatePixelPositions() void CtrlDisAsmView::search(bool continueSearch) { + auto memLock = Memory::Lock(); u32 searchAddress; if (continueSearch == false || searchQuery[0] == 0) @@ -1259,6 +1261,7 @@ void CtrlDisAsmView::search(bool continueSearch) std::string CtrlDisAsmView::disassembleRange(u32 start, u32 size) { + auto memLock = Memory::Lock(); std::string result; // gather all branch targets without labels diff --git a/Windows/Debugger/CtrlMemView.cpp b/Windows/Debugger/CtrlMemView.cpp index 4cede1270e..f1cc93e372 100644 --- a/Windows/Debugger/CtrlMemView.cpp +++ b/Windows/Debugger/CtrlMemView.cpp @@ -174,6 +174,8 @@ CtrlMemView *CtrlMemView::getFrom(HWND hwnd) void CtrlMemView::onPaint(WPARAM wParam, LPARAM lParam) { + auto memLock = Memory::Lock(); + // draw to a bitmap for double buffering PAINTSTRUCT ps; HDC actualHdc = BeginPaint(wnd, &ps); @@ -445,6 +447,7 @@ void CtrlMemView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) case ID_MEMVIEW_COPYVALUE_8: { + auto memLock = Memory::Lock(); char temp[24]; // it's admittedly not really useful like this @@ -462,6 +465,7 @@ void CtrlMemView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) case ID_MEMVIEW_COPYVALUE_16: { + auto memLock = Memory::Lock(); char temp[24]; sprintf(temp,"%04X",Memory::IsValidAddress(curAddress) ? Memory::Read_U16(curAddress) : 0xFFFF); @@ -471,6 +475,7 @@ void CtrlMemView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) case ID_MEMVIEW_COPYVALUE_32: { + auto memLock = Memory::Lock(); char temp[24]; sprintf(temp,"%08X",Memory::IsValidAddress(curAddress) ? Memory::Read_U32(curAddress) : 0xFFFFFFFF); diff --git a/Windows/Debugger/Debugger_Disasm.cpp b/Windows/Debugger/Debugger_Disasm.cpp index 5d187b915f..4a2e54ee9d 100644 --- a/Windows/Debugger/Debugger_Disasm.cpp +++ b/Windows/Debugger/Debugger_Disasm.cpp @@ -393,10 +393,9 @@ BOOL CDisasm::DlgProc(UINT message, WPARAM wParam, LPARAM lParam) { CtrlDisAsmView *ptr = CtrlDisAsmView::getFrom(GetDlgItem(m_hDlg,IDC_DISASMVIEW)); CtrlRegisterList *reglist = CtrlRegisterList::getFrom(GetDlgItem(m_hDlg,IDC_REGLIST)); - switch(LOWORD(wParam)) - { - case ID_TOGGLE_PAUSE: - SendMessage(MainWindow::GetHWND(),WM_COMMAND,ID_TOGGLE_PAUSE,0); + switch (LOWORD(wParam)) { + case ID_TOGGLE_BREAK: + SendMessage(MainWindow::GetHWND(), WM_COMMAND, ID_TOGGLE_BREAK, 0); break; case ID_DEBUG_DISPLAYMEMVIEW: @@ -809,7 +808,6 @@ void CDisasm::SetDebugMode(bool _bDebug, bool switchPC) if (_bDebug && GetUIState() == UISTATE_INGAME && PSP_IsInited()) { Core_WaitInactive(TEMP_BREAKPOINT_WAIT_MS); - CBreakPoints::ClearTemporaryBreakPoints(); breakpointList->reloadBreakpoints(); threadList->reloadThreads(); stackTraceView->loadStackTrace(); @@ -843,7 +841,7 @@ void CDisasm::SetDebugMode(bool _bDebug, bool switchPC) if (GetUIState() == UISTATE_INGAME && PSP_IsInited()) { - SetDlgItemText(m_hDlg, IDC_STOPGO, L"Stop"); + SetDlgItemText(m_hDlg, IDC_STOPGO, L"Break"); EnableWindow(GetDlgItem(hDlg, IDC_STOPGO), TRUE); } else @@ -913,4 +911,4 @@ void CDisasm::UpdateDialog(bool _bComplete) // redraw. all others are updated manually InvalidateRect (GetDlgItem(m_hDlg, IDC_DEBUGMEMVIEW), NULL, TRUE); UpdateWindow (GetDlgItem(m_hDlg, IDC_DEBUGMEMVIEW)); -} \ No newline at end of file +} diff --git a/Windows/EmuThread.cpp b/Windows/EmuThread.cpp index 34cf5846e8..987bb8b5e4 100644 --- a/Windows/EmuThread.cpp +++ b/Windows/EmuThread.cpp @@ -22,6 +22,7 @@ #include "Core/Host.h" #include "Core/System.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "thread/threadutil.h" enum class EmuThreadState { diff --git a/Windows/GEDebugger/GEDebugger.cpp b/Windows/GEDebugger/GEDebugger.cpp index 3c6f4af9d0..c0454d30e1 100644 --- a/Windows/GEDebugger/GEDebugger.cpp +++ b/Windows/GEDebugger/GEDebugger.cpp @@ -67,7 +67,7 @@ void CGEDebugger::Init() { CGEDebugger::CGEDebugger(HINSTANCE _hInstance, HWND _hParent) : Dialog((LPCSTR)IDD_GEDEBUGGER, _hInstance, _hParent) { GPUBreakpoints::Init(); - Core_ListenShutdown(ForceUnpause); + Core_ListenStopRequest(ForceUnpause); // minimum size = a little more than the default RECT windowRect; diff --git a/Windows/GEDebugger/SimpleGLWindow.cpp b/Windows/GEDebugger/SimpleGLWindow.cpp index f9c06b5c02..c24613711d 100644 --- a/Windows/GEDebugger/SimpleGLWindow.cpp +++ b/Windows/GEDebugger/SimpleGLWindow.cpp @@ -55,9 +55,7 @@ static const char tex_fs[] = "}\n"; static const char basic_vs[] = -#ifndef USING_GLES2 "#version 120\n" -#endif "attribute vec4 a_position;\n" "attribute vec2 a_texcoord0;\n" "uniform mat4 u_viewproj;\n" diff --git a/Windows/GEDebugger/TabState.cpp b/Windows/GEDebugger/TabState.cpp index 20ea2cdc3a..494cf3e999 100644 --- a/Windows/GEDebugger/TabState.cpp +++ b/Windows/GEDebugger/TabState.cpp @@ -880,7 +880,7 @@ void CtrlStateValues::OnRightClick(int row, int column, const POINT &point) { SetMenuDefaultItem(subMenu, ID_REGLIST_CHANGE, FALSE); // Ehh, kinda ugly. - if (rows_ == &watchList[0]) { + if (!watchList.empty() && rows_ == &watchList[0]) { ModifyMenu(subMenu, ID_GEDBG_WATCH, MF_BYCOMMAND | MF_STRING, ID_GEDBG_WATCH, L"Remove Watch"); } else { ModifyMenu(subMenu, ID_GEDBG_WATCH, MF_BYCOMMAND | MF_STRING, ID_GEDBG_WATCH, L"Add Watch"); diff --git a/Windows/GEDebugger/TabVertices.cpp b/Windows/GEDebugger/TabVertices.cpp index f3888669d6..e878105fdd 100644 --- a/Windows/GEDebugger/TabVertices.cpp +++ b/Windows/GEDebugger/TabVertices.cpp @@ -281,6 +281,10 @@ int CtrlVertexList::GetRowCount() { u32 cmd = Memory::Read_U32(list.pc); if ((cmd >> 24) == GE_CMD_PRIM) { rowCount_ = cmd & 0xFFFF; + } else if ((cmd >> 24) == GE_CMD_BEZIER || (cmd >> 24) == GE_CMD_SPLINE) { + u32 u = (cmd & 0x00FF) >> 0; + u32 v = (cmd & 0xFF00) >> 8; + rowCount_ = u * v; } } diff --git a/Windows/GEDebugger/VertexPreview.cpp b/Windows/GEDebugger/VertexPreview.cpp index 1f78554d91..42e006efd0 100644 --- a/Windows/GEDebugger/VertexPreview.cpp +++ b/Windows/GEDebugger/VertexPreview.cpp @@ -24,6 +24,7 @@ #include "Core/Config.h" #include "GPU/GPUInterface.h" #include "GPU/Common/GPUDebugInterface.h" +#include "GPU/Common/SplineCommon.h" #include "GPU/GPUState.h" static const char preview_fs[] = @@ -35,9 +36,7 @@ static const char preview_fs[] = "}\n"; static const char preview_vs[] = -#ifndef USING_GLES2 "#version 120\n" -#endif "attribute vec4 a_position;\n" "uniform mat4 u_viewproj;\n" "void main() {\n" @@ -153,11 +152,10 @@ static void ExpandRectangles(std::vector &vertices, std::vector< u32 CGEDebugger::PrimPreviewOp() { DisplayList list; - if (gpuDebug != nullptr && gpuDebug->GetCurrentDisplayList(list)) { + if (gpuDebug != nullptr && gpuDebug->GetCurrentDisplayList(list) && !showClut_) { const u32 op = Memory::Read_U32(list.pc); const u32 cmd = op >> 24; - // TODO: Bezier/spline? - if (cmd == GE_CMD_PRIM && !showClut_) { + if (cmd == GE_CMD_PRIM || cmd == GE_CMD_BEZIER || cmd == GE_CMD_SPLINE) { return op; } } @@ -165,9 +163,118 @@ u32 CGEDebugger::PrimPreviewOp() { return 0; } +static void ExpandBezier(int &count, int op, const std::vector &simpleVerts, const std::vector &indices, std::vector &generatedVerts, std::vector &generatedInds) { + int count_u = (op & 0x00FF) >> 0; + int count_v = (op & 0xFF00) >> 8; + + int tess_u = gstate.getPatchDivisionU(); + int tess_v = gstate.getPatchDivisionV(); + if (tess_u < 1) { + tess_u = 1; + } + if (tess_v < 1) { + tess_v = 1; + } + + // Bezier patches share less control points than spline patches. Otherwise they are pretty much the same (except bezier don't support the open/close thing) + int num_patches_u = (count_u - 1) / 3; + int num_patches_v = (count_v - 1) / 3; + int total_patches = num_patches_u * num_patches_v; + std::vector patches; + patches.resize(total_patches); + for (int patch_u = 0; patch_u < num_patches_u; patch_u++) { + for (int patch_v = 0; patch_v < num_patches_v; patch_v++) { + BezierPatch &patch = patches[patch_u + patch_v * num_patches_u]; + for (int point = 0; point < 16; ++point) { + int idx = (patch_u * 3 + point % 4) + (patch_v * 3 + point / 4) * count_u; + patch.points[point] = &simpleVerts[0] + (!indices.empty() ? indices[idx] : idx); + } + patch.u_index = patch_u * 3; + patch.v_index = patch_v * 3; + patch.index = patch_v * num_patches_u + patch_u; + patch.primType = gstate.getPatchPrimitiveType(); + patch.computeNormals = false; + patch.patchFacing = false; + } + } + + generatedVerts.resize((tess_u + 1) * (tess_v + 1) * total_patches); + generatedInds.resize(tess_u * tess_v * 6 * total_patches); + + count = 0; + u8 *dest = (u8 *)&generatedVerts[0]; + u16 *inds = &generatedInds[0]; + for (int patch_idx = 0; patch_idx < total_patches; ++patch_idx) { + const BezierPatch &patch = patches[patch_idx]; + TessellateBezierPatch(dest, inds, count, tess_u, tess_v, patch, gstate.vertType); + } +} + +static void ExpandSpline(int &count, int op, const std::vector &simpleVerts, const std::vector &indices, std::vector &generatedVerts, std::vector &generatedInds) { + SplinePatchLocal patch; + patch.computeNormals = false; + patch.primType = gstate.getPatchPrimitiveType(); + patch.patchFacing = false; + + patch.count_u = (op & 0x00FF) >> 0; + patch.count_v = (op & 0xFF00) >> 8; + patch.type_u = (op >> 16) & 0x3; + patch.type_v = (op >> 18) & 0x3; + + patch.tess_u = gstate.getPatchDivisionU(); + patch.tess_v = gstate.getPatchDivisionV(); + if (patch.tess_u < 1) { + patch.tess_u = 1; + } + if (patch.tess_v < 1) { + patch.tess_v = 1; + } + + // Real hardware seems to draw nothing when given < 4 either U or V. + if (patch.count_u < 4 || patch.count_v < 4) { + return; + } + + std::vector points; + points.resize(patch.count_u * patch.count_v); + + // Make an array of pointers to the control points, to get rid of indices. + for (int idx = 0; idx < patch.count_u * patch.count_v; idx++) { + points[idx] = &simpleVerts[0] + (!indices.empty() ? indices[idx] : idx); + } + patch.points = &points[0]; + + int patch_div_s = (patch.count_u - 3) * patch.tess_u; + int patch_div_t = (patch.count_v - 3) * patch.tess_v; + int maxVertexCount = (patch_div_s + 1) * (patch_div_t + 1); + + generatedVerts.resize(maxVertexCount); + generatedInds.resize(patch_div_s * patch_div_t * 6); + + count = 0; + u8 *dest = (u8 *)&generatedVerts[0]; + TessellateSplinePatch(dest, &generatedInds[0], count, patch, gstate.vertType, maxVertexCount); +} + void CGEDebugger::UpdatePrimPreview(u32 op, int which) { - const u32 prim_type = (op >> 16) & 0x7; - int count = op & 0xFFFF; + u32 prim_type = GE_PRIM_INVALID; + int count = 0; + int count_u = 0; + int count_v = 0; + + const u32 cmd = op >> 24; + if (cmd == GE_CMD_PRIM) { + prim_type = (op >> 16) & 0x7; + count = op & 0xFFFF; + } else { + const GEPrimitiveType primLookup[] = { GE_PRIM_TRIANGLES, GE_PRIM_LINES, GE_PRIM_POINTS, GE_PRIM_POINTS }; + if (gstate.getPatchPrimitiveType() < ARRAY_SIZE(primLookup)) + prim_type = primLookup[gstate.getPatchPrimitiveType()]; + count_u = (op & 0x00FF) >> 0; + count_v = (op & 0xFF00) >> 8; + count = count_u * count_v; + } + if (prim_type >= 7) { ERROR_LOG(G3D, "Unsupported prim type: %x", op); return; @@ -190,6 +297,36 @@ void CGEDebugger::UpdatePrimPreview(u32 op, int which) { return; } + if (cmd != GE_CMD_PRIM) { + static std::vector generatedVerts; + static std::vector generatedInds; + + static std::vector simpleVerts; + simpleVerts.resize(vertices.size()); + for (size_t i = 0; i < vertices.size(); ++i) { + // For now, let's just copy back so we can use TessellateBezierPatch/TessellateSplinePatch... + simpleVerts[i].uv[0] = vertices[i].u; + simpleVerts[i].uv[1] = vertices[i].v; + simpleVerts[i].pos = Vec3Packedf(vertices[i].x, vertices[i].y, vertices[i].z); + } + + if (cmd == GE_CMD_BEZIER) { + ExpandBezier(count, op, simpleVerts, indices, generatedVerts, generatedInds); + } else if (cmd == GE_CMD_SPLINE) { + ExpandSpline(count, op, simpleVerts, indices, generatedVerts, generatedInds); + } + + vertices.resize(generatedVerts.size()); + for (size_t i = 0; i < vertices.size(); ++i) { + vertices[i].u = generatedVerts[i].uv[0]; + vertices[i].v = generatedVerts[i].uv[1]; + vertices[i].x = generatedVerts[i].pos.x; + vertices[i].y = generatedVerts[i].pos.y; + vertices[i].z = generatedVerts[i].pos.z; + } + indices = generatedInds; + } + if (prim == GE_PRIM_RECTANGLES) { ExpandRectangles(vertices, indices, count, gpuDebug->GetGState().isModeThrough()); } diff --git a/Windows/GPU/D3D11Context.cpp b/Windows/GPU/D3D11Context.cpp index 28b1540af3..806458b1e8 100644 --- a/Windows/GPU/D3D11Context.cpp +++ b/Windows/GPU/D3D11Context.cpp @@ -2,6 +2,7 @@ #include "Common/CommonWindows.h" #include +#include #include #include "base/logging.h" @@ -9,6 +10,7 @@ #include "i18n/i18n.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/Reporting.h" #include "Core/System.h" #include "Windows/GPU/D3D11Context.h" @@ -22,6 +24,12 @@ #define __uuidof(type) IID_##type #endif +#ifndef DXGI_ERROR_NOT_FOUND +#define _FACDXGI 0x87a +#define MAKE_DXGI_HRESULT(code) MAKE_HRESULT(1, _FACDXGI, code) +#define DXGI_ERROR_NOT_FOUND MAKE_DXGI_HRESULT(2) +#endif + #if PPSSPP_PLATFORM(UWP) #error This file should not be compiled for UWP. #endif @@ -41,7 +49,7 @@ void D3D11Context::SwapInterval(int interval) { // Dummy } -HRESULT D3D11Context::CreateTheDevice() { +HRESULT D3D11Context::CreateTheDevice(IDXGIAdapter *adapter) { bool windowed = true; #ifdef _DEBUG UINT createDeviceFlags = D3D11_CREATE_DEVICE_DEBUG; @@ -70,19 +78,13 @@ HRESULT D3D11Context::CreateTheDevice() { const UINT numFeatureLevels = ARRAYSIZE(featureLevels); HRESULT hr = S_OK; - // Temporarily commenting out until we can dynamically load D3D11CreateDevice. - for (UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++) { - driverType_ = driverTypes[driverTypeIndex]; - hr = ptr_D3D11CreateDevice(nullptr, driverType_, nullptr, createDeviceFlags, (D3D_FEATURE_LEVEL *)featureLevels, numFeatureLevels, + D3D_DRIVER_TYPE driverType = D3D_DRIVER_TYPE_UNKNOWN; + hr = ptr_D3D11CreateDevice(adapter, driverType, nullptr, createDeviceFlags, (D3D_FEATURE_LEVEL *)featureLevels, numFeatureLevels, + D3D11_SDK_VERSION, &device_, &featureLevel_, &context_); + if (hr == E_INVALIDARG) { + // DirectX 11.0 platforms will not recognize D3D_FEATURE_LEVEL_11_1 so we need to retry without it + hr = ptr_D3D11CreateDevice(adapter, driverType, nullptr, createDeviceFlags, (D3D_FEATURE_LEVEL *)&featureLevels[3], numFeatureLevels - 3, D3D11_SDK_VERSION, &device_, &featureLevel_, &context_); - - if (hr == E_INVALIDARG) { - // DirectX 11.0 platforms will not recognize D3D_FEATURE_LEVEL_11_1 so we need to retry without it - hr = ptr_D3D11CreateDevice(nullptr, driverType_, nullptr, createDeviceFlags, (D3D_FEATURE_LEVEL *)&featureLevels[3], numFeatureLevels - 3, - D3D11_SDK_VERSION, &device_, &featureLevel_, &context_); - } - if (SUCCEEDED(hr)) - break; } return hr; } @@ -99,8 +101,30 @@ bool D3D11Context::Init(HINSTANCE hInst, HWND wnd, std::string *error_message) { LoadD3D11Error result = LoadD3D11(); HRESULT hr = E_FAIL; + std::vector adapterNames; if (result == LoadD3D11Error::SUCCESS) { - hr = CreateTheDevice(); + std::vector adapters; + int chosenAdapter = 0; + + IDXGIFactory * pFactory = nullptr; + ptr_CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&pFactory); + + IDXGIAdapter *pAdapter; + for (UINT i = 0; pFactory->EnumAdapters(i, &pAdapter) != DXGI_ERROR_NOT_FOUND; i++) { + adapters.push_back(pAdapter); + DXGI_ADAPTER_DESC desc; + pAdapter->GetDesc(&desc); + std::string str = ConvertWStringToUTF8(desc.Description); + adapterNames.push_back(str); + if (str == g_Config.sD3D11Device) { + chosenAdapter = i; + } + } + + hr = CreateTheDevice(adapters[chosenAdapter]); + for (int i = 0; i < (int)adapters.size(); i++) { + adapters[i]->Release(); + } } if (FAILED(hr)) { @@ -146,7 +170,7 @@ bool D3D11Context::Init(HINSTANCE hInst, HWND wnd, std::string *error_message) { } #endif - draw_ = Draw::T3DCreateD3D11Context(device_, context_, device1_, context1_, featureLevel_, hWnd_); + draw_ = Draw::T3DCreateD3D11Context(device_, context_, device1_, context1_, featureLevel_, hWnd_, adapterNames); SetGPUBackend(GPUBackend::DIRECT3D11); bool success = draw_->CreatePresets(); // If we can run D3D11, there's a compiler installed. I think. assert(success); diff --git a/Windows/GPU/D3D11Context.h b/Windows/GPU/D3D11Context.h index b0f4247628..88f1547295 100644 --- a/Windows/GPU/D3D11Context.h +++ b/Windows/GPU/D3D11Context.h @@ -40,7 +40,7 @@ public: Draw::DrawContext *GetDrawContext() override { return draw_; } private: - HRESULT CreateTheDevice(); + HRESULT CreateTheDevice(IDXGIAdapter *adapter); void LostBackbuffer(); void GotBackbuffer(); @@ -60,7 +60,6 @@ private: ID3D11InfoQueue *d3dInfoQueue_ = nullptr; #endif - D3D_DRIVER_TYPE driverType_; D3D_FEATURE_LEVEL featureLevel_ = D3D_FEATURE_LEVEL_11_0; int adapterId; HDC hDC; // Private GDI Device Context @@ -68,4 +67,4 @@ private: HMODULE hD3D11; int width; int height; -}; \ No newline at end of file +}; diff --git a/Windows/GPU/D3D9Context.cpp b/Windows/GPU/D3D9Context.cpp index ad71c0a48d..80933615b2 100644 --- a/Windows/GPU/D3D9Context.cpp +++ b/Windows/GPU/D3D9Context.cpp @@ -8,6 +8,7 @@ #include "i18n/i18n.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/Reporting.h" #include "Core/System.h" #include "Common/OSVersion.h" diff --git a/Windows/GPU/WindowsGLContext.cpp b/Windows/GPU/WindowsGLContext.cpp index 43eeaca913..f3808583b0 100644 --- a/Windows/GPU/WindowsGLContext.cpp +++ b/Windows/GPU/WindowsGLContext.cpp @@ -26,6 +26,7 @@ #include "GL/gl.h" #include "GL/wglew.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/Core.h" #include "util/text/utf8.h" #include "i18n/i18n.h" @@ -249,7 +250,7 @@ bool WindowsGLContext::InitFromRenderThread(std::string *error_message) { ExitProcess(1); } const char *defaultError = "Insufficient OpenGL driver support detected!\n\n" - "Your GPU reports that it does not support OpenGL 2.0. Would you like to try using DirectX 9 instead?\n\n" + "Your GPU reports that it does not support OpenGL 2.0. Would you like to try using DirectX instead?\n\n" "DirectX is currently compatible with less games, but on your GPU it may be the only choice.\n\n" "Visit the forums at http://forums.ppsspp.org for more information.\n\n"; @@ -262,7 +263,10 @@ bool WindowsGLContext::InitFromRenderThread(std::string *error_message) { if (yes) { // Change the config to D3D and restart. - g_Config.iGPUBackend = (int)GPUBackend::DIRECT3D9; + const char *d3d9Or11 = "Direct3D 9? (Or no for Direct3D 11)"; + std::wstring whichD3D9 = ConvertUTF8ToWString(err->T("D3D9or11", d3d9Or11)); + bool d3d9 = IDYES == MessageBox(hWnd_, whichD3D9.c_str(), title.c_str(), MB_YESNO); + g_Config.iGPUBackend = d3d9 ? (int)GPUBackend::DIRECT3D9 : (int)GPUBackend::DIRECT3D11; g_Config.Save(); W32Util::ExitAndRestart(); diff --git a/Windows/GPU/WindowsVulkanContext.cpp b/Windows/GPU/WindowsVulkanContext.cpp index 0e71ace543..ad619ab339 100644 --- a/Windows/GPU/WindowsVulkanContext.cpp +++ b/Windows/GPU/WindowsVulkanContext.cpp @@ -51,6 +51,7 @@ #include #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/System.h" #include "Common/Vulkan/VulkanLoader.h" #include "Common/Vulkan/VulkanContext.h" @@ -109,7 +110,12 @@ bool WindowsVulkanContext::Init(HINSTANCE hInst, HWND hWnd, std::string *error_m g_Vulkan = nullptr; return false; } - g_Vulkan->ChooseDevice(g_Vulkan->GetBestPhysicalDevice()); + int deviceNum = g_Vulkan->GetPhysicalDeviceByName(g_Config.sVulkanDevice); + if (deviceNum < 0) { + deviceNum = g_Vulkan->GetBestPhysicalDevice(); + g_Config.sVulkanDevice = g_Vulkan->GetPhysicalDeviceProperties(deviceNum).deviceName; + } + g_Vulkan->ChooseDevice(deviceNum); if (g_Vulkan->EnableDeviceExtension(VK_NV_DEDICATED_ALLOCATION_EXTENSION_NAME)) { supportsDedicatedAlloc_ = true; } diff --git a/Windows/MainWindow.cpp b/Windows/MainWindow.cpp index 67a820042e..83b5286771 100644 --- a/Windows/MainWindow.cpp +++ b/Windows/MainWindow.cpp @@ -42,6 +42,7 @@ #include "Core/Core.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/Debugger/SymbolMap.h" #include "Core/MIPS/JitCommon/JitCommon.h" #include "Core/MIPS/JitCommon/JitBlockCache.h" diff --git a/Windows/MainWindowMenu.cpp b/Windows/MainWindowMenu.cpp index 6a9fdf63ed..fc4c9ef3ae 100644 --- a/Windows/MainWindowMenu.cpp +++ b/Windows/MainWindowMenu.cpp @@ -20,6 +20,7 @@ #include "GPU/Common/PostShader.h" #include "GPU/GLES/FramebufferManagerGLES.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/FileSystems/MetaFileSystem.h" #include "UI/OnScreenDisplay.h" #include "Windows/MainWindowMenu.h" @@ -60,10 +61,11 @@ namespace MainWindow { EnableMenuItem(menu, ID_FILE_LOADSTATEFILE, menuEnable); EnableMenuItem(menu, ID_FILE_QUICKSAVESTATE, menuEnable); EnableMenuItem(menu, ID_FILE_QUICKLOADSTATE, menuEnable); - EnableMenuItem(menu, ID_TOGGLE_PAUSE, menuEnable); + EnableMenuItem(menu, ID_EMULATION_PAUSE, menuEnable); EnableMenuItem(menu, ID_EMULATION_STOP, menuEnable); EnableMenuItem(menu, ID_EMULATION_RESET, menuEnable); EnableMenuItem(menu, ID_EMULATION_SWITCH_UMD, umdSwitchEnable); + EnableMenuItem(menu, ID_TOGGLE_BREAK, menuEnable); EnableMenuItem(menu, ID_DEBUG_LOADMAPFILE, menuEnable); EnableMenuItem(menu, ID_DEBUG_SAVEMAPFILE, menuEnable); EnableMenuItem(menu, ID_DEBUG_LOADSYMFILE, menuEnable); @@ -289,7 +291,7 @@ namespace MainWindow { TranslateMenuItem(menu, ID_FILE_EXIT, L"\tAlt+F4"); // Emulation menu - TranslateMenuItem(menu, ID_TOGGLE_PAUSE, L"\tF8", "Pause"); + TranslateMenuItem(menu, ID_EMULATION_PAUSE); TranslateMenuItem(menu, ID_EMULATION_STOP, L"\tCtrl+W"); TranslateMenuItem(menu, ID_EMULATION_RESET, L"\tCtrl+B"); TranslateMenuItem(menu, ID_EMULATION_SWITCH_UMD, L"\tCtrl+U", "Switch UMD"); @@ -300,16 +302,17 @@ namespace MainWindow { TranslateMenuItem(menu, ID_EMULATION_ROTATION_V_R); // Debug menu + TranslateMenuItem(menu, ID_TOGGLE_BREAK, L"\tF8", "Break"); + TranslateMenuItem(menu, ID_DEBUG_BREAKONLOAD); + TranslateMenuItem(menu, ID_DEBUG_IGNOREILLEGALREADS); TranslateMenuItem(menu, ID_DEBUG_LOADMAPFILE); TranslateMenuItem(menu, ID_DEBUG_SAVEMAPFILE); TranslateMenuItem(menu, ID_DEBUG_LOADSYMFILE); TranslateMenuItem(menu, ID_DEBUG_SAVESYMFILE); TranslateMenuItem(menu, ID_DEBUG_RESETSYMBOLTABLE); - TranslateMenuItem(menu, ID_DEBUG_DUMPNEXTFRAME); TranslateMenuItem(menu, ID_DEBUG_TAKESCREENSHOT, L"\tF12"); + TranslateMenuItem(menu, ID_DEBUG_DUMPNEXTFRAME); TranslateMenuItem(menu, ID_DEBUG_SHOWDEBUGSTATISTICS); - TranslateMenuItem(menu, ID_DEBUG_IGNOREILLEGALREADS); - TranslateMenuItem(menu, ID_DEBUG_RUNONLOAD); TranslateMenuItem(menu, ID_DEBUG_DISASSEMBLY, L"\tCtrl+D"); TranslateMenuItem(menu, ID_DEBUG_GEDEBUGGER, L"\tCtrl+G"); TranslateMenuItem(menu, ID_DEBUG_EXTRACTFILE); @@ -496,9 +499,9 @@ namespace MainWindow { g_Config.iInternalScreenRotation = rotation; } - static void SaveStateActionFinished(bool result, const std::string &message, void *userdata) { + static void SaveStateActionFinished(SaveState::Status status, const std::string &message, void *userdata) { if (!message.empty()) { - osm.Show(message, 2.0); + osm.Show(message, status == SaveState::Status::SUCCESS ? 2.0 : 5.0); } PostMessage(MainWindow::GetHWND(), WM_USER_SAVESTATE_FINISH, 0, 0); } @@ -564,10 +567,6 @@ namespace MainWindow { NativeMessageReceived("gpu_resized", ""); } - static void setFpsLimit(int fps) { - g_Config.iFpsLimit = fps; - } - static void setFrameSkipping(int framesToSkip = -1) { if (framesToSkip >= FRAMESKIP_OFF) g_Config.iFrameSkip = framesToSkip; @@ -632,7 +631,7 @@ namespace MainWindow { ShellExecute(NULL, L"open", ConvertUTF8ToWString(g_Config.memStickDirectory).c_str(), 0, 0, SW_SHOW); break; - case ID_TOGGLE_PAUSE: + case ID_TOGGLE_BREAK: if (GetUIState() == UISTATE_PAUSEMENU) { // Causes hang //NativeMessageReceived("run", ""); @@ -653,6 +652,11 @@ namespace MainWindow { noFocusPause = !noFocusPause; // If we pause, override pause on lost focus break; + case ID_EMULATION_PAUSE: + NativeMessageReceived("pause", ""); + Core_EnableStepping(false); + break; + case ID_EMULATION_STOP: if (Core_IsStepping()) Core_EnableStepping(false); @@ -886,7 +890,7 @@ namespace MainWindow { PostMessage(hWnd, WM_CLOSE, 0, 0); break; - case ID_DEBUG_RUNONLOAD: + case ID_DEBUG_BREAKONLOAD: g_Config.bAutoRun = !g_Config.bAutoRun; break; @@ -1129,11 +1133,11 @@ namespace MainWindow { CHECKITEM(ID_DEBUG_IGNOREILLEGALREADS, g_Config.bIgnoreBadMemAccess); CHECKITEM(ID_DEBUG_SHOWDEBUGSTATISTICS, g_Config.bShowDebugStats); CHECKITEM(ID_OPTIONS_HARDWARETRANSFORM, g_Config.bHardwareTransform); - CHECKITEM(ID_DEBUG_RUNONLOAD, g_Config.bAutoRun); + CHECKITEM(ID_DEBUG_BREAKONLOAD, !g_Config.bAutoRun); CHECKITEM(ID_OPTIONS_VERTEXCACHE, g_Config.bVertexCache); CHECKITEM(ID_OPTIONS_SHOWFPS, g_Config.iShowFPSCounter); CHECKITEM(ID_OPTIONS_FRAMESKIP_AUTO, g_Config.bAutoFrameSkip); - CHECKITEM(ID_OPTIONS_FRAMESKIP, g_Config.iFrameSkip != 0); + CHECKITEM(ID_OPTIONS_FRAMESKIP, g_Config.iFrameSkip != FRAMESKIP_OFF); CHECKITEM(ID_OPTIONS_VSYNC, g_Config.bVSync); CHECKITEM(ID_OPTIONS_TOPMOST, g_Config.bTopMost); CHECKITEM(ID_OPTIONS_PAUSE_FOCUS, g_Config.bPauseOnLostFocus); @@ -1404,7 +1408,7 @@ namespace MainWindow { lastGlobalUIState = GetUIState(); bool isPaused = Core_IsStepping() && GetUIState() == UISTATE_INGAME; - TranslateMenuItem(menu, ID_TOGGLE_PAUSE, L"\tF8", isPaused ? "Run" : "Pause"); + TranslateMenuItem(menu, ID_TOGGLE_BREAK, L"\tF8", isPaused ? "Run" : "Break"); } // Message handler for about box. diff --git a/Windows/PPSSPP.vcxproj b/Windows/PPSSPP.vcxproj index 9e95c6a1b8..7fa9fc4525 100644 --- a/Windows/PPSSPP.vcxproj +++ b/Windows/PPSSPP.vcxproj @@ -1,4 +1,4 @@ - + @@ -341,60 +341,6 @@ - - true - true - true - true - - - true - true - true - true - - - true - true - true - true - - - true - true - true - true - - - true - true - true - true - - - true - true - true - true - - - true - true - true - true - - - true - true - true - true - - - true - true - true - true - true true @@ -494,15 +440,6 @@ - - - - - - - - - @@ -562,11 +499,6 @@ - - - - - @@ -627,4 +559,4 @@ - \ No newline at end of file + diff --git a/Windows/PPSSPP.vcxproj.filters b/Windows/PPSSPP.vcxproj.filters index 8cce6b63ec..8cfb5cdbb3 100644 --- a/Windows/PPSSPP.vcxproj.filters +++ b/Windows/PPSSPP.vcxproj.filters @@ -1,4 +1,4 @@ - + @@ -161,33 +161,6 @@ Other Platforms\Qt - - Other Platforms\Qt\Debugger - - - Other Platforms\Qt\Debugger - - - Other Platforms\Qt\Debugger - - - Other Platforms\Qt\Debugger - - - Other Platforms\Qt\Debugger - - - Other Platforms\Qt\Debugger - - - Other Platforms\Qt\Debugger - - - Other Platforms\Qt\Debugger - - - Other Platforms\Qt\Debugger - Windows\System @@ -341,30 +314,6 @@ Other Platforms\Qt\Debugger - - Other Platforms\Qt\Debugger - - - Other Platforms\Qt\Debugger - - - Other Platforms\Qt\Debugger - - - Other Platforms\Qt\Debugger - - - Other Platforms\Qt\Debugger - - - Other Platforms\Qt\Debugger - - - Other Platforms\Qt\Debugger - - - Other Platforms\Qt\Debugger - Windows\System @@ -468,21 +417,6 @@ Other Platforms\Qt - - Other Platforms\Qt\Debugger - - - Other Platforms\Qt\Debugger - - - Other Platforms\Qt\Debugger - - - Other Platforms\Qt\Debugger - - - Other Platforms\Qt\Debugger - @@ -494,4 +428,4 @@ Resource Files - \ No newline at end of file + diff --git a/Windows/WindowsHost.cpp b/Windows/WindowsHost.cpp index ce306cbfe0..5372d655a1 100644 --- a/Windows/WindowsHost.cpp +++ b/Windows/WindowsHost.cpp @@ -42,6 +42,7 @@ #include "Common/StringUtils.h" #include "Core/Core.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/CoreParameter.h" #include "Core/System.h" #include "Core/Debugger/SymbolMap.h" @@ -244,7 +245,6 @@ void WindowsHost::BootDone() { PostMessage(mainWindow_, WM_USER + 1, 0, 0); SetDebugMode(!g_Config.bAutoRun); - Core_EnableStepping(!g_Config.bAutoRun); } static std::string SymbolMapFilename(const char *currentFilename, const char* ext) { diff --git a/Windows/main.cpp b/Windows/main.cpp index cf04660d4e..01c0fe6664 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -36,6 +36,7 @@ #include "net/resolve.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/SaveState.h" #include "Windows/EmuThread.h" #include "Windows/DSoundStream.h" @@ -73,6 +74,11 @@ extern "C" { __declspec(dllexport) DWORD NvOptimusEnablement = 1; } +// Also on AMD PowerExpress: https://community.amd.com/thread/169965 +extern "C" { + __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; +} + CDisasm *disasmWindow[MAX_CPUCOUNT] = {0}; CGEDebugger *geDebuggerWindow = 0; CMemoryDlg *memoryWindow[MAX_CPUCOUNT] = {0}; @@ -453,9 +459,6 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin break; } - if (wideArgs[i] == L"--fullscreen") - g_Config.bFullScreen = true; - if (wideArgs[i] == L"--windowed") g_Config.bFullScreen = false; diff --git a/Windows/ppsspp.rc b/Windows/ppsspp.rc index 13ef496a9b..ce1d218407 100644 --- a/Windows/ppsspp.rc +++ b/Windows/ppsspp.rc @@ -80,7 +80,7 @@ BEGIN "1", ID_OPTIONS_RESOLUTIONDUMMY, VIRTKEY, CONTROL, NOINVERT VK_F7, ID_OPTIONS_FRAMESKIPDUMMY, VIRTKEY, NOINVERT VK_F12, ID_DEBUG_TAKESCREENSHOT, VIRTKEY, NOINVERT - VK_F8, ID_TOGGLE_PAUSE, VIRTKEY, NOINVERT + VK_F8, ID_TOGGLE_BREAK, VIRTKEY, NOINVERT VK_RETURN, ID_OPTIONS_FULLSCREEN, ALT, VIRTKEY, NOINVERT VK_F11, ID_OPTIONS_FULLSCREEN, VIRTKEY, NOINVERT END @@ -95,7 +95,7 @@ BEGIN "2", ID_DEBUG_DISPLAYBREAKPOINTLIST, VIRTKEY, CONTROL, NOINVERT "3", ID_DEBUG_DISPLAYTHREADLIST, VIRTKEY, CONTROL, NOINVERT "4", ID_DEBUG_DISPLAYSTACKFRAMELIST, VIRTKEY, CONTROL, NOINVERT - VK_F7, ID_TOGGLE_PAUSE, VIRTKEY, NOINVERT + VK_F7, ID_TOGGLE_BREAK, VIRTKEY, NOINVERT VK_F8, ID_DEBUG_STEPOUT, VIRTKEY, NOINVERT VK_F9, ID_DEBUG_RUNTOLINE, VIRTKEY, NOINVERT VK_F10, ID_DEBUG_STEPOVER, VIRTKEY, NOINVERT @@ -180,13 +180,13 @@ FONT 8, "MS Shell Dlg", 0, 0, 0x1 BEGIN LTEXT "Ctr:",IDC_STATIC,6,2,12,8 EDITTEXT IDC_DEBUG_COUNT,30,2,39,12,ES_UPPERCASE | ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER - PUSHBUTTON "Stop",IDC_STOPGO,111,0,24,14 - PUSHBUTTON "Step Into",IDC_STEP,143,0,39,14 - PUSHBUTTON "Step Over",IDC_STEPOVER,183,0,40,14 - PUSHBUTTON "Step Out",IDC_STEPOUT,224,0,40,14 - PUSHBUTTON "Next HLE",IDC_STEPHLE,272,0,40,14 - PUSHBUTTON "Breakpoint",IDC_MEMCHECK,313,0,45,14 - EDITTEXT IDC_THREADNAME,366,3,150,10,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + PUSHBUTTON "Break",IDC_STOPGO,111,0,28,14 + PUSHBUTTON "Step Into",IDC_STEP,147,0,39,14 + PUSHBUTTON "Step Over",IDC_STEPOVER,187,0,40,14 + PUSHBUTTON "Step Out",IDC_STEPOUT,228,0,40,14 + PUSHBUTTON "Next HLE",IDC_STEPHLE,276,0,40,14 + PUSHBUTTON "Breakpoint",IDC_MEMCHECK,317,0,45,14 + EDITTEXT IDC_THREADNAME,370,3,150,10,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER EDITTEXT IDC_ADDRESS,11,24,91,13,ES_AUTOHSCROLL | ES_WANTRETURN PUSHBUTTON "PC",IDC_GOTOPC,11,40,15,13 PUSHBUTTON "RA",IDC_GOTOLR,28,40,14,13 @@ -484,7 +484,7 @@ BEGIN POPUP "Emulation" BEGIN - MENUITEM "Run", ID_TOGGLE_PAUSE + MENUITEM "Pause", ID_EMULATION_PAUSE MENUITEM "Stop", ID_EMULATION_STOP MENUITEM "Reset", ID_EMULATION_RESET MENUITEM "Switch UMD", ID_EMULATION_SWITCH_UMD @@ -499,18 +499,19 @@ BEGIN POPUP "Debug" BEGIN + MENUITEM "Run", ID_TOGGLE_BREAK + MENUITEM "Break on Load", ID_DEBUG_BREAKONLOAD + MENUITEM "Ignore Illegal Reads/Writes", ID_DEBUG_IGNOREILLEGALREADS + MENUITEM SEPARATOR MENUITEM "Load Map File...", ID_DEBUG_LOADMAPFILE MENUITEM "Save Map File...", ID_DEBUG_SAVEMAPFILE MENUITEM "Load .sym File...", ID_DEBUG_LOADSYMFILE MENUITEM "Save .sym File...", ID_DEBUG_SAVESYMFILE MENUITEM "Reset Symbol Table", ID_DEBUG_RESETSYMBOLTABLE MENUITEM SEPARATOR - MENUITEM "Dump Next Frame to Log", ID_DEBUG_DUMPNEXTFRAME MENUITEM "Take Screenshot", ID_DEBUG_TAKESCREENSHOT - MENUITEM SEPARATOR + MENUITEM "Dump Next Frame to Log", ID_DEBUG_DUMPNEXTFRAME MENUITEM "Show Debug Statistics", ID_DEBUG_SHOWDEBUGSTATISTICS - MENUITEM "Ignore Illegal Reads/Writes", ID_DEBUG_IGNOREILLEGALREADS - MENUITEM "Run on Load", ID_DEBUG_RUNONLOAD MENUITEM SEPARATOR MENUITEM "Disassembly", ID_DEBUG_DISASSEMBLY MENUITEM "GE Debugger...", ID_DEBUG_GEDEBUGGER diff --git a/Windows/resource.h b/Windows/resource.h index 22c8bc2fd6..4fef56c2da 100644 --- a/Windows/resource.h +++ b/Windows/resource.h @@ -176,7 +176,7 @@ #define ID_DISASM_COPYINSTRUCTIONDISASM 40006 #define ID_DISASM_COPYINSTRUCTIONHEX 40007 #define ID_EMULATION_SPEEDLIMIT 40008 -#define ID_TOGGLE_PAUSE 40009 +#define ID_TOGGLE_BREAK 40009 #define ID_EMULATION_STOP 40010 #define ID_FILE_LOAD 40011 #define ID_HELP_ABOUT 40012 @@ -205,7 +205,7 @@ #define ID_FILE_QUICKSAVESTATE_HC 40036 #define ID_FILE_QUICKLOADSTATE_HC 40037 #define ID_OPTIONS_CONTROLS 40038 -#define ID_DEBUG_RUNONLOAD 40039 +#define ID_DEBUG_BREAKONLOAD 40039 #define ID_DEBUG_DUMPNEXTFRAME 40040 #define ID_OPTIONS_VERTEXCACHE 40041 #define ID_OPTIONS_SHOWFPS 40042 @@ -342,14 +342,15 @@ #define ID_GEDBG_REPLACEMENT_FILENAME 40199 #define ID_SIMPLEUI_TOGGLE 40200 #define ID_SIMPLEUI_HIDE 40201 +#define ID_EMULATION_PAUSE 40177 -#define ID_TEXTURESCALING_REALTIME 40177 -#define ID_TEXTURESCALING_REALTIME_HC 40178 -#define ID_TEXTURESCALING_4XBRZ 40179 -#define ID_TEXTURESCALING_XBR 40180 -#define ID_TEXTURESCALING_SABR 40181 -#define ID_TEXTURESCALING_GAUSSIAN 40182 -#define ID_TEXTURESCALING_COSINE 40183 +#define ID_TEXTURESCALING_REALTIME 40178 +#define ID_TEXTURESCALING_REALTIME_HC 40179 +#define ID_TEXTURESCALING_4XBRZ 40180 +#define ID_TEXTURESCALING_XBR 40181 +#define ID_TEXTURESCALING_SABR 40182 +#define ID_TEXTURESCALING_GAUSSIAN 40183 +#define ID_TEXTURESCALING_COSINE 40184 // Dummy option to let the buffered rendering hotkey cycle through all the options. #define ID_OPTIONS_BUFFEREDRENDERINGDUMMY 40500 @@ -362,7 +363,7 @@ #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 256 -#define _APS_NEXT_COMMAND_VALUE 40177 +#define _APS_NEXT_COMMAND_VALUE 40185 #define _APS_NEXT_CONTROL_VALUE 1200 #define _APS_NEXT_SYMED_VALUE 101 #endif diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index cc2938f5c5..7c2ca632bb 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -2,13 +2,13 @@ - + android:versionCode="16030000" + android:versionName="1.6.3.0"> + + android:targetSdkVersion="28" /> @@ -47,7 +47,7 @@ android:name=".PpssppActivity" android:configChanges="locale|keyboard|keyboardHidden|navigation|uiMode" android:label="@string/app_name" - android:theme="@android:style/Theme.NoTitleBar.Fullscreen"> + android:theme="@style/ppsspp_style"> diff --git a/android/build.gradle b/android/build.gradle index 59fe1ec9b7..55bf308f5d 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'com.gladed.androidgitversion' version '0.4.3' + id 'com.gladed.androidgitversion' version '0.4.4' } apply plugin: 'com.android.application' @@ -34,8 +34,8 @@ android { } } } - compileSdkVersion 26 - buildToolsVersion '27.0.0' + compileSdkVersion 28 + buildToolsVersion '28.0.0' defaultConfig { applicationId 'org.ppsspp.ppsspp' if (androidGitVersion.name() != "unknown" && androidGitVersion.code() >= 14000000) { @@ -51,7 +51,7 @@ android { new File("versioncode.txt").write(androidGitVersion.code().toString()) minSdkVersion 9 - targetSdkVersion 26 + targetSdkVersion 28 if (project.hasProperty("ANDROID_VERSION_CODE") && project.hasProperty("ANDROID_VERSION_NAME")) { versionCode ANDROID_VERSION_CODE versionName ANDROID_VERSION_NAME diff --git a/android/jni/Android.mk b/android/jni/Android.mk index fc04485bb6..13fc2838c3 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -161,6 +161,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/MIPS/MIPSAnalyst.cpp \ $(SRC)/Core/MIPS/MIPSDis.cpp \ $(SRC)/Core/MIPS/MIPSDisVFPU.cpp \ + $(SRC)/Core/MIPS/MIPSAsm.cpp \ $(SRC)/Core/MIPS/MIPSInt.cpp.arm \ $(SRC)/Core/MIPS/MIPSIntVFPU.cpp.arm \ $(SRC)/Core/MIPS/MIPSStackWalk.cpp \ @@ -296,8 +297,22 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/Screenshot.cpp \ $(SRC)/Core/System.cpp \ $(SRC)/Core/TextureReplacer.cpp \ + $(SRC)/Core/WebServer.cpp \ $(SRC)/Core/Debugger/Breakpoints.cpp \ + $(SRC)/Core/Debugger/DisassemblyManager.cpp \ $(SRC)/Core/Debugger/SymbolMap.cpp \ + $(SRC)/Core/Debugger/WebSocket.cpp \ + $(SRC)/Core/Debugger/WebSocket/BreakpointSubscriber.cpp \ + $(SRC)/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp \ + $(SRC)/Core/Debugger/WebSocket/DisasmSubscriber.cpp \ + $(SRC)/Core/Debugger/WebSocket/GameBroadcaster.cpp \ + $(SRC)/Core/Debugger/WebSocket/GameSubscriber.cpp \ + $(SRC)/Core/Debugger/WebSocket/GPUBufferSubscriber.cpp \ + $(SRC)/Core/Debugger/WebSocket/HLESubscriber.cpp \ + $(SRC)/Core/Debugger/WebSocket/LogBroadcaster.cpp \ + $(SRC)/Core/Debugger/WebSocket/SteppingBroadcaster.cpp \ + $(SRC)/Core/Debugger/WebSocket/SteppingSubscriber.cpp \ + $(SRC)/Core/Debugger/WebSocket/WebSocketUtils.cpp \ $(SRC)/Core/Dialog/PSPDialog.cpp \ $(SRC)/Core/Dialog/PSPGamedataInstallDialog.cpp \ $(SRC)/Core/Dialog/PSPMsgDialog.cpp \ @@ -397,12 +412,67 @@ EXEC_AND_LIB_FILES := \ LOCAL_MODULE := ppsspp_core LOCAL_SRC_FILES := $(EXEC_AND_LIB_FILES) - include $(BUILD_STATIC_LIBRARY) include $(CLEAR_VARS) include $(LOCAL_PATH)/Locals.mk -LOCAL_STATIC_LIBRARIES += ppsspp_core +LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SRC)/ext/armips $(LOCAL_C_INCLUDES) + +LIBARMIPS_FILES := \ + $(SRC)/ext/armips/Archs/ARM/Arm.cpp \ + $(SRC)/ext/armips/Archs/ARM/ArmOpcodes.cpp \ + $(SRC)/ext/armips/Archs/ARM/ArmParser.cpp \ + $(SRC)/ext/armips/Archs/ARM/ArmElfRelocator.cpp \ + $(SRC)/ext/armips/Archs/ARM/ArmExpressionFunctions.cpp \ + $(SRC)/ext/armips/Archs/ARM/CArmInstruction.cpp \ + $(SRC)/ext/armips/Archs/ARM/CThumbInstruction.cpp \ + $(SRC)/ext/armips/Archs/ARM/Pool.cpp \ + $(SRC)/ext/armips/Archs/ARM/ThumbOpcodes.cpp \ + $(SRC)/ext/armips/Archs/MIPS/CMipsInstruction.cpp \ + $(SRC)/ext/armips/Archs/MIPS/Mips.cpp \ + $(SRC)/ext/armips/Archs/MIPS/MipsElfFile.cpp \ + $(SRC)/ext/armips/Archs/MIPS/MipsElfRelocator.cpp \ + $(SRC)/ext/armips/Archs/MIPS/MipsExpressionFunctions.cpp \ + $(SRC)/ext/armips/Archs/MIPS/MipsMacros.cpp \ + $(SRC)/ext/armips/Archs/MIPS/MipsOpcodes.cpp \ + $(SRC)/ext/armips/Archs/MIPS/MipsParser.cpp \ + $(SRC)/ext/armips/Archs/MIPS/PsxRelocator.cpp \ + $(SRC)/ext/armips/Archs/Architecture.cpp \ + $(SRC)/ext/armips/Commands/CAssemblerCommand.cpp \ + $(SRC)/ext/armips/Commands/CAssemblerLabel.cpp \ + $(SRC)/ext/armips/Commands/CDirectiveArea.cpp \ + $(SRC)/ext/armips/Commands/CDirectiveConditional.cpp \ + $(SRC)/ext/armips/Commands/CDirectiveData.cpp \ + $(SRC)/ext/armips/Commands/CDirectiveFile.cpp \ + $(SRC)/ext/armips/Commands/CDirectiveMessage.cpp \ + $(SRC)/ext/armips/Commands/CommandSequence.cpp \ + $(SRC)/ext/armips/Core/ELF/ElfFile.cpp \ + $(SRC)/ext/armips/Core/ELF/ElfRelocator.cpp \ + $(SRC)/ext/armips/Core/Assembler.cpp \ + $(SRC)/ext/armips/Core/Common.cpp \ + $(SRC)/ext/armips/Core/Expression.cpp \ + $(SRC)/ext/armips/Core/ExpressionFunctions.cpp \ + $(SRC)/ext/armips/Core/FileManager.cpp \ + $(SRC)/ext/armips/Core/Misc.cpp \ + $(SRC)/ext/armips/Core/SymbolData.cpp \ + $(SRC)/ext/armips/Core/SymbolTable.cpp \ + $(SRC)/ext/armips/Parser/DirectivesParser.cpp \ + $(SRC)/ext/armips/Parser/ExpressionParser.cpp \ + $(SRC)/ext/armips/Parser/Parser.cpp \ + $(SRC)/ext/armips/Parser/Tokenizer.cpp \ + $(SRC)/ext/armips/Util/ByteArray.cpp \ + $(SRC)/ext/armips/Util/CRC.cpp \ + $(SRC)/ext/armips/Util/EncodingTable.cpp \ + $(SRC)/ext/armips/Util/FileClasses.cpp \ + $(SRC)/ext/armips/Util/Util.cpp + +LOCAL_MODULE := libarmips +LOCAL_SRC_FILES := $(LIBARMIPS_FILES) +include $(BUILD_STATIC_LIBRARY) + +include $(CLEAR_VARS) +include $(LOCAL_PATH)/Locals.mk +LOCAL_STATIC_LIBRARIES += ppsspp_core libarmips # These are the files just for ppsspp_jni LOCAL_MODULE := ppsspp_jni @@ -450,7 +520,7 @@ endif ifeq ($(HEADLESS),1) include $(CLEAR_VARS) include $(LOCAL_PATH)/Locals.mk - LOCAL_STATIC_LIBRARIES += ppsspp_core + LOCAL_STATIC_LIBRARIES += ppsspp_core libarmips # Android 5.0 requires PIE for executables. Only supported on 4.1+, but this is testing anyway. LOCAL_CFLAGS += -fPIE @@ -468,59 +538,12 @@ endif ifeq ($(UNITTEST),1) include $(CLEAR_VARS) include $(LOCAL_PATH)/Locals.mk - LOCAL_STATIC_LIBRARIES += ppsspp_core + LOCAL_STATIC_LIBRARIES += ppsspp_core libarmips # Android 5.0 requires PIE for executables. Only supported on 4.1+, but this is testing anyway. LOCAL_CFLAGS += -fPIE LOCAL_LDFLAGS += -fPIE -pie - LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SRC)/ext/armips $(LOCAL_C_INCLUDES) - - LIBARMIPS_FILES := \ - $(SRC)/ext/armips/Archs/ARM/Arm.cpp \ - $(SRC)/ext/armips/Archs/ARM/ArmOpcodes.cpp \ - $(SRC)/ext/armips/Archs/ARM/ArmParser.cpp \ - $(SRC)/ext/armips/Archs/ARM/ArmRelocator.cpp \ - $(SRC)/ext/armips/Archs/ARM/CArmInstruction.cpp \ - $(SRC)/ext/armips/Archs/ARM/CThumbInstruction.cpp \ - $(SRC)/ext/armips/Archs/ARM/Pool.cpp \ - $(SRC)/ext/armips/Archs/ARM/ThumbOpcodes.cpp \ - $(SRC)/ext/armips/Archs/MIPS/CMipsInstruction.cpp \ - $(SRC)/ext/armips/Archs/MIPS/Mips.cpp \ - $(SRC)/ext/armips/Archs/MIPS/MipsElfFile.cpp \ - $(SRC)/ext/armips/Archs/MIPS/MipsMacros.cpp \ - $(SRC)/ext/armips/Archs/MIPS/MipsOpcodes.cpp \ - $(SRC)/ext/armips/Archs/MIPS/MipsParser.cpp \ - $(SRC)/ext/armips/Archs/MIPS/PsxRelocator.cpp \ - $(SRC)/ext/armips/Archs/Architecture.cpp \ - $(SRC)/ext/armips/Commands/CAssemblerCommand.cpp \ - $(SRC)/ext/armips/Commands/CAssemblerLabel.cpp \ - $(SRC)/ext/armips/Commands/CDirectiveArea.cpp \ - $(SRC)/ext/armips/Commands/CDirectiveConditional.cpp \ - $(SRC)/ext/armips/Commands/CDirectiveData.cpp \ - $(SRC)/ext/armips/Commands/CDirectiveFile.cpp \ - $(SRC)/ext/armips/Commands/CDirectiveMessage.cpp \ - $(SRC)/ext/armips/Commands/CommandSequence.cpp \ - $(SRC)/ext/armips/Core/ELF/ElfFile.cpp \ - $(SRC)/ext/armips/Core/ELF/ElfRelocator.cpp \ - $(SRC)/ext/armips/Core/Assembler.cpp \ - $(SRC)/ext/armips/Core/Common.cpp \ - $(SRC)/ext/armips/Core/Expression.cpp \ - $(SRC)/ext/armips/Core/ExpressionFunctions.cpp \ - $(SRC)/ext/armips/Core/FileManager.cpp \ - $(SRC)/ext/armips/Core/Misc.cpp \ - $(SRC)/ext/armips/Core/SymbolData.cpp \ - $(SRC)/ext/armips/Core/SymbolTable.cpp \ - $(SRC)/ext/armips/Parser/DirectivesParser.cpp \ - $(SRC)/ext/armips/Parser/ExpressionParser.cpp \ - $(SRC)/ext/armips/Parser/Parser.cpp \ - $(SRC)/ext/armips/Parser/Tokenizer.cpp \ - $(SRC)/ext/armips/Util/ByteArray.cpp \ - $(SRC)/ext/armips/Util/CRC.cpp \ - $(SRC)/ext/armips/Util/EncodingTable.cpp \ - $(SRC)/ext/armips/Util/FileClasses.cpp \ - $(SRC)/ext/armips/Util/Util.cpp - ifeq ($(findstring arm64-v8a,$(TARGET_ARCH_ABI)),arm64-v8a) TESTARMEMITTER_FILE = $(SRC)/unittest/TestArm64Emitter.cpp else ifeq ($(findstring armeabi-v7a,$(TARGET_ARCH_ABI)),armeabi-v7a) @@ -539,8 +562,6 @@ ifeq ($(UNITTEST),1) LOCAL_MODULE := ppsspp_unittest LOCAL_SRC_FILES := \ - $(LIBARMIPS_FILES) \ - $(SRC)/Core/MIPS/MIPSAsm.cpp \ $(SRC)/unittest/JitHarness.cpp \ $(SRC)/unittest/TestVertexJit.cpp \ $(TESTARMEMITTER_FILE) \ diff --git a/android/jni/AndroidEGLContext.cpp b/android/jni/AndroidEGLContext.cpp index 1a40994cf7..34d21597f5 100644 --- a/android/jni/AndroidEGLContext.cpp +++ b/android/jni/AndroidEGLContext.cpp @@ -6,6 +6,7 @@ #include "AndroidEGLContext.h" #include "GL/GLInterface/EGLAndroid.h" +#include "Core/ConfigValues.h" #include "Core/System.h" bool AndroidEGLGraphicsContext::InitFromRenderThread(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) { @@ -22,7 +23,7 @@ bool AndroidEGLGraphicsContext::InitFromRenderThread(ANativeWindow *wnd, int des // Apparently we still have to set this through Java through setFixedSize on the bufferHolder for it to take effect... gl->SetBackBufferDimensions(backbufferWidth, backbufferHeight); - gl->SetMode(MODE_DETECT_ES); + gl->SetMode(MODE_DETECT); bool use565 = false; @@ -44,18 +45,26 @@ bool AndroidEGLGraphicsContext::InitFromRenderThread(ANativeWindow *wnd, int des return false; } gl->MakeCurrent(); + if (gl->GetMode() == GLInterfaceMode::MODE_OPENGL) + SetGLCoreContext(true); CheckGLExtensions(); draw_ = Draw::T3DCreateGLContext(); SetGPUBackend(GPUBackend::OPENGL); + renderManager_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); bool success = draw_->CreatePresets(); // There will always be a GLSL compiler capable of compiling these. assert(success); return true; } -void AndroidEGLGraphicsContext::Shutdown() { +void AndroidEGLGraphicsContext::ShutdownFromRenderThread() { + ILOG("AndroidEGLGraphicsContext::Shutdown"); + renderManager_->WaitUntilQueueIdle(); + renderManager_ = nullptr; // owned by draw_. delete draw_; draw_ = nullptr; - NativeShutdownGraphics(); +} + +void AndroidEGLGraphicsContext::Shutdown() { gl->ClearCurrent(); gl->Shutdown(); delete gl; diff --git a/android/jni/AndroidEGLContext.h b/android/jni/AndroidEGLContext.h index 2bc9d5341c..35ae334294 100644 --- a/android/jni/AndroidEGLContext.h +++ b/android/jni/AndroidEGLContext.h @@ -1,5 +1,6 @@ #pragma once +#include "thin3d/GLRenderManager.h" #include "AndroidGraphicsContext.h" #include "Common/GL/GLInterfaceBase.h" @@ -7,6 +8,7 @@ class AndroidEGLGraphicsContext : public AndroidGraphicsContext { public: AndroidEGLGraphicsContext() : draw_(nullptr), wnd_(nullptr), gl(nullptr) {} bool InitFromRenderThread(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) override; + void ShutdownFromRenderThread() override; void Shutdown() override; void SwapBuffers() override; void SwapInterval(int interval) override {} @@ -18,8 +20,26 @@ public: return draw_ != nullptr; } + void ThreadStart() override { + renderManager_->ThreadStart(); + } + + bool ThreadFrame() override { + return renderManager_->ThreadFrame(); + } + + void ThreadEnd() override { + renderManager_->ThreadEnd(); + } + + void StopThread() override { + renderManager_->WaitUntilQueueIdle(); + renderManager_->StopThread(); + } + private: Draw::DrawContext *draw_; ANativeWindow *wnd_; cInterfaceBase *gl; + GLRenderManager *renderManager_ = nullptr; }; diff --git a/android/jni/AndroidJavaGLContext.cpp b/android/jni/AndroidJavaGLContext.cpp index 0f0937db08..aca9f8fcc9 100644 --- a/android/jni/AndroidJavaGLContext.cpp +++ b/android/jni/AndroidJavaGLContext.cpp @@ -3,6 +3,7 @@ #include "AndroidJavaGLContext.h" #include "base/NativeApp.h" #include "gfx_es2/gpu_features.h" +#include "Core/ConfigValues.h" #include "Core/System.h" AndroidJavaEGLGraphicsContext::AndroidJavaEGLGraphicsContext() { diff --git a/android/jni/AndroidVulkanContext.cpp b/android/jni/AndroidVulkanContext.cpp index 4240bde18c..128183dcc9 100644 --- a/android/jni/AndroidVulkanContext.cpp +++ b/android/jni/AndroidVulkanContext.cpp @@ -9,6 +9,7 @@ #include "thin3d/thin3d_create.h" #include "util/text/parsers.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/System.h" static VulkanLogOptions g_LogOptions; diff --git a/android/jni/TestRunner.cpp b/android/jni/TestRunner.cpp index c58aa8e9e4..2a24811289 100644 --- a/android/jni/TestRunner.cpp +++ b/android/jni/TestRunner.cpp @@ -92,7 +92,7 @@ bool RunTests() { coreParam.graphicsContext = nullptr; coreParam.mountIso = ""; coreParam.mountRoot = baseDirectory + "pspautotests/"; - coreParam.startPaused = false; + coreParam.startBreak = false; coreParam.printfEmuLog = false; coreParam.headLess = true; coreParam.renderWidth = 480; diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 3043425f61..9206bcd59b 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -43,6 +43,7 @@ #include "AndroidJavaGLContext.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/Loaders.h" #include "Core/System.h" #include "Common/CPUDetect.h" @@ -323,6 +324,8 @@ bool System_GetPropertyBool(SystemProperty prop) { } std::string GetJavaString(JNIEnv *env, jstring jstr) { + if (!jstr) + return ""; const char *str = env->GetStringUTFChars(jstr, 0); std::string cpp_string = std::string(str); env->ReleaseStringUTFChars(jstr, str); @@ -397,7 +400,9 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init langRegion = GetJavaString(env, jlangRegion); std::string externalDir = GetJavaString(env, jexternalDir); - std::string user_data_path = GetJavaString(env, jdataDir) + "/"; + std::string user_data_path = GetJavaString(env, jdataDir); + if (user_data_path.size() > 0) + user_data_path += "/"; library_path = GetJavaString(env, jlibraryDir) + "/"; std::string shortcut_param = GetJavaString(env, jshortcutParam); std::string cacheDir = GetJavaString(env, jcacheDir); @@ -438,9 +443,12 @@ retry: switch (g_Config.iGPUBackend) { case (int)GPUBackend::OPENGL: useCPUThread = true; - _assert_(javaGL); - ILOG("NativeApp.init() -- creating OpenGL context (JavaGL)"); - graphicsContext = new AndroidJavaEGLGraphicsContext(); + if (javaGL) { + ILOG("NativeApp.init() -- creating OpenGL context (JavaGL)"); + graphicsContext = new AndroidJavaEGLGraphicsContext(); + } else { + graphicsContext = new AndroidEGLGraphicsContext(); + } break; case (int)GPUBackend::VULKAN: { @@ -517,7 +525,8 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_pause(JNIEnv *, jclass) { } extern "C" void Java_org_ppsspp_ppsspp_NativeApp_shutdown(JNIEnv *, jclass) { - if (useCPUThread && graphicsContext) { + if (renderer_inited && useCPUThread && graphicsContext) { + // Only used in Java EGL path. EmuThreadStop(); while (graphicsContext->ThreadFrame()) { continue; @@ -650,7 +659,8 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayRender(JNIEnv *env, if (useCPUThread) { // This is the "GPU thread". - graphicsContext->ThreadFrame(); + if (graphicsContext) + graphicsContext->ThreadFrame(); } else { UpdateRunLoopAndroid(env); } @@ -958,31 +968,54 @@ retry: } if (!exitRenderLoop) { - NativeInitGraphics(graphicsContext); + if (!useCPUThread) { + NativeInitGraphics(graphicsContext); + } + graphicsContext->ThreadStart(); renderer_inited = true; } - while (!exitRenderLoop) { + if (!exitRenderLoop) { static bool hasSetThreadName = false; if (!hasSetThreadName) { hasSetThreadName = true; setCurrentThreadName("AndroidRender"); } + } - NativeUpdate(); + if (useCPUThread) { + ELOG("Running graphics loop"); + while (!exitRenderLoop) { + // This is the "GPU thread". + graphicsContext->ThreadFrame(); + graphicsContext->SwapBuffers(); + } + } else { + while (!exitRenderLoop) { + NativeUpdate(); - NativeRender(graphicsContext); - time_update(); + NativeRender(graphicsContext); + time_update(); - graphicsContext->SwapBuffers(); + graphicsContext->SwapBuffers(); - ProcessFrameCommands(env); + ProcessFrameCommands(env); + } } ILOG("Leaving EGL/Vulkan render loop."); - NativeShutdownGraphics(); + if (useCPUThread) { + EmuThreadStop(); + while (graphicsContext->ThreadFrame()) { + continue; + } + EmuThreadJoin(); + } else { + NativeShutdownGraphics(); + } renderer_inited = false; + graphicsContext->ThreadEnd(); // Shut the graphics context down to the same state it was in when we entered the render thread. ILOG("Shutting down graphics context from render thread..."); diff --git a/android/res/values-v21/styles.xml b/android/res/values-v21/styles.xml new file mode 100644 index 0000000000..31ce1cc3d3 --- /dev/null +++ b/android/res/values-v21/styles.xml @@ -0,0 +1,9 @@ + + + #FF000000 + + diff --git a/android/res/values/styles.xml b/android/res/values/styles.xml new file mode 100644 index 0000000000..dfa2fc94ac --- /dev/null +++ b/android/res/values/styles.xml @@ -0,0 +1,8 @@ + + + #FF000000 + + diff --git a/android/src/org/ppsspp/ppsspp/AudioFocusChangeListener.java b/android/src/org/ppsspp/ppsspp/AudioFocusChangeListener.java index 960e8f40a9..02a74576b5 100644 --- a/android/src/org/ppsspp/ppsspp/AudioFocusChangeListener.java +++ b/android/src/org/ppsspp/ppsspp/AudioFocusChangeListener.java @@ -1,20 +1,20 @@ package org.ppsspp.ppsspp; + import android.media.AudioManager; import android.media.AudioManager.OnAudioFocusChangeListener; -public class AudioFocusChangeListener implements OnAudioFocusChangeListener{ +public class AudioFocusChangeListener implements OnAudioFocusChangeListener { // not used right now, but we may need to use it sometime. So just store it // for now. private boolean hasAudioFocus = false; - - + @Override public void onAudioFocusChange(int focusChange) { - switch (focusChange){ + switch (focusChange) { case AudioManager.AUDIOFOCUS_GAIN: hasAudioFocus = true; break; - + case AudioManager.AUDIOFOCUS_LOSS: hasAudioFocus = false; break; diff --git a/android/src/org/ppsspp/ppsspp/CameraHelper.java b/android/src/org/ppsspp/ppsspp/CameraHelper.java index 3675f65e81..ca4bd8894a 100644 --- a/android/src/org/ppsspp/ppsspp/CameraHelper.java +++ b/android/src/org/ppsspp/ppsspp/CameraHelper.java @@ -8,7 +8,6 @@ import android.graphics.SurfaceTexture; import android.graphics.YuvImage; import android.hardware.Camera; import android.util.Log; - import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.List; @@ -42,10 +41,10 @@ class CameraHelper { // crop to expected size and convert to Jpeg Rect rect = new Rect( - (previewWidth-targetW)/2, - (previewHeight-targetH)/2, - previewWidth-(previewWidth-targetW)/2, - previewHeight-(previewHeight-targetH)/2 + (previewWidth - targetW) / 2, + (previewHeight - targetH) / 2, + previewWidth - (previewWidth - targetW) / 2, + previewHeight - (previewHeight - targetH) / 2 ); yuvImage.compressToJpeg(rect, 80, baos); NativeApp.pushCameraImage(baos.toByteArray()); diff --git a/android/src/org/ppsspp/ppsspp/InputDeviceState.java b/android/src/org/ppsspp/ppsspp/InputDeviceState.java index 09381dbd71..89e9d0f016 100644 --- a/android/src/org/ppsspp/ppsspp/InputDeviceState.java +++ b/android/src/org/ppsspp/ppsspp/InputDeviceState.java @@ -11,42 +11,44 @@ import android.view.MotionEvent; @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) public class InputDeviceState { private static final String TAG = "InputDeviceState"; - + private static final int deviceId = NativeApp.DEVICE_ID_PAD_0; private InputDevice mDevice; private int[] mAxes; - InputDevice getDevice() { return mDevice; } + InputDevice getDevice() { + return mDevice; + } @TargetApi(19) void logAdvanced(InputDevice device) { - Log.i(TAG, "Vendor ID:" + device.getVendorId() + " productId: " + device.getProductId()); + Log.i(TAG, "Vendor ID:" + device.getVendorId() + " productId: " + device.getProductId()); } - + public InputDeviceState(InputDevice device) { - mDevice = device; - int numAxes = 0; - for (MotionRange range : device.getMotionRanges()) { - if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { - numAxes += 1; - } - } + mDevice = device; + int numAxes = 0; + for (MotionRange range : device.getMotionRanges()) { + if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { + numAxes += 1; + } + } - mAxes = new int[numAxes]; + mAxes = new int[numAxes]; - int i = 0; - for (MotionRange range : device.getMotionRanges()) { - if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { - mAxes[i++] = range.getAxis(); - } - } - - Log.i(TAG, "Registering input device with " + numAxes + " axes: " + device.getName()); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - logAdvanced(device); - } - NativeApp.sendMessage("inputDeviceConnected", device.getName()); + int i = 0; + for (MotionRange range : device.getMotionRanges()) { + if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { + mAxes[i++] = range.getAxis(); + } + } + + Log.i(TAG, "Registering input device with " + numAxes + " axes: " + device.getName()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + logAdvanced(device); + } + NativeApp.sendMessage("inputDeviceConnected", device.getName()); } public boolean onKeyDown(KeyEvent event) { @@ -54,12 +56,12 @@ public class InputDeviceState { boolean repeat = event.getRepeatCount() > 0; return NativeApp.keyDown(deviceId, keyCode, repeat); } - + public boolean onKeyUp(KeyEvent event) { int keyCode = event.getKeyCode(); - return NativeApp.keyUp(deviceId, keyCode); + return NativeApp.keyUp(deviceId, keyCode); } - + public boolean onJoystickMotion(MotionEvent event) { if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) == 0) { return false; diff --git a/android/src/org/ppsspp/ppsspp/LocationHelper.java b/android/src/org/ppsspp/ppsspp/LocationHelper.java index c6f4797d61..a71bcaeb19 100644 --- a/android/src/org/ppsspp/ppsspp/LocationHelper.java +++ b/android/src/org/ppsspp/ppsspp/LocationHelper.java @@ -48,12 +48,12 @@ class LocationHelper implements LocationListener { @Override public void onLocationChanged(Location location) { - float latitude = (float)location.getLatitude(); - float longitude = (float)location.getLongitude(); - float altitude = (float)location.getAltitude(); - float speed = location.getSpeed(); - float bearing = location.getBearing(); - long time = location.getTime() / 1000; // ms to s !! + float latitude = (float) location.getLatitude(); + float longitude = (float) location.getLongitude(); + float altitude = (float) location.getAltitude(); + float speed = location.getSpeed(); + float bearing = location.getBearing(); + long time = location.getTime() / 1000; // ms to s !! NativeApp.pushNewGpsData(latitude, longitude, altitude, speed, bearing, time); } diff --git a/android/src/org/ppsspp/ppsspp/MogaHack.java b/android/src/org/ppsspp/ppsspp/MogaHack.java index 7322fd4fd6..745a1fa8c8 100644 --- a/android/src/org/ppsspp/ppsspp/MogaHack.java +++ b/android/src/org/ppsspp/ppsspp/MogaHack.java @@ -1,27 +1,25 @@ /** * Mupen64PlusAE, an N64 emulator for the Android platform - * + * * Copyright (C) 2013 Paul Lamb - * + * * This file is part of Mupen64PlusAE. - * + * * Mupen64PlusAE is free software: you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. - * + * * Mupen64PlusAE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License along with Mupen64PlusAE. If * not, see . - * + * * Authors: Paul Lamb */ package org.ppsspp.ppsspp; -import java.util.List; - import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -29,90 +27,73 @@ import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.Build; import android.util.Log; - import com.bda.controller.Controller; import com.bda.controller.IControllerService; +import java.util.List; /** * Temporary hack for crash in MOGA library on Lollipop. This hack can be removed once MOGA fixes * their library. The actual issue is caused by the use of implicit service intents, which are * illegal in Lollipop, as seen in the logcat message below. - * + * *
  * {@code Service Intent must be explicit: Intent { act=com.bda.controller.IControllerService } }
  * 
- * + * * @see MOGA developer site * @see * Discussion on explicit intents */ -public class MogaHack -{ - public static void init(Controller controller, Context context ) - { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) - { - boolean mIsBound = false; - java.lang.reflect.Field fIsBound = null; - android.content.ServiceConnection mServiceConnection = null; - java.lang.reflect.Field fServiceConnection = null; - try - { - Class cMogaController = controller.getClass(); - fIsBound = cMogaController.getDeclaredField( "mIsBound" ); - fIsBound.setAccessible( true ); - mIsBound = fIsBound.getBoolean( controller ); - fServiceConnection = cMogaController.getDeclaredField( "mServiceConnection" ); - fServiceConnection.setAccessible( true ); - mServiceConnection = ( android.content.ServiceConnection ) fServiceConnection.get( controller ); - } - catch( NoSuchFieldException e ) - { - Log.e( "MogaHack", "MOGA Lollipop Hack NoSuchFieldException (get)", e ); - } - catch( IllegalAccessException e ) - { - Log.e( "MogaHack", "MOGA Lollipop Hack IllegalAccessException (get)", e ); - } - catch( IllegalArgumentException e ) - { - Log.e( "MogaHack", "MOGA Lollipop Hack IllegalArgumentException (get)", e ); - } - if( ( !mIsBound ) && ( mServiceConnection != null ) ) - { - // Convert implicit intent to explicit intent, see http://stackoverflow.com/a/26318757 - Intent intent = new Intent( IControllerService.class.getName() ); - List resolveInfos = context.getPackageManager().queryIntentServices( intent, 0 ); - if( resolveInfos == null || resolveInfos.size() != 1 ) - { - // What? this doesn't do anything. - // Log.e( "MogaHack", "Somebody is trying to intercept our intent. Disabling MOGA controller for security." ); - } - ServiceInfo serviceInfo = resolveInfos.get( 0 ).serviceInfo; - String packageName = serviceInfo.packageName; - String className = serviceInfo.name; - intent.setComponent( new ComponentName( packageName, className ) ); - - // Start the service explicitly - context.startService( intent ); - context.bindService( intent, mServiceConnection, 1 ); - try - { - fIsBound.setBoolean( controller, true ); - } - catch( IllegalAccessException e ) - { - Log.e( "MogaHack", "MOGA Lollipop Hack IllegalAccessException (set)", e ); - } - catch( IllegalArgumentException e ) - { - Log.e( "MogaHack", "MOGA Lollipop Hack IllegalArgumentException (set)", e ); - } - } - } - else - { - controller.init(); - } - } +public class MogaHack { + public static void init(Controller controller, Context context) { + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { + boolean mIsBound = false; + java.lang.reflect.Field fIsBound = null; + android.content.ServiceConnection mServiceConnection = null; + java.lang.reflect.Field fServiceConnection = null; + + try { + Class cMogaController = controller.getClass(); + fIsBound = cMogaController.getDeclaredField("mIsBound"); + fIsBound.setAccessible(true); + mIsBound = fIsBound.getBoolean(controller); + fServiceConnection = cMogaController.getDeclaredField("mServiceConnection"); + fServiceConnection.setAccessible(true); + mServiceConnection = (android.content.ServiceConnection) fServiceConnection.get(controller); + } catch (NoSuchFieldException e) { + Log.e("MogaHack", "MOGA Lollipop Hack NoSuchFieldException (get)", e); + } catch (IllegalAccessException e) { + Log.e("MogaHack", "MOGA Lollipop Hack IllegalAccessException (get)", e); + } catch (IllegalArgumentException e) { + Log.e("MogaHack", "MOGA Lollipop Hack IllegalArgumentException (get)", e); + } + + if ((!mIsBound) && (mServiceConnection != null)) { + // Convert implicit intent to explicit intent, see http://stackoverflow.com/a/26318757 + Intent intent = new Intent(IControllerService.class.getName()); + List resolveInfos = context.getPackageManager().queryIntentServices(intent, 0); + if (resolveInfos == null || resolveInfos.size() != 1) { + // What? this doesn't do anything. + // Log.e("MogaHack", "Somebody is trying to intercept our intent. Disabling MOGA controller for security."); + } + ServiceInfo serviceInfo = resolveInfos.get(0).serviceInfo; + String packageName = serviceInfo.packageName; + String className = serviceInfo.name; + intent.setComponent(new ComponentName(packageName, className)); + + // Start the service explicitly + context.startService(intent); + context.bindService(intent, mServiceConnection, 1); + try { + fIsBound.setBoolean(controller, true); + } catch (IllegalAccessException e) { + Log.e("MogaHack", "MOGA Lollipop Hack IllegalAccessException (set)", e); + } catch (IllegalArgumentException e) { + Log.e("MogaHack", "MOGA Lollipop Hack IllegalArgumentException (set)", e); + } + } + } else { + controller.init(); + } + } } diff --git a/android/src/org/ppsspp/ppsspp/NativeActivity.java b/android/src/org/ppsspp/ppsspp/NativeActivity.java index b524887241..563954e6b5 100644 --- a/android/src/org/ppsspp/ppsspp/NativeActivity.java +++ b/android/src/org/ppsspp/ppsspp/NativeActivity.java @@ -48,7 +48,6 @@ import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.Toast; - import java.io.File; import java.lang.reflect.Field; import java.util.List; @@ -116,14 +115,14 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C private int pixelHeight; private static final String[] permissionsForStorage = { - Manifest.permission.WRITE_EXTERNAL_STORAGE + Manifest.permission.WRITE_EXTERNAL_STORAGE, }; private static final String[] permissionsForLocation = { - Manifest.permission.ACCESS_FINE_LOCATION, - Manifest.permission.ACCESS_COARSE_LOCATION + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION, }; private static final String[] permissionsForCamera = { - Manifest.permission.CAMERA + Manifest.permission.CAMERA }; public static final int REQUEST_CODE_STORAGE_PERMISSION = 1; @@ -132,12 +131,12 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C // Functions for the app activity to override to change behaviour. - public native void registerCallbacks(); - public native void unregisterCallbacks(); + public native void registerCallbacks(); + public native void unregisterCallbacks(); - public boolean useLowProfileButtons() { - return true; - } + public boolean useLowProfileButtons() { + return true; + } NativeRenderer getRenderer() { return nativeRenderer; @@ -158,21 +157,21 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C } String getApplicationLibraryDir(ApplicationInfo application) { - String libdir = null; - try { - // Starting from Android 2.3, nativeLibraryDir is available: - Field field = ApplicationInfo.class.getField("nativeLibraryDir"); - libdir = (String) field.get(application); - } catch (SecurityException e1) { - } catch (NoSuchFieldException e1) { - } catch (IllegalArgumentException e) { - } catch (IllegalAccessException e) { - } - if (libdir == null) { - // Fallback for Android < 2.3: - libdir = application.dataDir + "/lib"; - } - return libdir; + String libdir = null; + try { + // Starting from Android 2.3, nativeLibraryDir is available: + Field field = ApplicationInfo.class.getField("nativeLibraryDir"); + libdir = (String) field.get(application); + } catch (SecurityException e1) { + } catch (NoSuchFieldException e1) { + } catch (IllegalArgumentException e) { + } catch (IllegalAccessException e) { + } + if (libdir == null) { + // Fallback for Android < 2.3: + libdir = application.dataDir + "/lib"; + } + return libdir; } @TargetApi(23) @@ -212,27 +211,26 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C } @Override - public void onRequestPermissionsResult(int requestCode, - String permissions[], int[] grantResults) { + public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { switch (requestCode) { - case REQUEST_CODE_STORAGE_PERMISSION: - if (permissionsGranted(permissions, grantResults)) { - NativeApp.sendMessage("permission_granted", "storage"); - } else { - NativeApp.sendMessage("permission_denied", "storage"); - } - break; - case REQUEST_CODE_LOCATION_PERMISSION: - if (permissionsGranted(permissions, grantResults)) { - mLocationHelper.startLocationUpdates(); - } - break; - case REQUEST_CODE_CAMERA_PERMISSION: - if (mCameraHelper != null && permissionsGranted(permissions, grantResults)) { - mCameraHelper.startCamera(); - } - break; - default: + case REQUEST_CODE_STORAGE_PERMISSION: + if (permissionsGranted(permissions, grantResults)) { + NativeApp.sendMessage("permission_granted", "storage"); + } else { + NativeApp.sendMessage("permission_denied", "storage"); + } + break; + case REQUEST_CODE_LOCATION_PERMISSION: + if (permissionsGranted(permissions, grantResults)) { + mLocationHelper.startLocationUpdates(); + } + break; + case REQUEST_CODE_CAMERA_PERMISSION: + if (mCameraHelper != null && permissionsGranted(permissions, grantResults)) { + mCameraHelper.startCamera(); + } + break; + default: } } @@ -241,62 +239,65 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C } public void Initialize() { - // Initialize audio classes. Do this here since detectOptimalAudioSettings() + // Initialize audio classes. Do this here since detectOptimalAudioSettings() // needs audioManager - this.audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE); + this.audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); this.audioFocusChangeListener = new AudioFocusChangeListener(); - if (Build.VERSION.SDK_INT >= 17) { - // Get the optimal buffer sz - detectOptimalAudioSettings(); - } - powerManager = (PowerManager)getSystemService(Context.POWER_SERVICE); - if (Build.VERSION.SDK_INT >= 24) { + if (Build.VERSION.SDK_INT >= 17) { + // Get the optimal buffer sz + detectOptimalAudioSettings(); + } + powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); + if (Build.VERSION.SDK_INT >= 24) { if (powerManager.isSustainedPerformanceModeSupported()) { sustainedPerfSupported = true; NativeApp.sendMessage("sustained_perf_supported", "1"); } - } + } - // isLandscape is used to trigger GetAppInfo currently, we - boolean landscape = NativeApp.isLandscape(); - Log.d(TAG, "Landscape: " + landscape); + // isLandscape is used to trigger GetAppInfo currently, we + boolean landscape = NativeApp.isLandscape(); + Log.d(TAG, "Landscape: " + landscape); - // Get system information + // Get system information ApplicationInfo appInfo = null; PackageManager packMgmr = getPackageManager(); String packageName = getPackageName(); try { - appInfo = packMgmr.getApplicationInfo(packageName, 0); - } catch (NameNotFoundException e) { - e.printStackTrace(); - throw new RuntimeException("Unable to locate assets, aborting..."); - } + appInfo = packMgmr.getApplicationInfo(packageName, 0); + } catch (NameNotFoundException e) { + e.printStackTrace(); + throw new RuntimeException("Unable to locate assets, aborting..."); + } int deviceType = NativeApp.DEVICE_TYPE_MOBILE; UiModeManager uiModeManager = (UiModeManager) getSystemService(UI_MODE_SERVICE); switch (uiModeManager.getCurrentModeType()) { case Configuration.UI_MODE_TYPE_TELEVISION: deviceType = NativeApp.DEVICE_TYPE_TV; - Log.i(TAG, "Running on an Android TV Device"); + Log.i(TAG, "Running on an Android TV Device"); break; case Configuration.UI_MODE_TYPE_DESK: deviceType = NativeApp.DEVICE_TYPE_DESKTOP; - Log.i(TAG, "Running on an Android desktop computer (!)"); + Log.i(TAG, "Running on an Android desktop computer (!)"); break; // All other device types are treated the same. } - - - isXperiaPlay = IsXperiaPlay(); + isXperiaPlay = IsXperiaPlay(); String libraryDir = getApplicationLibraryDir(appInfo); - File sdcard = Environment.getExternalStorageDirectory(); + File sdcard = Environment.getExternalStorageDirectory(); - String externalStorageDir = sdcard.getAbsolutePath(); - String dataDir = this.getFilesDir().getAbsolutePath(); + String externalStorageDir = sdcard.getAbsolutePath(); + File filesDir = this.getFilesDir(); + String dataDir = null; + if (filesDir != null) { + // Null has been seen in Google Play stacktraces + dataDir = filesDir.getAbsolutePath(); + } String apkFilePath = appInfo.sourceDir; String cacheDir = getCacheDir().getAbsolutePath(); @@ -317,27 +318,26 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C updateScreenRotation("Initialize"); } - // Detect OpenGL support. - // We don't currently use this detection for anything but good to have in the log. - if (!detectOpenGLES20()) { - Log.i(TAG, "OpenGL ES 2.0 NOT detected. Things will likely go badly."); - } else { - if (detectOpenGLES30()) { - Log.i(TAG, "OpenGL ES 3.0 detected."); - } - else { - Log.i(TAG, "OpenGL ES 2.0 detected."); - } - } + // Detect OpenGL support. + // We don't currently use this detection for anything but good to have in the log. + if (!detectOpenGLES20()) { + Log.i(TAG, "OpenGL ES 2.0 NOT detected. Things will likely go badly."); + } else { + if (detectOpenGLES30()) { + Log.i(TAG, "OpenGL ES 3.0 detected."); + } else { + Log.i(TAG, "OpenGL ES 2.0 detected."); + } + } - vibrator = (Vibrator)getSystemService(VIBRATOR_SERVICE); - if (Build.VERSION.SDK_INT >= 11) { - checkForVibrator(); - } + vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE); + if (Build.VERSION.SDK_INT >= 11) { + checkForVibrator(); + } mLocationHelper = new LocationHelper(this); - if (Build.VERSION.SDK_INT >= 11) { - // android.graphics.SurfaceTexture is not available before version 11. + if (Build.VERSION.SDK_INT >= 11) { + // android.graphics.SurfaceTexture is not available before version 11. mCameraHelper = new CameraHelper(this); } } @@ -415,16 +415,17 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C } else { Log.e(TAG, "updateSystemUiVisibility: decor view not yet created, ignoring"); } + updateDisplayMeasurements(); } // Need API 11 to check for existence of a vibrator? Zany. @TargetApi(11) public void checkForVibrator() { - if (Build.VERSION.SDK_INT >= 11) { - if (!vibrator.hasVibrator()) { - vibrator = null; - } - } + if (Build.VERSION.SDK_INT >= 11) { + if (!vibrator.hasVibrator()) { + vibrator = null; + } + } } private Runnable mEmulationRunner = new Runnable() { @@ -450,22 +451,33 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C sz.y = NativeApp.getDesiredBackbufferHeight(); } + @TargetApi(17) + public void updateDisplayMeasurements() { + Display display = getWindowManager().getDefaultDisplay(); + + DisplayMetrics metrics = new DisplayMetrics(); + if (useImmersive() && Build.VERSION.SDK_INT >= 17) { + display.getRealMetrics(metrics); + } else { + display.getMetrics(metrics); + } + densityDpi = metrics.densityDpi; + refreshRate = display.getRefreshRate(); + + NativeApp.setDisplayParameters(metrics.widthPixels, metrics.heightPixels, (int) densityDpi, refreshRate); + } + @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TextRenderer.init(this); shuttingDown = false; registerCallbacks(); - Display display = getWindowManager().getDefaultDisplay(); - DisplayMetrics metrics = new DisplayMetrics(); - display.getMetrics(metrics); - densityDpi = metrics.densityDpi; - refreshRate = display.getRefreshRate(); - - // Set early to be able to set defaults when loading config for the first time. Like figuring out + // This calls NativeApp.setDisplayParameters. Make sure that's done early in order + // to be able to set defaults when loading config for the first time. Like figuring out // whether to start at 1x or 2x. - NativeApp.setDisplayParameters(metrics.widthPixels, metrics.heightPixels, (int)densityDpi, refreshRate); + updateDisplayMeasurements(); if (!initialized) { Initialize(); @@ -476,43 +488,40 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C updateScreenRotation("onCreate"); updateSustainedPerformanceMode(); - // Keep the screen bright - very annoying if it goes dark when tilting away - Window window = this.getWindow(); - window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); setVolumeControlStream(AudioManager.STREAM_MUSIC); gainAudioFocus(this.audioManager, this.audioFocusChangeListener); - NativeApp.audioInit(); + NativeApp.audioInit(); - if (javaGL) { - mGLSurfaceView = new NativeGLView(this); + if (javaGL) { + mGLSurfaceView = new NativeGLView(this); nativeRenderer = new NativeRenderer(this); mGLSurfaceView.setEGLContextClientVersion(2); mGLSurfaceView.getHolder().addCallback(NativeActivity.this); - // Setup the GLSurface and ask android for the correct - // Number of bits for r, g, b, a, depth and stencil components - // The PSP only has 16-bit Z so that should be enough. - // Might want to change this for other apps (24-bit might be useful). - // Actually, we might be able to do without both stencil and depth in - // the back buffer, but that would kill non-buffered rendering. + // Setup the GLSurface and ask android for the correct + // Number of bits for r, g, b, a, depth and stencil components + // The PSP only has 16-bit Z so that should be enough. + // Might want to change this for other apps (24-bit might be useful). + // Actually, we might be able to do without both stencil and depth in + // the back buffer, but that would kill non-buffered rendering. - // It appears some gingerbread devices blow up if you use a config chooser at all ???? (Xperia Play) - //if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + // It appears some gingerbread devices blow up if you use a config chooser at all ???? (Xperia Play) + //if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - // On some (especially older devices), things blow up later (EGL_BAD_MATCH) if we don't set the format here, - // if we specify that we want destination alpha in the config chooser, which we do. - // http://grokbase.com/t/gg/android-developers/11bj40jm4w/fall-back + // On some (especially older devices), things blow up later (EGL_BAD_MATCH) if we don't set + // the format here, if we specify that we want destination alpha in the config chooser, which we do. + // http://grokbase.com/t/gg/android-developers/11bj40jm4w/fall-back - // Needed to avoid banding on Ouya? - if (Build.MANUFACTURER == "OUYA") { - mGLSurfaceView.getHolder().setFormat(PixelFormat.RGBX_8888); - mGLSurfaceView.setEGLConfigChooser(new NativeEGLConfigChooser()); - } - // Tried to mess around with config choosers here but fail completely on Xperia Play. + // Needed to avoid banding on Ouya? + if (Build.MANUFACTURER == "OUYA") { + mGLSurfaceView.getHolder().setFormat(PixelFormat.RGBX_8888); + mGLSurfaceView.setEGLConfigChooser(new NativeEGLConfigChooser()); + } + // Tried to mess around with config choosers here but fail completely on Xperia Play. mGLSurfaceView.setRenderer(nativeRenderer); setContentView(mGLSurfaceView); - } else { + } else { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { updateSystemUiVisibility(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { @@ -521,16 +530,17 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C } mSurfaceView = new NativeSurfaceView(NativeActivity.this); - mSurfaceView.getHolder().addCallback(NativeActivity.this); - Log.i(TAG, "setcontentview before"); + mSurfaceView.getHolder().addCallback(NativeActivity.this); + Log.i(TAG, "setcontentview before"); setContentView(mSurfaceView); Log.i(TAG, "setcontentview after"); ensureRenderLoop(); - } - } + } + } private Point desiredSize = new Point(); private int badOrientationCount = 0; + @Override public void surfaceCreated(SurfaceHolder holder) { pixelWidth = holder.getSurfaceFrame().width(); @@ -553,7 +563,7 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C } Log.d(TAG, "Surface created. pixelWidth=" + pixelWidth + ", pixelHeight=" + pixelHeight + " holder: " + holder.toString() + " or: " + requestedOr); - NativeApp.setDisplayParameters(pixelWidth, pixelHeight, (int)densityDpi, refreshRate); + NativeApp.setDisplayParameters(pixelWidth, pixelHeight, (int) densityDpi, refreshRate); getDesiredBackbufferSize(desiredSize); // Note that desiredSize might be 0,0 here - but that's fine when calling setFixedSize! It means auto. @@ -561,19 +571,25 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C holder.setFixedSize(desiredSize.x, desiredSize.y); } + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + updateSustainedPerformanceMode(); + } + @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { Log.v(TAG, "surfaceChanged: isCreating:" + holder.isCreating() + " holder: " + holder.toString()); if (holder.isCreating() && desiredSize.x > 0 && desiredSize.y > 0) { - // We have called setFixedSize which will trigger another surfaceChanged after the initial one. This one is the original one - // and we don't care about it. + // We have called setFixedSize which will trigger another surfaceChanged after the initial + // one. This one is the original one and we don't care about it. Log.w(TAG, "holder.isCreating = true, ignoring. width=" + width + " height=" + height + " desWidth=" + desiredSize.x + " desHeight=" + desiredSize.y); return; } Log.w(TAG, "Surface changed. Resolution: " + width + "x" + height + " Format: " + format); NativeApp.backbufferResize(width, height, format); mSurface = holder.getSurface(); - if (!javaGL) { + if (!javaGL) { // If we got a surface, this starts the thread. If not, it doesn't. if (mSurface == null) { joinRenderLoopThread(); @@ -581,6 +597,7 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C ensureRenderLoop(); } } + updateSustainedPerformanceMode(); } @Override @@ -597,8 +614,8 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C // Invariants: After this, mRenderLoopThread will be set, and the thread will be running. protected synchronized void ensureRenderLoop() { if (javaGL) { - Log.e(TAG, "JavaGL - should not get into ensureRenderLoop."); - return; + Log.e(TAG, "JavaGL - should not get into ensureRenderLoop."); + return; } if (mSurface == null) { Log.w(TAG, "ensureRenderLoop - not starting thread, needs surface"); @@ -615,8 +632,8 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C // Invariants: After this, mRenderLoopThread will be null, and the thread has exited. private synchronized void joinRenderLoopThread() { if (javaGL) { - Log.e(TAG, "JavaGL - should not get into joinRenderLoopThread."); - return; + Log.e(TAG, "JavaGL - should not get into joinRenderLoopThread."); + return; } if (mRenderLoopThread != null) { @@ -634,23 +651,23 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C } } - @TargetApi(19) + @TargetApi(19) void setupSystemUiCallback() { - getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(new OnSystemUiVisibilityChangeListener() { - @Override - public void onSystemUiVisibilityChange(int visibility) { - if (visibility == 0) { - updateSystemUiVisibility(); - } - } - }); - } + getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(new OnSystemUiVisibilityChangeListener() { + @Override + public void onSystemUiVisibilityChange(int visibility) { + if (visibility == 0) { + updateSystemUiVisibility(); + } + } + }); + } - @Override - protected void onStop() { - super.onStop(); - Log.i(TAG, "onStop - do nothing special"); - } + @Override + protected void onStop() { + super.onStop(); + Log.i(TAG, "onStop - do nothing special"); + } @Override protected void onDestroy() { @@ -687,45 +704,46 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C } } - @Override - protected void onPause() { + @Override + protected void onPause() { super.onPause(); Log.i(TAG, "onPause"); loseAudioFocus(this.audioManager, this.audioFocusChangeListener); NativeApp.pause(); - if (!javaGL) { + if (!javaGL) { mSurfaceView.onPause(); Log.i(TAG, "Joining render thread..."); joinRenderLoopThread(); Log.i(TAG, "Joined render thread"); - } else { + } else { if (mGLSurfaceView != null) { mGLSurfaceView.onPause(); } else { Log.e(TAG, "mGLSurfaceView really shouldn't be null in onPause"); } - } + } Log.i(TAG, "onPause completed"); } - private boolean detectOpenGLES20() { - ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); - ConfigurationInfo info = am.getDeviceConfigurationInfo(); - return info.reqGlEsVersion >= 0x20000; - } + private boolean detectOpenGLES20() { + ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); + ConfigurationInfo info = am.getDeviceConfigurationInfo(); + return info.reqGlEsVersion >= 0x20000; + } - private boolean detectOpenGLES30() { - ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); - ConfigurationInfo info = am.getDeviceConfigurationInfo(); - return info.reqGlEsVersion >= 0x30000; - } + private boolean detectOpenGLES30() { + ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); + ConfigurationInfo info = am.getDeviceConfigurationInfo(); + return info.reqGlEsVersion >= 0x30000; + } @Override protected void onResume() { super.onResume(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - updateSystemUiVisibility(); - } + updateSustainedPerformanceMode(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + updateSystemUiVisibility(); + } // OK, config should be initialized, we can query for screen rotation. if (javaGL || Build.VERSION.SDK_INT >= 9) { updateScreenRotation("onResume"); @@ -753,80 +771,79 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C } } - @Override - public void onConfigurationChanged(Configuration newConfig) { - Log.i(TAG, "onConfigurationChanged"); - super.onConfigurationChanged(newConfig); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - updateSystemUiVisibility(); - } - densityDpi = (float)newConfig.densityDpi; - } + @Override + public void onConfigurationChanged(Configuration newConfig) { + Log.i(TAG, "onConfigurationChanged"); + super.onConfigurationChanged(newConfig); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + updateSystemUiVisibility(); + } + densityDpi = (float) newConfig.densityDpi; + } - //keep this static so we can call this even if we don't - //instantiate NativeAudioPlayer + // keep this static so we can call this even if we don't + // instantiate NativeAudioPlayer public static void gainAudioFocus(AudioManager audioManager, AudioFocusChangeListener focusChangeListener) { if (audioManager != null) { - audioManager.requestAudioFocus(focusChangeListener, - AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); + audioManager.requestAudioFocus(focusChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); } } - //keep this static so we can call this even if we don't - //instantiate NativeAudioPlayer - public static void loseAudioFocus(AudioManager audioManager,AudioFocusChangeListener focusChangeListener){ + // keep this static so we can call this even if we don't + // instantiate NativeAudioPlayer + public static void loseAudioFocus(AudioManager audioManager, AudioFocusChangeListener focusChangeListener) { if (audioManager != null) { audioManager.abandonAudioFocus(focusChangeListener); } } - // We simply grab the first input device to produce an event and ignore all others that are connected. - @TargetApi(Build.VERSION_CODES.GINGERBREAD) + // We simply grab the first input device to produce an event and ignore all others that are connected. + @TargetApi(Build.VERSION_CODES.GINGERBREAD) private InputDeviceState getInputDeviceState(InputEvent event) { - InputDevice device = event.getDevice(); - if (device == null) { - return null; - } - if (inputPlayerA == null) { - inputPlayerADesc = getInputDesc(device); - Log.i(TAG, "Input player A registered: desc = " + inputPlayerADesc); - inputPlayerA = new InputDeviceState(device); - } + InputDevice device = event.getDevice(); + if (device == null) { + return null; + } + if (inputPlayerA == null) { + inputPlayerADesc = getInputDesc(device); + Log.i(TAG, "Input player A registered: desc = " + inputPlayerADesc); + inputPlayerA = new InputDeviceState(device); + } - if (inputPlayerA.getDevice() == device) { - return inputPlayerA; - } + if (inputPlayerA.getDevice() == device) { + return inputPlayerA; + } - if (inputPlayerB == null) { - Log.i(TAG, "Input player B registered: desc = " + getInputDesc(device)); - inputPlayerB = new InputDeviceState(device); - } + if (inputPlayerB == null) { + Log.i(TAG, "Input player B registered: desc = " + getInputDesc(device)); + inputPlayerB = new InputDeviceState(device); + } - if (inputPlayerB.getDevice() == device) { - return inputPlayerB; - } + if (inputPlayerB.getDevice() == device) { + return inputPlayerB; + } - if (inputPlayerC == null) { - Log.i(TAG, "Input player C registered"); - inputPlayerC = new InputDeviceState(device); - } + if (inputPlayerC == null) { + Log.i(TAG, "Input player C registered"); + inputPlayerC = new InputDeviceState(device); + } - if (inputPlayerC.getDevice() == device) { - return inputPlayerC; - } + if (inputPlayerC.getDevice() == device) { + return inputPlayerC; + } - return inputPlayerA; - } + return inputPlayerA; + } - public boolean IsXperiaPlay() { - return android.os.Build.MODEL.equals("R800a") || android.os.Build.MODEL.equals("R800i") || android.os.Build.MODEL.equals("R800x") || android.os.Build.MODEL.equals("R800at") || android.os.Build.MODEL.equals("SO-01D") || android.os.Build.MODEL.equals("zeus"); - } + public boolean IsXperiaPlay() { + return android.os.Build.MODEL.equals("R800a") || android.os.Build.MODEL.equals("R800i") || android.os.Build.MODEL.equals("R800x") || android.os.Build.MODEL.equals("R800at") || android.os.Build.MODEL.equals("SO-01D") || android.os.Build.MODEL.equals("zeus"); + } - // We grab the keys before onKeyDown/... even see them. This is also better because it lets us - // distinguish devices. - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 && !isXperiaPlay) { + // We grab the keys before onKeyDown/... even see them. This is also better because it lets us + // distinguish devices. + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 && !isXperiaPlay) { InputDeviceState state = getInputDeviceState(event); if (state == null) { return super.dispatchKeyEvent(event); @@ -869,14 +886,14 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C break; } } - } + } - // Let's go through the old path (onKeyUp, onKeyDown). + // Let's go through the old path (onKeyUp, onKeyDown). return super.dispatchKeyEvent(event); - } + } @TargetApi(16) - static public String getInputDesc(InputDevice input) { + public static String getInputDesc(InputDevice input) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { return input.getDescriptor(); } else { @@ -893,27 +910,27 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C public boolean onGenericMotionEvent(MotionEvent event) { // Log.d(TAG, "onGenericMotionEvent: " + event); if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) { - if (Build.VERSION.SDK_INT >= 12) { - InputDeviceState state = getInputDeviceState(event); - if (state == null) { - Log.w(TAG, "Joystick event but failed to get input device state."); - return super.onGenericMotionEvent(event); - } - state.onJoystickMotion(event); - return true; - } + if (Build.VERSION.SDK_INT >= 12) { + InputDeviceState state = getInputDeviceState(event); + if (state == null) { + Log.w(TAG, "Joystick event but failed to get input device state."); + return super.onGenericMotionEvent(event); + } + state.onJoystickMotion(event); + return true; + } } if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { - switch (event.getAction()) { - case MotionEvent.ACTION_HOVER_MOVE: - // process the mouse hover movement... - return true; - case MotionEvent.ACTION_SCROLL: - NativeApp.mouseWheelEvent(event.getX(), event.getY()); - return true; - } - } + switch (event.getAction()) { + case MotionEvent.ACTION_HOVER_MOVE: + // process the mouse hover movement... + return true; + case MotionEvent.ACTION_SCROLL: + NativeApp.mouseWheelEvent(event.getX(), event.getY()); + return true; + } + } return super.onGenericMotionEvent(event); } @@ -992,19 +1009,19 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C return NativeApp.keyUp(0, keyCode); } } + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == RESULT_LOAD_IMAGE && resultCode == RESULT_OK && null != data) { Uri selectedImage = data.getData(); - String[] filePathColumn = { MediaStore.Images.Media.DATA }; - Cursor cursor = getContentResolver().query(selectedImage, - filePathColumn, null, null, null); + String[] filePathColumn = {MediaStore.Images.Media.DATA}; + Cursor cursor = getContentResolver().query(selectedImage, filePathColumn, null, null, null); cursor.moveToFirst(); int columnIndex = cursor.getColumnIndex(filePathColumn[0]); String picturePath = cursor.getString(columnIndex); cursor.close(); - NativeApp.sendMessage("bgImage_updated" , picturePath); + NativeApp.sendMessage("bgImage_updated", picturePath); } } @@ -1047,17 +1064,17 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C // The return value is sent elsewhere. TODO in java, in SendMessage in C++. public void inputBox(final String title, String defaultText, String defaultAction) { - final FrameLayout fl = new FrameLayout(this); - final EditText input = new EditText(this); - input.setGravity(Gravity.CENTER); + final FrameLayout fl = new FrameLayout(this); + final EditText input = new EditText(this); + input.setGravity(Gravity.CENTER); - FrameLayout.LayoutParams editBoxLayout = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT); - editBoxLayout.setMargins(2, 20, 2, 20); - fl.addView(input, editBoxLayout); + FrameLayout.LayoutParams editBoxLayout = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT); + editBoxLayout.setMargins(2, 20, 2, 20); + fl.addView(input, editBoxLayout); - input.setInputType(InputType.TYPE_CLASS_TEXT); - input.setText(defaultText); - input.selectAll(); + input.setInputType(InputType.TYPE_CLASS_TEXT); + input.setText(defaultText); + input.selectAll(); // Lovely! AlertDialog.Builder bld; @@ -1072,30 +1089,31 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C else bld = createDialogBuilderNew(); - AlertDialog dlg = bld - .setView(fl) - .setTitle(title) - .setPositiveButton(defaultAction, new DialogInterface.OnClickListener(){ - @Override - public void onClick(DialogInterface d, int which) { - NativeApp.sendMessage("inputbox_completed", title + ":" + input.getText().toString()); - d.dismiss(); - } - }) - .setNegativeButton("Cancel", new DialogInterface.OnClickListener(){ - @Override - public void onClick(DialogInterface d, int which) { - NativeApp.sendMessage("inputbox_failed", ""); - d.cancel(); - } - }).create(); + AlertDialog dlg = bld + .setView(fl) + .setTitle(title) + .setPositiveButton(defaultAction, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface d, int which) { + NativeApp.sendMessage("inputbox_completed", title + ":" + input.getText().toString()); + d.dismiss(); + } + }) + .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface d, int which) { + NativeApp.sendMessage("inputbox_failed", ""); + d.cancel(); + } + }) + .create(); - dlg.setCancelable(true); - dlg.show(); - } + dlg.setCancelable(true); + dlg.show(); + } - public boolean processCommand(String command, String params) { - SurfaceView surfView = javaGL ? mGLSurfaceView : mSurfaceView; + public boolean processCommand(String command, String params) { + SurfaceView surfView = javaGL ? mGLSurfaceView : mSurfaceView; if (command.equals("launchBrowser")) { try { Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(params)); @@ -1110,24 +1128,22 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C try { Intent send = new Intent(Intent.ACTION_SENDTO); String uriText; - uriText = "mailto:email@gmail.com" + "?subject=Your app is..." - + "&body=great! Or?"; + uriText = "mailto:email@gmail.com" + "?subject=Your app is..." + "&body=great! Or?"; uriText = uriText.replace(" ", "%20"); Uri uri = Uri.parse(uriText); send.setData(uri); startActivity(Intent.createChooser(send, "E-mail the app author!")); return true; - } catch (Exception e) { // For example, android.content.ActivityNotFoundException + } catch (Exception e) { // For example, android.content.ActivityNotFoundException Log.e(TAG, e.toString()); return false; } } else if (command.equals("bgImage_browse")) { try { - Intent i = new Intent(Intent.ACTION_PICK, - android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI); + Intent i = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); startActivityForResult(i, RESULT_LOAD_IMAGE); return true; - } catch (Exception e) { // For example, android.content.ActivityNotFoundException + } catch (Exception e) { // For example, android.content.ActivityNotFoundException Log.e(TAG, e.toString()); return false; } @@ -1138,7 +1154,7 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C share.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + params)); startActivity(Intent.createChooser(share, "Share Picture")); return true; - } catch (Exception e) { // For example, android.content.ActivityNotFoundException + } catch (Exception e) { // For example, android.content.ActivityNotFoundException Log.e(TAG, e.toString()); return false; } @@ -1150,7 +1166,7 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C sendIntent.setAction(Intent.ACTION_SEND); startActivity(sendIntent); return true; - } catch (Exception e) { // For example, android.content.ActivityNotFoundException + } catch (Exception e) { // For example, android.content.ActivityNotFoundException Log.e(TAG, e.toString()); return false; } @@ -1158,16 +1174,12 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C try { String twitter_user_name = params; try { - startActivity(new Intent(Intent.ACTION_VIEW, - Uri.parse("twitter://user?screen_name=" - + twitter_user_name))); + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("twitter://user?screen_name=" + twitter_user_name))); } catch (Exception e) { - startActivity(new Intent( - Intent.ACTION_VIEW, - Uri.parse("https://twitter.com/#!/" + twitter_user_name))); + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://twitter.com/#!/" + twitter_user_name))); } return true; - } catch (Exception e) { // For example, android.content.ActivityNotFoundException + } catch (Exception e) { // For example, android.content.ActivityNotFoundException Log.e(TAG, e.toString()); return false; } @@ -1185,15 +1197,11 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); // No idea what the point of the ApplicationWindowToken is or if it // matters where we get it from... - inputMethodManager.toggleSoftInputFromWindow( - surfView.getApplicationWindowToken(), - InputMethodManager.SHOW_FORCED, 0); + inputMethodManager.toggleSoftInputFromWindow(surfView.getApplicationWindowToken(), InputMethodManager.SHOW_FORCED, 0); return true; } else if (command.equals("hideKeyboard") && surfView != null) { InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - inputMethodManager.toggleSoftInputFromWindow( - surfView.getApplicationWindowToken(), - InputMethodManager.SHOW_FORCED, 0); + inputMethodManager.toggleSoftInputFromWindow(surfView.getApplicationWindowToken(), InputMethodManager.SHOW_FORCED, 0); return true; } else if (command.equals("inputbox")) { String title = "Input"; @@ -1223,19 +1231,19 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C // permission. if (surfView != null) { switch (milliseconds) { - case -1: - surfView.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); - break; - case -2: - surfView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); - break; - case -3: - surfView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); - break; - default: - // Requires the vibrate permission, which we don't have, so disabled. - // vibrator.vibrate(milliseconds); - break; + case -1: + surfView.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); + break; + case -2: + surfView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); + break; + case -3: + surfView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); + break; + default: + // Requires the vibrate permission, which we don't have, so disabled. + // vibrator.vibrate(milliseconds); + break; } } else { Log.e(TAG, "Can't vibrate, no surface view"); @@ -1290,22 +1298,27 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C } else if (mCameraHelper != null && params.equals("stopVideo")) { mCameraHelper.stopCamera(); } + } else if (command.equals("uistate")) { + Window window = this.getWindow(); + if (params.equals("ingame")) { + // Keep the screen bright - very annoying if it goes dark when tilting away + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } else { + // Only keep the screen bright ingame. + window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } } - return false; - } + return false; + } - @SuppressLint("NewApi") + @SuppressLint("NewApi") @Override - public void recreate() - { - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) - { - super.recreate(); - } - else - { - startActivity(getIntent()); - finish(); - } - } + public void recreate() { + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + super.recreate(); + } else { + startActivity(getIntent()); + finish(); + } + } } diff --git a/android/src/org/ppsspp/ppsspp/NativeApp.java b/android/src/org/ppsspp/ppsspp/NativeApp.java index dd78133e0c..140f3edd59 100644 --- a/android/src/org/ppsspp/ppsspp/NativeApp.java +++ b/android/src/org/ppsspp/ppsspp/NativeApp.java @@ -1,17 +1,16 @@ package org.ppsspp.ppsspp; - // Note that the display* methods are in NativeRenderer.java public class NativeApp { - public final static int DEVICE_ID_DEFAULT = 0; - public final static int DEVICE_ID_KEYBOARD = 1; - public final static int DEVICE_ID_MOUSE = 2; - public final static int DEVICE_ID_PAD_0 = 10; + public static final int DEVICE_ID_DEFAULT = 0; + public static final int DEVICE_ID_KEYBOARD = 1; + public static final int DEVICE_ID_MOUSE = 2; + public static final int DEVICE_ID_PAD_0 = 10; - public final static int DEVICE_TYPE_MOBILE = 0; - public final static int DEVICE_TYPE_TV = 1; - public final static int DEVICE_TYPE_DESKTOP = 2; + public static final int DEVICE_TYPE_MOBILE = 0; + public static final int DEVICE_TYPE_TV = 1; + public static final int DEVICE_TYPE_DESKTOP = 2; public static native void init(String model, int deviceType, String languageRegion, String apkPath, String dataDir, String externalDir, String libraryDir, String cacheDir, String shortcutParam, int androidVersion, String board); public static native void audioInit(); @@ -56,4 +55,3 @@ public class NativeApp { public static native void pushCameraImage(byte[] image); } - diff --git a/android/src/org/ppsspp/ppsspp/NativeEGLConfigChooser.java b/android/src/org/ppsspp/ppsspp/NativeEGLConfigChooser.java index bdbca3c27d..1ec9ce5680 100644 --- a/android/src/org/ppsspp/ppsspp/NativeEGLConfigChooser.java +++ b/android/src/org/ppsspp/ppsspp/NativeEGLConfigChooser.java @@ -1,11 +1,10 @@ package org.ppsspp.ppsspp; -import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.egl.EGLDisplay; - import android.opengl.GLSurfaceView.EGLConfigChooser; import android.util.Log; +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLDisplay; public class NativeEGLConfigChooser implements EGLConfigChooser { private static final String TAG = "NativeEGLConfigChooser"; @@ -24,6 +23,7 @@ public class NativeEGLConfigChooser implements EGLConfigChooser { public int stencil; public int depth; public int samples; + public void Log() { Log.i(TAG, "EGLConfig: red=" + red + " green=" + green + " blue=" + blue + " alpha=" + alpha + " depth=" + depth + " stencil=" + stencil + " samples=" + samples); } @@ -79,22 +79,22 @@ public class NativeEGLConfigChooser implements EGLConfigChooser { }; int[] num_config = new int[1]; - if (!egl.eglChooseConfig(display, configSpec, null, 0, num_config)) { - throw new IllegalArgumentException("eglChooseConfig failed when counting"); - } + if (!egl.eglChooseConfig(display, configSpec, null, 0, num_config)) { + throw new IllegalArgumentException("eglChooseConfig failed when counting"); + } - int numConfigs = num_config[0]; - Log.i(TAG, "There are " + numConfigs + " egl configs"); - if (numConfigs <= 0) { - throw new IllegalArgumentException("No configs match configSpec"); - } + int numConfigs = num_config[0]; + Log.i(TAG, "There are " + numConfigs + " egl configs"); + if (numConfigs <= 0) { + throw new IllegalArgumentException("No configs match configSpec"); + } EGLConfig[] eglConfigs = new EGLConfig[numConfigs]; if (!egl.eglChooseConfig(display, configSpec, eglConfigs, numConfigs, num_config)) { - throw new IllegalArgumentException("eglChooseConfig failed when retrieving"); - } + throw new IllegalArgumentException("eglChooseConfig failed when retrieving"); + } - ConfigAttribs [] configs = getConfigAttribs(egl, display, eglConfigs); + ConfigAttribs[] configs = getConfigAttribs(egl, display, eglConfigs); ConfigAttribs chosen = null; @@ -103,7 +103,6 @@ public class NativeEGLConfigChooser implements EGLConfigChooser { configs[i].Log(); } - // We now ignore destination alpha as a workaround for the Mali issue // where we get badly composited if we use it. // Though, that may be possible to fix by using EGL10.EGL_TRANSPARENT_TYPE, EGL10.EGL_NONE. @@ -201,12 +200,11 @@ public class NativeEGLConfigChooser implements EGLConfigChooser { } if (chosen == null) { - throw new IllegalArgumentException("Failed to find a valid EGL config"); + throw new IllegalArgumentException("Failed to find a valid EGL config"); } Log.i(TAG, "Final chosen config: "); chosen.Log(); return chosen.config; } - } diff --git a/android/src/org/ppsspp/ppsspp/NativeGLView.java b/android/src/org/ppsspp/ppsspp/NativeGLView.java index 68c8a5082c..ce2e4be677 100644 --- a/android/src/org/ppsspp/ppsspp/NativeGLView.java +++ b/android/src/org/ppsspp/ppsspp/NativeGLView.java @@ -18,8 +18,6 @@ import android.os.Build; import android.os.Handler; import android.util.Log; import android.view.MotionEvent; -import android.view.View; - import com.bda.controller.*; public class NativeGLView extends GLSurfaceView implements SensorEventListener, ControllerListener { @@ -37,7 +35,7 @@ public class NativeGLView extends GLSurfaceView implements SensorEventListener, super(activity); mActivity = activity; - mSensorManager = (SensorManager)activity.getSystemService(Activity.SENSOR_SERVICE); + mSensorManager = (SensorManager) activity.getSystemService(Activity.SENSOR_SERVICE); mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); mController = Controller.getInstance(activity); @@ -89,7 +87,7 @@ public class NativeGLView extends GLSurfaceView implements SensorEventListener, if (code != 0) { if (canReadToolType) { int tool = getToolType(ev, i); - code |= tool << 10; // We use the Android tool type codes + code |= tool << 10; // We use the Android tool type codes } // Can't use || due to short circuit evaluation numTouchesHandled += NativeApp.touch(ev.getX(i), ev.getY(i), code, pid) ? 1 : 0; @@ -161,7 +159,7 @@ public class NativeGLView extends GLSurfaceView implements SensorEventListener, } } - boolean repeat = false; // Moga has no repeats? + boolean repeat = false; // Moga has no repeats? switch (event.getAction()) { case KeyEvent.ACTION_DOWN: NativeApp.keyDown(NativeApp.DEVICE_ID_PAD_0, event.getKeyCode(), repeat); diff --git a/android/src/org/ppsspp/ppsspp/NativeRenderer.java b/android/src/org/ppsspp/ppsspp/NativeRenderer.java index 288beba61a..2433b2b637 100644 --- a/android/src/org/ppsspp/ppsspp/NativeRenderer.java +++ b/android/src/org/ppsspp/ppsspp/NativeRenderer.java @@ -1,15 +1,10 @@ package org.ppsspp.ppsspp; +import android.opengl.GLSurfaceView; +import android.util.Log; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; -import android.graphics.Point; -import android.opengl.GLES20; -import android.opengl.GLSurfaceView; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.Display; - public class NativeRenderer implements GLSurfaceView.Renderer { private static String TAG = "NativeRenderer"; private NativeActivity mActivity; @@ -35,12 +30,13 @@ public class NativeRenderer implements GLSurfaceView.Renderer { // Actually, it seems that it is here we should recreate lost GL objects. displayInit(); } - + public void onSurfaceChanged(GL10 unused, int width, int height) { } // Note: This also means "device lost" and you should reload - // all buffered objects. - public native void displayInit(); + // all buffered objects. + public native void displayInit(); + public native void displayRender(); } diff --git a/android/src/org/ppsspp/ppsspp/NativeSurfaceView.java b/android/src/org/ppsspp/ppsspp/NativeSurfaceView.java index 1e13dba115..d09952b6b5 100644 --- a/android/src/org/ppsspp/ppsspp/NativeSurfaceView.java +++ b/android/src/org/ppsspp/ppsspp/NativeSurfaceView.java @@ -13,12 +13,9 @@ import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.Build; import android.os.Handler; -// import android.os.Build; -// import android.util.Log; import android.util.Log; import android.view.MotionEvent; import android.view.SurfaceView; - import com.bda.controller.*; public class NativeSurfaceView extends SurfaceView implements SensorEventListener, ControllerListener { @@ -35,7 +32,7 @@ public class NativeSurfaceView extends SurfaceView implements SensorEventListene Log.i(TAG, "NativeSurfaceView"); - mSensorManager = (SensorManager)activity.getSystemService(Activity.SENSOR_SERVICE); + mSensorManager = (SensorManager) activity.getSystemService(Activity.SENSOR_SERVICE); mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); mController = Controller.getInstance(activity); @@ -62,8 +59,6 @@ public class NativeSurfaceView extends SurfaceView implements SensorEventListene boolean canReadToolType = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH; int numTouchesHandled = 0; - //float scaleX = (float)mActivity.getRenderer().getDpiScaleX(); - //float scaleY = (float)mActivity.getRenderer().getDpiScaleY(); for (int i = 0; i < ev.getPointerCount(); i++) { int pid = ev.getPointerId(i); int code = 0; @@ -92,7 +87,7 @@ public class NativeSurfaceView extends SurfaceView implements SensorEventListene if (code != 0) { if (canReadToolType) { int tool = getToolType(ev, i); - code |= tool << 10; // We use the Android tool type codes + code |= tool << 10; // We use the Android tool type codes } // Can't use || due to short circuit evaluation numTouchesHandled += NativeApp.touch(ev.getX(i), ev.getY(i), code, pid) ? 1 : 0; @@ -157,7 +152,7 @@ public class NativeSurfaceView extends SurfaceView implements SensorEventListene } } - boolean repeat = false; // Moga has no repeats? + boolean repeat = false; // Moga has no repeats? switch (event.getAction()) { case KeyEvent.ACTION_DOWN: NativeApp.keyDown(NativeApp.DEVICE_ID_PAD_0, event.getKeyCode(), repeat); diff --git a/android/src/org/ppsspp/ppsspp/PowerSaveModeReceiver.java b/android/src/org/ppsspp/ppsspp/PowerSaveModeReceiver.java index 7620ad3322..69b396dfa8 100644 --- a/android/src/org/ppsspp/ppsspp/PowerSaveModeReceiver.java +++ b/android/src/org/ppsspp/ppsspp/PowerSaveModeReceiver.java @@ -55,7 +55,7 @@ public class PowerSaveModeReceiver extends BroadcastReceiver { @TargetApi(21) private static boolean getNativePowerSaving(final Context context) { - final PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); + final PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); return pm.isPowerSaveMode(); } diff --git a/android/src/org/ppsspp/ppsspp/PpssppActivity.java b/android/src/org/ppsspp/ppsspp/PpssppActivity.java index 98141c4a22..f96705dd97 100644 --- a/android/src/org/ppsspp/ppsspp/PpssppActivity.java +++ b/android/src/org/ppsspp/ppsspp/PpssppActivity.java @@ -7,7 +7,6 @@ import android.os.Build; import android.os.Bundle; import android.os.Looper; import android.util.Log; -import android.widget.Toast; public class PpssppActivity extends NativeActivity { private static final String TAG = "PpssppActivity"; @@ -58,7 +57,6 @@ public class PpssppActivity extends NativeActivity { } Looper.loop(); } - }.start(); try { @@ -95,7 +93,6 @@ public class PpssppActivity extends NativeActivity { super.onCreate(savedInstanceState); } - // called by the C++ code through JNI. Dispatch anything we can't directly handle // on the gfx thread to the UI thread. public void postCommand(String command, String parameter) { diff --git a/android/src/org/ppsspp/ppsspp/ShortcutActivity.java b/android/src/org/ppsspp/ppsspp/ShortcutActivity.java index dde0eeb703..8b33203d7e 100644 --- a/android/src/org/ppsspp/ppsspp/ShortcutActivity.java +++ b/android/src/org/ppsspp/ppsspp/ShortcutActivity.java @@ -1,7 +1,5 @@ package org.ppsspp.ppsspp; -import java.io.File; - import android.app.Activity; import android.app.AlertDialog; import android.content.Intent; @@ -11,10 +9,11 @@ import android.os.Bundle; import android.os.Environment; import android.os.Looper; import android.util.Log; +import java.io.File; /** - * This class will respond to android.intent.action.CREATE_SHORTCUT intent from - * launcher homescreen. Register this class in AndroidManifest.xml. + * This class will respond to android.intent.action.CREATE_SHORTCUT intent from launcher homescreen. + * Register this class in AndroidManifest.xml. */ public class ShortcutActivity extends Activity { private static final String TAG = "PPSSPP"; @@ -24,9 +23,7 @@ public class ShortcutActivity extends Activity { super.onCreate(savedInstanceState); // Show file selector dialog here. - SimpleFileChooser fileDialog = new SimpleFileChooser(this, - Environment.getExternalStorageDirectory(), - onFileSelectedListener); + SimpleFileChooser fileDialog = new SimpleFileChooser(this, Environment.getExternalStorageDirectory(), onFileSelectedListener); fileDialog.showDialog(); } @@ -56,10 +53,8 @@ public class ShortcutActivity extends Activity { Intent responseIntent = new Intent(); responseIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); responseIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name); - ShortcutIconResource iconResource = Intent.ShortcutIconResource - .fromContext(this, R.drawable.ic_launcher); - responseIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, - iconResource); + ShortcutIconResource iconResource = ShortcutIconResource.fromContext(this, R.drawable.ic_launcher); + responseIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource); setResult(RESULT_OK, responseIntent); @@ -97,5 +92,4 @@ public class ShortcutActivity extends Activity { respondToShortcutRequest(file.getAbsolutePath()); } }; - } diff --git a/android/src/org/ppsspp/ppsspp/SimpleFileChooser.java b/android/src/org/ppsspp/ppsspp/SimpleFileChooser.java index 9e73fbb119..bee2c4bc16 100644 --- a/android/src/org/ppsspp/ppsspp/SimpleFileChooser.java +++ b/android/src/org/ppsspp/ppsspp/SimpleFileChooser.java @@ -1,21 +1,17 @@ package org.ppsspp.ppsspp; +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.os.Environment; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; -import android.app.Activity; -import android.app.AlertDialog; -import android.content.DialogInterface; -import android.os.Environment; - -/** - * Simple dialog to pick file. - */ +/** Simple dialog to pick file. */ public class SimpleFileChooser { - public interface FileSelectedListener { void onFileSelected(File file); } @@ -27,8 +23,7 @@ public class SimpleFileChooser { private String[] mFileList; private File mCurrentPath; - public SimpleFileChooser(Activity activity, File path, - FileSelectedListener listener) { + public SimpleFileChooser(Activity activity, File path, FileSelectedListener listener) { this.mActivity = activity; this.mFileListener = listener; if (!path.exists()) @@ -83,8 +78,7 @@ public class SimpleFileChooser { return 1; else // when both are folders or both are files, sort by name - return file1.getName().toUpperCase() - .compareTo(file2.getName().toUpperCase()); + return file1.getName().toUpperCase().compareTo(file2.getName().toUpperCase()); } }; @@ -107,5 +101,4 @@ public class SimpleFileChooser { } } }; - } diff --git a/android/src/org/ppsspp/ppsspp/TextRenderer.java b/android/src/org/ppsspp/ppsspp/TextRenderer.java index e2e4c37bef..69d411e32d 100644 --- a/android/src/org/ppsspp/ppsspp/TextRenderer.java +++ b/android/src/org/ppsspp/ppsspp/TextRenderer.java @@ -1,21 +1,22 @@ package org.ppsspp.ppsspp; + import android.content.Context; import android.graphics.*; import android.util.Log; -import java.nio.ByteBuffer; - public class TextRenderer { private static Paint p; private static Paint bg; private static Typeface robotoCondensed; private static final String TAG = "TextRenderer"; + static { p = new Paint(Paint.SUBPIXEL_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG); p.setColor(Color.WHITE); bg = new Paint(); bg.setColor(Color.BLACK); } + public static void init(Context ctx) { robotoCondensed = Typeface.createFromAsset(ctx.getAssets(), "Roboto-Condensed.ttf"); if (robotoCondensed != null) { @@ -25,23 +26,25 @@ public class TextRenderer { Log.e(TAG, "Failed to load Roboto Condensed"); } } + private static Point measureLine(String string, double textSize) { int w; if (string.length() > 0) { - p.setTextSize((float)textSize); - w = (int)p.measureText(string); - // Round width up to even already here to avoid annoyances from odd-width 16-bit textures which - // OpenGL does not like - each line must be 4-byte aligned + p.setTextSize((float) textSize); + w = (int) p.measureText(string); + // Round width up to even already here to avoid annoyances from odd-width 16-bit textures + // which OpenGL does not like - each line must be 4-byte aligned w = (w + 5) & ~1; } else { w = 1; } - int h = (int)(p.descent() - p.ascent() + 2.0f); + int h = (int) (p.descent() - p.ascent() + 2.0f); Point p = new Point(); p.x = w; p.y = h; return p; } + private static Point measure(String string, double textSize) { String lines[] = string.replaceAll("\\r", "").split("\n"); Point total = new Point(); @@ -50,13 +53,15 @@ public class TextRenderer { Point sz = measureLine(line, textSize); total.x = Math.max(sz.x, total.x); } - total.y = (int)(p.descent() - p.ascent()) * lines.length + 2; + total.y = (int) (p.descent() - p.ascent()) * lines.length + 2; return total; } + public static int measureText(String string, double textSize) { Point s = measure(string, textSize); return (s.x << 16) | s.y; } + public static int[] renderText(String string, double textSize) { Point s = measure(string, textSize); @@ -79,7 +84,7 @@ public class TextRenderer { y += p.descent() - p.ascent(); } - int [] pixels = new int[w * h]; + int[] pixels = new int[w * h]; bmp.getPixels(pixels, 0, w, 0, 0, w, h); bmp.recycle(); return pixels; diff --git a/assets/gamecontrollerdb.txt b/assets/gamecontrollerdb.txt index 7201505468..aa97cd20a3 100644 --- a/assets/gamecontrollerdb.txt +++ b/assets/gamecontrollerdb.txt @@ -138,6 +138,7 @@ bd1200000000000015d0000000000000,Tomee SNES USB Controller,x:b3,a:b2,b:b1,y:b0,b 030000000d0f00002200000011010000,HORI CO. LTD. REAL ARCADE Pro.V3,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1, 030000000d0f00001000000011010000,HORI CO. LTD. FIGHTING STICK 3,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7 03000000f0250000c183000010010000,Goodbetterbest Ltd USB Controller,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3, +03000000f0250000c283000010010000,Goodbetterbest Ltd PC USB Controller,platform:Linux,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7 0000000058626f782047616d65706100,Xbox Gamepad (userspace driver),platform:Linux,a:b0,b:b1,x:b2,y:b3,start:b7,back:b6,guide:b8,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftshoulder:b4,rightshoulder:b5,lefttrigger:a5,righttrigger:a4,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a2,righty:a3, 03000000ff1100003133000010010000,PC Game Controller,a:b2,b:b1,y:b0,x:b3,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Linux, 030000005e0400008e02000020200000,SpeedLink XEOX Pro Analog Gamepad pad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4, diff --git a/assets/lang b/assets/lang index 44a654736d..1153a42930 160000 --- a/assets/lang +++ b/assets/lang @@ -1 +1 @@ -Subproject commit 44a654736d67b36a55171b1862d41df2dc19cb25 +Subproject commit 1153a4293006db9d2a6e701df72df843b98234b5 diff --git a/build.gradle b/build.gradle index 277c10f09b..b32f870af1 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' + classpath 'com.android.tools.build:gradle:3.1.3' } } diff --git a/cmake/Toolchains/vero4k.armv8.cmake b/cmake/Toolchains/vero4k.armv8.cmake new file mode 100644 index 0000000000..28720127b3 --- /dev/null +++ b/cmake/Toolchains/vero4k.armv8.cmake @@ -0,0 +1,22 @@ +include_directories(SYSTEM + /opt/vero3/include + /opt/vero3/include/EGL + /opt/vero3/include/GLES2 +) + +set(ARCH_FLAGS "-march=armv8-a+crc -mtune=cortex-a53 -mfloat-abi=hard -funsafe-math-optimizations") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${ARCH_FLAGS}" CACHE STRING "" FORCE) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${ARCH_FLAGS}" CACHE STRING "" FORCE) +set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} ${ARCH_FLAGS}" CACHE STRING "" FORCE) + +set(CMAKE_EXE_LINKER_FLAGS "-L/opt/vero3/lib" CACHE STRING "" FORCE) + +set(OPENGL_LIBRARIES /opt/vero3/lib/libGLESv2.so) +set(EGL_LIBRARIES /opt/vero3/lib/libEGL.so) + +set(USING_GLES2 ON) +set(USING_EGL ON) +set(USING_FBDEV ON) +set(ARMV7 ON) +set(FORCED_CPU armv7) +set(USING_X11_VULKAN OFF CACHE BOOL "" FORCE) diff --git a/ext/armips b/ext/armips index 8b4cadaf62..9efe336728 160000 --- a/ext/armips +++ b/ext/armips @@ -1 +1 @@ -Subproject commit 8b4cadaf62d7de42d374056fc6aafc555f2bc7dc +Subproject commit 9efe3367284d2d1eeb14fc302a2c12c36e3e255e diff --git a/ext/cmake/armips/CMakeLists.txt b/ext/cmake/armips/CMakeLists.txt index 3a1689334a..96ef055b47 100644 --- a/ext/cmake/armips/CMakeLists.txt +++ b/ext/cmake/armips/CMakeLists.txt @@ -78,8 +78,10 @@ add_library(armips ${ARMIPS_PATH}/Archs/ARM/ArmOpcodes.h ${ARMIPS_PATH}/Archs/ARM/ArmParser.cpp ${ARMIPS_PATH}/Archs/ARM/ArmParser.h - ${ARMIPS_PATH}/Archs/ARM/ArmRelocator.cpp - ${ARMIPS_PATH}/Archs/ARM/ArmRelocator.h + ${ARMIPS_PATH}/Archs/ARM/ArmElfRelocator.cpp + ${ARMIPS_PATH}/Archs/ARM/ArmElfRelocator.h + ${ARMIPS_PATH}/Archs/ARM/ArmExpressionFunctions.cpp + ${ARMIPS_PATH}/Archs/ARM/ArmExpressionFunctions.h ${ARMIPS_PATH}/Archs/ARM/CArmInstruction.cpp ${ARMIPS_PATH}/Archs/ARM/CArmInstruction.h ${ARMIPS_PATH}/Archs/ARM/CThumbInstruction.cpp @@ -95,6 +97,10 @@ add_library(armips ${ARMIPS_PATH}/Archs/MIPS/Mips.h ${ARMIPS_PATH}/Archs/MIPS/MipsElfFile.cpp ${ARMIPS_PATH}/Archs/MIPS/MipsElfFile.h + ${ARMIPS_PATH}/Archs/MIPS/MipsElfRelocator.cpp + ${ARMIPS_PATH}/Archs/MIPS/MipsElfRelocator.h + ${ARMIPS_PATH}/Archs/MIPS/MipsExpressionFunctions.cpp + ${ARMIPS_PATH}/Archs/MIPS/MipsExpressionFunctions.h ${ARMIPS_PATH}/Archs/MIPS/MipsMacros.cpp ${ARMIPS_PATH}/Archs/MIPS/MipsMacros.h ${ARMIPS_PATH}/Archs/MIPS/MipsOpcodes.cpp diff --git a/ext/libarmips.vcxproj b/ext/libarmips.vcxproj index 4fbff29f20..688f859d77 100644 --- a/ext/libarmips.vcxproj +++ b/ext/libarmips.vcxproj @@ -234,9 +234,10 @@ + + - @@ -244,6 +245,8 @@ + + @@ -287,9 +290,10 @@ + + - @@ -297,6 +301,8 @@ + + diff --git a/ext/libarmips.vcxproj.filters b/ext/libarmips.vcxproj.filters index bb35b6fa4f..e1c8ef9a3a 100644 --- a/ext/libarmips.vcxproj.filters +++ b/ext/libarmips.vcxproj.filters @@ -42,9 +42,6 @@ Archs\ARM - - Archs\ARM - Archs\ARM @@ -163,6 +160,18 @@ Core + + Archs\ARM + + + Archs\MIPS + + + Archs\ARM + + + Archs\MIPS + @@ -174,9 +183,6 @@ Archs\ARM - - Archs\ARM - Archs\ARM @@ -301,5 +307,17 @@ Core + + Archs\ARM + + + Archs\MIPS + + + Archs\ARM + + + Archs\MIPS + \ No newline at end of file diff --git a/ext/native/Android.mk b/ext/native/Android.mk index 78a75db9d4..b878bf1463 100644 --- a/ext/native/Android.mk +++ b/ext/native/Android.mk @@ -13,6 +13,7 @@ LOCAL_SRC_FILES :=\ base/timeutil.cpp \ base/colorutil.cpp \ base/stringutil.cpp \ + data/base64.cpp \ data/compression.cpp \ ext/rg_etc1/rg_etc1.cpp \ ext/cityhash/city.cpp \ @@ -35,8 +36,7 @@ LOCAL_SRC_FILES :=\ ext/jpge/jpgd.cpp \ ext/jpge/jpge.cpp \ ext/sha1/sha1.cpp \ - ext/vjson/json.cpp \ - ext/vjson/block_allocator.cpp \ + ext/gason/gason.cpp \ file/fd_util.cpp \ file/chunk_file.cpp \ file/file_util.cpp \ @@ -44,6 +44,7 @@ LOCAL_SRC_FILES :=\ file/path.cpp \ file/ini_file.cpp \ file/zip_read.cpp \ + json/json_reader.cpp \ json/json_writer.cpp \ i18n/i18n.cpp \ input/gesture_detector.cpp \ @@ -64,6 +65,7 @@ LOCAL_SRC_FILES :=\ net/resolve.cpp \ net/sinks.cpp \ net/url.cpp \ + net/websocket_server.cpp \ profiler/profiler.cpp \ thread/executor.cpp \ thread/threadutil.cpp \ diff --git a/ext/native/base/NativeApp.h b/ext/native/base/NativeApp.h index 36ef2d3d8b..39638436ab 100644 --- a/ext/native/base/NativeApp.h +++ b/ext/native/base/NativeApp.h @@ -45,7 +45,7 @@ bool NativeIsAtTopLevel(); // The very first function to be called after NativeGetAppInfo. Even NativeMix is not called // before this, although it may be called at any point in time afterwards (on any thread!) // This functions must NOT call OpenGL. Main thread. -void NativeInit(int argc, const char *argv[], const char *savegame_dir, const char *external_dir, const char *cache_dir, bool fs=false); +void NativeInit(int argc, const char *argv[], const char *savegame_dir, const char *external_dir, const char *cache_dir); // Runs after NativeInit() at some point. May (and probably should) call OpenGL. // Should not initialize anything screen-size-dependent - do that in NativeResized. diff --git a/ext/native/base/logging.h b/ext/native/base/logging.h index 41e6ab519a..0e5ac59def 100644 --- a/ext/native/base/logging.h +++ b/ext/native/base/logging.h @@ -102,7 +102,6 @@ const char *GetFn(const char *fn); #define ELOG(...) XLOG_IMPL("E", __VA_ARGS__) #define FLOG(...) do {XLOG_IMPL("F", __VA_ARGS__); Crash();} while (false) -// TODO: Win32 version using OutputDebugString #else #include diff --git a/ext/native/data/base64.cpp b/ext/native/data/base64.cpp new file mode 100644 index 0000000000..8df7abaa1e --- /dev/null +++ b/ext/native/data/base64.cpp @@ -0,0 +1,35 @@ +#include "data/base64.h" + +// TODO: This is a simple but not very efficient implementation. +std::string Base64Encode(const uint8_t *p, size_t sz) { + const char digits[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + size_t unpaddedLength = (4 * sz + 2) / 3; + std::string result; + result.resize((unpaddedLength + 3) & ~3, '='); + + for (size_t i = 0; i < unpaddedLength; ++i) { + // This is the index into the original string. + size_t pos = (i * 3) / 4; + int8_t off = 2 * ((i * 3) % 4); + + int c = p[pos]; + if (off > 2) { + c <<= 8; + off -= 8; + + // Grab more bits from the next character. + if (pos + 1 < sz) { + c |= p[pos + 1]; + } + } + + // Since we take from the big end, off starts at 2 and goes down. + int8_t shift = 2 - off; + + // Now take the bits at off and encode the character. + result[i] = digits[(c >> shift) & 0x3F]; + } + + return result; +} diff --git a/ext/native/data/base64.h b/ext/native/data/base64.h new file mode 100644 index 0000000000..b963bfb391 --- /dev/null +++ b/ext/native/data/base64.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +std::string Base64Encode(const uint8_t *p, size_t sz); diff --git a/ext/native/ext/cityhash/city.cpp b/ext/native/ext/cityhash/city.cpp index 6b5d4726d2..0428da728c 100644 --- a/ext/native/ext/cityhash/city.cpp +++ b/ext/native/ext/cityhash/city.cpp @@ -27,9 +27,6 @@ // possible hash functions, by using SIMD instructions, or by // compromising on hash quality. -#ifdef __ANDROID__ -#undef __SSE4_2__ -#endif #include "city.h" #include @@ -521,7 +518,7 @@ uint128 CityHash128(const char *s, size_t len) { CityHash128WithSeed(s, len, uint128(k0, k1)); } -#ifdef __SSE4_2__ +#if defined(__x86_64__) && defined(__SSE4_2__) #include "citycrc.h" #include diff --git a/ext/native/ext/gason/LICENSE b/ext/native/ext/gason/LICENSE new file mode 100644 index 0000000000..0c4133a47e --- /dev/null +++ b/ext/native/ext/gason/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013-2015 Ivan Vashchaev + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/ext/native/ext/gason/gason.cpp b/ext/native/ext/gason/gason.cpp new file mode 100644 index 0000000000..c3ce4efec7 --- /dev/null +++ b/ext/native/ext/gason/gason.cpp @@ -0,0 +1,334 @@ +#include "gason.h" +#include + +#define JSON_ZONE_SIZE 4096 +#define JSON_STACK_SIZE 32 + +const char *jsonStrError(int err) { + switch (err) { +#define XX(no, str) \ + case JSON_##no: \ + return str; + JSON_ERRNO_MAP(XX) +#undef XX + default: + return "unknown"; + } +} + +void *JsonAllocator::allocate(size_t size) { + size = (size + 7) & ~7; + + if (head && head->used + size <= JSON_ZONE_SIZE) { + char *p = (char *)head + head->used; + head->used += size; + return p; + } + + size_t allocSize = sizeof(Zone) + size; + Zone *zone = (Zone *)malloc(allocSize <= JSON_ZONE_SIZE ? JSON_ZONE_SIZE : allocSize); + if (zone == nullptr) + return nullptr; + zone->used = allocSize; + if (allocSize <= JSON_ZONE_SIZE || head == nullptr) { + zone->next = head; + head = zone; + } else { + zone->next = head->next; + head->next = zone; + } + return (char *)zone + sizeof(Zone); +} + +void JsonAllocator::deallocate() { + while (head) { + Zone *next = head->next; + free(head); + head = next; + } +} + +static inline bool isspace(char c) { + return c == ' ' || (c >= '\t' && c <= '\r'); +} + +static inline bool isdelim(char c) { + return c == ',' || c == ':' || c == ']' || c == '}' || isspace(c) || !c; +} + +static inline bool isdigit(char c) { + return c >= '0' && c <= '9'; +} + +static inline bool isxdigit(char c) { + return (c >= '0' && c <= '9') || ((c & ~' ') >= 'A' && (c & ~' ') <= 'F'); +} + +static inline int char2int(char c) { + if (c <= '9') + return c - '0'; + return (c & ~' ') - 'A' + 10; +} + +static double string2double(char *s, char **endptr) { + char ch = *s; + if (ch == '-') + ++s; + + double result = 0; + while (isdigit(*s)) + result = (result * 10) + (*s++ - '0'); + + if (*s == '.') { + ++s; + + double fraction = 1; + while (isdigit(*s)) { + fraction *= 0.1; + result += (*s++ - '0') * fraction; + } + } + + if (*s == 'e' || *s == 'E') { + ++s; + + double base = 10; + if (*s == '+') + ++s; + else if (*s == '-') { + ++s; + base = 0.1; + } + + unsigned int exponent = 0; + while (isdigit(*s)) + exponent = (exponent * 10) + (*s++ - '0'); + + double power = 1; + for (; exponent; exponent >>= 1, base *= base) + if (exponent & 1) + power *= base; + + result *= power; + } + + *endptr = s; + return ch == '-' ? -result : result; +} + +static inline JsonNode *insertAfter(JsonNode *tail, JsonNode *node) { + if (!tail) + return node->next = node; + node->next = tail->next; + tail->next = node; + return node; +} + +static inline JsonValue listToValue(JsonTag tag, JsonNode *tail) { + if (tail) { + auto head = tail->next; + tail->next = nullptr; + return JsonValue(tag, head); + } + return JsonValue(tag, nullptr); +} + +int jsonParse(char *s, char **endptr, JsonValue *value, JsonAllocator &allocator) { + JsonNode *tails[JSON_STACK_SIZE]; + JsonTag tags[JSON_STACK_SIZE]; + char *keys[JSON_STACK_SIZE]; + JsonValue o; + int pos = -1; + bool separator = true; + JsonNode *node; + *endptr = s; + + while (*s) { + while (isspace(*s)) { + ++s; + if (!*s) break; + } + *endptr = s++; + switch (**endptr) { + case '-': + if (!isdigit(*s) && *s != '.') { + *endptr = s; + return JSON_BAD_NUMBER; + } + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + o = JsonValue(string2double(*endptr, &s)); + if (!isdelim(*s)) { + *endptr = s; + return JSON_BAD_NUMBER; + } + break; + case '"': + o = JsonValue(JSON_STRING, s); + for (char *it = s; *s; ++it, ++s) { + int c = *it = *s; + if (c == '\\') { + c = *++s; + switch (c) { + case '\\': + case '"': + case '/': + *it = c; + break; + case 'b': + *it = '\b'; + break; + case 'f': + *it = '\f'; + break; + case 'n': + *it = '\n'; + break; + case 'r': + *it = '\r'; + break; + case 't': + *it = '\t'; + break; + case 'u': + c = 0; + for (int i = 0; i < 4; ++i) { + if (isxdigit(*++s)) { + c = c * 16 + char2int(*s); + } else { + *endptr = s; + return JSON_BAD_STRING; + } + } + if (c < 0x80) { + *it = c; + } else if (c < 0x800) { + *it++ = 0xC0 | (c >> 6); + *it = 0x80 | (c & 0x3F); + } else { + *it++ = 0xE0 | (c >> 12); + *it++ = 0x80 | ((c >> 6) & 0x3F); + *it = 0x80 | (c & 0x3F); + } + break; + default: + *endptr = s; + return JSON_BAD_STRING; + } + } else if ((unsigned int)c < ' ' || c == '\x7F') { + *endptr = s; + return JSON_BAD_STRING; + } else if (c == '"') { + *it = 0; + ++s; + break; + } + } + if (!isdelim(*s)) { + *endptr = s; + return JSON_BAD_STRING; + } + break; + case 't': + if (!(s[0] == 'r' && s[1] == 'u' && s[2] == 'e' && isdelim(s[3]))) + return JSON_BAD_IDENTIFIER; + o = JsonValue(JSON_TRUE); + s += 3; + break; + case 'f': + if (!(s[0] == 'a' && s[1] == 'l' && s[2] == 's' && s[3] == 'e' && isdelim(s[4]))) + return JSON_BAD_IDENTIFIER; + o = JsonValue(JSON_FALSE); + s += 4; + break; + case 'n': + if (!(s[0] == 'u' && s[1] == 'l' && s[2] == 'l' && isdelim(s[3]))) + return JSON_BAD_IDENTIFIER; + o = JsonValue(JSON_NULL); + s += 3; + break; + case ']': + if (pos == -1) + return JSON_STACK_UNDERFLOW; + if (tags[pos] != JSON_ARRAY) + return JSON_MISMATCH_BRACKET; + o = listToValue(JSON_ARRAY, tails[pos--]); + break; + case '}': + if (pos == -1) + return JSON_STACK_UNDERFLOW; + if (tags[pos] != JSON_OBJECT) + return JSON_MISMATCH_BRACKET; + if (keys[pos] != nullptr) + return JSON_UNEXPECTED_CHARACTER; + o = listToValue(JSON_OBJECT, tails[pos--]); + break; + case '[': + if (++pos == JSON_STACK_SIZE) + return JSON_STACK_OVERFLOW; + tails[pos] = nullptr; + tags[pos] = JSON_ARRAY; + keys[pos] = nullptr; + separator = true; + continue; + case '{': + if (++pos == JSON_STACK_SIZE) + return JSON_STACK_OVERFLOW; + tails[pos] = nullptr; + tags[pos] = JSON_OBJECT; + keys[pos] = nullptr; + separator = true; + continue; + case ':': + if (separator || keys[pos] == nullptr) + return JSON_UNEXPECTED_CHARACTER; + separator = true; + continue; + case ',': + if (separator || keys[pos] != nullptr) + return JSON_UNEXPECTED_CHARACTER; + separator = true; + continue; + case '\0': + continue; + default: + return JSON_UNEXPECTED_CHARACTER; + } + + separator = false; + + if (pos == -1) { + *endptr = s; + *value = o; + return JSON_OK; + } + + if (tags[pos] == JSON_OBJECT) { + if (!keys[pos]) { + if (o.getTag() != JSON_STRING) + return JSON_UNQUOTED_KEY; + keys[pos] = o.toString(); + continue; + } + if ((node = (JsonNode *) allocator.allocate(sizeof(JsonNode))) == nullptr) + return JSON_ALLOCATION_FAILURE; + tails[pos] = insertAfter(tails[pos], node); + tails[pos]->key = keys[pos]; + keys[pos] = nullptr; + } else { + if ((node = (JsonNode *) allocator.allocate(sizeof(JsonNode) - sizeof(char *))) == nullptr) + return JSON_ALLOCATION_FAILURE; + tails[pos] = insertAfter(tails[pos], node); + } + tails[pos]->value = o; + } + return JSON_BREAKING_BAD; +} diff --git a/ext/native/ext/gason/gason.h b/ext/native/ext/gason/gason.h new file mode 100644 index 0000000000..7c3eed96f6 --- /dev/null +++ b/ext/native/ext/gason/gason.h @@ -0,0 +1,135 @@ +#pragma once + +#include +#include +#include + +enum JsonTag { + JSON_NUMBER = 0, + JSON_STRING, + JSON_ARRAY, + JSON_OBJECT, + JSON_TRUE, + JSON_FALSE, + JSON_NULL = 0xF +}; + +struct JsonNode; + +#define JSON_VALUE_PAYLOAD_MASK 0x00007FFFFFFFFFFFULL +#define JSON_VALUE_NAN_MASK 0x7FF8000000000000ULL +#define JSON_VALUE_TAG_MASK 0xF +#define JSON_VALUE_TAG_SHIFT 47 + +union JsonValue { + uint64_t ival; + double fval; + + JsonValue(double x) + : fval(x) { + } + JsonValue(JsonTag tag = JSON_NULL, void *payload = nullptr) { + assert((uintptr_t)payload <= JSON_VALUE_PAYLOAD_MASK); + ival = JSON_VALUE_NAN_MASK | ((uint64_t)tag << JSON_VALUE_TAG_SHIFT) | (uintptr_t)payload; + } + bool isDouble() const { + return (int64_t)ival <= (int64_t)JSON_VALUE_NAN_MASK; + } + JsonTag getTag() const { + return isDouble() ? JSON_NUMBER : JsonTag((ival >> JSON_VALUE_TAG_SHIFT) & JSON_VALUE_TAG_MASK); + } + uint64_t getPayload() const { + assert(!isDouble()); + return ival & JSON_VALUE_PAYLOAD_MASK; + } + double toNumber() const { + assert(getTag() == JSON_NUMBER); + return fval; + } + char *toString() const { + assert(getTag() == JSON_STRING); + return (char *)getPayload(); + } + JsonNode *toNode() const { + assert(getTag() == JSON_ARRAY || getTag() == JSON_OBJECT); + return (JsonNode *)getPayload(); + } +}; + +struct JsonNode { + JsonValue value; + JsonNode *next; + char *key; +}; + +struct JsonIterator { + JsonNode *p; + + void operator++() { + p = p->next; + } + bool operator!=(const JsonIterator &x) const { + return p != x.p; + } + JsonNode *operator*() const { + return p; + } + JsonNode *operator->() const { + return p; + } +}; + +inline JsonIterator begin(JsonValue o) { + return JsonIterator{o.toNode()}; +} +inline JsonIterator end(JsonValue) { + return JsonIterator{nullptr}; +} + +#define JSON_ERRNO_MAP(XX) \ + XX(OK, "ok") \ + XX(BAD_NUMBER, "bad number") \ + XX(BAD_STRING, "bad string") \ + XX(BAD_IDENTIFIER, "bad identifier") \ + XX(STACK_OVERFLOW, "stack overflow") \ + XX(STACK_UNDERFLOW, "stack underflow") \ + XX(MISMATCH_BRACKET, "mismatch bracket") \ + XX(UNEXPECTED_CHARACTER, "unexpected character") \ + XX(UNQUOTED_KEY, "unquoted key") \ + XX(BREAKING_BAD, "breaking bad") \ + XX(ALLOCATION_FAILURE, "allocation failure") + +enum JsonErrno { +#define XX(no, str) JSON_##no, + JSON_ERRNO_MAP(XX) +#undef XX +}; + +const char *jsonStrError(int err); + +class JsonAllocator { + struct Zone { + Zone *next; + size_t used; + } *head; + +public: + JsonAllocator() : head(nullptr) {}; + JsonAllocator(const JsonAllocator &) = delete; + JsonAllocator &operator=(const JsonAllocator &) = delete; + JsonAllocator(JsonAllocator &&x) : head(x.head) { + x.head = nullptr; + } + JsonAllocator &operator=(JsonAllocator &&x) { + head = x.head; + x.head = nullptr; + return *this; + } + ~JsonAllocator() { + deallocate(); + } + void *allocate(size_t size); + void deallocate(); +}; + +int jsonParse(char *str, char **endptr, JsonValue *value, JsonAllocator &allocator); diff --git a/ext/native/ext/jpge/jpgd.cpp b/ext/native/ext/jpge/jpgd.cpp index 5fdc1ba52a..4f1d57096d 100644 --- a/ext/native/ext/jpge/jpgd.cpp +++ b/ext/native/ext/jpge/jpgd.cpp @@ -563,7 +563,7 @@ inline int jpeg_decoder::huff_decode(huff_tables *pH, int& extra_bits) // Tables and macro used to fully decode the DPCM differences. static const int s_extend_test[16] = { 0, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000 }; -static const int s_extend_offset[16] = { 0, ((-1)<<1) + 1, ((-1)<<2) + 1, ((-1)<<3) + 1, ((-1)<<4) + 1, ((-1)<<5) + 1, ((-1)<<6) + 1, ((-1)<<7) + 1, ((-1)<<8) + 1, ((-1)<<9) + 1, ((-1)<<10) + 1, ((-1)<<11) + 1, ((-1)<<12) + 1, ((-1)<<13) + 1, ((-1)<<14) + 1, ((-1)<<15) + 1 }; +static const int s_extend_offset[16] = { 0, -(1<<1) + 1, -(1<<2) + 1, -(1<<3) + 1, -(1<<4) + 1, -(1<<5) + 1, -(1<<6) + 1, -(1<<7) + 1, -(1<<8) + 1, -(1<<9) + 1, -(1<<10) + 1, -(1<<11) + 1, -(1<<12) + 1, -(1<<13) + 1, -(1<<14) + 1, -(1<<15) + 1 }; //static const int s_extend_mask[] = { 0, (1<<0), (1<<1), (1<<2), (1<<3), (1<<4), (1<<5), (1<<6), (1<<7), (1<<8), (1<<9), (1<<10), (1<<11), (1<<12), (1<<13), (1<<14), (1<<15), (1<<16) }; // The logical AND's in this macro are to shut up static code analysis (aren't really necessary - couldn't find another way to do this) #define JPGD_HUFF_EXTEND(x, s) (((x) < s_extend_test[s & 15]) ? ((x) + s_extend_offset[s & 15]) : (x)) @@ -3169,4 +3169,4 @@ unsigned char *decompress_jpeg_image_from_file(const char *pSrc_filename, int *w return decompress_jpeg_image_from_stream(&file_stream, width, height, actual_comps, req_comps); } -} // namespace jpgd \ No newline at end of file +} // namespace jpgd diff --git a/ext/native/ext/rg_etc1/rg_etc1.cpp b/ext/native/ext/rg_etc1/rg_etc1.cpp index e003f14863..a19bdd1a7b 100644 --- a/ext/native/ext/rg_etc1/rg_etc1.cpp +++ b/ext/native/ext/rg_etc1/rg_etc1.cpp @@ -1805,7 +1805,7 @@ namespace rg_etc1 { if (block_inten[0] > m_pSorted_luma[n - 1]) { - const uint min_error = (uint)(labs(block_inten[0] - m_pSorted_luma[n - 1])); + const uint min_error = (uint)(labs((int)block_inten[0] - (int)m_pSorted_luma[n - 1])); if (min_error >= trial_solution.m_error) continue; } @@ -1819,7 +1819,7 @@ namespace rg_etc1 { if (m_pSorted_luma[0] > block_inten[3]) { - const uint min_error = (uint)(labs(m_pSorted_luma[0] - block_inten[3])); + const uint min_error = (uint)(labs((int)m_pSorted_luma[0] - (int)block_inten[3])); if (min_error >= trial_solution.m_error) continue; } diff --git a/ext/native/ext/vjson/CMakeLists.txt b/ext/native/ext/vjson/CMakeLists.txt deleted file mode 100644 index b8d19dc34f..0000000000 --- a/ext/native/ext/vjson/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -cmake_minimum_required(VERSION 2.6) - -#if(UNIX) -add_definitions(-fPIC) -add_definitions(-g) -add_definitions(-O2) -add_definitions(-Wall) -#endif(UNIX) - -add_library(vjson json.cpp block_allocator.cpp) diff --git a/ext/native/ext/vjson/block_allocator.cpp b/ext/native/ext/vjson/block_allocator.cpp deleted file mode 100644 index f3a573c653..0000000000 --- a/ext/native/ext/vjson/block_allocator.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include -#include -#include -#include "block_allocator.h" - -block_allocator::block_allocator(size_t blocksize) - : m_head(0), m_blocksize(blocksize) -{ -} - -block_allocator::~block_allocator() -{ - while (m_head) - { - block *temp = m_head->next; - ::free(m_head); - m_head = temp; - } -} - -void block_allocator::swap(block_allocator &rhs) -{ - std::swap(m_blocksize, rhs.m_blocksize); - std::swap(m_head, rhs.m_head); -} - -void *block_allocator::Malloc(size_t size) -{ - if ((m_head && m_head->used + size > m_head->size) || !m_head) - { - // calc needed size for allocation - size_t alloc_size = std::max(sizeof(block) + size, m_blocksize); - - // create new block - char *buffer = (char *)::malloc(alloc_size); - block *b = reinterpret_cast(buffer); - b->size = alloc_size; - b->used = sizeof(block); - b->buffer = buffer; - b->next = m_head; - m_head = b; - } - - void *ptr = m_head->buffer + m_head->used; - m_head->used += size; - return ptr; -} - -void block_allocator::Free() -{ - block_allocator(0).swap(*this); -} diff --git a/ext/native/ext/vjson/block_allocator.h b/ext/native/ext/vjson/block_allocator.h deleted file mode 100644 index 9fff7efc50..0000000000 --- a/ext/native/ext/vjson/block_allocator.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef BLOCK_ALLOCATOR_H -#define BLOCK_ALLOCATOR_H - -class block_allocator -{ -public: - block_allocator(size_t blocksize); - ~block_allocator(); - - // exchange contents with rhs - void swap(block_allocator &rhs); - - // allocate memory - void *Malloc(size_t size); - - // free all allocated blocks - void Free(); - -private: - struct block - { - size_t size; - size_t used; - char *buffer; - block *next; - }; - - block *m_head; - size_t m_blocksize; - - block_allocator(const block_allocator &); - block_allocator &operator=(block_allocator &); -}; - -#endif diff --git a/ext/native/ext/vjson/json.cpp b/ext/native/ext/vjson/json.cpp deleted file mode 100644 index 7203cb577b..0000000000 --- a/ext/native/ext/vjson/json.cpp +++ /dev/null @@ -1,643 +0,0 @@ -#include -#include -#include -#include "json.h" -#include "file/zip_read.h" -#include "file/vfs.h" - -// true if character represent a digit -#define IS_DIGIT(c) (c >= '0' && c <= '9') - -int json_value::numChildren() const { - int count = 0; - const json_value *c = first_child; - while (c) { - count++; - c = c->next_sibling; - } - return count; -} - -// only works right for first child. includes itself in count. -int json_value::numSiblings() const { - const json_value *s = next_sibling; - int count = 1; - while (s) { - count++; - s = s->next_sibling; - } - return count; -} - -const json_value *json_value::get(const char *child_name) const { - if (!child_name) { - FLOG("JSON: Cannot get from null child name"); - return 0; - } - const json_value *c = first_child; - while (c) { - if (!strcmp(c->name, child_name)) { - return c; - } - c = c->next_sibling; - } - return 0; -} - -const json_value *json_value::get(const char *child_name, json_type type) const { - const json_value *v = get(child_name); - if (v && type == v->type) - return v; - else - return 0; -} - -const char *json_value::getStringOrDie(const char *child_name) const { - const json_value *val = get(child_name, JSON_STRING); - if (val) - return val->string_value; - else - FLOG("String '%s' missing from node '%s'", child_name, this->name); - return 0; -} - -const char *json_value::getString(const char *child_name, const char *default_value) const { - const json_value *val = get(child_name, JSON_STRING); - if (!val) - return default_value; - return val->string_value; -} - -bool json_value::getStringVector(std::vector *vec) const { - vec->clear(); - if (type == JSON_ARRAY) { - json_value *val = first_child; - while (val) { - if (val->type == JSON_STRING) { - vec->push_back(val->string_value); - } - } - return true; - } else { - return false; - } -} - -float json_value::getFloat(const char *child_name) const { - return get(child_name, JSON_FLOAT)->float_value; -} - -float json_value::getFloat(const char *child_name, float default_value) const { - const json_value *val = get(child_name, JSON_FLOAT); - if (!val) { - // Let's try int. - val = get(child_name, JSON_INT); - if (!val) - return default_value; - return val->int_value; - } - return val->float_value; -} - -int json_value::getInt(const char *child_name) const { - return get(child_name, JSON_INT)->int_value; -} - -int json_value::getInt(const char *child_name, int default_value) const { - const json_value *val = get(child_name, JSON_INT); - if (!val) - return default_value; - return val->int_value; -} - -bool json_value::getBool(const char *child_name) const { - return get(child_name, JSON_BOOL)->int_value != 0 ? true : false; -} - -bool json_value::getBool(const char *child_name, bool default_value) const { - const json_value *val = get(child_name, JSON_BOOL); - if (!val) - return default_value; - return val->int_value != 0 ? true : false; -} - - -// convert string to integer -char *atoi(char *first, char *last, int *out) -{ - int sign = 1; - if (first != last) - { - if (*first == '-') - { - sign = -1; - ++first; - } - else if (*first == '+') - { - ++first; - } - } - - int result = 0; - for (; first != last && IS_DIGIT(*first); ++first) - { - result = 10 * result + (*first - '0'); - } - *out = result * sign; - - return first; -} - -// convert hexadecimal string to unsigned integer -char *hatoui(char *first, char *last, unsigned int *out) -{ - unsigned int result = 0; - for (; first != last; ++first) - { - int digit; - if (IS_DIGIT(*first)) - { - digit = *first - '0'; - } - else if (*first >= 'a' && *first <= 'f') - { - digit = *first - 'a' + 10; - } - else if (*first >= 'A' && *first <= 'F') - { - digit = *first - 'A' + 10; - } - else - { - break; - } - result = 16 * result + digit; - } - *out = result; - - return first; -} - -// convert string to floating point -char *atof(char *first, char *last, float *out) -{ - // sign - float sign = 1; - if (first != last) - { - if (*first == '-') - { - sign = -1; - ++first; - } - else if (*first == '+') - { - ++first; - } - } - - // integer part - float result = 0; - for (; first != last && IS_DIGIT(*first); ++first) - { - result = 10 * result + (*first - '0'); - } - - // fraction part - if (first != last && *first == '.') - { - ++first; - - float inv_base = 0.1f; - for (; first != last && IS_DIGIT(*first); ++first) - { - result += (*first - '0') * inv_base; - inv_base *= 0.1f; - } - } - - // result w\o exponent - result *= sign; - - // exponent - bool exponent_negative = false; - int exponent = 0; - if (first != last && (*first == 'e' || *first == 'E')) - { - ++first; - - if (*first == '-') - { - exponent_negative = true; - ++first; - } - else if (*first == '+') - { - ++first; - } - - for (; first != last && IS_DIGIT(*first); ++first) - { - exponent = 10 * exponent + (*first - '0'); - } - } - - if (exponent) - { - float power_of_ten = 10; - for (; exponent > 1; exponent--) - { - power_of_ten *= 10; - } - - if (exponent_negative) - { - result /= power_of_ten; - } - else - { - result *= power_of_ten; - } - } - - *out = result; - - return first; -} - -json_value *json_alloc(block_allocator *allocator) -{ - json_value *value = (json_value *)allocator->Malloc(sizeof(json_value)); - memset(value, 0, sizeof(json_value)); - return value; -} - -void json_append(json_value *lhs, json_value *rhs) -{ - rhs->parent = lhs; - if (lhs->last_child) - { - lhs->last_child = lhs->last_child->next_sibling = rhs; - } - else - { - lhs->first_child = lhs->last_child = rhs; - } -} - -#undef ERROR - -#define ERROR(it, desc)\ - *error_pos = it;\ - *error_desc = (char *)desc;\ - *error_line = 1 - escaped_newlines;\ - for (char *c = it; c != source; --c)\ - if (*c == '\n') ++*error_line;\ - return 0 - -#define CHECK_TOP() if (!top) {ERROR(it, "Unexpected character");} - -json_value *json_parse(char *source, char **error_pos, char **error_desc, int *error_line, block_allocator *allocator) -{ - json_value *root = 0; - json_value *top = 0; - - char *name = 0; - char *it = source; - - int escaped_newlines = 0; - - while (*it) - { - switch (*it) - { - case '{': - case '[': - { - // create new value - json_value *object = json_alloc(allocator); - - // name - object->name = name; - name = 0; - - // type - object->type = (*it == '{') ? JSON_OBJECT : JSON_ARRAY; - - // skip open character - ++it; - - // set top and root - if (top) - { - json_append(top, object); - } - else if (!root) - { - root = object; - } - else - { - ERROR(it, "Second root. Only one root allowed"); - } - top = object; - } - break; - - case '}': - case ']': - { - if (!top || top->type != ((*it == '}') ? JSON_OBJECT : JSON_ARRAY)) - { - ERROR(it, "Mismatch closing brace/bracket"); - } - - // skip close character - ++it; - - // set top - top = top->parent; - } - break; - - case ':': - if (!top || top->type != JSON_OBJECT) - { - ERROR(it, "Unexpected character (no top level)"); - } - ++it; - break; - - case ',': - CHECK_TOP(); - ++it; - break; - - case '"': - { - CHECK_TOP(); - - // skip '"' character - ++it; - - char *first = it; - char *last = it; - while (*it) - { - if ((unsigned char)*it < '\x20') - { - ERROR(first, "Control characters not allowed in strings"); - } - else if (*it == '\\') - { - switch (it[1]) - { - case '"': - *last = '"'; - break; - case '\\': - *last = '\\'; - break; - case '/': - *last = '/'; - break; - case 'b': - *last = '\b'; - break; - case 'f': - *last = '\f'; - break; - case 'n': - *last = '\n'; - ++escaped_newlines; - break; - case 'r': - *last = '\r'; - break; - case 't': - *last = '\t'; - break; - case 'u': - { - unsigned int codepoint; - if (hatoui(it + 2, it + 6, &codepoint) != it + 6) - { - ERROR(it, "Bad unicode codepoint"); - } - - if (codepoint <= 0x7F) - { - *last = (char)codepoint; - } - else if (codepoint <= 0x7FF) - { - *last++ = (char)(0xC0 | (codepoint >> 6)); - *last = (char)(0x80 | (codepoint & 0x3F)); - } - else if (codepoint <= 0xFFFF) - { - *last++ = (char)(0xE0 | (codepoint >> 12)); - *last++ = (char)(0x80 | ((codepoint >> 6) & 0x3F)); - *last = (char)(0x80 | (codepoint & 0x3F)); - } - } - it += 4; - break; - default: - ERROR(first, "Unrecognized escape sequence"); - } - - ++last; - it += 2; - } - else if (*it == '"') - { - *last = 0; - ++it; - break; - } - else - { - *last++ = *it++; - } - } - - if (!name && top->type == JSON_OBJECT) - { - // field name in object - name = first; - } - else - { - // new string value - json_value *object = json_alloc(allocator); - - object->name = name; - name = 0; - - object->type = JSON_STRING; - object->string_value = first; - - json_append(top, object); - } - } - break; - - case 'n': - case 't': - case 'f': - { - CHECK_TOP(); - - // new null/bool value - json_value *object = json_alloc(allocator); - - object->name = name; - name = 0; - - // null - if (it[0] == 'n' && it[1] == 'u' && it[2] == 'l' && it[3] == 'l') - { - object->type = JSON_NULL; - it += 4; - } - // true - else if (it[0] == 't' && it[1] == 'r' && it[2] == 'u' && it[3] == 'e') - { - object->type = JSON_BOOL; - object->int_value = 1; - it += 4; - } - // false - else if (it[0] == 'f' && it[1] == 'a' && it[2] == 'l' && it[3] == 's' && it[4] == 'e') - { - object->type = JSON_BOOL; - object->int_value = 0; - it += 5; - } - else - { - ERROR(it, "Unknown identifier"); - } - - json_append(top, object); - } - break; - - case '-': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - CHECK_TOP(); - - // new number value - json_value *object = json_alloc(allocator); - - object->name = name; - name = 0; - - object->type = JSON_INT; - - char *first = it; - while (*it != '\x20' && *it != '\x9' && *it != '\xD' && *it != '\xA' && *it != ',' && *it != ']' && *it != '}') - { - if (*it == '.' || *it == 'e' || *it == 'E') - { - object->type = JSON_FLOAT; - } - ++it; - } - - if (object->type == JSON_INT && atoi(first, it, &object->int_value) != it) - { - ERROR(first, "Bad integer number"); - } - - if (object->type == JSON_FLOAT && atof(first, it, &object->float_value) != it) - { - ERROR(first, "Bad float number"); - } - - json_append(top, object); - } - break; - - default: - ERROR(it, "Unexpected character (unknown)"); - } - - // skip white space - while (*it == '\x20' || *it == '\x9' || *it == '\xD' || *it == '\xA') - { - ++it; - } - } - - if (top) - { - ERROR(it, "Not all objects/arrays have been properly closed"); - } - - return root; -} - -#define IDENT(n) for (int i = 0; i < n; ++i) printf(" ") - -void json_print(json_value *value, int ident) -{ - IDENT(ident); - if (value->name) printf("\"%s\" = ", value->name); - switch(value->type) - { - case JSON_NULL: - printf("null\n"); - break; - case JSON_OBJECT: - case JSON_ARRAY: - printf(value->type == JSON_OBJECT ? "{\n" : "[\n"); - for (json_value *it = value->first_child; it; it = it->next_sibling) - { - json_print(it, ident + 1); - } - IDENT(ident); - printf(value->type == JSON_OBJECT ? "}\n" : "]\n"); - break; - case JSON_STRING: - printf("\"%s\"\n", value->string_value); - break; - case JSON_INT: - printf("%d\n", value->int_value); - break; - case JSON_FLOAT: - printf("%f\n", value->float_value); - break; - case JSON_BOOL: - printf(value->int_value ? "true\n" : "false\n"); - break; - } -} - -JsonReader::JsonReader(const std::string &filename) : alloc_(1 << 12), root_(0) { - size_t buf_size; - buffer_ = (char *)VFSReadFile(filename.c_str(), &buf_size); - if (buffer_) { - parse(); - } else { - // Okay, try to read on the local file system - buffer_ = (char *)ReadLocalFile(filename.c_str(), &buf_size); - if (buffer_) { - parse(); - } else { - ELOG("Failed to read json %s", filename.c_str()); - } - } -} diff --git a/ext/native/ext/vjson/json.h b/ext/native/ext/vjson/json.h deleted file mode 100644 index 792ab1cba4..0000000000 --- a/ext/native/ext/vjson/json.h +++ /dev/null @@ -1,113 +0,0 @@ -#pragma once - -#include -#include -#include -#include "base/logging.h" -#include "block_allocator.h" -#include "file/vfs.h" - -enum json_type { - JSON_NULL, - JSON_OBJECT, - JSON_ARRAY, - JSON_STRING, - JSON_INT, - JSON_FLOAT, - JSON_BOOL, -}; - -struct json_value { - json_value() {} - - json_value *parent; - json_value *next_sibling; - json_value *first_child; - json_value *last_child; - - char *name; - union - { - char *string_value; - int int_value; - float float_value; - }; - - json_type type; - - int numChildren() const; - int numSiblings() const; // num siblings *after* this one only - const json_value *get(const char *child_name) const; - const json_value *get(const char *child_name, json_type type) const; - const json_value *getArray(const char *child_name) const { - return get(child_name, JSON_ARRAY); - } - const json_value *getDict(const char *child_name) const { - return get(child_name, JSON_OBJECT); - } - const char *getStringOrDie(const char *child_name) const; - const char *getString(const char *child_name, const char *default_value) const; - bool getStringVector(std::vector *vec) const; - float getFloat(const char *child_name) const; - float getFloat(const char *child_name, float default_value) const; - int getInt(const char *child_name) const; - int getInt(const char *child_name, int default_value) const; - bool getBool(const char *child_name) const; - bool getBool(const char *child_name, bool default_value) const; - - bool hasChild(const char *child_name, json_type child_type) const { - return get(child_name, child_type) != 0; - } - -private: - DISALLOW_COPY_AND_ASSIGN(json_value); -}; - -// low level interface -json_value *json_parse(char *source, char **error_pos, char **error_desc, int *error_line, block_allocator *allocator); -void json_print(json_value *value, int ident = 0); - -// Easy-wrapper -class JsonReader { -public: - JsonReader(const std::string &filename); - JsonReader(const char *data, size_t size) : alloc_(1 << 12) { - buffer_ = (char *)malloc(size + 1); - memcpy(buffer_, data, size); - buffer_[size] = 0; - parse(); - } - - ~JsonReader() { - if (buffer_) - free(buffer_); - } - - bool ok() const { return root_ != 0; } - - json_value *root() { return root_; } - const json_value *root() const { return root_; } - - void print() { - json_print(root_); - } - -private: - bool parse() { - char *error_pos; - char *error_desc; - int error_line; - root_ = json_parse((char *)buffer_, &error_pos, &error_desc, &error_line, &alloc_); - if (!root_) { - ELOG("Error at (%i): %s\n%s\n\n", error_line, error_desc, error_pos); - return false; - } - return true; - } - - char *buffer_; - block_allocator alloc_; - json_value *root_; - - DISALLOW_COPY_AND_ASSIGN(JsonReader); -}; diff --git a/ext/native/ext/vjson/main.cpp b/ext/native/ext/vjson/main.cpp deleted file mode 100644 index 81f72e5c28..0000000000 --- a/ext/native/ext/vjson/main.cpp +++ /dev/null @@ -1,118 +0,0 @@ -#include -#include -#include "json.h" - -void populate_sources(const char *filter, std::vector > &sources) -{ - char filename[256]; - for (int i = 1; i < 64; ++i) - { - sprintf(filename, filter, i); - FILE *fp = fopen(filename, "rb"); - if (fp) - { - fseek(fp, 0, SEEK_END); - int size = ftell(fp); - fseek(fp, 0, SEEK_SET); - std::vector buffer(size + 1); - fread (&buffer[0], 1, size, fp); - fclose(fp); - sources.push_back(buffer); - } - else - { - break; - } - } - - printf("Loaded %d json files\n", (int)sources.size()); -} - -#define IDENT(n) for (int i = 0; i < n; ++i) printf(" ") - -void print(json_value *value, int ident = 0) -{ - IDENT(ident); - if (value->name) printf("\"%s\" = ", value->name); - switch(value->type) - { - case JSON_NULL: - printf("null\n"); - break; - case JSON_OBJECT: - case JSON_ARRAY: - printf(value->type == JSON_OBJECT ? "{\n" : "[\n"); - for (json_value *it = value->first_child; it; it = it->next_sibling) - { - print(it, ident + 1); - } - IDENT(ident); - printf(value->type == JSON_OBJECT ? "}\n" : "]\n"); - break; - case JSON_STRING: - printf("\"%s\"\n", value->string_value); - break; - case JSON_INT: - printf("%d\n", value->int_value); - break; - case JSON_FLOAT: - printf("%f\n", value->float_value); - break; - case JSON_BOOL: - printf(value->int_value ? "true\n" : "false\n"); - break; - } -} - -bool parse(char *source) -{ - char *errorPos = 0; - char *errorDesc = 0; - int errorLine = 0; - block_allocator allocator(1 << 10); - - json_value *root = json_parse(source, &errorPos, &errorDesc, &errorLine, &allocator); - if (root) - { - print(root); - return true; - } - - printf("Error at line %d: %s\n%s\n\n", errorLine, errorDesc, errorPos); - return false; -} - -int main(int argc, char **argv) -{ - // Fail - printf("===FAIL===\n\n"); - std::vector > sources; - populate_sources("test/fail%d.json", sources); - int passed = 0; - for (size_t i = 0; i < sources.size(); ++i) - { - printf("Parsing %d\n", (int)i + 1); - if (parse(&sources[i][0])) - { - ++passed; - } - } - printf("Passed %d from %d tests\n", passed, (int)sources.size()); - - // Pass - sources.clear(); - printf("\n===PASS===\n\n"); - populate_sources("test/pass%d.json", sources); - passed = 0; - for (size_t i = 0; i < sources.size(); ++i) - { - printf("Parsing %d\n", (int)i + 1); - if (parse(&sources[i][0])) - { - ++passed; - } - } - printf("Passed %d from %d tests\n", passed, (int)sources.size()); - - return 0; -} diff --git a/ext/native/file/fd_util.cpp b/ext/native/file/fd_util.cpp index 8663d44760..0490c86a3e 100644 --- a/ext/native/file/fd_util.cpp +++ b/ext/native/file/fd_util.cpp @@ -133,14 +133,37 @@ void SetNonBlocking(int sock, bool non_blocking) { } std::string GetLocalIP(int sock) { - struct sockaddr_in server_addr; + union { + struct sockaddr sa; + struct sockaddr_in ipv4; + struct sockaddr_in6 ipv6; + } server_addr; memset(&server_addr, 0, sizeof(server_addr)); socklen_t len = sizeof(server_addr); if (getsockname(sock, (struct sockaddr *)&server_addr, &len) == 0) { - char *result = inet_ntoa(*(in_addr *)&server_addr.sin_addr); + char temp[64]{}; + + // We clear the port below for WSAAddressToStringA. + void *addr; + if (server_addr.sa.sa_family == AF_INET6) { + server_addr.ipv6.sin6_port = 0; + addr = &server_addr.ipv6.sin6_addr; + } else { + server_addr.ipv4.sin_port = 0; + addr = &server_addr.ipv4.sin_addr; + } +#ifdef _WIN32 + DWORD len = (DWORD)sizeof(temp); + // Windows XP doesn't support inet_ntop. + if (WSAAddressToStringA((struct sockaddr *)&server_addr, sizeof(server_addr), nullptr, temp, &len) == 0) { + return temp; + } +#else + const char *result = inet_ntop(server_addr.sa.sa_family, addr, temp, sizeof(temp)); if (result) { return result; } +#endif } return ""; } diff --git a/ext/native/file/zip_read.cpp b/ext/native/file/zip_read.cpp index fa3a7c38f6..5069cfc223 100644 --- a/ext/native/file/zip_read.cpp +++ b/ext/native/file/zip_read.cpp @@ -3,11 +3,6 @@ #include #include -#ifdef USING_QT_UI -#include -#include -#endif - #ifdef __ANDROID__ #include #endif @@ -66,58 +61,6 @@ uint8_t *ReadLocalFile(const char *filename, size_t *size) { return contents; } -#ifdef USING_QT_UI -uint8_t *AssetsAssetReader::ReadAsset(const char *path, size_t *size) { - QFile asset(QString(":/assets/") + path); - if (!asset.open(QIODevice::ReadOnly)) - return 0; - - uint8_t *contents = new uint8_t[asset.size()+1]; - memcpy(contents, (uint8_t*)asset.readAll().data(), asset.size()); - contents[asset.size()] = 0; - *size = asset.size(); - asset.close(); - return contents; -} - -bool AssetsAssetReader::GetFileListing(const char *path, std::vector *listing, const char *filter = 0) -{ - QDir assetDir(QString(":/assets/") + path); - QStringList filters = QString(filter).split(':', QString::SkipEmptyParts); - for (int i = 0; i < filters.count(); i++) - filters[i].prepend("*."); - - QFileInfoList infoList = assetDir.entryInfoList(filters, QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Files, QDir::Name); - foreach(QFileInfo qinfo, infoList) { - FileInfo info; - info.name = qinfo.fileName().toStdString(); - info.fullName = qinfo.absoluteFilePath().remove(":/assets/").toStdString(); - info.exists = true; - info.isWritable = false; - info.isDirectory = qinfo.isDir(); - listing->push_back(info); - } - return true; -} - -bool AssetsAssetReader::GetFileInfo(const char *path, FileInfo *info) { - QFileInfo qinfo(QString(":/assets/") + path); - if (!qinfo.exists()) { - info->exists = false; - info->size = 0; - return false; - } - - info->fullName = path; - info->exists = true; - info->isWritable = false; - info->isDirectory = qinfo.isDir(); - info->size = qinfo.size(); - return true; -} - -#endif - #ifdef __ANDROID__ ZipAssetReader::ZipAssetReader(const char *zip_file, const char *in_zip_path) { diff --git a/ext/native/file/zip_read.h b/ext/native/file/zip_read.h index 7e2ecbbf45..3607ddb926 100644 --- a/ext/native/file/zip_read.h +++ b/ext/native/file/zip_read.h @@ -26,21 +26,6 @@ public: virtual std::string toString() const = 0; }; -#ifdef USING_QT_UI -class AssetsAssetReader : public AssetReader { -public: - AssetsAssetReader() {} - ~AssetsAssetReader() {} - // use delete[] - virtual uint8_t *ReadAsset(const char *path, size_t *size); - virtual bool GetFileListing(const char *path, std::vector *listing, const char *filter); - virtual bool GetFileInfo(const char *path, FileInfo *info); - virtual std::string toString() const { - return ":assets/"; - } -}; -#endif - #ifdef __ANDROID__ uint8_t *ReadFromZip(zip *archive, const char* filename, size_t *size); class ZipAssetReader : public AssetReader { diff --git a/ext/native/gfx/d3d9_state.cpp b/ext/native/gfx/d3d9_state.cpp index aa20d88151..fe7849b894 100644 --- a/ext/native/gfx/d3d9_state.cpp +++ b/ext/native/gfx/d3d9_state.cpp @@ -35,6 +35,7 @@ void DirectXState::Restore() { scissorRect.restore(); count++; cullMode.restore(); count++; + shadeMode.restore(); count++; depthTest.restore(); count++; depthFunc.restore(); count++; diff --git a/ext/native/gfx/d3d9_state.h b/ext/native/gfx/d3d9_state.h index a9a8eed86d..3304c22a1e 100644 --- a/ext/native/gfx/d3d9_state.h +++ b/ext/native/gfx/d3d9_state.h @@ -455,6 +455,7 @@ public: BoolUnused dither; CullMode cullMode; + DxState1 shadeMode; BoolState depthTest; diff --git a/ext/native/gfx_es2/draw_text_qt.cpp b/ext/native/gfx_es2/draw_text_qt.cpp index 973e8d27ad..a2fae32f39 100644 --- a/ext/native/gfx_es2/draw_text_qt.cpp +++ b/ext/native/gfx_es2/draw_text_qt.cpp @@ -23,8 +23,7 @@ TextDrawerQt::~TextDrawerQt() { } uint32_t TextDrawerQt::SetFont(const char *fontName, int size, int flags) { - // We will only use the default font - uint32_t fontHash = 0; //hash::Adler32((const uint8_t *)fontName, strlen(fontName)); + uint32_t fontHash = fontName && strlen(fontName) ? hash::Adler32((const uint8_t *)fontName, strlen(fontName)) : 0; fontHash ^= size; fontHash ^= flags << 10; @@ -34,7 +33,7 @@ uint32_t TextDrawerQt::SetFont(const char *fontName, int size, int flags) { return fontHash; } - QFont* font = new QFont(); + QFont* font = fontName ? new QFont(fontName) : new QFont(); font->setPixelSize(size + 6); fontMap_[fontHash] = font; fontHash_ = fontHash; diff --git a/ext/native/gfx_es2/gpu_features.cpp b/ext/native/gfx_es2/gpu_features.cpp index 679c0c2b52..01b8c6c570 100644 --- a/ext/native/gfx_es2/gpu_features.cpp +++ b/ext/native/gfx_es2/gpu_features.cpp @@ -54,6 +54,23 @@ bool GLExtensions::VersionGEThan(int major, int minor, int sub) { return gl_extensions.ver[2] >= sub; } +int GLExtensions::GLSLVersion() { + // Used for shader translation and core contexts (Apple drives fail without an exact match.) + if (gl_extensions.VersionGEThan(3, 3)) { + return gl_extensions.ver[0] * 100 + gl_extensions.ver[1] * 10; + } else if (gl_extensions.VersionGEThan(3, 2)) { + return 150; + } else if (gl_extensions.VersionGEThan(3, 1)) { + return 140; + } else if (gl_extensions.VersionGEThan(3, 0)) { + return 130; + } else if (gl_extensions.VersionGEThan(2, 1)) { + return 120; + } else { + return 110; + } +} + void ProcessGPUFeatures() { gl_extensions.bugs = 0; @@ -99,7 +116,7 @@ void CheckGLExtensions() { gl_extensions.IsCoreContext = useCoreContext; #ifdef USING_GLES2 - gl_extensions.IsGLES = true; + gl_extensions.IsGLES = !useCoreContext; #endif const char *renderer = (const char *)glGetString(GL_RENDERER); @@ -184,6 +201,10 @@ void CheckGLExtensions() { // Most of it could be enabled on lower GPUs as well, but let's start this way. if (gl_extensions.VersionGEThan(4, 3, 0)) { gl_extensions.GLES3 = true; +#ifdef USING_GLES2 + // Try to load up the other funcs if we're not using glew. + gl3stubInit(); +#endif } } else { // Start by assuming we're at 2.0. @@ -505,15 +526,7 @@ std::string ApplyGLSLPrelude(const std::string &source, uint32_t stage) { std::string version = ""; if (!gl_extensions.IsGLES && gl_extensions.IsCoreContext) { // We need to add a corresponding #version. Apple drives fail without an exact match. - if (gl_extensions.VersionGEThan(3, 3)) { - version = StringFromFormat("#version %d%d0\n", gl_extensions.ver[0], gl_extensions.ver[1]); - } else if (gl_extensions.VersionGEThan(3, 2)) { - version = "#version 150\n"; - } else if (gl_extensions.VersionGEThan(3, 1)) { - version = "#version 140\n"; - } else { - version = "#version 130\n"; - } + version = StringFromFormat("#version %d\n", gl_extensions.GLSLVersion()); } if (stage == GL_FRAGMENT_SHADER) { temp = version + glsl_fragment_prelude + source; diff --git a/ext/native/gfx_es2/gpu_features.h b/ext/native/gfx_es2/gpu_features.h index b26e923b55..fc1c73a0fd 100644 --- a/ext/native/gfx_es2/gpu_features.h +++ b/ext/native/gfx_es2/gpu_features.h @@ -103,6 +103,7 @@ struct GLExtensions { // greater-or-equal than bool VersionGEThan(int major, int minor, int sub = 0); + int GLSLVersion(); }; extern GLExtensions gl_extensions; diff --git a/ext/native/json/json_reader.cpp b/ext/native/json/json_reader.cpp new file mode 100644 index 0000000000..83fbe94fa4 --- /dev/null +++ b/ext/native/json/json_reader.cpp @@ -0,0 +1,119 @@ +#include "file/zip_read.h" +#include "file/vfs.h" +#include "json/json_reader.h" + +JsonReader::JsonReader(const std::string &filename) { + size_t buf_size; + buffer_ = (char *)VFSReadFile(filename.c_str(), &buf_size); + if (buffer_) { + parse(); + } else { + // Okay, try to read on the local file system + buffer_ = (char *)ReadLocalFile(filename.c_str(), &buf_size); + if (buffer_) { + parse(); + } else { + ELOG("Failed to read json %s", filename.c_str()); + } + } +} + +int JsonGet::numChildren() const { + int count = 0; + if (value_.getTag() == JSON_OBJECT || value_.getTag() == JSON_ARRAY) { + for (auto it : value_) { + count++; + } + } + return count; +} + +const JsonNode *JsonGet::get(const char *child_name) const { + if (!child_name) { + FLOG("JSON: Cannot get from null child name"); + return nullptr; + } + if (value_.getTag() != JSON_OBJECT) { + return nullptr; + } + for (auto it : value_) { + if (!strcmp(it->key, child_name)) { + return it; + } + } + return nullptr; +} + +const JsonNode *JsonGet::get(const char *child_name, JsonTag type) const { + const JsonNode *v = get(child_name); + if (v && type == v->value.getTag()) + return v; + return nullptr; +} + +const char *JsonGet::getStringOrDie(const char *child_name) const { + const JsonNode *val = get(child_name, JSON_STRING); + if (val) + return val->value.toString(); + FLOG("String '%s' missing from node", child_name); + return nullptr; +} + +const char *JsonGet::getString(const char *child_name, const char *default_value) const { + const JsonNode *val = get(child_name, JSON_STRING); + if (!val) + return default_value; + return val->value.toString(); +} + +bool JsonGet::getStringVector(std::vector *vec) const { + vec->clear(); + if (value_.getTag() == JSON_ARRAY) { + for (auto it : value_) { + if (it->value.getTag() == JSON_STRING) { + vec->push_back(it->value.toString()); + } + } + return true; + } else { + return false; + } +} + +double JsonGet::getFloat(const char *child_name) const { + return get(child_name, JSON_NUMBER)->value.toNumber(); +} + +double JsonGet::getFloat(const char *child_name, double default_value) const { + const JsonNode *val = get(child_name, JSON_NUMBER); + if (!val) + return default_value; + return val->value.toNumber(); +} + +int JsonGet::getInt(const char *child_name) const { + return (int)get(child_name, JSON_NUMBER)->value.toNumber(); +} + +int JsonGet::getInt(const char *child_name, int default_value) const { + const JsonNode *val = get(child_name, JSON_NUMBER); + if (!val) + return default_value; + return (int)val->value.toNumber(); +} + +bool JsonGet::getBool(const char *child_name) const { + return get(child_name)->value.getTag() == JSON_TRUE; +} + +bool JsonGet::getBool(const char *child_name, bool default_value) const { + const JsonNode *val = get(child_name); + if (val) { + JsonTag tag = val->value.getTag(); + if (tag == JSON_TRUE) + return true; + if (tag == JSON_FALSE) + return false; + } + return default_value; +} diff --git a/ext/native/json/json_reader.h b/ext/native/json/json_reader.h new file mode 100644 index 0000000000..b9e8de5e53 --- /dev/null +++ b/ext/native/json/json_reader.h @@ -0,0 +1,85 @@ +#include +#include +#include "base/basictypes.h" +#include "base/logging.h" +#include "ext/gason/gason.h" + +struct JsonGet { + JsonGet(const JsonValue &value) : value_(value) { + } + + int numChildren() const; + const JsonNode *get(const char *child_name) const; + const JsonNode *get(const char *child_name, JsonTag type) const; + const JsonNode *getArray(const char *child_name) const { + return get(child_name, JSON_ARRAY); + } + const JsonGet getDict(const char *child_name) const { + return JsonGet(get(child_name, JSON_OBJECT)->value); + } + const char *getStringOrDie(const char *child_name) const; + const char *getString(const char *child_name, const char *default_value) const; + bool getStringVector(std::vector *vec) const; + double getFloat(const char *child_name) const; + double getFloat(const char *child_name, double default_value) const; + int getInt(const char *child_name) const; + int getInt(const char *child_name, int default_value) const; + bool getBool(const char *child_name) const; + bool getBool(const char *child_name, bool default_value) const; + + bool hasChild(const char *child_name, JsonTag child_type) const { + return get(child_name, child_type) != nullptr; + } + + operator bool() const { + return value_.getTag() != JSON_NULL; + } + + JsonValue value_; +}; + +// Easy-wrapper +class JsonReader { +public: + JsonReader(const std::string &filename); + JsonReader(const void *data, size_t size) { + buffer_ = (char *)malloc(size + 1); + memcpy(buffer_, data, size); + buffer_[size] = 0; + parse(); + } + JsonReader(const JsonNode *node) { + ok_ = true; + } + + ~JsonReader() { + if (buffer_) + free(buffer_); + } + + bool ok() const { return ok_; } + + JsonGet root() { return root_.getTag() == JSON_OBJECT ? JsonGet(root_) : JsonGet(JSON_NULL); } + const JsonValue rootArray() const { return root_.getTag() == JSON_ARRAY ? root_ : JSON_NULL; } + + const JsonValue rootValue() const { return root_; } + +private: + bool parse() { + char *error_pos; + int status = jsonParse(buffer_, &error_pos, &root_, alloc_); + if (status != JSON_OK) { + ELOG("Error at (%i): %s\n%s\n\n", (int)(error_pos - buffer_), jsonStrError(status), error_pos); + return false; + } + ok_ = true; + return true; + } + + char *buffer_ = nullptr; + JsonAllocator alloc_; + JsonValue root_; + bool ok_ = false; + + DISALLOW_COPY_AND_ASSIGN(JsonReader); +}; diff --git a/ext/native/json/json_writer.cpp b/ext/native/json/json_writer.cpp index ec9019c50d..7d1c47a620 100644 --- a/ext/native/json/json_writer.cpp +++ b/ext/native/json/json_writer.cpp @@ -1,6 +1,14 @@ +#include +#include +#include +#include "json/json_reader.h" #include "json/json_writer.h" -JsonWriter::JsonWriter() { +JsonWriter::JsonWriter(int flags) { + pretty_ = (flags & PRETTY) != 0; + str_.imbue(std::locale::classic()); + // Let's maximize precision by default. + str_.precision(53); } JsonWriter::~JsonWriter() { @@ -11,23 +19,44 @@ void JsonWriter::begin() { stack_.push_back(StackEntry(DICT)); } +void JsonWriter::beginArray() { + str_ << "["; + stack_.push_back(StackEntry(ARRAY)); +} + +void JsonWriter::beginRaw() { + // For the uncommon case of writing a value directly, to avoid duplicated code. + stack_.push_back(StackEntry(RAW)); +} + void JsonWriter::end() { pop(); - str_ << "\n"; + if (pretty_) + str_ << "\n"; } const char *JsonWriter::indent(int n) const { - static const char * const whitespace = " "; + if (!pretty_) + return ""; + static const char * const whitespace = " "; + if (n > 32) { + // Avoid crash. + return whitespace; + } return whitespace + (32 - n); } const char *JsonWriter::indent() const { + if (!pretty_) + return ""; int amount = (int)stack_.size() + 1; amount *= 2; // 2-space indent. return indent(amount); } const char *JsonWriter::arrayIndent() const { + if (!pretty_) + return ""; int amount = (int)stack_.size() + 1; amount *= 2; // 2-space indent. return stack_.back().first ? indent(amount) : ""; @@ -37,25 +66,42 @@ const char *JsonWriter::comma() const { if (stack_.back().first) { return ""; } else { - return ","; + return pretty_ ? ",\n" : ","; } } const char *JsonWriter::arrayComma() const { if (stack_.back().first) { - return "\n"; + return pretty_ ? "\n" : ""; } else { - return ", "; + return pretty_ ? ", " : ","; } } -void JsonWriter::pushDict(const char *name) { - str_ << comma() << "\n" << indent() << "\"" << name << "\": {"; +void JsonWriter::pushDict() { + str_ << arrayComma() << arrayIndent() << "{"; + stack_.back().first = false; stack_.push_back(StackEntry(DICT)); } -void JsonWriter::pushArray(const char *name) { - str_ << comma() << "\n" << indent() << "\"" << name << "\": ["; +void JsonWriter::pushDict(const std::string &name) { + str_ << comma() << indent() << "\""; + writeEscapedString(name); + str_ << (pretty_ ? "\": {" : "\":{"); + stack_.back().first = false; + stack_.push_back(StackEntry(DICT)); +} + +void JsonWriter::pushArray() { + str_ << arrayComma() << arrayIndent() << "["; + stack_.back().first = false; + stack_.push_back(StackEntry(ARRAY)); +} + +void JsonWriter::pushArray(const std::string &name) { + str_ << comma() << indent() << "\""; + writeEscapedString(name); + str_ << (pretty_ ? "\": [" : "\":["); stack_.push_back(StackEntry(ARRAY)); } @@ -64,8 +110,10 @@ void JsonWriter::writeBool(bool value) { stack_.back().first = false; } -void JsonWriter::writeBool(const char *name, bool value) { - str_ << comma() << "\n" << indent() << "\"" << name << "\": " << (value ? "true" : "false"); +void JsonWriter::writeBool(const std::string &name, bool value) { + str_ << comma() << indent() << "\""; + writeEscapedString(name); + str_ << (pretty_ ? "\": " : "\":") << (value ? "true" : "false"); stack_.back().first = false; } @@ -74,42 +122,264 @@ void JsonWriter::writeInt(int value) { stack_.back().first = false; } -void JsonWriter::writeInt(const char *name, int value) { - str_ << comma() << "\n" << indent() << "\"" << name << "\": " << value; +void JsonWriter::writeInt(const std::string &name, int value) { + str_ << comma() << indent() << "\""; + writeEscapedString(name); + str_ << (pretty_ ? "\": " : "\":") << value; stack_.back().first = false; } -void JsonWriter::writeFloat(double value) { +void JsonWriter::writeUint(uint32_t value) { str_ << arrayComma() << arrayIndent() << value; stack_.back().first = false; } -void JsonWriter::writeFloat(const char *name, double value) { - str_ << comma() << "\n" << indent() << "\"" << name << "\": " << value; +void JsonWriter::writeUint(const std::string &name, uint32_t value) { + str_ << comma() << indent() << "\""; + writeEscapedString(name); + str_ << (pretty_ ? "\": " : "\":") << value; stack_.back().first = false; } -void JsonWriter::writeString(const char *value) { - str_ << arrayComma() << arrayIndent() << "\"" << value << "\""; +void JsonWriter::writeFloat(double value) { + str_ << arrayComma() << arrayIndent(); + if (std::isfinite(value)) + str_ << value; + else + str_ << "null"; stack_.back().first = false; } -void JsonWriter::writeString(const char *name, const char *value) { - str_ << comma() << "\n" << indent() << "\"" << name << "\": \"" << value << "\""; +void JsonWriter::writeFloat(const std::string &name, double value) { + str_ << comma() << indent() << "\""; + writeEscapedString(name); + str_ << (pretty_ ? "\": " : "\":"); + if (std::isfinite(value)) + str_ << value; + else + str_ << "null"; + stack_.back().first = false; +} + +void JsonWriter::writeString(const std::string &value) { + str_ << arrayComma() << arrayIndent() << "\""; + writeEscapedString(value); + str_ << "\""; + stack_.back().first = false; +} + +void JsonWriter::writeString(const std::string &name, const std::string &value) { + str_ << comma() << indent() << "\""; + writeEscapedString(name); + str_ << (pretty_ ? "\": \"" : "\":\""); + writeEscapedString(value); + str_ << "\""; + stack_.back().first = false; +} + +void JsonWriter::writeRaw(const std::string &value) { + str_ << arrayComma() << arrayIndent() << value; + stack_.back().first = false; +} + +void JsonWriter::writeRaw(const std::string &name, const std::string &value) { + str_ << comma() << indent() << "\""; + writeEscapedString(name); + str_ << (pretty_ ? "\": " : "\":"); + str_ << value; + stack_.back().first = false; +} + +void JsonWriter::writeNull() { + str_ << arrayComma() << arrayIndent() << "null"; + stack_.back().first = false; +} + +void JsonWriter::writeNull(const std::string &name) { + str_ << comma() << indent() << "\""; + writeEscapedString(name); + str_ << (pretty_ ? "\": " : "\":") << "null"; stack_.back().first = false; } void JsonWriter::pop() { BlockType type = stack_.back().type; stack_.pop_back(); + if (pretty_) + str_ << "\n" << indent(); switch (type) { case ARRAY: - str_ << "\n" << indent() << "]"; + str_ << "]"; break; case DICT: - str_ << "\n" << indent() << "}"; + str_ << "}"; + break; + case RAW: break; } if (stack_.size() > 0) stack_.back().first = false; } + +void JsonWriter::writeEscapedString(const std::string &str) { + size_t pos = 0; + const size_t len = str.size(); + + auto update = [&](size_t current, size_t skip = 0) { + size_t end = current; + if (pos < end) + str_ << str.substr(pos, end - pos); + pos = end + skip; + }; + + for (size_t i = 0; i < len; ++i) { + switch (str[i]) { + case '\\': + case '"': + case '/': + update(i); + str_ << '\\'; + break; + + case '\r': + update(i, 1); + str_ << "\\r"; + break; + break; + + case '\n': + update(i, 1); + str_ << "\\n"; + break; + break; + + case '\t': + update(i, 1); + str_ << "\\t"; + break; + + case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 11: + case 12: case 14: case 15: case 16: case 17: case 18: case 19: case 20: + case 21: case 22: case 23: case 24: case 25: case 26: case 27: case 28: + case 29: case 30: case 31: + update(i, 1); + str_ << "\\u" << std::hex << std::setw(4) << std::setfill('0') << (int)str[i] << std::dec << std::setw(0); + break; + + default: + break; + } + } + + if (pos != 0) { + update(len); + } else { + str_ << str; + } +} + +static void json_stringify_object(JsonWriter &writer, const JsonNode *node); +static void json_stringify_array(JsonWriter &writer, const JsonNode *node); + +std::string json_stringify(const JsonNode *node) { + JsonWriter writer; + + // Handle direct values too, not just objects. + switch (node->value.getTag()) { + case JSON_NULL: + case JSON_STRING: + case JSON_NUMBER: + case JSON_TRUE: + case JSON_FALSE: + writer.beginRaw(); + // It's the same as a one entry array without brackets, so reuse. + json_stringify_array(writer, node); + break; + + case JSON_OBJECT: + writer.begin(); + for (const JsonNode *it : node->value) { + json_stringify_object(writer, it); + } + break; + case JSON_ARRAY: + writer.beginArray(); + for (const JsonNode *it : node->value) { + json_stringify_array(writer, it); + } + break; + } + + writer.end(); + return writer.str(); +} + +static void json_stringify_object(JsonWriter &writer, const JsonNode *node) { + switch (node->value.getTag()) { + case JSON_NULL: + writer.writeNull(node->key); + break; + case JSON_STRING: + writer.writeString(node->key, node->value.toString()); + break; + case JSON_NUMBER: + writer.writeFloat(node->key, node->value.toNumber()); + break; + case JSON_TRUE: + writer.writeBool(node->key, true); + break; + case JSON_FALSE: + writer.writeBool(node->key, false); + break; + + case JSON_OBJECT: + writer.pushDict(node->key); + for (const JsonNode *it : node->value) { + json_stringify_object(writer, it); + } + writer.pop(); + break; + case JSON_ARRAY: + writer.pushArray(node->key); + for (const JsonNode *it : node->value) { + json_stringify_array(writer, it); + } + writer.pop(); + break; + } +} + +static void json_stringify_array(JsonWriter &writer, const JsonNode *node) { + switch (node->value.getTag()) { + case JSON_NULL: + writer.writeRaw("null"); + break; + case JSON_STRING: + writer.writeString(node->value.toString()); + break; + case JSON_NUMBER: + writer.writeFloat(node->value.toNumber()); + break; + case JSON_TRUE: + writer.writeBool(true); + break; + case JSON_FALSE: + writer.writeBool(false); + break; + + case JSON_OBJECT: + writer.pushDict(); + for (const JsonNode *it : node->value) { + json_stringify_object(writer, it); + } + writer.pop(); + break; + case JSON_ARRAY: + writer.pushArray(); + for (const JsonNode *it : node->value) { + json_stringify_array(writer, it); + } + writer.pop(); + break; + } +} diff --git a/ext/native/json/json_writer.h b/ext/native/json/json_writer.h index b58ca37f6d..22c712c856 100644 --- a/ext/native/json/json_writer.h +++ b/ext/native/json/json_writer.h @@ -7,8 +7,7 @@ // // Does not deal with encodings in any way. // -// Zero dependencies apart from stdlib. -// See json_writer_test.cpp for usage. +// Zero dependencies apart from stdlib (if you remove the vhjson usage.) #include #include @@ -16,35 +15,59 @@ class JsonWriter { public: - JsonWriter(); + JsonWriter(int flags = NORMAL); ~JsonWriter(); void begin(); + void beginArray(); + void beginRaw(); void end(); - void pushDict(const char *name); - void pushArray(const char *name); + void pushDict(); + void pushDict(const std::string &name); + void pushArray(); + void pushArray(const std::string &name); void pop(); void writeBool(bool value); - void writeBool(const char *name, bool value); + void writeBool(const std::string &name, bool value); void writeInt(int value); - void writeInt(const char *name, int value); + void writeInt(const std::string &name, int value); + void writeUint(uint32_t value); + void writeUint(const std::string &name, uint32_t value); void writeFloat(double value); - void writeFloat(const char *name, double value); - void writeString(const char *value); - void writeString(const char *name, const char *value); + void writeFloat(const std::string &name, double value); + void writeString(const std::string &value); + void writeString(const std::string &name, const std::string &value); + void writeRaw(const std::string &value); + void writeRaw(const std::string &name, const std::string &value); + void writeNull(); + void writeNull(const std::string &name); std::string str() const { return str_.str(); } + std::string flush() { + std::string result = str_.str(); + str_.str(""); + return result; + } + + enum { + NORMAL = 0, + PRETTY = 1, + }; + private: const char *indent(int n) const; const char *comma() const; const char *arrayComma() const; const char *indent() const; const char *arrayIndent() const; + void writeEscapedString(const std::string &s); + enum BlockType { ARRAY, DICT, + RAW, }; struct StackEntry { StackEntry(BlockType t) : type(t), first(true) {} @@ -53,4 +76,8 @@ private: }; std::vector stack_; std::ostringstream str_; + bool pretty_; }; + +struct JsonNode; +std::string json_stringify(const JsonNode *json); diff --git a/ext/native/native.vcxproj b/ext/native/native.vcxproj index 8d43e4329d..8fb5a42746 100644 --- a/ext/native/native.vcxproj +++ b/ext/native/native.vcxproj @@ -202,10 +202,12 @@ + + @@ -220,8 +222,6 @@ - - @@ -238,6 +238,8 @@ + + @@ -317,6 +319,7 @@ + AnySuitable @@ -340,6 +343,7 @@ Default Default + @@ -683,8 +687,6 @@ - - @@ -699,7 +701,9 @@ + + diff --git a/ext/native/native.vcxproj.filters b/ext/native/native.vcxproj.filters index aecf223674..08305fee7c 100644 --- a/ext/native/native.vcxproj.filters +++ b/ext/native/native.vcxproj.filters @@ -242,12 +242,6 @@ ext\jpge - - ext\vjson - - - ext\vjson - gfx @@ -341,6 +335,18 @@ thin3d + + ext\gason + + + json + + + net + + + data + @@ -712,12 +718,6 @@ ext\jpge - - ext\vjson - - - ext\vjson - gfx @@ -814,6 +814,18 @@ thin3d + + ext\gason + + + json + + + net + + + data + @@ -876,14 +888,14 @@ {3721adbc-1a4a-4017-bc1d-893c7e86bc22} - - {c6910d82-73d3-4d97-ac9c-bcc03265b1bc} - {5ce64c0e-98e4-4411-86cc-aacaf2f60b21} {06c6305a-a646-485b-85b9-645a24dd6553} + + {5b740c6f-8dbc-4529-9114-6564b37b3548} + \ No newline at end of file diff --git a/ext/native/net/http_client.cpp b/ext/native/net/http_client.cpp index becb4e4473..3cb6967326 100644 --- a/ext/native/net/http_client.cpp +++ b/ext/native/net/http_client.cpp @@ -46,7 +46,7 @@ inline unsigned short myhtons(unsigned short x) { return (x >> 8) | (x << 8); } -bool Connection::Resolve(const char *host, int port) { +bool Connection::Resolve(const char *host, int port, DNSType type) { if ((intptr_t)sock_ != -1) { ELOG("Resolve: Already have a socket"); return false; @@ -63,7 +63,7 @@ bool Connection::Resolve(const char *host, int port) { snprintf(port_str, sizeof(port_str), "%d", port); std::string err; - if (!net::DNSResolve(host, port_str, &resolved_, err)) { + if (!net::DNSResolve(host, port_str, &resolved_, err, type)) { ELOG("Failed to resolve host %s: %s", host, err.c_str()); // So that future calls fail. port_ = 0; @@ -87,10 +87,10 @@ bool Connection::Connect(int maxTries, double timeout, bool *cancelConnect) { FD_ZERO(&fds); for (addrinfo *possible = resolved_; possible != nullptr; possible = possible->ai_next) { // TODO: Could support ipv6 without huge difficulty... - if (possible->ai_family != AF_INET) + if (possible->ai_family != AF_INET && possible->ai_family != AF_INET6) continue; - int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + int sock = socket(possible->ai_family, SOCK_STREAM, IPPROTO_TCP); if ((intptr_t)sock == -1) { ELOG("Bad socket"); continue; diff --git a/ext/native/net/http_client.h b/ext/native/net/http_client.h index 0f63c4bb9c..ac3d26ed97 100644 --- a/ext/native/net/http_client.h +++ b/ext/native/net/http_client.h @@ -6,6 +6,7 @@ #include "base/basictypes.h" #include "base/buffer.h" +#include "net/resolve.h" #ifdef _WIN32 #ifndef NOMINMAX @@ -28,7 +29,7 @@ public: virtual ~Connection(); // Inits the sockaddr_in. - bool Resolve(const char *host, int port); + bool Resolve(const char *host, int port, DNSType type = DNSType::ANY); bool Connect(int maxTries = 2, double timeout = 20.0f, bool *cancelConnect = nullptr); void Disconnect(); diff --git a/ext/native/net/http_server.cpp b/ext/native/net/http_server.cpp index 9357d2fe69..f7cae6cd30 100644 --- a/ext/native/net/http_server.cpp +++ b/ext/native/net/http_server.cpp @@ -59,7 +59,9 @@ Request::~Request() { CHECK(in_->Empty()); delete in_; - CHECK(out_->Empty()); + if (!out_->Empty()) { + ELOG("Output not empty - connection abort?"); + } delete out_; } @@ -87,8 +89,10 @@ void Request::WriteHttpResponseHeader(int status, int64_t size, const char *mime net::OutputSink *buffer = Out(); buffer->Printf("HTTP/1.0 %03d %s\r\n", status, statusStr); buffer->Push("Server: PPSSPPServer v0.1\r\n"); - buffer->Printf("Content-Type: %s\r\n", mimeType ? mimeType : DEFAULT_MIME_TYPE); - buffer->Push("Connection: close\r\n"); + if (!mimeType || strcmp(mimeType, "websocket") != 0) { + buffer->Printf("Content-Type: %s\r\n", mimeType ? mimeType : DEFAULT_MIME_TYPE); + buffer->Push("Connection: close\r\n"); + } if (size >= 0) { buffer->Printf("Content-Length: %llu\r\n", size); } @@ -130,9 +134,21 @@ void Server::SetFallbackHandler(UrlHandlerFunc handler) { fallback_ = handler; } -bool Server::Listen(int port) { +bool Server::Listen(int port, net::DNSType type) { + bool success = false; + if (type == net::DNSType::ANY || type == net::DNSType::IPV6) { + success = Listen6(port, type == net::DNSType::IPV6); + } + if (!success && (type == net::DNSType::ANY || type == net::DNSType::IPV4)) { + success = Listen4(port); + } + return success; +} + +bool Server::Listen4(int port) { listener_ = socket(AF_INET, SOCK_STREAM, 0); - CHECK_GE(listener_, 0); + if (listener_ < 0) + return false; struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); @@ -145,6 +161,7 @@ bool Server::Listen(int port) { setsockopt(listener_, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)); if (bind(listener_, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { + closesocket(listener_); ELOG("Failed to bind to port %i. Bailing.", port); return false; } @@ -152,7 +169,10 @@ bool Server::Listen(int port) { fd_util::SetNonBlocking(listener_, true); // 1024 is the max number of queued requests. - CHECK_GE(listen(listener_, 1024), 0); + if (listen(listener_, 1024) < 0) { + closesocket(listener_); + return false; + } socklen_t len = sizeof(server_addr); if (getsockname(listener_, (struct sockaddr *)&server_addr, &len) == 0) { @@ -165,6 +185,50 @@ bool Server::Listen(int port) { return true; } +bool Server::Listen6(int port, bool ipv6_only) { + listener_ = socket(AF_INET6, SOCK_STREAM, 0); + if (listener_ < 0) + return false; + + struct sockaddr_in6 server_addr; + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin6_family = AF_INET6; + server_addr.sin6_addr = in6addr_any; + server_addr.sin6_port = htons(port); + + int opt = 1; + // Enable re-binding to avoid the pain when restarting the server quickly. + setsockopt(listener_, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)); + + // Enable listening on IPv6 and IPv4? + opt = ipv6_only ? 1 : 0; + setsockopt(listener_, IPPROTO_IPV6, IPV6_V6ONLY, (const char *)&opt, sizeof(opt)); + + if (bind(listener_, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { + closesocket(listener_); + ELOG("Failed to bind to port %i. Bailing.", port); + return false; + } + + fd_util::SetNonBlocking(listener_, true); + + // 1024 is the max number of queued requests. + if (listen(listener_, 1024) < 0) { + closesocket(listener_); + return false; + } + + socklen_t len = sizeof(server_addr); + if (getsockname(listener_, (struct sockaddr *)&server_addr, &len) == 0) { + port = ntohs(server_addr.sin6_port); + } + + ILOG("HTTP server started on port %i", port); + port_ = port; + + return true; +} + bool Server::RunSlice(double timeout) { if (listener_ < 0 || port_ == 0) { return false; @@ -177,9 +241,13 @@ bool Server::RunSlice(double timeout) { return false; } - sockaddr client_addr; + union { + struct sockaddr sa; + struct sockaddr_in ipv4; + struct sockaddr_in6 ipv6; + } client_addr; socklen_t client_addr_size = sizeof(client_addr); - int conn_fd = accept(listener_, &client_addr, &client_addr_size); + int conn_fd = accept(listener_, &client_addr.sa, &client_addr_size); if (conn_fd >= 0) { executor_->Run(std::bind(&Server::HandleConnection, this, conn_fd)); return true; @@ -213,7 +281,7 @@ void Server::HandleConnection(int conn_fd) { WLOG("Bad request, ignoring."); return; } - HandleRequestDefault(request); + HandleRequest(request); // TODO: Way to mark the content body as read, read it here if never read. // This allows the handler to stream if need be. diff --git a/ext/native/net/http_server.h b/ext/native/net/http_server.h index 974b45ee6d..6fc97d907b 100644 --- a/ext/native/net/http_server.h +++ b/ext/native/net/http_server.h @@ -6,6 +6,7 @@ #include "base/buffer.h" #include "net/http_headers.h" +#include "net/resolve.h" #include "thread/executor.h" namespace net { @@ -62,6 +63,7 @@ private: class Server { public: Server(threading::Executor *executor); + virtual ~Server() {} typedef std::function UrlHandlerFunc; typedef std::map UrlHandlerMap; @@ -73,7 +75,7 @@ public: // May run for (significantly) longer than timeout, but won't wait longer than that // for a new connection to handle. bool RunSlice(double timeout); - bool Listen(int port); + bool Listen(int port, net::DNSType type = net::DNSType::ANY); void Stop(); void RegisterHandler(const char *url_path, UrlHandlerFunc handler); @@ -85,10 +87,13 @@ public: virtual void HandleRequest(const Request &request); int Port() { - return port_; + return port_; } private: + bool Listen6(int port, bool ipv6_only); + bool Listen4(int port); + void HandleConnection(int conn_fd); // Things like default 404, etc. diff --git a/ext/native/net/resolve.cpp b/ext/native/net/resolve.cpp index 1876427b14..ec7d17f1d1 100644 --- a/ext/native/net/resolve.cpp +++ b/ext/native/net/resolve.cpp @@ -77,7 +77,7 @@ char *DNSResolve(const char *host) return ip; } -bool DNSResolve(const std::string &host, const std::string &service, addrinfo **res, std::string &error) +bool DNSResolve(const std::string &host, const std::string &service, addrinfo **res, std::string &error, DNSType type) { addrinfo hints = {0}; // TODO: Might be uses to lookup other values. @@ -89,7 +89,11 @@ bool DNSResolve(const std::string &host, const std::string &service, addrinfo ** // http://stackoverflow.com/questions/1408030/what-is-the-purpose-of-the-ai-v4mapped-flag-in-getaddrinfo hints.ai_flags = /*AI_V4MAPPED |*/ AI_ADDRCONFIG; #endif - hints.ai_protocol = IPPROTO_TCP; + hints.ai_protocol = 0; + if (type == DNSType::IPV4) + hints.ai_family = AF_INET; + else if (type == DNSType::IPV6) + hints.ai_family = AF_INET6; const char *servicep = service.length() == 0 ? NULL : service.c_str(); diff --git a/ext/native/net/resolve.h b/ext/native/net/resolve.h index 46a8b3e598..58ce0984d9 100644 --- a/ext/native/net/resolve.h +++ b/ext/native/net/resolve.h @@ -14,7 +14,13 @@ void Shutdown(); char *DNSResolveTry(const char *host, const char **err); char *DNSResolve(const char *host); -bool DNSResolve(const std::string &host, const std::string &service, addrinfo **res, std::string &error); +enum class DNSType { + ANY = 0, + IPV4 = 1, + IPV6 = 2, +}; + +bool DNSResolve(const std::string &host, const std::string &service, addrinfo **res, std::string &error, DNSType type = DNSType::ANY); void DNSResolveFree(addrinfo *res); int inet_pton(int af, const char* src, void* dst); diff --git a/ext/native/net/sinks.cpp b/ext/native/net/sinks.cpp index aaac2f1888..e011f33453 100644 --- a/ext/native/net/sinks.cpp +++ b/ext/native/net/sinks.cpp @@ -201,6 +201,11 @@ bool InputSink::Empty() { return valid_ == 0; } +bool InputSink::TryFill() { + Fill(); + return !Empty(); +} + OutputSink::OutputSink(size_t fd) : fd_(fd), read_(0), write_(0), valid_(0) { fd_util::SetNonBlocking((int)fd_, true); } @@ -316,7 +321,7 @@ bool OutputSink::Block() { return true; } -bool OutputSink::Flush() { +bool OutputSink::Flush(bool allowBlock) { while (valid_ > 0) { size_t avail = std::min(BUFFER_SIZE - read_, valid_); @@ -325,15 +330,23 @@ bool OutputSink::Flush() { if (bytes == 0) { // This may also drain. Either way, keep looping. - if (!Block()) { + if (!allowBlock || !Block()) { return false; } + } else if (bytes < 0) { + return false; } } return true; } +void OutputSink::Discard() { + read_ = 0; + write_ = 0; + valid_ = 0; +} + void OutputSink::Drain() { // Avoid small reads if possible. if (valid_ > PRESSURE) { diff --git a/ext/native/net/sinks.h b/ext/native/net/sinks.h index 0138d74a6c..fa5546c917 100644 --- a/ext/native/net/sinks.h +++ b/ext/native/net/sinks.h @@ -21,6 +21,7 @@ public: bool Skip(size_t bytes); bool Empty(); + bool TryFill(); private: void Fill(); @@ -49,7 +50,8 @@ public: bool PushCRLF(const std::string &s); bool Printf(const char *fmt, ...); - bool Flush(); + bool Flush(bool allowBlock = true); + void Discard(); bool Empty(); diff --git a/ext/native/net/websocket_server.cpp b/ext/native/net/websocket_server.cpp new file mode 100644 index 0000000000..397616a058 --- /dev/null +++ b/ext/native/net/websocket_server.cpp @@ -0,0 +1,547 @@ +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#endif +#include "base/stringutil.h" +#include "data/base64.h" +#include "net/http_server.h" +#include "net/sinks.h" +#include "net/websocket_server.h" +// TODO: Not a great cross dependency. +#include "Common/Crypto/sha1.h" + +#ifdef _WIN32 +// Function Cross-Compatibility +#define strcasecmp _stricmp +#endif + +static const char *const WEBSOCKET_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + +namespace net { + +enum class Opcode { + CONTINUE = 0, + TEXT = 1, + BINARY = 2, + + CLOSE = 8, + PING = 9, + PONG = 10, + + PAYLOAD_MAX = 2, + CONTROL_MIN = 8, + CONTROL_MAX = 10, +}; + +static const size_t OUT_PRESSURE = 65536; + +static inline std::string TrimString(const std::string &s) { + auto wsfront = std::find_if_not(s.begin(), s.end(), [](int c) { + // isspace() expects 0 - 255, so convert any sign-extended value. + return std::isspace(c & 0xFF); + }); + auto wsback = std::find_if_not(s.rbegin(), s.rend(), [](int c){ + return std::isspace(c & 0xFF); + }).base(); + return wsback > wsfront ? std::string(wsfront, wsback) : std::string(); +} + +static bool ListContainsNoCase(const std::string &list, const std::string value) { + std::vector split; + SplitString(list, ',', split); + + for (auto item : split) { + std::transform(item.begin(), item.end(), item.begin(), tolower); + if (TrimString(item) == value) { + return true; + } + } + + return false; +} + +WebSocketServer *WebSocketServer::CreateAsUpgrade(const http::Request &request, const std::string &protocol) { + auto requireHeader = [&](const char *name, const char *expected) { + std::string val; + if (!request.GetHeader(name, &val)) { + return false; + } + return strcasecmp(val.c_str(), expected) == 0; + }; + auto requireHeaderContains = [&](const char *name, const char *expected) { + std::string val; + if (!request.GetHeader(name, &val)) { + return false; + } + return ListContainsNoCase(val, expected); + }; + + if (!requireHeader("upgrade", "websocket") || !requireHeaderContains("connection", "upgrade")) { + request.WriteHttpResponseHeader(400, -1, "text/plain"); + request.Out()->Push("Must send a websocket request."); + return nullptr; + } + if (!requireHeader("sec-websocket-version", "13")) { + request.WriteHttpResponseHeader(400, -1, "text/plain", "Sec-WebSocket-Version: 13\r\n"); + request.Out()->Push("Unsupported version."); + return nullptr; + } + + std::string requestedProtocols; + std::string obtainedProtocolHeader; + if (!protocol.empty() && request.GetHeader("sec-websocket-protocol", &requestedProtocols)) { + if (ListContainsNoCase(requestedProtocols, protocol)) { + obtainedProtocolHeader = "Sec-WebSocket-Protocol: " + protocol + "\r\n"; + } + } + + std::string key; + if (!request.GetHeader("sec-websocket-key", &key)) { + request.WriteHttpResponseHeader(400, -1, "text/plain"); + request.Out()->Push("Cannot accept without key."); + return nullptr; + } + + key += WEBSOCKET_GUID; + unsigned char accept[20]; + sha1((unsigned char *)key.c_str(), (int)key.size(), accept); + + std::string acceptKey = Base64Encode(accept, 20); + std::string otherHeaders = StringFromFormat("Upgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: %s\r\n%s", acceptKey.c_str(), obtainedProtocolHeader.c_str()); + + // Okay, we're good to go then. + request.WriteHttpResponseHeader(101, -1, "websocket", otherHeaders.c_str()); + request.WritePartial(); + + return new WebSocketServer(request.fd(), request.In(), request.Out()); +} + +void WebSocketServer::Send(const std::string &str) { + assert(open_); + assert(fragmentOpcode_ == -1); + SendHeader(true, (int)Opcode::TEXT, str.size()); + SendBytes(str.c_str(), str.size()); +} + +void WebSocketServer::Send(const std::vector &payload) { + assert(open_); + assert(fragmentOpcode_ == -1); + SendHeader(true, (int)Opcode::BINARY, payload.size()); + SendBytes((const char *)&payload[0], payload.size()); +} + +void WebSocketServer::AddFragment(bool finish, const std::string &str) { + assert(open_); + if (fragmentOpcode_ == -1) { + SendHeader(finish, (int)Opcode::TEXT, str.size()); + fragmentOpcode_ = (int)Opcode::TEXT; + } else if (fragmentOpcode_ == (int)Opcode::TEXT) { + SendHeader(finish, (int)Opcode::CONTINUE, str.size()); + } else { + assert(fragmentOpcode_ == (int)Opcode::TEXT || fragmentOpcode_ == -1); + } + SendBytes(str.c_str(), str.size()); + if (finish) { + fragmentOpcode_ = -1; + } +} + +void WebSocketServer::AddFragment(bool finish, const std::vector &payload) { + assert(open_); + if (fragmentOpcode_ == -1) { + SendHeader(finish, (int)Opcode::BINARY, payload.size()); + fragmentOpcode_ = (int)Opcode::BINARY; + } else if (fragmentOpcode_ == (int)Opcode::BINARY) { + SendHeader(finish, (int)Opcode::CONTINUE, payload.size()); + } else { + assert(fragmentOpcode_ == (int)Opcode::BINARY || fragmentOpcode_ == -1); + } + SendBytes((const char *)&payload[0], payload.size()); + if (finish) { + fragmentOpcode_ = -1; + } +} + +void WebSocketServer::Ping(const std::vector &payload) { + assert(open_); + assert(payload.size() <= 125); + SendHeader(true, (int)Opcode::PING, payload.size()); + SendBytes((const char *)&payload[0], payload.size()); +} + +void WebSocketServer::Pong(const std::vector &payload) { + assert(open_); + assert(payload.size() <= 125); + SendHeader(true, (int)Opcode::PONG, payload.size()); + SendBytes((const char *)&payload[0], payload.size()); +} + +void WebSocketServer::Close(WebSocketClose reason) { + closeReason_ = reason; + if (reason == WebSocketClose::NO_STATUS) { + // This means we received a CLOSE without a code. + SendHeader(true, (int)Opcode::CLOSE, 0); + } else { + SendHeader(true, (int)Opcode::CLOSE, 2); + + uint16_t r = (uint16_t)reason; + uint8_t reasonData[] = { + (uint8_t)((r >> 8) & 0xFF), + (uint8_t)((r >> 0) & 0xFF), + }; + SendBytes((const char *)reasonData, sizeof(reasonData)); + } + + sentClose_ = true; +} + +bool WebSocketServer::Process(float timeout) { + if (!open_) { + return false; + } + + SendFlush(); + + if (outBuf_.empty() && out_->Empty() && sentClose_) { + // Okay, we've sent the close. Don't wait for anything else (whether we got a close or not.) + open_ = false; + return false; + } + + struct timeval tv; + tv.tv_sec = floor(timeout); + tv.tv_usec = (timeout - floor(timeout)) * 1000000.0; + + fd_set read; + FD_ZERO(&read); + // In case we closed due to protocol error, don't even try to read. + if (!sentClose_) { + FD_SET(fd_, &read); + } + + fd_set write; + FD_ZERO(&write); + if (!outBuf_.empty() || !out_->Empty()) { + FD_SET(fd_, &write); + } + + // First argument to select is the highest socket in the set + 1. + int rval = select((int)fd_ + 1, &read, &write, nullptr, &tv); + if (rval < 0) { + // Something went wrong with the select() call. + // TODO: Could be EINTR, for now returning true... + return true; + } + + if (rval == 0) { + // Timed out. + return true; + } + + if (FD_ISSET(fd_, &write)) { + SendFlush(); + } + if (FD_ISSET(fd_, &read)) { + if (in_->Empty() && !in_->TryFill()) { + // Since select said it was readable, we assume this means disconnect. + closeReason_ = WebSocketClose::ABNORMAL; + open_ = false; + // Kill any remaining output too. + out_->Discard(); + return false; + } + + while (ReadFrames() && !in_->Empty()) + continue; + } + + return true; +} + +bool WebSocketServer::ReadFrames() { + if (pendingLeft_ != 0) { + return ReadPending(); + } + + return ReadFrame(); +} + +bool WebSocketServer::ReadFrame() { + assert(pendingLeft_ == 0); + + // TODO: For now blocking on header trickle, shouldn't be common. + auto readExact = [&](void *p, size_t sz) { + if (!in_->TakeExact((char *)p, sz)) { + // TODO: Failing on too slow trickle timeout for now. + Close(WebSocketClose::POLICY_VIOLATION); + return false; + } + return true; + }; + + // Client frames are always between 6 and 14 bytes. We start with 6. + uint8_t header[14]; + if (!readExact(header, 6)) + return false; + + // Don't allow reserved bits to be set, require masking. + if ((header[0] & 0x70) != 0 || (header[1] & 0x80) == 0) { + Close(WebSocketClose::PROTOCOL_ERROR); + return false; + } + + const bool fin = (header[0] & 0x80) != 0; + const int opcode = header[0] & 0x0F; + uint64_t sz = header[1] & 0x7F; + const uint8_t *mask = &header[2]; + + if (opcode >= (int)Opcode::CONTROL_MIN && (sz > 125 || !fin)) { + // Control frames must be <= 125 bytes. + Close(WebSocketClose::PROTOCOL_ERROR); + return false; + } + + if (opcode > (int)Opcode::CONTROL_MAX || (opcode > (int)Opcode::PAYLOAD_MAX && opcode < (int)Opcode::CONTROL_MIN)) { + // Undefined opcode. + Close(WebSocketClose::PROTOCOL_ERROR); + return false; + } + + if (!pendingFin_ && opcode == (int)Opcode::CONTINUE) { + // Can't continue what you haven't started. + Close(WebSocketClose::PROTOCOL_ERROR); + return false; + } + if (pendingFin_ && opcode != (int)Opcode::CONTINUE && opcode < (int)Opcode::CONTROL_MIN) { + // Can't start something else until you finish your thought. + Close(WebSocketClose::PROTOCOL_ERROR); + return false; + } + + if (sz == 126) { + // Read the rest of the mask. + if (!readExact((char *)&header[6], 2)) + return false; + + mask = &header[4]; + sz = (header[2] << 8) | (header[3] << 0); + } else if (sz == 127) { + // We only have half the size so far - read the rest, and the mask. + if (!readExact((char *)&header[6], 8)) + return false; + + mask = &header[10]; + // Read from big endian. + uint64_t high = (header[2] << 24) | (header[3] << 16) || (header[4] << 8) | (header[5] << 0); + uint64_t low = (header[6] << 24) | (header[7] << 16) || (header[8] << 8) | (header[9] << 0); + sz = (high << 32) | low; + + if ((sz & 0x8000000000000000ULL) != 0) { + Close(WebSocketClose::PROTOCOL_ERROR); + return false; + } + } + + if (opcode >= (int)Opcode::CONTROL_MIN) { + // It's safe to overwrite this since we can be between fragmented frames, but not inside a frame. + memcpy(pendingMask_, mask, sizeof(pendingMask_)); + return ReadControlFrame(opcode, sz); + } + + // The data could be split among many TCP packets, so read it as it comes. + if (!pendingFin_) + pendingOpcode_ = opcode; + pendingFin_ = !fin; + pendingLeft_ = sz; + memcpy(pendingMask_, mask, sizeof(pendingMask_)); + + // Payload data is actually read in ReadPending(). + return true; +} + +bool WebSocketServer::ReadPending() { + size_t pos = pendingBuf_.size(); + pendingBuf_.resize(pendingBuf_.size() + pendingLeft_); + + // Read what we can. + size_t readBytes = in_->TakeAtMost((char *)&pendingBuf_[pos], pendingLeft_); + for (size_t i = 0; i < readBytes; ++i) { + pendingBuf_[pos + i] ^= pendingMask_[i & 3]; + } + pendingLeft_ -= readBytes; + + if (pendingLeft_ != 0) { + // Still more to read. Careful: we might need to rotate the mask. + // Example: if we read only 3 bytes, next read should start at fourth byte in mask. + int offset = readBytes & 3; + if (offset) { + uint8_t orig[4]; + memcpy(orig, pendingMask_, sizeof(orig)); + for (size_t i = 0; i < sizeof(orig); ++i) { + pendingMask_[i] = orig[(offset + i) & 3]; + } + } + + // Truncate out the unread bytes for next time. + pendingBuf_.resize(pos + readBytes); + return true; + } + + // We're done, but were we waiting for a FIN packet? + if (pendingFin_) + return true; + + if (pendingOpcode_ == (int)Opcode::TEXT) { + if (text_) { + text_(std::string(pendingBuf_.begin(), pendingBuf_.end())); + } + } else if (pendingOpcode_ == (int)Opcode::BINARY) { + if (binary_) { + binary_(pendingBuf_); + } + } else { + assert(false); + } + + // All done, clear it out. + pendingBuf_.clear(); + pendingOpcode_ = -1; + + return true; +} + +bool WebSocketServer::ReadControlFrame(int opcode, size_t sz) { + std::vector payload; + payload.resize(sz); + // Just block here to read the payload. + if (!in_->TakeExact((char *)&payload[0], sz)) { + // TODO: Failing on too slow trickle timeout for now. + Close(WebSocketClose::POLICY_VIOLATION); + return false; + } + + for (size_t i = 0; i < sz; ++i) { + payload[i] ^= pendingMask_[i & 3]; + } + + if (opcode == (int)Opcode::PING) { + Pong(payload); + // Try to send immediately if possible, but don't block. + SendFlush(); + + if (ping_) { + ping_(payload); + } + } else if (opcode == (int)Opcode::PONG) { + if (pong_) { + pong_(payload); + } + } else if (opcode == (int)Opcode::CLOSE) { + if (payload.size() >= 2) { + uint16_t reason = (payload[0] << 8) | payload[1]; + // Send back a close right away. + Close(WebSocketClose(reason)); + } else { + Close(WebSocketClose::NO_STATUS); + } + // Don't read anything more. + return false; + } else { + assert(false); + } + + return true; +} + +void WebSocketServer::SendHeader(bool fin, int opcode, size_t sz) { + assert((opcode & 0x0F) == opcode); + uint8_t frameHeader = (fin ? 0x80 : 0x00) | opcode; + SendBytes(&frameHeader, 1); + + // We never mask from the server. + if (sz <= 125) { + uint8_t frameSize = (int8_t)sz; + SendBytes(&frameSize, 1); + } else if (sz <= 0xFFFF) { + uint8_t frameSize[] = { + 126, + (uint8_t)((sz >> 8) & 0xFF), + (uint8_t)((sz >> 0) & 0xFF), + }; + SendBytes(frameSize, sizeof(frameSize)); + } else { + uint64_t sz64 = sz; + assert((sz64 & 0x8000000000000000ULL) == 0); + uint8_t frameSize[] = { + 127, + (uint8_t)((sz64 >> 56) & 0xFF), + (uint8_t)((sz64 >> 48) & 0xFF), + (uint8_t)((sz64 >> 40) & 0xFF), + (uint8_t)((sz64 >> 32) & 0xFF), + (uint8_t)((sz64 >> 24) & 0xFF), + (uint8_t)((sz64 >> 16) & 0xFF), + (uint8_t)((sz64 >> 8) & 0xFF), + (uint8_t)((sz64 >> 0) & 0xFF), + }; + SendBytes(frameSize, sizeof(frameSize)); + } +} + +void WebSocketServer::SendBytes(const void *p, size_t sz) { + const char *data = (const char *)p; + if (outBuf_.empty()) { + size_t pushed = out_->PushAtMost(data, sz); + data += pushed; + sz -= pushed; + } + + if (sz != 0) { + size_t pos = outBuf_.size(); + outBuf_.resize(pos + sz); + memcpy(&outBuf_[pos], data, sz); + + if (pos + sz > lastPressure_ + OUT_PRESSURE) { + size_t pushed = out_->PushAtMost((const char *)&outBuf_[0], outBuf_.size()); + if (pushed != 0) { + outBuf_.erase(outBuf_.begin(), outBuf_.begin() + pushed); + } + lastPressure_ = outBuf_.size(); + } + } +} + +void WebSocketServer::SendFlush() { + out_->Flush(false); + + // Drain out as much of our buffer as possible. + size_t totalPushed = 0; + while (outBuf_.size() - totalPushed != 0) { + size_t pushed = out_->PushAtMost((const char *)&outBuf_[totalPushed], outBuf_.size() - totalPushed); + if (pushed == 0) + break; + + totalPushed += pushed; + out_->Flush(false); + } + + if (totalPushed != 0) { + // Hopefully this is usually the entire buffer. + outBuf_.erase(outBuf_.begin(), outBuf_.begin() + totalPushed); + } + lastPressure_ = outBuf_.size(); +} + +}; diff --git a/ext/native/net/websocket_server.h b/ext/native/net/websocket_server.h new file mode 100644 index 0000000000..2c4c59739e --- /dev/null +++ b/ext/native/net/websocket_server.h @@ -0,0 +1,105 @@ +#pragma once + +#include +#include +#include "net/http_server.h" +#include "net/sinks.h" + +namespace net { + +enum class WebSocketClose : uint16_t { + NORMAL = 1000, + GOING_AWAY = 1001, + PROTOCOL_ERROR = 1002, + UNSUPPORTED_DATA = 1003, + INVALID_DATA = 1007, + POLICY_VIOLATION = 1008, + MESSAGE_TOO_LONG = 1009, + MISSING_EXTENSION = 1010, + INTERNAL_ERROR = 1011, + SERVICE_RESTART = 1012, + TRY_AGAIN_LATER = 1013, + BAD_GATEWAY = 1014, + + NO_STATUS = 1005, + ABNORMAL = 1006, +}; + +// RFC 6455 +class WebSocketServer { +public: + static WebSocketServer *CreateAsUpgrade(const http::Request &request, const std::string &protocol = ""); + + void Send(const std::string &str); + void Send(const std::vector &payload); + + // Call with finish = false to start and continue, then finally with finish = true to complete. + // Note: Fragmented data cannot be interleaved, per protocol. + void AddFragment(bool finish, const std::string &str); + void AddFragment(bool finish, const std::vector &payload); + + void Ping(const std::vector &payload = {}); + void Pong(const std::vector &payload = {}); + void Close(WebSocketClose reason = WebSocketClose::GOING_AWAY); + + // Note: may interrupt early. Call in a loop. + bool Process(float timeout = -1.0f); + + void SetTextHandler(std::function func) { + text_ = func; + } + void SetBinaryHandler(std::function &)> func) { + binary_ = func; + } + // Doesn't need to send a Pong. + void SetPingHandler(std::function &)> func) { + ping_ = func; + } + void SetPongHandler(std::function &)> func) { + pong_ = func; + } + + bool IsOpen() { + return open_; + } + WebSocketClose CloseReason() { + return closeReason_; + } + +protected: + WebSocketServer(size_t fd, InputSink *in, OutputSink *out) : fd_(fd), in_(in), out_(out) { + } + + void SendHeader(bool fin, int opcode, size_t sz); + void SendBytes(const void *p, size_t sz); + void SendFlush(); + bool ReadFrames(); + bool ReadFrame(); + bool ReadPending(); + bool ReadControlFrame(int opcode, size_t sz); + + bool open_ = true; + bool sentClose_ = false; + int fragmentOpcode_ = -1; + size_t fd_ = 0; + InputSink *in_ = nullptr; + OutputSink *out_ = nullptr; + WebSocketClose closeReason_ = WebSocketClose::NO_STATUS; + std::vector outBuf_; + size_t lastPressure_ = 0; + + std::vector pendingBuf_; + uint8_t pendingMask_[4]{}; + // Bytes left to read in the frame (in case of a partial frame read.) + uint64_t pendingLeft_ = 0; + int pendingOpcode_ = -1; + // Waiting for a frame with FIN. + bool pendingFin_ = false; + + std::function text_; + std::function &)> binary_; + std::function &)> ping_; + std::function &)> pong_; +}; + +}; diff --git a/ext/native/thin3d/GLQueueRunner.cpp b/ext/native/thin3d/GLQueueRunner.cpp index 653125d3fe..1ae55e39f6 100644 --- a/ext/native/thin3d/GLQueueRunner.cpp +++ b/ext/native/thin3d/GLQueueRunner.cpp @@ -24,6 +24,7 @@ extern void bindDefaultFBO(); GLuint g_defaultFBO = 0; void GLQueueRunner::CreateDeviceObjects() { + CHECK_GL_ERROR_IF_DEBUG(); if (gl_extensions.EXT_texture_filter_anisotropic) { glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAnisotropyLevel_); } else { @@ -52,9 +53,11 @@ void GLQueueRunner::CreateDeviceObjects() { if (!gl_extensions.IsCoreContext) { // Not OK to query this in core profile! populate(GL_EXTENSIONS); } + CHECK_GL_ERROR_IF_DEBUG(); } void GLQueueRunner::DestroyDeviceObjects() { + CHECK_GL_ERROR_IF_DEBUG(); if (!nameCache_.empty()) { glDeleteTextures((GLsizei)nameCache_.size(), &nameCache_[0]); nameCache_.clear(); @@ -66,6 +69,25 @@ void GLQueueRunner::DestroyDeviceObjects() { readbackBufferSize_ = 0; delete[] tempBuffer_; tempBufferSize_ = 0; + CHECK_GL_ERROR_IF_DEBUG(); +} + +template +static std::string GetInfoLog(GLuint name, Getiv getiv, GetLog getLog) { + GLint bufLength = 0; + getiv(name, GL_INFO_LOG_LENGTH, &bufLength); + if (bufLength <= 0) + bufLength = 2048; + + std::string infoLog; + infoLog.resize(bufLength); + GLsizei len = 0; + getLog(name, (GLsizei)infoLog.size(), &len, &infoLog[0]); + if (len <= 0) + return "(unknown reason)"; + + infoLog.resize(len); + return infoLog; } void GLQueueRunner::RunInitSteps(const std::vector &steps) { @@ -83,6 +105,7 @@ void GLQueueRunner::RunInitSteps(const std::vector &steps) { glGenTextures(1, &tex->texture); glBindTexture(tex->target, tex->texture); boundTexture = tex->texture; + CHECK_GL_ERROR_IF_DEBUG(); break; } case GLRInitStepType::CREATE_BUFFER: @@ -91,6 +114,7 @@ void GLQueueRunner::RunInitSteps(const std::vector &steps) { glGenBuffers(1, &buffer->buffer); glBindBuffer(buffer->target_, buffer->buffer); glBufferData(buffer->target_, step.create_buffer.size, nullptr, step.create_buffer.usage); + CHECK_GL_ERROR_IF_DEBUG(); break; } case GLRInitStepType::BUFFER_SUBDATA: @@ -100,6 +124,7 @@ void GLQueueRunner::RunInitSteps(const std::vector &steps) { glBufferSubData(GL_ARRAY_BUFFER, step.buffer_subdata.offset, step.buffer_subdata.size, step.buffer_subdata.data); if (step.buffer_subdata.deleteData) delete[] step.buffer_subdata.data; + CHECK_GL_ERROR_IF_DEBUG(); break; } case GLRInitStepType::CREATE_PROGRAM: @@ -136,36 +161,30 @@ void GLQueueRunner::RunInitSteps(const std::vector &steps) { GLint linkStatus = GL_FALSE; glGetProgramiv(program->program, GL_LINK_STATUS, &linkStatus); if (linkStatus != GL_TRUE) { - GLint bufLength = 0; - glGetProgramiv(program->program, GL_INFO_LOG_LENGTH, &bufLength); - if (bufLength) { - char *buf = new char[bufLength]; - glGetProgramInfoLog(program->program, bufLength, nullptr, buf); + std::string infoLog = GetInfoLog(program->program, glGetProgramiv, glGetProgramInfoLog); - // TODO: Could be other than vs/fs. Also, we're assuming order here... - const char *vsDesc = step.create_program.shaders[0]->desc.c_str(); - const char *fsDesc = step.create_program.num_shaders > 1 ? step.create_program.shaders[1]->desc.c_str() : nullptr; - const char *vsCode = step.create_program.shaders[0]->code.c_str(); - const char *fsCode = step.create_program.num_shaders > 1 ? step.create_program.shaders[1]->code.c_str() : nullptr; - Reporting::ReportMessage("Error in shader program link: info: %s\nfs: %s\n%s\nvs: %s\n%s", buf, fsDesc, fsCode, vsDesc, vsCode); + // TODO: Could be other than vs/fs. Also, we're assuming order here... + GLRShader *vs = step.create_program.shaders[0]; + GLRShader *fs = step.create_program.num_shaders > 1 ? step.create_program.shaders[1] : nullptr; + std::string vsDesc = vs->desc + (vs->failed ? " (failed)" : ""); + std::string fsDesc = fs ? (fs->desc + (fs->failed ? " (failed)" : "")) : "(none)"; + const char *vsCode = vs->code.c_str(); + const char *fsCode = fs ? fs->code.c_str() : "(none)"; + Reporting::ReportMessage("Error in shader program link: info: %s\nfs: %s\n%s\nvs: %s\n%s", infoLog.c_str(), fsDesc.c_str(), fsCode, vsDesc.c_str(), vsCode); - ELOG("Could not link program:\n %s", buf); - ERROR_LOG(G3D, "VS desc:\n%s", vsDesc); - ERROR_LOG(G3D, "FS desc:\n%s", fsDesc); - ERROR_LOG(G3D, "VS:\n%s\n", vsCode); - ERROR_LOG(G3D, "FS:\n%s\n", fsCode); + ELOG("Could not link program:\n %s", infoLog.c_str()); + ERROR_LOG(G3D, "VS desc:\n%s", vsDesc.c_str()); + ERROR_LOG(G3D, "FS desc:\n%s", fsDesc.c_str()); + ERROR_LOG(G3D, "VS:\n%s\n", vsCode); + ERROR_LOG(G3D, "FS:\n%s\n", fsCode); #ifdef _WIN32 - OutputDebugStringUTF8(buf); - if (vsCode) - OutputDebugStringUTF8(LineNumberString(vsCode).c_str()); - if (fsCode) - OutputDebugStringUTF8(LineNumberString(fsCode).c_str()); + OutputDebugStringUTF8(infoLog.c_str()); + if (vsCode) + OutputDebugStringUTF8(LineNumberString(vsCode).c_str()); + if (fsCode) + OutputDebugStringUTF8(LineNumberString(fsCode).c_str()); #endif - delete[] buf; - } else { - ELOG("Could not link program with %d shaders for unknown reason:", step.create_program.num_shaders); - } CHECK_GL_ERROR_IF_DEBUG(); break; } @@ -204,23 +223,18 @@ void GLQueueRunner::RunInitSteps(const std::vector &steps) { GLint success = 0; glGetShaderiv(shader, GL_COMPILE_STATUS, &success); if (!success) { -#define MAX_INFO_LOG_SIZE 2048 - GLchar infoLog[MAX_INFO_LOG_SIZE]; - GLsizei len = 0; - glGetShaderInfoLog(shader, MAX_INFO_LOG_SIZE, &len, infoLog); - infoLog[len] = '\0'; + std::string infoLog = GetInfoLog(shader, glGetShaderiv, glGetShaderInfoLog); #ifdef __ANDROID__ - ELOG("Error in shader compilation! %s\n", infoLog); + ELOG("Error in shader compilation! %s\n", infoLog.c_str()); ELOG("Shader source:\n%s\n", (const char *)code); #endif ERROR_LOG(G3D, "Error in shader compilation for: %s", step.create_shader.shader->desc.c_str()); - ERROR_LOG(G3D, "Info log: %s", infoLog); + ERROR_LOG(G3D, "Info log: %s", infoLog.c_str()); ERROR_LOG(G3D, "Shader source:\n%s\n", (const char *)code); - Reporting::ReportMessage("Error in shader compilation: info: %s\n%s\n%s", infoLog, step.create_shader.shader->desc.c_str(), (const char *)code); + Reporting::ReportMessage("Error in shader compilation: info: %s\n%s\n%s", infoLog.c_str(), step.create_shader.shader->desc.c_str(), (const char *)code); #ifdef SHADERLOG - OutputDebugStringUTF8(infoLog); + OutputDebugStringUTF8(infoLog.c_str()); #endif - step.create_shader.shader->valid = false; step.create_shader.shader->failed = true; step.create_shader.shader->error = infoLog; } @@ -233,7 +247,7 @@ void GLQueueRunner::RunInitSteps(const std::vector &steps) { } case GLRInitStepType::CREATE_INPUT_LAYOUT: { - GLRInputLayout *layout = step.create_input_layout.inputLayout; + // GLRInputLayout *layout = step.create_input_layout.inputLayout; // Nothing to do unless we want to create vertexbuffer objects (GL 4.5) break; } @@ -432,6 +446,7 @@ void GLQueueRunner::InitCreateFramebuffer(const GLRInitStep &step) { } void GLQueueRunner::RunSteps(const std::vector &steps) { + CHECK_GL_ERROR_IF_DEBUG(); for (size_t i = 0; i < steps.size(); i++) { const GLRStep &step = *steps[i]; switch (step.stepType) { @@ -456,6 +471,7 @@ void GLQueueRunner::RunSteps(const std::vector &steps) { } delete steps[i]; } + CHECK_GL_ERROR_IF_DEBUG(); } void GLQueueRunner::LogSteps(const std::vector &steps) { @@ -464,6 +480,7 @@ void GLQueueRunner::LogSteps(const std::vector &steps) { void GLQueueRunner::PerformBlit(const GLRStep &step) { + CHECK_GL_ERROR_IF_DEBUG(); // Without FBO_ARB / GLES3, this will collide with bind_for_read, but there's nothing // in ES 2.0 that actually separate them anyway of course, so doesn't matter. fbo_bind_fb_target(false, step.blit.dst->handle); @@ -492,6 +509,7 @@ void GLQueueRunner::PerformBlit(const GLRStep &step) { } void GLQueueRunner::PerformRenderPass(const GLRStep &step) { + CHECK_GL_ERROR_IF_DEBUG(); // Don't execute empty renderpasses. if (step.commands.empty()) { // Nothing to do. @@ -526,7 +544,6 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step) { glBindVertexArray(globalVAO_); } - GLRFramebuffer *fb = step.render.framebuffer; GLRProgram *curProgram = nullptr; int activeSlot = 0; glActiveTexture(GL_TEXTURE0 + activeSlot); @@ -536,7 +553,6 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step) { int colorMask = -1; int depthMask = -1; int depthFunc = -1; - int logicOp = -1; GLuint curArrayBuffer = (GLuint)-1; GLuint curElemArrayBuffer = (GLuint)-1; bool depthEnabled = false; @@ -544,7 +560,10 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step) { bool blendEnabled = false; bool cullEnabled = false; bool ditherEnabled = false; +#ifndef USING_GLES2 + int logicOp = -1; bool logicEnabled = false; +#endif GLuint blendEqColor = (GLuint)-1; GLuint blendEqAlpha = (GLuint)-1; @@ -583,6 +602,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step) { glDisable(GL_STENCIL_TEST); stencilEnabled = false; } + CHECK_GL_ERROR_IF_DEBUG(); break; case GLRRenderCommand::STENCILOP: glStencilOp(c.stencilOp.sFail, c.stencilOp.zFail, c.stencilOp.pass); @@ -608,6 +628,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step) { glColorMask(c.blend.mask & 1, (c.blend.mask >> 1) & 1, (c.blend.mask >> 2) & 1, (c.blend.mask >> 3) & 1); colorMask = c.blend.mask; } + CHECK_GL_ERROR_IF_DEBUG(); break; case GLRRenderCommand::LOGICOP: #ifndef USING_GLES2 @@ -624,6 +645,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step) { logicEnabled = false; } #endif + CHECK_GL_ERROR_IF_DEBUG(); break; case GLRRenderCommand::CLEAR: // Scissor test is on, and should be on after leaving this case. If we disable it, @@ -656,6 +678,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step) { if (c.clear.scissorW == 0) { glEnable(GL_SCISSOR_TEST); } + CHECK_GL_ERROR_IF_DEBUG(); break; case GLRRenderCommand::INVALIDATE: { @@ -668,6 +691,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step) { if (c.clear.clearMask & GL_STENCIL_BUFFER_BIT) attachments[count++] = GL_STENCIL_BUFFER_BIT; glInvalidateFramebuffer(GL_FRAMEBUFFER, count, attachments); + CHECK_GL_ERROR_IF_DEBUG(); break; } case GLRRenderCommand::BLENDCOLOR: @@ -686,6 +710,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step) { #else glDepthRangef(c.viewport.vp.minZ, c.viewport.vp.maxZ); #endif + CHECK_GL_ERROR_IF_DEBUG(); break; } case GLRRenderCommand::SCISSOR: @@ -694,6 +719,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step) { if (!curFB_) y = curFBHeight_ - y - c.scissor.rc.h; glScissor(c.scissor.rc.x, y, c.scissor.rc.w, c.scissor.rc.h); + CHECK_GL_ERROR_IF_DEBUG(); break; } case GLRRenderCommand::UNIFORM4F: @@ -718,6 +744,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step) { break; } } + CHECK_GL_ERROR_IF_DEBUG(); break; } case GLRRenderCommand::UNIFORM4I: @@ -742,6 +769,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step) { break; } } + CHECK_GL_ERROR_IF_DEBUG(); break; } case GLRRenderCommand::UNIFORMMATRIX: @@ -753,6 +781,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step) { if (loc >= 0) { glUniformMatrix4fv(loc, 1, false, c.uniformMatrix4.m); } + CHECK_GL_ERROR_IF_DEBUG(); break; } case GLRRenderCommand::BINDTEXTURE: @@ -798,6 +827,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step) { glUseProgram(c.program.program->program); curProgram = c.program.program; } + CHECK_GL_ERROR_IF_DEBUG(); break; } case GLRRenderCommand::BIND_VERTEX_BUFFER: @@ -861,6 +891,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step) { } else { glDrawElementsInstanced(c.drawIndexed.mode, c.drawIndexed.count, c.drawIndexed.indexType, c.drawIndexed.indices, c.drawIndexed.instances); } + CHECK_GL_ERROR_IF_DEBUG(); break; case GLRRenderCommand::TEXTURESAMPLER: { @@ -949,6 +980,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step) { glDisable(GL_DITHER); ditherEnabled = false; } + CHECK_GL_ERROR_IF_DEBUG(); break; default: Crash(); @@ -966,6 +998,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step) { glActiveTexture(GL_TEXTURE0); activeSlot = 0; // doesn't matter, just nice. } + CHECK_GL_ERROR_IF_DEBUG(); // Wipe out the current state. glBindBuffer(GL_ARRAY_BUFFER, 0); @@ -986,6 +1019,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step) { } void GLQueueRunner::PerformCopy(const GLRStep &step) { + CHECK_GL_ERROR_IF_DEBUG(); GLuint srcTex = 0; GLuint dstTex = 0; GLuint target = GL_TEXTURE_2D; @@ -1047,24 +1081,39 @@ void GLQueueRunner::PerformCopy(const GLRStep &step) { void GLQueueRunner::PerformReadback(const GLRStep &pass) { using namespace Draw; + CHECK_GL_ERROR_IF_DEBUG(); GLRFramebuffer *fb = pass.readback.src; fbo_bind_fb_target(true, fb ? fb->handle : 0); - // Reads from the "bound for read" framebuffer. - if (gl_extensions.GLES3 || !gl_extensions.IsGLES) + // Reads from the "bound for read" framebuffer. Note that if there's no fb, it's not valid to call this. + if (fb && (gl_extensions.GLES3 || !gl_extensions.IsGLES)) glReadBuffer(GL_COLOR_ATTACHMENT0); CHECK_GL_ERROR_IF_DEBUG(); - // Always read back in 8888 format. - const GLuint internalFormat = GL_RGBA; - const GLuint format = GL_RGBA; - const GLuint type = GL_UNSIGNED_BYTE; - const int srcAlignment = 4; + // Always read back in 8888 format for the color aspect. + GLuint internalFormat = GL_RGBA; + GLuint format = GL_RGBA; + GLuint type = GL_UNSIGNED_BYTE; + int srcAlignment = 4; int dstAlignment = (int)DataFormatSizeInBytes(pass.readback.dstFormat); +#ifndef USING_GLES2 + if (pass.readback.aspectMask & GL_DEPTH_BUFFER_BIT) { + internalFormat = GL_DEPTH_COMPONENT; + format = GL_DEPTH_COMPONENT; + type = GL_FLOAT; + srcAlignment = 4; + } else if (pass.readback.aspectMask & GL_STENCIL_BUFFER_BIT) { + internalFormat = GL_STENCIL_INDEX; + format = GL_STENCIL_INDEX; + type = GL_UNSIGNED_BYTE; + srcAlignment = 1; + } +#endif + int pixelStride = pass.readback.srcRect.w; // Apply the correct alignment. glPixelStorei(GL_PACK_ALIGNMENT, srcAlignment); @@ -1075,7 +1124,7 @@ void GLQueueRunner::PerformReadback(const GLRStep &pass) { GLRect2D rect = pass.readback.srcRect; - bool convert = pass.readback.dstFormat != DataFormat::R8G8B8A8_UNORM; + bool convert = internalFormat == GL_RGBA && pass.readback.dstFormat != DataFormat::R8G8B8A8_UNORM; int tempSize = srcAlignment * rect.w * rect.h; int readbackSize = dstAlignment * rect.w * rect.h; @@ -1149,6 +1198,7 @@ void GLQueueRunner::PerformBindFramebufferAsRenderTarget(const GLRStep &pass) { fbo_unbind(); // Backbuffer is now bound. } + CHECK_GL_ERROR_IF_DEBUG(); } void GLQueueRunner::CopyReadbackBuffer(int width, int height, Draw::DataFormat srcFormat, Draw::DataFormat destFormat, int pixelStride, uint8_t *pixels) { @@ -1168,6 +1218,7 @@ GLuint GLQueueRunner::AllocTextureName() { } u32 name = nameCache_.back(); nameCache_.pop_back(); + CHECK_GL_ERROR_IF_DEBUG(); return name; } @@ -1178,6 +1229,8 @@ GLuint GLQueueRunner::AllocTextureName() { void GLQueueRunner::fbo_ext_create(const GLRInitStep &step) { GLRFramebuffer *fbo = step.create_framebuffer.framebuffer; + CHECK_GL_ERROR_IF_DEBUG(); + // Color texture is same everywhere glGenFramebuffersEXT(1, &fbo->handle); glGenTextures(1, &fbo->color_texture.texture); @@ -1230,6 +1283,8 @@ void GLQueueRunner::fbo_ext_create(const GLRInitStep &step) { glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0); glBindTexture(GL_TEXTURE_2D, 0); + CHECK_GL_ERROR_IF_DEBUG(); + currentDrawHandle_ = fbo->handle; currentReadHandle_ = fbo->handle; } @@ -1295,6 +1350,7 @@ void GLQueueRunner::fbo_unbind() { } GLRFramebuffer::~GLRFramebuffer() { + CHECK_GL_ERROR_IF_DEBUG(); if (gl_extensions.ARB_framebuffer_object || gl_extensions.IsGLES) { if (handle) { glBindFramebuffer(GL_FRAMEBUFFER, handle); @@ -1326,4 +1382,5 @@ GLRFramebuffer::~GLRFramebuffer() { glDeleteRenderbuffers(1, &stencil_buffer); #endif } + CHECK_GL_ERROR_IF_DEBUG(); } diff --git a/ext/native/thin3d/GLRenderManager.cpp b/ext/native/thin3d/GLRenderManager.cpp index 1e986b4195..75de166d37 100644 --- a/ext/native/thin3d/GLRenderManager.cpp +++ b/ext/native/thin3d/GLRenderManager.cpp @@ -14,9 +14,11 @@ #endif static std::thread::id renderThreadId; +#if MAX_LOGLEVEL >= DEBUG_LEVEL static bool OnRenderThread() { return std::this_thread::get_id() == renderThreadId; } +#endif // Runs on the GPU thread. void GLDeleter::Perform(GLRenderManager *renderManager) { @@ -576,7 +578,7 @@ void GLRenderManager::WaitUntilQueueIdle() { GLPushBuffer::GLPushBuffer(GLRenderManager *render, GLuint target, size_t size) : render_(render), target_(target), size_(size) { bool res = AddBuffer(); - assert(res); + _assert_(res); } GLPushBuffer::~GLPushBuffer() { @@ -698,7 +700,7 @@ void GLPushBuffer::NextBuffer(size_t minSize) { } void GLPushBuffer::Defragment() { - _dbg_assert_(G3D, std::this_thread::get_id() != renderThreadId); + _dbg_assert_(G3D, !OnRenderThread()); if (buffers_.size() <= 1) { // Let's take this chance to jetison localMemory we don't need. @@ -718,7 +720,7 @@ void GLPushBuffer::Defragment() { size_ = newSize; bool res = AddBuffer(); - assert(res); + _assert_(res); } size_t GLPushBuffer::GetTotalSize() const { diff --git a/ext/native/thin3d/VulkanQueueRunner.cpp b/ext/native/thin3d/VulkanQueueRunner.cpp index 2737ff59ee..2e122de544 100644 --- a/ext/native/thin3d/VulkanQueueRunner.cpp +++ b/ext/native/thin3d/VulkanQueueRunner.cpp @@ -55,7 +55,7 @@ void VulkanQueueRunner::ResizeReadbackBuffer(VkDeviceSize requiredSize) { VkFlags typeReqs = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; bool success = vulkan_->MemoryTypeFromProperties(reqs.memoryTypeBits, typeReqs, &alloc.memoryTypeIndex); - assert(success); + _assert_(success); vkAllocateMemory(device, &alloc, nullptr, &readbackMemory_); uint32_t offset = 0; @@ -64,7 +64,6 @@ void VulkanQueueRunner::ResizeReadbackBuffer(VkDeviceSize requiredSize) { void VulkanQueueRunner::DestroyDeviceObjects() { ILOG("VulkanQueueRunner::DestroyDeviceObjects"); - VkDevice device = vulkan_->GetDevice(); vulkan_->Delete().QueueDeleteDeviceMemory(readbackMemory_); vulkan_->Delete().QueueDeleteBuffer(readbackBuffer_); readbackBufferSize_ = 0; @@ -150,7 +149,7 @@ void VulkanQueueRunner::InitBackbufferRenderPass() { rp_info.pDependencies = &dep; VkResult res = vkCreateRenderPass(vulkan_->GetDevice(), &rp_info, nullptr, &backbufferRenderPass_); - assert(res == VK_SUCCESS); + _assert_(res == VK_SUCCESS); } VkRenderPass VulkanQueueRunner::GetRenderPass(const RPKey &key) { @@ -318,6 +317,9 @@ VkRenderPass VulkanQueueRunner::GetRenderPass(const RPKey &key) { case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: // Nothing to do. break; + default: + _dbg_assert_msg_(G3D, false, "GetRenderPass: Unexpected final color layout %d", (int)key.finalColorLayout); + break; } if (deps[numDeps].dstAccessMask) { @@ -353,8 +355,6 @@ void VulkanQueueRunner::RunSteps(VkCommandBuffer cmd, std::vector &st // * Create copies of render target that are rendered to multiple times and textured from in sequence, and push those render passes // as early as possible in the frame (Wipeout billboards). - // Push down empty "Clear/Store" renderpasses, and merge them with the first "Load/Store" to the same framebuffer. - // Actually let's just bother with the first one for now. This affects Wipeout Pure. for (int j = 0; j < (int)steps.size() - 1; j++) { if (steps[j]->stepType == VKRStepType::RENDER && steps[j]->render.finalColorLayout == VK_IMAGE_LAYOUT_UNDEFINED) { @@ -362,8 +362,11 @@ void VulkanQueueRunner::RunSteps(VkCommandBuffer cmd, std::vector &st steps[j]->render.finalColorLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; } + // Push down empty "Clear/Store" renderpasses, and merge them with the first "Load/Store" to the same framebuffer. + // Actually let's just bother with the first one for now. This affects Wipeout Pure. if (steps.size() > 1 && steps[j]->stepType == VKRStepType::RENDER && steps[j]->render.numDraws == 0 && + steps[j]->render.numReads == 0 && steps[j]->render.color == VKRRenderPassAction::CLEAR && steps[j]->render.stencil == VKRRenderPassAction::CLEAR && steps[j]->render.depth == VKRRenderPassAction::CLEAR) { @@ -391,6 +394,7 @@ void VulkanQueueRunner::RunSteps(VkCommandBuffer cmd, std::vector &st steps[i]->copy.src == steps[j]->render.framebuffer) { // Can't eliminate the clear if a game copies from it before it's // rendered to. However this should be rare. + // TODO: This should never happen when we check numReads now. break; } } @@ -461,6 +465,8 @@ void VulkanQueueRunner::ApplyMGSHack(std::vector &steps) { if (steps[j]->copy.dst != steps[i]->copy.dst) last = j - 1; break; + default: + break; } if (last != -1) break; @@ -533,6 +539,9 @@ void VulkanQueueRunner::ApplySonicHack(std::vector &steps) { if (steps[j]->render.numDraws != 3 && steps[j]->render.numDraws != 6) last = j - 1; } + break; + default: + break; } if (last != -1) break; @@ -611,6 +620,10 @@ void VulkanQueueRunner::LogRenderPass(const VKRStep &pass) { ILOG("RenderPass Begin(%x)", fb); for (auto &cmd : pass.commands) { switch (cmd.cmd) { + case VKRRenderCommand::REMOVED: + ILOG(" (Removed)"); + break; + case VKRRenderCommand::BIND_PIPELINE: ILOG(" BindPipeline(%x)", (int)(intptr_t)cmd.pipeline.pipeline); break; @@ -638,6 +651,9 @@ void VulkanQueueRunner::LogRenderPass(const VKRStep &pass) { case VKRRenderCommand::PUSH_CONSTANTS: ILOG(" PushConstants(%d)", cmd.push.size); break; + + case VKRRenderCommand::NUM_RENDER_COMMANDS: + break; } } ILOG("RenderPass End(%x)", fb); @@ -669,9 +685,8 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c barrier.subresourceRange.layerCount = 1; barrier.subresourceRange.levelCount = 1; barrier.image = iter.fb->color.image; - barrier.srcAccessMask = 0; - VkPipelineStageFlags srcStage; - VkPipelineStageFlags dstStage; + VkPipelineStageFlags srcStage{}; + VkPipelineStageFlags dstStage{}; switch (barrier.oldLayout) { case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT; @@ -686,7 +701,7 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT; break; default: - Crash(); + _assert_msg_(G3D, false, "PerformRenderPass: Unexpected oldLayout: %d", (int)barrier.oldLayout); break; } barrier.newLayout = iter.targetLayout; @@ -696,7 +711,7 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c dstStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; break; default: - Crash(); + _assert_msg_(G3D, false, "PerformRenderPass: Unexpected newLayout: %d", (int)barrier.newLayout); break; } barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; @@ -723,7 +738,6 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c VKRFramebuffer *fb = step.render.framebuffer; VkPipeline lastPipeline = VK_NULL_HANDLE; - VkDescriptorSet lastDescSet = VK_NULL_HANDLE; auto &commands = step.commands; @@ -733,6 +747,9 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c for (const auto &c : commands) { switch (c.cmd) { + case VKRRenderCommand::REMOVED: + break; + case VKRRenderCommand::BIND_PIPELINE: if (c.pipeline.pipeline != lastPipeline) { vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, c.pipeline.pipeline); @@ -841,7 +858,7 @@ void VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKRStep &step // See pull request #10723. bool maliBugWorkaround = step.render.numDraws == 0 && step.render.color == VKRRenderPassAction::CLEAR && - vulkan_->GetPhysicalDeviceProperties().driverVersion == 0xaa9c4b29; + vulkan_->GetPhysicalDeviceProperties(vulkan_->GetCurrentPhysicalDevice()).driverVersion == 0xaa9c4b29; if (maliBugWorkaround) { TransitionImageLayout2(cmd, step.render.framebuffer->color.image, 0, 1, VK_IMAGE_ASPECT_COLOR_BIT, fb->color.layout, VK_IMAGE_LAYOUT_GENERAL, @@ -1077,7 +1094,12 @@ void VulkanQueueRunner::SetupTransitionToTransferSrc(VKRImage &img, VkImageMemor } barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; - barrier.subresourceRange.aspectMask = aspect; + if (img.format == VK_FORMAT_D16_UNORM_S8_UINT || img.format == VK_FORMAT_D24_UNORM_S8_UINT || img.format == VK_FORMAT_D32_SFLOAT_S8_UINT) { + // Barrier must specify both for combined depth/stencil buffers. + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + } else { + barrier.subresourceRange.aspectMask = aspect; + } barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; img.layout = barrier.newLayout; @@ -1131,7 +1153,12 @@ void VulkanQueueRunner::SetupTransitionToTransferDst(VKRImage &img, VkImageMemor } barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - barrier.subresourceRange.aspectMask = aspect; + if (img.format == VK_FORMAT_D16_UNORM_S8_UINT || img.format == VK_FORMAT_D24_UNORM_S8_UINT || img.format == VK_FORMAT_D32_SFLOAT_S8_UINT) { + // Barrier must specify both for combined depth/stencil buffers. + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + } else { + barrier.subresourceRange.aspectMask = aspect; + } barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; img.layout = barrier.newLayout; @@ -1173,7 +1200,7 @@ void VulkanQueueRunner::PerformReadback(const VKRStep &step, VkCommandBuffer cmd if (step.readback.src == nullptr) { // We only take screenshots after the main render pass (anything else would be stupid) so we need to transition out of PRESENT, // and then back into it. - TransitionImageLayout2(cmd, backbufferImage_, VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, + TransitionImageLayout2(cmd, backbufferImage_, 0, 1, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, VK_ACCESS_TRANSFER_READ_BIT); @@ -1183,11 +1210,9 @@ void VulkanQueueRunner::PerformReadback(const VKRStep &step, VkCommandBuffer cmd VKRImage *srcImage; if (step.readback.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) { srcImage = &step.readback.src->color; - } - else if (step.readback.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { + } else if (step.readback.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { srcImage = &step.readback.src->depth; - } - else { + } else { _dbg_assert_msg_(G3D, false, "No image aspect to readback?"); return; } @@ -1210,7 +1235,7 @@ void VulkanQueueRunner::PerformReadback(const VKRStep &step, VkCommandBuffer cmd if (step.readback.src == nullptr) { // We only take screenshots after the main render pass (anything else would be stupid) so we need to transition out of PRESENT, // and then back into it. - TransitionImageLayout2(cmd, backbufferImage_, VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, + TransitionImageLayout2(cmd, backbufferImage_, 0, 1, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_READ_BIT, 0); diff --git a/ext/native/thin3d/VulkanQueueRunner.h b/ext/native/thin3d/VulkanQueueRunner.h index d743bda730..c01873f99a 100644 --- a/ext/native/thin3d/VulkanQueueRunner.h +++ b/ext/native/thin3d/VulkanQueueRunner.h @@ -16,6 +16,7 @@ enum { }; enum class VKRRenderCommand : uint8_t { + REMOVED, BIND_PIPELINE, STENCIL, BLEND, @@ -25,6 +26,7 @@ enum class VKRRenderCommand : uint8_t { DRAW, DRAW_INDEXED, PUSH_CONSTANTS, + NUM_RENDER_COMMANDS, }; struct VkRenderData { @@ -120,6 +122,8 @@ struct VKRStep { float clearDepth; int clearStencil; int numDraws; + // Downloads and textures from this pass. + int numReads; VkImageLayout finalColorLayout; } render; struct { @@ -244,7 +248,6 @@ private: VkFramebuffer backbuffer_; VkImage backbufferImage_; - VkFramebuffer curFramebuffer_ = VK_NULL_HANDLE; VkRenderPass backbufferRenderPass_ = VK_NULL_HANDLE; VkRenderPass framebufferRenderPass_ = VK_NULL_HANDLE; diff --git a/ext/native/thin3d/VulkanRenderManager.cpp b/ext/native/thin3d/VulkanRenderManager.cpp index 31ccb18630..3609cbddb7 100644 --- a/ext/native/thin3d/VulkanRenderManager.cpp +++ b/ext/native/thin3d/VulkanRenderManager.cpp @@ -1,3 +1,4 @@ +#include #include #include "Common/Log.h" @@ -91,7 +92,7 @@ void CreateImage(VulkanContext *vulkan, VkCommandBuffer cmd, VKRImage &img, int break; default: Crash(); - break; + return; } TransitionImageLayout2(cmd, img.image, 0, 1, aspects, @@ -136,7 +137,7 @@ VulkanRenderManager::VulkanRenderManager(VulkanContext *vulkan) : vulkan_(vulkan queueRunner_.CreateDeviceObjects(); // Temporary AMD hack for issue #10097 - if (vulkan_->GetPhysicalDeviceProperties().vendorID == VULKAN_VENDOR_AMD) { + if (vulkan_->GetPhysicalDeviceProperties(vulkan_->GetCurrentPhysicalDevice()).vendorID == VULKAN_VENDOR_AMD) { if (!g_Config.bVulkanMultithreading) { useThread_ = false; } @@ -265,7 +266,6 @@ void VulkanRenderManager::DestroyBackbuffers() { StopThread(); vulkan_->WaitUntilQueueIdle(); - VkDevice device = vulkan_->GetDevice(); for (uint32_t i = 0; i < swapchainImageCount_; i++) { vulkan_->Delete().QueueDeleteImageView(swapchainImages_[i].view); } @@ -291,7 +291,6 @@ VulkanRenderManager::~VulkanRenderManager() { vkDestroySemaphore(device, acquireSemaphore_, nullptr); vkDestroySemaphore(device, renderingCompleteSemaphore_, nullptr); for (int i = 0; i < vulkan_->GetInflightFrames(); i++) { - VkCommandBuffer cmdBuf[2]{ frameData_[i].mainCmd, frameData_[i].initCmd }; vkFreeCommandBuffers(device, frameData_[i].cmdPoolInit, 1, &frameData_[i].initCmd); vkFreeCommandBuffers(device, frameData_[i].cmdPoolMain, 1, &frameData_[i].mainCmd); vkDestroyCommandPool(device, frameData_[i].cmdPoolInit, nullptr); @@ -388,7 +387,7 @@ VkCommandBuffer VulkanRenderManager::GetInitCmd() { VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT }; VkResult res = vkBeginCommandBuffer(frameData.initCmd, &begin); - assert(res == VK_SUCCESS); + _assert_(res == VK_SUCCESS); frameData.hasInitCommands = true; } return frameData_[curFrame].initCmd; @@ -424,6 +423,7 @@ void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRR step->render.clearDepth = clearDepth; step->render.clearStencil = clearStencil; step->render.numDraws = 0; + step->render.numReads = 0; step->render.finalColorLayout = !fb ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED; steps_.push_back(step); @@ -433,6 +433,13 @@ void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRR } bool VulkanRenderManager::CopyFramebufferToMemorySync(VKRFramebuffer *src, int aspectBits, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride) { + for (int i = (int)steps_.size() - 1; i >= 0; i--) { + if (steps_[i]->stepType == VKRStepType::RENDER && steps_[i]->render.framebuffer == src) { + steps_[i]->render.numReads++; + break; + } + } + VKRStep *step = new VKRStep{ VKRStepType::READBACK }; step->readback.aspectMask = aspectBits; step->readback.src = src; @@ -444,15 +451,14 @@ bool VulkanRenderManager::CopyFramebufferToMemorySync(VKRFramebuffer *src, int a FlushSync(); - Draw::DataFormat srcFormat; + Draw::DataFormat srcFormat = Draw::DataFormat::UNDEFINED; if (aspectBits & VK_IMAGE_ASPECT_COLOR_BIT) { if (src) { switch (src->color.format) { case VK_FORMAT_R8G8B8A8_UNORM: srcFormat = Draw::DataFormat::R8G8B8A8_UNORM; break; default: _assert_(false); } - } - else { + } else { // Backbuffer. if (!(vulkan_->GetSurfaceCapabilities().supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT)) { ELOG("Copying from backbuffer not supported, can't take screenshots"); @@ -615,18 +621,84 @@ bool VulkanRenderManager::InitDepthStencilBuffer(VkCommandBuffer cmd) { return true; } +static void RemoveDrawCommands(std::vector *cmds) { + // Here we remove any DRAW type commands when we hit a CLEAR. + for (auto &c : *cmds) { + if (c.cmd == VKRRenderCommand::DRAW || c.cmd == VKRRenderCommand::DRAW_INDEXED) { + c.cmd = VKRRenderCommand::REMOVED; + } + } +} + +static void CleanupRenderCommands(std::vector *cmds) { + size_t lastCommand[(int)VKRRenderCommand::NUM_RENDER_COMMANDS]; + memset(lastCommand, -1, sizeof(lastCommand)); + + // Find any duplicate state commands (likely from RemoveDrawCommands.) + for (size_t i = 0; i < cmds->size(); ++i) { + auto &c = cmds->at(i); + auto &lastOfCmd = lastCommand[(uint8_t)c.cmd]; + + switch (c.cmd) { + case VKRRenderCommand::REMOVED: + continue; + + case VKRRenderCommand::BIND_PIPELINE: + case VKRRenderCommand::VIEWPORT: + case VKRRenderCommand::SCISSOR: + case VKRRenderCommand::BLEND: + case VKRRenderCommand::STENCIL: + if (lastOfCmd != -1) { + cmds->at(lastOfCmd).cmd = VKRRenderCommand::REMOVED; + } + break; + + case VKRRenderCommand::PUSH_CONSTANTS: + // TODO: For now, we have to keep this one (it has an offset.) Still update lastCommand. + break; + + case VKRRenderCommand::CLEAR: + // Ignore, doesn't participate in state. + continue; + + case VKRRenderCommand::DRAW_INDEXED: + case VKRRenderCommand::DRAW: + default: + // Boundary - must keep state before this. + memset(lastCommand, -1, sizeof(lastCommand)); + continue; + } + + lastOfCmd = i; + } + + // At this point, anything in lastCommand can be cleaned up too. + // Note that it's safe to remove the last unused PUSH_CONSTANTS here. + for (size_t i = 0; i < ARRAY_SIZE(lastCommand); ++i) { + auto &lastOfCmd = lastCommand[i]; + if (lastOfCmd != -1) { + cmds->at(lastOfCmd).cmd = VKRRenderCommand::REMOVED; + } + } +} + void VulkanRenderManager::Clear(uint32_t clearColor, float clearZ, int clearStencil, int clearMask) { _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER); if (!clearMask) return; - // If this is the first drawing command, merge it into the pass. - if (curRenderStep_->render.numDraws == 0) { + // If this is the first drawing command or clears everything, merge it into the pass. + int allAspects = VK_IMAGE_ASPECT_COLOR_BIT | VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + if (curRenderStep_->render.numDraws == 0 || clearMask == allAspects) { curRenderStep_->render.clearColor = clearColor; curRenderStep_->render.clearDepth = clearZ; curRenderStep_->render.clearStencil = clearStencil; curRenderStep_->render.color = (clearMask & VK_IMAGE_ASPECT_COLOR_BIT) ? VKRRenderPassAction::CLEAR : VKRRenderPassAction::KEEP; curRenderStep_->render.depth = (clearMask & VK_IMAGE_ASPECT_DEPTH_BIT) ? VKRRenderPassAction::CLEAR : VKRRenderPassAction::KEEP; curRenderStep_->render.stencil = (clearMask & VK_IMAGE_ASPECT_STENCIL_BIT) ? VKRRenderPassAction::CLEAR : VKRRenderPassAction::KEEP; + + // In case there were commands already. + curRenderStep_->render.numDraws = 0; + RemoveDrawCommands(&curRenderStep_->commands); } else { VkRenderData data{ VKRRenderCommand::CLEAR }; data.clear.clearColor = clearColor; @@ -656,6 +728,7 @@ void VulkanRenderManager::CopyFramebuffer(VKRFramebuffer *src, VkRect2D srcRect, if (steps_[i]->render.finalColorLayout == VK_IMAGE_LAYOUT_UNDEFINED) { steps_[i]->render.finalColorLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; } + steps_[i]->render.numReads++; break; } } @@ -698,6 +771,13 @@ void VulkanRenderManager::BlitFramebuffer(VKRFramebuffer *src, VkRect2D srcRect, _dbg_assert_msg_(G3D, dstRect.extent.width > 0, "blit dstwidth == 0"); _dbg_assert_msg_(G3D, dstRect.extent.height > 0, "blit dstheight == 0"); + for (int i = (int)steps_.size() - 1; i >= 0; i--) { + if (steps_[i]->stepType == VKRStepType::RENDER && steps_[i]->render.framebuffer == src) { + steps_[i]->render.numReads++; + break; + } + } + VKRStep *step = new VKRStep{ VKRStepType::BLIT }; step->blit.aspectMask = aspectMask; @@ -720,6 +800,7 @@ VkImageView VulkanRenderManager::BindFramebufferAsTexture(VKRFramebuffer *fb, in if (steps_[i]->render.finalColorLayout == VK_IMAGE_LAYOUT_UNDEFINED) { steps_[i]->render.finalColorLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; } + steps_[i]->render.numReads++; break; } } @@ -736,6 +817,14 @@ VkImageView VulkanRenderManager::BindFramebufferAsTexture(VKRFramebuffer *fb, in void VulkanRenderManager::Finish() { curRenderStep_ = nullptr; + + // Let's do just a bit of cleanup on render commands now. + for (auto &step : steps_) { + if (step->stepType == VKRStepType::RENDER) { + CleanupRenderCommands(&step->commands); + } + } + int curFrame = vulkan_->GetCurFrame(); FrameData &frameData = frameData_[curFrame]; if (!useThread_) { @@ -922,7 +1011,7 @@ void VulkanRenderManager::EndSyncFrame(int frame) { VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT }; VkResult res = vkBeginCommandBuffer(frameData.mainCmd, &begin); - assert(res == VK_SUCCESS); + _assert_(res == VK_SUCCESS); if (useThread_) { std::unique_lock lock(frameData.push_mutex); diff --git a/ext/native/thin3d/thin3d.cpp b/ext/native/thin3d/thin3d.cpp index b877eb6765..ddc70e2c7e 100644 --- a/ext/native/thin3d/thin3d.cpp +++ b/ext/native/thin3d/thin3d.cpp @@ -6,6 +6,7 @@ #include "thin3d/thin3d.h" #include "Common/Log.h" #include "Common/ColorConv.h" +#include "Core/Reporting.h" namespace Draw { @@ -226,7 +227,7 @@ static const std::vector vsCol = { "}\n" }, { ShaderLanguage::GLSL_VULKAN, - "#version 400\n" + "#version 450\n" "#extension GL_ARB_separate_shader_objects : enable\n" "#extension GL_ARB_shading_language_420pack : enable\n" "layout (std140, set = 0, binding = 0) uniform bufferVals {\n" @@ -292,7 +293,7 @@ static const std::vector vsTexCol = { "}\n" }, { ShaderLanguage::GLSL_VULKAN, - "#version 400\n" + "#version 450\n" "#extension GL_ARB_separate_shader_objects : enable\n" "#extension GL_ARB_shading_language_420pack : enable\n" "layout (std140, set = 0, binding = 0) uniform bufferVals {\n" @@ -372,6 +373,14 @@ void ConvertFromRGBA8888(uint8_t *dst, const uint8_t *src, uint32_t dstStride, u dst32 += dstStride; } } + } else if (format == Draw::DataFormat::R8G8B8_UNORM) { + for (uint32_t y = 0; y < height; ++y) { + for (uint32_t x = 0; x < width; ++x) { + memcpy(dst + x * 3, src32 + x, 3); + } + src32 += srcStride; + dst += dstStride * 3; + } } else { // But here it shouldn't matter if they do intersect uint16_t *dst16 = (uint16_t *)dst; @@ -400,7 +409,7 @@ void ConvertFromRGBA8888(uint8_t *dst, const uint8_t *src, uint32_t dstStride, u case Draw::DataFormat::R8G8B8A8_UNORM: case Draw::DataFormat::UNDEFINED: default: - // Not possible. + WARN_LOG_REPORT_ONCE(convFromRGBA, G3D, "Unable to convert from format: %d", (int)format); break; } } @@ -412,19 +421,40 @@ void ConvertFromBGRA8888(uint8_t *dst, const uint8_t *src, uint32_t dstStride, u // Must skip stride in the cases below. Some games pack data into the cracks, like MotoGP. const uint32_t *src32 = (const uint32_t *)src; - if (format == Draw::DataFormat::R8G8B8A8_UNORM) { + if (format == Draw::DataFormat::B8G8R8A8_UNORM) { + uint32_t *dst32 = (uint32_t *)dst; + if (src == dst) { + return; + } else { + for (uint32_t y = 0; y < height; ++y) { + memcpy(dst32, src32, width * 4); + src32 += srcStride; + dst32 += dstStride; + } + } + } else if (format == Draw::DataFormat::R8G8B8A8_UNORM) { uint32_t *dst32 = (uint32_t *)dst; for (uint32_t y = 0; y < height; ++y) { ConvertBGRA8888ToRGBA8888(dst32, src32, width); src32 += srcStride; dst32 += dstStride; } - } - else { - // Don't even bother with these, this path only happens in screenshots and we don't save those to 16-bit. - assert(false); + } else if (format == Draw::DataFormat::R8G8B8_UNORM) { + for (uint32_t y = 0; y < height; ++y) { + for (uint32_t x = 0; x < width; ++x) { + uint32_t c = src32[x]; + dst[x * 3 + 0] = (c >> 16) & 0xFF; + dst[x * 3 + 1] = (c >> 8) & 0xFF; + dst[x * 3 + 2] = (c >> 0) & 0xFF; + } + src32 += srcStride; + dst += dstStride * 3; + } + } else { + WARN_LOG_REPORT_ONCE(convFromBGRA, G3D, "Unable to convert from format to BGRA: %d", (int)format); } } + void ConvertToD32F(uint8_t *dst, const uint8_t *src, uint32_t dstStride, uint32_t srcStride, uint32_t width, uint32_t height, DataFormat format) { if (format == Draw::DataFormat::D32F) { const float *src32 = (const float *)src; diff --git a/ext/native/thin3d/thin3d.h b/ext/native/thin3d/thin3d.h index 2aa081c273..73088939fb 100644 --- a/ext/native/thin3d/thin3d.h +++ b/ext/native/thin3d/thin3d.h @@ -2,7 +2,7 @@ // Does not involve context creation etc, that should be handled separately - only does drawing. // The goals may change in the future though. -// MIT licensed, by Henrik Rydgård 2014. +// MIT licensed, by Henrik RydgÃ¥rd 2014. #pragma once @@ -258,6 +258,7 @@ enum class NativeObject { BOUND_TEXTURE0_IMAGEVIEW, BOUND_TEXTURE1_IMAGEVIEW, RENDER_MANAGER, + NULL_IMAGEVIEW, }; enum FBColorDepth { @@ -490,6 +491,7 @@ struct DeviceCaps { bool framebufferBlitSupported; bool framebufferDepthCopySupported; bool framebufferDepthBlitSupported; + std::string deviceName; // The device name to use when creating the thin3d context, to get the same one. }; struct TextureDesc { @@ -531,6 +533,7 @@ public: virtual uint32_t GetDataFormatSupport(DataFormat fmt) const = 0; virtual std::vector GetFeatureList() const { return std::vector(); } virtual std::vector GetExtensionList() const { return std::vector(); } + virtual std::vector GetDeviceList() const { return std::vector(); } virtual uint32_t GetSupportedShaderLanguages() const = 0; @@ -564,6 +567,9 @@ public: virtual bool CopyFramebufferToMemorySync(Framebuffer *src, int channelBits, int x, int y, int w, int h, Draw::DataFormat format, void *pixels, int pixelStride) { return false; } + virtual DataFormat PreferredFramebufferReadbackFormat(Framebuffer *src) { + return DataFormat::R8G8B8A8_UNORM; + } // These functions should be self explanatory. // Binding a zero render target means binding the backbuffer. @@ -597,7 +603,8 @@ public: virtual void UpdateDynamicUniformBuffer(const void *ub, size_t size) = 0; void BindTexture(int stage, Texture *texture) { - BindTextures(stage, 1, &texture); + Texture *textures[1] = { texture }; + BindTextures(stage, 1, textures); } // from sampler 0 and upwards // Call this with 0 to signal that you have been drawing on your own, and need the state reset on the next pipeline bind. diff --git a/ext/native/thin3d/thin3d_create.h b/ext/native/thin3d/thin3d_create.h index 3d4ea0f1a4..a89c74ab96 100644 --- a/ext/native/thin3d/thin3d_create.h +++ b/ext/native/thin3d/thin3d_create.h @@ -1,5 +1,7 @@ #pragma once +#include +#include #include "thin3d/thin3d.h" // Separated this stuff into its own file so we don't get Windows.h included if all we want is the thin3d declarations. @@ -30,9 +32,9 @@ DrawContext *T3DCreateGLContext(); #ifdef _WIN32 DrawContext *T3DCreateDX9Context(IDirect3D9 *d3d, IDirect3D9Ex *d3dEx, int adapterId, IDirect3DDevice9 *device, IDirect3DDevice9Ex *deviceEx); -DrawContext *T3DCreateD3D11Context(ID3D11Device *device, ID3D11DeviceContext *context, ID3D11Device1 *device1, ID3D11DeviceContext1 *context1, D3D_FEATURE_LEVEL featureLevel, HWND hWnd); +DrawContext *T3DCreateD3D11Context(ID3D11Device *device, ID3D11DeviceContext *context, ID3D11Device1 *device1, ID3D11DeviceContext1 *context1, D3D_FEATURE_LEVEL featureLevel, HWND hWnd, std::vector adapterNames); #endif -DrawContext *T3DCreateVulkanContext(VulkanContext *context, bool split); +DrawContext *T3DCreateVulkanContext(VulkanContext *context, bool splitSubmit); } // namespace Draw diff --git a/ext/native/thin3d/thin3d_d3d11.cpp b/ext/native/thin3d/thin3d_d3d11.cpp index 5e6e7f7ec3..4978568344 100644 --- a/ext/native/thin3d/thin3d_d3d11.cpp +++ b/ext/native/thin3d/thin3d_d3d11.cpp @@ -38,12 +38,15 @@ class D3D11RasterState; class D3D11DrawContext : public DrawContext { public: - D3D11DrawContext(ID3D11Device *device, ID3D11DeviceContext *deviceContext, ID3D11Device1 *device1, ID3D11DeviceContext1 *deviceContext1, D3D_FEATURE_LEVEL featureLevel, HWND hWnd); + D3D11DrawContext(ID3D11Device *device, ID3D11DeviceContext *deviceContext, ID3D11Device1 *device1, ID3D11DeviceContext1 *deviceContext1, D3D_FEATURE_LEVEL featureLevel, HWND hWnd, std::vector deviceList); ~D3D11DrawContext(); const DeviceCaps &GetDeviceCaps() const override { return caps_; } + std::vector GetDeviceList() const override { + return deviceList_; + } uint32_t GetSupportedShaderLanguages() const override { return (uint32_t)ShaderLanguage::HLSL_D3D11 | (uint32_t)ShaderLanguage::HLSL_D3D11_BYTECODE; } @@ -210,15 +213,17 @@ private: // System info D3D_FEATURE_LEVEL featureLevel_; std::string adapterDesc_; + std::vector deviceList_; }; -D3D11DrawContext::D3D11DrawContext(ID3D11Device *device, ID3D11DeviceContext *deviceContext, ID3D11Device1 *device1, ID3D11DeviceContext1 *deviceContext1, D3D_FEATURE_LEVEL featureLevel, HWND hWnd) +D3D11DrawContext::D3D11DrawContext(ID3D11Device *device, ID3D11DeviceContext *deviceContext, ID3D11Device1 *device1, ID3D11DeviceContext1 *deviceContext1, D3D_FEATURE_LEVEL featureLevel, HWND hWnd, std::vector deviceList) : device_(device), context_(deviceContext1), device1_(device1), context1_(deviceContext1), featureLevel_(featureLevel), - hWnd_(hWnd) { + hWnd_(hWnd), + deviceList_(deviceList) { // Seems like a fair approximation... caps_.dualSourceBlend = featureLevel_ >= D3D_FEATURE_LEVEL_10_0; @@ -229,7 +234,6 @@ D3D11DrawContext::D3D11DrawContext(ID3D11Device *device, ID3D11DeviceContext *de caps_.framebufferDepthBlitSupported = false; caps_.framebufferDepthCopySupported = true; - D3D11_FEATURE_DATA_D3D11_OPTIONS options{}; HRESULT result = device_->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS, &options, sizeof(options)); if (SUCCEEDED(result)) { @@ -239,8 +243,6 @@ D3D11DrawContext::D3D11DrawContext(ID3D11Device *device, ID3D11DeviceContext *de // caps_.logicOpSupported = true; } } - // Obtain DXGI factory from device (since we used nullptr for pAdapter above) - IDXGIFactory1* dxgiFactory = nullptr; IDXGIDevice* dxgiDevice = nullptr; IDXGIAdapter* adapter = nullptr; HRESULT hr = device_->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast(&dxgiDevice)); @@ -1363,8 +1365,19 @@ bool D3D11DrawContext::CopyFramebufferToMemorySync(Framebuffer *src, int channel if (fb) { assert(fb->colorFormat == DXGI_FORMAT_R8G8B8A8_UNORM); + + // TODO: Figure out where the badness really comes from. + if (bx + bw > fb->width) { + bw -= (bx + bw) - fb->width; + } + if (by + bh > fb->height) { + bh -= (by + bh) - fb->height; + } } + if (bh <= 0 || bw <= 0) + return true; + bool useGlobalPacktex = (bx + bw <= 512 && by + bh <= 512) && channelBits == FB_COLOR_BIT; ID3D11Texture2D *packTex; @@ -1541,8 +1554,8 @@ void D3D11DrawContext::GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h } } -DrawContext *T3DCreateD3D11Context(ID3D11Device *device, ID3D11DeviceContext *context, ID3D11Device1 *device1, ID3D11DeviceContext1 *context1, D3D_FEATURE_LEVEL featureLevel, HWND hWnd) { - return new D3D11DrawContext(device, context, device1, context1, featureLevel, hWnd); +DrawContext *T3DCreateD3D11Context(ID3D11Device *device, ID3D11DeviceContext *context, ID3D11Device1 *device1, ID3D11DeviceContext1 *context1, D3D_FEATURE_LEVEL featureLevel, HWND hWnd, std::vector adapterNames) { + return new D3D11DrawContext(device, context, device1, context1, featureLevel, hWnd, adapterNames); } } // namespace Draw diff --git a/ext/native/thin3d/thin3d_gl.cpp b/ext/native/thin3d/thin3d_gl.cpp index 18891fded6..0952366dd4 100644 --- a/ext/native/thin3d/thin3d_gl.cpp +++ b/ext/native/thin3d/thin3d_gl.cpp @@ -260,7 +260,6 @@ private: ShaderLanguage language_; GLRShader *shader_ = nullptr; GLuint glstage_ = 0; - bool ok_ = false; std::string source_; // So we can recompile in case of context loss. }; @@ -347,11 +346,10 @@ public: return caps_; } uint32_t GetSupportedShaderLanguages() const override { -#if defined(USING_GLES2) - return (uint32_t)ShaderLanguage::GLSL_ES_200 | (uint32_t)ShaderLanguage::GLSL_ES_300; -#else - return (uint32_t)ShaderLanguage::GLSL_ES_200 | (uint32_t)ShaderLanguage::GLSL_410; -#endif + if (gl_extensions.IsGLES) + return (uint32_t)ShaderLanguage::GLSL_ES_200 | (uint32_t)ShaderLanguage::GLSL_ES_300; + else + return (uint32_t)ShaderLanguage::GLSL_ES_200 | (uint32_t)ShaderLanguage::GLSL_410; } uint32_t GetDataFormatSupport(DataFormat fmt) const override; @@ -720,6 +718,10 @@ static void LogReadPixelsError(GLenum error) { #endif bool OpenGLContext::CopyFramebufferToMemorySync(Framebuffer *src, int channelBits, int x, int y, int w, int h, Draw::DataFormat dataFormat, void *pixels, int pixelStride) { + if (gl_extensions.IsGLES && (channelBits & FB_COLOR_BIT) == 0) { + // Can't readback depth or stencil on GLES. + return false; + } OpenGLFramebuffer *fb = (OpenGLFramebuffer *)src; GLuint aspect = 0; if (channelBits & FB_COLOR_BIT) @@ -858,8 +860,14 @@ Pipeline *OpenGLContext::CreateGraphicsPipeline(const PipelineDesc &desc) { } OpenGLPipeline *pipeline = new OpenGLPipeline(&renderManager_); for (auto iter : desc.shaders) { - iter->AddRef(); - pipeline->shaders.push_back(static_cast(iter)); + if (iter) { + iter->AddRef(); + pipeline->shaders.push_back(static_cast(iter)); + } else { + ELOG("ERROR: Tried to create graphics pipeline with a null shader module"); + delete pipeline; + return nullptr; + } } if (pipeline->LinkShaders()) { // Build the rest of the virtual pipeline object. @@ -878,7 +886,7 @@ Pipeline *OpenGLContext::CreateGraphicsPipeline(const PipelineDesc &desc) { } else { ELOG("Failed to create pipeline - shaders failed to link"); delete pipeline; - return NULL; + return nullptr; } } @@ -1030,8 +1038,6 @@ OpenGLInputLayout::~OpenGLInputLayout() { } void OpenGLInputLayout::Compile(const InputLayoutDesc &desc) { - int semMask = 0; - // TODO: This is only accurate if there's only one stream. But whatever, for now we // never use multiple streams anyway. stride = (GLsizei)desc.bindings[0].stride; @@ -1153,11 +1159,8 @@ uint32_t OpenGLContext::GetDataFormatSupport(DataFormat fmt) const { case DataFormat::B4G4R4A4_UNORM_PACK16: return FMT_RENDERTARGET | FMT_TEXTURE | FMT_AUTOGEN_MIPS; // native support case DataFormat::A4R4G4B4_UNORM_PACK16: -#ifndef USING_GLES2 // Can support this if _REV formats are supported. - return FMT_TEXTURE; -#endif - return 0; + return gl_extensions.IsGLES ? 0 : FMT_TEXTURE; case DataFormat::R8G8B8A8_UNORM: return FMT_RENDERTARGET | FMT_TEXTURE | FMT_INPUTLAYOUT | FMT_AUTOGEN_MIPS; diff --git a/ext/native/thin3d/thin3d_vulkan.cpp b/ext/native/thin3d/thin3d_vulkan.cpp index 1344af1644..8f6906d839 100644 --- a/ext/native/thin3d/thin3d_vulkan.cpp +++ b/ext/native/thin3d/thin3d_vulkan.cpp @@ -261,7 +261,7 @@ public: // Returns the binding offset, and the VkBuffer to bind. size_t PushUBO(VulkanPushBuffer *buf, VulkanContext *vulkan, VkBuffer *vkbuf) { - return buf->PushAligned(ubo_, uboSize_, vulkan->GetPhysicalDeviceProperties().limits.minUniformBufferOffsetAlignment, vkbuf); + return buf->PushAligned(ubo_, uboSize_, vulkan->GetPhysicalDeviceProperties(vulkan_->GetCurrentPhysicalDevice()).limits.minUniformBufferOffsetAlignment, vkbuf); } int GetUniformLoc(const char *name); @@ -312,8 +312,13 @@ public: } VkImageView GetImageView() { - vkTex_->Touch(); - return vkTex_->GetImageView(); + if (vkTex_) { + vkTex_->Touch(); + return vkTex_->GetImageView(); + } else { + // This would be bad. + return VK_NULL_HANDLE; + } } private: @@ -345,6 +350,13 @@ public: const DeviceCaps &GetDeviceCaps() const override { return caps_; } + std::vector GetDeviceList() const override { + std::vector list; + for (int i = 0; i < vulkan_->GetNumPhysicalDevices(); i++) { + list.push_back(vulkan_->GetPhysicalDeviceProperties(i).deviceName); + } + return list; + } uint32_t GetSupportedShaderLanguages() const override { return (uint32_t)ShaderLanguage::GLSL_VULKAN | (uint32_t)ShaderLanguage::SPIRV_VULKAN; } @@ -367,6 +379,7 @@ public: void CopyFramebufferImage(Framebuffer *src, int level, int x, int y, int z, Framebuffer *dst, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, int channelBits) override; bool BlitFramebuffer(Framebuffer *src, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *dst, int dstX1, int dstY1, int dstX2, int dstY2, int channelBits, FBBlitFilter filter) override; bool CopyFramebufferToMemorySync(Framebuffer *src, int channelBits, int x, int y, int w, int h, Draw::DataFormat format, void *pixels, int pixelStride) override; + DataFormat PreferredFramebufferReadbackFormat(Framebuffer *src) override; // These functions should be self explanatory. void BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp) override; @@ -441,13 +454,13 @@ public: // TODO: Make these actually query the right information switch (info) { case APINAME: return "Vulkan"; - case VENDORSTRING: return vulkan_->GetPhysicalDeviceProperties().deviceName; - case VENDOR: return VulkanVendorString(vulkan_->GetPhysicalDeviceProperties().vendorID); - case DRIVER: return FormatDriverVersion(vulkan_->GetPhysicalDeviceProperties()); + case VENDORSTRING: return vulkan_->GetPhysicalDeviceProperties(vulkan_->GetCurrentPhysicalDevice()).deviceName; + case VENDOR: return VulkanVendorString(vulkan_->GetPhysicalDeviceProperties(vulkan_->GetCurrentPhysicalDevice()).vendorID); + case DRIVER: return FormatDriverVersion(vulkan_->GetPhysicalDeviceProperties(vulkan_->GetCurrentPhysicalDevice())); case SHADELANGVERSION: return "N/A";; case APIVERSION: { - uint32_t ver = vulkan_->GetPhysicalDeviceProperties().apiVersion; + uint32_t ver = vulkan_->GetPhysicalDeviceProperties(vulkan_->GetCurrentPhysicalDevice()).apiVersion; return StringFromFormat("%d.%d.%d", ver >> 22, (ver >> 12) & 0x3ff, ver & 0xfff); } default: return "?"; @@ -476,6 +489,8 @@ public: return (uintptr_t)boundImageView_[1]; case NativeObject::RENDER_MANAGER: return (uintptr_t)&renderManager_; + case NativeObject::NULL_IMAGEVIEW: + return (uintptr_t)GetNullTexture()->GetImageView(); default: Crash(); return 0; @@ -485,12 +500,15 @@ public: void HandleEvent(Event ev, int width, int height, void *param1, void *param2) override; private: + VulkanTexture *GetNullTexture(); VulkanContext *vulkan_ = nullptr; VulkanRenderManager renderManager_; VulkanDeviceAllocator *allocator_ = nullptr; + VulkanTexture *nullTexture_ = nullptr; + VKPipeline *curPipeline_ = nullptr; VKBuffer *curVBuffers_[4]{}; int curVBufferOffsets_[4]{}; @@ -604,6 +622,32 @@ static inline VkSamplerAddressMode AddressModeToVulkan(Draw::TextureAddressMode } } +VulkanTexture *VKContext::GetNullTexture() { + if (!nullTexture_) { + VkCommandBuffer cmdInit = renderManager_.GetInitCmd(); + nullTexture_ = new VulkanTexture(vulkan_, allocator_); + nullTexture_->SetTag("Null"); + int w = 8; + int h = 8; + nullTexture_->CreateDirect(cmdInit, w, h, 1, VK_FORMAT_A8B8G8R8_UNORM_PACK32, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); + uint32_t bindOffset; + VkBuffer bindBuf; + uint32_t *data = (uint32_t *)push_->Push(w * h * 4, &bindOffset, &bindBuf); + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + // data[y*w + x] = ((x ^ y) & 1) ? 0xFF808080 : 0xFF000000; // gray/black checkerboard + data[y*w + x] = 0; // black + } + } + nullTexture_->UploadMip(cmdInit, 0, w, h, bindBuf, bindOffset, w); + nullTexture_->EndCreate(cmdInit); + } else { + nullTexture_->Touch(); + } + return nullTexture_; +} + class VKSamplerState : public SamplerState { public: VKSamplerState(VulkanContext *vulkan, const SamplerStateDesc &desc) : vulkan_(vulkan) { @@ -617,7 +661,7 @@ public: s.mipmapMode = desc.mipFilter == TextureFilter::LINEAR ? VK_SAMPLER_MIPMAP_MODE_LINEAR : VK_SAMPLER_MIPMAP_MODE_NEAREST; s.maxLod = desc.maxLod; VkResult res = vkCreateSampler(vulkan_->GetDevice(), &s, nullptr, &sampler_); - assert(VK_SUCCESS == res); + _assert_(VK_SUCCESS == res); } ~VKSamplerState() { vulkan_->Delete().QueueDeleteSampler(sampler_); @@ -663,7 +707,6 @@ bool VKTexture::Create(VkCommandBuffer cmd, VulkanPushBuffer *push, const Textur vkTex_ = new VulkanTexture(vulkan_, alloc); vkTex_->SetTag(desc.tag); VkFormat vulkanFormat = DataFormatToVulkan(format_); - int stride = desc.width * (int)DataFormatSizeInBytes(format_); int bpp = GetBpp(vulkanFormat); int bytesPerPixel = bpp / 8; int usageBits = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; @@ -710,7 +753,7 @@ VKContext::VKContext(VulkanContext *vulkan, bool splitSubmit) caps_.framebufferDepthCopySupported = true; // Will pretty much always be the case. caps_.preferredDepthBufferFormat = DataFormat::D24_S8; // TODO: Ask vulkan. - switch (vulkan->GetPhysicalDeviceProperties().vendorID) { + switch (vulkan->GetPhysicalDeviceProperties(vulkan_->GetCurrentPhysicalDevice()).vendorID) { case VULKAN_VENDOR_AMD: caps_.vendor = GPUVendor::VENDOR_AMD; break; case VULKAN_VENDOR_ARM: caps_.vendor = GPUVendor::VENDOR_ARM; break; case VULKAN_VENDOR_IMGTEC: caps_.vendor = GPUVendor::VENDOR_IMGTEC; break; @@ -725,7 +768,6 @@ VKContext::VKContext(VulkanContext *vulkan, bool splitSubmit) queue_ = vulkan->GetGraphicsQueue(); queueFamilyIndex_ = vulkan->GetGraphicsQueueFamilyIndex(); - memset(boundTextures_, 0, sizeof(boundTextures_)); VkDescriptorPoolSize dpTypes[2]; dpTypes[0].descriptorCount = 200; @@ -746,7 +788,7 @@ VKContext::VKContext(VulkanContext *vulkan, bool splitSubmit) for (int i = 0; i < VulkanContext::MAX_INFLIGHT_FRAMES; i++) { frame_[i].pushBuffer = new VulkanPushBuffer(vulkan_, 1024 * 1024); VkResult res = vkCreateDescriptorPool(device_, &dp, nullptr, &frame_[i].descriptorPool); - assert(res == VK_SUCCESS); + _assert_(res == VK_SUCCESS); } // binding 0 - uniform data @@ -787,8 +829,14 @@ VKContext::VKContext(VulkanContext *vulkan, bool splitSubmit) } VKContext::~VKContext() { + delete nullTexture_; allocator_->Destroy(); - delete allocator_; + // We have to delete on queue, so this can free its queued deletions. + vulkan_->Delete().QueueCallback([](void *ptr) { + auto allocator = static_cast(ptr); + delete allocator; + }, allocator_); + allocator_ = nullptr; // This also destroys all descriptor sets. for (int i = 0; i < VulkanContext::MAX_INFLIGHT_FRAMES; i++) { frame_[i].descSets_.clear(); @@ -814,7 +862,7 @@ void VKContext::BeginFrame() { frame.descSets_.clear(); VkResult result = vkResetDescriptorPool(device_, frame.descriptorPool, 0); - assert(result == VK_SUCCESS); + _assert_(result == VK_SUCCESS); } void VKContext::WaitRenderCompletion(Framebuffer *fbo) { @@ -855,7 +903,7 @@ VkDescriptorSet VKContext::GetOrCreateDescriptorSet(VkBuffer buf) { alloc.pSetLayouts = &descriptorSetLayout_; alloc.descriptorSetCount = 1; VkResult res = vkAllocateDescriptorSets(device_, &alloc, &descSet); - assert(VK_SUCCESS == res); + _assert_(VK_SUCCESS == res); VkDescriptorBufferInfo bufferDesc; bufferDesc.buffer = buf; @@ -863,8 +911,8 @@ VkDescriptorSet VKContext::GetOrCreateDescriptorSet(VkBuffer buf) { bufferDesc.range = curPipeline_->GetUBOSize(); VkDescriptorImageInfo imageDesc; - imageDesc.imageView = boundTextures_[0]->GetImageView(); - imageDesc.sampler = boundSamplers_[0]->GetSampler(); + imageDesc.imageView = boundTextures_[0] ? boundTextures_[0]->GetImageView() : VK_NULL_HANDLE; + imageDesc.sampler = boundSamplers_[0] ? boundSamplers_[0]->GetSampler() : VK_NULL_HANDLE; #ifdef VULKAN_USE_GENERAL_LAYOUT_FOR_COLOR imageDesc.imageLayout = VK_IMAGE_LAYOUT_GENERAL; #else @@ -1112,7 +1160,7 @@ void VKContext::UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t offset, void VKContext::BindTextures(int start, int count, Texture **textures) { for (int i = start; i < start + count; i++) { boundTextures_[i] = static_cast(textures[i]); - boundImageView_[i] = boundTextures_[i]->GetImageView(); + boundImageView_[i] = boundTextures_[i] ? boundTextures_[i]->GetImageView() : GetNullTexture()->GetImageView(); } } @@ -1359,6 +1407,17 @@ bool VKContext::CopyFramebufferToMemorySync(Framebuffer *srcfb, int channelBits, return renderManager_.CopyFramebufferToMemorySync(src ? src->GetFB() : nullptr, aspectMask, x, y, w, h, format, (uint8_t *)pixels, pixelStride); } +DataFormat VKContext::PreferredFramebufferReadbackFormat(Framebuffer *src) { + if (src) { + return DrawContext::PreferredFramebufferReadbackFormat(src); + } + + if (vulkan_->GetSwapchainFormat() == VK_FORMAT_B8G8R8A8_UNORM) { + return Draw::DataFormat::B8G8R8A8_UNORM; + } + return DrawContext::PreferredFramebufferReadbackFormat(src); +} + void VKContext::BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp) { VKFramebuffer *fb = (VKFramebuffer *)fbo; VKRRenderPassAction color = (VKRRenderPassAction)rp.color; diff --git a/ext/native/thread/executor.cpp b/ext/native/thread/executor.cpp index 8c5d6cd097..197f9d7920 100644 --- a/ext/native/thread/executor.cpp +++ b/ext/native/thread/executor.cpp @@ -1,11 +1,16 @@ #include "thread/executor.h" #include +#include namespace threading { void SameThreadExecutor::Run(std::function func) { - func(); + func(); +} + +void NewThreadExecutor::Run(std::function func) { + std::thread(func).detach(); } } // namespace threading diff --git a/ext/native/thread/executor.h b/ext/native/thread/executor.h index 7a5b5286a1..6d6515920c 100644 --- a/ext/native/thread/executor.h +++ b/ext/native/thread/executor.h @@ -6,13 +6,18 @@ namespace threading { // Stuff that can execute other stuff, like threadpools, should inherit from this. class Executor { - public: - virtual void Run(std::function func) = 0; +public: + virtual void Run(std::function func) = 0; }; class SameThreadExecutor : public Executor { - public: - virtual void Run(std::function func); +public: + void Run(std::function func) override; +}; + +class NewThreadExecutor : public Executor { +public: + void Run(std::function func) override; }; } // namespace threading diff --git a/ext/native/ui/ui_screen.cpp b/ext/native/ui/ui_screen.cpp index e6830635f3..3339c8d25b 100644 --- a/ext/native/ui/ui_screen.cpp +++ b/ext/native/ui/ui_screen.cpp @@ -444,6 +444,8 @@ void PopupMultiChoice::Update() { } void PopupMultiChoice::UpdateText() { + if (!choices_) + return; I18NCategory *category = GetI18NCategory(category_); // Clamp the value to be safe. if (*value_ < minVal_ || *value_ > minVal_ + numChoices_ - 1) { @@ -466,6 +468,7 @@ void PopupMultiChoice::ChoiceCallback(int num) { if (restoreFocus_) { SetFocusedView(this); } + PostChoiceCallback(num); } } @@ -513,6 +516,8 @@ EventReturn PopupSliderChoice::HandleClick(EventParams &e) { restoreFocus_ = HasFocus(); SliderPopupScreen *popupScreen = new SliderPopupScreen(value_, minValue_, maxValue_, ChopTitle(text_), step_, units_); + if (!negativeLabel_.empty()) + popupScreen->SetNegativeDisable(negativeLabel_); popupScreen->OnChange.Handle(this, &PopupSliderChoice::HandleChange); if (e.v) popupScreen->SetPopupOrigin(e.v); @@ -538,9 +543,12 @@ void PopupSliderChoice::Draw(UIContext &dc) { int paddingX = 12; dc.SetFontStyle(dc.theme->uiFont); - char temp[32]; + // Always good to have space for Unicode. + char temp[256]; if (zeroLabel_.size() && *value_ == 0) { strcpy(temp, zeroLabel_.c_str()); + } else if (negativeLabel_.size() && *value_ < 0) { + strcpy(temp, negativeLabel_.c_str()); } else { sprintf(temp, fmt_, *value_); } @@ -582,7 +590,7 @@ void PopupSliderChoiceFloat::Draw(UIContext &dc) { int paddingX = 12; dc.SetFontStyle(dc.theme->uiFont); - char temp[32]; + char temp[256]; if (zeroLabel_.size() && *value_ == 0.0f) { strcpy(temp, zeroLabel_.c_str()); } else { @@ -608,6 +616,7 @@ EventReturn SliderPopupScreen::OnDecrease(EventParams ¶ms) { sprintf(temp, "%d", sliderValue_); edit_->SetText(temp); changing_ = false; + disabled_ = false; return EVENT_DONE; } @@ -622,6 +631,7 @@ EventReturn SliderPopupScreen::OnIncrease(EventParams ¶ms) { sprintf(temp, "%d", sliderValue_); edit_->SetText(temp); changing_ = false; + disabled_ = false; return EVENT_DONE; } @@ -631,12 +641,14 @@ EventReturn SliderPopupScreen::OnSliderChange(EventParams ¶ms) { sprintf(temp, "%d", sliderValue_); edit_->SetText(temp); changing_ = false; + disabled_ = false; return EVENT_DONE; } EventReturn SliderPopupScreen::OnTextChange(EventParams ¶ms) { if (!changing_) { sliderValue_ = atoi(edit_->GetText().c_str()); + disabled_ = false; slider_->Clamp(); } return EVENT_DONE; @@ -647,6 +659,8 @@ void SliderPopupScreen::CreatePopupContents(UI::ViewGroup *parent) { UIContext &dc = *screenManager()->getUIContext(); sliderValue_ = *value_; + if (disabled_ && sliderValue_ < 0) + sliderValue_ = 0; LinearLayout *vert = parent->Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(UI::Margins(10, 10)))); slider_ = new Slider(&sliderValue_, minValue_, maxValue_, new LinearLayoutParams(UI::Margins(10, 10))); slider_->OnChange.Handle(this, &SliderPopupScreen::OnSliderChange); @@ -669,6 +683,9 @@ void SliderPopupScreen::CreatePopupContents(UI::ViewGroup *parent) { if (!units_.empty()) lin->Add(new TextView(units_, new LinearLayoutParams(10.0f)))->SetTextColor(dc.theme->popupStyle.fgColor); + if (!negativeLabel_.empty()) + vert->Add(new CheckBox(&disabled_, negativeLabel_)); + if (IsFocusMovementEnabled()) UI::SetFocusedView(slider_); } @@ -751,7 +768,7 @@ EventReturn SliderFloatPopupScreen::OnTextChange(EventParams ¶ms) { void SliderPopupScreen::OnCompleted(DialogResult result) { if (result == DR_OK) { - *value_ = sliderValue_; + *value_ = disabled_ ? -1 : sliderValue_; EventParams e{}; e.v = nullptr; e.a = *value_; diff --git a/ext/native/ui/ui_screen.h b/ext/native/ui/ui_screen.h index f2fe7b157f..99a6c486eb 100644 --- a/ext/native/ui/ui_screen.h +++ b/ext/native/ui/ui_screen.h @@ -169,9 +169,14 @@ namespace UI { class SliderPopupScreen : public PopupScreen { public: SliderPopupScreen(int *value, int minValue, int maxValue, const std::string &title, int step = 1, const std::string &units = "") - : PopupScreen(title, "OK", "Cancel"), units_(units), value_(value), minValue_(minValue), maxValue_(maxValue), step_(step), changing_(false) {} + : PopupScreen(title, "OK", "Cancel"), units_(units), value_(value), minValue_(minValue), maxValue_(maxValue), step_(step) {} virtual void CreatePopupContents(ViewGroup *parent) override; + void SetNegativeDisable(const std::string &str) { + negativeLabel_ = str; + disabled_ = *value_ < 0; + } + Event OnChange; private: @@ -183,12 +188,14 @@ private: Slider *slider_; UI::TextEdit *edit_; std::string units_; + std::string negativeLabel_; int *value_; int sliderValue_; int minValue_; int maxValue_; int step_; - bool changing_; + bool changing_ = false; + bool disabled_ = false; }; class SliderFloatPopupScreen : public PopupScreen { @@ -237,11 +244,13 @@ private: class PopupMultiChoice : public UI::Choice { public: PopupMultiChoice(int *value, const std::string &text, const char **choices, int minVal, int numChoices, - const char *category, ScreenManager *screenManager, UI::LayoutParams *layoutParams = 0) + const char *category, ScreenManager *screenManager, UI::LayoutParams *layoutParams = nullptr) : UI::Choice(text, "", false, layoutParams), value_(value), choices_(choices), minVal_(minVal), numChoices_(numChoices), category_(category), screenManager_(screenManager) { - if (*value >= numChoices+minVal) *value = numChoices+minVal-1; - if (*value < minVal) *value = minVal; + if (*value >= numChoices + minVal) + *value = numChoices + minVal - 1; + if (*value < minVal) + *value = minVal; OnClick.Handle(this, &PopupMultiChoice::HandleClick); UpdateText(); } @@ -255,16 +264,19 @@ public: UI::Event OnChoice; -private: - void UpdateText(); - UI::EventReturn HandleClick(UI::EventParams &e); - - void ChoiceCallback(int num); - +protected: int *value_; const char **choices_; int minVal_; int numChoices_; + void UpdateText(); + +private: + UI::EventReturn HandleClick(UI::EventParams &e); + + void ChoiceCallback(int num); + virtual void PostChoiceCallback(int num) {} + const char *category_; ScreenManager *screenManager_; std::string valueText_; @@ -272,6 +284,40 @@ private: std::set hidden_; }; +// Allows passing in a dynamic vector of strings. Saves the string. +class PopupMultiChoiceDynamic : public PopupMultiChoice { +public: + PopupMultiChoiceDynamic(std::string *value, const std::string &text, std::vector choices, + const char *category, ScreenManager *screenManager, UI::LayoutParams *layoutParams = nullptr) + : UI::PopupMultiChoice(&valueInt_, text, nullptr, 0, (int)choices.size(), category, screenManager, layoutParams), + valueStr_(value) { + choices_ = new const char *[numChoices_]; + valueInt_ = 0; + for (int i = 0; i < numChoices_; i++) { + choices_[i] = new char[choices[i].size() + 1]; + memcpy((char *)choices_[i], choices[i].c_str(), choices[i].size() + 1); + if (*value == choices_[i]) + valueInt_ = i; + } + value_ = &valueInt_; + UpdateText(); + } + ~PopupMultiChoiceDynamic() { + for (int i = 0; i < numChoices_; i++) { + delete[] choices_[i]; + } + delete[] choices_; + } + +protected: + void PostChoiceCallback(int num) { + *valueStr_ = choices_[num]; + } + +private: + int valueInt_; + std::string *valueStr_; +}; class PopupSliderChoice : public Choice { public: @@ -286,6 +332,9 @@ public: void SetZeroLabel(const std::string &str) { zeroLabel_ = str; } + void SetNegativeDisable(const std::string &str) { + negativeLabel_ = str; + } Event OnChange; @@ -299,6 +348,7 @@ private: int step_; const char *fmt_; std::string zeroLabel_; + std::string negativeLabel_; std::string units_; ScreenManager *screenManager_; bool restoreFocus_; diff --git a/ext/native/ui/view.cpp b/ext/native/ui/view.cpp index 58427c8715..5f1deac264 100644 --- a/ext/native/ui/view.cpp +++ b/ext/native/ui/view.cpp @@ -733,8 +733,8 @@ void Button::GetContentDimensions(const UIContext &dc, float &w, float &h) const dc.MeasureText(dc.theme->uiFont, 1.0f, 1.0f, text_.c_str(), &w, &h); } // Add some internal padding to not look totally ugly - w += 16; - h += 8; + w += paddingW_; + h += paddingH_; } void Button::Draw(UIContext &dc) { diff --git a/ext/native/ui/view.h b/ext/native/ui/view.h index 05e3100ab0..8c24b2fadb 100644 --- a/ext/native/ui/view.h +++ b/ext/native/ui/view.h @@ -508,11 +508,17 @@ public: void Draw(UIContext &dc) override; void GetContentDimensions(const UIContext &dc, float &w, float &h) const override; const std::string &GetText() const { return text_; } + void SetPadding(int w, int h) { + paddingW_ = w; + paddingH_ = h; + } private: Style style_; std::string text_; ImageID imageID_; + int paddingW_ = 16; + int paddingH_ = 8; }; class Slider : public Clickable { diff --git a/ext/native/util/text/utf8.cpp b/ext/native/util/text/utf8.cpp index 33e780d663..4183463d46 100644 --- a/ext/native/util/text/utf8.cpp +++ b/ext/native/util/text/utf8.cpp @@ -463,6 +463,20 @@ std::wstring ConvertUTF8ToWString(const std::string &source) { #else +std::string ConvertWStringToUTF8(const std::wstring &wstr) { + std::string s; + // Worst case. + s.resize(wstr.size() * 4); + + size_t pos = 0; + for (wchar_t c : wstr) { + pos += UTF8::encode(&s[pos], c); + } + + s.resize(pos); + return s; +} + static size_t ConvertUTF8ToWStringInternal(wchar_t *dest, size_t destSize, const std::string &source) { const wchar_t *const orig = dest; const wchar_t *const destEnd = dest + destSize; diff --git a/ext/native/util/text/utf8.h b/ext/native/util/text/utf8.h index 70dbf80b07..6303c25b45 100644 --- a/ext/native/util/text/utf8.h +++ b/ext/native/util/text/utf8.h @@ -83,6 +83,10 @@ bool UTF8StringHasNonASCII(const char *utf8string); std::string ConvertWStringToUTF8(const std::wstring &wstr); std::string ConvertWStringToUTF8(const wchar_t *wstr); +#else + +std::string ConvertWStringToUTF8(const std::wstring &wstr); + #endif // Dest size in units, not bytes. diff --git a/ext/udis86/syn.c b/ext/udis86/syn.c index 630c21f0c7..c66e2433be 100644 --- a/ext/udis86/syn.c +++ b/ext/udis86/syn.c @@ -23,6 +23,7 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include #include "types.h" #include "decode.h" #include "syn.h" @@ -137,14 +138,14 @@ ud_syn_print_addr(struct ud *u, uint64_t addr) name = u->sym_resolver(u, addr, &offset); if (name) { if (offset) { - ud_asmprintf(u, "%s%+" FMT64 "d", name, offset); + ud_asmprintf(u, "%s%+" PRId64, name, offset); } else { ud_asmprintf(u, "%s", name); } return; } } - ud_asmprintf(u, "0x%" FMT64 "x", addr); + ud_asmprintf(u, "0x%" PRIx64, addr); } @@ -171,7 +172,7 @@ ud_syn_print_imm(struct ud* u, const struct ud_operand *op) default: UD_ASSERT(!"invalid offset"); v = 0; /* keep cc happy */ } } - ud_asmprintf(u, "0x%" FMT64 "x", v); + ud_asmprintf(u, "0x%" PRIx64, v); } uint64_t @@ -193,7 +194,7 @@ ud_syn_print_mem_disp(struct ud* u, const struct ud_operand *op, int sign) case 64: v = op->lval.uqword; break; default: UD_ASSERT(!"invalid offset"); v = 0; /* keep cc happy */ } - ud_asmprintf(u, "0x%" FMT64 "x", v); + ud_asmprintf(u, "0x%" PRIx64, v); } else { int64_t v; UD_ASSERT(op->offset != 64); @@ -206,9 +207,9 @@ ud_syn_print_mem_disp(struct ud* u, const struct ud_operand *op, int sign) if (op->base == UD_R_RIP) { ud_syn_print_addr(u, ud_syn_rip_target(u, op)); } else if (v < 0) { - ud_asmprintf(u, "-0x%" FMT64 "x", -v); + ud_asmprintf(u, "-0x%" PRIx64, -v); } else if (v > 0) { - ud_asmprintf(u, "%s0x%" FMT64 "x", sign? "+" : "", v); + ud_asmprintf(u, "%s0x%" PRIx64, sign? "+" : "", v); } } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1f02b5e381..95c68256f8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Oct 26 10:42:05 CEST 2017 +#Tue May 08 18:23:21 CEST 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.8.1-all.zip diff --git a/headless/Headless.cpp b/headless/Headless.cpp index 6877795818..84b5bda269 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -11,12 +11,14 @@ #include "Common/FileUtil.h" #include "Common/GraphicsContext.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/Core.h" #include "Core/CoreTiming.h" #include "Core/System.h" #include "Core/HLE/sceUtility.h" #include "Core/Host.h" #include "Core/SaveState.h" +#include "GPU/Common/FramebufferCommon.h" #include "Log.h" #include "LogManager.h" #include "base/NativeApp.h" @@ -333,7 +335,7 @@ int main(int argc, const char* argv[]) coreParameter.enableSound = false; coreParameter.mountIso = mountIso ? mountIso : ""; coreParameter.mountRoot = mountRoot ? mountRoot : ""; - coreParameter.startPaused = false; + coreParameter.startBreak = false; coreParameter.printfEmuLog = !autoCompare; coreParameter.headLess = true; coreParameter.renderWidth = 480; @@ -348,13 +350,9 @@ int main(int argc, const char* argv[]) // Never report from tests. g_Config.sReportHost = ""; g_Config.bAutoSaveSymbolMap = false; - g_Config.iRenderingMode = 1; + g_Config.iRenderingMode = FB_BUFFERED_MODE; g_Config.bHardwareTransform = true; -#ifdef USING_GLES2 - g_Config.iAnisotropyLevel = 0; -#else g_Config.iAnisotropyLevel = 0; // When testing mipmapping we really don't want this. -#endif g_Config.bVertexCache = true; g_Config.bTrueColor = true; g_Config.iLanguage = PSP_SYSTEMPARAM_LANGUAGE_ENGLISH; @@ -376,6 +374,7 @@ int main(int argc, const char* argv[]) g_Config.bBlockTransferGPU = true; g_Config.iSplineBezierQuality = 2; g_Config.bHighQualityDepth = true; + g_Config.bMemStickInserted = true; #ifdef _WIN32 InitSysDirectories(); diff --git a/headless/SDLHeadlessHost.cpp b/headless/SDLHeadlessHost.cpp index a2ec3a9b51..1545a53f9c 100644 --- a/headless/SDLHeadlessHost.cpp +++ b/headless/SDLHeadlessHost.cpp @@ -27,6 +27,7 @@ #include "Common/GraphicsContext.h" #include "Core/CoreParameter.h" +#include "Core/ConfigValues.h" #include "Core/System.h" #include "GPU/Common/GPUDebugInterface.h" #include "GPU/GPUState.h" diff --git a/headless/StubHost.cpp b/headless/StubHost.cpp index 82698770da..dc3520013e 100644 --- a/headless/StubHost.cpp +++ b/headless/StubHost.cpp @@ -71,7 +71,9 @@ void HeadlessHost::SendDebugScreenshot(const u8 *pixbuf, u32 w, u32 h) FILE *bmp = File::OpenCFile(comparisonScreenshot_, "rb"); if (bmp) { - fread(&header, sizeof(header), 1, bmp); + if (fread(&header, sizeof(header), 1, bmp) != 1) { + SendOrCollectDebugOutput("Failed to read original screenshot header.\n"); + } fclose(bmp); } diff --git a/ios/ViewController.mm b/ios/ViewController.mm index 2b36a65ada..3ca85ebcdf 100644 --- a/ios/ViewController.mm +++ b/ios/ViewController.mm @@ -23,6 +23,7 @@ #include "gfx_es2/gpu_features.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/System.h" #include "Common/GraphicsContext.h" diff --git a/libretro/LibretroGLContext.cpp b/libretro/LibretroGLContext.cpp index c8a620c3cc..c8260650cb 100644 --- a/libretro/LibretroGLContext.cpp +++ b/libretro/LibretroGLContext.cpp @@ -1,6 +1,7 @@ #include "Common/Log.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/System.h" #include "gfx_es2/gpu_features.h" diff --git a/libretro/LibretroVulkanContext.cpp b/libretro/LibretroVulkanContext.cpp index 7530fd6916..5cc7d4f09b 100644 --- a/libretro/LibretroVulkanContext.cpp +++ b/libretro/LibretroVulkanContext.cpp @@ -4,6 +4,7 @@ #include "Common/Vulkan/VulkanDebug.h" #include "Common/Log.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/System.h" #include "GPU/GPUInterface.h" #include "util/text/parsers.h" diff --git a/libretro/Makefile.common b/libretro/Makefile.common index c732d9a1e4..d905f0a57a 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -125,8 +125,7 @@ SOURCES_CXX += \ SOURCES_CXX += $(EXTDIR)/xbrz/xbrz.cpp SOURCES_CXX += \ - $(NATIVEDIR)/ext/vjson/json.cpp \ - $(NATIVEDIR)/ext/vjson/block_allocator.cpp + $(NATIVEDIR)/ext/gason/gason.cpp SOURCES_CXX += $(NATIVEDIR)/ext/cityhash/city.cpp @@ -203,10 +202,11 @@ SOURCES_CXX += \ $(EXTDIR)/native/base/buffer.cpp \ $(EXTDIR)/native/base/colorutil.cpp \ $(EXTDIR)/native/base/display.cpp \ - $(EXTDIR)/native/base/logging.cpp \ + $(EXTDIR)/native/base/logging.cpp \ $(EXTDIR)/native/base/stringutil.cpp \ $(EXTDIR)/native/base/timeutil.cpp \ $(EXTDIR)/native/data/compression.cpp \ + $(EXTDIR)/native/json/json_reader.cpp \ $(EXTDIR)/glslang/OGLCompilersDLL/InitializeDll.cpp \ $(EXTDIR)/glslang/glslang/GenericCodeGen/CodeGen.cpp \ $(EXTDIR)/glslang/glslang/GenericCodeGen/Link.cpp \ diff --git a/libretro/libretro.cpp b/libretro/libretro.cpp index a76e8a4c44..50866689d3 100644 --- a/libretro/libretro.cpp +++ b/libretro/libretro.cpp @@ -8,6 +8,7 @@ #include "base/timeutil.h" #include "Common/ChunkFile.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "Core/Core.h" #include "Core/HLE/sceCtrl.h" #include "Core/HLE/sceUtility.h" @@ -552,7 +553,7 @@ bool retro_load_game(const struct retro_game_info *game) { coreParam.enableSound = true; coreParam.fileToStart = std::string(game->path); coreParam.mountIso = ""; - coreParam.startPaused = false; + coreParam.startBreak = false; coreParam.printfEmuLog = true; coreParam.headLess = true; coreParam.unthrottle = true; diff --git a/unittest/JitHarness.cpp b/unittest/JitHarness.cpp index 29ab315e44..a34f3f4952 100644 --- a/unittest/JitHarness.cpp +++ b/unittest/JitHarness.cpp @@ -19,6 +19,7 @@ #include "base/timeutil.h" #include "base/NativeApp.h" +#include "Core/ConfigValues.h" #include "Core/MIPS/JitCommon/JitCommon.h" #include "Core/MIPS/JitCommon/JitBlockCache.h" #include "Core/MIPS/MIPSCodeUtils.h" diff --git a/unittest/TestVertexJit.cpp b/unittest/TestVertexJit.cpp index 40dd640c8b..75e5985888 100644 --- a/unittest/TestVertexJit.cpp +++ b/unittest/TestVertexJit.cpp @@ -18,6 +18,7 @@ #include "base/timeutil.h" #include "Common/Common.h" #include "Core/Config.h" +#include "Core/ConfigValues.h" #include "GPU/Common/VertexDecoderCommon.h" #include "GPU/ge_constants.h" #include "GPU/GPUState.h"