diff --git a/Common/FileUtil.cpp b/Common/FileUtil.cpp index d73b30468a..7b9e8259b7 100644 --- a/Common/FileUtil.cpp +++ b/Common/FileUtil.cpp @@ -648,10 +648,8 @@ bool CreateEmptyFile(const std::string &filename) // Deletes the given directory and anything under it. Returns true on success. bool DeleteDirRecursively(const std::string &directory) -{ -#if PPSSPP_PLATFORM(UWP) - return false; -#else +{ + //Removed check, it prevents the UWP from deleting store downloads INFO_LOG(COMMON, "DeleteDirRecursively: %s", directory.c_str()); #ifdef _WIN32 @@ -720,7 +718,6 @@ bool DeleteDirRecursively(const std::string &directory) closedir(dirp); #endif return File::DeleteDir(directory); -#endif } @@ -763,43 +760,15 @@ void CopyDir(const std::string &source_path, const std::string &dest_path) #endif } -void openIniFile(const std::string fileName) { - std::string iniFile; +void openIniFile(const std::string& fileName) { #if defined(_WIN32) #if PPSSPP_PLATFORM(UWP) // Do nothing. #else - iniFile = fileName; - // Can't rely on a .txt file extension to auto-open in the right editor, - // so let's find notepad - wchar_t notepad_path[MAX_PATH + 1]; - GetSystemDirectory(notepad_path, MAX_PATH); - wcscat(notepad_path, L"\\notepad.exe"); - - wchar_t ini_path[MAX_PATH + 1] = { 0 }; - wcsncpy(ini_path, ConvertUTF8ToWString(iniFile).c_str(), MAX_PATH); - // Flip any slashes... - for (size_t i = 0; i < wcslen(ini_path); i++) { - if (ini_path[i] == '/') - ini_path[i] = '\\'; - } - - // One for the space, one for the null. - wchar_t command_line[MAX_PATH * 2 + 1 + 1]; - wsprintf(command_line, L"%s %s", notepad_path, ini_path); - - STARTUPINFO si{}; - si.cb = sizeof(si); - si.wShowWindow = SW_SHOW; - PROCESS_INFORMATION pi{}; - UINT retval = CreateProcess(0, command_line, 0, 0, 0, 0, 0, 0, &si, &pi); - if (!retval) { - ERROR_LOG(COMMON, "Failed creating notepad process"); - } - CloseHandle(pi.hThread); - CloseHandle(pi.hProcess); + ShellExecuteW(nullptr, L"open", ConvertUTF8ToWString(fileName).c_str(), nullptr, nullptr, SW_SHOW); #endif #elif !defined(MOBILE_DEVICE) + std::string iniFile; #if defined(__APPLE__) iniFile = "open "; #else diff --git a/Common/FileUtil.h b/Common/FileUtil.h index 31e0e7a373..e088ceda4c 100644 --- a/Common/FileUtil.h +++ b/Common/FileUtil.h @@ -117,7 +117,7 @@ std::string GetCurrentDir(); void CopyDir(const std::string &source_path, const std::string &dest_path); // Opens ini file (cheats, texture replacements etc.) -void openIniFile(const std::string fileName); +void openIniFile(const std::string& fileName); // Set the current directory to given directory bool SetCurrentDir(const std::string &directory); diff --git a/Common/MemArenaAndroid.cpp b/Common/MemArenaAndroid.cpp index 0ff75317cf..31eda9d3d0 100644 --- a/Common/MemArenaAndroid.cpp +++ b/Common/MemArenaAndroid.cpp @@ -19,6 +19,7 @@ #ifdef __ANDROID__ +#include "base/logging.h" #include "MemoryUtil.h" #include "MemArena.h" #include "StringUtils.h" @@ -102,12 +103,28 @@ void MemArena::ReleaseView(void* view, size_t size) { u8* MemArena::Find4GBBase() { #if PPSSPP_ARCH(64BIT) - // Just grab some random 4GB... - return reinterpret_cast(0x2300000000ULL); + // We should probably just go look in /proc/self/maps for some free space. + // But let's try the anonymous mmap trick, just like on 32-bit, but bigger and + // aligned to 4GB for the movk trick. We can ensure that we get an aligned 4GB + // address by grabbing 8GB and aligning the pointer. + const uint64_t EIGHT_GIGS = 0x200000000ULL; + void *base = mmap(0, EIGHT_GIGS, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0); + if (base) { + ILOG("base: %p", base); + uint64_t aligned_base = ((uint64_t)base + 0xFFFFFFFF) & ~0xFFFFFFFFULL; + ILOG("aligned_base: %p", (void *)aligned_base); + munmap(base, EIGHT_GIGS); + return reinterpret_cast(aligned_base); + } else { + u8 *hardcoded_ptr = reinterpret_cast(0x2300000000ULL); + ILOG("Failed to anonymously map 8GB. Fall back to the hardcoded pointer %p.", hardcoded_ptr); + // Just grab some random 4GB... + // This has been known to fail lately though, see issue #12249. + return hardcoded_ptr; + } #else // Address masking is used in 32-bit mode, so we can get away with less memory. - void* base = mmap(0, 0x10000000, PROT_READ | PROT_WRITE, - MAP_ANON | MAP_SHARED, -1, 0); + void* base = mmap(0, 0x10000000, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0); if (base == MAP_FAILED) { PanicAlert("Failed to map 256 MB of memory space: %s", strerror(errno)); return 0; diff --git a/Common/Vulkan/VulkanContext.cpp b/Common/Vulkan/VulkanContext.cpp index 10c4c4de32..dc87073214 100644 --- a/Common/Vulkan/VulkanContext.cpp +++ b/Common/Vulkan/VulkanContext.cpp @@ -516,8 +516,8 @@ void VulkanContext::ChooseDevice(int physical_device) { vkGetPhysicalDeviceQueueFamilyProperties(physical_devices_[physical_device_], &queue_count, nullptr); assert(queue_count >= 1); - queue_props.resize(queue_count); - vkGetPhysicalDeviceQueueFamilyProperties(physical_devices_[physical_device_], &queue_count, queue_props.data()); + queueFamilyProperties_.resize(queue_count); + vkGetPhysicalDeviceQueueFamilyProperties(physical_devices_[physical_device_], &queue_count, queueFamilyProperties_.data()); assert(queue_count >= 1); // Detect preferred formats, in this order. @@ -619,7 +619,7 @@ VkResult VulkanContext::CreateDevice() { queue_info.pQueuePriorities = queue_priorities; bool found = false; for (int i = 0; i < (int)queue_count; i++) { - if (queue_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (queueFamilyProperties_[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { queue_info.queueFamilyIndex = i; found = true; break; @@ -816,7 +816,7 @@ bool VulkanContext::InitQueue() { uint32_t graphicsQueueNodeIndex = UINT32_MAX; uint32_t presentQueueNodeIndex = UINT32_MAX; for (uint32_t i = 0; i < queue_count; i++) { - if ((queue_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) { + if ((queueFamilyProperties_[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) { if (graphicsQueueNodeIndex == UINT32_MAX) { graphicsQueueNodeIndex = i; } @@ -1014,6 +1014,8 @@ bool VulkanContext::InitSwapchain() { g_display_rot_matrix.setRotationZ270(); std::swap(swapChainExtent_.width, swapChainExtent_.height); break; + default: + assert(false); } } else { // Let the OS rotate the image (potentially slow on many Android devices) diff --git a/Common/Vulkan/VulkanContext.h b/Common/Vulkan/VulkanContext.h index 01761aab6b..d3ecb96963 100644 --- a/Common/Vulkan/VulkanContext.h +++ b/Common/Vulkan/VulkanContext.h @@ -201,6 +201,10 @@ public: return physicalDeviceProperties_[i]; } + const VkQueueFamilyProperties &GetQueueFamilyProperties(int family) const { + return queueFamilyProperties_[family]; + } + VkResult GetInstanceLayerExtensionList(const char *layerName, std::vector &extensions); VkResult GetInstanceLayerProperties(); @@ -303,8 +307,8 @@ private: int physical_device_ = -1; uint32_t graphics_queue_family_index_ = -1; - std::vector physicalDeviceProperties_{}; - std::vector queue_props; + std::vector physicalDeviceProperties_; + std::vector queueFamilyProperties_; VkPhysicalDeviceMemoryProperties memory_properties{}; // Custom collection of things that are good to know diff --git a/Common/Vulkan/VulkanDebug.cpp b/Common/Vulkan/VulkanDebug.cpp index be7512b738..55825f8209 100644 --- a/Common/Vulkan/VulkanDebug.cpp +++ b/Common/Vulkan/VulkanDebug.cpp @@ -113,6 +113,13 @@ VkBool32 VKAPI_CALL VulkanDebugUtilsCallback( std::ostringstream message; const char *pMessage = pCallbackData->pMessage; + + // Apparent bugs around timestamp validation in the validation layers + if (strstr(pMessage, "vkCmdBeginQuery(): VkQueryPool")) + return false; + if (strstr(pMessage, "vkGetQueryPoolResults() on VkQueryPool")) + return false; + int messageCode = pCallbackData->messageIdNumber; const char *pLayerPrefix = ""; if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { diff --git a/Common/Vulkan/VulkanImage.cpp b/Common/Vulkan/VulkanImage.cpp index 5c5e513948..8ad99b774d 100644 --- a/Common/Vulkan/VulkanImage.cpp +++ b/Common/Vulkan/VulkanImage.cpp @@ -142,10 +142,7 @@ bool VulkanTexture::CreateDirect(VkCommandBuffer cmd, VulkanDeviceAllocator *all if (mapping) { view_info.components = *mapping; } else { - view_info.components.r = VK_COMPONENT_SWIZZLE_R; - view_info.components.g = VK_COMPONENT_SWIZZLE_G; - view_info.components.b = VK_COMPONENT_SWIZZLE_B; - view_info.components.a = VK_COMPONENT_SWIZZLE_A; + view_info.components = { VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY }; } view_info.subresourceRange.aspectMask = aspect; view_info.subresourceRange.baseMipLevel = 0; diff --git a/Common/Vulkan/VulkanLoader.cpp b/Common/Vulkan/VulkanLoader.cpp index aedcfd639b..4f4c25feb0 100644 --- a/Common/Vulkan/VulkanLoader.cpp +++ b/Common/Vulkan/VulkanLoader.cpp @@ -17,8 +17,11 @@ #include "Common/Vulkan/VulkanLoader.h" #include +#include + #include "base/logging.h" #include "base/basictypes.h" +#include "base/NativeApp.h" #ifndef _WIN32 #include @@ -61,9 +64,7 @@ PFN_vkGetPhysicalDeviceSparseImageFormatProperties vkGetPhysicalDeviceSparseImag PFN_vkQueueBindSparse vkQueueBindSparse; PFN_vkCreateFence vkCreateFence; PFN_vkDestroyFence vkDestroyFence; -PFN_vkResetFences vkResetFences; PFN_vkGetFenceStatus vkGetFenceStatus; -PFN_vkWaitForFences vkWaitForFences; PFN_vkCreateSemaphore vkCreateSemaphore; PFN_vkDestroySemaphore vkDestroySemaphore; PFN_vkCreateEvent vkCreateEvent; @@ -115,7 +116,7 @@ PFN_vkResetCommandPool vkResetCommandPool; PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers; PFN_vkFreeCommandBuffers vkFreeCommandBuffers; -// Used frequently +// Used frequently together PFN_vkCmdBindPipeline vkCmdBindPipeline; PFN_vkCmdSetViewport vkCmdSetViewport; PFN_vkCmdSetScissor vkCmdSetScissor; @@ -128,14 +129,16 @@ PFN_vkCmdBindIndexBuffer vkCmdBindIndexBuffer; PFN_vkCmdBindVertexBuffers vkCmdBindVertexBuffers; PFN_vkCmdDraw vkCmdDraw; PFN_vkCmdDrawIndexed vkCmdDrawIndexed; -PFN_vkCmdClearAttachments vkCmdClearAttachments; PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier; PFN_vkCmdPushConstants vkCmdPushConstants; // Every frame to a few times per frame +PFN_vkWaitForFences vkWaitForFences; +PFN_vkResetFences vkResetFences; PFN_vkBeginCommandBuffer vkBeginCommandBuffer; PFN_vkEndCommandBuffer vkEndCommandBuffer; PFN_vkResetCommandBuffer vkResetCommandBuffer; +PFN_vkCmdClearAttachments vkCmdClearAttachments; PFN_vkCmdSetEvent vkCmdSetEvent; PFN_vkCmdResetEvent vkCmdResetEvent; PFN_vkCmdWaitEvents vkCmdWaitEvents; @@ -147,6 +150,7 @@ PFN_vkCmdBlitImage vkCmdBlitImage; PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage; PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer; +// Rare or not used PFN_vkCmdSetDepthBounds vkCmdSetDepthBounds; PFN_vkCmdSetLineWidth vkCmdSetLineWidth; PFN_vkCmdSetDepthBias vkCmdSetDepthBias; @@ -226,9 +230,15 @@ bool g_vulkanMayBeAvailable = false; #define LOAD_GLOBAL_FUNC_LOCAL(lib, x) (PFN_ ## x)dlsym(lib, #x); +static const char *device_name_blacklist[] = { + "NVIDIA:SHIELD Tablet K1", +}; + static const char *so_names[] = { "libvulkan.so", +#if !defined(__ANDROID__) "libvulkan.so.1", +#endif }; void VulkanSetAvailable(bool available) { @@ -239,11 +249,23 @@ void VulkanSetAvailable(bool available) { bool VulkanMayBeAvailable() { if (g_vulkanAvailabilityChecked) return g_vulkanMayBeAvailable; + + std::string name = System_GetProperty(SYSPROP_NAME); + for (const char *blacklisted_name : device_name_blacklist) { + if (!strcmp(name.c_str(), blacklisted_name)) { + g_vulkanAvailabilityChecked = true; + g_vulkanMayBeAvailable = false; + return false; + } + } + ILOG("VulkanMayBeAvailable: Device allowed ('%s')", name.c_str()); + #ifndef _WIN32 void *lib = nullptr; for (int i = 0; i < ARRAY_SIZE(so_names); i++) { lib = dlopen(so_names[i], RTLD_NOW | RTLD_LOCAL); if (lib) { + ILOG("VulkanMayBeAvailable: Library loaded ('%s')", so_names[i]); break; } } @@ -252,6 +274,7 @@ bool VulkanMayBeAvailable() { HINSTANCE lib = LoadLibrary(L"vulkan-1.dll"); #endif if (!lib) { + ILOG("Vulkan loader: Library not available"); g_vulkanAvailabilityChecked = true; g_vulkanMayBeAvailable = false; return false; @@ -270,15 +293,15 @@ bool VulkanMayBeAvailable() { VkApplicationInfo info{ VK_STRUCTURE_TYPE_APPLICATION_INFO }; std::vector devices; bool anyGood = false; - const char *instanceExtensions[2]{}; + const char *instanceExtensions[10]{}; VkInstance instance = VK_NULL_HANDLE; - VkResult res; + VkResult res = VK_SUCCESS; uint32_t physicalDeviceCount = 0; - uint32_t instanceExtCount = 1; + uint32_t instanceExtCount = 0; bool surfaceExtensionFound = false; bool platformSurfaceExtensionFound = false; std::vector instanceExts; - instanceExtensions[ci.enabledExtensionCount++] = VK_KHR_SURFACE_EXTENSION_NAME; + ci.enabledExtensionCount = 0; // Should have been reset by struct initialization anyway, just paranoia. #ifdef _WIN32 const char * const platformSurfaceExtension = VK_KHR_WIN32_SURFACE_EXTENSION_NAME; @@ -288,9 +311,12 @@ bool VulkanMayBeAvailable() { const char *platformSurfaceExtension = 0; #endif - if (!localEnumerateInstanceExtensionProperties || !localCreateInstance || !localEnumerate || !localDestroyInstance || !localGetPhysicalDeviceProperties) + if (!localEnumerateInstanceExtensionProperties || !localCreateInstance || !localEnumerate || !localDestroyInstance || !localGetPhysicalDeviceProperties) { + WLOG("VulkanMayBeAvailable: Function pointer missing, bailing"); goto bail; + } + ILOG("VulkanMayBeAvailable: Enumerating instance extensions"); res = localEnumerateInstanceExtensionProperties(nullptr, &instanceExtCount, nullptr); // Maximum paranoia. if (res != VK_SUCCESS) { @@ -301,6 +327,7 @@ bool VulkanMayBeAvailable() { ELOG("No VK instance extensions - won't be able to present."); goto bail; } + ILOG("VulkanMayBeAvailable: Instance extension count: %d", instanceExtCount); instanceExts.resize(instanceExtCount); res = localEnumerateInstanceExtensionProperties(nullptr, &instanceExtCount, instanceExts.data()); if (res != VK_SUCCESS) { @@ -310,11 +337,14 @@ bool VulkanMayBeAvailable() { if (platformSurfaceExtension) { for (auto iter : instanceExts) { + ILOG("VulkanMaybeAvailable: Instance extension found: %s (%08x)", iter.extensionName, iter.specVersion); if (!strcmp(iter.extensionName, platformSurfaceExtension)) { + ILOG("VulkanMayBeAvailable: Found platform surface extension '%s'", platformSurfaceExtension); instanceExtensions[ci.enabledExtensionCount++] = platformSurfaceExtension; platformSurfaceExtensionFound = true; break; } else if (!strcmp(iter.extensionName, VK_KHR_SURFACE_EXTENSION_NAME)) { + instanceExtensions[ci.enabledExtensionCount++] = VK_KHR_SURFACE_EXTENSION_NAME; surfaceExtensionFound = true; } } @@ -323,6 +353,11 @@ bool VulkanMayBeAvailable() { goto bail; } } + // This can't happen unless the driver is double-reporting a surface extension. + if (ci.enabledExtensionCount > 2) { + ELOG("Unexpected number of enabled instance extensions"); + goto bail; + } ci.ppEnabledExtensionNames = instanceExtensions; ci.enabledLayerCount = 0; @@ -330,29 +365,30 @@ bool VulkanMayBeAvailable() { info.applicationVersion = 1; info.engineVersion = 1; info.pApplicationName = "VulkanChecker"; - info.pEngineName = "VulkanChecker"; + info.pEngineName = "VulkanCheckerEngine"; ci.pApplicationInfo = &info; ci.flags = 0; + ILOG("VulkanMayBeAvailable: Calling vkCreateInstance"); res = localCreateInstance(&ci, nullptr, &instance); if (res != VK_SUCCESS) { instance = nullptr; ELOG("Failed to create vulkan instance."); goto bail; } - ILOG("Vulkan test instance created successfully."); + ILOG("VulkanMayBeAvailable: Vulkan test instance created successfully."); res = localEnumerate(instance, &physicalDeviceCount, nullptr); if (res != VK_SUCCESS) { - ELOG("Failed to count physical devices."); + ELOG("VulkanMayBeAvailable: Failed to count physical devices."); goto bail; } if (physicalDeviceCount == 0) { - ELOG("No physical Vulkan devices."); + ELOG("VulkanMayBeAvailable: No physical Vulkan devices."); goto bail; } devices.resize(physicalDeviceCount); res = localEnumerate(instance, &physicalDeviceCount, devices.data()); if (res != VK_SUCCESS) { - ELOG("Failed to enumerate physical devices."); + ELOG("VulkanMayBeAvailable: Failed to enumerate physical devices."); goto bail; } anyGood = false; @@ -372,16 +408,17 @@ bool VulkanMayBeAvailable() { } if (!anyGood) { - WLOG("Found Vulkan API, but no good Vulkan device!"); + WLOG("VulkanMayBeAvailable: Found Vulkan API, but no good Vulkan device!"); g_vulkanMayBeAvailable = false; } else { - ILOG("Found working Vulkan API!"); + ILOG("VulkanMayBeAvailable: Found working Vulkan API!"); g_vulkanMayBeAvailable = true; } bail: g_vulkanAvailabilityChecked = true; if (instance) { + ILOG("VulkanMayBeAvailable: Destroying instance"); localDestroyInstance(instance, nullptr); } if (lib) { @@ -401,8 +438,10 @@ bool VulkanLoad() { #ifndef _WIN32 for (int i = 0; i < ARRAY_SIZE(so_names); i++) { vulkanLibrary = dlopen(so_names[i], RTLD_NOW | RTLD_LOCAL); - if (vulkanLibrary) + if (vulkanLibrary) { + ILOG("VulkanLoad: Found library '%s'", so_names[i]); break; + } } #else // LoadLibrary etc @@ -420,8 +459,19 @@ bool VulkanLoad() { LOAD_GLOBAL_FUNC(vkEnumerateInstanceExtensionProperties); LOAD_GLOBAL_FUNC(vkEnumerateInstanceLayerProperties); - WLOG("Vulkan base functions loaded."); - return true; + if (vkCreateInstance && vkGetInstanceProcAddr && vkGetDeviceProcAddr && vkEnumerateInstanceExtensionProperties && vkEnumerateInstanceLayerProperties) { + WLOG("VulkanLoad: Base functions loaded."); + return true; + } else { + ELOG("VulkanLoad: Failed to load Vulkan base functions."); +#ifndef _WIN32 + dlclose(vulkanLibrary); +#else + FreeLibrary(vulkanLibrary); +#endif + vulkanLibrary = nullptr; + return false; + } } void VulkanLoadInstanceFunctions(VkInstance instance, const VulkanDeviceExtensions &enabledExtensions) { @@ -439,125 +489,7 @@ void VulkanLoadInstanceFunctions(VkInstance instance, const VulkanDeviceExtensio LOAD_INSTANCE_FUNC(instance, vkEnumerateDeviceExtensionProperties); LOAD_INSTANCE_FUNC(instance, vkEnumerateDeviceLayerProperties); LOAD_INSTANCE_FUNC(instance, vkGetDeviceQueue); - LOAD_INSTANCE_FUNC(instance, vkQueueSubmit); - LOAD_INSTANCE_FUNC(instance, vkQueueWaitIdle); LOAD_INSTANCE_FUNC(instance, vkDeviceWaitIdle); - LOAD_INSTANCE_FUNC(instance, vkAllocateMemory); - LOAD_INSTANCE_FUNC(instance, vkFreeMemory); - LOAD_INSTANCE_FUNC(instance, vkMapMemory); - LOAD_INSTANCE_FUNC(instance, vkUnmapMemory); - LOAD_INSTANCE_FUNC(instance, vkFlushMappedMemoryRanges); - LOAD_INSTANCE_FUNC(instance, vkInvalidateMappedMemoryRanges); - LOAD_INSTANCE_FUNC(instance, vkGetDeviceMemoryCommitment); - LOAD_INSTANCE_FUNC(instance, vkBindBufferMemory); - LOAD_INSTANCE_FUNC(instance, vkBindImageMemory); - LOAD_INSTANCE_FUNC(instance, vkGetBufferMemoryRequirements); - LOAD_INSTANCE_FUNC(instance, vkGetImageMemoryRequirements); - LOAD_INSTANCE_FUNC(instance, vkGetImageSparseMemoryRequirements); - LOAD_INSTANCE_FUNC(instance, vkGetPhysicalDeviceSparseImageFormatProperties); - LOAD_INSTANCE_FUNC(instance, vkQueueBindSparse); - LOAD_INSTANCE_FUNC(instance, vkCreateFence); - LOAD_INSTANCE_FUNC(instance, vkDestroyFence); - LOAD_INSTANCE_FUNC(instance, vkResetFences); - LOAD_INSTANCE_FUNC(instance, vkGetFenceStatus); - LOAD_INSTANCE_FUNC(instance, vkWaitForFences); - LOAD_INSTANCE_FUNC(instance, vkCreateSemaphore); - LOAD_INSTANCE_FUNC(instance, vkDestroySemaphore); - LOAD_INSTANCE_FUNC(instance, vkCreateEvent); - LOAD_INSTANCE_FUNC(instance, vkDestroyEvent); - LOAD_INSTANCE_FUNC(instance, vkGetEventStatus); - LOAD_INSTANCE_FUNC(instance, vkSetEvent); - LOAD_INSTANCE_FUNC(instance, vkResetEvent); - LOAD_INSTANCE_FUNC(instance, vkCreateQueryPool); - LOAD_INSTANCE_FUNC(instance, vkDestroyQueryPool); - LOAD_INSTANCE_FUNC(instance, vkGetQueryPoolResults); - LOAD_INSTANCE_FUNC(instance, vkCreateBuffer); - LOAD_INSTANCE_FUNC(instance, vkDestroyBuffer); - LOAD_INSTANCE_FUNC(instance, vkCreateBufferView); - LOAD_INSTANCE_FUNC(instance, vkDestroyBufferView); - LOAD_INSTANCE_FUNC(instance, vkCreateImage); - LOAD_INSTANCE_FUNC(instance, vkDestroyImage); - LOAD_INSTANCE_FUNC(instance, vkGetImageSubresourceLayout); - LOAD_INSTANCE_FUNC(instance, vkCreateImageView); - LOAD_INSTANCE_FUNC(instance, vkDestroyImageView); - LOAD_INSTANCE_FUNC(instance, vkCreateShaderModule); - LOAD_INSTANCE_FUNC(instance, vkDestroyShaderModule); - LOAD_INSTANCE_FUNC(instance, vkCreatePipelineCache); - LOAD_INSTANCE_FUNC(instance, vkDestroyPipelineCache); - LOAD_INSTANCE_FUNC(instance, vkGetPipelineCacheData); - LOAD_INSTANCE_FUNC(instance, vkMergePipelineCaches); - LOAD_INSTANCE_FUNC(instance, vkCreateGraphicsPipelines); - LOAD_INSTANCE_FUNC(instance, vkCreateComputePipelines); - LOAD_INSTANCE_FUNC(instance, vkDestroyPipeline); - LOAD_INSTANCE_FUNC(instance, vkCreatePipelineLayout); - LOAD_INSTANCE_FUNC(instance, vkDestroyPipelineLayout); - LOAD_INSTANCE_FUNC(instance, vkCreateSampler); - LOAD_INSTANCE_FUNC(instance, vkDestroySampler); - LOAD_INSTANCE_FUNC(instance, vkCreateDescriptorSetLayout); - LOAD_INSTANCE_FUNC(instance, vkDestroyDescriptorSetLayout); - LOAD_INSTANCE_FUNC(instance, vkCreateDescriptorPool); - LOAD_INSTANCE_FUNC(instance, vkDestroyDescriptorPool); - LOAD_INSTANCE_FUNC(instance, vkResetDescriptorPool); - LOAD_INSTANCE_FUNC(instance, vkAllocateDescriptorSets); - LOAD_INSTANCE_FUNC(instance, vkFreeDescriptorSets); - LOAD_INSTANCE_FUNC(instance, vkUpdateDescriptorSets); - LOAD_INSTANCE_FUNC(instance, vkCreateFramebuffer); - LOAD_INSTANCE_FUNC(instance, vkDestroyFramebuffer); - LOAD_INSTANCE_FUNC(instance, vkCreateRenderPass); - LOAD_INSTANCE_FUNC(instance, vkDestroyRenderPass); - LOAD_INSTANCE_FUNC(instance, vkGetRenderAreaGranularity); - LOAD_INSTANCE_FUNC(instance, vkCreateCommandPool); - LOAD_INSTANCE_FUNC(instance, vkDestroyCommandPool); - LOAD_INSTANCE_FUNC(instance, vkResetCommandPool); - LOAD_INSTANCE_FUNC(instance, vkAllocateCommandBuffers); - LOAD_INSTANCE_FUNC(instance, vkFreeCommandBuffers); - LOAD_INSTANCE_FUNC(instance, vkBeginCommandBuffer); - LOAD_INSTANCE_FUNC(instance, vkEndCommandBuffer); - LOAD_INSTANCE_FUNC(instance, vkResetCommandBuffer); - LOAD_INSTANCE_FUNC(instance, vkCmdBindPipeline); - LOAD_INSTANCE_FUNC(instance, vkCmdSetViewport); - LOAD_INSTANCE_FUNC(instance, vkCmdSetScissor); - LOAD_INSTANCE_FUNC(instance, vkCmdSetLineWidth); - LOAD_INSTANCE_FUNC(instance, vkCmdSetDepthBias); - LOAD_INSTANCE_FUNC(instance, vkCmdSetBlendConstants); - LOAD_INSTANCE_FUNC(instance, vkCmdSetDepthBounds); - LOAD_INSTANCE_FUNC(instance, vkCmdSetStencilCompareMask); - LOAD_INSTANCE_FUNC(instance, vkCmdSetStencilWriteMask); - LOAD_INSTANCE_FUNC(instance, vkCmdSetStencilReference); - LOAD_INSTANCE_FUNC(instance, vkCmdBindDescriptorSets); - LOAD_INSTANCE_FUNC(instance, vkCmdBindIndexBuffer); - LOAD_INSTANCE_FUNC(instance, vkCmdBindVertexBuffers); - LOAD_INSTANCE_FUNC(instance, vkCmdDraw); - LOAD_INSTANCE_FUNC(instance, vkCmdDrawIndexed); - LOAD_INSTANCE_FUNC(instance, vkCmdDrawIndirect); - LOAD_INSTANCE_FUNC(instance, vkCmdDrawIndexedIndirect); - LOAD_INSTANCE_FUNC(instance, vkCmdDispatch); - LOAD_INSTANCE_FUNC(instance, vkCmdDispatchIndirect); - LOAD_INSTANCE_FUNC(instance, vkCmdCopyBuffer); - LOAD_INSTANCE_FUNC(instance, vkCmdCopyImage); - LOAD_INSTANCE_FUNC(instance, vkCmdBlitImage); - LOAD_INSTANCE_FUNC(instance, vkCmdCopyBufferToImage); - LOAD_INSTANCE_FUNC(instance, vkCmdCopyImageToBuffer); - LOAD_INSTANCE_FUNC(instance, vkCmdUpdateBuffer); - LOAD_INSTANCE_FUNC(instance, vkCmdFillBuffer); - LOAD_INSTANCE_FUNC(instance, vkCmdClearColorImage); - LOAD_INSTANCE_FUNC(instance, vkCmdClearDepthStencilImage); - LOAD_INSTANCE_FUNC(instance, vkCmdClearAttachments); - LOAD_INSTANCE_FUNC(instance, vkCmdResolveImage); - LOAD_INSTANCE_FUNC(instance, vkCmdSetEvent); - LOAD_INSTANCE_FUNC(instance, vkCmdResetEvent); - LOAD_INSTANCE_FUNC(instance, vkCmdWaitEvents); - LOAD_INSTANCE_FUNC(instance, vkCmdPipelineBarrier); - LOAD_INSTANCE_FUNC(instance, vkCmdBeginQuery); - LOAD_INSTANCE_FUNC(instance, vkCmdEndQuery); - LOAD_INSTANCE_FUNC(instance, vkCmdResetQueryPool); - LOAD_INSTANCE_FUNC(instance, vkCmdWriteTimestamp); - LOAD_INSTANCE_FUNC(instance, vkCmdCopyQueryPoolResults); - LOAD_INSTANCE_FUNC(instance, vkCmdPushConstants); - LOAD_INSTANCE_FUNC(instance, vkCmdBeginRenderPass); - LOAD_INSTANCE_FUNC(instance, vkCmdNextSubpass); - LOAD_INSTANCE_FUNC(instance, vkCmdEndRenderPass); - LOAD_INSTANCE_FUNC(instance, vkCmdExecuteCommands); LOAD_INSTANCE_FUNC(instance, vkGetPhysicalDeviceSurfaceSupportKHR); LOAD_INSTANCE_FUNC(instance, vkGetPhysicalDeviceSurfaceCapabilitiesKHR); @@ -609,15 +541,129 @@ void VulkanLoadInstanceFunctions(VkInstance instance, const VulkanDeviceExtensio // On some implementations, loading functions (that have Device as their first parameter) via vkGetDeviceProcAddr may // increase performance - but then these function pointers will only work on that specific device. Thus, this loader is not very -// good for multi-device. +// good for multi-device - not likely we'll ever try that anyway though. void VulkanLoadDeviceFunctions(VkDevice device, const VulkanDeviceExtensions &enabledExtensions) { WLOG("Vulkan device functions loaded."); - // TODO: Move more functions VulkanLoadInstanceFunctions to here. - LOAD_DEVICE_FUNC(device, vkCreateSwapchainKHR); - LOAD_DEVICE_FUNC(device, vkDestroySwapchainKHR); - LOAD_DEVICE_FUNC(device, vkGetSwapchainImagesKHR); - LOAD_DEVICE_FUNC(device, vkAcquireNextImageKHR); - LOAD_DEVICE_FUNC(device, vkQueuePresentKHR); + + LOAD_DEVICE_FUNC(device, vkQueueSubmit); + LOAD_DEVICE_FUNC(device, vkQueueWaitIdle); + LOAD_DEVICE_FUNC(device, vkAllocateMemory); + LOAD_DEVICE_FUNC(device, vkFreeMemory); + LOAD_DEVICE_FUNC(device, vkMapMemory); + LOAD_DEVICE_FUNC(device, vkUnmapMemory); + LOAD_DEVICE_FUNC(device, vkFlushMappedMemoryRanges); + LOAD_DEVICE_FUNC(device, vkInvalidateMappedMemoryRanges); + LOAD_DEVICE_FUNC(device, vkGetDeviceMemoryCommitment); + LOAD_DEVICE_FUNC(device, vkBindBufferMemory); + LOAD_DEVICE_FUNC(device, vkBindImageMemory); + LOAD_DEVICE_FUNC(device, vkGetBufferMemoryRequirements); + LOAD_DEVICE_FUNC(device, vkGetImageMemoryRequirements); + LOAD_DEVICE_FUNC(device, vkGetImageSparseMemoryRequirements); + LOAD_DEVICE_FUNC(device, vkGetPhysicalDeviceSparseImageFormatProperties); + LOAD_DEVICE_FUNC(device, vkQueueBindSparse); + LOAD_DEVICE_FUNC(device, vkCreateFence); + LOAD_DEVICE_FUNC(device, vkDestroyFence); + LOAD_DEVICE_FUNC(device, vkResetFences); + LOAD_DEVICE_FUNC(device, vkGetFenceStatus); + LOAD_DEVICE_FUNC(device, vkWaitForFences); + LOAD_DEVICE_FUNC(device, vkCreateSemaphore); + LOAD_DEVICE_FUNC(device, vkDestroySemaphore); + LOAD_DEVICE_FUNC(device, vkCreateEvent); + LOAD_DEVICE_FUNC(device, vkDestroyEvent); + LOAD_DEVICE_FUNC(device, vkGetEventStatus); + LOAD_DEVICE_FUNC(device, vkSetEvent); + LOAD_DEVICE_FUNC(device, vkResetEvent); + LOAD_DEVICE_FUNC(device, vkCreateQueryPool); + LOAD_DEVICE_FUNC(device, vkDestroyQueryPool); + LOAD_DEVICE_FUNC(device, vkGetQueryPoolResults); + LOAD_DEVICE_FUNC(device, vkCreateBuffer); + LOAD_DEVICE_FUNC(device, vkDestroyBuffer); + LOAD_DEVICE_FUNC(device, vkCreateBufferView); + LOAD_DEVICE_FUNC(device, vkDestroyBufferView); + LOAD_DEVICE_FUNC(device, vkCreateImage); + LOAD_DEVICE_FUNC(device, vkDestroyImage); + LOAD_DEVICE_FUNC(device, vkGetImageSubresourceLayout); + LOAD_DEVICE_FUNC(device, vkCreateImageView); + LOAD_DEVICE_FUNC(device, vkDestroyImageView); + LOAD_DEVICE_FUNC(device, vkCreateShaderModule); + LOAD_DEVICE_FUNC(device, vkDestroyShaderModule); + LOAD_DEVICE_FUNC(device, vkCreatePipelineCache); + LOAD_DEVICE_FUNC(device, vkDestroyPipelineCache); + LOAD_DEVICE_FUNC(device, vkGetPipelineCacheData); + LOAD_DEVICE_FUNC(device, vkMergePipelineCaches); + LOAD_DEVICE_FUNC(device, vkCreateGraphicsPipelines); + LOAD_DEVICE_FUNC(device, vkCreateComputePipelines); + LOAD_DEVICE_FUNC(device, vkDestroyPipeline); + LOAD_DEVICE_FUNC(device, vkCreatePipelineLayout); + LOAD_DEVICE_FUNC(device, vkDestroyPipelineLayout); + LOAD_DEVICE_FUNC(device, vkCreateSampler); + LOAD_DEVICE_FUNC(device, vkDestroySampler); + LOAD_DEVICE_FUNC(device, vkCreateDescriptorSetLayout); + LOAD_DEVICE_FUNC(device, vkDestroyDescriptorSetLayout); + LOAD_DEVICE_FUNC(device, vkCreateDescriptorPool); + LOAD_DEVICE_FUNC(device, vkDestroyDescriptorPool); + LOAD_DEVICE_FUNC(device, vkResetDescriptorPool); + LOAD_DEVICE_FUNC(device, vkAllocateDescriptorSets); + LOAD_DEVICE_FUNC(device, vkFreeDescriptorSets); + LOAD_DEVICE_FUNC(device, vkUpdateDescriptorSets); + LOAD_DEVICE_FUNC(device, vkCreateFramebuffer); + LOAD_DEVICE_FUNC(device, vkDestroyFramebuffer); + LOAD_DEVICE_FUNC(device, vkCreateRenderPass); + LOAD_DEVICE_FUNC(device, vkDestroyRenderPass); + LOAD_DEVICE_FUNC(device, vkGetRenderAreaGranularity); + LOAD_DEVICE_FUNC(device, vkCreateCommandPool); + LOAD_DEVICE_FUNC(device, vkDestroyCommandPool); + LOAD_DEVICE_FUNC(device, vkResetCommandPool); + LOAD_DEVICE_FUNC(device, vkAllocateCommandBuffers); + LOAD_DEVICE_FUNC(device, vkFreeCommandBuffers); + LOAD_DEVICE_FUNC(device, vkBeginCommandBuffer); + LOAD_DEVICE_FUNC(device, vkEndCommandBuffer); + LOAD_DEVICE_FUNC(device, vkResetCommandBuffer); + LOAD_DEVICE_FUNC(device, vkCmdBindPipeline); + LOAD_DEVICE_FUNC(device, vkCmdSetViewport); + LOAD_DEVICE_FUNC(device, vkCmdSetScissor); + LOAD_DEVICE_FUNC(device, vkCmdSetLineWidth); + LOAD_DEVICE_FUNC(device, vkCmdSetDepthBias); + LOAD_DEVICE_FUNC(device, vkCmdSetBlendConstants); + LOAD_DEVICE_FUNC(device, vkCmdSetDepthBounds); + LOAD_DEVICE_FUNC(device, vkCmdSetStencilCompareMask); + LOAD_DEVICE_FUNC(device, vkCmdSetStencilWriteMask); + LOAD_DEVICE_FUNC(device, vkCmdSetStencilReference); + LOAD_DEVICE_FUNC(device, vkCmdBindDescriptorSets); + LOAD_DEVICE_FUNC(device, vkCmdBindIndexBuffer); + LOAD_DEVICE_FUNC(device, vkCmdBindVertexBuffers); + LOAD_DEVICE_FUNC(device, vkCmdDraw); + LOAD_DEVICE_FUNC(device, vkCmdDrawIndexed); + LOAD_DEVICE_FUNC(device, vkCmdDrawIndirect); + LOAD_DEVICE_FUNC(device, vkCmdDrawIndexedIndirect); + LOAD_DEVICE_FUNC(device, vkCmdDispatch); + LOAD_DEVICE_FUNC(device, vkCmdDispatchIndirect); + LOAD_DEVICE_FUNC(device, vkCmdCopyBuffer); + LOAD_DEVICE_FUNC(device, vkCmdCopyImage); + LOAD_DEVICE_FUNC(device, vkCmdBlitImage); + LOAD_DEVICE_FUNC(device, vkCmdCopyBufferToImage); + LOAD_DEVICE_FUNC(device, vkCmdCopyImageToBuffer); + LOAD_DEVICE_FUNC(device, vkCmdUpdateBuffer); + LOAD_DEVICE_FUNC(device, vkCmdFillBuffer); + LOAD_DEVICE_FUNC(device, vkCmdClearColorImage); + LOAD_DEVICE_FUNC(device, vkCmdClearDepthStencilImage); + LOAD_DEVICE_FUNC(device, vkCmdClearAttachments); + LOAD_DEVICE_FUNC(device, vkCmdResolveImage); + LOAD_DEVICE_FUNC(device, vkCmdSetEvent); + LOAD_DEVICE_FUNC(device, vkCmdResetEvent); + LOAD_DEVICE_FUNC(device, vkCmdWaitEvents); + LOAD_DEVICE_FUNC(device, vkCmdPipelineBarrier); + LOAD_DEVICE_FUNC(device, vkCmdBeginQuery); + LOAD_DEVICE_FUNC(device, vkCmdEndQuery); + LOAD_DEVICE_FUNC(device, vkCmdResetQueryPool); + LOAD_DEVICE_FUNC(device, vkCmdWriteTimestamp); + LOAD_DEVICE_FUNC(device, vkCmdCopyQueryPoolResults); + LOAD_DEVICE_FUNC(device, vkCmdPushConstants); + LOAD_DEVICE_FUNC(device, vkCmdBeginRenderPass); + LOAD_DEVICE_FUNC(device, vkCmdNextSubpass); + LOAD_DEVICE_FUNC(device, vkCmdEndRenderPass); + LOAD_DEVICE_FUNC(device, vkCmdExecuteCommands); + if (enabledExtensions.EXT_external_memory_host) { LOAD_DEVICE_FUNC(device, vkGetMemoryHostPointerPropertiesEXT); } diff --git a/Core/Compatibility.cpp b/Core/Compatibility.cpp index 7dd884d49e..3778bd8361 100644 --- a/Core/Compatibility.cpp +++ b/Core/Compatibility.cpp @@ -68,6 +68,7 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) { CheckSetting(iniFile, gameID, "DisableAccurateDepth", &flags_.DisableAccurateDepth); CheckSetting(iniFile, gameID, "MGS2AcidHack", &flags_.MGS2AcidHack); CheckSetting(iniFile, gameID, "SonicRivalsHack", &flags_.SonicRivalsHack); + CheckSetting(iniFile, gameID, "RenderPassMerge", &flags_.RenderPassMerge); CheckSetting(iniFile, gameID, "AdhocHackDisableNBSwitch", &flags_.AdhocHackDisableNBSwitch); CheckSetting(iniFile, gameID, "AdhocHackStopFriendClear", &flags_.AdhocHackStopFriendClear); CheckSetting(iniFile, gameID, "AdhocHackPhantasyStarOffset", &flags_.AdhocHackPhantasyStarOffset); diff --git a/Core/Compatibility.h b/Core/Compatibility.h index aea49a7091..8b8643f291 100644 --- a/Core/Compatibility.h +++ b/Core/Compatibility.h @@ -68,6 +68,7 @@ struct CompatFlags { bool DisableAccurateDepth; bool MGS2AcidHack; bool SonicRivalsHack; + bool RenderPassMerge; bool AdhocHackDisableNBSwitch; bool AdhocHackStopFriendClear; bool AdhocHackPhantasyStarOffset; diff --git a/Core/Config.cpp b/Core/Config.cpp index d11b573bf4..3aacba4d15 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -582,7 +582,14 @@ static int DefaultGPUBackend() { if (DoesVersionMatchWindows(6, 2, 0, 0, true)) { return (int)GPUBackend::DIRECT3D11; } +#elif PPSSPP_PLATFORM(ANDROID) + // Default to Vulkan only on Pie (level 28) devices or newer. Drivers before Pie + // were generally too unreliable to default to (with some exceptions, of course). + if (System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 28) { + return (int)GPUBackend::VULKAN; + } #endif + // TODO: On some additional Linux platforms, we should also default to Vulkan. return (int)GPUBackend::OPENGL; } @@ -980,9 +987,10 @@ static ConfigSetting debuggerSettings[] = { ConfigSetting("FontWidth", &g_Config.iFontWidth, 8), ConfigSetting("FontHeight", &g_Config.iFontHeight, 12), ConfigSetting("DisplayStatusBar", &g_Config.bDisplayStatusBar, true), - ConfigSetting("ShowBottomTabTitles",&g_Config.bShowBottomTabTitles,true), + ConfigSetting("ShowBottomTabTitles",&g_Config.bShowBottomTabTitles, true), ConfigSetting("ShowDeveloperMenu", &g_Config.bShowDeveloperMenu, false), ConfigSetting("ShowAllocatorDebug", &g_Config.bShowAllocatorDebug, false, false), + ConfigSetting("ShowGpuProfile", &g_Config.bShowGpuProfile, false, false), ConfigSetting("SkipDeadbeefFilling", &g_Config.bSkipDeadbeefFilling, false), ConfigSetting("FuncHashMap", &g_Config.bFuncHashMap, false), diff --git a/Core/Config.h b/Core/Config.h index c7edea1cbb..ccc44055d6 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -71,21 +71,20 @@ public: bool bEnableLogging; bool bDumpDecryptedEboot; bool bFullscreenOnDoubleclick; -#if defined(USING_WIN_UI) + + // These four are Win UI only bool bPauseOnLostFocus; bool bTopMost; bool bIgnoreWindowsKey; bool bRestartRequired; -#endif -#if defined(USING_WIN_UI) || defined(USING_QT_UI) || PPSSPP_PLATFORM(UWP) + std::string sFont; -#endif bool bPauseWhenMinimized; -#if !defined(MOBILE_DEVICE) + // Not used on mobile devices. bool bPauseExitsEmulator; -#endif + bool bPauseMenuExitsEmulator; // Core @@ -136,9 +135,8 @@ public: // 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 + std::string sD3D11Device; // Windows only + bool bSoftwareRendering; bool bHardwareTransform; // only used in the GLES backend bool bSoftwareSkinning; // may speed up some games @@ -247,6 +245,7 @@ public: bool bLogFrameDrops; bool bShowDebugStats; bool bShowAudioDebug; + bool bShowGpuProfile; bool bAudioResampler; //Analog stick tilting @@ -379,8 +378,8 @@ public: int iPSPModel; int iFirmwareVersion; // TODO: Make this work with your platform, too! -#if defined(USING_WIN_UI) bool bBypassOSKWithKeyboard; +#if defined(USING_WIN_UI) bool bDisableWinMenu; bool bDisableWinBorders; #endif diff --git a/Core/HLE/sceJpeg.cpp b/Core/HLE/sceJpeg.cpp index 790bcb0bb6..f532233203 100644 --- a/Core/HLE/sceJpeg.cpp +++ b/Core/HLE/sceJpeg.cpp @@ -23,6 +23,7 @@ #include "Core/HLE/FunctionWrappers.h" #include "Core/HLE/sceJpeg.h" #include "Core/HLE/sceMpeg.h" +#include "GPU/GPUCommon.h" #include "Core/MemMap.h" #include "Core/Reporting.h" @@ -112,7 +113,10 @@ static void __JpegCsc(u32 imageAddr, u32 yCbCrAddr, int widthHeight, int bufferW static int sceJpegMJpegCsc(u32 imageAddr, u32 yCbCrAddr, int widthHeight, int bufferWidth) { __JpegCsc(imageAddr, yCbCrAddr, widthHeight, bufferWidth); - DEBUG_LOG(ME, "sceJpegMJpegCsc(%i, %i, %i, %i)", imageAddr, yCbCrAddr, widthHeight, bufferWidth); + int width = (widthHeight >> 16) & 0xFFF; + int height = widthHeight & 0xFFF; + DEBUG_LOG(ME, "sceJpegMJpegCsc(%08x, %08x, (%dx%d), %i)", imageAddr, yCbCrAddr, width, height, bufferWidth); + gpu->NotifyVideoUpload(imageAddr, width * height * 4, width, GE_FORMAT_8888); return 0; } @@ -165,7 +169,7 @@ static int sceJpegDecodeMJpeg(u32 jpegAddr, int jpegSize, u32 imageAddr, int dht return 0; } - DEBUG_LOG(ME, "sceJpegDecodeMJpeg(%i, %i, %i, %i)", jpegAddr, jpegSize, imageAddr, dhtMode); + DEBUG_LOG(ME, "sceJpegDecodeMJpeg(%08x, %i, %08x, %i)", jpegAddr, jpegSize, imageAddr, dhtMode); return __DecodeJpeg(jpegAddr, jpegSize, imageAddr); } @@ -180,19 +184,19 @@ static int sceJpegDecodeMJpegSuccessively(u32 jpegAddr, int jpegSize, u32 imageA return 0; } - DEBUG_LOG(ME, "sceJpegDecodeMJpegSuccessively(%i, %i, %i, %i)", jpegAddr, jpegSize, imageAddr, dhtMode); + DEBUG_LOG(ME, "sceJpegDecodeMJpegSuccessively(%08x, %i, %08x, %i)", jpegAddr, jpegSize, imageAddr, dhtMode); return __DecodeJpeg(jpegAddr, jpegSize, imageAddr); } static int sceJpegCsc(u32 imageAddr, u32 yCbCrAddr, int widthHeight, int bufferWidth, int colourInfo) { if (bufferWidth < 0 || widthHeight < 0){ - WARN_LOG(ME, "sceJpegCsc(%i, %i, %i, %i, %i)", imageAddr, yCbCrAddr, widthHeight, bufferWidth, colourInfo); + WARN_LOG(ME, "sceJpegCsc(%08x, %08x, %i, %i, %i)", imageAddr, yCbCrAddr, widthHeight, bufferWidth, colourInfo); return ERROR_JPEG_INVALID_VALUE; } __JpegCsc(imageAddr, yCbCrAddr, widthHeight, bufferWidth); - DEBUG_LOG(ME, "sceJpegCsc(%i, %i, %i, %i, %i)", imageAddr, yCbCrAddr, widthHeight, bufferWidth, colourInfo); + DEBUG_LOG(ME, "sceJpegCsc(%08x, %08x, %i, %i, %i)", imageAddr, yCbCrAddr, widthHeight, bufferWidth, colourInfo); return 0; } @@ -257,7 +261,7 @@ static int sceJpegGetOutputInfo(u32 jpegAddr, int jpegSize, u32 colourInfoAddr, return getYCbCrBufferSize(0, 0); } - DEBUG_LOG(ME, "sceJpegGetOutputInfo(%i, %i, %i, %i)", jpegAddr, jpegSize, colourInfoAddr, dhtMode); + DEBUG_LOG(ME, "sceJpegGetOutputInfo(%08x, %i, %08x, %i)", jpegAddr, jpegSize, colourInfoAddr, dhtMode); return __JpegGetOutputInfo(jpegAddr, jpegSize, colourInfoAddr); } @@ -345,7 +349,7 @@ static int sceJpegDecodeMJpegYCbCr(u32 jpegAddr, int jpegSize, u32 yCbCrAddr, in return getWidthHeight(0, 0); } - DEBUG_LOG(ME, "sceJpegDecodeMJpegYCbCr(%i, %i, %i, %i, %i)", jpegAddr, jpegSize, yCbCrAddr, yCbCrSize, dhtMode); + DEBUG_LOG(ME, "sceJpegDecodeMJpegYCbCr(%08x, %i, %08x, %i, %i)", jpegAddr, jpegSize, yCbCrAddr, yCbCrSize, dhtMode); return __JpegDecodeMJpegYCbCr(jpegAddr, jpegSize, yCbCrAddr); } @@ -355,7 +359,7 @@ static int sceJpegDecodeMJpegYCbCrSuccessively(u32 jpegAddr, int jpegSize, u32 y return getWidthHeight(0, 0); } - DEBUG_LOG(ME, "sceJpegDecodeMJpegYCbCrSuccessively(%i, %i, %i, %i, %i)", jpegAddr, jpegSize, yCbCrAddr, yCbCrSize, dhtMode); + DEBUG_LOG(ME, "sceJpegDecodeMJpegYCbCrSuccessively(%08x, %i, %08x, %i, %i)", jpegAddr, jpegSize, yCbCrAddr, yCbCrSize, dhtMode); // Do as same way as sceJpegDecodeMJpegYCbCr() but with smaller block size return __JpegDecodeMJpegYCbCr(jpegAddr, jpegSize, yCbCrAddr); } diff --git a/Core/HLE/sceKernel.cpp b/Core/HLE/sceKernel.cpp index bf409a247c..c375c59a04 100644 --- a/Core/HLE/sceKernel.cpp +++ b/Core/HLE/sceKernel.cpp @@ -81,6 +81,7 @@ #include "sceHeap.h" #include "sceDmac.h" #include "sceMp4.h" +#include "sceUsbCam.h" #include "../Util/PPGeDraw.h" @@ -143,6 +144,7 @@ void __KernelInit() __AudioCodecInit(); __VideoPmpInit(); __UsbGpsInit(); + __UsbCamInit(); SaveState::Init(); // Must be after IO, as it may create a directory Reporting::Init(); @@ -166,6 +168,8 @@ void __KernelShutdown() hleCurrentThreadName = NULL; kernelObjects.Clear(); + __UsbCamShutdown(); + __AudioCodecShutdown(); __VideoPmpShutdown(); __AACShutdown(); diff --git a/Core/HLE/sceUsb.cpp b/Core/HLE/sceUsb.cpp index 7ddf16ec61..361f55f26b 100644 --- a/Core/HLE/sceUsb.cpp +++ b/Core/HLE/sceUsb.cpp @@ -63,13 +63,13 @@ void __UsbDoState(PointerWrap &p) } static int sceUsbStart(const char* driverName, u32 argsSize, u32 argsPtr) { - ERROR_LOG(HLE, "UNIMPL sceUsbStart(%s, %i, %08x)", driverName, argsSize, argsPtr); + INFO_LOG(HLE, "sceUsbStart(%s, %i, %08x)", driverName, argsSize, argsPtr); usbStarted = true; return 0; } static int sceUsbStop(const char* driverName, u32 argsSize, u32 argsPtr) { - ERROR_LOG(HLE, "UNIMPL sceUsbStop(%s, %i, %08x)", driverName, argsSize, argsPtr); + INFO_LOG(HLE, "sceUsbStop(%s, %i, %08x)", driverName, argsSize, argsPtr); usbStarted = false; return 0; } @@ -83,18 +83,18 @@ static int sceUsbGetState() { | (usbConnected ? USB_STATUS_CONNECTED : USB_STATUS_DISCONNECTED) | (usbActivated ? USB_STATUS_ACTIVATED : USB_STATUS_DEACTIVATED); } - ERROR_LOG(HLE, "UNIMPL sceUsbGetState: 0x%x", state); + INFO_LOG(HLE, "sceUsbGetState: 0x%x", state); return state; } static int sceUsbActivate(u32 pid) { - ERROR_LOG(HLE, "UNIMPL sceUsbActivate(%i)", pid); + INFO_LOG(HLE, "sceUsbActivate(%i)", pid); usbActivated = true; return 0; } static int sceUsbDeactivate(u32 pid) { - ERROR_LOG(HLE, "UNIMPL sceUsbDeactivate(%i)", pid); + INFO_LOG(HLE, "sceUsbDeactivate(%i)", pid); usbActivated = false; return 0; } diff --git a/Core/HLE/sceUsbCam.cpp b/Core/HLE/sceUsbCam.cpp index e1b5986e8d..7667cc335f 100644 --- a/Core/HLE/sceUsbCam.cpp +++ b/Core/HLE/sceUsbCam.cpp @@ -15,6 +15,7 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#include #include #include "base/NativeApp.h" @@ -29,9 +30,25 @@ PspUsbCamSetupVideoParam videoParam; unsigned int videoBufferLength = 0; unsigned int nextVideoFrame = 0; -unsigned char videoBuffer[40 * 1000]; +uint8_t *videoBuffer; std::mutex videoBufferMutex; +enum { + VIDEO_BUFFER_SIZE = 40 * 1000, +}; + +void __UsbCamInit() { + videoBuffer = new uint8_t[VIDEO_BUFFER_SIZE]; +} + +void __UsbCamShutdown() { + delete[] videoBuffer; + videoBuffer = nullptr; +} + +// TODO: Technically, we should store the videoBuffer into the savestate, if this +// module has been initialized. + static int sceUsbCamSetupMic(u32 paramAddr, u32 workareaAddr, int wasize) { INFO_LOG(HLE, "UNIMPL sceUsbCamSetupMic"); if (Memory::IsValidRange(paramAddr, sizeof(micParam))) { @@ -67,13 +84,14 @@ static int sceUsbCamSetupVideo(u32 paramAddr, u32 workareaAddr, int wasize) { Memory::ReadStruct(paramAddr, &videoParam); } - std::lock_guard lock(videoBufferMutex); - videoBufferLength = sizeof(sceUsbCamDummyImage); - memset(videoBuffer, 0, sizeof(videoBuffer)); - memcpy(videoBuffer, sceUsbCamDummyImage, sizeof(sceUsbCamDummyImage)); - INFO_LOG(HLE, "UNIMPL sceUsbCamSetupVideo - size: %d", videoParam.size); INFO_LOG(HLE, "UNIMPL sceUsbCamSetupVideo - resolution: %d", videoParam.resolution); + INFO_LOG(HLE, "UNIMPL sceUsbCamSetupVideo - framesize: %d", videoParam.framesize); + + std::lock_guard lock(videoBufferMutex); + videoBufferLength = sizeof(sceUsbCamDummyImage); + memset(videoBuffer, 0, VIDEO_BUFFER_SIZE); + memcpy(videoBuffer, sceUsbCamDummyImage, sizeof(sceUsbCamDummyImage)); return 0; } @@ -96,27 +114,26 @@ static int sceUsbCamAutoImageReverseSW(int rev) { static int sceUsbCamReadVideoFrameBlocking(u32 bufAddr, u32 size) { std::lock_guard lock(videoBufferMutex); - for (unsigned int i = 0; i < videoBufferLength && i < size; i++) { - if (Memory::IsValidAddress(bufAddr + i)) { - Memory::Write_U8(videoBuffer[i], bufAddr + i); - } + + u32 transferSize = std::min(videoBufferLength, size); + if (Memory::IsValidRange(bufAddr, size)) { + Memory::Memcpy(bufAddr, videoBuffer, transferSize); } return videoBufferLength; } static int sceUsbCamReadVideoFrame(u32 bufAddr, u32 size) { std::lock_guard lock(videoBufferMutex); - for (unsigned int i = 0; i < videoBufferLength && i < size; i++) { - if (Memory::IsValidAddress(bufAddr + i)) { - Memory::Write_U8(videoBuffer[i], bufAddr + i); - } + u32 transferSize = std::min(videoBufferLength, size); + if (Memory::IsValidRange(bufAddr, size)) { + Memory::Memcpy(bufAddr, videoBuffer, transferSize); } nextVideoFrame = videoBufferLength; return 0; } static int sceUsbCamPollReadVideoFrameEnd() { - INFO_LOG(HLE, "UNIMPL sceUsbCamPollReadVideoFrameEnd: %d", nextVideoFrame); + VERBOSE_LOG(HLE, "UNIMPL sceUsbCamPollReadVideoFrameEnd: %d", nextVideoFrame); return nextVideoFrame; } @@ -186,7 +203,12 @@ void Register_sceUsbCam() void Camera::pushCameraImage(long long length, unsigned char* image) { std::lock_guard lock(videoBufferMutex); - videoBufferLength = length; - memset (videoBuffer, 0, sizeof(videoBuffer)); - memcpy (videoBuffer, image, length); + memset(videoBuffer, 0, VIDEO_BUFFER_SIZE); + if (length > VIDEO_BUFFER_SIZE) { + videoBufferLength = 0; + ERROR_LOG(HLE, "pushCameraImage: length error: %lld > %d", length, VIDEO_BUFFER_SIZE); + } else { + videoBufferLength = length; + memcpy(videoBuffer, image, length); + } } diff --git a/Core/HLE/sceUsbCam.h b/Core/HLE/sceUsbCam.h index ef088fe91d..ebb5388db6 100644 --- a/Core/HLE/sceUsbCam.h +++ b/Core/HLE/sceUsbCam.h @@ -19,6 +19,9 @@ void Register_sceUsbCam(); +void __UsbCamInit(); +void __UsbCamShutdown(); + namespace Camera { void pushCameraImage(long long length, unsigned char *image); } diff --git a/Core/HLE/sceUsbMic.h b/Core/HLE/sceUsbMic.h index 6540246f12..35e1132861 100644 --- a/Core/HLE/sceUsbMic.h +++ b/Core/HLE/sceUsbMic.h @@ -16,5 +16,4 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #pragma once - void Register_sceUsbMic(); diff --git a/Core/HW/SasAudio.cpp b/Core/HW/SasAudio.cpp index 4a1a7459ed..a0afc9e9b4 100644 --- a/Core/HW/SasAudio.cpp +++ b/Core/HW/SasAudio.cpp @@ -413,7 +413,9 @@ int SasInstance::EstimateMixUs() { } // Each voice costs extra time, and each byte of grain costs extra time. - return 20 + voicesPlayingCount * 68 + (grainSize * 60) / 100; + int cycles = 20 + voicesPlayingCount * 68 + (grainSize * 60) / 100; + // Cap to 1200 to fix FFT, see issue #9956. + return std::min(cycles, 1200); } void SasVoice::ReadSamples(s16 *output, int numSamples) { diff --git a/Core/MIPS/ARM/ArmJit.cpp b/Core/MIPS/ARM/ArmJit.cpp index 0e091ff792..5820509c78 100644 --- a/Core/MIPS/ARM/ArmJit.cpp +++ b/Core/MIPS/ARM/ArmJit.cpp @@ -111,7 +111,7 @@ ArmJit::ArmJit(MIPSState *mips) : blocks(mips, this), gpr(mips, &js, &jo), fpr(m AllocCodeSpace(1024 * 1024 * 16); // 32MB is the absolute max because that's what an ARM branch instruction can reach, backwards and forwards. GenerateFixedCode(); - INFO_LOG(JIT, "ARM JIT initialized: %d MB of code space", GetSpaceLeft() / (1024 * 1024)); + INFO_LOG(JIT, "ARM JIT initialized: %lld MB of code space", (long long)(GetSpaceLeft() / (1024 * 1024))); js.startDefaultPrefix = mips_->HasDefaultPrefix(); diff --git a/Core/MIPS/x86/RegCacheFPU.h b/Core/MIPS/x86/RegCacheFPU.h index dfd22d5c35..dd0916ebc9 100644 --- a/Core/MIPS/x86/RegCacheFPU.h +++ b/Core/MIPS/x86/RegCacheFPU.h @@ -126,11 +126,11 @@ public: const Gen::OpArg &R(int freg) const {return regs[freg].location;} const Gen::OpArg &V(int vreg) const { - _dbg_assert_msg_(JIT, vregs[vreg].lane == 0, "SIMD reg %d used as V reg (use VS instead)", vreg); + _dbg_assert_msg_(JIT, vregs[vreg].lane == 0, "SIMD reg %d used as V reg (use VS instead). pc=%08x", vreg, mips->pc); return vregs[vreg].location; } const Gen::OpArg &VS(const u8 *vs) const { - _dbg_assert_msg_(JIT, vregs[vs[0]].lane != 0, "V reg %d used as VS reg (use V instead)", vs[0]); + _dbg_assert_msg_(JIT, vregs[vs[0]].lane != 0, "V reg %d used as VS reg (use V instead). pc=%08x", vs[0], mips->pc); return vregs[vs[0]].location; } @@ -142,7 +142,7 @@ public: } Gen::X64Reg VX(int vreg) const { - _dbg_assert_msg_(JIT, vregs[vreg].lane == 0, "SIMD reg %d used as V reg (use VSX instead)", vreg); + _dbg_assert_msg_(JIT, vregs[vreg].lane == 0, "SIMD reg %d used as V reg (use VSX instead). pc=%08x", vreg, mips->pc); if (vregs[vreg].away && vregs[vreg].location.IsSimpleReg()) return vregs[vreg].location.GetSimpleReg(); PanicAlert("Not so simple - v%i", vreg); @@ -150,7 +150,7 @@ public: } Gen::X64Reg VSX(const u8 *vs) const { - _dbg_assert_msg_(JIT, vregs[vs[0]].lane != 0, "V reg %d used as VS reg (use VX instead)", vs[0]); + _dbg_assert_msg_(JIT, vregs[vs[0]].lane != 0, "V reg %d used as VS reg (use VX instead). pc=%08x", vs[0], mips->pc); if (vregs[vs[0]].away && vregs[vs[0]].location.IsSimpleReg()) return vregs[vs[0]].location.GetSimpleReg(); PanicAlert("Not so simple - v%i", vs[0]); diff --git a/Core/WebServer.cpp b/Core/WebServer.cpp index b9fd5169f0..3bec6d622a 100644 --- a/Core/WebServer.cpp +++ b/Core/WebServer.cpp @@ -37,7 +37,6 @@ enum class ServerStatus { STARTING, RUNNING, STOPPING, - RESTARTING, }; static const char *REPORT_HOSTNAME = "report.ppsspp.org"; @@ -53,15 +52,6 @@ static void UpdateStatus(ServerStatus s) { 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; @@ -120,81 +110,129 @@ bool RemoteISOFileSupported(const std::string &filename) { return true; } return false; - } -static void RegisterDiscHandlers(http::Server *http, std::unordered_map *paths) { - for (std::string filename : g_Config.recentIsos) { +static std::string RemotePathForRecent(const std::string &filename) { #ifdef _WIN32 - static const std::string sep = "\\/"; + static const std::string sep = "\\/"; #else - static const std::string sep = "/"; + 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)); + 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 (RemoteISOFileSupported(basename)) { - (*paths)[ReplaceAll(basename, " ", "%20")] = filename; + if (basename == "/EBOOT.PBP") { + // Go up one more folder. + size_t nextpos = filename.find_last_of(sep, basepos - 1); + basename = "/" + (nextpos == filename.npos ? filename : filename.substr(nextpos + 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 (RemoteISOFileSupported(basename)) { + return ReplaceAll(basename, " ", "%20"); + } + return ""; +} + +static std::string LocalFromRemotePath(const std::string &path) { + for (const std::string &filename : g_Config.recentIsos) { + std::string basename = RemotePathForRecent(filename); + if (basename == path) { + return filename; + } + } + return ""; +} + +static void DiscHandler(const http::Request &request, const std::string &filename) { + s64 sz = File::GetFileSize(filename); + + std::string range; + if (request.Method() == http::RequestHeader::HEAD) { + request.WriteHttpResponseHeader("1.0", 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("1.0", 400, -1, "text/plain"); + request.Out()->Push("Could not understand range request."); + return; + } + + if (begin < 0 || begin > last || last >= sz) { + request.WriteHttpResponseHeader("1.0", 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("1.0", 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("1.0", 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("1.0", 418, -1, "text/plain"); + request.Out()->Push("This server only supports range requests."); + } +} + +static void HandleListing(const http::Request &request) { + request.WriteHttpResponseHeader("1.0", 200, -1, "text/plain"); + request.Out()->Printf("/\n"); + if (serverFlags & (int)WebServerFlags::DISCS) { + // List the current discs in their recent order. + for (const std::string &filename : g_Config.recentIsos) { + std::string basename = RemotePathForRecent(filename); + if (!basename.empty()) { + request.Out()->Printf("%s\n", basename.c_str()); + } + } + } + if (serverFlags & (int)WebServerFlags::DEBUGGER) { + request.Out()->Printf("/debugger\n"); + } +} + +static void HandleFallback(const http::Request &request) { + if (serverFlags & (int)WebServerFlags::DISCS) { + std::string filename = LocalFromRemotePath(request.resource()); + if (!filename.empty()) { + DiscHandler(request, filename); + return; } } - auto handler = [paths](const http::Request &request) { - std::string filename = (*paths)[request.resource()]; - s64 sz = File::GetFileSize(filename); + static const std::string payload = "404 not found\r\n"; + request.WriteHttpResponseHeader("1.0", 404, (int)payload.size(), "text/plain"); + request.Out()->Push(payload); +} - std::string range; - if (request.Method() == http::RequestHeader::HEAD) { - request.WriteHttpResponseHeader("1.0", 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("1.0", 400, -1, "text/plain"); - request.Out()->Push("Could not understand range request."); - return; - } - - if (begin < 0 || begin > last || last >= sz) { - request.WriteHttpResponseHeader("1.0", 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("1.0", 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("1.0", 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("1.0", 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 ForwardDebuggerRequest(const http::Request &request) { + if (serverFlags & (int)WebServerFlags::DEBUGGER) { + HandleDebuggerRequest(request); + } else { + HandleFallback(request); } } @@ -202,14 +240,10 @@ 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); - } + http->RegisterHandler("/", &HandleListing); + // This lists all the (current) recent ISOs. + http->SetFallbackHandler(&HandleFallback); + http->RegisterHandler("/debugger", &ForwardDebuggerRequest); if (!http->Listen(g_Config.iRemoteISOPort)) { if (!http->Listen(0)) { @@ -237,24 +271,16 @@ static void ExecuteWebServer() { StopAllDebuggers(); delete http; - // Move to STARTING to lock flags/STOPPING. - if (UpdateStatus(ServerStatus::STARTING, ServerStatus::RESTARTING)) { - ExecuteWebServer(); - } else { - UpdateStatus(ServerStatus::STOPPED); - } + 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; @@ -272,24 +298,19 @@ bool StartWebServer(WebServerFlags flags) { bool StopWebServer(WebServerFlags flags) { std::lock_guard guard(serverStatusLock); - if (serverStatus != ServerStatus::RUNNING && serverStatus != ServerStatus::RESTARTING) { + if (serverStatus != ServerStatus::RUNNING) { 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; } diff --git a/GPU/Common/FramebufferCommon.cpp b/GPU/Common/FramebufferCommon.cpp index ef1a9eb8e8..2fdefdbc66 100644 --- a/GPU/Common/FramebufferCommon.cpp +++ b/GPU/Common/FramebufferCommon.cpp @@ -2085,11 +2085,6 @@ void FramebufferManagerCommon::DownloadFramebufferForClut(u32 fb_address, u32 lo int w = std::min(pixels % vfb->fb_stride, (int)vfb->width); int h = std::min((pixels + vfb->fb_stride - 1) / vfb->fb_stride, (int)vfb->height); - if (w == 0 && h > 0) { - // Exactly aligned (means pixels == vfb->fb_stride, so modulus gave zero.) - w = std::min(vfb->fb_stride, (int)vfb->width); - } - // We might still have a pending draw to the fb in question, flush if so. FlushBeforeCopy(); diff --git a/GPU/Common/TextureScalerCommon.cpp b/GPU/Common/TextureScalerCommon.cpp index 1adb605d35..ba5e43c4fe 100644 --- a/GPU/Common/TextureScalerCommon.cpp +++ b/GPU/Common/TextureScalerCommon.cpp @@ -44,6 +44,8 @@ // Report the time and throughput for each larger scaling operation in the log //#define SCALING_MEASURE_TIME +//#define DEBUG_SCALER_OUTPUT + #ifdef SCALING_MEASURE_TIME #include "base/timeutil.h" #endif @@ -460,6 +462,8 @@ void bilinearV(int factor, u32* data, u32* out, int w, int gl, int gu, int l, in #undef B #undef A +#ifdef DEBUG_SCALER_OUTPUT + // used for debugging texture scaling (writing textures to files) static int g_imgCount = 0; void dbgPPM(int w, int h, u8* pixels, const char* prefix = "dbg") { // 3 component RGB @@ -490,6 +494,9 @@ void dbgPGM(int w, int h, u32* pixels, const char* prefix = "dbg") { // 1 compon } fclose(fp); } + +#endif + } /////////////////////////////////////// Texture Scaler diff --git a/GPU/Vulkan/DebugVisVulkan.cpp b/GPU/Vulkan/DebugVisVulkan.cpp index d93195bc16..8f2cc381a4 100644 --- a/GPU/Vulkan/DebugVisVulkan.cpp +++ b/GPU/Vulkan/DebugVisVulkan.cpp @@ -26,6 +26,8 @@ #include "GPU/Vulkan/GPU_Vulkan.h" #include "GPU/Vulkan/VulkanUtil.h" +#undef DrawText + void DrawAllocatorVis(UIContext *ui, GPUInterface *gpu) { if (!gpu) { return; @@ -94,3 +96,28 @@ void DrawAllocatorVis(UIContext *ui, GPUInterface *gpu) { for (auto iter : texturesToDelete) iter->Release(); } + +void DrawProfilerVis(UIContext *ui, GPUInterface *gpu) { + if (!gpu) { + return; + } + using namespace Draw; + const int padding = 10; + const int columnWidth = 256; + const int starty = padding * 8; + int x = padding; + int y = starty; + int w = columnWidth; // We will double this when actually drawing to make the pixels visible. + + ui->Begin(); + + GPU_Vulkan *gpuVulkan = static_cast(gpu); + + std::string text = gpuVulkan->GetGpuProfileString(); + + Draw::DrawContext *draw = ui->GetDrawContext(); + ui->SetFontScale(0.4f, 0.4f); + ui->DrawTextShadow(text.c_str(), 10, 50, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII); + ui->SetFontScale(1.0f, 1.0f); + ui->Flush(); +} diff --git a/GPU/Vulkan/DebugVisVulkan.h b/GPU/Vulkan/DebugVisVulkan.h index 9c0590bab4..e220003a56 100644 --- a/GPU/Vulkan/DebugVisVulkan.h +++ b/GPU/Vulkan/DebugVisVulkan.h @@ -24,3 +24,4 @@ class UIContext; // gpu MUST be an instance of GPU_Vulkan. If not, will definitely crash. void DrawAllocatorVis(UIContext *ui, GPUInterface *gpu); +void DrawProfilerVis(UIContext *ui, GPUInterface *gpu); diff --git a/GPU/Vulkan/DepalettizeShaderVulkan.cpp b/GPU/Vulkan/DepalettizeShaderVulkan.cpp index b759882ad7..a55352b31c 100644 --- a/GPU/Vulkan/DepalettizeShaderVulkan.cpp +++ b/GPU/Vulkan/DepalettizeShaderVulkan.cpp @@ -37,8 +37,8 @@ void main() { static const VkComponentMapping VULKAN_4444_SWIZZLE = { VK_COMPONENT_SWIZZLE_A, VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B }; static const VkComponentMapping VULKAN_1555_SWIZZLE = { VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_A }; -static const VkComponentMapping VULKAN_565_SWIZZLE = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A }; -static const VkComponentMapping VULKAN_8888_SWIZZLE = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A }; +static const VkComponentMapping VULKAN_565_SWIZZLE = { VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY }; +static const VkComponentMapping VULKAN_8888_SWIZZLE = { VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY }; static VkFormat GetClutDestFormat(GEPaletteFormat format, VkComponentMapping *componentMapping) { switch (format) { diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index 6507f0f0de..0e55bbd84e 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -475,6 +475,9 @@ void GPU_Vulkan::InitDeviceObjects() { hacks |= QUEUE_HACK_MGS2_ACID; if (PSP_CoreParameter().compat.flags().SonicRivalsHack) hacks |= QUEUE_HACK_SONIC; + if (PSP_CoreParameter().compat.flags().RenderPassMerge) + hacks |= QUEUE_HACK_RENDERPASS_MERGE; + if (hacks) { rm->GetQueueRunner()->EnableHacks(hacks); } @@ -638,3 +641,8 @@ std::string GPU_Vulkan::DebugGetShaderString(std::string id, DebugShaderType typ return std::string(); } } + +std::string GPU_Vulkan::GetGpuProfileString() { + VulkanRenderManager *rm = (VulkanRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); + return rm->GetGpuProfileString(); +} diff --git a/GPU/Vulkan/GPU_Vulkan.h b/GPU/Vulkan/GPU_Vulkan.h index 42de6916c7..bb2efe5877 100644 --- a/GPU/Vulkan/GPU_Vulkan.h +++ b/GPU/Vulkan/GPU_Vulkan.h @@ -66,6 +66,8 @@ public: return textureCacheVulkan_; } + std::string GetGpuProfileString(); + protected: void FinishDeferred() override; diff --git a/Qt/QtMain.cpp b/Qt/QtMain.cpp index cdef67f0a8..3c9bdb102d 100644 --- a/Qt/QtMain.cpp +++ b/Qt/QtMain.cpp @@ -439,6 +439,8 @@ void MainUI::initializeGL() { g_Config.iGPUBackend = (int)GPUBackend::OPENGL; } + SetGLCoreContext(format().profile() == QGLFormat::CoreProfile); + #ifndef USING_GLES2 // Some core profile drivers elide certain extensions from GL_EXTENSIONS/etc. // glewExperimental allows us to force GLEW to search for the pointers anyway. @@ -569,6 +571,14 @@ int main(int argc, char *argv[]) #if defined(Q_OS_LINUX) QApplication::setAttribute(Qt::AA_X11InitThreads, true); #endif + + // Qt would otherwise default to a 3.0 compatibility profile + // except on Nvidia, where Nvidia gives us the highest supported anyway + QGLFormat format; + format.setVersion(4, 6); + format.setProfile(QGLFormat::CoreProfile); + QGLFormat::setDefaultFormat(format); + QApplication a(argc, argv); QSize res = QApplication::desktop()->screenGeometry().size(); if (res.width() < res.height()) diff --git a/UI/BackgroundAudio.cpp b/UI/BackgroundAudio.cpp index c87237c0ac..53c5b9103f 100644 --- a/UI/BackgroundAudio.cpp +++ b/UI/BackgroundAudio.cpp @@ -17,13 +17,7 @@ class AT3PlusReader { public: AT3PlusReader(const std::string &data) - : file_((const uint8_t *)&data[0], (int32_t)data.size()), - raw_data_(0), - raw_data_size_(0), - raw_offset_(0), - buffer_(0), - decoder_(0) { - + : file_((const uint8_t *)&data[0], (int32_t)data.size()) { // Normally 8k but let's be safe. buffer_ = new short[32 * 1024]; @@ -78,18 +72,64 @@ public: return; } + // If we have no loop info, we'll just loop the entire audio. + raw_offset_loop_start_ = 0; + raw_offset_loop_end_ = 0; + skip_next_samples_ = 0; + + if (file_.Descend('smpl')) { + std::vector smplData; + smplData.resize(file_.GetCurrentChunkSize()); + file_.ReadData(&smplData[0], (int)smplData.size()); + + int numLoops = *(int *)&smplData[28]; + struct AtracLoopInfo { + int cuePointID; + int type; + int startSample; + int endSample; + int fraction; + int playCount; + }; + + if (numLoops > 0 && smplData.size() >= 36 + sizeof(AtracLoopInfo) * numLoops) { + AtracLoopInfo *loops = (AtracLoopInfo *)&smplData[36]; + int samplesPerFrame = codec == PSP_CODEC_AT3PLUS ? 2048 : 1024; + + for (int i = 0; i < numLoops; ++i) { + // Only seen forward loops, so let's ignore others. + if (loops[i].type != 0) + continue; + + // We ignore loop interpolation (fraction) and play count for now. + raw_offset_loop_start_ = (loops[i].startSample / samplesPerFrame) * raw_bytes_per_frame_; + loop_start_offset_ = loops[i].startSample % samplesPerFrame; + raw_offset_loop_end_ = (loops[i].endSample / samplesPerFrame) * raw_bytes_per_frame_; + loop_end_offset_ = loops[i].endSample % samplesPerFrame; + + if (loops[i].playCount == 0) { + // This was an infinite loop, so ignore the rest. + // In practice, there's usually only one and it's usually infinite. + break; + } + } + } + + file_.Ascend(); + } + if (file_.Descend('data')) { //enter the data chunk int numBytes = file_.GetCurrentChunkSize(); numFrames = numBytes / raw_bytes_per_frame_; // numFrames raw_data_ = (uint8_t *)malloc(numBytes); raw_data_size_ = numBytes; - if (/*raw_bytes_per_frame_ == 280 && */ num_channels == 1 || num_channels == 2) { + if (num_channels == 1 || num_channels == 2) { file_.ReadData(raw_data_, numBytes); } else { ELOG("Error - bad blockalign or channels"); free(raw_data_); - raw_data_ = 0; + raw_data_ = nullptr; return; } file_.Ascend(); @@ -116,30 +156,45 @@ public: void Shutdown() { free(raw_data_); - raw_data_ = 0; + raw_data_ = nullptr; delete[] buffer_; - buffer_ = 0; + buffer_ = nullptr; delete decoder_; - decoder_ = 0; + decoder_ = nullptr; } - bool IsOK() { return raw_data_ != 0; } + bool IsOK() { return raw_data_ != nullptr; } bool Read(int *buffer, int len) { if (!raw_data_) return false; while (bgQueue.size() < (size_t)(len * 2)) { - int outBytes; + int outBytes = 0; decoder_->Decode(raw_data_ + raw_offset_, raw_bytes_per_frame_, (uint8_t *)buffer_, &outBytes); if (!outBytes) return false; - for (int i = 0; i < outBytes / 2; i++) { + if (raw_offset_loop_end_ != 0 && raw_offset_ == raw_offset_loop_end_) { + // Only take the remaining bytes, but convert to stereo s16. + outBytes = std::min(outBytes, loop_end_offset_ * 4); + } + + int start = skip_next_samples_; + skip_next_samples_ = 0; + + for (int i = start; i < outBytes / 2; i++) { bgQueue.push(buffer_[i]); } - // loop! + if (raw_offset_loop_end_ != 0 && raw_offset_ == raw_offset_loop_end_) { + // Time to loop. Account for the addition below. + raw_offset_ = raw_offset_loop_start_ - raw_bytes_per_frame_; + // This time we're counting each stereo sample. + skip_next_samples_ = loop_start_offset_ * 2; + } + + // Handle loops when there's no loop info. raw_offset_ += raw_bytes_per_frame_; if (raw_offset_ >= raw_data_size_) { raw_offset_ = 0; @@ -154,13 +209,18 @@ public: private: RIFFReader file_; - uint8_t *raw_data_; - int raw_data_size_; - int raw_offset_; + uint8_t *raw_data_ = nullptr; + int raw_data_size_ = 0; + int raw_offset_ = 0; int raw_bytes_per_frame_; + int raw_offset_loop_start_ = 0; + int raw_offset_loop_end_ = 0; + int loop_start_offset_ = 0; + int loop_end_offset_ = 0; + int skip_next_samples_ = 0; FixedSizeQueue bgQueue; - short *buffer_; - SimpleAudio *decoder_; + short *buffer_ = nullptr; + SimpleAudio *decoder_ = nullptr; }; static std::mutex bgMutex; diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index 3814b5b336..0c8d63705e 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -80,7 +80,10 @@ void DevMenu::CreatePopupContents(UI::ViewGroup *parent) { items->Add(new Choice(sy->T("Developer Tools")))->OnClick.Handle(this, &DevMenu::OnDeveloperTools); items->Add(new Choice(dev->T("Jit Compare")))->OnClick.Handle(this, &DevMenu::OnJitCompare); items->Add(new Choice(dev->T("Shader Viewer")))->OnClick.Handle(this, &DevMenu::OnShaderView); - items->Add(new CheckBox(&g_Config.bShowAllocatorDebug, dev->T("Allocator Viewer"))); + if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN) { + items->Add(new CheckBox(&g_Config.bShowAllocatorDebug, dev->T("Allocator Viewer"))); + items->Add(new CheckBox(&g_Config.bShowGpuProfile, dev->T("GPU Profile"))); + } items->Add(new Choice(dev->T("Toggle Freeze")))->OnClick.Handle(this, &DevMenu::OnFreezeFrame); items->Add(new Choice(dev->T("Dump Frame GPU Commands")))->OnClick.Handle(this, &DevMenu::OnDumpFrame); items->Add(new Choice(dev->T("Toggle Audio Debug")))->OnClick.Handle(this, &DevMenu::OnToggleAudioDebug); diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index e27344be76..38684fc587 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -1382,6 +1382,11 @@ void EmuScreen::renderUI() { if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN && g_Config.bShowAllocatorDebug) { DrawAllocatorVis(ctx, gpu); } + + if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN && g_Config.bShowGpuProfile) { + DrawProfilerVis(ctx, gpu); + } + #endif if ((g_Config.bShowFrameProfiler || g_Config.bSimpleFrameStats) && !invalid_) { diff --git a/UI/GameInfoCache.cpp b/UI/GameInfoCache.cpp index 7070139a4c..2ccdd7f6ed 100644 --- a/UI/GameInfoCache.cpp +++ b/UI/GameInfoCache.cpp @@ -92,6 +92,7 @@ bool GameInfo::Delete() { { const std::string &fileToRemove = filePath_; File::Delete(fileToRemove); + g_Config.RemoveRecent(filePath_); return true; } diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index b5f266a34c..c21f52998a 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -22,6 +22,7 @@ #include "base/colorutil.h" #include "base/timeutil.h" #include "math/curves.h" +#include "net/resolve.h" #include "gfx_es2/gpu_features.h" #include "gfx_es2/draw_buffer.h" #include "i18n/i18n.h" @@ -295,7 +296,7 @@ void GameSettingsScreen::CreateViews() { } #if !defined(MOBILE_DEVICE) - graphicsSettings->Add(new CheckBox(&g_Config.bFullScreen, gr->T("FullScreen")))->OnClick.Handle(this, &GameSettingsScreen::OnFullscreenChange); + graphicsSettings->Add(new CheckBox(&g_Config.bFullScreen, gr->T("FullScreen", "Full Screen")))->OnClick.Handle(this, &GameSettingsScreen::OnFullscreenChange); if (System_GetPropertyInt(SYSPROP_DISPLAY_COUNT) > 1) { CheckBox *fullscreenMulti = new CheckBox(&g_Config.bFullScreenMulti, gr->T("Use all displays")); fullscreenMulti->SetEnabledPtr(&g_Config.bFullScreen); @@ -309,7 +310,8 @@ void GameSettingsScreen::CreateViews() { #ifdef __ANDROID__ // Hide Immersive Mode on pre-kitkat Android if (System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 19) { - graphicsSettings->Add(new CheckBox(&g_Config.bImmersiveMode, gr->T("Immersive Mode")))->OnClick.Handle(this, &GameSettingsScreen::OnImmersiveModeChange); + // Let's reuse the Fullscreen translation string from desktop. + graphicsSettings->Add(new CheckBox(&g_Config.bImmersiveMode, gr->T("FullScreen", "Full Screen")))->OnClick.Handle(this, &GameSettingsScreen::OnImmersiveModeChange); } #endif @@ -669,13 +671,7 @@ void GameSettingsScreen::CreateViews() { networkingSettings->Add(new CheckBox(&g_Config.bEnableWlan, n->T("Enable networking", "Enable networking/wlan (beta)"))); -#if !defined(MOBILE_DEVICE) - networkingSettings->Add(new PopupTextInputChoice(&g_Config.proAdhocServer, n->T("Change proAdhocServer Address"), "", 255, screenManager())); -#elif defined(__ANDROID__) networkingSettings->Add(new ChoiceWithValueDisplay(&g_Config.proAdhocServer, n->T("Change proAdhocServer Address"), (const char *)nullptr))->OnClick.Handle(this, &GameSettingsScreen::OnChangeproAdhocServerAddress); -#else - networkingSettings->Add(new ChoiceWithValueDisplay(&g_Config.proAdhocServer, n->T("Change proAdhocServer Address"), (const char *)nullptr))->OnClick.Handle(this, &GameSettingsScreen::OnChangeproAdhocServerAddress); -#endif networkingSettings->Add(new CheckBox(&g_Config.bEnableAdhocServer, n->T("Enable built-in PRO Adhoc Server", "Enable built-in PRO Adhoc Server"))); networkingSettings->Add(new ChoiceWithValueDisplay(&g_Config.sMACAddress, n->T("Change Mac Address"), (const char *)nullptr))->OnClick.Handle(this, &GameSettingsScreen::OnChangeMacAddress); networkingSettings->Add(new PopupSliderChoice(&g_Config.iPortOffset, 0, 60000, n->T("Port offset", "Port offset(0 = PSP compatibility)"), 100, screenManager())); @@ -1263,24 +1259,12 @@ UI::EventReturn GameSettingsScreen::OnChangeNickname(UI::EventParams &e) { } UI::EventReturn GameSettingsScreen::OnChangeproAdhocServerAddress(UI::EventParams &e) { -#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) - if (!g_Config.bFullScreen) { - const size_t name_len = 256; + I18NCategory *sy = GetI18NCategory("System"); - char name[name_len]; - memset(name, 0, sizeof(name)); - - if (System_InputBoxGetString("Enter an IP address", g_Config.proAdhocServer.c_str(), name, name_len)) { - std::string stripped = StripSpaces(name); - g_Config.proAdhocServer = stripped; - } - } - else - screenManager()->push(new ProAdhocServerScreen); -#elif defined(__ANDROID__) +#if defined(__ANDROID__) System_SendMessage("inputbox", ("IP:" + g_Config.proAdhocServer).c_str()); #else - screenManager()->push(new ProAdhocServerScreen); + screenManager()->push(new HostnameSelectScreen(&g_Config.proAdhocServer, sy->T("proAdhocServer Address:"))); #endif return UI::EVENT_DONE; @@ -1611,130 +1595,156 @@ void DeveloperToolsScreen::update() { canAllowDebugger_ = !WebServerStopping(WebServerFlags::DEBUGGER); } -void ProAdhocServerScreen::CreateViews() { +void HostnameSelectScreen::CreatePopupContents(UI::ViewGroup *parent) { using namespace UI; I18NCategory *sy = GetI18NCategory("System"); I18NCategory *di = GetI18NCategory("Dialog"); + I18NCategory *n = GetI18NCategory("Networking"); - tempProAdhocServer = g_Config.proAdhocServer; - root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT)); - LinearLayout *leftColumn = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT)); + LinearLayout *valueRow = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT, Margins(0, 0, 0, 10))); - leftColumn->Add(new ItemHeader(sy->T("proAdhocServer Address:"))); - addrView_ = new TextView(tempProAdhocServer, ALIGN_LEFT, false); - leftColumn->Add(addrView_); - LinearLayout *rightColumn = new LinearLayout(ORIENT_HORIZONTAL, new AnchorLayoutParams(0, 120, 10, NONE, NONE,10)); - rightColumn->Add(new Button("0"))->OnClick.Handle(this, &ProAdhocServerScreen::On0Click); - rightColumn->Add(new Button("1"))->OnClick.Handle(this, &ProAdhocServerScreen::On1Click); - rightColumn->Add(new Button("2"))->OnClick.Handle(this, &ProAdhocServerScreen::On2Click); - rightColumn->Add(new Button("3"))->OnClick.Handle(this, &ProAdhocServerScreen::On3Click); - rightColumn->Add(new Button("4"))->OnClick.Handle(this, &ProAdhocServerScreen::On4Click); - rightColumn->Add(new Button("5"))->OnClick.Handle(this, &ProAdhocServerScreen::On5Click); - rightColumn->Add(new Button("6"))->OnClick.Handle(this, &ProAdhocServerScreen::On6Click); - rightColumn->Add(new Button("7"))->OnClick.Handle(this, &ProAdhocServerScreen::On7Click); - rightColumn->Add(new Button("8"))->OnClick.Handle(this, &ProAdhocServerScreen::On8Click); - rightColumn->Add(new Button("9"))->OnClick.Handle(this, &ProAdhocServerScreen::On9Click); - rightColumn->Add(new Button("."))->OnClick.Handle(this, &ProAdhocServerScreen::OnPointClick); - rightColumn->Add(new Button(di->T("Delete")))->OnClick.Handle(this, &ProAdhocServerScreen::OnDeleteClick); - rightColumn->Add(new Button(di->T("Delete all")))->OnClick.Handle(this, &ProAdhocServerScreen::OnDeleteAllClick); - rightColumn->Add(new Button(di->T("OK")))->OnClick.Handle(this, &ProAdhocServerScreen::OnOKClick); - rightColumn->Add(new Button(di->T("Cancel")))->OnClick.Handle(this, &ProAdhocServerScreen::OnCancelClick); - root_->Add(leftColumn); - root_->Add(rightColumn); + addrView_ = new TextEdit(*value_, ""); + addrView_->SetTextAlign(FLAG_DYNAMIC_ASCII); + valueRow->Add(addrView_); + parent->Add(valueRow); + + LinearLayout *buttonsRow1 = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); + LinearLayout *buttonsRow2 = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); + parent->Add(buttonsRow1); + parent->Add(buttonsRow2); + + buttonsRow1->Add(new Spacer(new LinearLayoutParams(1.0, G_LEFT))); + for (char c = '0'; c <= '9'; ++c) { + char label[] = { c, '\0' }; + auto button = buttonsRow1->Add(new Button(label)); + button->OnClick.Handle(this, &HostnameSelectScreen::OnNumberClick); + button->SetTag(label); + } + buttonsRow1->Add(new Button("."))->OnClick.Handle(this, &HostnameSelectScreen::OnPointClick); + buttonsRow1->Add(new Spacer(new LinearLayoutParams(1.0, G_RIGHT))); + + buttonsRow2->Add(new Spacer(new LinearLayoutParams(1.0, G_LEFT))); + buttonsRow2->Add(new Button(di->T("Delete")))->OnClick.Handle(this, &HostnameSelectScreen::OnDeleteClick); + buttonsRow2->Add(new Button(di->T("Delete all")))->OnClick.Handle(this, &HostnameSelectScreen::OnDeleteAllClick); + buttonsRow2->Add(new Spacer(new LinearLayoutParams(1.0, G_RIGHT))); + + errorView_ = parent->Add(new TextView(n->T("Invalid IP or hostname"), ALIGN_HCENTER, false, new LinearLayoutParams(Margins(0, 10, 0, 0)))); + errorView_->SetTextColor(0xFF3030FF); + errorView_->SetVisibility(V_GONE); + + progressView_ = parent->Add(new TextView(n->T("Validating address..."), ALIGN_HCENTER, false, new LinearLayoutParams(Margins(0, 10, 0, 0)))); + progressView_->SetVisibility(V_GONE); } -UI::EventReturn ProAdhocServerScreen::On0Click(UI::EventParams &e) { - if (tempProAdhocServer.length() > 0) - tempProAdhocServer.append("0"); - addrView_->SetText(tempProAdhocServer); +void HostnameSelectScreen::SendEditKey(int keyCode, int flags) { + auto oldView = UI::GetFocusedView(); + UI::SetFocusedView(addrView_); + KeyInput fakeKey{ DEVICE_ID_KEYBOARD, keyCode, KEY_DOWN | flags }; + addrView_->Key(fakeKey); + UI::SetFocusedView(oldView); +} + +UI::EventReturn HostnameSelectScreen::OnNumberClick(UI::EventParams &e) { + std::string text = e.v ? e.v->Tag() : ""; + if (text.length() == 1 && text[0] >= '0' && text[0] <= '9') { + SendEditKey(text[0], KEY_CHAR); + } return UI::EVENT_DONE; } -UI::EventReturn ProAdhocServerScreen::On1Click(UI::EventParams &e) { - tempProAdhocServer.append("1"); - addrView_->SetText(tempProAdhocServer); +UI::EventReturn HostnameSelectScreen::OnPointClick(UI::EventParams &e) { + SendEditKey('.', KEY_CHAR); return UI::EVENT_DONE; } -UI::EventReturn ProAdhocServerScreen::On2Click(UI::EventParams &e) { - tempProAdhocServer.append("2"); - addrView_->SetText(tempProAdhocServer); +UI::EventReturn HostnameSelectScreen::OnDeleteClick(UI::EventParams &e) { + SendEditKey(NKCODE_DEL); return UI::EVENT_DONE; } -UI::EventReturn ProAdhocServerScreen::On3Click(UI::EventParams &e) { - tempProAdhocServer.append("3"); - addrView_->SetText(tempProAdhocServer); +UI::EventReturn HostnameSelectScreen::OnDeleteAllClick(UI::EventParams &e) { + addrView_->SetText(""); return UI::EVENT_DONE; } -UI::EventReturn ProAdhocServerScreen::On4Click(UI::EventParams &e) { - tempProAdhocServer.append("4"); - addrView_->SetText(tempProAdhocServer); - return UI::EVENT_DONE; +void HostnameSelectScreen::ResolverThread() { + std::unique_lock guard(resolverLock_); + + while (resolverState_ != ResolverState::QUIT) { + resolverCond_.wait(guard); + + if (resolverState_ == ResolverState::QUEUED) { + resolverState_ = ResolverState::PROGRESS; + + addrinfo *resolved = nullptr; + std::string err; + toResolveResult_ = net::DNSResolve(toResolve_, "80", &resolved, err); + if (resolved) + net::DNSResolveFree(resolved); + + resolverState_ = ResolverState::READY; + } + } } -UI::EventReturn ProAdhocServerScreen::On5Click(UI::EventParams &e) { - tempProAdhocServer.append("5"); - addrView_->SetText(tempProAdhocServer); - return UI::EVENT_DONE; +bool HostnameSelectScreen::CanComplete(DialogResult result) { + if (result != DR_OK) + return true; + + std::string value = addrView_->GetText(); + if (lastResolved_ == value) { + return true; + } + + // Currently running. + if (resolverState_ == ResolverState::PROGRESS) + return false; + + std::lock_guard guard(resolverLock_); + switch (resolverState_) { + case ResolverState::PROGRESS: + case ResolverState::QUIT: + return false; + + case ResolverState::QUEUED: + case ResolverState::WAITING: + break; + + case ResolverState::READY: + if (toResolve_ == value) { + // Reset the state, nothing there now. + resolverState_ = ResolverState::WAITING; + toResolve_.clear(); + lastResolved_ = value; + lastResolvedResult_ = toResolveResult_; + + if (lastResolvedResult_) { + errorView_->SetVisibility(UI::V_GONE); + } else { + errorView_->SetVisibility(UI::V_VISIBLE); + } + progressView_->SetVisibility(UI::V_GONE); + + return true; + } + + // Throw away that last result, it was for a different value. + break; + } + + resolverState_ = ResolverState::QUEUED; + toResolve_ = value; + resolverCond_.notify_one(); + + progressView_->SetVisibility(UI::V_VISIBLE); + errorView_->SetVisibility(UI::V_GONE); + + return false; } -UI::EventReturn ProAdhocServerScreen::On6Click(UI::EventParams &e) { - tempProAdhocServer.append("6"); - addrView_->SetText(tempProAdhocServer); - return UI::EVENT_DONE; -} - -UI::EventReturn ProAdhocServerScreen::On7Click(UI::EventParams &e) { - tempProAdhocServer.append("7"); - addrView_->SetText(tempProAdhocServer); - return UI::EVENT_DONE; -} - -UI::EventReturn ProAdhocServerScreen::On8Click(UI::EventParams &e) { - tempProAdhocServer.append("8"); - addrView_->SetText(tempProAdhocServer); - return UI::EVENT_DONE; -} - -UI::EventReturn ProAdhocServerScreen::On9Click(UI::EventParams &e) { - tempProAdhocServer.append("9"); - addrView_->SetText(tempProAdhocServer); - return UI::EVENT_DONE; -} - - -UI::EventReturn ProAdhocServerScreen::OnPointClick(UI::EventParams &e) { - if (tempProAdhocServer.length() > 0 && tempProAdhocServer.at(tempProAdhocServer.length() - 1) != '.') - tempProAdhocServer.append("."); - addrView_->SetText(tempProAdhocServer); - return UI::EVENT_DONE; -} - -UI::EventReturn ProAdhocServerScreen::OnDeleteClick(UI::EventParams &e) { - if (tempProAdhocServer.length() > 0) - tempProAdhocServer.erase(tempProAdhocServer.length() -1, 1); - addrView_->SetText(tempProAdhocServer); - return UI::EVENT_DONE; -} - -UI::EventReturn ProAdhocServerScreen::OnDeleteAllClick(UI::EventParams &e) { - tempProAdhocServer = ""; - addrView_->SetText(tempProAdhocServer); - return UI::EVENT_DONE; -} - -UI::EventReturn ProAdhocServerScreen::OnOKClick(UI::EventParams &e) { - g_Config.proAdhocServer = StripSpaces(tempProAdhocServer); - UIScreen::OnBack(e); - return UI::EVENT_DONE; -} - -UI::EventReturn ProAdhocServerScreen::OnCancelClick(UI::EventParams &e) { - tempProAdhocServer = g_Config.proAdhocServer; - UIScreen::OnBack(e); - return UI::EVENT_DONE; +void HostnameSelectScreen::OnCompleted(DialogResult result) { + if (result == DR_OK) + *value_ = addrView_->GetText(); } SettingInfoMessage::SettingInfoMessage(int align, UI::AnchorLayoutParams *lp) @@ -1743,7 +1753,6 @@ SettingInfoMessage::SettingInfoMessage(int align, UI::AnchorLayoutParams *lp) SetSpacing(0.0f); Add(new UI::Spacer(10.0f)); text_ = Add(new UI::TextView("", align, false, new LinearLayoutParams(1.0, Margins(0, 10)))); - text_->SetTag("TEST?"); Add(new UI::Spacer(10.0f)); } diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h index b78bc0ad64..03417e443f 100644 --- a/UI/GameSettingsScreen.h +++ b/UI/GameSettingsScreen.h @@ -18,6 +18,9 @@ #pragma once #include "ppsspp_config.h" +#include +#include +#include #include "ui/ui_screen.h" #include "UI/MiscScreens.h" @@ -190,28 +193,53 @@ private: }; class ProAdhocServerScreen : public UIDialogScreenWithBackground { +class HostnameSelectScreen : public PopupScreen { public: - ProAdhocServerScreen() {} + HostnameSelectScreen(std::string *value, const std::string &title) + : PopupScreen(title, "OK", "Cancel"), value_(value) { + resolver_ = std::thread([](HostnameSelectScreen *thiz) { + thiz->ResolverThread(); + }, this); + } + ~HostnameSelectScreen() { + resolverState_ = ResolverState::QUIT; + resolverCond_.notify_one(); + resolver_.join(); + } + + void CreatePopupContents(UI::ViewGroup *parent) override; protected: - void CreateViews() override; + void OnCompleted(DialogResult result) override; + bool CanComplete(DialogResult result) override; -private: - std::string tempProAdhocServer; - UI::TextView *addrView_; - UI::EventReturn On0Click(UI::EventParams &e); - UI::EventReturn On1Click(UI::EventParams &e); - UI::EventReturn On2Click(UI::EventParams &e); - UI::EventReturn On3Click(UI::EventParams &e); - UI::EventReturn On4Click(UI::EventParams &e); - UI::EventReturn On5Click(UI::EventParams &e); - UI::EventReturn On6Click(UI::EventParams &e); - UI::EventReturn On7Click(UI::EventParams &e); - UI::EventReturn On8Click(UI::EventParams &e); - UI::EventReturn On9Click(UI::EventParams &e); +private: + void ResolverThread(); + void SendEditKey(int keyCode, int flags = 0); + UI::EventReturn OnNumberClick(UI::EventParams &e); UI::EventReturn OnPointClick(UI::EventParams &e); UI::EventReturn OnDeleteClick(UI::EventParams &e); UI::EventReturn OnDeleteAllClick(UI::EventParams &e); - UI::EventReturn OnOKClick(UI::EventParams &e); - UI::EventReturn OnCancelClick(UI::EventParams &e); + + enum class ResolverState { + WAITING, + QUEUED, + PROGRESS, + READY, + QUIT, + }; + + std::string *value_; + UI::TextEdit *addrView_ = nullptr; + UI::TextView *errorView_ = nullptr; + UI::TextView *progressView_ = nullptr; + + std::thread resolver_; + ResolverState resolverState_ = ResolverState::WAITING; + std::mutex resolverLock_; + std::condition_variable resolverCond_; + std::string toResolve_ = ""; + bool toResolveResult_ = false; + std::string lastResolved_ = ""; + bool lastResolvedResult_ = false; }; diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj index 5cac0a005d..a26bcbac2c 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj +++ b/UWP/CoreUWP/CoreUWP.vcxproj @@ -501,6 +501,7 @@ + @@ -712,6 +713,7 @@ + diff --git a/UWP/CoreUWP/CoreUWP.vcxproj.filters b/UWP/CoreUWP/CoreUWP.vcxproj.filters index 9a887c8a3b..fe872876ad 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj.filters +++ b/UWP/CoreUWP/CoreUWP.vcxproj.filters @@ -631,6 +631,9 @@ HLE + + HLE + Debugger @@ -1243,6 +1246,9 @@ HLE + + HLE + Debugger @@ -1292,4 +1298,4 @@ MIPS\ARM64 - \ No newline at end of file + diff --git a/Windows/Debugger/Debugger_MemoryDlg.cpp b/Windows/Debugger/Debugger_MemoryDlg.cpp index a60f3ebfec..6943a8b1a3 100644 --- a/Windows/Debugger/Debugger_MemoryDlg.cpp +++ b/Windows/Debugger/Debugger_MemoryDlg.cpp @@ -4,6 +4,7 @@ #include #include "..\resource.h" +#include "base/display.h" #include "util/text/utf8.h" #include "Core/Debugger/SymbolMap.h" @@ -218,6 +219,8 @@ void CMemoryDlg::Goto(u32 addr) void CMemoryDlg::Size() { + const float fontScale = 1.0f / g_dpi_scale_real_y; + RECT rc; GetClientRect(m_hDlg,&rc); int dw=rc.right-rc.left; @@ -225,8 +228,8 @@ void CMemoryDlg::Size() HWND memView = GetDlgItem(m_hDlg, IDC_MEMVIEW); HWND symList = GetDlgItem(m_hDlg, IDC_SYMBOLS); int wf = slRect.right-slRect.left; - int w = dw-3-wf; - int top = 48; + int w = dw - 3 * fontScale - wf; + int top = 48 * fontScale; MoveWindow(symList,0,top,wf,dh-top,TRUE); MoveWindow(memView,wf+4,top,w,dh-top,TRUE); } diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 07237cd121..246814efd8 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -102,14 +102,6 @@ - - - - - - - - diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 8d6e5f4c44..7014ee72ca 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -404,6 +404,8 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init systemName = GetJavaString(env, jmodel); langRegion = GetJavaString(env, jlangRegion); + ILOG("NativeApp.init(): device name: '%s'", systemName.c_str()); + std::string externalDir = GetJavaString(env, jexternalDir); std::string user_data_path = GetJavaString(env, jdataDir); if (user_data_path.size() > 0) diff --git a/android/jni/native-audio-so.cpp b/android/jni/native-audio-so.cpp index 607142f022..27729ab2cd 100644 --- a/android/jni/native-audio-so.cpp +++ b/android/jni/native-audio-so.cpp @@ -157,7 +157,6 @@ OpenSLContext::~OpenSLContext() { bqPlayerObject = nullptr; bqPlayerPlay = nullptr; bqPlayerBufferQueue = nullptr; - bqPlayerMuteSolo = nullptr; bqPlayerVolume = nullptr; } diff --git a/android/jni/native-audio-so.h b/android/jni/native-audio-so.h index a69f879855..286a3338eb 100644 --- a/android/jni/native-audio-so.h +++ b/android/jni/native-audio-so.h @@ -35,7 +35,6 @@ private: SLObjectItf bqPlayerObject = nullptr; SLPlayItf bqPlayerPlay = nullptr; SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue = nullptr; - SLMuteSoloItf bqPlayerMuteSolo = nullptr; SLVolumeItf bqPlayerVolume = nullptr; // Double buffering. diff --git a/android/src/org/ppsspp/ppsspp/CameraHelper.java b/android/src/org/ppsspp/ppsspp/CameraHelper.java index ca4bd8894a..9979b81660 100644 --- a/android/src/org/ppsspp/ppsspp/CameraHelper.java +++ b/android/src/org/ppsspp/ppsspp/CameraHelper.java @@ -7,7 +7,12 @@ import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.graphics.YuvImage; import android.hardware.Camera; +import android.os.SystemClock; import android.util.Log; +import android.view.Display; +import android.view.Surface; +import android.view.WindowManager; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.List; @@ -15,67 +20,140 @@ import java.util.List; @TargetApi(23) @SuppressWarnings("deprecation") class CameraHelper { - private static final String TAG = "CameraHelper"; + private static final String TAG = CameraHelper.class.getSimpleName(); + private Display mDisplay; private Camera mCamera = null; - private int previewWidth = 0; - private int previewHeight = 0; - private long lastTime = 0; + private boolean mIsCameraRunning = false; + private int mCameraOrientation = 0; + private Camera.Size mPreviewSize = null; + private long mLastFrameTime = 0; private SurfaceTexture mSurfaceTexture; + private int getCameraRotation() { + int displayRotation = mDisplay.getRotation(); + int displayDegrees = 0; + switch (displayRotation) { + case Surface.ROTATION_0: displayDegrees = 0; break; + case Surface.ROTATION_90: displayDegrees = 90; break; + case Surface.ROTATION_180: displayDegrees = 180; break; + case Surface.ROTATION_270: displayDegrees = 270; break; + } + return (mCameraOrientation - displayDegrees + 360) % 360; + } + + static byte[] rotateNV21(final byte[] input, final int inWidth, final int inHeight, + final int outWidth, final int outHeight, final int rotation) { + + final int inFrameSize = inWidth * inHeight; + final int outFrameSize = outWidth * outHeight; + final byte[] output = new byte[outFrameSize + outFrameSize/2]; + + if (rotation == 0 || rotation == 180) { + final int crop_left = (inWidth - outWidth) / 2; + final int crop_top = (inHeight - outHeight) / 2; + for (int j = 0; j < outHeight; j++) { + final int yInCol = (crop_top + j) * inWidth + crop_left; + final int uvInCol = inFrameSize + ((crop_top + j) >> 1) * inWidth + crop_left; + final int jOut = rotation == 180 ? outHeight - j - 1 : j; + final int yOutCol = jOut * outWidth; + final int uvOutCol = outFrameSize + (jOut >> 1) * outWidth; + for (int i = 0; i < outWidth; i++) { + final int yIn = yInCol + i; + final int uIn = uvInCol + (i & ~1); + final int vIn = uIn + 1; + final int iOut = rotation == 180 ? outWidth - i - 1 : i; + final int yOut = yOutCol + iOut; + final int uOut = uvOutCol + (iOut & ~1); + final int vOut = uOut + 1; + output[yOut] = input[yIn]; + output[uOut] = input[uIn]; + output[vOut] = input[vIn]; + } + } + } else if (rotation == 90 || rotation == 270) { + int crop_left = (inWidth - outHeight) / 2; + int crop_top = (inHeight - outWidth) / 2; + for (int j = 0; j < outWidth; j++) { + final int yInCol = (crop_top + j) * inWidth + crop_left; + final int uvInCol = inFrameSize + ((crop_top + j) >> 1) * inWidth + crop_left; + final int iOut = rotation == 90 ? outWidth - j - 1 : j; + for (int i = 0; i < outHeight; i++) { + final int yIn = yInCol + i; + final int uIn = uvInCol + (i & ~1); + final int vIn = uIn + 1; + final int jOut = rotation == 270 ? outHeight - i - 1 : i; + final int yOut = jOut * outWidth + iOut; + final int uOut = outFrameSize + (jOut >> 1) * outWidth + (iOut & ~1); + final int vOut = uOut + 1; + output[yOut] = input[yIn]; + output[uOut] = input[uIn]; + output[vOut] = input[vIn]; + } + } + } + return output; + } + private Camera.PreviewCallback mPreviewCallback = new Camera.PreviewCallback() { @Override public void onPreviewFrame(byte[] previewData, Camera camera) { - // throttle at 100 ms - long currentTime = System.currentTimeMillis(); - if (currentTime - lastTime < 100) { + // throttle at 66 ms + long currentTime = SystemClock.elapsedRealtime(); + if (currentTime - mLastFrameTime < 66) { return; } - lastTime = currentTime; - - YuvImage yuvImage = new YuvImage(previewData, ImageFormat.NV21, previewWidth, previewHeight, null); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); + mLastFrameTime = currentTime; // the expected values arrives in sceUsbCamSetupVideo int targetW = 480; int targetH = 272; - // 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 - ); - yuvImage.compressToJpeg(rect, 80, baos); + int cameraRotation = getCameraRotation(); + byte[] newPreviewData = rotateNV21(previewData, mPreviewSize.width, mPreviewSize.height, + targetW, targetH, cameraRotation); + YuvImage yuvImage = new YuvImage(newPreviewData, ImageFormat.NV21, targetW, targetH, null); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + // convert to Jpeg + Rect crop = new Rect(0, 0, targetW, targetH); + yuvImage.compressToJpeg(crop, 80, baos); NativeApp.pushCameraImage(baos.toByteArray()); + try { + baos.close(); + } catch (IOException e) { + e.printStackTrace(); + } } }; @SuppressWarnings("unused") - CameraHelper(Context context) { + CameraHelper(final Context context) { + mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); mSurfaceTexture = new SurfaceTexture(10); } void startCamera() { Log.d(TAG, "startCamera"); try { + Camera.CameraInfo info = new android.hardware.Camera.CameraInfo(); + Camera.getCameraInfo(0, info); + mCameraOrientation = info.orientation; + mCamera = Camera.open(); Camera.Parameters param = mCamera.getParameters(); // Set preview size List previewSizes = param.getSupportedPreviewSizes(); - previewWidth = previewSizes.get(0).width; - previewHeight = previewSizes.get(0).height; + mPreviewSize = previewSizes.get(0); for (int i = 0; i < previewSizes.size(); i++) { Log.d(TAG, "getSupportedPreviewSizes[" + i + "]: " + previewSizes.get(i).height + " " + previewSizes.get(i).width); if (previewSizes.get(i).width <= 640 && previewSizes.get(i).height <= 480) { - previewWidth = previewSizes.get(i).width; - previewHeight = previewSizes.get(i).height; + mPreviewSize = previewSizes.get(i); break; } } - Log.d(TAG, "setPreviewSize(" + previewWidth + ", " + previewHeight + ")"); - param.setPreviewSize(previewWidth, previewHeight); + Log.d(TAG, "setPreviewSize(" + mPreviewSize.width + ", " + mPreviewSize.height + ")"); + param.setPreviewSize(mPreviewSize.width, mPreviewSize.height); // Set preview FPS int[] fps; @@ -94,18 +172,31 @@ class CameraHelper { mCamera.setPreviewTexture(mSurfaceTexture); mCamera.setPreviewCallback(mPreviewCallback); mCamera.startPreview(); + mIsCameraRunning = true; } catch (IOException e) { Log.e(TAG, "Cannot start camera: " + e.toString()); } } - void stopCamera() { - Log.d(TAG, "stopCamera"); - if (mCamera != null) { + void pause() { + if (mIsCameraRunning && mCamera != null) { + Log.d(TAG, "pause"); mCamera.setPreviewCallback(null); mCamera.stopPreview(); mCamera.release(); mCamera = null; } } + + void resume() { + if (mIsCameraRunning) { + Log.d(TAG, "resume"); + startCamera(); + } + } + + void stopCamera() { + pause(); + mIsCameraRunning = false; + } } diff --git a/android/src/org/ppsspp/ppsspp/NativeActivity.java b/android/src/org/ppsspp/ppsspp/NativeActivity.java index 3bec1191cf..c32d0321e3 100644 --- a/android/src/org/ppsspp/ppsspp/NativeActivity.java +++ b/android/src/org/ppsspp/ppsspp/NativeActivity.java @@ -106,6 +106,8 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C private InputDeviceState inputPlayerC; private String inputPlayerADesc; + private PowerSaveModeReceiver mPowerSaveModeReceiver = null; + private static LocationHelper mLocationHelper; private static CameraHelper mCameraHelper; @@ -311,7 +313,6 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C javaGL = "true".equalsIgnoreCase(NativeApp.queryConfig("androidJavaGL")); sendInitialGrants(); - PowerSaveModeReceiver.initAndSend(this); // OK, config should be initialized, we can query for screen rotation. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { @@ -489,6 +490,10 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C initialized = true; } + if (mPowerSaveModeReceiver == null) { + mPowerSaveModeReceiver = new PowerSaveModeReceiver(this); + } + // OK, config should be initialized, we can query for screen rotation. updateScreenRotation("onCreate"); updateSustainedPerformanceMode(); @@ -703,6 +708,10 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C mSurfaceView.onDestroy(); mSurfaceView = null; } + if (mPowerSaveModeReceiver != null) { + mPowerSaveModeReceiver.destroy(this); + mPowerSaveModeReceiver = null; + } // TODO: Can we ensure that the GL thread has stopped rendering here? // I've seen crashes that seem to indicate that sometimes it hasn't... NativeApp.audioShutdown(); @@ -731,6 +740,7 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C Log.e(TAG, "mGLSurfaceView really shouldn't be null in onPause"); } } + mCameraHelper.pause(); Log.i(TAG, "onPause completed"); } @@ -770,6 +780,7 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C mSurfaceView.onResume(); } } + mCameraHelper.resume(); gainAudioFocus(this.audioManager, this.audioFocusChangeListener); NativeApp.resume(); @@ -1326,6 +1337,10 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C // Only keep the screen bright ingame. window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } + } else if (command.equals("event")) { + if (params.equals("exitgame")) { + mCameraHelper.stopCamera(); + } } return false; } diff --git a/android/src/org/ppsspp/ppsspp/PowerSaveModeReceiver.java b/android/src/org/ppsspp/ppsspp/PowerSaveModeReceiver.java index 69b396dfa8..38649b873c 100644 --- a/android/src/org/ppsspp/ppsspp/PowerSaveModeReceiver.java +++ b/android/src/org/ppsspp/ppsspp/PowerSaveModeReceiver.java @@ -5,33 +5,42 @@ import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.database.ContentObserver; import android.net.Uri; import android.os.Build; import android.os.PowerManager; import android.provider.Settings; +import android.text.TextUtils; import android.util.Log; public class PowerSaveModeReceiver extends BroadcastReceiver { + private static final String TAG = PowerSaveModeReceiver.class.getSimpleName(); private static boolean isPowerSaving = false; private static boolean isBatteryLow = false; - private static final String TAG = "PowerSaveModeReceiver"; - @Override public void onReceive(final Context context, final Intent intent) { final String action = intent.getAction(); - if (action.equals(Intent.ACTION_BATTERY_LOW)) { + if (Intent.ACTION_BATTERY_LOW.equals(action)) { isBatteryLow = true; - } else if (action.equals(Intent.ACTION_BATTERY_OKAY)) { + } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) { isBatteryLow = false; + } else if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(action)) { + // sendPowerSaving() } sendPowerSaving(context); } - public static void initAndSend(final Activity activity) { - sendPowerSaving(activity); + public PowerSaveModeReceiver(final Activity activity) { + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_BATTERY_LOW); + filter.addAction(Intent.ACTION_BATTERY_OKAY); + if (Build.VERSION.SDK_INT >= 21) { + filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); + } + activity.registerReceiver(this, filter); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { activity.getContentResolver().registerContentObserver(Settings.System.CONTENT_URI, true, new ContentObserver(null) { @@ -46,11 +55,16 @@ public class PowerSaveModeReceiver extends BroadcastReceiver { } key = key.substring(key.lastIndexOf("/") + 1, key.length()); if (key.equals("user_powersaver_enable") || key.equals("psm_switch") || key.equals("powersaving_switch")) { - PowerSaveModeReceiver.sendPowerSaving(activity); + sendPowerSaving(activity); } } }); } + sendPowerSaving(activity); + } + + public void destroy(final Context context) { + context.unregisterReceiver(this); } @TargetApi(21) @@ -67,7 +81,7 @@ public class PowerSaveModeReceiver extends BroadcastReceiver { } // Samsung (Touchwiz) String s5Value = Settings.System.getString(context.getContentResolver(), "powersaving_switch"); - boolean hasS5Value = s5Value != null && !s5Value.equals(""); + boolean hasS5Value = !TextUtils.isEmpty(s5Value); // On newer devices, psm_switch is always set, and powersaving_switch is used instead. if ((!hasS5Value && getBooleanSetting(context, "psm_switch")) || getBooleanSetting(context, "powersaving_switch")) { return true; @@ -80,7 +94,7 @@ public class PowerSaveModeReceiver extends BroadcastReceiver { return value != null && value.equals("1"); } - private static void sendPowerSaving(final Context context) { + protected void sendPowerSaving(final Context context) { if (Build.VERSION.SDK_INT >= 21) { isPowerSaving = getNativePowerSaving(context); } else { diff --git a/assets/compat.ini b/assets/compat.ini index 19205e8a07..9c96499819 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -760,3 +760,39 @@ NPUH10047 = true ULAS42214 = true ULJS19054 = true NPJH50184 = true + +[RenderPassMerge] +UCJS10114 = true +UCKS45084 = true +# GOW : Ghost of Sparta +UCUS98737 = true +UCAS40323 = true +NPHG00092 = true +NPEG00044 = true +NPEG00045 = true +NPJG00120 = true +NPUG80508 = true +UCJS10114 = true +UCES01401 = true +UCES01473 = true +# GOW : Ghost of Sparta Demo +NPEG90035 = true +NPUG70125 = true +NPJG90095 = true +# GOW : Chains Of Olympus +UCAS40198 = true +UCUS98653 = true +UCES00842 = true +ULJM05438 = true +ULJM05348 = true +UCKS45084 = true +NPUG80325 = true +NPEG00023 = true +NPHG00027 = true +NPHG00028 = true +NPJH50170 = true +UCET00844 = true +# GOW: Chains of Olympus Demo +UCUS98705 = true +UCED00971 = true +UCUS98713 = true diff --git a/assets/lang b/assets/lang index b2d7f53fa0..7f098771e0 160000 --- a/assets/lang +++ b/assets/lang @@ -1 +1 @@ -Subproject commit b2d7f53fa056586504590d271ab5f3198bfc2acd +Subproject commit 7f098771e00ce8fca6d116c973c711275deec68c diff --git a/build.gradle b/build.gradle index dcf3372b38..6fe5d2708d 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.4.2' + classpath 'com.android.tools.build:gradle:3.5.0' } } diff --git a/ext/native/gfx_es2/gl3stub.c b/ext/native/gfx_es2/gl3stub.c index 2a846a32d9..265004a9c3 100644 --- a/ext/native/gfx_es2/gl3stub.c +++ b/ext/native/gfx_es2/gl3stub.c @@ -14,15 +14,14 @@ * limitations under the License. */ +#include "ppsspp_config.h" #include "../gfx/gl_common.h" #if defined(USING_GLES2) -#if !defined(IOS) +#if !PPSSPP_PLATFORM(IOS) #include "EGL/egl.h" -#endif GLboolean gl3stubInit() { -#if !defined(IOS) #define FIND_PROC(s) s = (void*)eglGetProcAddress(#s) FIND_PROC(glReadBuffer); FIND_PROC(glDrawRangeElements); @@ -145,7 +144,6 @@ GLboolean gl3stubInit() { #undef FIND_PROC -#endif // IOS if (!glReadBuffer || !glDrawRangeElements || !glTexImage3D || @@ -257,8 +255,6 @@ GLboolean gl3stubInit() { return GL_TRUE; } -#if !defined(IOS) - /* Function pointer definitions */ GL_APICALL void (* GL_APIENTRY glReadBuffer) (GLenum mode); GL_APICALL void (* GL_APIENTRY glDrawRangeElements) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid* indices); @@ -379,6 +375,12 @@ GL_APICALL void (* GL_APIENTRY glBufferStorageEXT) (GLenum target, GLs /* OES_copy_image, etc. */ GL_APICALL void (* GL_APIENTRY glCopyImageSubDataOES) (GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); +#else + +GLboolean gl3stubInit() { + return GL_TRUE; +} + #endif // IOS #endif // GLES2 diff --git a/ext/native/gfx_es2/gl3stub.h b/ext/native/gfx_es2/gl3stub.h index 843b1fb667..26b571b982 100644 --- a/ext/native/gfx_es2/gl3stub.h +++ b/ext/native/gfx_es2/gl3stub.h @@ -11,6 +11,8 @@ * - Added gl3stubInit() declaration */ +#include "ppsspp_config.h" + #if defined(USING_GLES2) #ifdef __cplusplus @@ -45,7 +47,7 @@ extern "C" { * otherwise. */ GLboolean gl3stubInit(); -#if !defined(IOS) +#if !PPSSPP_PLATFORM(IOS) /*------------------------------------------------------------------------- * Data type definitions diff --git a/ext/native/thin3d/GLQueueRunner.cpp b/ext/native/thin3d/GLQueueRunner.cpp index 5fd98a49f6..90540eb90a 100644 --- a/ext/native/thin3d/GLQueueRunner.cpp +++ b/ext/native/thin3d/GLQueueRunner.cpp @@ -107,7 +107,6 @@ void GLQueueRunner::RunInitSteps(const std::vector &steps, bool ski } case GLRInitStepType::TEXTURE_IMAGE: { - GLRTexture *tex = step.texture_image.texture; if (step.texture_image.allocType == GLRAllocType::ALIGNED) { FreeAlignedMemory(step.texture_image.data); } else if (step.texture_image.allocType == GLRAllocType::NEW) { @@ -1317,9 +1316,8 @@ void GLQueueRunner::PerformReadback(const GLRStep &pass) { } void GLQueueRunner::PerformReadbackImage(const GLRStep &pass) { - GLRTexture *tex = pass.readback_image.texture; - #ifndef USING_GLES2 + GLRTexture *tex = pass.readback_image.texture; GLRect2D rect = pass.readback_image.srcRect; if (gl_extensions.VersionGEThan(4, 5)) { diff --git a/ext/native/thin3d/GLRenderManager.cpp b/ext/native/thin3d/GLRenderManager.cpp index 8affe78c8f..f3fe9ce65a 100644 --- a/ext/native/thin3d/GLRenderManager.cpp +++ b/ext/native/thin3d/GLRenderManager.cpp @@ -762,7 +762,7 @@ void GLPushBuffer::Defragment() { size_ = newSize; bool res = AddBuffer(); - _dbg_assert_msg_(G3D, res, "AddBuffer failed"); + _assert_msg_(G3D, res, "AddBuffer failed"); } size_t GLPushBuffer::GetTotalSize() const { diff --git a/ext/native/thin3d/VulkanQueueRunner.cpp b/ext/native/thin3d/VulkanQueueRunner.cpp index fdbdf3ebe9..8be1ca7120 100644 --- a/ext/native/thin3d/VulkanQueueRunner.cpp +++ b/ext/native/thin3d/VulkanQueueRunner.cpp @@ -1,3 +1,4 @@ +#include #include "DataFormat.h" #include "VulkanQueueRunner.h" #include "VulkanRenderManager.h" @@ -374,12 +375,11 @@ VkRenderPass VulkanQueueRunner::GetRenderPass(const RPKey &key) { return pass; } -void VulkanQueueRunner::RunSteps(VkCommandBuffer cmd, std::vector &steps) { +void VulkanQueueRunner::RunSteps(VkCommandBuffer cmd, std::vector &steps, VkQueryPool queryPool, std::vector *timestampDescriptions) { // Optimizes renderpasses, then sequences them. // Planned optimizations: // * 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). - // * Merge subsequent render passes to the same target that are interspersed with unrelated draws to other render targets (God of War). for (int j = 0; j < (int)steps.size(); j++) { if (steps[j]->stepType == VKRStepType::RENDER && @@ -392,7 +392,6 @@ void VulkanQueueRunner::RunSteps(VkCommandBuffer cmd, std::vector &st for (int j = 0; j < (int)steps.size() - 1; j++) { // 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 && @@ -400,8 +399,8 @@ void VulkanQueueRunner::RunSteps(VkCommandBuffer cmd, std::vector &st steps[j]->render.stencil == VKRRenderPassAction::CLEAR && steps[j]->render.depth == VKRRenderPassAction::CLEAR) { - // Drop the first step, and merge it into the next step that touches the same framebuffer. - for (size_t i = j + 1; i < steps.size(); i++) { + // Drop the clear step, and merge it into the next step that touches the same framebuffer. + for (int i = j + 1; i < (int)steps.size(); i++) { if (steps[i]->stepType == VKRStepType::RENDER && steps[i]->render.framebuffer == steps[j]->render.framebuffer) { if (steps[i]->render.color != VKRRenderPassAction::CLEAR) { @@ -439,6 +438,9 @@ void VulkanQueueRunner::RunSteps(VkCommandBuffer cmd, std::vector &st if (hacksEnabled_ & QUEUE_HACK_SONIC) { ApplySonicHack(steps); } + if (hacksEnabled_ & QUEUE_HACK_RENDERPASS_MERGE) { + ApplyRenderPassMerge(steps); + } } for (size_t i = 0; i < steps.size(); i++) { @@ -462,6 +464,11 @@ void VulkanQueueRunner::RunSteps(VkCommandBuffer cmd, std::vector &st case VKRStepType::RENDER_SKIP: break; } + + if (queryPool) { + vkCmdWriteTimestamp(cmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, queryPool, (uint32_t)timestampDescriptions->size()); + timestampDescriptions->push_back(StepToString(step)); + } } // Deleting all in one go should be easier on the instruction cache than deleting @@ -696,10 +703,111 @@ void VulkanQueueRunner::ApplySonicHack(std::vector &steps) { } } +std::string VulkanQueueRunner::StepToString(const VKRStep &step) const { + char buffer[256]; + switch (step.stepType) { + case VKRStepType::RENDER: + { + int w = step.render.framebuffer ? step.render.framebuffer->width : vulkan_->GetBackbufferWidth(); + int h = step.render.framebuffer ? step.render.framebuffer->height : vulkan_->GetBackbufferHeight(); + snprintf(buffer, sizeof(buffer), "RenderPass (draws: %d, %dx%d, fb: %p, )", step.render.numDraws, w, h, step.render.framebuffer); + break; + } + case VKRStepType::COPY: + snprintf(buffer, sizeof(buffer), "Copy (%dx%d)", step.copy.srcRect.extent.width, step.copy.srcRect.extent.height); + break; + case VKRStepType::BLIT: + snprintf(buffer, sizeof(buffer), "Blit (%dx%d->%dx%d)", step.blit.srcRect.extent.width, step.blit.srcRect.extent.height, step.blit.dstRect.extent.width, step.blit.dstRect.extent.height); + break; + case VKRStepType::READBACK: + snprintf(buffer, sizeof(buffer), "Readback (%dx%d, fb: %p)", step.readback.srcRect.extent.width, step.readback.srcRect.extent.height, step.readback.src); + break; + case VKRStepType::READBACK_IMAGE: + snprintf(buffer, sizeof(buffer), "ReadbackImage (%dx%d)", step.readback_image.srcRect.extent.width, step.readback_image.srcRect.extent.height); + break; + case VKRStepType::RENDER_SKIP: + snprintf(buffer, sizeof(buffer), "(SKIPPED RenderPass)"); + break; + default: + buffer[0] = 0; + break; + } + return std::string(buffer); +} + +// Ideally, this should be cheap enough to be applied to all games. At least on mobile, it's pretty +// much a guaranteed neutral or win in terms of GPU power. However, dependency calculation really +// must be perfect! +void VulkanQueueRunner::ApplyRenderPassMerge(std::vector &steps) { + // First let's count how many times each framebuffer is rendered to. + // If it's more than one, let's do our best to merge them. This can help God of War quite a bit. + std::map counts; + for (int i = 0; i < (int)steps.size(); i++) { + if (steps[i]->stepType == VKRStepType::RENDER) { + counts[steps[i]->render.framebuffer]++; + } + } + + // Now, let's go through the steps. If we find one that is rendered to more than once, + // we'll scan forward and slurp up any rendering that can be merged across. + for (int i = 0; i < (int)steps.size(); i++) { + if (steps[i]->stepType == VKRStepType::RENDER && counts[steps[i]->render.framebuffer] > 1) { + auto fb = steps[i]->render.framebuffer; + TinySet touchedFramebuffers; // must be the same fast-size as the dependencies TinySet for annoying reasons. + for (int j = i + 1; j < (int)steps.size(); j++) { + // If any other passes are reading from this framebuffer as-is, we cancel the scan. + switch (steps[j]->stepType) { + case VKRStepType::RENDER: + if (steps[j]->dependencies.contains(fb)) { + goto done_fb; + } + // Prevent Unknown's example case from https://github.com/hrydgard/ppsspp/pull/12242 + if (steps[j]->dependencies.contains(touchedFramebuffers)) { + goto done_fb; + } + if (steps[j]->render.framebuffer == fb) { + // ok. Now, if it's a render, slurp up all the commands + // and kill the step. + // Also slurp up any pretransitions. + steps[i]->preTransitions.insert(steps[i]->preTransitions.end(), steps[j]->preTransitions.begin(), steps[j]->preTransitions.end()); + steps[i]->commands.insert(steps[i]->commands.end(), steps[j]->commands.begin(), steps[j]->commands.end()); + steps[j]->stepType = VKRStepType::RENDER_SKIP; + } + // Remember the framebuffer this wrote to. We can't merge with later passes that depend on these. + if (steps[j]->render.framebuffer != fb) { + touchedFramebuffers.insert(steps[j]->render.framebuffer); + } + break; + case VKRStepType::COPY: + if (steps[j]->copy.src == fb || steps[j]->copy.dst == fb) { + goto done_fb; + } + break; + case VKRStepType::BLIT: + if (steps[j]->blit.src == fb || steps[j]->blit.dst == fb) { + goto done_fb; + } + break; + case VKRStepType::READBACK: + // Not sure this has much effect, when executed READBACK is always the last step + // since we stall the GPU and wait immediately after. + if (steps[j]->readback.src == fb) { + goto done_fb; + } + break; + } + } + done_fb: + ; + } + } +} + void VulkanQueueRunner::LogSteps(const std::vector &steps) { ILOG("======================================="); for (size_t i = 0; i < steps.size(); i++) { const VKRStep &step = *steps[i]; + ILOG("%s", StepToString(step).c_str()); switch (step.stepType) { case VKRStepType::RENDER: LogRenderPass(step); @@ -767,20 +875,20 @@ void VulkanQueueRunner::LogRenderPass(const VKRStep &pass) { ILOG("RenderPass End(%x)", fb); } -void VulkanQueueRunner::LogCopy(const VKRStep &pass) { - ILOG("Copy()"); +void VulkanQueueRunner::LogCopy(const VKRStep &step) { + ILOG("%s", StepToString(step).c_str()); } -void VulkanQueueRunner::LogBlit(const VKRStep &pass) { - ILOG("Blit()"); +void VulkanQueueRunner::LogBlit(const VKRStep &step) { + ILOG("%s", StepToString(step).c_str()); } -void VulkanQueueRunner::LogReadback(const VKRStep &pass) { - ILOG("Readback"); +void VulkanQueueRunner::LogReadback(const VKRStep &step) { + ILOG("%s", StepToString(step).c_str()); } -void VulkanQueueRunner::LogReadbackImage(const VKRStep &pass) { - ILOG("ReadbackImage"); +void VulkanQueueRunner::LogReadbackImage(const VKRStep &step) { + ILOG("%s", StepToString(step).c_str()); } void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer cmd) { @@ -869,9 +977,11 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c auto &commands = step.commands; - // TODO: Dynamic state commands (SetViewport, SetScissor, SetBlendConstants, SetStencil*) are only - // valid when a pipeline is bound with those as dynamic state. So we need to add some state tracking here - // for this to be correct. This is a bit of a pain but also will let us eliminate redundant calls. + // We can do a little bit of state tracking here to eliminate some calls into the driver. + // The stencil ones are very commonly mostly redundant so let's eliminate them where possible. + int lastStencilWriteMask = -1; + int lastStencilCompareMask = -1; + int lastStencilReference = -1; for (const auto &c : commands) { switch (c.cmd) { @@ -882,6 +992,10 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c if (c.pipeline.pipeline != lastPipeline) { vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, c.pipeline.pipeline); lastPipeline = c.pipeline.pipeline; + // Reset dynamic state so it gets refreshed with the new pipeline. + lastStencilWriteMask = -1; + lastStencilCompareMask = -1; + lastStencilReference = -1; } break; @@ -902,9 +1016,18 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c break; case VKRRenderCommand::STENCIL: - vkCmdSetStencilWriteMask(cmd, VK_STENCIL_FRONT_AND_BACK, c.stencil.stencilWriteMask); - vkCmdSetStencilCompareMask(cmd, VK_STENCIL_FRONT_AND_BACK, c.stencil.stencilCompareMask); - vkCmdSetStencilReference(cmd, VK_STENCIL_FRONT_AND_BACK, c.stencil.stencilRef); + if (lastStencilWriteMask != c.stencil.stencilWriteMask) { + lastStencilWriteMask = (int)c.stencil.stencilWriteMask; + vkCmdSetStencilWriteMask(cmd, VK_STENCIL_FRONT_AND_BACK, c.stencil.stencilWriteMask); + } + if (lastStencilCompareMask != c.stencil.stencilCompareMask) { + lastStencilCompareMask = c.stencil.stencilCompareMask; + vkCmdSetStencilCompareMask(cmd, VK_STENCIL_FRONT_AND_BACK, c.stencil.stencilCompareMask); + } + if (lastStencilReference != c.stencil.stencilRef) { + lastStencilReference = c.stencil.stencilRef; + vkCmdSetStencilReference(cmd, VK_STENCIL_FRONT_AND_BACK, c.stencil.stencilRef); + } break; case VKRRenderCommand::DRAW_INDEXED: @@ -924,6 +1047,7 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c case VKRRenderCommand::CLEAR: { + // If we get here, we failed to merge a clear into a render pass load op. This is bad for perf. int numAttachments = 0; VkClearRect rc{}; rc.baseArrayLayer = 0; diff --git a/ext/native/thin3d/VulkanQueueRunner.h b/ext/native/thin3d/VulkanQueueRunner.h index 17f0acb69a..3c840856c6 100644 --- a/ext/native/thin3d/VulkanQueueRunner.h +++ b/ext/native/thin3d/VulkanQueueRunner.h @@ -13,6 +13,72 @@ struct VKRImage; enum { QUEUE_HACK_MGS2_ACID = 1, QUEUE_HACK_SONIC = 2, + // Killzone PR = 4. + QUEUE_HACK_RENDERPASS_MERGE = 8, +}; + +// Insert-only small-set implementation. Performs no allocation unless MaxFastSize is exceeded. +template +struct TinySet { + ~TinySet() { delete slowLookup_; } + inline void insert(T t) { + // Fast linear scan. + for (int i = 0; i < fastCount; i++) { + if (fastLookup_[i] == t) + return; // We already have it. + } + // Fast insertion + if (fastCount < MaxFastSize) { + fastLookup_[fastCount++] = t; + return; + } + // Fall back to slow path. + insertSlow(t); + } + bool contains(T t) const { + for (int i = 0; i < fastCount; i++) { + if (fastLookup_[i] == t) + return true; + } + if (slowLookup_) { + for (auto x : *slowLookup_) { + if (x == t) + return true; + } + } + return false; + } + bool contains(const TinySet &otherSet) { + // Awkward, kind of ruins the fun. + for (int i = 0; i < fastCount; i++) { + if (otherSet.contains(fastLookup_[i])) + return true; + } + if (slowLookup_) { + for (auto x : *slowLookup_) { + if (otherSet.contains(x)) + return true; + } + } + return false; + } + +private: + void insertSlow(T t) { + if (!slowLookup_) { + slowLookup_ = new std::vector(); + } else { + for (size_t i = 0; i < slowLookup_->size(); i++) { + if ((*slowLookup_)[i] == t) + return; + } + } + slowLookup_->push_back(t); + } + T fastLookup_[MaxFastSize]; + int fastCount = 0; + int slowCount = 0; + std::vector *slowLookup_ = nullptr; }; enum class VKRRenderCommand : uint8_t { @@ -109,9 +175,12 @@ struct TransitionRequest { struct VKRStep { VKRStep(VKRStepType _type) : stepType(_type) {} + ~VKRStep() {} + VKRStepType stepType; std::vector commands; std::vector preTransitions; + TinySet dependencies; union { struct { VKRFramebuffer *framebuffer; @@ -163,9 +232,11 @@ public: } // RunSteps can modify steps but will leave it in a valid state. - void RunSteps(VkCommandBuffer cmd, std::vector &steps); + void RunSteps(VkCommandBuffer cmd, std::vector &steps, VkQueryPool queryPool, std::vector *timestampDescriptions); void LogSteps(const std::vector &steps); + std::string StepToString(const VKRStep &step) const; + void CreateDeviceObjects(); void DestroyDeviceObjects(); @@ -240,6 +311,7 @@ private: void ApplyMGSHack(std::vector &steps); void ApplySonicHack(std::vector &steps); + void ApplyRenderPassMerge(std::vector &steps); static void SetupTransitionToTransferSrc(VKRImage &img, VkImageMemoryBarrier &barrier, VkPipelineStageFlags &stage, VkImageAspectFlags aspect); static void SetupTransitionToTransferDst(VKRImage &img, VkImageMemoryBarrier &barrier, VkPipelineStageFlags &stage, VkImageAspectFlags aspect); diff --git a/ext/native/thin3d/VulkanRenderManager.cpp b/ext/native/thin3d/VulkanRenderManager.cpp index 50a7efb849..921a2c05a9 100644 --- a/ext/native/thin3d/VulkanRenderManager.cpp +++ b/ext/native/thin3d/VulkanRenderManager.cpp @@ -1,6 +1,8 @@ #include #include +#include + #include "Common/Log.h" #include "base/logging.h" @@ -65,7 +67,7 @@ void CreateImage(VulkanContext *vulkan, VkCommandBuffer cmd, VKRImage &img, int VkImageAspectFlags aspects = color ? VK_IMAGE_ASPECT_COLOR_BIT : (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT); VkImageViewCreateInfo ivci{ VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; - ivci.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A }; + ivci.components = { VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY }; ivci.format = ici.format; ivci.image = img.image; ivci.viewType = VK_IMAGE_VIEW_TYPE_2D; @@ -132,6 +134,11 @@ VulkanRenderManager::VulkanRenderManager(VulkanContext *vulkan) : vulkan_(vulkan res = vkAllocateCommandBuffers(vulkan_->GetDevice(), &cmd_alloc, &frameData_[i].mainCmd); assert(res == VK_SUCCESS); frameData_[i].fence = vulkan_->CreateFence(true); // So it can be instantly waited on + + VkQueryPoolCreateInfo query_ci{ VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO }; + query_ci.queryCount = MAX_TIMESTAMP_QUERIES; + query_ci.queryType = VK_QUERY_TYPE_TIMESTAMP; + res = vkCreateQueryPool(vulkan_->GetDevice(), &query_ci, nullptr, &frameData_[i].timestampQueryPool_); } queueRunner_.CreateDeviceObjects(); @@ -164,10 +171,10 @@ void VulkanRenderManager::CreateBackbuffers() { VkImageViewCreateInfo color_image_view = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; color_image_view.format = vulkan_->GetSwapchainFormat(); - color_image_view.components.r = VK_COMPONENT_SWIZZLE_R; - color_image_view.components.g = VK_COMPONENT_SWIZZLE_G; - color_image_view.components.b = VK_COMPONENT_SWIZZLE_B; - color_image_view.components.a = VK_COMPONENT_SWIZZLE_A; + color_image_view.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + color_image_view.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + color_image_view.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + color_image_view.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; color_image_view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; color_image_view.subresourceRange.baseMipLevel = 0; color_image_view.subresourceRange.levelCount = 1; @@ -222,6 +229,8 @@ void VulkanRenderManager::StopThread() { std::unique_lock lock(frameData.pull_mutex); frameData.pull_condVar.notify_all(); } + // Zero the queries so we don't try to pull them later. + frameData.timestampDescriptions.clear(); } thread_.join(); ILOG("Vulkan submission thread joined. Frame=%d", vulkan_->GetCurFrame()); @@ -292,6 +301,7 @@ VulkanRenderManager::~VulkanRenderManager() { vkDestroyCommandPool(device, frameData_[i].cmdPoolInit, nullptr); vkDestroyCommandPool(device, frameData_[i].cmdPoolMain, nullptr); vkDestroyFence(device, frameData_[i].fence, nullptr); + vkDestroyQueryPool(device, frameData_[i].timestampQueryPool_, nullptr); } queueRunner_.DestroyDeviceObjects(); } @@ -342,12 +352,13 @@ void VulkanRenderManager::ThreadFunc() { VLOG("PULL: Quitting"); } -void VulkanRenderManager::BeginFrame() { +void VulkanRenderManager::BeginFrame(bool enableProfiling) { VLOG("BeginFrame"); VkDevice device = vulkan_->GetDevice(); int curFrame = vulkan_->GetCurFrame(); FrameData &frameData = frameData_[curFrame]; + frameData.profilingEnabled_ = enableProfiling; // Make sure the very last command buffer from the frame before the previous has been fully executed. if (useThread_) { @@ -363,6 +374,41 @@ void VulkanRenderManager::BeginFrame() { vkWaitForFences(device, 1, &frameData.fence, true, UINT64_MAX); vkResetFences(device, 1, &frameData.fence); + uint64_t queryResults[MAX_TIMESTAMP_QUERIES]; + + if (frameData.profilingEnabled_) { + // Pull the profiling results from last time and produce a summary! + if (!frameData.timestampDescriptions.empty()) { + int numQueries = (int)frameData.timestampDescriptions.size(); + VkResult res = vkGetQueryPoolResults( + vulkan_->GetDevice(), + frameData.timestampQueryPool_, 0, numQueries, sizeof(uint64_t) * numQueries, &queryResults[0], sizeof(uint64_t), + VK_QUERY_RESULT_64_BIT); + if (res == VK_SUCCESS) { + double timestampConversionFactor = (double)vulkan_->GetPhysicalDeviceProperties().properties.limits.timestampPeriod * (1.0 / 1000000.0); + int validBits = vulkan_->GetQueueFamilyProperties(vulkan_->GetGraphicsQueueFamilyIndex()).timestampValidBits; + uint64_t timestampDiffMask = validBits == 64 ? 0xFFFFFFFFFFFFFFFFULL : ((1ULL << validBits) - 1); + std::stringstream str; + + char line[256]; + snprintf(line, sizeof(line), "Total GPU time: %0.3f ms\n", ((double)((queryResults[numQueries - 1] - queryResults[0]) & timestampDiffMask) * timestampConversionFactor)); + str << line; + for (int i = 0; i < numQueries - 1; i++) { + uint64_t diff = (queryResults[i + 1] - queryResults[i]) & timestampDiffMask; + double milliseconds = (double)diff * timestampConversionFactor; + snprintf(line, sizeof(line), "%s: %0.3f ms\n", frameData.timestampDescriptions[i + 1].c_str(), milliseconds); + str << line; + } + frameData.profileSummary = str.str(); + } else { + frameData.profileSummary = "(error getting GPU profile - not ready?)"; + } + } + else { + frameData.profileSummary = "(no GPU profile data collected)"; + } + } + // Must be after the fence - this performs deletes. VLOG("PUSH: BeginFrame %d", curFrame); if (!run_) { @@ -371,6 +417,18 @@ void VulkanRenderManager::BeginFrame() { vulkan_->BeginFrame(); insideFrame_ = true; + + frameData.timestampDescriptions.clear(); + if (frameData_->profilingEnabled_) { + // For various reasons, we need to always use an init cmd buffer in this case to perform the vkCmdResetQueryPool, + // unless we want to limit ourselves to only measure the main cmd buffer. + // Reserve the first two queries for initCmd. + frameData.timestampDescriptions.push_back("initCmd Begin"); + frameData.timestampDescriptions.push_back("initCmd"); + VkCommandBuffer initCmd = GetInitCmd(); + vkCmdResetQueryPool(initCmd, frameData.timestampQueryPool_, 0, MAX_TIMESTAMP_QUERIES); + vkCmdWriteTimestamp(initCmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, frameData.timestampQueryPool_, 0); + } } VkCommandBuffer VulkanRenderManager::GetInitCmd() { @@ -397,6 +455,7 @@ void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRR if (steps_.size() && steps_.back()->render.framebuffer == fb && steps_.back()->stepType == VKRStepType::RENDER) { if (color != VKRRenderPassAction::CLEAR && depth != VKRRenderPassAction::CLEAR && stencil != VKRRenderPassAction::CLEAR) { // We don't move to a new step, this bind was unnecessary and we can safely skip it. + // Not sure how much this is still happening but probably worth checking for nevertheless. return; } } @@ -561,7 +620,6 @@ bool VulkanRenderManager::InitDepthStencilBuffer(VkCommandBuffer cmd) { image_info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; image_info.flags = 0; - depth_.format = depth_format; VkDevice device = vulkan_->GetDevice(); @@ -611,10 +669,10 @@ bool VulkanRenderManager::InitDepthStencilBuffer(VkCommandBuffer cmd) { VkImageViewCreateInfo depth_view_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; depth_view_info.image = depth_.image; depth_view_info.format = depth_format; - depth_view_info.components.r = VK_COMPONENT_SWIZZLE_R; - depth_view_info.components.g = VK_COMPONENT_SWIZZLE_G; - depth_view_info.components.b = VK_COMPONENT_SWIZZLE_B; - depth_view_info.components.a = VK_COMPONENT_SWIZZLE_A; + depth_view_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + depth_view_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + depth_view_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + depth_view_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; depth_view_info.subresourceRange.aspectMask = aspectMask; depth_view_info.subresourceRange.baseMipLevel = 0; depth_view_info.subresourceRange.levelCount = 1; @@ -803,7 +861,9 @@ void VulkanRenderManager::BlitFramebuffer(VKRFramebuffer *src, VkRect2D srcRect, } VkImageView VulkanRenderManager::BindFramebufferAsTexture(VKRFramebuffer *fb, int binding, int aspectBit, int attachment) { - // Should just mark the dependency and return the image. + _dbg_assert_(G3D, curRenderStep_ != nullptr); + // Mark the dependency, check for required transitions, and return the image. + for (int i = (int)steps_.size() - 1; i >= 0; i--) { if (steps_[i]->stepType == VKRStepType::RENDER && steps_[i]->render.framebuffer == fb) { // If this framebuffer was rendered to earlier in this frame, make sure to pre-transition it to the correct layout. @@ -815,14 +875,18 @@ VkImageView VulkanRenderManager::BindFramebufferAsTexture(VKRFramebuffer *fb, in } } + // Track dependencies fully. + curRenderStep_->dependencies.insert(fb); + if (!curRenderStep_->preTransitions.empty() && curRenderStep_->preTransitions.back().fb == fb && curRenderStep_->preTransitions.back().targetLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { // We're done. return fb->color.imageView; + } else { + curRenderStep_->preTransitions.push_back({ fb, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL }); + return fb->color.imageView; } - curRenderStep_->preTransitions.push_back({ fb, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL }); - return fb->color.imageView; } void VulkanRenderManager::Finish() { @@ -885,6 +949,7 @@ void VulkanRenderManager::BeginSubmitFrame(int frame) { VkCommandBufferBeginInfo begin{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; res = vkBeginCommandBuffer(frameData.mainCmd, &begin); + _assert_msg_(G3D, res == VK_SUCCESS, "vkBeginCommandBuffer failed! result=%s", VulkanResultToString(res)); queueRunner_.SetBackbuffer(framebuffers_[frameData.curSwapchainImage], swapchainImages_[frameData.curSwapchainImage].image); @@ -896,6 +961,10 @@ void VulkanRenderManager::BeginSubmitFrame(int frame) { void VulkanRenderManager::Submit(int frame, bool triggerFence) { FrameData &frameData = frameData_[frame]; if (frameData.hasInitCommands) { + if (frameData.profilingEnabled_ && triggerFence) { + // Pre-allocated query ID 1. + vkCmdWriteTimestamp(frameData.initCmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, frameData.timestampQueryPool_, 1); + } VkResult res = vkEndCommandBuffer(frameData.initCmd); _assert_msg_(G3D, res == VK_SUCCESS, "vkEndCommandBuffer failed (init)! result=%s", VulkanResultToString(res)); } @@ -995,7 +1064,7 @@ void VulkanRenderManager::Run(int frame) { auto &stepsOnThread = frameData_[frame].steps; VkCommandBuffer cmd = frameData.mainCmd; // queueRunner_.LogSteps(stepsOnThread); - queueRunner_.RunSteps(cmd, stepsOnThread); + queueRunner_.RunSteps(cmd, stepsOnThread, frameData.profilingEnabled_ ? frameData.timestampQueryPool_ : VK_NULL_HANDLE, &frameData.timestampDescriptions); stepsOnThread.clear(); switch (frameData.type) { diff --git a/ext/native/thin3d/VulkanRenderManager.h b/ext/native/thin3d/VulkanRenderManager.h index 84f7cbeed2..a06332aa69 100644 --- a/ext/native/thin3d/VulkanRenderManager.h +++ b/ext/native/thin3d/VulkanRenderManager.h @@ -92,7 +92,7 @@ public: void ThreadFunc(); // Makes sure that the GPU has caught up enough that we can start writing buffers of this frame again. - void BeginFrame(); + void BeginFrame(bool enableProfiling); // Can run on a different thread! void Finish(); void Run(int frame); @@ -242,6 +242,10 @@ public: return &queueRunner_; } + std::string GetGpuProfileString() const { + return frameData_[vulkan_->GetCurFrame()].profileSummary; + } + private: bool InitBackbufferFramebuffers(int width, int height); bool InitDepthStencilBuffer(VkCommandBuffer cmd); // Used for non-buffered rendering. @@ -284,9 +288,20 @@ private: // Swapchain. bool hasBegun = false; uint32_t curSwapchainImage = -1; + + // Profiling. + VkQueryPool timestampQueryPool_ = VK_NULL_HANDLE; + bool profilingEnabled_; + std::vector timestampDescriptions; + std::string profileSummary; }; + FrameData frameData_[VulkanContext::MAX_INFLIGHT_FRAMES]; + enum { + MAX_TIMESTAMP_QUERIES = 256, + }; + // Submission time state int curWidth_ = -1; int curHeight_ = -1; diff --git a/ext/native/thin3d/thin3d_vulkan.cpp b/ext/native/thin3d/thin3d_vulkan.cpp index a62761feea..a0f384f149 100644 --- a/ext/native/thin3d/thin3d_vulkan.cpp +++ b/ext/native/thin3d/thin3d_vulkan.cpp @@ -879,7 +879,7 @@ VKContext::~VKContext() { } void VKContext::BeginFrame() { - renderManager_.BeginFrame(); + renderManager_.BeginFrame(g_Config.bShowGpuProfile); FrameData &frame = frame_[vulkan_->GetCurFrame()]; push_ = frame.pushBuffer; diff --git a/ext/native/ui/ui_screen.cpp b/ext/native/ui/ui_screen.cpp index a49877327d..b2662ba385 100644 --- a/ext/native/ui/ui_screen.cpp +++ b/ext/native/ui/ui_screen.cpp @@ -268,6 +268,9 @@ bool PopupScreen::key(const KeyInput &key) { void PopupScreen::update() { UIDialogScreen::update(); + if (defaultButton_) + defaultButton_->SetEnabled(CanComplete(DR_OK)); + float animatePos = 1.0f; ++frames_; @@ -314,10 +317,12 @@ void PopupScreen::SetPopupOrigin(const UI::View *view) { } void PopupScreen::TriggerFinish(DialogResult result) { - finishFrame_ = frames_; - finishResult_ = result; + if (CanComplete(result)) { + finishFrame_ = frames_; + finishResult_ = result; - OnCompleted(result); + OnCompleted(result); + } } void PopupScreen::resized() { diff --git a/ext/native/ui/ui_screen.h b/ext/native/ui/ui_screen.h index c044e961a0..c45c8b1329 100644 --- a/ext/native/ui/ui_screen.h +++ b/ext/native/ui/ui_screen.h @@ -84,6 +84,7 @@ protected: virtual bool FillVertical() const { return false; } virtual UI::Size PopupWidth() const { return 550; } virtual bool ShowButtons() const { return true; } + virtual bool CanComplete(DialogResult result) { return true; } virtual void OnCompleted(DialogResult result) {} virtual void update() override; @@ -225,7 +226,7 @@ private: class TextEditPopupScreen : public PopupScreen { public: - TextEditPopupScreen(std::string *value, std::string &placeholder, const std::string &title, int maxLen) + TextEditPopupScreen(std::string *value, const std::string &placeholder, const std::string &title, int maxLen) : PopupScreen(title, "OK", "Cancel"), value_(value), placeholder_(placeholder), maxLen_(maxLen) {} virtual void CreatePopupContents(ViewGroup *parent) override; diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index cd3f2c2a38..17c3841aef 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Apr 19 22:49:10 CEST 2019 +#Fri Aug 23 16:10:47 CEST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip