mirror of
https://github.com/cemu-project/Cemu.git
synced 2025-07-11 01:08:30 +12:00
Merge branch 'main' into loadaudio
This commit is contained in:
commit
925e853980
29 changed files with 416 additions and 287 deletions
|
@ -280,6 +280,10 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
|
||||||
m_enabled = m_default_enabled;
|
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");
|
auto option_vendorFilter = rules.FindOption("vendorFilter");
|
||||||
if (option_vendorFilter)
|
if (option_vendorFilter)
|
||||||
{
|
{
|
||||||
|
|
|
@ -113,6 +113,7 @@ public:
|
||||||
const std::string& GetVirtualPath() const { return m_virtualPath; } // returns the path in the gfx tree hierarchy
|
const std::string& GetVirtualPath() const { return m_virtualPath; } // returns the path in the gfx tree hierarchy
|
||||||
const std::string& GetDescription() const { return m_description; }
|
const std::string& GetDescription() const { return m_description; }
|
||||||
bool IsDefaultEnabled() const { return m_default_enabled; }
|
bool IsDefaultEnabled() const { return m_default_enabled; }
|
||||||
|
bool AllowRendertargetSizeOptimization() const { return m_allowRendertargetSizeOptimization; }
|
||||||
|
|
||||||
void SetEnabled(bool state) { m_enabled = state; }
|
void SetEnabled(bool state) { m_enabled = state; }
|
||||||
|
|
||||||
|
@ -217,6 +218,8 @@ private:
|
||||||
|
|
||||||
bool m_default_enabled = false;
|
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
|
// filter
|
||||||
std::optional<RendererAPI> m_renderer_api;
|
std::optional<RendererAPI> m_renderer_api;
|
||||||
std::optional<GfxVendor> m_gfx_vendor;
|
std::optional<GfxVendor> m_gfx_vendor;
|
||||||
|
|
|
@ -45,12 +45,17 @@ public:
|
||||||
|
|
||||||
static void ClearRange(MPTR address, uint32 length)
|
static void ClearRange(MPTR address, uint32 length)
|
||||||
{
|
{
|
||||||
|
if (length == 0)
|
||||||
|
return;
|
||||||
s_lock.lock();
|
s_lock.lock();
|
||||||
while (length > 0)
|
for (;;)
|
||||||
{
|
{
|
||||||
auto itr = s_typeStorage.find(address);
|
auto itr = s_typeStorage.find(address);
|
||||||
if (itr != s_typeStorage.end())
|
if (itr != s_typeStorage.end())
|
||||||
s_typeStorage.erase(itr);
|
s_typeStorage.erase(itr);
|
||||||
|
|
||||||
|
if (length <= 4)
|
||||||
|
break;
|
||||||
address += 4;
|
address += 4;
|
||||||
length -= 4;
|
length -= 4;
|
||||||
}
|
}
|
||||||
|
@ -60,4 +65,4 @@ public:
|
||||||
private:
|
private:
|
||||||
static FSpinlock s_lock;
|
static FSpinlock s_lock;
|
||||||
static std::unordered_map<MPTR, DEBUG_SYMBOL_TYPE> s_typeStorage;
|
static std::unordered_map<MPTR, DEBUG_SYMBOL_TYPE> s_typeStorage;
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,6 +25,8 @@ struct LatteGPUState_t
|
||||||
// context control
|
// context control
|
||||||
uint32 contextControl0;
|
uint32 contextControl0;
|
||||||
uint32 contextControl1;
|
uint32 contextControl1;
|
||||||
|
// optional features
|
||||||
|
bool allowFramebufferSizeOptimization{false}; // allow using scissor box as size hint to determine non-padded rendertarget size
|
||||||
// draw context
|
// draw context
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
|
// 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
|
// use the scissor box as a clue to figure out the original resolution if possible
|
||||||
#if 0
|
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();
|
uint32 scissorBoxWidth = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_BR.get_BR_X();
|
||||||
if (((scissorBoxWidth + 7) & ~7) == colorBufferWidth)
|
uint32 scissorBoxHeight = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_BR.get_BR_Y();
|
||||||
colorBufferWidth = scissorBoxWidth;
|
if (((scissorBoxWidth + 7) & ~7) == colorBufferWidth)
|
||||||
if (((colorBufferHeight + 31) & ~31) == colorBufferHeight)
|
colorBufferWidth = scissorBoxWidth;
|
||||||
colorBufferHeight = scissorBoxHeight;
|
if (((colorBufferHeight + 31) & ~31) == colorBufferHeight)
|
||||||
#endif
|
colorBufferHeight = scissorBoxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
// log resolution changes if the above heuristic takes effect
|
// 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
|
// 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)
|
if (colorBufferView == nullptr)
|
||||||
{
|
{
|
||||||
// create color buffer view
|
// 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;
|
LatteGPUState.repeatTextureInitialization = true;
|
||||||
checkForTextureChanges = false;
|
checkForTextureChanges = false;
|
||||||
}
|
}
|
||||||
|
@ -582,7 +583,7 @@ bool LatteMRT::UpdateCurrentFBO()
|
||||||
if (!depthBufferView)
|
if (!depthBufferView)
|
||||||
{
|
{
|
||||||
// create new depth buffer view and if it doesn't exist then also create the texture
|
// 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;
|
LatteGPUState.repeatTextureInitialization = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#include "Cafe/HW/Latte/Core/Latte.h"
|
#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/LatteShader.h"
|
||||||
#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h"
|
#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h"
|
||||||
#include "Cafe/HW/Latte/Core/LatteTexture.h"
|
#include "Cafe/HW/Latte/Core/LatteTexture.h"
|
||||||
|
@ -9,6 +8,8 @@
|
||||||
|
|
||||||
#include "Cafe/GraphicPack/GraphicPack2.h"
|
#include "Cafe/GraphicPack/GraphicPack2.h"
|
||||||
|
|
||||||
|
#include <boost/container/small_vector.hpp>
|
||||||
|
|
||||||
struct TexMemOccupancyEntry
|
struct TexMemOccupancyEntry
|
||||||
{
|
{
|
||||||
uint32 addrStart;
|
uint32 addrStart;
|
||||||
|
@ -963,7 +964,7 @@ void LatteTexture_RecreateTextureWithDifferentMipSliceCount(LatteTexture* textur
|
||||||
}
|
}
|
||||||
|
|
||||||
// create new texture representation
|
// 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
|
// 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)
|
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
|
// todo, depth and numSlice are redundant
|
||||||
|
|
||||||
sint32 sliceCount = firstSlice + numSlice;
|
sint32 sliceCount = firstSlice + numSlice;
|
||||||
std::vector<LatteTexture*> list_overlappingTextures;
|
boost::container::small_vector<LatteTexture*, 16> list_overlappingTextures;
|
||||||
for (sint32 sliceIndex = 0; sliceIndex < sliceCount; sliceIndex++)
|
for (sint32 sliceIndex = 0; sliceIndex < sliceCount; sliceIndex++)
|
||||||
{
|
{
|
||||||
sint32 mipIndex = 0;
|
sint32 mipIndex = 0;
|
||||||
|
|
|
@ -175,6 +175,23 @@ int Latte_ThreadEntry()
|
||||||
|
|
||||||
// before doing anything with game specific shaders, we need to wait for graphic packs to finish loading
|
// before doing anything with game specific shaders, we need to wait for graphic packs to finish loading
|
||||||
GraphicPack2::WaitUntilReady();
|
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
|
// load disk shader cache
|
||||||
LatteShaderCache_Load();
|
LatteShaderCache_Load();
|
||||||
// init registers
|
// init registers
|
||||||
|
|
|
@ -973,7 +973,7 @@ void _emitOperandInputCode(LatteDecompilerShaderContext* shaderContext, LatteDec
|
||||||
}
|
}
|
||||||
else
|
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();
|
debugBreakpoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,8 +30,6 @@ public:
|
||||||
void Flush(bool waitIdle = false) override;
|
void Flush(bool waitIdle = false) override;
|
||||||
void NotifyLatteCommandProcessorIdle() override;
|
void NotifyLatteCommandProcessorIdle() override;
|
||||||
|
|
||||||
void UpdateVSyncState();
|
|
||||||
|
|
||||||
void EnableDebugMode() override;
|
void EnableDebugMode() override;
|
||||||
void SwapBuffers(bool swapTV = true, bool swapDRC = true) override;
|
void SwapBuffers(bool swapTV = true, bool swapDRC = true) override;
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,34 @@
|
||||||
#include "SwapchainInfoVk.h"
|
#include "SwapchainInfoVk.h"
|
||||||
|
|
||||||
#include "config/CemuConfig.h"
|
#include "config/CemuConfig.h"
|
||||||
|
#include "gui/guiWrapper.h"
|
||||||
#include "Cafe/HW/Latte/Core/Latte.h"
|
#include "Cafe/HW/Latte/Core/Latte.h"
|
||||||
#include "Cafe/HW/Latte/Core/LatteTiming.h"
|
#include "Cafe/HW/Latte/Core/LatteTiming.h"
|
||||||
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.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;
|
auto& windowHandleInfo = mainWindow ? gui_getWindowInfo().canvas_main : gui_getWindowInfo().canvas_pad;
|
||||||
m_logicalDevice = logicalDevice;
|
auto renderer = VulkanRenderer::GetInstance();
|
||||||
const auto details = QuerySwapchainSupport(surface, physicalDevice);
|
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_surfaceFormat = ChooseSurfaceFormat(details.formats);
|
||||||
m_actualExtent = ChooseSwapExtent(details.capabilities);
|
m_actualExtent = ChooseSwapExtent(details.capabilities);
|
||||||
|
|
||||||
|
@ -20,28 +39,28 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe
|
||||||
if(image_count < 2)
|
if(image_count < 2)
|
||||||
cemuLog_log(LogType::Force, "Vulkan: Swapchain image count less than 2 may cause problems");
|
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.oldSwapchain = nullptr;
|
||||||
create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
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)
|
if (result != VK_SUCCESS)
|
||||||
UnrecoverableError("Error attempting to create a swapchain");
|
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)
|
if (result != VK_SUCCESS)
|
||||||
UnrecoverableError("Error attempting to retrieve the count of swapchain images");
|
UnrecoverableError("Error attempting to retrieve the count of swapchain images");
|
||||||
|
|
||||||
|
|
||||||
m_swapchainImages.resize(image_count);
|
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)
|
if (result != VK_SUCCESS)
|
||||||
UnrecoverableError("Error attempting to retrieve swapchain images");
|
UnrecoverableError("Error attempting to retrieve swapchain images");
|
||||||
// create default renderpass
|
// create default renderpass
|
||||||
VkAttachmentDescription colorAttachment = {};
|
VkAttachmentDescription colorAttachment = {};
|
||||||
colorAttachment.format = m_surfaceFormat.format;
|
colorAttachment.format = m_surfaceFormat.format;
|
||||||
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
|
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.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||||
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||||
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_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.pAttachments = &colorAttachment;
|
||||||
renderPassInfo.subpassCount = 1;
|
renderPassInfo.subpassCount = 1;
|
||||||
renderPassInfo.pSubpasses = &subpass;
|
renderPassInfo.pSubpasses = &subpass;
|
||||||
result = vkCreateRenderPass(logicalDevice, &renderPassInfo, nullptr, &m_swapchainRenderPass);
|
result = vkCreateRenderPass(m_logicalDevice, &renderPassInfo, nullptr, &m_swapchainRenderPass);
|
||||||
if (result != VK_SUCCESS)
|
if (result != VK_SUCCESS)
|
||||||
UnrecoverableError("Failed to create renderpass for swapchain");
|
UnrecoverableError("Failed to create renderpass for swapchain");
|
||||||
|
|
||||||
|
@ -84,7 +103,7 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe
|
||||||
createInfo.subresourceRange.levelCount = 1;
|
createInfo.subresourceRange.levelCount = 1;
|
||||||
createInfo.subresourceRange.baseArrayLayer = 0;
|
createInfo.subresourceRange.baseArrayLayer = 0;
|
||||||
createInfo.subresourceRange.layerCount = 1;
|
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)
|
if (result != VK_SUCCESS)
|
||||||
UnrecoverableError("Failed to create imageviews for swapchain");
|
UnrecoverableError("Failed to create imageviews for swapchain");
|
||||||
}
|
}
|
||||||
|
@ -104,7 +123,7 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe
|
||||||
framebufferInfo.width = m_actualExtent.width;
|
framebufferInfo.width = m_actualExtent.width;
|
||||||
framebufferInfo.height = m_actualExtent.height;
|
framebufferInfo.height = m_actualExtent.height;
|
||||||
framebufferInfo.layers = 1;
|
framebufferInfo.layers = 1;
|
||||||
result = vkCreateFramebuffer(logicalDevice, &framebufferInfo, nullptr, &m_swapchainFramebuffers[i]);
|
result = vkCreateFramebuffer(m_logicalDevice, &framebufferInfo, nullptr, &m_swapchainFramebuffers[i]);
|
||||||
if (result != VK_SUCCESS)
|
if (result != VK_SUCCESS)
|
||||||
UnrecoverableError("Failed to create framebuffer for swapchain");
|
UnrecoverableError("Failed to create framebuffer for swapchain");
|
||||||
}
|
}
|
||||||
|
@ -114,7 +133,7 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe
|
||||||
VkSemaphoreCreateInfo info = {};
|
VkSemaphoreCreateInfo info = {};
|
||||||
info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
||||||
for (auto& semaphore : m_presentSemaphores){
|
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");
|
UnrecoverableError("Failed to create semaphore for swapchain present");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,14 +142,14 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe
|
||||||
info = {};
|
info = {};
|
||||||
info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
||||||
for (auto& semaphore : m_acquireSemaphores){
|
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");
|
UnrecoverableError("Failed to create semaphore for swapchain acquire");
|
||||||
}
|
}
|
||||||
|
|
||||||
VkFenceCreateInfo fenceInfo = {};
|
VkFenceCreateInfo fenceInfo = {};
|
||||||
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
||||||
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
|
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)
|
if (result != VK_SUCCESS)
|
||||||
UnrecoverableError("Failed to create fence for swapchain");
|
UnrecoverableError("Failed to create fence for swapchain");
|
||||||
|
|
||||||
|
@ -167,19 +186,20 @@ void SwapchainInfoVk::Cleanup()
|
||||||
|
|
||||||
if (m_imageAvailableFence)
|
if (m_imageAvailableFence)
|
||||||
{
|
{
|
||||||
|
WaitAvailableFence();
|
||||||
vkDestroyFence(m_logicalDevice, m_imageAvailableFence, nullptr);
|
vkDestroyFence(m_logicalDevice, m_imageAvailableFence, nullptr);
|
||||||
m_imageAvailableFence = nullptr;
|
m_imageAvailableFence = nullptr;
|
||||||
}
|
}
|
||||||
if (swapchain)
|
if (m_swapchain)
|
||||||
{
|
{
|
||||||
vkDestroySwapchainKHR(m_logicalDevice, swapchain, nullptr);
|
vkDestroySwapchainKHR(m_logicalDevice, m_swapchain, nullptr);
|
||||||
swapchain = VK_NULL_HANDLE;
|
m_swapchain = VK_NULL_HANDLE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SwapchainInfoVk::IsValid() const
|
bool SwapchainInfoVk::IsValid() const
|
||||||
{
|
{
|
||||||
return swapchain && !m_acquireSemaphores.empty();
|
return m_swapchain && !m_acquireSemaphores.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SwapchainInfoVk::WaitAvailableFence()
|
void SwapchainInfoVk::WaitAvailableFence()
|
||||||
|
@ -207,7 +227,7 @@ bool SwapchainInfoVk::AcquireImage(uint64 timeout)
|
||||||
ResetAvailableFence();
|
ResetAvailableFence();
|
||||||
|
|
||||||
VkSemaphore acquireSemaphore = m_acquireSemaphores[m_acquireIndex];
|
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)
|
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR)
|
||||||
m_shouldRecreate = true;
|
m_shouldRecreate = true;
|
||||||
if (result < 0)
|
if (result < 0)
|
||||||
|
@ -231,35 +251,6 @@ void SwapchainInfoVk::UnrecoverableError(const char* errMsg)
|
||||||
throw std::runtime_error(errMsg);
|
throw std::runtime_error(errMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
SwapchainInfoVk::QueueFamilyIndices SwapchainInfoVk::FindQueueFamilies(VkSurfaceKHR surface, VkPhysicalDevice device)
|
|
||||||
{
|
|
||||||
uint32_t queueFamilyCount = 0;
|
|
||||||
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
|
|
||||||
|
|
||||||
std::vector<VkQueueFamilyProperties> 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)
|
SwapchainInfoVk::SwapchainSupportDetails SwapchainInfoVk::QuerySwapchainSupport(VkSurfaceKHR surface, const VkPhysicalDevice& device)
|
||||||
{
|
{
|
||||||
|
@ -391,7 +382,7 @@ VkSwapchainCreateInfoKHR SwapchainInfoVk::CreateSwapchainCreateInfo(VkSurfaceKHR
|
||||||
createInfo.imageArrayLayers = 1;
|
createInfo.imageArrayLayers = 1;
|
||||||
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
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 };
|
m_swapchainQueueFamilyIndices = { (uint32)indices.graphicsFamily, (uint32)indices.presentFamily };
|
||||||
if (indices.graphicsFamily != indices.presentFamily)
|
if (indices.graphicsFamily != indices.presentFamily)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
|
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
|
struct SwapchainSupportDetails
|
||||||
{
|
{
|
||||||
VkSurfaceCapabilitiesKHR capabilities;
|
VkSurfaceCapabilitiesKHR capabilities;
|
||||||
|
@ -30,7 +22,7 @@ struct SwapchainInfoVk
|
||||||
};
|
};
|
||||||
|
|
||||||
void Cleanup();
|
void Cleanup();
|
||||||
void Create(VkPhysicalDevice physicalDevice, VkDevice logicalDevice);
|
void Create();
|
||||||
|
|
||||||
bool IsValid() const;
|
bool IsValid() const;
|
||||||
|
|
||||||
|
@ -45,8 +37,6 @@ struct SwapchainInfoVk
|
||||||
|
|
||||||
static void UnrecoverableError(const char* errMsg);
|
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);
|
static SwapchainSupportDetails QuerySwapchainSupport(VkSurfaceKHR surface, const VkPhysicalDevice& device);
|
||||||
|
|
||||||
VkPresentModeKHR ChoosePresentMode(const std::vector<VkPresentModeKHR>& modes);
|
VkPresentModeKHR ChoosePresentMode(const std::vector<VkPresentModeKHR>& modes);
|
||||||
|
@ -61,14 +51,10 @@ struct SwapchainInfoVk
|
||||||
return m_actualExtent;
|
return m_actualExtent;
|
||||||
}
|
}
|
||||||
|
|
||||||
SwapchainInfoVk(VkSurfaceKHR surface, bool mainWindow)
|
SwapchainInfoVk(bool mainWindow, Vector2i size);
|
||||||
: surface(surface), mainWindow(mainWindow) {}
|
|
||||||
SwapchainInfoVk(const SwapchainInfoVk&) = delete;
|
SwapchainInfoVk(const SwapchainInfoVk&) = delete;
|
||||||
SwapchainInfoVk(SwapchainInfoVk&&) noexcept = default;
|
SwapchainInfoVk(SwapchainInfoVk&&) noexcept = default;
|
||||||
~SwapchainInfoVk()
|
~SwapchainInfoVk();
|
||||||
{
|
|
||||||
Cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool mainWindow{};
|
bool mainWindow{};
|
||||||
|
|
||||||
|
@ -77,11 +63,12 @@ struct SwapchainInfoVk
|
||||||
VSync m_vsyncState = VSync::Immediate;
|
VSync m_vsyncState = VSync::Immediate;
|
||||||
bool hasDefinedSwapchainImage{}; // indicates if the swapchain image is in a defined state
|
bool hasDefinedSwapchainImage{}; // indicates if the swapchain image is in a defined state
|
||||||
|
|
||||||
|
VkInstance m_instance{};
|
||||||
VkPhysicalDevice m_physicalDevice{};
|
VkPhysicalDevice m_physicalDevice{};
|
||||||
VkDevice m_logicalDevice{};
|
VkDevice m_logicalDevice{};
|
||||||
VkSurfaceKHR surface{};
|
VkSurfaceKHR m_surface{};
|
||||||
VkSurfaceFormatKHR m_surfaceFormat{};
|
VkSurfaceFormatKHR m_surfaceFormat{};
|
||||||
VkSwapchainKHR swapchain{};
|
VkSwapchainKHR m_swapchain{};
|
||||||
Vector2i m_desiredExtent{};
|
Vector2i m_desiredExtent{};
|
||||||
uint32 swapchainImageIndex = (uint32)-1;
|
uint32 swapchainImageIndex = (uint32)-1;
|
||||||
|
|
||||||
|
|
|
@ -167,6 +167,7 @@ std::vector<VulkanRenderer::DeviceInfo> VulkanRenderer::GetDevices()
|
||||||
result.emplace_back(physDeviceProps.properties.deviceName, physDeviceIDProps.deviceUUID);
|
result.emplace_back(physDeviceProps.properties.deviceName, physDeviceIDProps.deviceUUID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
vkDestroySurfaceKHR(instance, surface, nullptr);
|
||||||
}
|
}
|
||||||
catch (...)
|
catch (...)
|
||||||
{
|
{
|
||||||
|
@ -441,7 +442,7 @@ VulkanRenderer::VulkanRenderer()
|
||||||
}
|
}
|
||||||
|
|
||||||
// create logical device
|
// create logical device
|
||||||
m_indices = SwapchainInfoVk::FindQueueFamilies(surface, m_physicalDevice);
|
m_indices = FindQueueFamilies(surface, m_physicalDevice);
|
||||||
std::set<int> uniqueQueueFamilies = { m_indices.graphicsFamily, m_indices.presentFamily };
|
std::set<int> uniqueQueueFamilies = { m_indices.graphicsFamily, m_indices.presentFamily };
|
||||||
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos = CreateQueueCreateInfos(uniqueQueueFamilies);
|
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos = CreateQueueCreateInfos(uniqueQueueFamilies);
|
||||||
VkPhysicalDeviceFeatures deviceFeatures = {};
|
VkPhysicalDeviceFeatures deviceFeatures = {};
|
||||||
|
@ -510,7 +511,7 @@ VulkanRenderer::VulkanRenderer()
|
||||||
PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT = reinterpret_cast<PFN_vkCreateDebugUtilsMessengerEXT>(vkGetInstanceProcAddr(m_instance, "vkCreateDebugUtilsMessengerEXT"));
|
PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT = reinterpret_cast<PFN_vkCreateDebugUtilsMessengerEXT>(vkGetInstanceProcAddr(m_instance, "vkCreateDebugUtilsMessengerEXT"));
|
||||||
|
|
||||||
VkDebugUtilsMessengerCreateInfoEXT debugCallback{};
|
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.pNext = nullptr;
|
||||||
debugCallback.flags = 0;
|
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;
|
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)
|
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)
|
if (mainWindow)
|
||||||
{
|
{
|
||||||
m_mainSwapchainInfo = std::make_unique<SwapchainInfoVk>(surface, mainWindow);
|
m_mainSwapchainInfo = std::make_unique<SwapchainInfoVk>(mainWindow, size);
|
||||||
m_mainSwapchainInfo->m_desiredExtent = size;
|
m_mainSwapchainInfo->Create();
|
||||||
m_mainSwapchainInfo->Create(m_physicalDevice, m_logicalDevice);
|
|
||||||
|
|
||||||
// aquire first command buffer
|
// aquire first command buffer
|
||||||
InitFirstCommandBuffer();
|
InitFirstCommandBuffer();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_padSwapchainInfo = std::make_unique<SwapchainInfoVk>(surface, mainWindow);
|
m_padSwapchainInfo = std::make_unique<SwapchainInfoVk>(mainWindow, size);
|
||||||
m_padSwapchainInfo->m_desiredExtent = size;
|
|
||||||
// todo: figure out a way to exclusively create swapchain on main LatteThread
|
// 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()
|
void VulkanRenderer::StopUsingPadAndWait()
|
||||||
{
|
{
|
||||||
m_destroyPadSwapchainNextAcquire = true;
|
m_destroyPadSwapchainNextAcquire.test_and_set();
|
||||||
m_padCloseReadySemaphore.wait();
|
m_destroyPadSwapchainNextAcquire.wait(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VulkanRenderer::IsPadWindowActive()
|
bool VulkanRenderer::IsPadWindowActive()
|
||||||
|
@ -1074,6 +1070,36 @@ RendererShader* VulkanRenderer::shader_create(RendererShader::ShaderType type, u
|
||||||
return new RendererShaderVk(type, baseHash, auxHash, isGameShader, isGfxPackShader, source);
|
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<VkQueueFamilyProperties> 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)
|
bool VulkanRenderer::CheckDeviceExtensionSupport(const VkPhysicalDevice device, FeatureControl& info)
|
||||||
{
|
{
|
||||||
std::vector<VkExtensionProperties> availableDeviceExtensions;
|
std::vector<VkExtensionProperties> availableDeviceExtensions;
|
||||||
|
@ -1215,7 +1241,7 @@ std::vector<const char*> VulkanRenderer::CheckInstanceExtensionSupport(FeatureCo
|
||||||
|
|
||||||
bool VulkanRenderer::IsDeviceSuitable(VkSurfaceKHR surface, const VkPhysicalDevice& device)
|
bool VulkanRenderer::IsDeviceSuitable(VkSurfaceKHR surface, const VkPhysicalDevice& device)
|
||||||
{
|
{
|
||||||
if (!SwapchainInfoVk::FindQueueFamilies(surface, device).IsComplete())
|
if (!FindQueueFamilies(surface, device).IsComplete())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// check API version (using Vulkan 1.0 way of querying properties)
|
// check API version (using Vulkan 1.0 way of querying properties)
|
||||||
|
@ -2557,11 +2583,11 @@ bool VulkanRenderer::AcquireNextSwapchainImage(bool mainWindow)
|
||||||
if(!IsSwapchainInfoValid(mainWindow))
|
if(!IsSwapchainInfoValid(mainWindow))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if(!mainWindow && m_destroyPadSwapchainNextAcquire)
|
if(!mainWindow && m_destroyPadSwapchainNextAcquire.test())
|
||||||
{
|
{
|
||||||
RecreateSwapchain(mainWindow, true);
|
RecreateSwapchain(mainWindow, true);
|
||||||
m_destroyPadSwapchainNextAcquire = false;
|
m_destroyPadSwapchainNextAcquire.clear();
|
||||||
m_padCloseReadySemaphore.notify();
|
m_destroyPadSwapchainNextAcquire.notify_all();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2605,7 +2631,7 @@ void VulkanRenderer::RecreateSwapchain(bool mainWindow, bool skipCreate)
|
||||||
chainInfo.m_desiredExtent = size;
|
chainInfo.m_desiredExtent = size;
|
||||||
if(!skipCreate)
|
if(!skipCreate)
|
||||||
{
|
{
|
||||||
chainInfo.Create(m_physicalDevice, m_logicalDevice);
|
chainInfo.Create();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mainWindow)
|
if (mainWindow)
|
||||||
|
@ -2675,7 +2701,7 @@ void VulkanRenderer::SwapBuffer(bool mainWindow)
|
||||||
VkPresentInfoKHR presentInfo = {};
|
VkPresentInfoKHR presentInfo = {};
|
||||||
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
||||||
presentInfo.swapchainCount = 1;
|
presentInfo.swapchainCount = 1;
|
||||||
presentInfo.pSwapchains = &chainInfo.swapchain;
|
presentInfo.pSwapchains = &chainInfo.m_swapchain;
|
||||||
presentInfo.pImageIndices = &chainInfo.swapchainImageIndex;
|
presentInfo.pImageIndices = &chainInfo.swapchainImageIndex;
|
||||||
// wait on command buffer semaphore
|
// wait on command buffer semaphore
|
||||||
presentInfo.waitSemaphoreCount = 1;
|
presentInfo.waitSemaphoreCount = 1;
|
||||||
|
|
|
@ -127,7 +127,6 @@ class VulkanRenderer : public Renderer
|
||||||
friend class PipelineCompiler;
|
friend class PipelineCompiler;
|
||||||
|
|
||||||
using VSync = SwapchainInfoVk::VSync;
|
using VSync = SwapchainInfoVk::VSync;
|
||||||
using QueueFamilyIndices = SwapchainInfoVk::QueueFamilyIndices;
|
|
||||||
|
|
||||||
static const inline int UNIFORMVAR_RINGBUFFER_SIZE = 1024 * 1024 * 16; // 16MB
|
static const inline int UNIFORMVAR_RINGBUFFER_SIZE = 1024 * 1024 * 16; // 16MB
|
||||||
|
|
||||||
|
@ -414,14 +413,25 @@ private:
|
||||||
}m_state;
|
}m_state;
|
||||||
|
|
||||||
std::unique_ptr<SwapchainInfoVk> m_mainSwapchainInfo{}, m_padSwapchainInfo{};
|
std::unique_ptr<SwapchainInfoVk> m_mainSwapchainInfo{}, m_padSwapchainInfo{};
|
||||||
Semaphore m_padCloseReadySemaphore;
|
std::atomic_flag m_destroyPadSwapchainNextAcquire{};
|
||||||
bool m_destroyPadSwapchainNextAcquire = false;
|
|
||||||
bool IsSwapchainInfoValid(bool mainWindow) const;
|
bool IsSwapchainInfoValid(bool mainWindow) const;
|
||||||
|
|
||||||
VkRenderPass m_imguiRenderPass = VK_NULL_HANDLE;
|
VkRenderPass m_imguiRenderPass = VK_NULL_HANDLE;
|
||||||
|
|
||||||
VkDescriptorPool m_descriptorPool;
|
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 FeatureControl
|
||||||
{
|
{
|
||||||
struct
|
struct
|
||||||
|
|
|
@ -1159,9 +1159,11 @@ namespace coreinit
|
||||||
#include <sys/prctl.h>
|
#include <sys/prctl.h>
|
||||||
|
|
||||||
std::vector<pid_t> g_schedulerThreadIds;
|
std::vector<pid_t> g_schedulerThreadIds;
|
||||||
|
std::mutex g_schedulerThreadIdsLock;
|
||||||
|
|
||||||
std::vector<pid_t>& OSGetSchedulerThreadIds()
|
std::vector<pid_t>& OSGetSchedulerThreadIds()
|
||||||
{
|
{
|
||||||
|
std::lock_guard schedulerThreadIdsLockGuard(g_schedulerThreadIdsLock);
|
||||||
return g_schedulerThreadIds;
|
return g_schedulerThreadIds;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -1183,7 +1185,10 @@ namespace coreinit
|
||||||
}
|
}
|
||||||
|
|
||||||
pid_t tid = gettid();
|
pid_t tid = gettid();
|
||||||
g_schedulerThreadIds.emplace_back(tid);
|
{
|
||||||
|
std::lock_guard schedulerThreadIdsLockGuard(g_schedulerThreadIdsLock);
|
||||||
|
g_schedulerThreadIds.emplace_back(tid);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
t_schedulerFiber = Fiber::PrepareCurrentThread();
|
t_schedulerFiber = Fiber::PrepareCurrentThread();
|
||||||
|
@ -1238,7 +1243,10 @@ namespace coreinit
|
||||||
sSchedulerThreads.clear();
|
sSchedulerThreads.clear();
|
||||||
g_schedulerThreadHandles.clear();
|
g_schedulerThreadHandles.clear();
|
||||||
#if BOOST_OS_LINUX
|
#if BOOST_OS_LINUX
|
||||||
g_schedulerThreadIds.clear();
|
{
|
||||||
|
std::lock_guard schedulerThreadIdsLockGuard(g_schedulerThreadIdsLock);
|
||||||
|
g_schedulerThreadIds.clear();
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
// clean up all fibers
|
// clean up all fibers
|
||||||
for (auto& it : g_idleLoopFiber)
|
for (auto& it : g_idleLoopFiber)
|
||||||
|
|
|
@ -90,8 +90,11 @@ struct ParsedMetaXml
|
||||||
else if (boost::starts_with(name, "longname_"))
|
else if (boost::starts_with(name, "longname_"))
|
||||||
{
|
{
|
||||||
const sint32 index = GetLanguageIndex(name.substr(std::size("longname_") - 1));
|
const sint32 index = GetLanguageIndex(name.substr(std::size("longname_") - 1));
|
||||||
if (index != -1)
|
if (index != -1){
|
||||||
parsedMetaXml->m_long_name[index] = child.text().as_string();
|
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_"))
|
else if (boost::starts_with(name, L"shortname_"))
|
||||||
{
|
{
|
||||||
|
|
|
@ -637,9 +637,9 @@ std::string TitleInfo::GetMetaTitleName() const
|
||||||
if (m_parsedMetaXml)
|
if (m_parsedMetaXml)
|
||||||
{
|
{
|
||||||
std::string titleNameCfgLanguage;
|
std::string titleNameCfgLanguage;
|
||||||
titleNameCfgLanguage = m_parsedMetaXml->GetShortName(GetConfig().console_language);
|
titleNameCfgLanguage = m_parsedMetaXml->GetLongName(GetConfig().console_language);
|
||||||
if (titleNameCfgLanguage.empty()) //Get English Title
|
if (titleNameCfgLanguage.empty()) //Get English Title
|
||||||
titleNameCfgLanguage = m_parsedMetaXml->GetShortName(CafeConsoleLanguage::EN);
|
titleNameCfgLanguage = m_parsedMetaXml->GetLongName(CafeConsoleLanguage::EN);
|
||||||
if (titleNameCfgLanguage.empty()) //Unknown Title
|
if (titleNameCfgLanguage.empty()) //Unknown Title
|
||||||
titleNameCfgLanguage = "Unknown Title";
|
titleNameCfgLanguage = "Unknown Title";
|
||||||
return titleNameCfgLanguage;
|
return titleNameCfgLanguage;
|
||||||
|
|
|
@ -85,6 +85,8 @@ void CemuConfig::Load(XMLConfigParser& parser)
|
||||||
game_list_style = gamelist.get("style", 0);
|
game_list_style = gamelist.get("style", 0);
|
||||||
game_list_column_order = gamelist.get("order", "");
|
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
|
// return default width if value in config file out of range
|
||||||
auto loadColumnSize = [&gamelist] (const char *name, uint32 defaultWidth)
|
auto loadColumnSize = [&gamelist] (const char *name, uint32 defaultWidth)
|
||||||
{
|
{
|
||||||
|
@ -387,6 +389,7 @@ void CemuConfig::Save(XMLConfigParser& parser)
|
||||||
psize.set<sint32>("x", pad_size.x);
|
psize.set<sint32>("x", pad_size.x);
|
||||||
psize.set<sint32>("y", pad_size.y);
|
psize.set<sint32>("y", pad_size.y);
|
||||||
config.set<bool>("pad_maximized", pad_maximized);
|
config.set<bool>("pad_maximized", pad_maximized);
|
||||||
|
config.set<bool>("show_icon_column" , show_icon_column);
|
||||||
|
|
||||||
auto gamelist = config.set("GameList");
|
auto gamelist = config.set("GameList");
|
||||||
gamelist.set("style", game_list_style);
|
gamelist.set("style", game_list_style);
|
||||||
|
|
|
@ -419,6 +419,8 @@ struct CemuConfig
|
||||||
ConfigValue<bool> did_show_graphic_pack_download{false};
|
ConfigValue<bool> did_show_graphic_pack_download{false};
|
||||||
ConfigValue<bool> did_show_macos_disclaimer{false};
|
ConfigValue<bool> did_show_macos_disclaimer{false};
|
||||||
|
|
||||||
|
ConfigValue<bool> show_icon_column{ false };
|
||||||
|
|
||||||
int game_list_style = 0;
|
int game_list_style = 0;
|
||||||
std::string game_list_column_order;
|
std::string game_list_column_order;
|
||||||
struct
|
struct
|
||||||
|
|
|
@ -59,7 +59,12 @@ bool CemuApp::OnInit()
|
||||||
fs::path user_data_path, config_path, cache_path, data_path;
|
fs::path user_data_path, config_path, cache_path, data_path;
|
||||||
auto standardPaths = wxStandardPaths::Get();
|
auto standardPaths = wxStandardPaths::Get();
|
||||||
fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath()));
|
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.
|
// Try a portable path first, if it exists.
|
||||||
user_data_path = config_path = cache_path = data_path = exePath.parent_path() / "portable";
|
user_data_path = config_path = cache_path = data_path = exePath.parent_path() / "portable";
|
||||||
#if BOOST_OS_MACOS
|
#if BOOST_OS_MACOS
|
||||||
|
|
|
@ -110,14 +110,6 @@ void deleteDownloadedGraphicPacks()
|
||||||
|
|
||||||
void DownloadGraphicPacksWindow::UpdateThread()
|
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
|
// get github url
|
||||||
std::string githubAPIUrl;
|
std::string githubAPIUrl;
|
||||||
curlDownloadFileState_t tempDownloadState;
|
curlDownloadFileState_t tempDownloadState;
|
||||||
|
@ -326,8 +318,6 @@ DownloadGraphicPacksWindow::DownloadGraphicPacksWindow(wxWindow* parent)
|
||||||
|
|
||||||
|
|
||||||
m_downloadState = std::make_unique<curlDownloadFileState_t>();
|
m_downloadState = std::make_unique<curlDownloadFileState_t>();
|
||||||
|
|
||||||
m_thread = std::thread(&DownloadGraphicPacksWindow::UpdateThread, this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DownloadGraphicPacksWindow::~DownloadGraphicPacksWindow()
|
DownloadGraphicPacksWindow::~DownloadGraphicPacksWindow()
|
||||||
|
@ -344,6 +334,12 @@ const std::string& DownloadGraphicPacksWindow::GetException() const
|
||||||
|
|
||||||
int DownloadGraphicPacksWindow::ShowModal()
|
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();
|
wxDialog::ShowModal();
|
||||||
return m_threadState == ThreadCanceled ? wxID_CANCEL : wxID_OK;
|
return m_threadState == ThreadCanceled ? wxID_CANCEL : wxID_OK;
|
||||||
}
|
}
|
||||||
|
|
|
@ -319,6 +319,7 @@ GraphicPacksWindow2::GraphicPacksWindow2(wxWindow* parent, uint64_t title_id_fil
|
||||||
|
|
||||||
SetSizer(main_sizer);
|
SetSizer(main_sizer);
|
||||||
|
|
||||||
|
UpdateTitleRunning(CafeSystem::IsTitleRunning());
|
||||||
FillGraphicPackList();
|
FillGraphicPackList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -676,6 +677,15 @@ void GraphicPacksWindow2::OnInstalledGamesChanged(wxCommandEvent& event)
|
||||||
event.Skip();
|
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
|
void GraphicPacksWindow2::ReloadPack(const GraphicPackPtr& graphic_pack) const
|
||||||
{
|
{
|
||||||
if (graphic_pack->HasShaders() || graphic_pack->HasPatches() || graphic_pack->HasCustomVSyncFrequency())
|
if (graphic_pack->HasShaders() || graphic_pack->HasPatches() || graphic_pack->HasCustomVSyncFrequency())
|
||||||
|
|
|
@ -21,6 +21,7 @@ public:
|
||||||
~GraphicPacksWindow2();
|
~GraphicPacksWindow2();
|
||||||
|
|
||||||
static void RefreshGraphicPacks();
|
static void RefreshGraphicPacks();
|
||||||
|
void UpdateTitleRunning(bool running);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string m_filter;
|
std::string m_filter;
|
||||||
|
|
|
@ -625,6 +625,7 @@ bool MainWindow::FileLoad(const fs::path launchPath, wxLaunchGameEvent::INITIATE
|
||||||
CreateCanvas();
|
CreateCanvas();
|
||||||
CafeSystem::LaunchForegroundTitle();
|
CafeSystem::LaunchForegroundTitle();
|
||||||
RecreateMenu();
|
RecreateMenu();
|
||||||
|
UpdateChildWindowTitleRunningState();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -683,6 +684,7 @@ void MainWindow::OnFileMenu(wxCommandEvent& event)
|
||||||
RecreateMenu();
|
RecreateMenu();
|
||||||
CreateGameListAndStatusBar();
|
CreateGameListAndStatusBar();
|
||||||
DoLayout();
|
DoLayout();
|
||||||
|
UpdateChildWindowTitleRunningState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2320,6 +2322,14 @@ void MainWindow::RecreateMenu()
|
||||||
SetMenuVisible(false);
|
SetMenuVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::UpdateChildWindowTitleRunningState()
|
||||||
|
{
|
||||||
|
const bool running = CafeSystem::IsTitleRunning();
|
||||||
|
|
||||||
|
if(m_graphic_pack_window)
|
||||||
|
m_graphic_pack_window->UpdateTitleRunning(running);
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::RestoreSettingsAfterGameExited()
|
void MainWindow::RestoreSettingsAfterGameExited()
|
||||||
{
|
{
|
||||||
RecreateMenu();
|
RecreateMenu();
|
||||||
|
|
|
@ -21,6 +21,7 @@ class DebuggerWindow2;
|
||||||
struct GameEntry;
|
struct GameEntry;
|
||||||
class DiscordPresence;
|
class DiscordPresence;
|
||||||
class TitleManager;
|
class TitleManager;
|
||||||
|
class GraphicPacksWindow2;
|
||||||
class wxLaunchGameEvent;
|
class wxLaunchGameEvent;
|
||||||
|
|
||||||
wxDECLARE_EVENT(wxEVT_LAUNCH_GAME, wxLaunchGameEvent);
|
wxDECLARE_EVENT(wxEVT_LAUNCH_GAME, wxLaunchGameEvent);
|
||||||
|
@ -146,6 +147,7 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void RecreateMenu();
|
void RecreateMenu();
|
||||||
|
void UpdateChildWindowTitleRunningState();
|
||||||
static wxString GetInitialWindowTitle();
|
static wxString GetInitialWindowTitle();
|
||||||
void ShowGettingStartedDialog();
|
void ShowGettingStartedDialog();
|
||||||
|
|
||||||
|
@ -163,7 +165,7 @@ private:
|
||||||
MemorySearcherTool* m_toolWindow = nullptr;
|
MemorySearcherTool* m_toolWindow = nullptr;
|
||||||
TitleManager* m_title_manager = nullptr;
|
TitleManager* m_title_manager = nullptr;
|
||||||
PadViewFrame* m_padView = nullptr;
|
PadViewFrame* m_padView = nullptr;
|
||||||
wxWindow* m_graphic_pack_window = nullptr;
|
GraphicPacksWindow2* m_graphic_pack_window = nullptr;
|
||||||
|
|
||||||
wxTimer* m_timer;
|
wxTimer* m_timer;
|
||||||
wxPoint m_mouse_position{};
|
wxPoint m_mouse_position{};
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
#include <wx/utils.h>
|
#include <wx/utils.h>
|
||||||
#include <wx/clipbrd.h>
|
#include <wx/clipbrd.h>
|
||||||
|
|
||||||
|
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
#include <boost/tokenizer.hpp>
|
#include <boost/tokenizer.hpp>
|
||||||
|
|
||||||
|
@ -89,7 +88,10 @@ wxGameList::wxGameList(wxWindow* parent, wxWindowID id)
|
||||||
const auto& config = GetConfig();
|
const auto& config = GetConfig();
|
||||||
|
|
||||||
InsertColumn(ColumnHiddenName, "", wxLIST_FORMAT_LEFT, 0);
|
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(ColumnName, _("Game"), wxLIST_FORMAT_LEFT, config.column_width.name);
|
||||||
InsertColumn(ColumnVersion, _("Version"), wxLIST_FORMAT_RIGHT, config.column_width.version);
|
InsertColumn(ColumnVersion, _("Version"), wxLIST_FORMAT_RIGHT, config.column_width.version);
|
||||||
InsertColumn(ColumnDLC, _("DLC"), wxLIST_FORMAT_RIGHT, config.column_width.dlc);
|
InsertColumn(ColumnDLC, _("DLC"), wxLIST_FORMAT_RIGHT, config.column_width.dlc);
|
||||||
|
@ -526,7 +528,6 @@ void wxGameList::OnKeyDown(wxListEvent& event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
enum ContextMenuEntries
|
enum ContextMenuEntries
|
||||||
{
|
{
|
||||||
kContextMenuRefreshGames = wxID_HIGHEST + 1,
|
kContextMenuRefreshGames = wxID_HIGHEST + 1,
|
||||||
|
@ -732,7 +733,7 @@ void wxGameList::OnContextMenuSelected(wxCommandEvent& event)
|
||||||
{
|
{
|
||||||
if (wxTheClipboard->Open())
|
if (wxTheClipboard->Open())
|
||||||
{
|
{
|
||||||
wxTheClipboard->SetData(new wxTextDataObject(gameInfo.GetTitleName()));
|
wxTheClipboard->SetData(new wxTextDataObject(wxString::FromUTF8(gameInfo.GetTitleName())));
|
||||||
wxTheClipboard->Close();
|
wxTheClipboard->Close();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -796,6 +797,7 @@ void wxGameList::OnColumnRightClick(wxListEvent& event)
|
||||||
ResetWidth = wxID_HIGHEST + 1,
|
ResetWidth = wxID_HIGHEST + 1,
|
||||||
ResetOrder,
|
ResetOrder,
|
||||||
|
|
||||||
|
ShowIcon,
|
||||||
ShowName,
|
ShowName,
|
||||||
ShowVersion,
|
ShowVersion,
|
||||||
ShowDlc,
|
ShowDlc,
|
||||||
|
@ -812,6 +814,7 @@ void wxGameList::OnColumnRightClick(wxListEvent& event)
|
||||||
menu.Append(ResetOrder, _("Reset &order")) ;
|
menu.Append(ResetOrder, _("Reset &order")) ;
|
||||||
|
|
||||||
menu.AppendSeparator();
|
menu.AppendSeparator();
|
||||||
|
menu.AppendCheckItem(ShowIcon, _("Show &icon"))->Check(GetColumnWidth(ColumnIcon) > 0);
|
||||||
menu.AppendCheckItem(ShowName, _("Show &name"))->Check(GetColumnWidth(ColumnName) > 0);
|
menu.AppendCheckItem(ShowName, _("Show &name"))->Check(GetColumnWidth(ColumnName) > 0);
|
||||||
menu.AppendCheckItem(ShowVersion, _("Show &version"))->Check(GetColumnWidth(ColumnVersion) > 0);
|
menu.AppendCheckItem(ShowVersion, _("Show &version"))->Check(GetColumnWidth(ColumnVersion) > 0);
|
||||||
menu.AppendCheckItem(ShowDlc, _("Show &dlc"))->Check(GetColumnWidth(ColumnDLC) > 0);
|
menu.AppendCheckItem(ShowDlc, _("Show &dlc"))->Check(GetColumnWidth(ColumnDLC) > 0);
|
||||||
|
@ -830,6 +833,9 @@ void wxGameList::OnColumnRightClick(wxListEvent& event)
|
||||||
|
|
||||||
switch (event.GetId())
|
switch (event.GetId())
|
||||||
{
|
{
|
||||||
|
case ShowIcon:
|
||||||
|
config.show_icon_column = menu->IsChecked(ShowIcon);
|
||||||
|
break;
|
||||||
case ShowName:
|
case ShowName:
|
||||||
config.column_width.name = menu->IsChecked(ShowName) ? DefaultColumnSize::name : 0;
|
config.column_width.name = menu->IsChecked(ShowName) ? DefaultColumnSize::name : 0;
|
||||||
break;
|
break;
|
||||||
|
@ -909,7 +915,10 @@ void wxGameList::ApplyGameListColumnWidths()
|
||||||
{
|
{
|
||||||
const auto& config = GetConfig();
|
const auto& config = GetConfig();
|
||||||
wxWindowUpdateLocker lock(this);
|
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(ColumnName, config.column_width.name);
|
||||||
SetColumnWidth(ColumnVersion, config.column_width.version);
|
SetColumnWidth(ColumnVersion, config.column_width.version);
|
||||||
SetColumnWidth(ColumnDLC, config.column_width.dlc);
|
SetColumnWidth(ColumnDLC, config.column_width.dlc);
|
||||||
|
@ -1276,129 +1285,169 @@ void wxGameList::DeleteCachedStrings()
|
||||||
m_name_cache.clear();
|
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
|
#if BOOST_OS_LINUX
|
||||||
const wxString desktop_entry_name = wxString::Format("%s.desktop", title_name);
|
void wxGameList::CreateShortcut(GameInfo2& gameInfo)
|
||||||
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);
|
const auto titleId = gameInfo.GetBaseTitleId();
|
||||||
#elif BOOST_OS_WINDOWS
|
const auto titleName = wxString::FromUTF8(gameInfo.GetTitleName());
|
||||||
// Get '%APPDATA%\Microsoft\Windows\Start Menu\Programs' path
|
auto exePath = ActiveSettings::GetExecutablePath();
|
||||||
PWSTR user_shortcut_folder;
|
const char* flatpakId = getenv("FLATPAK_ID");
|
||||||
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();
|
|
||||||
|
|
||||||
#if BOOST_OS_LINUX
|
const wxString desktopEntryName = wxString::Format("%s.desktop", titleName);
|
||||||
std::optional<fs::path> icon_path;
|
wxFileDialog entryDialog(this, _("Choose desktop entry location"), "~/.local/share/applications", desktopEntryName,
|
||||||
// Obtain and convert icon
|
"Desktop file (*.desktop)|*.desktop", wxFD_SAVE | wxFD_CHANGE_DIR | wxFD_OVERWRITE_PROMPT);
|
||||||
{
|
const auto result = entryDialog.ShowModal();
|
||||||
m_icon_cache_mtx.lock();
|
if (result == wxID_CANCEL)
|
||||||
const auto icon_iter = m_icon_cache.find(title_id);
|
return;
|
||||||
const auto result_index = (icon_iter != m_icon_cache.cend()) ? std::optional<int>(icon_iter->second.first) : std::nullopt;
|
const auto output_path = entryDialog.GetPath();
|
||||||
m_icon_cache_mtx.unlock();
|
|
||||||
|
|
||||||
// In most cases it should find it
|
std::optional<fs::path> iconPath;
|
||||||
if (!result_index){
|
// Obtain and convert icon
|
||||||
wxMessageBox(_("Icon is yet to load, so will not be used by the shortcut"), _("Warning"), wxOK | wxCENTRE | wxICON_WARNING);
|
[&]()
|
||||||
}
|
{
|
||||||
else {
|
int iconIndex, smallIconIndex;
|
||||||
const fs::path out_icon_dir = ActiveSettings::GetUserDataPath("icons");
|
|
||||||
|
|
||||||
if (!fs::exists(out_icon_dir) && !fs::create_directories(out_icon_dir)){
|
if (!QueryIconForTitle(titleId, iconIndex, smallIconIndex))
|
||||||
wxMessageBox(_("Cannot access the icon directory, the shortcut will have no icon"), _("Warning"), wxOK | wxCENTRE | wxICON_WARNING);
|
{
|
||||||
}
|
cemuLog_log(LogType::Force, "Icon hasn't loaded");
|
||||||
else {
|
return;
|
||||||
icon_path = out_icon_dir / fmt::format("{:016x}.png", gameInfo.GetBaseTitleId());
|
}
|
||||||
|
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()));
|
iconPath = outIconDir / fmt::format("{:016x}.png", gameInfo.GetBaseTitleId());
|
||||||
wxPNGHandler pngHandler;
|
wxFileOutputStream pngFileStream(_pathToUtf8(iconPath.value()));
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string desktop_exec_entry;
|
auto image = m_image_list->GetIcon(iconIndex).ConvertToImage();
|
||||||
if (flatpak_id)
|
wxPNGHandler pngHandler;
|
||||||
desktop_exec_entry = fmt::format("/usr/bin/flatpak run {0} --title-id {1:016x}", flatpak_id, title_id);
|
if (!pngHandler.SaveFile(&image, pngFileStream, false))
|
||||||
else
|
{
|
||||||
desktop_exec_entry = fmt::format("{0:?} --title-id {1:016x}", _pathToUtf8(exe_path), title_id);
|
iconPath = std::nullopt;
|
||||||
|
cemuLog_log(LogType::Force, "Icon failed to save");
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
|
||||||
// 'Icon' accepts spaces in file name, does not accept quoted file paths
|
std::string desktopExecEntry = flatpakId ? fmt::format("/usr/bin/flatpak run {0} --title-id {1:016x}", flatpakId, titleId)
|
||||||
// 'Exec' does not accept non-escaped spaces, and can accept quoted file paths
|
: fmt::format("{0:?} --title-id {1:016x}", _pathToUtf8(exePath), titleId);
|
||||||
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("")));
|
|
||||||
|
|
||||||
if (flatpak_id)
|
// 'Icon' accepts spaces in file name, does not accept quoted file paths
|
||||||
desktop_entry_string += fmt::format("X-Flatpak={}\n", flatpak_id);
|
// '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 (flatpakId)
|
||||||
if (!output_stream.good())
|
desktopEntryString += fmt::format("X-Flatpak={}\n", flatpakId);
|
||||||
{
|
|
||||||
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;
|
|
||||||
|
|
||||||
#elif BOOST_OS_WINDOWS
|
std::ofstream outputStream(output_path.utf8_string());
|
||||||
IShellLinkW *shell_link;
|
if (!outputStream.good())
|
||||||
HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink, reinterpret_cast<LPVOID*>(&shell_link));
|
{
|
||||||
if (SUCCEEDED(hres))
|
auto errorMsg = formatWxString(_("Failed to save desktop entry to {}"), output_path.utf8_string());
|
||||||
{
|
wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
|
||||||
const auto description = wxString::Format("Play %s on Cemu", title_name);
|
return;
|
||||||
const auto args = wxString::Format("-t %016llx", title_id);
|
}
|
||||||
|
outputStream << desktopEntryString;
|
||||||
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<LPVOID*>(&shell_link_file));
|
|
||||||
if (SUCCEEDED(hres))
|
|
||||||
{
|
|
||||||
hres = shell_link_file->Save(output_path.wc_str(), TRUE);
|
|
||||||
shell_link_file->Release();
|
|
||||||
}
|
|
||||||
shell_link->Release();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
#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<fs::path> 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<LPVOID*>(&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<LPVOID*>(&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
|
|
@ -295,32 +295,6 @@ wxWindow* InputSettings2::initialize_page(size_t index)
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<size_t, size_t> 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<ControllerBase> InputSettings2::get_active_controller() const
|
std::shared_ptr<ControllerBase> InputSettings2::get_active_controller() const
|
||||||
{
|
{
|
||||||
auto& page_data = get_current_page_data();
|
auto& page_data = get_current_page_data();
|
||||||
|
@ -771,14 +745,16 @@ void InputSettings2::on_emulated_controller_dropdown(wxCommandEvent& event)
|
||||||
wxWindowUpdateLocker lock(emulated_controllers);
|
wxWindowUpdateLocker lock(emulated_controllers);
|
||||||
|
|
||||||
bool is_gamepad_selected = false;
|
bool is_gamepad_selected = false;
|
||||||
|
bool is_wpad_selected = false;
|
||||||
const auto selected = emulated_controllers->GetSelection();
|
const auto selected = emulated_controllers->GetSelection();
|
||||||
const auto selected_value = emulated_controllers->GetStringSelection();
|
const auto selected_value = emulated_controllers->GetStringSelection();
|
||||||
if(selected != wxNOT_FOUND)
|
if(selected != wxNOT_FOUND)
|
||||||
{
|
{
|
||||||
is_gamepad_selected = selected_value == to_wxString(EmulatedController::type_to_string(EmulatedController::Type::VPAD));
|
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->Clear();
|
||||||
emulated_controllers->AppendString(_("Disabled"));
|
emulated_controllers->AppendString(_("Disabled"));
|
||||||
|
@ -786,7 +762,7 @@ void InputSettings2::on_emulated_controller_dropdown(wxCommandEvent& event)
|
||||||
if (vpad_count < InputManager::kMaxVPADControllers || is_gamepad_selected)
|
if (vpad_count < InputManager::kMaxVPADControllers || is_gamepad_selected)
|
||||||
emulated_controllers->Append(to_wxString(EmulatedController::type_to_string(EmulatedController::Type::VPAD)));
|
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::Pro)));
|
||||||
emulated_controllers->AppendString(to_wxString(EmulatedController::type_to_string(EmulatedController::Type::Classic)));
|
emulated_controllers->AppendString(to_wxString(EmulatedController::type_to_string(EmulatedController::Type::Classic)));
|
||||||
|
|
|
@ -27,9 +27,6 @@ private:
|
||||||
|
|
||||||
wxWindow* initialize_page(size_t index);
|
wxWindow* initialize_page(size_t index);
|
||||||
|
|
||||||
// count active <vpad, wpad> controllers
|
|
||||||
std::pair<size_t, size_t> get_emulated_controller_types() const;
|
|
||||||
|
|
||||||
// currently selected controller from active tab
|
// currently selected controller from active tab
|
||||||
std::shared_ptr<ControllerBase> get_active_controller() const;
|
std::shared_ptr<ControllerBase> get_active_controller() const;
|
||||||
|
|
||||||
|
|
|
@ -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.03 0.99 -0.13
|
||||||
0.01 0.13 0.99
|
0.01 0.13 0.99
|
||||||
|
|
||||||
Turned 45° to the right:
|
Turned 45° to the right:
|
||||||
0.71 -0.03 0.71
|
0.71 -0.03 0.71
|
||||||
0.12 0.99 -0.08
|
0.12 0.99 -0.08
|
||||||
-0.70 0.14 0.70
|
-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.08 -0.03 1.00 -> Z points towards person
|
||||||
0.15 0.99 0.01
|
0.15 0.99 0.01
|
||||||
-0.99 0.15 0.09 -> DRC Z-Axis now points towards X-minus
|
-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
|
-1.00 -0.01 0.06
|
||||||
0.00 0.99 0.15
|
0.00 0.99 0.15
|
||||||
-0.06 0.15 -0.99
|
-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.17 -0.01 -0.99
|
||||||
-0.13 0.99 0.02
|
-0.13 0.99 0.02
|
||||||
0.98 0.13 -0.17
|
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.99 -0.03 -0.11
|
||||||
0.01 0.99 -0.13
|
0.01 0.99 -0.13
|
||||||
0.12 0.12 0.99
|
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.66 -0.75 -0.03
|
||||||
0.74 0.66 -0.11
|
0.74 0.66 -0.11
|
||||||
0.10 0.05 0.99
|
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.03 -1.00 -0.00
|
||||||
0.99 -0.03 -0.15
|
0.99 -0.03 -0.15
|
||||||
0.15 -0.01 0.99
|
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.75 0.65 -0.11
|
||||||
-0.65 0.76 0.07
|
-0.65 0.76 0.07
|
||||||
0.12 0.02 0.99
|
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.99 -0.05 -0.10
|
||||||
-0.10 0.01 -0.99
|
-0.10 0.01 -0.99
|
||||||
0.05 1.00 0.01
|
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.09 -0.01 1.00
|
||||||
-0.01 -1.00 -0.01
|
-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.99 -0.03 -0.15
|
||||||
-0.04 -1.00 -0.08
|
-0.04 -1.00 -0.08
|
||||||
-0.15 0.09 -0.99
|
-0.15 0.09 -0.99
|
||||||
|
|
|
@ -30,6 +30,28 @@
|
||||||
<string>${MACOSX_BUNDLE_CATEGORY}</string>
|
<string>${MACOSX_BUNDLE_CATEGORY}</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>${MACOSX_MINIMUM_SYSTEM_VERSION}</string>
|
<string>${MACOSX_MINIMUM_SYSTEM_VERSION}</string>
|
||||||
|
<key>CFBundleLocalizations</key>
|
||||||
|
<array>
|
||||||
|
<string>ca</string>
|
||||||
|
<string>de</string>
|
||||||
|
<string>en</string>
|
||||||
|
<string>es</string>
|
||||||
|
<string>fr</string>
|
||||||
|
<string>he</string>
|
||||||
|
<string>hu</string>
|
||||||
|
<string>it</string>
|
||||||
|
<string>ja</string>
|
||||||
|
<string>ko</string>
|
||||||
|
<string>nb</string>
|
||||||
|
<string>nl</string>
|
||||||
|
<string>pl</string>
|
||||||
|
<string>pt</string>
|
||||||
|
<string>ru</string>
|
||||||
|
<string>sv</string>
|
||||||
|
<string>tr</string>
|
||||||
|
<string>uk</string>
|
||||||
|
<string>zh</string>
|
||||||
|
</array>
|
||||||
<key>CFBundleDocumentTypes</key>
|
<key>CFBundleDocumentTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue