From 2477bad06b77aa03bbc9a1a94f9a1cc9f0d512d2 Mon Sep 17 00:00:00 2001 From: Samuliak Date: Thu, 25 Jul 2024 11:18:35 +0200 Subject: [PATCH] start metal backend --- CMakeLists.txt | 7 +- src/Cafe/CMakeLists.txt | 10 + src/Cafe/GraphicPack/GraphicPack2.cpp | 42 ++-- .../HW/Latte/Renderer/Metal/MetalRenderer.cpp | 198 ++++++++++++++++++ .../HW/Latte/Renderer/Metal/MetalRenderer.h | 142 +++++++++++++ src/Cafe/HW/Latte/Renderer/Renderer.h | 7 +- src/Cemu/Logging/CemuLogging.cpp | 3 +- src/Cemu/Logging/CemuLogging.h | 5 +- src/config/CemuConfig.h | 19 +- src/gui/CMakeLists.txt | 4 +- src/gui/GameProfileWindow.cpp | 24 ++- src/gui/GeneralSettings2.cpp | 86 ++++---- src/gui/LoggingWindow.cpp | 5 +- src/gui/MainWindow.cpp | 61 +++--- src/gui/PadViewFrame.cpp | 9 +- src/gui/canvas/MetalCanvas.cpp | 63 ++++++ src/gui/canvas/MetalCanvas.h | 19 ++ src/gui/guiWrapper.cpp | 9 +- src/imgui/CMakeLists.txt | 2 + 19 files changed, 586 insertions(+), 129 deletions(-) create mode 100644 src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp create mode 100644 src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h create mode 100644 src/gui/canvas/MetalCanvas.cpp create mode 100644 src/gui/canvas/MetalCanvas.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b5f3881..c3b940f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,6 +78,7 @@ endif() option(ENABLE_OPENGL "Enables the OpenGL backend" ON) option(ENABLE_VULKAN "Enables the Vulkan backend" ON) +option(ENABLE_METAL "Enables the Metal backend" ON) option(ENABLE_DISCORD_RPC "Enables the Discord Rich Presence feature" ON) @@ -190,9 +191,9 @@ if (ENABLE_WXWIDGETS) endif() if (ENABLE_CUBEB) - if (NOT ENABLE_VCPKG) - find_package(cubeb) - endif() + #if (NOT ENABLE_VCPKG) + #find_package(cubeb) + #endif() if (NOT cubeb_FOUND) option(BUILD_TESTS "" OFF) option(BUILD_TOOLS "" OFF) diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index 0fb7a44b..f7f25644 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -524,6 +524,16 @@ if(APPLE) target_sources(CemuCafe PRIVATE "HW/Latte/Renderer/Vulkan/CocoaSurface.mm") endif() +if(ENABLE_METAL) + if(APPLE) + target_sources(CemuCafe PRIVATE + HW/Latte/Renderer/Metal/MetalRenderer.cpp + ) + else() + message(FATAL_ERROR "Metal is only supported on macOS") + endif() +endif() + set_property(TARGET CemuCafe PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") target_include_directories(CemuCafe PUBLIC "../") diff --git a/src/Cafe/GraphicPack/GraphicPack2.cpp b/src/Cafe/GraphicPack/GraphicPack2.cpp index 27d423b9..3fbfb518 100644 --- a/src/Cafe/GraphicPack/GraphicPack2.cpp +++ b/src/Cafe/GraphicPack/GraphicPack2.cpp @@ -109,7 +109,7 @@ bool GraphicPack2::LoadGraphicPack(const fs::path& rulesPath, IniParser& rules) gp->SetActivePreset(kv.first, kv.second, false); } - + gp->SetEnabled(enabled); } @@ -141,7 +141,7 @@ bool GraphicPack2::DeactivateGraphicPack(const std::shared_ptr& gr if (!graphic_pack->IsActivated()) return false; - const auto it = std::find_if(s_active_graphic_packs.begin(), s_active_graphic_packs.end(), + const auto it = std::find_if(s_active_graphic_packs.begin(), s_active_graphic_packs.end(), [graphic_pack](const GraphicPackPtr& gp) { return gp->GetNormalizedPathString() == graphic_pack->GetNormalizedPathString(); @@ -269,6 +269,8 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules) m_renderer_api = RendererAPI::Vulkan; else if (boost::iequals(*option_rendererFilter, "opengl")) m_renderer_api = RendererAPI::OpenGL; + else if (boost::iequals(*option_rendererFilter, "metal")) + m_renderer_api = RendererAPI::Metal; else cemuLog_log(LogType::Force, "Unknown value '{}' for rendererFilter option", *option_rendererFilter); } @@ -348,7 +350,7 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules) cemuLog_log(LogType::Force, "Graphic pack \"{}\": Preset in line {} skipped because it has no name option defined", m_name, rules.GetCurrentSectionLineNumber()); continue; } - + const auto category = rules.FindOption("category"); const auto condition = rules.FindOption("condition"); const auto default_selected = rules.FindOption("default"); @@ -420,12 +422,12 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules) { // store by category std::unordered_map> tmp_map; - + // all vars must be defined in the default preset vars before for (const auto& entry : m_presets) { tmp_map[entry->category].emplace_back(entry); - + for (auto& kv : entry->variables) { const auto it = m_preset_vars.find(kv.first); @@ -560,7 +562,7 @@ void GraphicPack2::ValidatePresetSelections() // // example: a preset category might be hidden entirely (e.g. due to a separate advanced options dropdown) // how to handle: leave the previously selected preset - // + // // the logic is therefore as follows: // if there is a preset category with at least 1 visible preset entry then make sure one of those is actually selected // for completely hidden preset categories we leave the selection as-is @@ -624,17 +626,17 @@ bool GraphicPack2::SetActivePreset(std::string_view category, std::string_view n // disable currently active preset std::for_each(m_presets.begin(), m_presets.end(), [category](PresetPtr& p) { - if(p->category == category) + if(p->category == category) p->active = false; }); - + if (name.empty()) return true; - + // enable new preset const auto it = std::find_if(m_presets.cbegin(), m_presets.cend(), [category, name](const PresetPtr& preset) { - return preset->category == category && preset->name == name; + return preset->category == category && preset->name == name; }); bool result; @@ -775,7 +777,7 @@ std::optional GraphicPack2::GetPresetVariable(const std return it->second; } } - + for (const auto& preset : presets) { if (!preset->visible) @@ -785,7 +787,7 @@ std::optional GraphicPack2::GetPresetVariable(const std return it->second; } } - + const auto it = std::find_if(m_preset_vars.cbegin(), m_preset_vars.cend(), [&var_name](auto p) { return p.first == var_name; }); if (it != m_preset_vars.cend()) { @@ -831,7 +833,7 @@ void GraphicPack2::_iterateReplacedFiles(const fs::path& currentPath, bool isAOC virtualMountPath = fs::path("vol/content/") / virtualMountPath; } fscDeviceRedirect_add(virtualMountPath.generic_string(), it.path().generic_string(), m_fs_priority); - } + } } } @@ -851,7 +853,7 @@ void GraphicPack2::LoadReplacedFiles() std::error_code ec; if (fs::exists(contentPath, ec)) { - // setup redirections + // setup redirections fscDeviceRedirect_map(); _iterateReplacedFiles(contentPath, false); } @@ -864,7 +866,7 @@ void GraphicPack2::LoadReplacedFiles() uint64 aocTitleId = CafeSystem::GetForegroundTitleId(); aocTitleId = aocTitleId & 0xFFFFFFFFULL; aocTitleId |= 0x0005000c00000000ULL; - // setup redirections + // setup redirections fscDeviceRedirect_map(); _iterateReplacedFiles(aocPath, true); } @@ -980,7 +982,7 @@ bool GraphicPack2::Activate() // enable patch groups EnablePatches(); - + // load replaced files LoadReplacedFiles(); @@ -1026,7 +1028,7 @@ bool GraphicPack2::Deactivate() m_output_shader_source.clear(); m_upscaling_shader_source.clear(); m_downscaling_shader_source.clear(); - + if (HasCustomVSyncFrequency()) { m_vsync_frequency = -1; @@ -1058,7 +1060,7 @@ const std::string* GraphicPack2::FindCustomShaderSource(uint64 shaderBaseHash, u std::unordered_map> GraphicPack2::GetCategorizedPresets(std::vector& order) const { order.clear(); - + std::unordered_map> result; for(const auto& entry : m_presets) { @@ -1067,13 +1069,13 @@ std::unordered_map> GraphicPac if (it == order.cend()) order.emplace_back(entry->category); } - + return result; } bool GraphicPack2::HasShaders() const { - return !GetCustomShaders().empty() + return !GetCustomShaders().empty() || !m_output_shader_source.empty() || !m_upscaling_shader_source.empty() || !m_downscaling_shader_source.empty(); } diff --git a/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp new file mode 100644 index 00000000..0e0bfb9a --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp @@ -0,0 +1,198 @@ +#include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h" + +void MetalRenderer::InitializeLayer(const Vector2i& size, bool mainWindow) { + /* + const auto& windowInfo = gui_getWindowInfo().window_main; + + NSView* view = (NS::View*)handle; + + MetalView* childView = [[MetalView alloc] initWithFrame:view.bounds]; + childView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; + childView.wantsLayer = YES; + + [view addSubview:childView]; + + VkMetalSurfaceCreateInfoEXT surface; + surface.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT; + surface.pNext = NULL; + surface.flags = 0; + surface.pLayer = (CAMetalLayer*)childView.layer; + */ +} + +void MetalRenderer::Initialize() { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::Shutdown() { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +bool MetalRenderer::IsPadWindowActive() { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +bool MetalRenderer::GetVRAMInfo(int& usageInMB, int& totalInMB) const { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::ClearColorbuffer(bool padView) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::DrawEmptyFrame(bool mainWindow) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::SwapBuffers(bool swapTV, bool swapDRC) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutputShader* shader, bool useLinearTexFilter, + sint32 imageX, sint32 imageY, sint32 imageWidth, sint32 imageHeight, + bool padView, bool clearBackground) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} +bool MetalRenderer::BeginFrame(bool mainWindow) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::Flush(bool waitIdle) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::NotifyLatteCommandProcessorIdle() { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::AppendOverlayDebugInfo() { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::renderTarget_setViewport(float x, float y, float width, float height, float nearZ, float farZ, bool halfZ) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::renderTarget_setScissor(sint32 scissorX, sint32 scissorY, sint32 scissorWidth, sint32 scissorHeight) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +LatteCachedFBO* MetalRenderer::rendertarget_createCachedFBO(uint64 key) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::rendertarget_deleteCachedFBO(LatteCachedFBO* fbo) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::rendertarget_bindFramebufferObject(LatteCachedFBO* cfbo) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void* MetalRenderer::texture_acquireTextureUploadBuffer(uint32 size) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::texture_releaseTextureUploadBuffer(uint8* mem) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +TextureDecoder* MetalRenderer::texture_chooseDecodedFormat(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, uint32 width, uint32 height) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::texture_clearSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::texture_loadSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 compressedImageSize) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::texture_clearColorSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::texture_clearDepthSlice(LatteTexture* hostTexture, uint32 sliceIndex, sint32 mipIndex, bool clearDepth, bool clearStencil, float depthValue, uint32 stencilValue) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +LatteTexture* MetalRenderer::texture_createTextureEx(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::texture_setLatteTexture(LatteTextureView* textureView, uint32 textureUnit) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::texture_copyImageSubData(LatteTexture* src, sint32 srcMip, sint32 effectiveSrcX, sint32 effectiveSrcY, sint32 srcSlice, LatteTexture* dst, sint32 dstMip, sint32 effectiveDstX, sint32 effectiveDstY, sint32 dstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight, sint32 srcDepth) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +LatteTextureReadbackInfo* MetalRenderer::texture_createReadback(LatteTextureView* textureView) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* sourceTexture, sint32 srcMip, sint32 srcSlice, LatteTexture* destinationTexture, sint32 dstMip, sint32 dstSlice, sint32 width, sint32 height) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::bufferCache_init(const sint32 bufferSize) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::bufferCache_upload(uint8* buffer, sint32 size, uint32 bufferOffset) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::bufferCache_copy(uint32 srcOffset, uint32 dstOffset, uint32 size) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::bufferCache_copyStreamoutToMainBuffer(uint32 srcOffset, uint32 dstOffset, uint32 size) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::buffer_bindVertexBuffer(uint32 bufferIndex, uint32 offset, uint32 size) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::buffer_bindUniformBuffer(LatteConst::ShaderType shaderType, uint32 bufferIndex, uint32 offset, uint32 size) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +RendererShader* MetalRenderer::shader_create(RendererShader::ShaderType type, uint64 baseHash, uint64 auxHash, const std::string& source, bool compileAsync, bool isGfxPackSource) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::streamout_setupXfbBuffer(uint32 bufferIndex, sint32 ringBufferOffset, uint32 rangeAddr, uint32 rangeSize) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::streamout_begin() { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::streamout_rendererFinishDrawcall() { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::draw_beginSequence() { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::draw_endSequence() { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void* MetalRenderer::indexData_reserveIndexMemory(uint32 size, uint32& offset, uint32& bufferIndex) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} + +void MetalRenderer::indexData_uploadIndexMemory(uint32 offset, uint32 size) { + cemuLog_logDebug(LogType::Force, "not implemented"); +} diff --git a/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h b/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h new file mode 100644 index 00000000..017c32fd --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h @@ -0,0 +1,142 @@ +#pragma once + +#include "Cafe/HW/Latte/Renderer/Renderer.h" + +class MetalRenderer : public Renderer +{ +public: + ~MetalRenderer() = default; + + RendererAPI GetType() override + { + return RendererAPI::Metal; + } + + static MetalRenderer* GetInstance() { + return static_cast(g_renderer.get()); + } + + void InitializeLayer(const Vector2i& size, bool mainWindow); + + void Initialize() override; + void Shutdown() override; + bool IsPadWindowActive() override; + + bool GetVRAMInfo(int& usageInMB, int& totalInMB) const override; + + void ClearColorbuffer(bool padView) override; + void DrawEmptyFrame(bool mainWindow) override; + void SwapBuffers(bool swapTV, bool swapDRC) override; + + void HandleScreenshotRequest(LatteTextureView* texView, bool padView) override { + cemuLog_logDebug(LogType::Force, "Screenshots are not yet supported on Metal"); + } + + void DrawBackbufferQuad(LatteTextureView* texView, RendererOutputShader* shader, bool useLinearTexFilter, + sint32 imageX, sint32 imageY, sint32 imageWidth, sint32 imageHeight, + bool padView, bool clearBackground) override; + bool BeginFrame(bool mainWindow) override; + + // flush control + void Flush(bool waitIdle = false) override; // called when explicit flush is required (e.g. by imgui) + void NotifyLatteCommandProcessorIdle() override; // called when command processor has no more commands available or when stalled + + // imgui + bool ImguiBegin(bool mainWindow) override { + cemuLog_logDebug(LogType::Force, "Imgui is not yet supported on Metal"); + }; + + void ImguiEnd() override { + cemuLog_logDebug(LogType::Force, "Imgui is not yet supported on Metal"); + }; + + ImTextureID GenerateTexture(const std::vector& data, const Vector2i& size) override { + cemuLog_logDebug(LogType::Force, "Imgui is not yet supported on Metal"); + }; + + void DeleteTexture(ImTextureID id) override { + cemuLog_logDebug(LogType::Force, "Imgui is not yet supported on Metal"); + }; + + void DeleteFontTextures() override { + cemuLog_logDebug(LogType::Force, "Imgui is not yet supported on Metal"); + }; + + void AppendOverlayDebugInfo() override; + + // rendertarget + void renderTarget_setViewport(float x, float y, float width, float height, float nearZ, float farZ, bool halfZ = false) override; + void renderTarget_setScissor(sint32 scissorX, sint32 scissorY, sint32 scissorWidth, sint32 scissorHeight) override; + + LatteCachedFBO* rendertarget_createCachedFBO(uint64 key) override; + void rendertarget_deleteCachedFBO(LatteCachedFBO* fbo) override; + void rendertarget_bindFramebufferObject(LatteCachedFBO* cfbo) override; + + // texture functions + void* texture_acquireTextureUploadBuffer(uint32 size) override; + void texture_releaseTextureUploadBuffer(uint8* mem) override; + + TextureDecoder* texture_chooseDecodedFormat(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, uint32 width, uint32 height) override; + + void texture_clearSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex) override; + void texture_loadSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 compressedImageSize) override; + void texture_clearColorSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a) override; + void texture_clearDepthSlice(LatteTexture* hostTexture, uint32 sliceIndex, sint32 mipIndex, bool clearDepth, bool clearStencil, float depthValue, uint32 stencilValue) override; + + LatteTexture* texture_createTextureEx(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth) override; + + void texture_setLatteTexture(LatteTextureView* textureView, uint32 textureUnit) override; + void texture_copyImageSubData(LatteTexture* src, sint32 srcMip, sint32 effectiveSrcX, sint32 effectiveSrcY, sint32 srcSlice, LatteTexture* dst, sint32 dstMip, sint32 effectiveDstX, sint32 effectiveDstY, sint32 dstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight, sint32 srcDepth) override; + + LatteTextureReadbackInfo* texture_createReadback(LatteTextureView* textureView) override; + + // surface copy + void surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* sourceTexture, sint32 srcMip, sint32 srcSlice, LatteTexture* destinationTexture, sint32 dstMip, sint32 dstSlice, sint32 width, sint32 height) override; + + // buffer cache + void bufferCache_init(const sint32 bufferSize) override; + void bufferCache_upload(uint8* buffer, sint32 size, uint32 bufferOffset) override; + void bufferCache_copy(uint32 srcOffset, uint32 dstOffset, uint32 size) override; + void bufferCache_copyStreamoutToMainBuffer(uint32 srcOffset, uint32 dstOffset, uint32 size) override; + + void buffer_bindVertexBuffer(uint32 bufferIndex, uint32 offset, uint32 size) override; + void buffer_bindUniformBuffer(LatteConst::ShaderType shaderType, uint32 bufferIndex, uint32 offset, uint32 size) override; + + // shader + RendererShader* shader_create(RendererShader::ShaderType type, uint64 baseHash, uint64 auxHash, const std::string& source, bool compileAsync, bool isGfxPackSource) override; + + // streamout + void streamout_setupXfbBuffer(uint32 bufferIndex, sint32 ringBufferOffset, uint32 rangeAddr, uint32 rangeSize) override; + void streamout_begin() override; + void streamout_rendererFinishDrawcall() override; + + // core drawing logic + void draw_beginSequence() override; + void draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst) override; + void draw_endSequence() override; + + // index + void* indexData_reserveIndexMemory(uint32 size, uint32& offset, uint32& bufferIndex) override; + void indexData_uploadIndexMemory(uint32 offset, uint32 size) override; + + // occlusion queries + LatteQueryObject* occlusionQuery_create() override { + cemuLog_logDebug(LogType::Force, "Occlusion queries are not yet supported on Metal"); + } + + void occlusionQuery_destroy(LatteQueryObject* queryObj) override { + cemuLog_logDebug(LogType::Force, "Occlusion queries are not yet supported on Metal"); + } + + void occlusionQuery_flush() override { + cemuLog_logDebug(LogType::Force, "Occlusion queries are not yet supported on Metal"); + } + + void occlusionQuery_updateState() override { + cemuLog_logDebug(LogType::Force, "Occlusion queries are not yet supported on Metal"); + } + + +protected: + //CA::MetalLayer* m_metalLayer; +}; diff --git a/src/Cafe/HW/Latte/Renderer/Renderer.h b/src/Cafe/HW/Latte/Renderer/Renderer.h index 0b694bb9..a94ad155 100644 --- a/src/Cafe/HW/Latte/Renderer/Renderer.h +++ b/src/Cafe/HW/Latte/Renderer/Renderer.h @@ -33,6 +33,7 @@ enum class RendererAPI { OpenGL, Vulkan, + Metal, MAX }; @@ -66,9 +67,9 @@ public: virtual void SwapBuffers(bool swapTV, bool swapDRC) = 0; virtual void HandleScreenshotRequest(LatteTextureView* texView, bool padView){} - - virtual void DrawBackbufferQuad(LatteTextureView* texView, RendererOutputShader* shader, bool useLinearTexFilter, - sint32 imageX, sint32 imageY, sint32 imageWidth, sint32 imageHeight, + + virtual void DrawBackbufferQuad(LatteTextureView* texView, RendererOutputShader* shader, bool useLinearTexFilter, + sint32 imageX, sint32 imageY, sint32 imageWidth, sint32 imageHeight, bool padView, bool clearBackground) = 0; virtual bool BeginFrame(bool mainWindow) = 0; diff --git a/src/Cemu/Logging/CemuLogging.cpp b/src/Cemu/Logging/CemuLogging.cpp index 5cde2a7f..6b77b226 100644 --- a/src/Cemu/Logging/CemuLogging.cpp +++ b/src/Cemu/Logging/CemuLogging.cpp @@ -59,6 +59,7 @@ const std::map g_logging_window_mapping {LogType::TextureReadback, "Texture readback"}, {LogType::OpenGLLogging, "OpenGL debug output"}, {LogType::VulkanValidation, "Vulkan validation layer"}, + {LogType::MetalLogging, "Metal debug output"}, }; bool cemuLog_advancedPPCLoggingEnabled() @@ -158,7 +159,7 @@ bool cemuLog_log(LogType type, std::string_view text) bool cemuLog_log(LogType type, std::u8string_view text) { - std::basic_string_view s((char*)text.data(), text.size()); + std::basic_string_view s((char*)text.data(), text.size()); return cemuLog_log(type, s); } diff --git a/src/Cemu/Logging/CemuLogging.h b/src/Cemu/Logging/CemuLogging.h index a671ce51..edca3241 100644 --- a/src/Cemu/Logging/CemuLogging.h +++ b/src/Cemu/Logging/CemuLogging.h @@ -20,6 +20,7 @@ enum class LogType : sint32 OpenGLLogging = 10, // OpenGL debug logging TextureCache = 11, // texture cache warnings and info VulkanValidation = 12, // Vulkan validation layer + MetalLogging = 13, // Metal debug logging Patches = 14, CoreinitMem = 8, // coreinit memory functions CoreinitMP = 15, @@ -52,7 +53,7 @@ enum class LogType : sint32 template <> struct fmt::formatter : formatter { template - auto format(std::u8string_view v, FormatContext& ctx) + auto format(std::u8string_view v, FormatContext& ctx) { string_view s((char*)v.data(), v.size()); return formatter::format(s, ctx); @@ -96,7 +97,7 @@ bool cemuLog_log(LogType type, std::basic_string formatStr, TArgs&&... args) } return true; } - + template bool cemuLog_log(LogType type, const T* format, TArgs&&... args) { diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index 2a1d29cb..0dc23ce1 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -32,7 +32,7 @@ struct GameEntry std::wstring save_folder; std::wstring update_folder; std::wstring dlc_folder; - + uint64 legacy_time_played = 0; uint64 legacy_last_played = 0; @@ -74,6 +74,7 @@ enum GraphicAPI { kOpenGL = 0, kVulkan, + kMetal, }; enum AudioChannels @@ -105,7 +106,7 @@ enum class ScreenPosition kTopRight, kBottomLeft, kBottomCenter, - kBottomRight, + kBottomRight, }; enum class PrecompiledShaderOption @@ -134,7 +135,7 @@ enum class CPUMode ENABLE_ENUM_ITERATORS(CPUMode, CPUMode::SinglecoreInterpreter, CPUMode::Auto); -enum class CPUModeLegacy +enum class CPUModeLegacy { SinglecoreInterpreter = 0, SinglecoreRecompiler = 1, @@ -270,7 +271,7 @@ struct fmt::formatter : formatter { case CafeConsoleRegion::TWN: name = wxTRANSLATE("Taiwan"); break; case CafeConsoleRegion::Auto: name = wxTRANSLATE("Auto"); break; default: name = wxTRANSLATE("many"); break; - + } return formatter::format(name, ctx); } @@ -312,7 +313,7 @@ struct fmt::formatter : formatter { case CrashDump::Lite: name = "Lite"; break; case CrashDump::Full: name = "Full"; break; default: name = "unknown"; break; - + } return formatter::format(name, ctx); } @@ -363,7 +364,7 @@ struct CemuConfig ConfigValue advanced_ppc_logging{ false }; ConfigValue permanent_storage{ true }; - + ConfigValue language{ wxLANGUAGE_DEFAULT }; ConfigValue use_discord_presence{ true }; ConfigValue mlc_path{}; @@ -387,7 +388,7 @@ struct CemuConfig // optimized access std::set game_cache_favorites; // per titleId - + struct _path_hash { std::size_t operator()(const fs::path& path) const { return fs::hash_value(path); @@ -514,7 +515,7 @@ struct CemuConfig NetworkService GetAccountNetworkService(uint32 persistentId); void SetAccountSelectedService(uint32 persistentId, NetworkService serviceIndex); - + // emulated usb devices struct { @@ -530,5 +531,3 @@ struct CemuConfig typedef XMLDataConfig XMLCemuConfig_t; extern XMLCemuConfig_t g_config; inline CemuConfig& GetConfig() { return g_config.data(); } - - diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 02f96a9c..df98c1f1 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -1,9 +1,11 @@ -add_library(CemuGui +add_library(CemuGui canvas/IRenderCanvas.h canvas/OpenGLCanvas.cpp canvas/OpenGLCanvas.h canvas/VulkanCanvas.cpp canvas/VulkanCanvas.h + canvas/MetalCanvas.cpp + canvas/MetalCanvas.h CemuApp.cpp CemuApp.h CemuUpdateWindow.cpp diff --git a/src/gui/GameProfileWindow.cpp b/src/gui/GameProfileWindow.cpp index f15395e4..fe332702 100644 --- a/src/gui/GameProfileWindow.cpp +++ b/src/gui/GameProfileWindow.cpp @@ -61,7 +61,7 @@ GameProfileWindow::GameProfileWindow(wxWindow* parent, uint64_t title_id) const sint32 m_cpu_modeNChoices = std::size(cpu_modes); m_cpu_mode = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, m_cpu_modeNChoices, cpu_modes, 0); m_cpu_mode->SetToolTip(_("Set the CPU emulation mode")); - first_row->Add(m_cpu_mode, 0, wxALL, 5); + first_row->Add(m_cpu_mode, 0, wxALL, 5); first_row->Add(new wxStaticText(box, wxID_ANY, _("Thread quantum")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); @@ -112,10 +112,14 @@ GameProfileWindow::GameProfileWindow(wxWindow* parent, uint64_t title_id) first_row->Add(new wxStaticText(panel, wxID_ANY, _("Graphics API")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); - wxString gapi_values[] = { "", "OpenGL", "Vulkan" }; + wxString gapi_values[] = { "", "OpenGL", "Vulkan", +#ifdef __APPLE__ + "Metal" +#endif + }; m_graphic_api = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, (int)std::size(gapi_values), gapi_values); first_row->Add(m_graphic_api, 0, wxALL, 5); - + first_row->Add(new wxStaticText(panel, wxID_ANY, _("Shader multiplication accuracy")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); wxString mul_values[] = { _("false"), _("true")}; @@ -249,7 +253,7 @@ void GameProfileWindow::ApplyProfile() // general m_load_libs->SetValue(m_game_profile.m_loadSharedLibraries.value()); m_start_with_padview->SetValue(m_game_profile.m_startWithPadView); - + // cpu // wxString cpu_modes[] = { _("Singlecore-Interpreter"), _("Singlecore-Recompiler"), _("Triplecore-Recompiler"), _("Auto (recommended)") }; switch(m_game_profile.m_cpuMode.value()) @@ -258,9 +262,9 @@ void GameProfileWindow::ApplyProfile() case CPUMode::SinglecoreRecompiler: m_cpu_mode->SetSelection(1); break; case CPUMode::DualcoreRecompiler: m_cpu_mode->SetSelection(2); break; case CPUMode::MulticoreRecompiler: m_cpu_mode->SetSelection(2); break; - default: m_cpu_mode->SetSelection(3); + default: m_cpu_mode->SetSelection(3); } - + m_thread_quantum->SetStringSelection(fmt::format("{}", m_game_profile.m_threadQuantum)); // gpu @@ -275,7 +279,7 @@ void GameProfileWindow::ApplyProfile() // controller auto profiles = InputManager::get_profiles(); - + for (const auto& cb : m_controller_profile) { cb->Clear(); @@ -293,7 +297,7 @@ void GameProfileWindow::ApplyProfile() const auto& v = m_game_profile.m_controllerProfile[i].value(); m_controller_profile[i]->SetStringSelection(wxString::FromUTF8(v)); } - + else m_controller_profile[i]->SetSelection(wxNOT_FOUND); } @@ -317,7 +321,7 @@ void GameProfileWindow::SaveProfile() m_game_profile.m_cpuMode = CPUMode::Auto; } - + const wxString thread_quantum = m_thread_quantum->GetStringSelection(); if (!thread_quantum.empty()) { @@ -365,4 +369,4 @@ void GameProfileWindow::SetSliderValue(wxSlider* slider, sint32 new_value) const slider_event.SetEventObject(slider); slider_event.SetClientData((void*)IsFrozen()); wxPostEvent(slider->GetEventHandler(), slider_event); -} \ No newline at end of file +} diff --git a/src/gui/GeneralSettings2.cpp b/src/gui/GeneralSettings2.cpp index 08395cd3..4f7a7fe7 100644 --- a/src/gui/GeneralSettings2.cpp +++ b/src/gui/GeneralSettings2.cpp @@ -101,7 +101,7 @@ public: Account& GetAccount() { return m_account; } const Account& GetAccount() const { return m_account; } - + private: Account m_account; }; @@ -165,11 +165,11 @@ wxPanel* GeneralSettings2::AddGeneralPage(wxNotebook* notebook) m_auto_update = new wxCheckBox(box, wxID_ANY, _("Automatically check for updates")); m_auto_update->SetToolTip(_("Automatically checks for new cemu versions on startup")); second_row->Add(m_auto_update, 0, botflag, 5); -#if BOOST_OS_LINUX +#if BOOST_OS_LINUX if (!std::getenv("APPIMAGE")) { m_auto_update->Disable(); - } -#endif + } +#endif second_row->AddSpacer(10); m_save_screenshot = new wxCheckBox(box, wxID_ANY, _("Save screenshot")); m_save_screenshot->SetToolTip(_("Pressing the screenshot key (F12) will save a screenshot directly to the screenshots folder")); @@ -276,12 +276,14 @@ wxPanel* GeneralSettings2::AddGraphicsPage(wxNotebook* notebook) row->Add(new wxStaticText(box, wxID_ANY, _("Graphics API")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); sint32 api_size = 1; - wxString choices[2] = { "OpenGL" }; + wxString choices[3] = { "OpenGL" }; if (g_vulkan_available) { - choices[1] = "Vulkan"; - api_size = 2; + choices[api_size++] = "Vulkan"; } +#ifdef __APPLE__ + choices[api_size++] = "Metal"; +#endif m_graphic_api = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, api_size, choices); m_graphic_api->SetSelection(0); @@ -728,7 +730,7 @@ wxPanel* GeneralSettings2::AddAccountPage(wxNotebook* notebook) auto* row = new wxFlexGridSizer(0, 2, 0, 0); row->SetFlexibleDirection(wxBOTH); row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); - + const wxImage tmp = wxBITMAP_PNG_FROM_DATA(PNG_ERROR).ConvertToImage(); m_validate_online = new wxBitmapButton(box, wxID_ANY, tmp.Scale(16, 16)); m_validate_online->Bind(wxEVT_BUTTON, &GeneralSettings2::OnShowOnlineValidator, this); @@ -738,7 +740,7 @@ wxPanel* GeneralSettings2::AddAccountPage(wxNotebook* notebook) row->Add(m_online_status, 1, wxALL | wxALIGN_CENTRE_VERTICAL, 5); box_sizer->Add(row, 1, wxEXPAND, 5); - + auto* tutorial_link = new wxHyperlinkCtrl(box, wxID_ANY, _("Online play tutorial"), "https://cemu.info/online-guide"); box_sizer->Add(tutorial_link, 0, wxALL, 5); @@ -856,14 +858,14 @@ GeneralSettings2::GeneralSettings2(wxWindow* parent, bool game_launched) notebook->AddPage(AddGeneralPage(notebook), _("General")); notebook->AddPage(AddGraphicsPage(notebook), _("Graphics")); - notebook->AddPage(AddAudioPage(notebook), _("Audio")); + notebook->AddPage(AddAudioPage(notebook), _("Audio")); notebook->AddPage(AddOverlayPage(notebook), _("Overlay")); notebook->AddPage(AddAccountPage(notebook), _("Account")); notebook->AddPage(AddDebugPage(notebook), _("Debug")); Bind(wxEVT_CLOSE_WINDOW, &GeneralSettings2::OnClose, this); - // + // sizer->Add(notebook, 1, wxEXPAND | wxALL, 5); @@ -878,7 +880,7 @@ GeneralSettings2::GeneralSettings2(wxWindow* parent, bool game_launched) ApplyConfig(); HandleGraphicsApiSelection(); - + DisableSettings(game_launched); } @@ -890,7 +892,7 @@ uint32 GeneralSettings2::GetSelectedAccountPersistentId() return dynamic_cast(m_active_account->GetClientObject(active_account))->GetAccount().GetPersistentId(); } -void GeneralSettings2::StoreConfig() +void GeneralSettings2::StoreConfig() { auto* app = (CemuApp*)wxTheApp; auto& config = GetConfig(); @@ -908,7 +910,7 @@ void GeneralSettings2::StoreConfig() { ScreenSaver::SetInhibit(config.disable_screensaver); } - + // -1 is default wx widget value -> set to dummy 0 so mainwindow and padwindow will update it config.window_position = m_save_window_position_size->IsChecked() ? Vector2i{ 0,0 } : Vector2i{-1,-1}; config.window_size = m_save_window_position_size->IsChecked() ? Vector2i{ 0,0 } : Vector2i{-1,-1}; @@ -951,7 +953,7 @@ void GeneralSettings2::StoreConfig() config.pad_channels = kStereo; // (AudioChannels)m_pad_channels->GetSelection(); //config.input_channels = (AudioChannels)m_input_channels->GetSelection(); config.input_channels = kMono; // (AudioChannels)m_input_channels->GetSelection(); - + config.tv_volume = m_tv_volume->GetValue(); config.pad_volume = m_pad_volume->GetValue(); config.input_volume = m_input_volume->GetValue(); @@ -997,16 +999,16 @@ void GeneralSettings2::StoreConfig() } else config.graphic_device_uuid = {}; - + config.vsync = m_vsync->GetSelection(); config.gx2drawdone_sync = m_gx2drawdone_sync->IsChecked(); config.async_compile = m_async_compile->IsChecked(); - + config.upscale_filter = m_upscale_filter->GetSelection(); config.downscale_filter = m_downscale_filter->GetSelection(); config.fullscreen_scaling = m_fullscreen_scaling->GetSelection(); - + config.overlay.position = (ScreenPosition)m_overlay_position->GetSelection(); wxASSERT((int)config.overlay.position <= (int)ScreenPosition::kBottomRight); config.overlay.text_color = m_overlay_font_color->GetColour().GetRGBA(); config.overlay.text_scale = m_overlay_scale->GetSelection() * 25 + 50; @@ -1064,7 +1066,7 @@ void GeneralSettings2::ValidateConfig() void GeneralSettings2::DisableSettings(bool game_launched) { - + } void GeneralSettings2::OnAudioLatencyChanged(wxCommandEvent& event) @@ -1075,7 +1077,7 @@ void GeneralSettings2::OnAudioLatencyChanged(wxCommandEvent& event) void GeneralSettings2::OnVolumeChanged(wxCommandEvent& event) { - + if(event.GetEventObject() == m_input_volume) { std::shared_lock lock(g_audioInputMutex); @@ -1099,7 +1101,7 @@ void GeneralSettings2::OnVolumeChanged(wxCommandEvent& event) g_tvAudio->SetVolume(event.GetInt()); } } - + event.Skip(); } @@ -1112,7 +1114,7 @@ void GeneralSettings2::OnInputVolumeChanged(wxCommandEvent& event) g_padAudio->SetInputVolume(event.GetInt()); g_padVolume = event.GetInt(); } - + event.Skip(); } @@ -1190,7 +1192,7 @@ void GeneralSettings2::UpdateAudioDeviceList() // todo reset global instance of audio device } -void GeneralSettings2::ResetAccountInformation() +void GeneralSettings2::ResetAccountInformation() { m_account_grid->SetSplitterPosition(100); m_active_account->SetSelection(0); @@ -1218,7 +1220,7 @@ void GeneralSettings2::OnAccountCreate(wxCommandEvent& event) Account account(dialog.GetPersistentId(), dialog.GetMiiName().ToStdWstring()); account.Save(); Account::RefreshAccounts(); - + const int index = m_active_account->Append(account.ToString(), new wxAccountData(account)); // update ui @@ -1227,7 +1229,7 @@ void GeneralSettings2::OnAccountCreate(wxCommandEvent& event) m_create_account->Enable(m_active_account->GetCount() < 0xC); m_delete_account->Enable(m_active_account->GetCount() > 1); - + // send main window event wxASSERT(GetParent()); wxCommandEvent refresh_event(wxEVT_ACCOUNTLIST_REFRESH); @@ -1257,7 +1259,7 @@ void GeneralSettings2::OnAccountDelete(wxCommandEvent& event) return; // todo: ask if saves should be deleted too? - + const fs::path path = account.GetFileName(); try { @@ -1275,7 +1277,7 @@ void GeneralSettings2::OnAccountDelete(wxCommandEvent& event) SystemException sys(ex); cemuLog_log(LogType::Force, sys.what()); } - + } void GeneralSettings2::OnAccountSettingsChanged(wxPropertyGridEvent& event) @@ -1330,7 +1332,7 @@ void GeneralSettings2::OnAccountSettingsChanged(wxPropertyGridEvent& event) else if (property->GetName() == kPropertyEmail) { account.SetEmail(value.As().ToStdString()); - + } else if (property->GetName() == kPropertyCountry) { @@ -1338,7 +1340,7 @@ void GeneralSettings2::OnAccountSettingsChanged(wxPropertyGridEvent& event) } else cemu_assert_debug(false); - + account.Save(); Account::RefreshAccounts(); // refresh internal account list UpdateAccountInformation(); // refresh on invalid values @@ -1378,7 +1380,7 @@ void GeneralSettings2::UpdateAccountInformation() gender_property->SetChoiceSelection(std::min(gender_property->GetChoices().GetCount() - 1, (uint32)account.GetGender())); m_account_grid->GetProperty(kPropertyEmail)->SetValueFromString(std::string{ account.GetEmail() }); - + auto* country_property = dynamic_cast(m_account_grid->GetProperty(kPropertyCountry)); wxASSERT(country_property); int index = (country_property)->GetIndexForValue(account.GetCountry()); @@ -1462,7 +1464,7 @@ void GeneralSettings2::HandleGraphicsApiSelection() int selection = m_vsync->GetSelection(); if(selection == wxNOT_FOUND) selection = GetConfig().vsync; - + m_vsync->Clear(); if(m_graphic_api->GetSelection() == 0) { @@ -1494,7 +1496,7 @@ void GeneralSettings2::HandleGraphicsApiSelection() #endif m_vsync->Select(selection); - + m_graphic_device->Enable(); auto devices = VulkanRenderer::GetDevices(); m_graphic_device->Clear(); @@ -1618,7 +1620,7 @@ void GeneralSettings2::ApplyConfig() m_pad_channels->SetSelection(0); //m_input_channels->SetSelection(config.pad_channels); m_input_channels->SetSelection(0); - + SendSliderEvent(m_tv_volume, config.tv_volume); if (!config.tv_device.empty() && m_tv_device->HasClientObjectData()) @@ -1635,7 +1637,7 @@ void GeneralSettings2::ApplyConfig() } else m_tv_device->SetSelection(0); - + SendSliderEvent(m_pad_volume, config.pad_volume); if (!config.pad_device.empty() && m_pad_device->HasClientObjectData()) { @@ -1768,7 +1770,7 @@ void GeneralSettings2::UpdateAudioDevice() } } } - + // pad audio device { const auto selection = m_pad_device->GetSelection(); @@ -1884,14 +1886,14 @@ void GeneralSettings2::OnAudioChannelsSelected(wxCommandEvent& event) { if (config.tv_channels == (AudioChannels)obj->GetSelection()) return; - + config.tv_channels = (AudioChannels)obj->GetSelection(); } else if (obj == m_pad_channels) { if (config.pad_channels == (AudioChannels)obj->GetSelection()) return; - + config.pad_channels = (AudioChannels)obj->GetSelection(); } else @@ -2034,23 +2036,23 @@ void GeneralSettings2::OnShowOnlineValidator(wxCommandEvent& event) const auto selection = m_active_account->GetSelection(); if (selection == wxNOT_FOUND) return; - + const auto* obj = dynamic_cast(m_active_account->GetClientObject(selection)); wxASSERT(obj); const auto& account = obj->GetAccount(); - + const auto validator = account.ValidateOnlineFiles(); if (validator) // everything valid? shouldn't happen return; - + wxString err; err << _("The following error(s) have been found:") << '\n'; - + if (validator.otp == OnlineValidator::FileState::Missing) err << _("otp.bin missing in Cemu directory") << '\n'; else if(validator.otp == OnlineValidator::FileState::Corrupted) err << _("otp.bin is invalid") << '\n'; - + if (validator.seeprom == OnlineValidator::FileState::Missing) err << _("seeprom.bin missing in Cemu directory") << '\n'; else if(validator.seeprom == OnlineValidator::FileState::Corrupted) diff --git a/src/gui/LoggingWindow.cpp b/src/gui/LoggingWindow.cpp index 4026113e..0d25e279 100644 --- a/src/gui/LoggingWindow.cpp +++ b/src/gui/LoggingWindow.cpp @@ -21,7 +21,7 @@ LoggingWindow::LoggingWindow(wxFrame* parent) filter_row->Add(new wxStaticText( this, wxID_ANY, _("Filter")), 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); - wxString choices[] = {"Unsupported APIs calls", "Coreinit Logging", "Coreinit File-Access", "Coreinit Thread-Synchronization", "Coreinit Memory", "Coreinit MP", "Coreinit Thread", "nn::nfp", "GX2", "Audio", "Input", "Socket", "Save", "H264", "Graphic pack patches", "Texture cache", "Texture readback", "OpenGL debug output", "Vulkan validation layer"}; + wxString choices[] = {"Unsupported APIs calls", "Coreinit Logging", "Coreinit File-Access", "Coreinit Thread-Synchronization", "Coreinit Memory", "Coreinit MP", "Coreinit Thread", "nn::nfp", "GX2", "Audio", "Input", "Socket", "Save", "H264", "Graphic pack patches", "Texture cache", "Texture readback", "OpenGL debug output", "Vulkan validation layer", "Metal debug output"}; m_filter = new wxComboBox( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, std::size(choices), choices, 0 ); m_filter->Bind(wxEVT_COMBOBOX, &LoggingWindow::OnFilterChange, this); m_filter->Bind(wxEVT_TEXT, &LoggingWindow::OnFilterChange, this); @@ -83,7 +83,7 @@ void LoggingWindow::Log(std::string_view filter, std::wstring_view message) void LoggingWindow::OnLogMessage(wxLogEvent& event) { - m_log_list->PushEntry(event.GetFilter(), event.GetMessage()); + m_log_list->PushEntry(event.GetFilter(), event.GetMessage()); } void LoggingWindow::OnFilterChange(wxCommandEvent& event) @@ -97,4 +97,3 @@ void LoggingWindow::OnFilterMessageChange(wxCommandEvent& event) m_log_list->SetFilterMessage(m_filter_message->GetValue()); event.Skip(); } - diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index c83ab16b..7f738c2e 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -12,6 +12,7 @@ #include "audio/audioDebuggerWindow.h" #include "gui/canvas/OpenGLCanvas.h" #include "gui/canvas/VulkanCanvas.h" +#include "gui/canvas/MetalCanvas.h" #include "Cafe/OS/libs/nfc/nfc.h" #include "Cafe/OS/libs/swkbd/swkbd.h" #include "gui/debugger/DebuggerWindow2.h" @@ -93,7 +94,7 @@ enum // options -> account MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_1 = 20350, MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_12 = 20350 + 11, - + // options -> system language MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_JAPANESE = 20500, MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_ENGLISH, @@ -243,7 +244,7 @@ public: { if(!m_window->IsGameLaunched() && filenames.GetCount() == 1) return m_window->FileLoad(_utf8ToPath(filenames[0].utf8_string()), wxLaunchGameEvent::INITIATED_BY::DRAG_AND_DROP); - + return false; } @@ -455,7 +456,7 @@ bool MainWindow::InstallUpdate(const fs::path& metaFilePath) { throw std::runtime_error(frame.GetExceptionMessage()); } - } + } } catch(const AbortException&) { @@ -639,13 +640,13 @@ void MainWindow::OnFileMenu(wxCommandEvent& event) _("Wii U executable (*.rpx, *.elf)"), _("All files (*.*)") ); - + wxFileDialog openFileDialog(this, _("Open file to launch"), wxEmptyString, wxEmptyString, wildcard, wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (openFileDialog.ShowModal() == wxID_CANCEL || openFileDialog.GetPath().IsEmpty()) return; - const wxString wxStrFilePath = openFileDialog.GetPath(); + const wxString wxStrFilePath = openFileDialog.GetPath(); FileLoad(_utf8ToPath(wxStrFilePath.utf8_string()), wxLaunchGameEvent::INITIATED_BY::MENU); } else if (menuId >= MAINFRAME_MENU_ID_FILE_RECENT_0 && menuId <= MAINFRAME_MENU_ID_FILE_RECENT_LAST) @@ -784,7 +785,7 @@ void MainWindow::TogglePadView() { if (m_padView) return; - + m_padView = new PadViewFrame(this); m_padView->Bind(wxEVT_CLOSE_WINDOW, &MainWindow::OnPadClose, this); @@ -992,7 +993,7 @@ void MainWindow::OnConsoleLanguage(wxCommandEvent& event) // GetConfig().cpu_mode = CPUMode::TriplecoreRecompiler; // else // cemu_assert_debug(false); -// +// // g_config.Save(); //} @@ -1056,7 +1057,7 @@ void MainWindow::OnDebugSetting(wxCommandEvent& event) ActiveSettings::SetTimerShiftFactor(6); else cemu_assert_debug(false); - + g_config.Save(); } @@ -1132,7 +1133,7 @@ void MainWindow::OnLoggingWindow(wxCommandEvent& event) return; m_logging_window = new LoggingWindow(this); - m_logging_window->Bind(wxEVT_CLOSE_WINDOW, + m_logging_window->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event) { m_logging_window = nullptr; event.Skip(); @@ -1307,7 +1308,7 @@ void MainWindow::SaveSettings() { auto lock = g_config.Lock(); auto& config = GetConfig(); - + if (config.window_position != Vector2i{ -1,-1 }) { config.window_position.x = m_restored_position.x; @@ -1344,7 +1345,7 @@ void MainWindow::SaveSettings() if(m_game_list) m_game_list->SaveConfig(); - + g_config.Save(); } @@ -1374,14 +1375,14 @@ void MainWindow::OnMouseMove(wxMouseEvent& event) void MainWindow::OnMouseLeft(wxMouseEvent& event) { auto& instance = InputManager::instance(); - + std::scoped_lock lock(instance.m_main_mouse.m_mutex); instance.m_main_mouse.left_down = event.ButtonDown(wxMOUSE_BTN_LEFT); auto physPos = ToPhys(event.GetPosition()); instance.m_main_mouse.position = { physPos.x, physPos.y }; if (event.ButtonDown(wxMOUSE_BTN_LEFT)) instance.m_main_mouse.left_down_toggle = true; - + event.Skip(); } @@ -1395,7 +1396,7 @@ void MainWindow::OnMouseRight(wxMouseEvent& event) instance.m_main_mouse.position = { physPos.x, physPos.y }; if(event.ButtonDown(wxMOUSE_BTN_RIGHT)) instance.m_main_mouse.right_down_toggle = true; - + event.Skip(); } @@ -1443,7 +1444,7 @@ void MainWindow::OnKeyUp(wxKeyEvent& event) void MainWindow::OnKeyDown(wxKeyEvent& event) { - if ((event.AltDown() && event.GetKeyCode() == WXK_F4) || + if ((event.AltDown() && event.GetKeyCode() == WXK_F4) || (event.CmdDown() && event.GetKeyCode() == 'Q')) { Close(true); @@ -1458,7 +1459,7 @@ void MainWindow::OnChar(wxKeyEvent& event) { if (swkbd_hasKeyboardInputHook()) swkbd_keyInput(event.GetUnicodeKey()); - + // event.Skip(); } @@ -1483,7 +1484,7 @@ void MainWindow::OnToolsInput(wxCommandEvent& event) case MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER: { const auto default_tab = id == MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER ? TitleManagerPage::TitleManager : TitleManagerPage::DownloadManager; - + if (m_title_manager) m_title_manager->SetFocusAndTab(default_tab); else @@ -1533,7 +1534,7 @@ void MainWindow::OnGesturePan(wxPanGestureEvent& event) instance.m_main_touch.left_down = event.IsGestureStart() || !event.IsGestureEnd(); if (event.IsGestureStart() || !event.IsGestureEnd()) instance.m_main_touch.left_down_toggle = true; - + event.Skip(); } @@ -1567,8 +1568,10 @@ void MainWindow::CreateCanvas() // create canvas if (ActiveSettings::GetGraphicsAPI() == kVulkan) m_render_canvas = new VulkanCanvas(m_game_panel, wxSize(1280, 720), true); - else + else if (ActiveSettings::GetGraphicsAPI() == kOpenGL) m_render_canvas = GLCanvas_Create(m_game_panel, wxSize(1280, 720), true); + else + m_render_canvas = new MetalCanvas(m_game_panel, wxSize(1280, 720), true); // mouse events m_render_canvas->Bind(wxEVT_MOTION, &MainWindow::OnMouseMove, this); @@ -1748,10 +1751,10 @@ void MainWindow::UpdateNFCMenu() const auto& entry = config.recent_nfc_files[i]; if (entry.empty()) continue; - + if (!fs::exists(_utf8ToPath(entry))) continue; - + if (recentFileIndex == 0) m_nfcMenuSeparator0 = m_nfcMenu->AppendSeparator(); @@ -1802,7 +1805,7 @@ void MainWindow::OnTimer(wxTimerEvent& event) { ShowCursor(false); } - + } #define BUILD_DATE __DATE__ " " __TIME__ @@ -2061,9 +2064,9 @@ void MainWindow::RecreateMenu() m_menuBar->Destroy(); m_menuBar = nullptr; } - + auto& config = GetConfig(); - + m_menuBar = new wxMenuBar(); // file submenu m_fileMenu = new wxMenu(); @@ -2115,7 +2118,7 @@ void MainWindow::RecreateMenu() item->Check(account_id == account.GetPersistentId()); if (m_game_launched || LaunchSettings::GetPersistentId().has_value()) item->Enable(false); - + ++index; } @@ -2145,8 +2148,8 @@ void MainWindow::RecreateMenu() // options submenu wxMenu* optionsMenu = new wxMenu(); m_fullscreenMenuItem = optionsMenu->AppendCheckItem(MAINFRAME_MENU_ID_OPTIONS_FULLSCREEN, _("&Fullscreen"), wxEmptyString); - m_fullscreenMenuItem->Check(ActiveSettings::FullscreenEnabled()); - + m_fullscreenMenuItem->Check(ActiveSettings::FullscreenEnabled()); + optionsMenu->Append(MAINFRAME_MENU_ID_OPTIONS_GRAPHIC_PACKS2, _("&Graphic packs")); m_padViewMenuItem = optionsMenu->AppendCheckItem(MAINFRAME_MENU_ID_OPTIONS_SECOND_WINDOW_PADVIEW, _("&Separate GamePad view"), wxEmptyString); m_padViewMenuItem->Check(GetConfig().pad_open); @@ -2227,6 +2230,7 @@ void MainWindow::RecreateMenu() debugLoggingMenu->AppendSeparator(); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::OpenGLLogging), _("&OpenGL debug output"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::OpenGLLogging)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::VulkanValidation), _("&Vulkan validation layer (slow)"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::VulkanValidation)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::MetalLogging), _("&Metal debug output"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::MetalLogging)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_ADVANCED_PPC_INFO, _("&Log PPC context for API"), wxEmptyString)->Check(cemuLog_advancedPPCLoggingEnabled()); m_loggingSubmenu = debugLoggingMenu; // debug->dump submenu @@ -2240,7 +2244,7 @@ void MainWindow::RecreateMenu() debugMenu->AppendSubMenu(debugLoggingMenu, _("&Logging")); debugMenu->AppendSubMenu(debugDumpMenu, _("&Dump")); debugMenu->AppendSeparator(); - + auto upsidedownItem = debugMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_RENDER_UPSIDE_DOWN, _("&Render upside-down"), wxEmptyString); upsidedownItem->Check(ActiveSettings::RenderUpsideDownEnabled()); if(LaunchSettings::RenderUpsideDownEnabled().has_value()) @@ -2296,6 +2300,7 @@ void MainWindow::RecreateMenu() // these options cant be toggled after the renderer backend is initialized: m_loggingSubmenu->Enable(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::OpenGLLogging), false); m_loggingSubmenu->Enable(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::VulkanValidation), false); + m_loggingSubmenu->Enable(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::MetalLogging), false); UpdateNFCMenu(); } diff --git a/src/gui/PadViewFrame.cpp b/src/gui/PadViewFrame.cpp index e7cc5c18..6d1ec7d6 100644 --- a/src/gui/PadViewFrame.cpp +++ b/src/gui/PadViewFrame.cpp @@ -8,6 +8,7 @@ #include "Cafe/OS/libs/swkbd/swkbd.h" #include "gui/canvas/OpenGLCanvas.h" #include "gui/canvas/VulkanCanvas.h" +#include "gui/canvas/MetalCanvas.h" #include "config/CemuConfig.h" #include "gui/MainWindow.h" #include "gui/helpers/wxHelpers.h" @@ -74,8 +75,10 @@ void PadViewFrame::InitializeRenderCanvas() { if (ActiveSettings::GetGraphicsAPI() == kVulkan) m_render_canvas = new VulkanCanvas(this, wxSize(854, 480), false); - else + else if (ActiveSettings::GetGraphicsAPI() == kOpenGL) m_render_canvas = GLCanvas_Create(this, wxSize(854, 480), false); + else + m_render_canvas = new MetalCanvas(this, wxSize(854, 480), false); sizer->Add(m_render_canvas, 1, wxEXPAND, 0, nullptr); } SetSizer(sizer); @@ -173,7 +176,7 @@ void PadViewFrame::OnChar(wxKeyEvent& event) { if (swkbd_hasKeyboardInputHook()) swkbd_keyInput(event.GetUnicodeKey()); - + event.Skip(); } @@ -198,7 +201,7 @@ void PadViewFrame::OnMouseLeft(wxMouseEvent& event) instance.m_pad_mouse.position = { physPos.x, physPos.y }; if (event.ButtonDown(wxMOUSE_BTN_LEFT)) instance.m_pad_mouse.left_down_toggle = true; - + } void PadViewFrame::OnMouseRight(wxMouseEvent& event) diff --git a/src/gui/canvas/MetalCanvas.cpp b/src/gui/canvas/MetalCanvas.cpp new file mode 100644 index 00000000..fe8dc4ee --- /dev/null +++ b/src/gui/canvas/MetalCanvas.cpp @@ -0,0 +1,63 @@ +#include "gui/canvas/MetalCanvas.h" +#include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h" +#include "gui/guiWrapper.h" + +#include +#include + +MetalCanvas::MetalCanvas(wxWindow* parent, const wxSize& size, bool is_main_window) + : IRenderCanvas(is_main_window), wxWindow(parent, wxID_ANY, wxDefaultPosition, size, wxNO_FULL_REPAINT_ON_RESIZE | wxWANTS_CHARS) +{ + Bind(wxEVT_PAINT, &MetalCanvas::OnPaint, this); + Bind(wxEVT_SIZE, &MetalCanvas::OnResize, this); + + WindowHandleInfo& canvas = is_main_window ? gui_getWindowInfo().canvas_main : gui_getWindowInfo().canvas_pad; + gui_initHandleContextFromWxWidgetsWindow(canvas, this); + + try + { + if (is_main_window) + g_renderer = std::make_unique(); + + auto metal_renderer = MetalRenderer::GetInstance(); + metal_renderer->InitializeLayer({size.x, size.y}, is_main_window); + } + catch(const std::exception& ex) + { + cemuLog_log(LogType::Force, "Error when initializing Metal renderer: {}", ex.what()); + auto msg = formatWxString(_("Error when initializing Metal renderer:\n{}"), ex.what()); + wxMessageDialog dialog(this, msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + dialog.ShowModal(); + exit(0); + } + + wxWindow::EnableTouchEvents(wxTOUCH_PAN_GESTURES); +} + +MetalCanvas::~MetalCanvas() +{ + Unbind(wxEVT_PAINT, &MetalCanvas::OnPaint, this); + Unbind(wxEVT_SIZE, &MetalCanvas::OnResize, this); + + if(!m_is_main_window) + { + // TODO + //MetalRenderer* vkr = (MetalRenderer*)g_renderer.get(); + //if(vkr) + // vkr->StopUsingPadAndWait(); + } +} + +void MetalCanvas::OnPaint(wxPaintEvent& event) +{ +} + +void MetalCanvas::OnResize(wxSizeEvent& event) +{ + const wxSize size = GetSize(); + if (size.GetWidth() == 0 || size.GetHeight() == 0) + return; + + const wxRect refreshRect(size); + RefreshRect(refreshRect, false); +} diff --git a/src/gui/canvas/MetalCanvas.h b/src/gui/canvas/MetalCanvas.h new file mode 100644 index 00000000..4dc4d49f --- /dev/null +++ b/src/gui/canvas/MetalCanvas.h @@ -0,0 +1,19 @@ +#pragma once + +#include "gui/canvas/IRenderCanvas.h" + +#include + +#include + +class MetalCanvas : public IRenderCanvas, public wxWindow +{ +public: + MetalCanvas(wxWindow* parent, const wxSize& size, bool is_main_window); + ~MetalCanvas(); + +private: + + void OnPaint(wxPaintEvent& event); + void OnResize(wxSizeEvent& event); +}; diff --git a/src/gui/guiWrapper.cpp b/src/gui/guiWrapper.cpp index d887e89a..8f004edd 100644 --- a/src/gui/guiWrapper.cpp +++ b/src/gui/guiWrapper.cpp @@ -82,11 +82,14 @@ void gui_updateWindowTitles(bool isIdle, bool isLoading, double fps) case RendererAPI::OpenGL: renderer = "[OpenGL]"; break; - case RendererAPI::Vulkan: + case RendererAPI::Vulkan: renderer = "[Vulkan]"; break; + case RendererAPI::Metal: + renderer = "[Metal]"; + break; default: ; - } + } } // get GPU vendor/mode @@ -217,7 +220,7 @@ void gui_initHandleContextFromWxWidgetsWindow(WindowHandleInfo& handleInfoOut, c cemuLog_log(LogType::Force, "Unable to get xlib display"); } } - else + else #ifdef HAS_WAYLAND if(GDK_IS_WAYLAND_WINDOW(gdkWindow)) { diff --git a/src/imgui/CMakeLists.txt b/src/imgui/CMakeLists.txt index db7686bd..c3fc4a0e 100644 --- a/src/imgui/CMakeLists.txt +++ b/src/imgui/CMakeLists.txt @@ -7,6 +7,8 @@ add_library(imguiImpl imgui_extension.h ) +# TODO: add Metal + set_property(TARGET imguiImpl PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") target_include_directories(imguiImpl PUBLIC "../")