diff --git a/src/Cafe/GraphicPack/GraphicPack2.cpp b/src/Cafe/GraphicPack/GraphicPack2.cpp index b581316e..27d423b9 100644 --- a/src/Cafe/GraphicPack/GraphicPack2.cpp +++ b/src/Cafe/GraphicPack/GraphicPack2.cpp @@ -280,6 +280,10 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules) m_enabled = m_default_enabled; } + auto option_allowRendertargetSizeOptimization = rules.FindOption("colorbufferOptimizationAware"); + if (option_allowRendertargetSizeOptimization) + m_allowRendertargetSizeOptimization = boost::iequals(*option_allowRendertargetSizeOptimization, "true") || boost::iequals(*option_allowRendertargetSizeOptimization, "1"); + auto option_vendorFilter = rules.FindOption("vendorFilter"); if (option_vendorFilter) { diff --git a/src/Cafe/GraphicPack/GraphicPack2.h b/src/Cafe/GraphicPack/GraphicPack2.h index 6b07cce9..9b6a86d4 100644 --- a/src/Cafe/GraphicPack/GraphicPack2.h +++ b/src/Cafe/GraphicPack/GraphicPack2.h @@ -113,6 +113,7 @@ public: const std::string& GetVirtualPath() const { return m_virtualPath; } // returns the path in the gfx tree hierarchy const std::string& GetDescription() const { return m_description; } bool IsDefaultEnabled() const { return m_default_enabled; } + bool AllowRendertargetSizeOptimization() const { return m_allowRendertargetSizeOptimization; } void SetEnabled(bool state) { m_enabled = state; } @@ -217,6 +218,8 @@ private: bool m_default_enabled = false; + bool m_allowRendertargetSizeOptimization = false; // gfx pack supports framebuffers with non-padded sizes, which is an optional optimization introduced with Cemu 2.0-74 + // filter std::optional m_renderer_api; std::optional m_gfx_vendor; diff --git a/src/Cafe/HW/Espresso/Debugger/DebugSymbolStorage.h b/src/Cafe/HW/Espresso/Debugger/DebugSymbolStorage.h index aba6a9b5..0a46951d 100644 --- a/src/Cafe/HW/Espresso/Debugger/DebugSymbolStorage.h +++ b/src/Cafe/HW/Espresso/Debugger/DebugSymbolStorage.h @@ -45,12 +45,17 @@ public: static void ClearRange(MPTR address, uint32 length) { + if (length == 0) + return; s_lock.lock(); - while (length > 0) + for (;;) { auto itr = s_typeStorage.find(address); if (itr != s_typeStorage.end()) s_typeStorage.erase(itr); + + if (length <= 4) + break; address += 4; length -= 4; } @@ -60,4 +65,4 @@ public: private: static FSpinlock s_lock; static std::unordered_map s_typeStorage; -}; \ No newline at end of file +}; diff --git a/src/Cafe/HW/Latte/Core/Latte.h b/src/Cafe/HW/Latte/Core/Latte.h index d9419a6a..e8cb2be4 100644 --- a/src/Cafe/HW/Latte/Core/Latte.h +++ b/src/Cafe/HW/Latte/Core/Latte.h @@ -25,6 +25,8 @@ struct LatteGPUState_t // context control uint32 contextControl0; uint32 contextControl1; + // optional features + bool allowFramebufferSizeOptimization{false}; // allow using scissor box as size hint to determine non-padded rendertarget size // draw context struct { diff --git a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp index 30069712..f165e257 100644 --- a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp +++ b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp @@ -267,14 +267,15 @@ LatteTextureView* LatteMRT::GetColorAttachmentTexture(uint32 index, bool createN // colorbuffer width/height has to be padded to 8/32 alignment but the actual resolution might be smaller // use the scissor box as a clue to figure out the original resolution if possible -#if 0 - uint32 scissorBoxWidth = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_BR.get_BR_X(); - uint32 scissorBoxHeight = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_BR.get_BR_Y(); - if (((scissorBoxWidth + 7) & ~7) == colorBufferWidth) - colorBufferWidth = scissorBoxWidth; - if (((colorBufferHeight + 31) & ~31) == colorBufferHeight) - colorBufferHeight = scissorBoxHeight; -#endif + if(LatteGPUState.allowFramebufferSizeOptimization) + { + uint32 scissorBoxWidth = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_BR.get_BR_X(); + uint32 scissorBoxHeight = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_BR.get_BR_Y(); + if (((scissorBoxWidth + 7) & ~7) == colorBufferWidth) + colorBufferWidth = scissorBoxWidth; + if (((colorBufferHeight + 31) & ~31) == colorBufferHeight) + colorBufferHeight = scissorBoxHeight; + } // log resolution changes if the above heuristic takes effect // this is useful to find resolutions which need to be updated in gfx pack texture rules @@ -303,7 +304,7 @@ LatteTextureView* LatteMRT::GetColorAttachmentTexture(uint32 index, bool createN if (colorBufferView == nullptr) { // create color buffer view - colorBufferView = LatteTexture_CreateMapping(colorBufferPhysMem, 0, colorBufferWidth, colorBufferHeight, (viewFirstSlice + viewNumSlices), colorBufferPitch, colorBufferTileMode, colorBufferSwizzle>>8, viewFirstMip, 1, viewFirstSlice, viewNumSlices, (Latte::E_GX2SURFFMT)colorBufferFormat, (viewFirstSlice + viewNumSlices)>1? Latte::E_DIM::DIM_2D_ARRAY: Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, false); + colorBufferView = LatteTexture_CreateMapping(colorBufferPhysMem, 0, colorBufferWidth, colorBufferHeight, (viewFirstSlice + viewNumSlices), colorBufferPitch, colorBufferTileMode, colorBufferSwizzle>>8, viewFirstMip, 1, viewFirstSlice, viewNumSlices, (Latte::E_GX2SURFFMT)colorBufferFormat, (viewFirstSlice + viewNumSlices)>1? Latte::E_DIM::DIM_2D_ARRAY: Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, false, true); LatteGPUState.repeatTextureInitialization = true; checkForTextureChanges = false; } @@ -582,7 +583,7 @@ bool LatteMRT::UpdateCurrentFBO() if (!depthBufferView) { // create new depth buffer view and if it doesn't exist then also create the texture - depthBufferView = LatteTexture_CreateMapping(depthBufferPhysMem, 0, depthBufferWidth, depthBufferHeight, depthBufferViewFirstSlice+1, depthBufferPitch, depthBufferTileMode, depthBufferSwizzle, 0, 1, depthBufferViewFirstSlice, 1, depthBufferFormat, depthBufferViewFirstSlice > 0 ? Latte::E_DIM::DIM_2D_ARRAY : Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, true); + depthBufferView = LatteTexture_CreateMapping(depthBufferPhysMem, 0, depthBufferWidth, depthBufferHeight, depthBufferViewFirstSlice+1, depthBufferPitch, depthBufferTileMode, depthBufferSwizzle, 0, 1, depthBufferViewFirstSlice, 1, depthBufferFormat, depthBufferViewFirstSlice > 0 ? Latte::E_DIM::DIM_2D_ARRAY : Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, true, true); LatteGPUState.repeatTextureInitialization = true; } else diff --git a/src/Cafe/HW/Latte/Core/LatteTexture.cpp b/src/Cafe/HW/Latte/Core/LatteTexture.cpp index d6f576d4..3754fb19 100644 --- a/src/Cafe/HW/Latte/Core/LatteTexture.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTexture.cpp @@ -1,5 +1,4 @@ #include "Cafe/HW/Latte/Core/Latte.h" -#include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" #include "Cafe/HW/Latte/Core/LatteTexture.h" @@ -9,6 +8,8 @@ #include "Cafe/GraphicPack/GraphicPack2.h" +#include + struct TexMemOccupancyEntry { uint32 addrStart; @@ -963,7 +964,7 @@ void LatteTexture_RecreateTextureWithDifferentMipSliceCount(LatteTexture* textur } // create new texture representation -// if allowCreateNewDataTexture is true, a new texture will be created if necessary. If it is false, only existing textures may be used, except if a data-compatible version of the requested texture already exists and it's not view compatible +// if allowCreateNewDataTexture is true, a new texture will be created if necessary. If it is false, only existing textures may be used, except if a data-compatible version of the requested texture already exists and it's not view compatible (todo - we should differentiate between Latte compatible views and renderer compatible) // the returned view will map to the provided mip and slice range within the created texture, this is to match the behavior of lookupSliceEx LatteTextureView* LatteTexture_CreateMapping(MPTR physAddr, MPTR physMipAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, Latte::E_HWTILEMODE tileMode, uint32 swizzle, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dimBase, Latte::E_DIM dimView, bool isDepth, bool allowCreateNewDataTexture) { @@ -980,7 +981,7 @@ LatteTextureView* LatteTexture_CreateMapping(MPTR physAddr, MPTR physMipAddr, si // todo, depth and numSlice are redundant sint32 sliceCount = firstSlice + numSlice; - std::vector list_overlappingTextures; + boost::container::small_vector list_overlappingTextures; for (sint32 sliceIndex = 0; sliceIndex < sliceCount; sliceIndex++) { sint32 mipIndex = 0; diff --git a/src/Cafe/HW/Latte/Core/LatteThread.cpp b/src/Cafe/HW/Latte/Core/LatteThread.cpp index bd312d93..a23bd5be 100644 --- a/src/Cafe/HW/Latte/Core/LatteThread.cpp +++ b/src/Cafe/HW/Latte/Core/LatteThread.cpp @@ -175,6 +175,23 @@ int Latte_ThreadEntry() // before doing anything with game specific shaders, we need to wait for graphic packs to finish loading GraphicPack2::WaitUntilReady(); + // if legacy packs are enabled we cannot use the colorbuffer resolution optimization + LatteGPUState.allowFramebufferSizeOptimization = true; + for(auto& pack : GraphicPack2::GetActiveGraphicPacks()) + { + if(pack->AllowRendertargetSizeOptimization()) + continue; + for(auto& rule : pack->GetTextureRules()) + { + if(rule.filter_settings.width >= 0 || rule.filter_settings.height >= 0 || rule.filter_settings.depth >= 0 || + rule.overwrite_settings.width >= 0 || rule.overwrite_settings.height >= 0 || rule.overwrite_settings.depth >= 0) + { + LatteGPUState.allowFramebufferSizeOptimization = false; + cemuLog_log(LogType::Force, "Graphic pack {} prevents rendertarget size optimization.", pack->GetName()); + break; + } + } + } // load disk shader cache LatteShaderCache_Load(); // init registers diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp index f3d2c7a8..e19535be 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp @@ -973,7 +973,7 @@ void _emitOperandInputCode(LatteDecompilerShaderContext* shaderContext, LatteDec } else { - cemuLog_log(LogType::Force, "Unsupported shader ALU operand sel 0x%x\n", aluInstruction->sourceOperand[operandIndex].sel); + cemuLog_log(LogType::Force, "Unsupported shader ALU operand sel {:#x}\n", aluInstruction->sourceOperand[operandIndex].sel); debugBreakpoint(); } diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h index 3a892191..313ea3c0 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h @@ -30,8 +30,6 @@ public: void Flush(bool waitIdle = false) override; void NotifyLatteCommandProcessorIdle() override; - void UpdateVSyncState(); - void EnableDebugMode() override; void SwapBuffers(bool swapTV = true, bool swapDRC = true) override; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp index 14b7a17c..b00f5490 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp @@ -1,15 +1,34 @@ #include "SwapchainInfoVk.h" #include "config/CemuConfig.h" +#include "gui/guiWrapper.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteTiming.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" -void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDevice) +SwapchainInfoVk::SwapchainInfoVk(bool mainWindow, Vector2i size) : mainWindow(mainWindow), m_desiredExtent(size) { - m_physicalDevice = physicalDevice; - m_logicalDevice = logicalDevice; - const auto details = QuerySwapchainSupport(surface, physicalDevice); + auto& windowHandleInfo = mainWindow ? gui_getWindowInfo().canvas_main : gui_getWindowInfo().canvas_pad; + auto renderer = VulkanRenderer::GetInstance(); + m_instance = renderer->GetVkInstance(); + m_logicalDevice = renderer->GetLogicalDevice(); + m_physicalDevice = renderer->GetPhysicalDevice(); + + m_surface = renderer->CreateFramebufferSurface(m_instance, windowHandleInfo); +} + + +SwapchainInfoVk::~SwapchainInfoVk() +{ + Cleanup(); + if(m_surface != VK_NULL_HANDLE) + vkDestroySurfaceKHR(m_instance, m_surface, nullptr); +} + +void SwapchainInfoVk::Create() +{ + const auto details = QuerySwapchainSupport(m_surface, m_physicalDevice); m_surfaceFormat = ChooseSurfaceFormat(details.formats); m_actualExtent = ChooseSwapExtent(details.capabilities); @@ -20,28 +39,28 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe if(image_count < 2) cemuLog_log(LogType::Force, "Vulkan: Swapchain image count less than 2 may cause problems"); - VkSwapchainCreateInfoKHR create_info = CreateSwapchainCreateInfo(surface, details, m_surfaceFormat, image_count, m_actualExtent); + VkSwapchainCreateInfoKHR create_info = CreateSwapchainCreateInfo(m_surface, details, m_surfaceFormat, image_count, m_actualExtent); create_info.oldSwapchain = nullptr; create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; - VkResult result = vkCreateSwapchainKHR(logicalDevice, &create_info, nullptr, &swapchain); + VkResult result = vkCreateSwapchainKHR(m_logicalDevice, &create_info, nullptr, &m_swapchain); if (result != VK_SUCCESS) UnrecoverableError("Error attempting to create a swapchain"); - result = vkGetSwapchainImagesKHR(logicalDevice, swapchain, &image_count, nullptr); + result = vkGetSwapchainImagesKHR(m_logicalDevice, m_swapchain, &image_count, nullptr); if (result != VK_SUCCESS) UnrecoverableError("Error attempting to retrieve the count of swapchain images"); m_swapchainImages.resize(image_count); - result = vkGetSwapchainImagesKHR(logicalDevice, swapchain, &image_count, m_swapchainImages.data()); + result = vkGetSwapchainImagesKHR(m_logicalDevice, m_swapchain, &image_count, m_swapchainImages.data()); if (result != VK_SUCCESS) UnrecoverableError("Error attempting to retrieve swapchain images"); // create default renderpass VkAttachmentDescription colorAttachment = {}; colorAttachment.format = m_surfaceFormat.format; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; - colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; @@ -62,7 +81,7 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe renderPassInfo.pAttachments = &colorAttachment; renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpass; - result = vkCreateRenderPass(logicalDevice, &renderPassInfo, nullptr, &m_swapchainRenderPass); + result = vkCreateRenderPass(m_logicalDevice, &renderPassInfo, nullptr, &m_swapchainRenderPass); if (result != VK_SUCCESS) UnrecoverableError("Failed to create renderpass for swapchain"); @@ -84,7 +103,7 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe createInfo.subresourceRange.levelCount = 1; createInfo.subresourceRange.baseArrayLayer = 0; createInfo.subresourceRange.layerCount = 1; - result = vkCreateImageView(logicalDevice, &createInfo, nullptr, &m_swapchainImageViews[i]); + result = vkCreateImageView(m_logicalDevice, &createInfo, nullptr, &m_swapchainImageViews[i]); if (result != VK_SUCCESS) UnrecoverableError("Failed to create imageviews for swapchain"); } @@ -104,7 +123,7 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe framebufferInfo.width = m_actualExtent.width; framebufferInfo.height = m_actualExtent.height; framebufferInfo.layers = 1; - result = vkCreateFramebuffer(logicalDevice, &framebufferInfo, nullptr, &m_swapchainFramebuffers[i]); + result = vkCreateFramebuffer(m_logicalDevice, &framebufferInfo, nullptr, &m_swapchainFramebuffers[i]); if (result != VK_SUCCESS) UnrecoverableError("Failed to create framebuffer for swapchain"); } @@ -114,7 +133,7 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe VkSemaphoreCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; for (auto& semaphore : m_presentSemaphores){ - if (vkCreateSemaphore(logicalDevice, &info, nullptr, &semaphore) != VK_SUCCESS) + if (vkCreateSemaphore(m_logicalDevice, &info, nullptr, &semaphore) != VK_SUCCESS) UnrecoverableError("Failed to create semaphore for swapchain present"); } @@ -123,14 +142,14 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe info = {}; info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; for (auto& semaphore : m_acquireSemaphores){ - if (vkCreateSemaphore(logicalDevice, &info, nullptr, &semaphore) != VK_SUCCESS) + if (vkCreateSemaphore(m_logicalDevice, &info, nullptr, &semaphore) != VK_SUCCESS) UnrecoverableError("Failed to create semaphore for swapchain acquire"); } VkFenceCreateInfo fenceInfo = {}; fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; - result = vkCreateFence(logicalDevice, &fenceInfo, nullptr, &m_imageAvailableFence); + result = vkCreateFence(m_logicalDevice, &fenceInfo, nullptr, &m_imageAvailableFence); if (result != VK_SUCCESS) UnrecoverableError("Failed to create fence for swapchain"); @@ -167,19 +186,20 @@ void SwapchainInfoVk::Cleanup() if (m_imageAvailableFence) { + WaitAvailableFence(); vkDestroyFence(m_logicalDevice, m_imageAvailableFence, nullptr); m_imageAvailableFence = nullptr; } - if (swapchain) + if (m_swapchain) { - vkDestroySwapchainKHR(m_logicalDevice, swapchain, nullptr); - swapchain = VK_NULL_HANDLE; + vkDestroySwapchainKHR(m_logicalDevice, m_swapchain, nullptr); + m_swapchain = VK_NULL_HANDLE; } } bool SwapchainInfoVk::IsValid() const { - return swapchain && !m_acquireSemaphores.empty(); + return m_swapchain && !m_acquireSemaphores.empty(); } void SwapchainInfoVk::WaitAvailableFence() @@ -207,7 +227,7 @@ bool SwapchainInfoVk::AcquireImage(uint64 timeout) ResetAvailableFence(); VkSemaphore acquireSemaphore = m_acquireSemaphores[m_acquireIndex]; - VkResult result = vkAcquireNextImageKHR(m_logicalDevice, swapchain, timeout, acquireSemaphore, m_imageAvailableFence, &swapchainImageIndex); + VkResult result = vkAcquireNextImageKHR(m_logicalDevice, m_swapchain, timeout, acquireSemaphore, m_imageAvailableFence, &swapchainImageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) m_shouldRecreate = true; if (result < 0) @@ -231,35 +251,6 @@ void SwapchainInfoVk::UnrecoverableError(const char* errMsg) throw std::runtime_error(errMsg); } -SwapchainInfoVk::QueueFamilyIndices SwapchainInfoVk::FindQueueFamilies(VkSurfaceKHR surface, VkPhysicalDevice device) -{ - uint32_t queueFamilyCount = 0; - vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); - - std::vector queueFamilies(queueFamilyCount); - vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); - - QueueFamilyIndices indices; - for (int i = 0; i < (int)queueFamilies.size(); ++i) - { - const auto& queueFamily = queueFamilies[i]; - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) - indices.graphicsFamily = i; - - VkBool32 presentSupport = false; - const VkResult result = vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - if (result != VK_SUCCESS) - throw std::runtime_error(fmt::format("Error while attempting to check if a surface supports presentation: {}", result)); - - if (queueFamily.queueCount > 0 && presentSupport) - indices.presentFamily = i; - - if (indices.IsComplete()) - break; - } - - return indices; -} SwapchainInfoVk::SwapchainSupportDetails SwapchainInfoVk::QuerySwapchainSupport(VkSurfaceKHR surface, const VkPhysicalDevice& device) { @@ -391,7 +382,7 @@ VkSwapchainCreateInfoKHR SwapchainInfoVk::CreateSwapchainCreateInfo(VkSurfaceKHR createInfo.imageArrayLayers = 1; createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; - const QueueFamilyIndices indices = FindQueueFamilies(surface, m_physicalDevice); + const VulkanRenderer::QueueFamilyIndices indices = VulkanRenderer::GetInstance()->FindQueueFamilies(surface, m_physicalDevice); m_swapchainQueueFamilyIndices = { (uint32)indices.graphicsFamily, (uint32)indices.presentFamily }; if (indices.graphicsFamily != indices.presentFamily) { diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h index 26dbc7d1..0e8c2ade 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h @@ -14,14 +14,6 @@ struct SwapchainInfoVk SYNC_AND_LIMIT = 3, // synchronize emulated vsync events to monitor vsync. But skip events if rate higher than virtual vsync period }; - struct QueueFamilyIndices - { - int32_t graphicsFamily = -1; - int32_t presentFamily = -1; - - bool IsComplete() const { return graphicsFamily >= 0 && presentFamily >= 0; } - }; - struct SwapchainSupportDetails { VkSurfaceCapabilitiesKHR capabilities; @@ -30,7 +22,7 @@ struct SwapchainInfoVk }; void Cleanup(); - void Create(VkPhysicalDevice physicalDevice, VkDevice logicalDevice); + void Create(); bool IsValid() const; @@ -45,8 +37,6 @@ struct SwapchainInfoVk static void UnrecoverableError(const char* errMsg); - // todo: move this function somewhere more sensible. Not directly swapchain related - static QueueFamilyIndices FindQueueFamilies(VkSurfaceKHR surface, VkPhysicalDevice device); static SwapchainSupportDetails QuerySwapchainSupport(VkSurfaceKHR surface, const VkPhysicalDevice& device); VkPresentModeKHR ChoosePresentMode(const std::vector& modes); @@ -61,14 +51,10 @@ struct SwapchainInfoVk return m_actualExtent; } - SwapchainInfoVk(VkSurfaceKHR surface, bool mainWindow) - : surface(surface), mainWindow(mainWindow) {} + SwapchainInfoVk(bool mainWindow, Vector2i size); SwapchainInfoVk(const SwapchainInfoVk&) = delete; SwapchainInfoVk(SwapchainInfoVk&&) noexcept = default; - ~SwapchainInfoVk() - { - Cleanup(); - } + ~SwapchainInfoVk(); bool mainWindow{}; @@ -77,11 +63,12 @@ struct SwapchainInfoVk VSync m_vsyncState = VSync::Immediate; bool hasDefinedSwapchainImage{}; // indicates if the swapchain image is in a defined state + VkInstance m_instance{}; VkPhysicalDevice m_physicalDevice{}; VkDevice m_logicalDevice{}; - VkSurfaceKHR surface{}; + VkSurfaceKHR m_surface{}; VkSurfaceFormatKHR m_surfaceFormat{}; - VkSwapchainKHR swapchain{}; + VkSwapchainKHR m_swapchain{}; Vector2i m_desiredExtent{}; uint32 swapchainImageIndex = (uint32)-1; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index d62b61a6..e0ebda2a 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -167,6 +167,7 @@ std::vector VulkanRenderer::GetDevices() result.emplace_back(physDeviceProps.properties.deviceName, physDeviceIDProps.deviceUUID); } } + vkDestroySurfaceKHR(instance, surface, nullptr); } catch (...) { @@ -441,7 +442,7 @@ VulkanRenderer::VulkanRenderer() } // create logical device - m_indices = SwapchainInfoVk::FindQueueFamilies(surface, m_physicalDevice); + m_indices = FindQueueFamilies(surface, m_physicalDevice); std::set uniqueQueueFamilies = { m_indices.graphicsFamily, m_indices.presentFamily }; std::vector queueCreateInfos = CreateQueueCreateInfos(uniqueQueueFamilies); VkPhysicalDeviceFeatures deviceFeatures = {}; @@ -510,7 +511,7 @@ VulkanRenderer::VulkanRenderer() PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT = reinterpret_cast(vkGetInstanceProcAddr(m_instance, "vkCreateDebugUtilsMessengerEXT")); VkDebugUtilsMessengerCreateInfoEXT debugCallback{}; - debugCallback.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT; + debugCallback.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; debugCallback.pNext = nullptr; debugCallback.flags = 0; debugCallback.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT; @@ -673,24 +674,19 @@ VulkanRenderer* VulkanRenderer::GetInstance() void VulkanRenderer::InitializeSurface(const Vector2i& size, bool mainWindow) { - auto& windowHandleInfo = mainWindow ? gui_getWindowInfo().canvas_main : gui_getWindowInfo().canvas_pad; - - const auto surface = CreateFramebufferSurface(m_instance, windowHandleInfo); if (mainWindow) { - m_mainSwapchainInfo = std::make_unique(surface, mainWindow); - m_mainSwapchainInfo->m_desiredExtent = size; - m_mainSwapchainInfo->Create(m_physicalDevice, m_logicalDevice); + m_mainSwapchainInfo = std::make_unique(mainWindow, size); + m_mainSwapchainInfo->Create(); // aquire first command buffer InitFirstCommandBuffer(); } else { - m_padSwapchainInfo = std::make_unique(surface, mainWindow); - m_padSwapchainInfo->m_desiredExtent = size; + m_padSwapchainInfo = std::make_unique(mainWindow, size); // todo: figure out a way to exclusively create swapchain on main LatteThread - m_padSwapchainInfo->Create(m_physicalDevice, m_logicalDevice); + m_padSwapchainInfo->Create(); } } @@ -706,8 +702,8 @@ SwapchainInfoVk& VulkanRenderer::GetChainInfo(bool mainWindow) const void VulkanRenderer::StopUsingPadAndWait() { - m_destroyPadSwapchainNextAcquire = true; - m_padCloseReadySemaphore.wait(); + m_destroyPadSwapchainNextAcquire.test_and_set(); + m_destroyPadSwapchainNextAcquire.wait(true); } bool VulkanRenderer::IsPadWindowActive() @@ -1074,6 +1070,36 @@ RendererShader* VulkanRenderer::shader_create(RendererShader::ShaderType type, u return new RendererShaderVk(type, baseHash, auxHash, isGameShader, isGfxPackShader, source); } +VulkanRenderer::QueueFamilyIndices VulkanRenderer::FindQueueFamilies(VkSurfaceKHR surface, VkPhysicalDevice device) +{ + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + QueueFamilyIndices indices; + for (int i = 0; i < (int)queueFamilies.size(); ++i) + { + const auto& queueFamily = queueFamilies[i]; + if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) + indices.graphicsFamily = i; + + VkBool32 presentSupport = false; + const VkResult result = vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + if (result != VK_SUCCESS) + throw std::runtime_error(fmt::format("Error while attempting to check if a surface supports presentation: {}", result)); + + if (queueFamily.queueCount > 0 && presentSupport) + indices.presentFamily = i; + + if (indices.IsComplete()) + break; + } + + return indices; +} + bool VulkanRenderer::CheckDeviceExtensionSupport(const VkPhysicalDevice device, FeatureControl& info) { std::vector availableDeviceExtensions; @@ -1215,7 +1241,7 @@ std::vector VulkanRenderer::CheckInstanceExtensionSupport(FeatureCo bool VulkanRenderer::IsDeviceSuitable(VkSurfaceKHR surface, const VkPhysicalDevice& device) { - if (!SwapchainInfoVk::FindQueueFamilies(surface, device).IsComplete()) + if (!FindQueueFamilies(surface, device).IsComplete()) return false; // check API version (using Vulkan 1.0 way of querying properties) @@ -2557,11 +2583,11 @@ bool VulkanRenderer::AcquireNextSwapchainImage(bool mainWindow) if(!IsSwapchainInfoValid(mainWindow)) return false; - if(!mainWindow && m_destroyPadSwapchainNextAcquire) + if(!mainWindow && m_destroyPadSwapchainNextAcquire.test()) { RecreateSwapchain(mainWindow, true); - m_destroyPadSwapchainNextAcquire = false; - m_padCloseReadySemaphore.notify(); + m_destroyPadSwapchainNextAcquire.clear(); + m_destroyPadSwapchainNextAcquire.notify_all(); return false; } @@ -2605,7 +2631,7 @@ void VulkanRenderer::RecreateSwapchain(bool mainWindow, bool skipCreate) chainInfo.m_desiredExtent = size; if(!skipCreate) { - chainInfo.Create(m_physicalDevice, m_logicalDevice); + chainInfo.Create(); } if (mainWindow) @@ -2675,7 +2701,7 @@ void VulkanRenderer::SwapBuffer(bool mainWindow) VkPresentInfoKHR presentInfo = {}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.swapchainCount = 1; - presentInfo.pSwapchains = &chainInfo.swapchain; + presentInfo.pSwapchains = &chainInfo.m_swapchain; presentInfo.pImageIndices = &chainInfo.swapchainImageIndex; // wait on command buffer semaphore presentInfo.waitSemaphoreCount = 1; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index e0a4c75b..2491d052 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -127,7 +127,6 @@ class VulkanRenderer : public Renderer friend class PipelineCompiler; using VSync = SwapchainInfoVk::VSync; - using QueueFamilyIndices = SwapchainInfoVk::QueueFamilyIndices; static const inline int UNIFORMVAR_RINGBUFFER_SIZE = 1024 * 1024 * 16; // 16MB @@ -414,14 +413,25 @@ private: }m_state; std::unique_ptr m_mainSwapchainInfo{}, m_padSwapchainInfo{}; - Semaphore m_padCloseReadySemaphore; - bool m_destroyPadSwapchainNextAcquire = false; + std::atomic_flag m_destroyPadSwapchainNextAcquire{}; bool IsSwapchainInfoValid(bool mainWindow) const; VkRenderPass m_imguiRenderPass = VK_NULL_HANDLE; VkDescriptorPool m_descriptorPool; + public: + struct QueueFamilyIndices + { + int32_t graphicsFamily = -1; + int32_t presentFamily = -1; + + bool IsComplete() const { return graphicsFamily >= 0 && presentFamily >= 0; } + }; + static QueueFamilyIndices FindQueueFamilies(VkSurfaceKHR surface, VkPhysicalDevice device); + + private: + struct FeatureControl { struct diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp index 8ce5de07..809d7be4 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp @@ -1159,9 +1159,11 @@ namespace coreinit #include std::vector g_schedulerThreadIds; + std::mutex g_schedulerThreadIdsLock; std::vector& OSGetSchedulerThreadIds() { + std::lock_guard schedulerThreadIdsLockGuard(g_schedulerThreadIdsLock); return g_schedulerThreadIds; } #endif @@ -1183,7 +1185,10 @@ namespace coreinit } pid_t tid = gettid(); - g_schedulerThreadIds.emplace_back(tid); + { + std::lock_guard schedulerThreadIdsLockGuard(g_schedulerThreadIdsLock); + g_schedulerThreadIds.emplace_back(tid); + } #endif t_schedulerFiber = Fiber::PrepareCurrentThread(); @@ -1238,7 +1243,10 @@ namespace coreinit sSchedulerThreads.clear(); g_schedulerThreadHandles.clear(); #if BOOST_OS_LINUX - g_schedulerThreadIds.clear(); + { + std::lock_guard schedulerThreadIdsLockGuard(g_schedulerThreadIdsLock); + g_schedulerThreadIds.clear(); + } #endif // clean up all fibers for (auto& it : g_idleLoopFiber) diff --git a/src/Cafe/TitleList/ParsedMetaXml.h b/src/Cafe/TitleList/ParsedMetaXml.h index 7537d603..f1aee37e 100644 --- a/src/Cafe/TitleList/ParsedMetaXml.h +++ b/src/Cafe/TitleList/ParsedMetaXml.h @@ -90,8 +90,11 @@ struct ParsedMetaXml else if (boost::starts_with(name, "longname_")) { const sint32 index = GetLanguageIndex(name.substr(std::size("longname_") - 1)); - if (index != -1) - parsedMetaXml->m_long_name[index] = child.text().as_string(); + if (index != -1){ + std::string longname = child.text().as_string(); + std::replace_if(longname.begin(), longname.end(), [](char c) { return c == '\r' || c == '\n';}, ' '); + parsedMetaXml->m_long_name[index] = longname; + } } else if (boost::starts_with(name, L"shortname_")) { diff --git a/src/Cafe/TitleList/TitleInfo.cpp b/src/Cafe/TitleList/TitleInfo.cpp index ff457575..d23e1d0a 100644 --- a/src/Cafe/TitleList/TitleInfo.cpp +++ b/src/Cafe/TitleList/TitleInfo.cpp @@ -637,9 +637,9 @@ std::string TitleInfo::GetMetaTitleName() const if (m_parsedMetaXml) { std::string titleNameCfgLanguage; - titleNameCfgLanguage = m_parsedMetaXml->GetShortName(GetConfig().console_language); + titleNameCfgLanguage = m_parsedMetaXml->GetLongName(GetConfig().console_language); if (titleNameCfgLanguage.empty()) //Get English Title - titleNameCfgLanguage = m_parsedMetaXml->GetShortName(CafeConsoleLanguage::EN); + titleNameCfgLanguage = m_parsedMetaXml->GetLongName(CafeConsoleLanguage::EN); if (titleNameCfgLanguage.empty()) //Unknown Title titleNameCfgLanguage = "Unknown Title"; return titleNameCfgLanguage; diff --git a/src/config/CemuConfig.cpp b/src/config/CemuConfig.cpp index a47e8737..8e98a8bf 100644 --- a/src/config/CemuConfig.cpp +++ b/src/config/CemuConfig.cpp @@ -85,6 +85,8 @@ void CemuConfig::Load(XMLConfigParser& parser) game_list_style = gamelist.get("style", 0); game_list_column_order = gamelist.get("order", ""); + show_icon_column = parser.get("show_icon_column", true); + // return default width if value in config file out of range auto loadColumnSize = [&gamelist] (const char *name, uint32 defaultWidth) { @@ -387,6 +389,7 @@ void CemuConfig::Save(XMLConfigParser& parser) psize.set("x", pad_size.x); psize.set("y", pad_size.y); config.set("pad_maximized", pad_maximized); + config.set("show_icon_column" , show_icon_column); auto gamelist = config.set("GameList"); gamelist.set("style", game_list_style); diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index 611be008..b2107586 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -419,6 +419,8 @@ struct CemuConfig ConfigValue did_show_graphic_pack_download{false}; ConfigValue did_show_macos_disclaimer{false}; + ConfigValue show_icon_column{ false }; + int game_list_style = 0; std::string game_list_column_order; struct diff --git a/src/gui/CemuApp.cpp b/src/gui/CemuApp.cpp index fde4bcc0..7f11d4c6 100644 --- a/src/gui/CemuApp.cpp +++ b/src/gui/CemuApp.cpp @@ -59,7 +59,12 @@ bool CemuApp::OnInit() fs::path user_data_path, config_path, cache_path, data_path; auto standardPaths = wxStandardPaths::Get(); fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath())); - +#if BOOST_OS_LINUX + // GetExecutablePath returns the AppImage's temporary mount location + wxString appImagePath; + if (wxGetEnv(("APPIMAGE"), &appImagePath)) + exePath = wxHelper::MakeFSPath(appImagePath); +#endif // Try a portable path first, if it exists. user_data_path = config_path = cache_path = data_path = exePath.parent_path() / "portable"; #if BOOST_OS_MACOS diff --git a/src/gui/DownloadGraphicPacksWindow.cpp b/src/gui/DownloadGraphicPacksWindow.cpp index 8189b50a..03f102d2 100644 --- a/src/gui/DownloadGraphicPacksWindow.cpp +++ b/src/gui/DownloadGraphicPacksWindow.cpp @@ -110,14 +110,6 @@ void deleteDownloadedGraphicPacks() void DownloadGraphicPacksWindow::UpdateThread() { - if (CafeSystem::IsTitleRunning()) - { - wxMessageBox(_("Graphic packs cannot be updated while a game is running."), _("Graphic packs"), 5, this->GetParent()); - // cancel update - m_threadState = ThreadFinished; - return; - } - // get github url std::string githubAPIUrl; curlDownloadFileState_t tempDownloadState; @@ -326,8 +318,6 @@ DownloadGraphicPacksWindow::DownloadGraphicPacksWindow(wxWindow* parent) m_downloadState = std::make_unique(); - - m_thread = std::thread(&DownloadGraphicPacksWindow::UpdateThread, this); } DownloadGraphicPacksWindow::~DownloadGraphicPacksWindow() @@ -344,6 +334,12 @@ const std::string& DownloadGraphicPacksWindow::GetException() const int DownloadGraphicPacksWindow::ShowModal() { + if(CafeSystem::IsTitleRunning()) + { + wxMessageBox(_("Graphic packs cannot be updated while a game is running."), _("Graphic packs"), 5, this->GetParent()); + return wxID_CANCEL; + } + m_thread = std::thread(&DownloadGraphicPacksWindow::UpdateThread, this); wxDialog::ShowModal(); return m_threadState == ThreadCanceled ? wxID_CANCEL : wxID_OK; } diff --git a/src/gui/GraphicPacksWindow2.cpp b/src/gui/GraphicPacksWindow2.cpp index 78b344d5..29f4b865 100644 --- a/src/gui/GraphicPacksWindow2.cpp +++ b/src/gui/GraphicPacksWindow2.cpp @@ -319,6 +319,7 @@ GraphicPacksWindow2::GraphicPacksWindow2(wxWindow* parent, uint64_t title_id_fil SetSizer(main_sizer); + UpdateTitleRunning(CafeSystem::IsTitleRunning()); FillGraphicPackList(); } @@ -676,6 +677,15 @@ void GraphicPacksWindow2::OnInstalledGamesChanged(wxCommandEvent& event) event.Skip(); } +void GraphicPacksWindow2::UpdateTitleRunning(bool running) +{ + m_update_graphicPacks->Enable(!running); + if(running) + m_update_graphicPacks->SetToolTip(_("Graphic packs cannot be updated while a game is running.")); + else + m_update_graphicPacks->SetToolTip(nullptr); +} + void GraphicPacksWindow2::ReloadPack(const GraphicPackPtr& graphic_pack) const { if (graphic_pack->HasShaders() || graphic_pack->HasPatches() || graphic_pack->HasCustomVSyncFrequency()) diff --git a/src/gui/GraphicPacksWindow2.h b/src/gui/GraphicPacksWindow2.h index a068f2b6..a79c62c1 100644 --- a/src/gui/GraphicPacksWindow2.h +++ b/src/gui/GraphicPacksWindow2.h @@ -21,6 +21,7 @@ public: ~GraphicPacksWindow2(); static void RefreshGraphicPacks(); + void UpdateTitleRunning(bool running); private: std::string m_filter; diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 311ddfb7..023918bd 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -625,6 +625,7 @@ bool MainWindow::FileLoad(const fs::path launchPath, wxLaunchGameEvent::INITIATE CreateCanvas(); CafeSystem::LaunchForegroundTitle(); RecreateMenu(); + UpdateChildWindowTitleRunningState(); return true; } @@ -683,6 +684,7 @@ void MainWindow::OnFileMenu(wxCommandEvent& event) RecreateMenu(); CreateGameListAndStatusBar(); DoLayout(); + UpdateChildWindowTitleRunningState(); } } @@ -2320,6 +2322,14 @@ void MainWindow::RecreateMenu() SetMenuVisible(false); } +void MainWindow::UpdateChildWindowTitleRunningState() +{ + const bool running = CafeSystem::IsTitleRunning(); + + if(m_graphic_pack_window) + m_graphic_pack_window->UpdateTitleRunning(running); +} + void MainWindow::RestoreSettingsAfterGameExited() { RecreateMenu(); diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 88d2a1d3..7191df12 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -21,6 +21,7 @@ class DebuggerWindow2; struct GameEntry; class DiscordPresence; class TitleManager; +class GraphicPacksWindow2; class wxLaunchGameEvent; wxDECLARE_EVENT(wxEVT_LAUNCH_GAME, wxLaunchGameEvent); @@ -146,6 +147,7 @@ public: private: void RecreateMenu(); + void UpdateChildWindowTitleRunningState(); static wxString GetInitialWindowTitle(); void ShowGettingStartedDialog(); @@ -163,7 +165,7 @@ private: MemorySearcherTool* m_toolWindow = nullptr; TitleManager* m_title_manager = nullptr; PadViewFrame* m_padView = nullptr; - wxWindow* m_graphic_pack_window = nullptr; + GraphicPacksWindow2* m_graphic_pack_window = nullptr; wxTimer* m_timer; wxPoint m_mouse_position{}; diff --git a/src/gui/components/wxGameList.cpp b/src/gui/components/wxGameList.cpp index 8e8f3c4d..d7c9a4f8 100644 --- a/src/gui/components/wxGameList.cpp +++ b/src/gui/components/wxGameList.cpp @@ -19,7 +19,6 @@ #include #include - #include #include @@ -89,7 +88,10 @@ wxGameList::wxGameList(wxWindow* parent, wxWindowID id) const auto& config = GetConfig(); InsertColumn(ColumnHiddenName, "", wxLIST_FORMAT_LEFT, 0); - InsertColumn(ColumnIcon, "", wxLIST_FORMAT_LEFT, kListIconWidth); + if(config.show_icon_column) + InsertColumn(ColumnIcon, _("Icon"), wxLIST_FORMAT_LEFT, kListIconWidth); + else + InsertColumn(ColumnIcon, _("Icon"), wxLIST_FORMAT_LEFT, 0); InsertColumn(ColumnName, _("Game"), wxLIST_FORMAT_LEFT, config.column_width.name); InsertColumn(ColumnVersion, _("Version"), wxLIST_FORMAT_RIGHT, config.column_width.version); InsertColumn(ColumnDLC, _("DLC"), wxLIST_FORMAT_RIGHT, config.column_width.dlc); @@ -526,7 +528,6 @@ void wxGameList::OnKeyDown(wxListEvent& event) } } - enum ContextMenuEntries { kContextMenuRefreshGames = wxID_HIGHEST + 1, @@ -732,7 +733,7 @@ void wxGameList::OnContextMenuSelected(wxCommandEvent& event) { if (wxTheClipboard->Open()) { - wxTheClipboard->SetData(new wxTextDataObject(gameInfo.GetTitleName())); + wxTheClipboard->SetData(new wxTextDataObject(wxString::FromUTF8(gameInfo.GetTitleName()))); wxTheClipboard->Close(); } break; @@ -796,6 +797,7 @@ void wxGameList::OnColumnRightClick(wxListEvent& event) ResetWidth = wxID_HIGHEST + 1, ResetOrder, + ShowIcon, ShowName, ShowVersion, ShowDlc, @@ -812,6 +814,7 @@ void wxGameList::OnColumnRightClick(wxListEvent& event) menu.Append(ResetOrder, _("Reset &order")) ; menu.AppendSeparator(); + menu.AppendCheckItem(ShowIcon, _("Show &icon"))->Check(GetColumnWidth(ColumnIcon) > 0); menu.AppendCheckItem(ShowName, _("Show &name"))->Check(GetColumnWidth(ColumnName) > 0); menu.AppendCheckItem(ShowVersion, _("Show &version"))->Check(GetColumnWidth(ColumnVersion) > 0); menu.AppendCheckItem(ShowDlc, _("Show &dlc"))->Check(GetColumnWidth(ColumnDLC) > 0); @@ -830,6 +833,9 @@ void wxGameList::OnColumnRightClick(wxListEvent& event) switch (event.GetId()) { + case ShowIcon: + config.show_icon_column = menu->IsChecked(ShowIcon); + break; case ShowName: config.column_width.name = menu->IsChecked(ShowName) ? DefaultColumnSize::name : 0; break; @@ -909,7 +915,10 @@ void wxGameList::ApplyGameListColumnWidths() { const auto& config = GetConfig(); wxWindowUpdateLocker lock(this); - SetColumnWidth(ColumnIcon, kListIconWidth); + if(config.show_icon_column) + SetColumnWidth(ColumnIcon, kListIconWidth); + else + SetColumnWidth(ColumnIcon, 0); SetColumnWidth(ColumnName, config.column_width.name); SetColumnWidth(ColumnVersion, config.column_width.version); SetColumnWidth(ColumnDLC, config.column_width.dlc); @@ -1276,129 +1285,169 @@ void wxGameList::DeleteCachedStrings() m_name_cache.clear(); } -#if BOOST_OS_LINUX || BOOST_OS_WINDOWS -void wxGameList::CreateShortcut(GameInfo2& gameInfo) { - const auto title_id = gameInfo.GetBaseTitleId(); - const auto title_name = gameInfo.GetTitleName(); - auto exe_path = ActiveSettings::GetExecutablePath(); - const char *flatpak_id = getenv("FLATPAK_ID"); - - // GetExecutablePath returns the AppImage's temporary mount location, instead of its actual path - wxString appimage_path; - if (wxGetEnv(("APPIMAGE"), &appimage_path)) { - exe_path = appimage_path.utf8_string(); - } - #if BOOST_OS_LINUX - const wxString desktop_entry_name = wxString::Format("%s.desktop", title_name); - wxFileDialog entry_dialog(this, _("Choose desktop entry location"), "~/.local/share/applications", desktop_entry_name, - "Desktop file (*.desktop)|*.desktop", wxFD_SAVE | wxFD_CHANGE_DIR | wxFD_OVERWRITE_PROMPT); -#elif BOOST_OS_WINDOWS - // Get '%APPDATA%\Microsoft\Windows\Start Menu\Programs' path - PWSTR user_shortcut_folder; - SHGetKnownFolderPath(FOLDERID_Programs, 0, NULL, &user_shortcut_folder); - const wxString shortcut_name = wxString::Format("%s.lnk", title_name); - wxFileDialog entry_dialog(this, _("Choose shortcut location"), _pathToUtf8(user_shortcut_folder), shortcut_name, - "Shortcut (*.lnk)|*.lnk", wxFD_SAVE | wxFD_CHANGE_DIR | wxFD_OVERWRITE_PROMPT); -#endif - const auto result = entry_dialog.ShowModal(); - if (result == wxID_CANCEL) - return; - const auto output_path = entry_dialog.GetPath(); +void wxGameList::CreateShortcut(GameInfo2& gameInfo) +{ + const auto titleId = gameInfo.GetBaseTitleId(); + const auto titleName = wxString::FromUTF8(gameInfo.GetTitleName()); + auto exePath = ActiveSettings::GetExecutablePath(); + const char* flatpakId = getenv("FLATPAK_ID"); -#if BOOST_OS_LINUX - std::optional icon_path; - // Obtain and convert icon - { - m_icon_cache_mtx.lock(); - const auto icon_iter = m_icon_cache.find(title_id); - const auto result_index = (icon_iter != m_icon_cache.cend()) ? std::optional(icon_iter->second.first) : std::nullopt; - m_icon_cache_mtx.unlock(); + const wxString desktopEntryName = wxString::Format("%s.desktop", titleName); + wxFileDialog entryDialog(this, _("Choose desktop entry location"), "~/.local/share/applications", desktopEntryName, + "Desktop file (*.desktop)|*.desktop", wxFD_SAVE | wxFD_CHANGE_DIR | wxFD_OVERWRITE_PROMPT); + const auto result = entryDialog.ShowModal(); + if (result == wxID_CANCEL) + return; + const auto output_path = entryDialog.GetPath(); - // In most cases it should find it - if (!result_index){ - wxMessageBox(_("Icon is yet to load, so will not be used by the shortcut"), _("Warning"), wxOK | wxCENTRE | wxICON_WARNING); - } - else { - const fs::path out_icon_dir = ActiveSettings::GetUserDataPath("icons"); + std::optional iconPath; + // Obtain and convert icon + [&]() + { + int iconIndex, smallIconIndex; - if (!fs::exists(out_icon_dir) && !fs::create_directories(out_icon_dir)){ - wxMessageBox(_("Cannot access the icon directory, the shortcut will have no icon"), _("Warning"), wxOK | wxCENTRE | wxICON_WARNING); - } - else { - icon_path = out_icon_dir / fmt::format("{:016x}.png", gameInfo.GetBaseTitleId()); + if (!QueryIconForTitle(titleId, iconIndex, smallIconIndex)) + { + cemuLog_log(LogType::Force, "Icon hasn't loaded"); + return; + } + const fs::path outIconDir = ActiveSettings::GetUserDataPath("icons"); - auto image = m_image_list->GetIcon(result_index.value()).ConvertToImage(); + if (!fs::exists(outIconDir) && !fs::create_directories(outIconDir)) + { + cemuLog_log(LogType::Force, "Failed to create icon directory"); + return; + } - wxFileOutputStream png_file(_pathToUtf8(icon_path.value())); - wxPNGHandler pngHandler; - if (!pngHandler.SaveFile(&image, png_file, false)) { - icon_path = std::nullopt; - wxMessageBox(_("The icon was unable to be saved, the shortcut will have no icon"), _("Warning"), wxOK | wxCENTRE | wxICON_WARNING); - } - } - } - } + iconPath = outIconDir / fmt::format("{:016x}.png", gameInfo.GetBaseTitleId()); + wxFileOutputStream pngFileStream(_pathToUtf8(iconPath.value())); - std::string desktop_exec_entry; - if (flatpak_id) - desktop_exec_entry = fmt::format("/usr/bin/flatpak run {0} --title-id {1:016x}", flatpak_id, title_id); - else - desktop_exec_entry = fmt::format("{0:?} --title-id {1:016x}", _pathToUtf8(exe_path), title_id); + auto image = m_image_list->GetIcon(iconIndex).ConvertToImage(); + wxPNGHandler pngHandler; + if (!pngHandler.SaveFile(&image, pngFileStream, false)) + { + iconPath = std::nullopt; + cemuLog_log(LogType::Force, "Icon failed to save"); + } + }(); - // 'Icon' accepts spaces in file name, does not accept quoted file paths - // 'Exec' does not accept non-escaped spaces, and can accept quoted file paths - auto desktop_entry_string = - fmt::format("[Desktop Entry]\n" - "Name={0}\n" - "Comment=Play {0} on Cemu\n" - "Exec={1}\n" - "Icon={2}\n" - "Terminal=false\n" - "Type=Application\n" - "Categories=Game;\n", - title_name, - desktop_exec_entry, - _pathToUtf8(icon_path.value_or(""))); + std::string desktopExecEntry = flatpakId ? fmt::format("/usr/bin/flatpak run {0} --title-id {1:016x}", flatpakId, titleId) + : fmt::format("{0:?} --title-id {1:016x}", _pathToUtf8(exePath), titleId); - if (flatpak_id) - desktop_entry_string += fmt::format("X-Flatpak={}\n", flatpak_id); + // 'Icon' accepts spaces in file name, does not accept quoted file paths + // 'Exec' does not accept non-escaped spaces, and can accept quoted file paths + auto desktopEntryString = fmt::format( + "[Desktop Entry]\n" + "Name={0}\n" + "Comment=Play {0} on Cemu\n" + "Exec={1}\n" + "Icon={2}\n" + "Terminal=false\n" + "Type=Application\n" + "Categories=Game;\n", + titleName.utf8_string(), + desktopExecEntry, + _pathToUtf8(iconPath.value_or(""))); - std::ofstream output_stream(output_path); - if (!output_stream.good()) - { - auto errorMsg = formatWxString(_("Failed to save desktop entry to {}"), output_path.utf8_string()); - wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); - return; - } - output_stream << desktop_entry_string; + if (flatpakId) + desktopEntryString += fmt::format("X-Flatpak={}\n", flatpakId); -#elif BOOST_OS_WINDOWS - IShellLinkW *shell_link; - HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink, reinterpret_cast(&shell_link)); - if (SUCCEEDED(hres)) - { - const auto description = wxString::Format("Play %s on Cemu", title_name); - const auto args = wxString::Format("-t %016llx", title_id); - - shell_link->SetPath(exe_path.wstring().c_str()); - shell_link->SetDescription(description.wc_str()); - shell_link->SetArguments(args.wc_str()); - shell_link->SetWorkingDirectory(exe_path.parent_path().wstring().c_str()); - // Use icon from Cemu exe for now since we can't embed icons into the shortcut - // in the future we could convert and store icons in AppData or ProgramData - shell_link->SetIconLocation(exe_path.wstring().c_str(), 0); - - IPersistFile *shell_link_file; - // save the shortcut - hres = shell_link->QueryInterface(IID_IPersistFile, reinterpret_cast(&shell_link_file)); - if (SUCCEEDED(hres)) - { - hres = shell_link_file->Save(output_path.wc_str(), TRUE); - shell_link_file->Release(); - } - shell_link->Release(); - } -#endif + std::ofstream outputStream(output_path.utf8_string()); + if (!outputStream.good()) + { + auto errorMsg = formatWxString(_("Failed to save desktop entry to {}"), output_path.utf8_string()); + wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + return; + } + outputStream << desktopEntryString; } -#endif +#elif BOOST_OS_WINDOWS +void wxGameList::CreateShortcut(GameInfo2& gameInfo) +{ + const auto titleId = gameInfo.GetBaseTitleId(); + const auto titleName = wxString::FromUTF8(gameInfo.GetTitleName()); + auto exePath = ActiveSettings::GetExecutablePath(); + + // Get '%APPDATA%\Microsoft\Windows\Start Menu\Programs' path + PWSTR userShortcutFolder; + SHGetKnownFolderPath(FOLDERID_Programs, 0, NULL, &userShortcutFolder); + const wxString shortcutName = wxString::Format("%s.lnk", titleName); + wxFileDialog shortcutDialog(this, _("Choose shortcut location"), _pathToUtf8(userShortcutFolder), shortcutName, + "Shortcut (*.lnk)|*.lnk", wxFD_SAVE | wxFD_CHANGE_DIR | wxFD_OVERWRITE_PROMPT); + + const auto result = shortcutDialog.ShowModal(); + if (result == wxID_CANCEL) + return; + const auto outputPath = shortcutDialog.GetPath(); + + std::optional icon_path = std::nullopt; + [&]() + { + int iconIdx; + int smallIconIdx; + if (!QueryIconForTitle(titleId, iconIdx, smallIconIdx)) + { + cemuLog_log(LogType::Force, "Icon hasn't loaded"); + return; + } + const auto icon = m_image_list->GetIcon(iconIdx); + PWSTR localAppData; + const auto hres = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &localAppData); + wxBitmap bitmap{}; + auto folder = fs::path(localAppData) / "Cemu" / "icons"; + if (!SUCCEEDED(hres) || (!fs::exists(folder) && !fs::create_directories(folder))) + { + cemuLog_log(LogType::Force, "Failed to create icon directory"); + return; + } + if (!bitmap.CopyFromIcon(icon)) + { + cemuLog_log(LogType::Force, "Failed to copy icon"); + return; + } + + icon_path = folder / fmt::format("{:016x}.ico", titleId); + auto stream = wxFileOutputStream(_pathToUtf8(*icon_path)); + auto image = bitmap.ConvertToImage(); + wxICOHandler icohandler{}; + if (!icohandler.SaveFile(&image, stream, false)) + { + icon_path = std::nullopt; + cemuLog_log(LogType::Force, "Icon failed to save"); + } + }(); + + IShellLinkW* shellLink; + HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink, reinterpret_cast(&shellLink)); + if (SUCCEEDED(hres)) + { + const auto description = wxString::Format("Play %s on Cemu", titleName); + const auto args = wxString::Format("-t %016llx", titleId); + + shellLink->SetPath(exePath.wstring().c_str()); + shellLink->SetDescription(description.wc_str()); + shellLink->SetArguments(args.wc_str()); + shellLink->SetWorkingDirectory(exePath.parent_path().wstring().c_str()); + + if (icon_path) + shellLink->SetIconLocation(icon_path->wstring().c_str(), 0); + else + shellLink->SetIconLocation(exePath.wstring().c_str(), 0); + + IPersistFile* shellLinkFile; + // save the shortcut + hres = shellLink->QueryInterface(IID_IPersistFile, reinterpret_cast(&shellLinkFile)); + if (SUCCEEDED(hres)) + { + hres = shellLinkFile->Save(outputPath.wc_str(), TRUE); + shellLinkFile->Release(); + } + shellLink->Release(); + } + if (!SUCCEEDED(hres)) { + auto errorMsg = formatWxString(_("Failed to save shortcut to {}"), outputPath); + wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + } +} +#endif \ No newline at end of file diff --git a/src/gui/input/InputSettings2.cpp b/src/gui/input/InputSettings2.cpp index 58c168a3..72bf4f7d 100644 --- a/src/gui/input/InputSettings2.cpp +++ b/src/gui/input/InputSettings2.cpp @@ -295,32 +295,6 @@ wxWindow* InputSettings2::initialize_page(size_t index) return page; } -std::pair InputSettings2::get_emulated_controller_types() const -{ - size_t vpad = 0, wpad = 0; - for(size_t i = 0; i < m_notebook->GetPageCount(); ++i) - { - auto* page = m_notebook->GetPage(i); - auto* page_data = (wxControllerPageData*)page->GetClientObject(); - if (!page_data) - continue; - - if (!page_data->ref().m_controller) // = disabled - continue; - - const auto api_type = page_data->ref().m_controller->type(); - if (api_type) - continue; - - if (api_type == EmulatedController::VPAD) - ++vpad; - else - ++wpad; - } - - return std::make_pair(vpad, wpad); -} - std::shared_ptr InputSettings2::get_active_controller() const { auto& page_data = get_current_page_data(); @@ -771,14 +745,16 @@ void InputSettings2::on_emulated_controller_dropdown(wxCommandEvent& event) wxWindowUpdateLocker lock(emulated_controllers); bool is_gamepad_selected = false; + bool is_wpad_selected = false; const auto selected = emulated_controllers->GetSelection(); const auto selected_value = emulated_controllers->GetStringSelection(); if(selected != wxNOT_FOUND) { is_gamepad_selected = selected_value == to_wxString(EmulatedController::type_to_string(EmulatedController::Type::VPAD)); + is_wpad_selected = !is_gamepad_selected && selected != 0; } - const auto [vpad_count, wpad_count] = get_emulated_controller_types(); + const auto [vpad_count, wpad_count] = InputManager::instance().get_controller_count(); emulated_controllers->Clear(); emulated_controllers->AppendString(_("Disabled")); @@ -786,7 +762,7 @@ void InputSettings2::on_emulated_controller_dropdown(wxCommandEvent& event) if (vpad_count < InputManager::kMaxVPADControllers || is_gamepad_selected) emulated_controllers->Append(to_wxString(EmulatedController::type_to_string(EmulatedController::Type::VPAD))); - if (wpad_count < InputManager::kMaxWPADControllers || !is_gamepad_selected) + if (wpad_count < InputManager::kMaxWPADControllers || is_wpad_selected) { emulated_controllers->AppendString(to_wxString(EmulatedController::type_to_string(EmulatedController::Type::Pro))); emulated_controllers->AppendString(to_wxString(EmulatedController::type_to_string(EmulatedController::Type::Classic))); diff --git a/src/gui/input/InputSettings2.h b/src/gui/input/InputSettings2.h index 01313653..1a3c8bb1 100644 --- a/src/gui/input/InputSettings2.h +++ b/src/gui/input/InputSettings2.h @@ -27,9 +27,6 @@ private: wxWindow* initialize_page(size_t index); - // count active controllers - std::pair get_emulated_controller_types() const; - // currently selected controller from active tab std::shared_ptr get_active_controller() const; diff --git a/src/input/motion/MotionSample.h b/src/input/motion/MotionSample.h index bb47f784..0697711b 100644 --- a/src/input/motion/MotionSample.h +++ b/src/input/motion/MotionSample.h @@ -258,48 +258,48 @@ DRC flat on table, screen facing up. Top pointing away (away from person, pointi 0.03 0.99 -0.13 0.01 0.13 0.99 -Turned 45° to the right: +Turned 45° to the right: 0.71 -0.03 0.71 0.12 0.99 -0.08 -0.70 0.14 0.70 -Turned 45° to the right (top of GamePad pointing right now): +Turned 45° to the right (top of GamePad pointing right now): 0.08 -0.03 1.00 -> Z points towards person 0.15 0.99 0.01 -0.99 0.15 0.09 -> DRC Z-Axis now points towards X-minus -Turned 90° to the right (top of gamepad now pointing towards holder, away from monitor): +Turned 90° to the right (top of gamepad now pointing towards holder, away from monitor): -1.00 -0.01 0.06 0.00 0.99 0.15 -0.06 0.15 -0.99 -Turned 90° to the right (pointing left): +Turned 90° to the right (pointing left): -0.17 -0.01 -0.99 -0.13 0.99 0.02 0.98 0.13 -0.17 -After another 90° we end up in the initial position: +After another 90° we end up in the initial position: 0.99 -0.03 -0.11 0.01 0.99 -0.13 0.12 0.12 0.99 ------ -From initial position, lean the GamePad on its left side. 45° up. So the screen is pointing to the top left +From initial position, lean the GamePad on its left side. 45° up. So the screen is pointing to the top left 0.66 -0.75 -0.03 0.74 0.66 -0.11 0.10 0.05 0.99 -Further 45°, GamePad now on its left, screen pointing left: +Further 45°, GamePad now on its left, screen pointing left: -0.03 -1.00 -0.00 0.99 -0.03 -0.15 0.15 -0.01 0.99 -From initial position, lean the GamePad on its right side. 45° up. So the screen is pointing to the top right +From initial position, lean the GamePad on its right side. 45° up. So the screen is pointing to the top right 0.75 0.65 -0.11 -0.65 0.76 0.07 0.12 0.02 0.99 -From initial position, tilt the GamePad up 90° (bottom side remains in touch with surface): +From initial position, tilt the GamePad up 90° (bottom side remains in touch with surface): 0.99 -0.05 -0.10 -0.10 0.01 -0.99 0.05 1.00 0.01 @@ -309,7 +309,7 @@ From initial position, stand the GamePad on its top side: 0.09 -0.01 1.00 -0.01 -1.00 -0.01 -Rotate GamePad 180° around x axis, so it now lies on its screen (top of GamePad pointing to holder): +Rotate GamePad 180° around x axis, so it now lies on its screen (top of GamePad pointing to holder): 0.99 -0.03 -0.15 -0.04 -1.00 -0.08 -0.15 0.09 -0.99 diff --git a/src/resource/MacOSXBundleInfo.plist.in b/src/resource/MacOSXBundleInfo.plist.in index 98064735..ccd1c922 100644 --- a/src/resource/MacOSXBundleInfo.plist.in +++ b/src/resource/MacOSXBundleInfo.plist.in @@ -30,6 +30,28 @@ ${MACOSX_BUNDLE_CATEGORY} LSMinimumSystemVersion ${MACOSX_MINIMUM_SYSTEM_VERSION} + CFBundleLocalizations + + ca + de + en + es + fr + he + hu + it + ja + ko + nb + nl + pl + pt + ru + sv + tr + uk + zh + CFBundleDocumentTypes