diff options
Diffstat (limited to 'src')
42 files changed, 787 insertions, 177 deletions
diff --git a/src/core/core.cpp b/src/core/core.cpp index 31c590866..572814e4b 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -443,27 +443,31 @@ std::shared_ptr<FileSys::VfsFilesystem> System::GetFilesystem() const { return impl->virtual_filesystem; } -void System::SetProfileSelector(std::unique_ptr<Core::Frontend::ProfileSelectApplet> applet) { +void System::SetProfileSelector(std::unique_ptr<Frontend::ProfileSelectApplet> applet) { impl->profile_selector = std::move(applet); } -const Core::Frontend::ProfileSelectApplet& System::GetProfileSelector() const { +const Frontend::ProfileSelectApplet& System::GetProfileSelector() const { return *impl->profile_selector; } -void System::SetSoftwareKeyboard(std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> applet) { +void System::SetSoftwareKeyboard(std::unique_ptr<Frontend::SoftwareKeyboardApplet> applet) { impl->software_keyboard = std::move(applet); } -const Core::Frontend::SoftwareKeyboardApplet& System::GetSoftwareKeyboard() const { +const Frontend::SoftwareKeyboardApplet& System::GetSoftwareKeyboard() const { return *impl->software_keyboard; } -void System::SetWebBrowser(std::unique_ptr<Core::Frontend::WebBrowserApplet> applet) { +void System::SetWebBrowser(std::unique_ptr<Frontend::WebBrowserApplet> applet) { impl->web_browser = std::move(applet); } -const Core::Frontend::WebBrowserApplet& System::GetWebBrowser() const { +Frontend::WebBrowserApplet& System::GetWebBrowser() { + return *impl->web_browser; +} + +const Frontend::WebBrowserApplet& System::GetWebBrowser() const { return *impl->web_browser; } diff --git a/src/core/core.h b/src/core/core.h index a53dbb4d4..511a5ad3a 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -243,17 +243,18 @@ public: std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const; - void SetProfileSelector(std::unique_ptr<Core::Frontend::ProfileSelectApplet> applet); + void SetProfileSelector(std::unique_ptr<Frontend::ProfileSelectApplet> applet); - const Core::Frontend::ProfileSelectApplet& GetProfileSelector() const; + const Frontend::ProfileSelectApplet& GetProfileSelector() const; - void SetSoftwareKeyboard(std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> applet); + void SetSoftwareKeyboard(std::unique_ptr<Frontend::SoftwareKeyboardApplet> applet); - const Core::Frontend::SoftwareKeyboardApplet& GetSoftwareKeyboard() const; + const Frontend::SoftwareKeyboardApplet& GetSoftwareKeyboard() const; - void SetWebBrowser(std::unique_ptr<Core::Frontend::WebBrowserApplet> applet); + void SetWebBrowser(std::unique_ptr<Frontend::WebBrowserApplet> applet); - const Core::Frontend::WebBrowserApplet& GetWebBrowser() const; + Frontend::WebBrowserApplet& GetWebBrowser(); + const Frontend::WebBrowserApplet& GetWebBrowser() const; private: System(); diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 19b6f8600..5aa3b600b 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -359,6 +359,8 @@ bool NCA::ReadPFS0Section(const NCASectionHeader& section, const NCASectionTable dirs.push_back(std::move(npfs)); if (IsDirectoryExeFS(dirs.back())) exefs = dirs.back(); + else if (IsDirectoryLogoPartition(dirs.back())) + logo = dirs.back(); } else { if (has_rights_id) status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; @@ -546,4 +548,8 @@ u64 NCA::GetBaseIVFCOffset() const { return ivfc_offset; } +VirtualDir NCA::GetLogoPartition() const { + return logo; +} + } // namespace FileSys diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index 99294cbb4..5d4d05c82 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h @@ -74,6 +74,13 @@ inline bool IsDirectoryExeFS(const std::shared_ptr<VfsDirectory>& pfs) { return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr; } +inline bool IsDirectoryLogoPartition(const VirtualDir& pfs) { + // NintendoLogo is the static image in the top left corner while StartupMovie is the animation + // in the bottom right corner. + return pfs->GetFile("NintendoLogo.png") != nullptr && + pfs->GetFile("StartupMovie.gif") != nullptr; +} + // An implementation of VfsDirectory that represents a Nintendo Content Archive (NCA) conatiner. // After construction, use GetStatus to determine if the file is valid and ready to be used. class NCA : public ReadOnlyVfsDirectory { @@ -102,6 +109,8 @@ public: // Returns the base ivfc offset used in BKTR patching. u64 GetBaseIVFCOffset() const; + VirtualDir GetLogoPartition() const; + private: bool CheckSupportedNCA(const NCAHeader& header); bool HandlePotentialHeaderDecryption(); @@ -122,6 +131,7 @@ private: VirtualFile romfs = nullptr; VirtualDir exefs = nullptr; + VirtualDir logo = nullptr; VirtualFile file; VirtualFile bktr_base_romfs; u64 ivfc_offset = 0; diff --git a/src/core/file_sys/directory.h b/src/core/file_sys/directory.h index 6690aa575..7b5c509fb 100644 --- a/src/core/file_sys/directory.h +++ b/src/core/file_sys/directory.h @@ -39,27 +39,4 @@ static_assert(sizeof(Entry) == 0x310, "Directory Entry struct isn't exactly 0x31 static_assert(offsetof(Entry, type) == 0x304, "Wrong offset for type in Entry."); static_assert(offsetof(Entry, file_size) == 0x308, "Wrong offset for file_size in Entry."); -class DirectoryBackend : NonCopyable { -public: - DirectoryBackend() {} - virtual ~DirectoryBackend() {} - - /** - * List files contained in the directory - * @param count Number of entries to return at once in entries - * @param entries Buffer to read data into - * @return Number of entries listed - */ - virtual u64 Read(const u64 count, Entry* entries) = 0; - - /// Returns the number of entries still left to read. - virtual u64 GetEntryCount() const = 0; - - /** - * Close the directory - * @return true if the directory closed correctly - */ - virtual bool Close() const = 0; -}; - } // namespace FileSys diff --git a/src/core/frontend/applets/web_browser.cpp b/src/core/frontend/applets/web_browser.cpp index 6a36b4b8f..3a3d3d0bf 100644 --- a/src/core/frontend/applets/web_browser.cpp +++ b/src/core/frontend/applets/web_browser.cpp @@ -13,7 +13,7 @@ DefaultWebBrowserApplet::~DefaultWebBrowserApplet() = default; void DefaultWebBrowserApplet::OpenPage(std::string_view filename, std::function<void()> unpack_romfs_callback, - std::function<void()> finished_callback) const { + std::function<void()> finished_callback) { LOG_INFO(Service_AM, "(STUBBED) called - No suitable web browser implementation found to open website page " "at '{}'!", diff --git a/src/core/frontend/applets/web_browser.h b/src/core/frontend/applets/web_browser.h index 41d272d26..f952856af 100644 --- a/src/core/frontend/applets/web_browser.h +++ b/src/core/frontend/applets/web_browser.h @@ -14,7 +14,7 @@ public: virtual ~WebBrowserApplet(); virtual void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback, - std::function<void()> finished_callback) const = 0; + std::function<void()> finished_callback) = 0; }; class DefaultWebBrowserApplet final : public WebBrowserApplet { @@ -22,7 +22,7 @@ public: ~DefaultWebBrowserApplet() override; void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback, - std::function<void()> finished_callback) const override; + std::function<void()> finished_callback) override; }; } // namespace Core::Frontend diff --git a/src/core/hle/service/am/applets/web_browser.cpp b/src/core/hle/service/am/applets/web_browser.cpp index d975207f5..9b0aa7f5f 100644 --- a/src/core/hle/service/am/applets/web_browser.cpp +++ b/src/core/hle/service/am/applets/web_browser.cpp @@ -2,9 +2,16 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <array> +#include <cstring> +#include <vector> + +#include "common/assert.h" +#include "common/common_funcs.h" #include "common/common_paths.h" +#include "common/file_util.h" #include "common/hex_util.h" -#include "common/logging/backend.h" +#include "common/logging/log.h" #include "common/string_util.h" #include "core/core.h" #include "core/file_sys/content_archive.h" @@ -12,7 +19,6 @@ #include "core/file_sys/nca_metadata.h" #include "core/file_sys/registered_cache.h" #include "core/file_sys/romfs.h" -#include "core/file_sys/romfs_factory.h" #include "core/file_sys/vfs_types.h" #include "core/frontend/applets/web_browser.h" #include "core/hle/kernel/process.h" @@ -146,7 +152,7 @@ void WebBrowser::Execute() { return; } - const auto& frontend{Core::System::GetInstance().GetWebBrowser()}; + auto& frontend{Core::System::GetInstance().GetWebBrowser()}; frontend.OpenPage(filename, [this] { UnpackRomFS(); }, [this] { Finalize(); }); } diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 01f984098..bb925f4a6 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -178,6 +178,8 @@ public: /** * Get the banner (typically banner section) of the application + * In the context of NX, this is the animation that displays in the bottom right of the screen + * when a game boots. Stored in GIF format. * @param buffer Reference to buffer to store data * @return ResultStatus result of function */ @@ -187,6 +189,8 @@ public: /** * Get the logo (typically logo section) of the application + * In the context of NX, this is the static image that displays in the top left of the screen + * when a game boots. Stored in JPEG format. * @param buffer Reference to buffer to store data * @return ResultStatus result of function */ diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp index a093e3d36..93a970d10 100644 --- a/src/core/loader/nax.cpp +++ b/src/core/loader/nax.cpp @@ -79,4 +79,13 @@ u64 AppLoader_NAX::ReadRomFSIVFCOffset() const { ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) { return nca_loader->ReadProgramId(out_program_id); } + +ResultStatus AppLoader_NAX::ReadBanner(std::vector<u8>& buffer) { + return nca_loader->ReadBanner(buffer); +} + +ResultStatus AppLoader_NAX::ReadLogo(std::vector<u8>& buffer) { + return nca_loader->ReadLogo(buffer); +} + } // namespace Loader diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h index 0a97511b8..f40079574 100644 --- a/src/core/loader/nax.h +++ b/src/core/loader/nax.h @@ -39,6 +39,9 @@ public: u64 ReadRomFSIVFCOffset() const override; ResultStatus ReadProgramId(u64& out_program_id) override; + ResultStatus ReadBanner(std::vector<u8>& buffer) override; + ResultStatus ReadLogo(std::vector<u8>& buffer) override; + private: std::unique_ptr<FileSys::NAX> nax; std::unique_ptr<AppLoader_NCA> nca_loader; diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp index 7e1b0d84f..ce8196fcf 100644 --- a/src/core/loader/nca.cpp +++ b/src/core/loader/nca.cpp @@ -84,4 +84,23 @@ ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) { return ResultStatus::Success; } +ResultStatus AppLoader_NCA::ReadBanner(std::vector<u8>& buffer) { + if (nca == nullptr || nca->GetStatus() != ResultStatus::Success) + return ResultStatus::ErrorNotInitialized; + const auto logo = nca->GetLogoPartition(); + if (logo == nullptr) + return ResultStatus::ErrorNoIcon; + buffer = logo->GetFile("StartupMovie.gif")->ReadAllBytes(); + return ResultStatus::Success; +} + +ResultStatus AppLoader_NCA::ReadLogo(std::vector<u8>& buffer) { + if (nca == nullptr || nca->GetStatus() != ResultStatus::Success) + return ResultStatus::ErrorNotInitialized; + const auto logo = nca->GetLogoPartition(); + if (logo == nullptr) + return ResultStatus::ErrorNoIcon; + buffer = logo->GetFile("NintendoLogo.png")->ReadAllBytes(); + return ResultStatus::Success; +} } // namespace Loader diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h index cbbe701d2..b9f077468 100644 --- a/src/core/loader/nca.h +++ b/src/core/loader/nca.h @@ -39,6 +39,9 @@ public: u64 ReadRomFSIVFCOffset() const override; ResultStatus ReadProgramId(u64& out_program_id) override; + ResultStatus ReadBanner(std::vector<u8>& buffer) override; + ResultStatus ReadLogo(std::vector<u8>& buffer) override; + private: std::unique_ptr<FileSys::NCA> nca; std::unique_ptr<AppLoader_DeconstructedRomDirectory> directory_loader; diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index 7fcb12aa2..7da1f8960 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp @@ -166,4 +166,13 @@ ResultStatus AppLoader_NSP::ReadManualRomFS(FileSys::VirtualFile& file) { file = nca->GetRomFS(); return file == nullptr ? ResultStatus::ErrorNoRomFS : ResultStatus::Success; } + +ResultStatus AppLoader_NSP::ReadBanner(std::vector<u8>& buffer) { + return secondary_loader->ReadBanner(buffer); +} + +ResultStatus AppLoader_NSP::ReadLogo(std::vector<u8>& buffer) { + return secondary_loader->ReadLogo(buffer); +} + } // namespace Loader diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h index b6b309400..953a1b508 100644 --- a/src/core/loader/nsp.h +++ b/src/core/loader/nsp.h @@ -46,6 +46,9 @@ public: ResultStatus ReadControlData(FileSys::NACP& nacp) override; ResultStatus ReadManualRomFS(FileSys::VirtualFile& file) override; + ResultStatus ReadBanner(std::vector<u8>& buffer) override; + ResultStatus ReadLogo(std::vector<u8>& buffer) override; + private: std::unique_ptr<FileSys::NSP> nsp; std::unique_ptr<AppLoader> secondary_loader; diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index ff60a3756..89f7bbf77 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -137,4 +137,12 @@ ResultStatus AppLoader_XCI::ReadManualRomFS(FileSys::VirtualFile& file) { return file == nullptr ? ResultStatus::ErrorNoRomFS : ResultStatus::Success; } +ResultStatus AppLoader_XCI::ReadBanner(std::vector<u8>& buffer) { + return nca_loader->ReadBanner(buffer); +} + +ResultStatus AppLoader_XCI::ReadLogo(std::vector<u8>& buffer) { + return nca_loader->ReadLogo(buffer); +} + } // namespace Loader diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h index e18531c93..d6995b61e 100644 --- a/src/core/loader/xci.h +++ b/src/core/loader/xci.h @@ -46,6 +46,9 @@ public: ResultStatus ReadControlData(FileSys::NACP& control) override; ResultStatus ReadManualRomFS(FileSys::VirtualFile& file) override; + ResultStatus ReadBanner(std::vector<u8>& buffer) override; + ResultStatus ReadLogo(std::vector<u8>& buffer) override; + private: std::unique_ptr<FileSys::XCI> xci; std::unique_ptr<AppLoader_NCA> nca_loader; diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 0ed7bc5d8..d64a5080b 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -135,6 +135,25 @@ void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) { if (regs.reg_array[method_call.method] != method_call.argument) { regs.reg_array[method_call.method] = method_call.argument; + // Color buffers + constexpr u32 first_rt_reg = MAXWELL3D_REG_INDEX(rt); + constexpr u32 registers_per_rt = sizeof(regs.rt[0]) / sizeof(u32); + if (method_call.method >= first_rt_reg && + method_call.method < first_rt_reg + registers_per_rt * Regs::NumRenderTargets) { + const std::size_t rt_index = (method_call.method - first_rt_reg) / registers_per_rt; + dirty_flags.color_buffer |= 1u << static_cast<u32>(rt_index); + } + + // Zeta buffer + constexpr u32 registers_in_zeta = sizeof(regs.zeta) / sizeof(u32); + if (method_call.method == MAXWELL3D_REG_INDEX(zeta_enable) || + method_call.method == MAXWELL3D_REG_INDEX(zeta_width) || + method_call.method == MAXWELL3D_REG_INDEX(zeta_height) || + (method_call.method >= MAXWELL3D_REG_INDEX(zeta) && + method_call.method < MAXWELL3D_REG_INDEX(zeta) + registers_in_zeta)) { + dirty_flags.zeta_buffer = true; + } + // Shader constexpr u32 shader_registers_count = sizeof(regs.shader_config[0]) * Regs::MaxShaderProgram / sizeof(u32); diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index d50e5a126..1f76aa670 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -1089,12 +1089,17 @@ public: MemoryManager& memory_manager; struct DirtyFlags { + u8 color_buffer = 0xFF; + bool zeta_buffer = true; + bool shaders = true; bool vertex_attrib_format = true; u32 vertex_array = 0xFFFFFFFF; void OnMemoryWrite() { + color_buffer = 0xFF; + zeta_buffer = true; shaders = true; vertex_array = 0xFFFFFFFF; } diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h index 06fc59dbe..ff5310848 100644 --- a/src/video_core/rasterizer_interface.h +++ b/src/video_core/rasterizer_interface.h @@ -4,6 +4,7 @@ #pragma once +#include <functional> #include "common/common_types.h" #include "video_core/engines/fermi_2d.h" #include "video_core/gpu.h" @@ -11,6 +12,14 @@ namespace VideoCore { +enum class LoadCallbackStage { + Prepare, + Decompile, + Build, + Complete, +}; +using DiskResourceLoadCallback = std::function<void(LoadCallbackStage, std::size_t, std::size_t)>; + class RasterizerInterface { public: virtual ~RasterizerInterface() {} diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp index 46a6c0308..bd2b30e77 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp @@ -14,7 +14,7 @@ namespace OpenGL { OGLBufferCache::OGLBufferCache(RasterizerOpenGL& rasterizer, std::size_t size) - : RasterizerCache{rasterizer}, stream_buffer(GL_ARRAY_BUFFER, size) {} + : RasterizerCache{rasterizer}, stream_buffer(size, true) {} GLintptr OGLBufferCache::UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size, std::size_t alignment, bool cache) { diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 73567eb8c..2bf086902 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -135,27 +135,31 @@ void RasterizerOpenGL::CheckExtensions() { } } -void RasterizerOpenGL::SetupVertexFormat() { +GLuint RasterizerOpenGL::SetupVertexFormat() { auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); const auto& regs = gpu.regs; - if (!gpu.dirty_flags.vertex_attrib_format) - return; + if (!gpu.dirty_flags.vertex_attrib_format) { + return state.draw.vertex_array; + } gpu.dirty_flags.vertex_attrib_format = false; MICROPROFILE_SCOPE(OpenGL_VAO); auto [iter, is_cache_miss] = vertex_array_cache.try_emplace(regs.vertex_attrib_format); - auto& VAO = iter->second; + auto& vao_entry = iter->second; if (is_cache_miss) { - VAO.Create(); - state.draw.vertex_array = VAO.handle; - state.ApplyVertexBufferState(); + vao_entry.Create(); + const GLuint vao = vao_entry.handle; - // The index buffer binding is stored within the VAO. Stupid OpenGL, but easy to work - // around. - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer_cache.GetHandle()); + // Eventhough we are using DSA to create this vertex array, there is a bug on Intel's blob + // that fails to properly create the vertex array if it's not bound even after creating it + // with glCreateVertexArrays + state.draw.vertex_array = vao; + state.ApplyVertexArrayState(); + + glVertexArrayElementBuffer(vao, buffer_cache.GetHandle()); // Use the vertex array as-is, assumes that the data is formatted correctly for OpenGL. // Enables the first 16 vertex attributes always, as we don't know which ones are actually @@ -163,7 +167,7 @@ void RasterizerOpenGL::SetupVertexFormat() { // for now to avoid OpenGL errors. // TODO(Subv): Analyze the shader to identify which attributes are actually used and don't // assume every shader uses them all. - for (unsigned index = 0; index < 16; ++index) { + for (u32 index = 0; index < 16; ++index) { const auto& attrib = regs.vertex_attrib_format[index]; // Ignore invalid attributes. @@ -178,28 +182,29 @@ void RasterizerOpenGL::SetupVertexFormat() { ASSERT(buffer.IsEnabled()); - glEnableVertexAttribArray(index); + glEnableVertexArrayAttrib(vao, index); if (attrib.type == Tegra::Engines::Maxwell3D::Regs::VertexAttribute::Type::SignedInt || attrib.type == Tegra::Engines::Maxwell3D::Regs::VertexAttribute::Type::UnsignedInt) { - glVertexAttribIFormat(index, attrib.ComponentCount(), - MaxwellToGL::VertexType(attrib), attrib.offset); + glVertexArrayAttribIFormat(vao, index, attrib.ComponentCount(), + MaxwellToGL::VertexType(attrib), attrib.offset); } else { - glVertexAttribFormat(index, attrib.ComponentCount(), - MaxwellToGL::VertexType(attrib), - attrib.IsNormalized() ? GL_TRUE : GL_FALSE, attrib.offset); + glVertexArrayAttribFormat( + vao, index, attrib.ComponentCount(), MaxwellToGL::VertexType(attrib), + attrib.IsNormalized() ? GL_TRUE : GL_FALSE, attrib.offset); } - glVertexAttribBinding(index, attrib.buffer); + glVertexArrayAttribBinding(vao, index, attrib.buffer); } } - state.draw.vertex_array = VAO.handle; - state.ApplyVertexBufferState(); // Rebinding the VAO invalidates the vertex buffer bindings. gpu.dirty_flags.vertex_array = 0xFFFFFFFF; + + state.draw.vertex_array = vao_entry.handle; + return vao_entry.handle; } -void RasterizerOpenGL::SetupVertexBuffer() { +void RasterizerOpenGL::SetupVertexBuffer(GLuint vao) { auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); const auto& regs = gpu.regs; @@ -217,7 +222,7 @@ void RasterizerOpenGL::SetupVertexBuffer() { if (!vertex_array.IsEnabled()) continue; - Tegra::GPUVAddr start = vertex_array.StartAddress(); + const Tegra::GPUVAddr start = vertex_array.StartAddress(); const Tegra::GPUVAddr end = regs.vertex_array_limit[index].LimitAddress(); ASSERT(end > start); @@ -225,21 +230,18 @@ void RasterizerOpenGL::SetupVertexBuffer() { const GLintptr vertex_buffer_offset = buffer_cache.UploadMemory(start, size); // Bind the vertex array to the buffer at the current offset. - glBindVertexBuffer(index, buffer_cache.GetHandle(), vertex_buffer_offset, - vertex_array.stride); + glVertexArrayVertexBuffer(vao, index, buffer_cache.GetHandle(), vertex_buffer_offset, + vertex_array.stride); if (regs.instanced_arrays.IsInstancingEnabled(index) && vertex_array.divisor != 0) { // Enable vertex buffer instancing with the specified divisor. - glVertexBindingDivisor(index, vertex_array.divisor); + glVertexArrayBindingDivisor(vao, index, vertex_array.divisor); } else { // Disable the vertex buffer instancing. - glVertexBindingDivisor(index, 0); + glVertexArrayBindingDivisor(vao, index, 0); } } - // Implicit set by glBindVertexBuffer. Stupid glstate handling... - state.draw.vertex_buffer = buffer_cache.GetHandle(); - gpu.dirty_flags.vertex_array = 0; } @@ -365,7 +367,7 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { // (sometimes it's half the screen, sometimes three quarters). To avoid this, enable the // clip distances only when it's written by a shader stage. for (std::size_t i = 0; i < Maxwell::NumClipDistances; ++i) { - clip_distances[i] |= shader->GetShaderEntries().clip_distances[i]; + clip_distances[i] = clip_distances[i] || shader->GetShaderEntries().clip_distances[i]; } // When VertexA is enabled, we have dual vertex shaders @@ -488,7 +490,19 @@ void RasterizerOpenGL::ConfigureFramebuffers(OpenGLState& current_state, bool us bool using_depth_fb, bool preserve_contents, std::optional<std::size_t> single_color_target) { MICROPROFILE_SCOPE(OpenGL_Framebuffer); - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); + const auto& regs = gpu.regs; + + const FramebufferConfigState fb_config_state{using_color_fb, using_depth_fb, preserve_contents, + single_color_target}; + if (fb_config_state == current_framebuffer_config_state && gpu.dirty_flags.color_buffer == 0 && + !gpu.dirty_flags.zeta_buffer) { + // Only skip if the previous ConfigureFramebuffers call was from the same kind (multiple or + // single color targets). This is done because the guest registers may not change but the + // host framebuffer may contain different attachments + return; + } + current_framebuffer_config_state = fb_config_state; Surface depth_surface; if (using_depth_fb) { @@ -691,9 +705,6 @@ void RasterizerOpenGL::DrawArrays() { // Draw the vertex batch const bool is_indexed = accelerate_draw == AccelDraw::Indexed; - state.draw.vertex_buffer = buffer_cache.GetHandle(); - state.ApplyVertexBufferState(); - std::size_t buffer_size = CalculateVertexArraysSize(); // Add space for index buffer (keeping in mind non-core primitives) @@ -723,8 +734,9 @@ void RasterizerOpenGL::DrawArrays() { gpu.dirty_flags.vertex_array = 0xFFFFFFFF; } - SetupVertexFormat(); - SetupVertexBuffer(); + const GLuint vao = SetupVertexFormat(); + SetupVertexBuffer(vao); + DrawParameters params = SetupDraw(); SetupShaders(params.primitive_mode); diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index a53edee6d..21c51f874 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -99,6 +99,23 @@ private: float max_anisotropic = 1.0f; }; + struct FramebufferConfigState { + bool using_color_fb{}; + bool using_depth_fb{}; + bool preserve_contents{}; + std::optional<std::size_t> single_color_target; + + bool operator==(const FramebufferConfigState& rhs) const { + return std::tie(using_color_fb, using_depth_fb, preserve_contents, + single_color_target) == std::tie(rhs.using_color_fb, rhs.using_depth_fb, + rhs.preserve_contents, + rhs.single_color_target); + } + bool operator!=(const FramebufferConfigState& rhs) const { + return !operator==(rhs); + } + }; + /** * Configures the color and depth framebuffer states. * @param use_color_fb If true, configure color framebuffers. @@ -203,6 +220,7 @@ private: vertex_array_cache; std::map<FramebufferCacheKey, OGLFramebuffer> framebuffer_cache; + FramebufferConfigState current_framebuffer_config_state; std::array<SamplerInfo, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> texture_samplers; @@ -215,8 +233,10 @@ private: std::size_t CalculateIndexBufferSize() const; - void SetupVertexFormat(); - void SetupVertexBuffer(); + /// Updates and returns a vertex array object representing current vertex format + GLuint SetupVertexFormat(); + + void SetupVertexBuffer(GLuint vao); DrawParameters SetupDraw(); diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index bff0c65cd..a05b8b936 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -919,9 +919,16 @@ Surface RasterizerCacheOpenGL::GetTextureSurface(const Tegra::Texture::FullTextu } Surface RasterizerCacheOpenGL::GetDepthBufferSurface(bool preserve_contents) { - const auto& regs{Core::System::GetInstance().GPU().Maxwell3D().regs}; + auto& gpu{Core::System::GetInstance().GPU().Maxwell3D()}; + const auto& regs{gpu.regs}; + + if (!gpu.dirty_flags.zeta_buffer) { + return last_depth_buffer; + } + gpu.dirty_flags.zeta_buffer = false; + if (!regs.zeta.Address() || !regs.zeta_enable) { - return {}; + return last_depth_buffer = {}; } SurfaceParams depth_params{SurfaceParams::CreateForDepthBuffer( @@ -929,25 +936,31 @@ Surface RasterizerCacheOpenGL::GetDepthBufferSurface(bool preserve_contents) { regs.zeta.memory_layout.block_width, regs.zeta.memory_layout.block_height, regs.zeta.memory_layout.block_depth, regs.zeta.memory_layout.type)}; - return GetSurface(depth_params, preserve_contents); + return last_depth_buffer = GetSurface(depth_params, preserve_contents); } Surface RasterizerCacheOpenGL::GetColorBufferSurface(std::size_t index, bool preserve_contents) { - const auto& regs{Core::System::GetInstance().GPU().Maxwell3D().regs}; + auto& gpu{Core::System::GetInstance().GPU().Maxwell3D()}; + const auto& regs{gpu.regs}; + + if ((gpu.dirty_flags.color_buffer & (1u << static_cast<u32>(index))) == 0) { + return last_color_buffers[index]; + } + gpu.dirty_flags.color_buffer &= ~(1u << static_cast<u32>(index)); ASSERT(index < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets); if (index >= regs.rt_control.count) { - return {}; + return last_color_buffers[index] = {}; } if (regs.rt[index].Address() == 0 || regs.rt[index].format == Tegra::RenderTargetFormat::NONE) { - return {}; + return last_color_buffers[index] = {}; } const SurfaceParams color_params{SurfaceParams::CreateForFramebuffer(index)}; - return GetSurface(color_params, preserve_contents); + return last_color_buffers[index] = GetSurface(color_params, preserve_contents); } void RasterizerCacheOpenGL::LoadSurface(const Surface& surface) { diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h index 7223700c4..37611c4fc 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h @@ -396,6 +396,9 @@ private: /// Use a Pixel Buffer Object to download the previous texture and then upload it to the new one /// using the new format. OGLBuffer copy_pbo; + + std::array<Surface, Tegra::Engines::Maxwell3D::Regs::NumRenderTargets> last_color_buffers; + Surface last_depth_buffer; }; } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_resource_manager.cpp b/src/video_core/renderer_opengl/gl_resource_manager.cpp index c17d5ac00..1da744158 100644 --- a/src/video_core/renderer_opengl/gl_resource_manager.cpp +++ b/src/video_core/renderer_opengl/gl_resource_manager.cpp @@ -117,7 +117,7 @@ void OGLBuffer::Create() { return; MICROPROFILE_SCOPE(OpenGL_ResourceCreation); - glGenBuffers(1, &handle); + glCreateBuffers(1, &handle); } void OGLBuffer::Release() { @@ -126,7 +126,6 @@ void OGLBuffer::Release() { MICROPROFILE_SCOPE(OpenGL_ResourceDeletion); glDeleteBuffers(1, &handle); - OpenGLState::GetCurState().ResetBuffer(handle).Apply(); handle = 0; } @@ -152,7 +151,7 @@ void OGLVertexArray::Create() { return; MICROPROFILE_SCOPE(OpenGL_ResourceCreation); - glGenVertexArrays(1, &handle); + glCreateVertexArrays(1, &handle); } void OGLVertexArray::Release() { diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp index dc0a5ed5e..b7ba59350 100644 --- a/src/video_core/renderer_opengl/gl_state.cpp +++ b/src/video_core/renderer_opengl/gl_state.cpp @@ -83,8 +83,6 @@ OpenGLState::OpenGLState() { draw.read_framebuffer = 0; draw.draw_framebuffer = 0; draw.vertex_array = 0; - draw.vertex_buffer = 0; - draw.uniform_buffer = 0; draw.shader_program = 0; draw.program_pipeline = 0; @@ -505,7 +503,6 @@ void OpenGLState::ApplySamplers() const { } void OpenGLState::ApplyFramebufferState() const { - // Framebuffer if (draw.read_framebuffer != cur_state.draw.read_framebuffer) { glBindFramebuffer(GL_READ_FRAMEBUFFER, draw.read_framebuffer); } @@ -514,16 +511,10 @@ void OpenGLState::ApplyFramebufferState() const { } } -void OpenGLState::ApplyVertexBufferState() const { - // Vertex array +void OpenGLState::ApplyVertexArrayState() const { if (draw.vertex_array != cur_state.draw.vertex_array) { glBindVertexArray(draw.vertex_array); } - - // Vertex buffer - if (draw.vertex_buffer != cur_state.draw.vertex_buffer) { - glBindBuffer(GL_ARRAY_BUFFER, draw.vertex_buffer); - } } void OpenGLState::ApplyDepthClamp() const { @@ -543,11 +534,7 @@ void OpenGLState::ApplyDepthClamp() const { void OpenGLState::Apply() const { ApplyFramebufferState(); - ApplyVertexBufferState(); - // Uniform buffer - if (draw.uniform_buffer != cur_state.draw.uniform_buffer) { - glBindBuffer(GL_UNIFORM_BUFFER, draw.uniform_buffer); - } + ApplyVertexArrayState(); // Shader program if (draw.shader_program != cur_state.draw.shader_program) { @@ -638,16 +625,6 @@ OpenGLState& OpenGLState::ResetPipeline(GLuint handle) { return *this; } -OpenGLState& OpenGLState::ResetBuffer(GLuint handle) { - if (draw.vertex_buffer == handle) { - draw.vertex_buffer = 0; - } - if (draw.uniform_buffer == handle) { - draw.uniform_buffer = 0; - } - return *this; -} - OpenGLState& OpenGLState::ResetVertexArray(GLuint handle) { if (draw.vertex_array == handle) { draw.vertex_array = 0; diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index 439bfbc98..a5a7c0920 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h @@ -154,8 +154,6 @@ public: GLuint read_framebuffer; // GL_READ_FRAMEBUFFER_BINDING GLuint draw_framebuffer; // GL_DRAW_FRAMEBUFFER_BINDING GLuint vertex_array; // GL_VERTEX_ARRAY_BINDING - GLuint vertex_buffer; // GL_ARRAY_BUFFER_BINDING - GLuint uniform_buffer; // GL_UNIFORM_BUFFER_BINDING GLuint shader_program; // GL_CURRENT_PROGRAM GLuint program_pipeline; // GL_PROGRAM_PIPELINE_BINDING } draw; @@ -206,10 +204,10 @@ public: } /// Apply this state as the current OpenGL state void Apply() const; - /// Apply only the state afecting the framebuffer + /// Apply only the state affecting the framebuffer void ApplyFramebufferState() const; - /// Apply only the state afecting the vertex buffer - void ApplyVertexBufferState() const; + /// Apply only the state affecting the vertex array + void ApplyVertexArrayState() const; /// Set the initial OpenGL state static void ApplyDefaultState(); /// Resets any references to the given resource @@ -217,7 +215,6 @@ public: OpenGLState& ResetSampler(GLuint handle); OpenGLState& ResetProgram(GLuint handle); OpenGLState& ResetPipeline(GLuint handle); - OpenGLState& ResetBuffer(GLuint handle); OpenGLState& ResetVertexArray(GLuint handle); OpenGLState& ResetFramebuffer(GLuint handle); void EmulateViewportWithScissor(); diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.cpp b/src/video_core/renderer_opengl/gl_stream_buffer.cpp index b97b895a4..d0b14b3f6 100644 --- a/src/video_core/renderer_opengl/gl_stream_buffer.cpp +++ b/src/video_core/renderer_opengl/gl_stream_buffer.cpp @@ -15,13 +15,12 @@ MICROPROFILE_DEFINE(OpenGL_StreamBuffer, "OpenGL", "Stream Buffer Orphaning", namespace OpenGL { -OGLStreamBuffer::OGLStreamBuffer(GLenum target, GLsizeiptr size, bool prefer_coherent) - : gl_target(target), buffer_size(size) { +OGLStreamBuffer::OGLStreamBuffer(GLsizeiptr size, bool vertex_data_usage, bool prefer_coherent) + : buffer_size(size) { gl_buffer.Create(); - glBindBuffer(gl_target, gl_buffer.handle); GLsizeiptr allocate_size = size; - if (target == GL_ARRAY_BUFFER) { + if (vertex_data_usage) { // On AMD GPU there is a strange crash in indexed drawing. The crash happens when the buffer // read position is near the end and is an out-of-bound access to the vertex buffer. This is // probably a bug in the driver and is related to the usage of vec3<byte> attributes in the @@ -35,18 +34,17 @@ OGLStreamBuffer::OGLStreamBuffer(GLenum target, GLsizeiptr size, bool prefer_coh coherent = prefer_coherent; const GLbitfield flags = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | (coherent ? GL_MAP_COHERENT_BIT : 0); - glBufferStorage(gl_target, allocate_size, nullptr, flags); - mapped_ptr = static_cast<u8*>(glMapBufferRange( - gl_target, 0, buffer_size, flags | (coherent ? 0 : GL_MAP_FLUSH_EXPLICIT_BIT))); + glNamedBufferStorage(gl_buffer.handle, allocate_size, nullptr, flags); + mapped_ptr = static_cast<u8*>(glMapNamedBufferRange( + gl_buffer.handle, 0, buffer_size, flags | (coherent ? 0 : GL_MAP_FLUSH_EXPLICIT_BIT))); } else { - glBufferData(gl_target, allocate_size, nullptr, GL_STREAM_DRAW); + glNamedBufferData(gl_buffer.handle, allocate_size, nullptr, GL_STREAM_DRAW); } } OGLStreamBuffer::~OGLStreamBuffer() { if (persistent) { - glBindBuffer(gl_target, gl_buffer.handle); - glUnmapBuffer(gl_target); + glUnmapNamedBuffer(gl_buffer.handle); } gl_buffer.Release(); } @@ -74,7 +72,7 @@ std::tuple<u8*, GLintptr, bool> OGLStreamBuffer::Map(GLsizeiptr size, GLintptr a invalidate = true; if (persistent) { - glUnmapBuffer(gl_target); + glUnmapNamedBuffer(gl_buffer.handle); } } @@ -84,7 +82,7 @@ std::tuple<u8*, GLintptr, bool> OGLStreamBuffer::Map(GLsizeiptr size, GLintptr a (coherent ? GL_MAP_COHERENT_BIT : GL_MAP_FLUSH_EXPLICIT_BIT) | (invalidate ? GL_MAP_INVALIDATE_BUFFER_BIT : GL_MAP_UNSYNCHRONIZED_BIT); mapped_ptr = static_cast<u8*>( - glMapBufferRange(gl_target, buffer_pos, buffer_size - buffer_pos, flags)); + glMapNamedBufferRange(gl_buffer.handle, buffer_pos, buffer_size - buffer_pos, flags)); mapped_offset = buffer_pos; } @@ -95,11 +93,11 @@ void OGLStreamBuffer::Unmap(GLsizeiptr size) { ASSERT(size <= mapped_size); if (!coherent && size > 0) { - glFlushMappedBufferRange(gl_target, buffer_pos - mapped_offset, size); + glFlushMappedNamedBufferRange(gl_buffer.handle, buffer_pos - mapped_offset, size); } if (!persistent) { - glUnmapBuffer(gl_target); + glUnmapNamedBuffer(gl_buffer.handle); } buffer_pos += size; diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.h b/src/video_core/renderer_opengl/gl_stream_buffer.h index ae7961bd7..3d18ecb4d 100644 --- a/src/video_core/renderer_opengl/gl_stream_buffer.h +++ b/src/video_core/renderer_opengl/gl_stream_buffer.h @@ -13,7 +13,7 @@ namespace OpenGL { class OGLStreamBuffer : private NonCopyable { public: - explicit OGLStreamBuffer(GLenum target, GLsizeiptr size, bool prefer_coherent = false); + explicit OGLStreamBuffer(GLsizeiptr size, bool vertex_data_usage, bool prefer_coherent = false); ~OGLStreamBuffer(); GLuint GetHandle() const; @@ -33,7 +33,6 @@ public: private: OGLBuffer gl_buffer; - GLenum gl_target; bool coherent = false; bool persistent = false; diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 235732d86..c268c9686 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -245,20 +245,20 @@ void RendererOpenGL::InitOpenGLObjects() { // Generate VAO vertex_array.Create(); - state.draw.vertex_array = vertex_array.handle; - state.draw.vertex_buffer = vertex_buffer.handle; - state.draw.uniform_buffer = 0; - state.Apply(); // Attach vertex data to VAO - glBufferData(GL_ARRAY_BUFFER, sizeof(ScreenRectVertex) * 4, nullptr, GL_STREAM_DRAW); - glVertexAttribPointer(attrib_position, 2, GL_FLOAT, GL_FALSE, sizeof(ScreenRectVertex), - (GLvoid*)offsetof(ScreenRectVertex, position)); - glVertexAttribPointer(attrib_tex_coord, 2, GL_FLOAT, GL_FALSE, sizeof(ScreenRectVertex), - (GLvoid*)offsetof(ScreenRectVertex, tex_coord)); - glEnableVertexAttribArray(attrib_position); - glEnableVertexAttribArray(attrib_tex_coord); + glNamedBufferData(vertex_buffer.handle, sizeof(ScreenRectVertex) * 4, nullptr, GL_STREAM_DRAW); + glVertexArrayAttribFormat(vertex_array.handle, attrib_position, 2, GL_FLOAT, GL_FALSE, + offsetof(ScreenRectVertex, position)); + glVertexArrayAttribFormat(vertex_array.handle, attrib_tex_coord, 2, GL_FLOAT, GL_FALSE, + offsetof(ScreenRectVertex, tex_coord)); + glVertexArrayAttribBinding(vertex_array.handle, attrib_position, 0); + glVertexArrayAttribBinding(vertex_array.handle, attrib_tex_coord, 0); + glEnableVertexArrayAttrib(vertex_array.handle, attrib_position); + glEnableVertexArrayAttrib(vertex_array.handle, attrib_tex_coord); + glVertexArrayVertexBuffer(vertex_array.handle, 0, vertex_buffer.handle, 0, + sizeof(ScreenRectVertex)); // Allocate textures for the screen screen_info.texture.resource.Create(); @@ -370,14 +370,12 @@ void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x, state.texture_units[0].texture = screen_info.display_texture; state.texture_units[0].swizzle = {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA}; // Workaround brigthness problems in SMO by enabling sRGB in the final output - // if it has been used in the frame - // Needed because of this bug in QT - // QTBUG-50987 + // if it has been used in the frame. Needed because of this bug in QT: QTBUG-50987 state.framebuffer_srgb.enabled = OpenGLState::GetsRGBUsed(); state.Apply(); - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data()); + glNamedBufferSubData(vertex_buffer.handle, 0, sizeof(vertices), vertices.data()); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - // restore default state + // Restore default state state.framebuffer_srgb.enabled = false; state.texture_units[0].texture = 0; state.Apply(); diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 1f852df4b..4cab599b4 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -68,6 +68,8 @@ add_executable(yuzu game_list_p.h game_list_worker.cpp game_list_worker.h + loading_screen.cpp + loading_screen.h hotkeys.cpp hotkeys.h main.cpp @@ -102,9 +104,10 @@ set(UIS configuration/configure_system.ui configuration/configure_touchscreen_advanced.ui configuration/configure_web.ui + compatdb.ui hotkeys.ui + loading_screen.ui main.ui - compatdb.ui ) file(GLOB COMPAT_LIST diff --git a/src/yuzu/applets/web_browser.cpp b/src/yuzu/applets/web_browser.cpp index c59b7ade1..6a9138d53 100644 --- a/src/yuzu/applets/web_browser.cpp +++ b/src/yuzu/applets/web_browser.cpp @@ -86,9 +86,9 @@ QtWebBrowser::QtWebBrowser(GMainWindow& main_window) { QtWebBrowser::~QtWebBrowser() = default; void QtWebBrowser::OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback, - std::function<void()> finished_callback) const { - this->unpack_romfs_callback = unpack_romfs_callback; - this->finished_callback = finished_callback; + std::function<void()> finished_callback) { + this->unpack_romfs_callback = std::move(unpack_romfs_callback); + this->finished_callback = std::move(finished_callback); const auto index = url.find('?'); if (index == std::string::npos) { diff --git a/src/yuzu/applets/web_browser.h b/src/yuzu/applets/web_browser.h index bba273767..1a3d67353 100644 --- a/src/yuzu/applets/web_browser.h +++ b/src/yuzu/applets/web_browser.h @@ -38,16 +38,15 @@ public: ~QtWebBrowser() override; void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback, - std::function<void()> finished_callback) const override; + std::function<void()> finished_callback) override; signals: void MainWindowOpenPage(std::string_view filename, std::string_view additional_args) const; -public slots: +private: void MainWindowUnpackRomFS(); void MainWindowFinishedBrowsing(); -private: - mutable std::function<void()> unpack_romfs_callback; - mutable std::function<void()> finished_callback; + std::function<void()> unpack_romfs_callback; + std::function<void()> finished_callback; }; diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 40db7a5e9..f74cb693a 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -3,9 +3,7 @@ #include <QKeyEvent> #include <QScreen> #include <QWindow> - #include <fmt/format.h> - #include "common/microprofile.h" #include "common/scm_rev.h" #include "core/core.h" @@ -17,6 +15,7 @@ #include "video_core/renderer_base.h" #include "video_core/video_core.h" #include "yuzu/bootmanager.h" +#include "yuzu/main.h" EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {} @@ -114,6 +113,8 @@ GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) InputCommon::Init(); InputCommon::StartJoystickEventHandler(); + connect(this, &GRenderWindow::FirstFrameDisplayed, static_cast<GMainWindow*>(parent), + &GMainWindow::OnLoadComplete); } GRenderWindow::~GRenderWindow() { @@ -141,6 +142,10 @@ void GRenderWindow::SwapBuffers() { child->makeCurrent(); child->swapBuffers(); + if (!first_frame) { + emit FirstFrameDisplayed(); + first_frame = true; + } } void GRenderWindow::MakeCurrent() { @@ -309,6 +314,8 @@ void GRenderWindow::InitRenderTarget() { delete layout(); } + first_frame = false; + // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, // WA_DontShowOnScreen, WA_DeleteOnClose QGLFormat fmt; diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index 4e3028215..d1f37e503 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -152,6 +152,7 @@ public slots: signals: /// Emitted when the window is closed void Closed(); + void FirstFrameDisplayed(); private: std::pair<unsigned, unsigned> ScaleTouch(const QPointF pos) const; @@ -171,6 +172,8 @@ private: /// Temporary storage of the screenshot taken QImage screenshot_image; + bool first_frame = false; + protected: void showEvent(QShowEvent* event) override; }; diff --git a/src/yuzu/configuration/configure_web.cpp b/src/yuzu/configuration/configure_web.cpp index 3c2ccb76f..18566d028 100644 --- a/src/yuzu/configuration/configure_web.cpp +++ b/src/yuzu/configuration/configure_web.cpp @@ -89,12 +89,11 @@ void ConfigureWeb::OnLoginChanged() { void ConfigureWeb::VerifyLogin() { ui->button_verify_login->setDisabled(true); - ui->button_verify_login->setText(tr("Verifying")); - verify_watcher.setFuture( - QtConcurrent::run([this, username = ui->edit_username->text().toStdString(), - token = ui->edit_token->text().toStdString()]() { - return Core::VerifyLogin(username, token); - })); + ui->button_verify_login->setText(tr("Verifying...")); + verify_watcher.setFuture(QtConcurrent::run([username = ui->edit_username->text().toStdString(), + token = ui->edit_token->text().toStdString()] { + return Core::VerifyLogin(username, token); + })); } void ConfigureWeb::OnLoginVerified() { diff --git a/src/yuzu/loading_screen.cpp b/src/yuzu/loading_screen.cpp new file mode 100644 index 000000000..907aac4f1 --- /dev/null +++ b/src/yuzu/loading_screen.cpp @@ -0,0 +1,213 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <unordered_map> +#include <QBuffer> +#include <QByteArray> +#include <QGraphicsOpacityEffect> +#include <QHBoxLayout> +#include <QIODevice> +#include <QImage> +#include <QLabel> +#include <QPainter> +#include <QPalette> +#include <QPixmap> +#include <QProgressBar> +#include <QPropertyAnimation> +#include <QStyleOption> +#include <QTime> +#include <QtConcurrent/QtConcurrentRun> +#include "common/logging/log.h" +#include "core/loader/loader.h" +#include "ui_loading_screen.h" +#include "video_core/rasterizer_interface.h" +#include "yuzu/loading_screen.h" + +// Mingw seems to not have QMovie at all. If QMovie is missing then use a single frame instead of an +// showing the full animation +#if !YUZU_QT_MOVIE_MISSING +#include <QMovie> +#endif + +constexpr const char PROGRESSBAR_STYLE_PREPARE[] = R"( +QProgressBar {} +QProgressBar::chunk {})"; + +constexpr const char PROGRESSBAR_STYLE_DECOMPILE[] = R"( +QProgressBar { + background-color: black; + border: 2px solid white; + border-radius: 4px; + padding: 2px; +} +QProgressBar::chunk { + background-color: #0ab9e6; +})"; + +constexpr const char PROGRESSBAR_STYLE_BUILD[] = R"( +QProgressBar { + background-color: black; + border: 2px solid white; + border-radius: 4px; + padding: 2px; +} +QProgressBar::chunk { + background-color: #ff3c28; +})"; + +constexpr const char PROGRESSBAR_STYLE_COMPLETE[] = R"( +QProgressBar { + background-color: #0ab9e6; + border: 2px solid white; + border-radius: 4px; + padding: 2px; +} +QProgressBar::chunk { + background-color: #ff3c28; +})"; + +LoadingScreen::LoadingScreen(QWidget* parent) + : QWidget(parent), ui(std::make_unique<Ui::LoadingScreen>()), + previous_stage(VideoCore::LoadCallbackStage::Complete) { + ui->setupUi(this); + setMinimumSize(1280, 720); + + // Create a fade out effect to hide this loading screen widget. + // When fading opacity, it will fade to the parent widgets background color, which is why we + // create an internal widget named fade_widget that we use the effect on, while keeping the + // loading screen widget's background color black. This way we can create a fade to black effect + opacity_effect = new QGraphicsOpacityEffect(this); + opacity_effect->setOpacity(1); + ui->fade_parent->setGraphicsEffect(opacity_effect); + fadeout_animation = std::make_unique<QPropertyAnimation>(opacity_effect, "opacity"); + fadeout_animation->setDuration(500); + fadeout_animation->setStartValue(1); + fadeout_animation->setEndValue(0); + fadeout_animation->setEasingCurve(QEasingCurve::OutBack); + + // After the fade completes, hide the widget and reset the opacity + connect(fadeout_animation.get(), &QPropertyAnimation::finished, [this] { + hide(); + opacity_effect->setOpacity(1); + emit Hidden(); + }); + connect(this, &LoadingScreen::LoadProgress, this, &LoadingScreen::OnLoadProgress, + Qt::QueuedConnection); + qRegisterMetaType<VideoCore::LoadCallbackStage>(); + + stage_translations = { + {VideoCore::LoadCallbackStage::Prepare, tr("Loading...")}, + {VideoCore::LoadCallbackStage::Decompile, tr("Preparing Shaders %1 / %2")}, + {VideoCore::LoadCallbackStage::Build, tr("Loading Shaders %1 / %2")}, + {VideoCore::LoadCallbackStage::Complete, tr("Launching...")}, + }; + progressbar_style = { + {VideoCore::LoadCallbackStage::Prepare, PROGRESSBAR_STYLE_PREPARE}, + {VideoCore::LoadCallbackStage::Decompile, PROGRESSBAR_STYLE_DECOMPILE}, + {VideoCore::LoadCallbackStage::Build, PROGRESSBAR_STYLE_BUILD}, + {VideoCore::LoadCallbackStage::Complete, PROGRESSBAR_STYLE_COMPLETE}, + }; +} + +LoadingScreen::~LoadingScreen() = default; + +void LoadingScreen::Prepare(Loader::AppLoader& loader) { + std::vector<u8> buffer; + if (loader.ReadBanner(buffer) == Loader::ResultStatus::Success) { +#ifdef YUZU_QT_MOVIE_MISSING + QPixmap map; + map.loadFromData(buffer.data(), buffer.size()); + ui->banner->setPixmap(map); +#else + backing_mem = std::make_unique<QByteArray>(reinterpret_cast<char*>(buffer.data()), + static_cast<int>(buffer.size())); + backing_buf = std::make_unique<QBuffer>(backing_mem.get()); + backing_buf->open(QIODevice::ReadOnly); + animation = std::make_unique<QMovie>(backing_buf.get(), QByteArray()); + animation->start(); + ui->banner->setMovie(animation.get()); +#endif + buffer.clear(); + } + if (loader.ReadLogo(buffer) == Loader::ResultStatus::Success) { + QPixmap map; + map.loadFromData(buffer.data(), static_cast<uint>(buffer.size())); + ui->logo->setPixmap(map); + } + + slow_shader_compile_start = false; + OnLoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); +} + +void LoadingScreen::OnLoadComplete() { + fadeout_animation->start(QPropertyAnimation::KeepWhenStopped); +} + +void LoadingScreen::OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, + std::size_t total) { + using namespace std::chrono; + auto now = high_resolution_clock::now(); + // reset the timer if the stage changes + if (stage != previous_stage) { + ui->progress_bar->setStyleSheet(progressbar_style[stage]); + // Hide the progress bar during the prepare stage + if (stage == VideoCore::LoadCallbackStage::Prepare) { + ui->progress_bar->hide(); + } else { + ui->progress_bar->show(); + } + previous_stage = stage; + // reset back to fast shader compiling since the stage changed + slow_shader_compile_start = false; + } + // update the max of the progress bar if the number of shaders change + if (total != previous_total) { + ui->progress_bar->setMaximum(static_cast<int>(total)); + previous_total = total; + } + + QString estimate; + // If theres a drastic slowdown in the rate, then display an estimate + if (now - previous_time > milliseconds{50} || slow_shader_compile_start) { + if (!slow_shader_compile_start) { + slow_shader_start = high_resolution_clock::now(); + slow_shader_compile_start = true; + slow_shader_first_value = value; + } + // only calculate an estimate time after a second has passed since stage change + auto diff = duration_cast<milliseconds>(now - slow_shader_start); + if (diff > seconds{1}) { + auto eta_mseconds = + static_cast<long>(static_cast<double>(total - slow_shader_first_value) / + (value - slow_shader_first_value) * diff.count()); + estimate = + tr("Estimated Time %1") + .arg(QTime(0, 0, 0, 0) + .addMSecs(std::max<long>(eta_mseconds - diff.count() + 1000, 1000)) + .toString("mm:ss")); + } + } + + // update labels and progress bar + ui->stage->setText(stage_translations[stage].arg(value).arg(total)); + ui->value->setText(estimate); + ui->progress_bar->setValue(static_cast<int>(value)); + previous_time = now; +} + +void LoadingScreen::paintEvent(QPaintEvent* event) { + QStyleOption opt; + opt.init(this); + QPainter p(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); + QWidget::paintEvent(event); +} + +void LoadingScreen::Clear() { +#ifndef YUZU_QT_MOVIE_MISSING + animation.reset(); + backing_buf.reset(); + backing_mem.reset(); +#endif +} diff --git a/src/yuzu/loading_screen.h b/src/yuzu/loading_screen.h new file mode 100644 index 000000000..801d08e1a --- /dev/null +++ b/src/yuzu/loading_screen.h @@ -0,0 +1,92 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <chrono> +#include <memory> +#include <QString> +#include <QWidget> + +#if !QT_CONFIG(movie) +#define YUZU_QT_MOVIE_MISSING 1 +#endif + +namespace Loader { +class AppLoader; +} + +namespace Ui { +class LoadingScreen; +} + +namespace VideoCore { +enum class LoadCallbackStage; +} + +class QBuffer; +class QByteArray; +class QGraphicsOpacityEffect; +class QMovie; +class QPropertyAnimation; + +class LoadingScreen : public QWidget { + Q_OBJECT + +public: + explicit LoadingScreen(QWidget* parent = nullptr); + + ~LoadingScreen(); + + /// Call before showing the loading screen to load the widgets with the logo and banner for the + /// currently loaded application. + void Prepare(Loader::AppLoader& loader); + + /// After the loading screen is hidden, the owner of this class can call this to clean up any + /// used resources such as the logo and banner. + void Clear(); + + /// Slot used to update the status of the progress bar + void OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total); + + /// Hides the LoadingScreen with a fade out effect + void OnLoadComplete(); + + // In order to use a custom widget with a stylesheet, you need to override the paintEvent + // See https://wiki.qt.io/How_to_Change_the_Background_Color_of_QWidget + void paintEvent(QPaintEvent* event) override; + +signals: + void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total); + /// Signals that this widget is completely hidden now and should be replaced with the other + /// widget + void Hidden(); + +private: +#ifndef YUZU_QT_MOVIE_MISSING + std::unique_ptr<QMovie> animation; + std::unique_ptr<QBuffer> backing_buf; + std::unique_ptr<QByteArray> backing_mem; +#endif + std::unique_ptr<Ui::LoadingScreen> ui; + std::size_t previous_total = 0; + VideoCore::LoadCallbackStage previous_stage; + + QGraphicsOpacityEffect* opacity_effect = nullptr; + std::unique_ptr<QPropertyAnimation> fadeout_animation; + + // Definitions for the differences in text and styling for each stage + std::unordered_map<VideoCore::LoadCallbackStage, const char*> progressbar_style; + std::unordered_map<VideoCore::LoadCallbackStage, QString> stage_translations; + + // newly generated shaders are added to the end of the file, so when loading and compiling + // shaders, it will start quickly but end slow if new shaders were added since previous launch. + // These variables are used to detect the change in speed so we can generate an ETA + bool slow_shader_compile_start = false; + std::chrono::high_resolution_clock::time_point slow_shader_start; + std::chrono::high_resolution_clock::time_point previous_time; + std::size_t slow_shader_first_value = 0; +}; + +Q_DECLARE_METATYPE(VideoCore::LoadCallbackStage); diff --git a/src/yuzu/loading_screen.ui b/src/yuzu/loading_screen.ui new file mode 100644 index 000000000..a67d273fd --- /dev/null +++ b/src/yuzu/loading_screen.ui @@ -0,0 +1,161 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>LoadingScreen</class> + <widget class="QWidget" name="LoadingScreen"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>746</width> + <height>495</height> + </rect> + </property> + <property name="styleSheet"> + <string notr="true">background-color: rgb(0, 0, 0);</string> + </property> + <layout class="QVBoxLayout"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QWidget" name="fade_parent" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item alignment="Qt::AlignLeft|Qt::AlignTop"> + <widget class="QLabel" name="logo"> + <property name="text"> + <string/> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + <property name="margin"> + <number>30</number> + </property> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout" stretch="1,0,1"> + <property name="spacing"> + <number>15</number> + </property> + <property name="sizeConstraint"> + <enum>QLayout::SetNoConstraint</enum> + </property> + <item alignment="Qt::AlignHCenter|Qt::AlignBottom"> + <widget class="QLabel" name="stage"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="styleSheet"> + <string notr="true">background-color: black; color: white; +font: 75 20pt "Arial";</string> + </property> + <property name="text"> + <string>Loading Shaders 387 / 1628</string> + </property> + </widget> + </item> + <item alignment="Qt::AlignHCenter|Qt::AlignTop"> + <widget class="QProgressBar" name="progress_bar"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>500</width> + <height>40</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">QProgressBar { +color: white; +border: 2px solid white; +outline-color: black; +border-radius: 20px; +} +QProgressBar::chunk { +background-color: white; +border-radius: 15px; +}</string> + </property> + <property name="value"> + <number>50</number> + </property> + <property name="textVisible"> + <bool>false</bool> + </property> + <property name="format"> + <string>Loading Shaders %v out of %m</string> + </property> + </widget> + </item> + <item alignment="Qt::AlignHCenter|Qt::AlignTop"> + <widget class="QLabel" name="value"> + <property name="toolTip"> + <string notr="true"/> + </property> + <property name="styleSheet"> + <string notr="true">background-color: black; color: white; +font: 75 15pt "Arial";</string> + </property> + <property name="text"> + <string>Stage 1 of 2. Estimate Time 5m 4s</string> + </property> + </widget> + </item> + </layout> + </item> + <item alignment="Qt::AlignRight|Qt::AlignBottom"> + <widget class="QLabel" name="banner"> + <property name="styleSheet"> + <string notr="true">background-color: black;</string> + </property> + <property name="text"> + <string/> + </property> + <property name="margin"> + <number>30</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index f564de994..2c3e27c2e 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -92,6 +92,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "yuzu/game_list.h" #include "yuzu/game_list_p.h" #include "yuzu/hotkeys.h" +#include "yuzu/loading_screen.h" #include "yuzu/main.h" #include "yuzu/ui_settings.h" @@ -411,6 +412,17 @@ void GMainWindow::InitializeWidgets() { game_list = new GameList(vfs, this); ui.horizontalLayout->addWidget(game_list); + loading_screen = new LoadingScreen(this); + loading_screen->hide(); + ui.horizontalLayout->addWidget(loading_screen); + connect(loading_screen, &LoadingScreen::Hidden, [&] { + loading_screen->Clear(); + if (emulation_running) { + render_window->show(); + render_window->setFocus(); + } + }); + // Create status bar message_label = new QLabel(); // Configured separately for left alignment @@ -897,8 +909,8 @@ void GMainWindow::BootGame(const QString& filename) { .arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc, QString::fromStdString(title_name))); - render_window->show(); - render_window->setFocus(); + loading_screen->Prepare(Core::System::GetInstance().GetAppLoader()); + loading_screen->show(); emulation_running = true; if (ui.action_Fullscreen->isChecked()) { @@ -932,6 +944,8 @@ void GMainWindow::ShutdownGame() { ui.action_Load_Amiibo->setEnabled(false); ui.action_Capture_Screenshot->setEnabled(false); render_window->hide(); + loading_screen->hide(); + loading_screen->Clear(); game_list->show(); game_list->setFilterFocus(); setWindowTitle(QString("yuzu %1| %2-%3") @@ -1505,6 +1519,10 @@ void GMainWindow::OnStopGame() { ShutdownGame(); } +void GMainWindow::OnLoadComplete() { + loading_screen->OnLoadComplete(); +} + void GMainWindow::OnMenuReportCompatibility() { if (!Settings::values.yuzu_token.empty() && !Settings::values.yuzu_username.empty()) { CompatDB compatdb{this}; @@ -1771,9 +1789,8 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { this, tr("Confirm Key Rederivation"), tr("You are about to force rederive all of your keys. \nIf you do not know what this " "means or what you are doing, \nthis is a potentially destructive action. \nPlease " - "make " - "sure this is what you want \nand optionally make backups.\n\nThis will delete your " - "autogenerated key files and re-run the key derivation module."), + "make sure this is what you want \nand optionally make backups.\n\nThis will delete " + "your autogenerated key files and re-run the key derivation module."), QMessageBox::StandardButtons{QMessageBox::Ok, QMessageBox::Cancel}); if (res == QMessageBox::Cancel) @@ -1818,7 +1835,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { errors + tr("<br><br>You can get all of these and dump all of your games easily by " "following <a href='https://yuzu-emu.org/help/quickstart/'>the " - "quickstart guide</a>. Alternatively, you can use another method of dumping " + "quickstart guide</a>. Alternatively, you can use another method of dumping" "to obtain all of your keys.")); } diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 2d705ad54..e07c892cf 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -25,6 +25,7 @@ class GImageInfo; class GraphicsBreakPointsWidget; class GraphicsSurfaceWidget; class GRenderWindow; +class LoadingScreen; class MicroProfileDialog; class ProfilerWidget; class QLabel; @@ -109,10 +110,10 @@ signals: void WebBrowserFinishedBrowsing(); public slots: + void OnLoadComplete(); void ProfileSelectorSelectProfile(); void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters); void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message); - void WebBrowserOpenPage(std::string_view filename, std::string_view arguments); private: @@ -212,6 +213,7 @@ private: GRenderWindow* render_window; GameList* game_list; + LoadingScreen* loading_screen; // Status bar elements QLabel* message_label = nullptr; |