Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
abcf6b8256 | ||
|
ca6dae1744 | ||
|
b6acebcb11 | ||
|
ba702043f0 | ||
|
2a4c60c1dd | ||
|
a1532f813b | ||
|
26d5727b19 | ||
|
680e132318 | ||
|
90a5d989e7 | ||
|
de40153fa4 | ||
|
e9936e01c2 |
12
externals/CMakeLists.txt
vendored
12
externals/CMakeLists.txt
vendored
@@ -41,9 +41,15 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Catch2
|
# Catch2
|
||||||
set(CATCH_INSTALL_DOCS OFF CACHE BOOL "")
|
add_library(catch2 INTERFACE)
|
||||||
set(CATCH_INSTALL_EXTRAS OFF CACHE BOOL "")
|
if(USE_SYSTEM_CATCH2)
|
||||||
add_subdirectory(catch2)
|
find_package(Catch2 3.0.0 REQUIRED)
|
||||||
|
else()
|
||||||
|
set(CATCH_INSTALL_DOCS OFF CACHE BOOL "")
|
||||||
|
set(CATCH_INSTALL_EXTRAS OFF CACHE BOOL "")
|
||||||
|
add_subdirectory(catch2)
|
||||||
|
endif()
|
||||||
|
target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain)
|
||||||
|
|
||||||
# Crypto++
|
# Crypto++
|
||||||
if(USE_SYSTEM_CRYPTOPP)
|
if(USE_SYSTEM_CRYPTOPP)
|
||||||
|
@@ -25,6 +25,7 @@ option(USE_SYSTEM_LODEPNG "Use the system lodepng (instead of the bundled one)"
|
|||||||
option(USE_SYSTEM_OPENAL "Use the system OpenAL (instead of the bundled one)" OFF)
|
option(USE_SYSTEM_OPENAL "Use the system OpenAL (instead of the bundled one)" OFF)
|
||||||
option(USE_SYSTEM_VMA "Use the system VulkanMemoryAllocator (instead of the bundled one)" OFF)
|
option(USE_SYSTEM_VMA "Use the system VulkanMemoryAllocator (instead of the bundled one)" OFF)
|
||||||
option(USE_SYSTEM_VULKAN_HEADERS "Use the system Vulkan headers (instead of the bundled ones)" OFF)
|
option(USE_SYSTEM_VULKAN_HEADERS "Use the system Vulkan headers (instead of the bundled ones)" OFF)
|
||||||
|
option(USE_SYSTEM_CATCH2 "Use the system Catch2 (instead of the bundled one)" OFF)
|
||||||
|
|
||||||
# Qt and MoltenVK are handled separately
|
# Qt and MoltenVK are handled separately
|
||||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_SDL2 "Disable system SDL2" OFF "USE_SYSTEM_LIBS" OFF)
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_SDL2 "Disable system SDL2" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
@@ -49,6 +50,7 @@ CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_LODEPNG "Disable system lodepng" OFF "USE_
|
|||||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_OPENAL "Disable system OpenAL" OFF "USE_SYSTEM_LIBS" OFF)
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_OPENAL "Disable system OpenAL" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_VMA "Disable system VulkanMemoryAllocator" OFF "USE_SYSTEM_LIBS" OFF)
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_VMA "Disable system VulkanMemoryAllocator" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_VULKAN_HEADERS "Disable system Vulkan headers" OFF "USE_SYSTEM_LIBS" OFF)
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_VULKAN_HEADERS "Disable system Vulkan headers" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_CATCH2 "Disable system Catch2" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
|
|
||||||
set(LIB_VAR_LIST
|
set(LIB_VAR_LIST
|
||||||
SDL2
|
SDL2
|
||||||
@@ -73,6 +75,7 @@ set(LIB_VAR_LIST
|
|||||||
OPENAL
|
OPENAL
|
||||||
VMA
|
VMA
|
||||||
VULKAN_HEADERS
|
VULKAN_HEADERS
|
||||||
|
CATCH2
|
||||||
)
|
)
|
||||||
|
|
||||||
# First, check that USE_SYSTEM_XXX is not used with USE_SYSTEM_LIBS
|
# First, check that USE_SYSTEM_XXX is not used with USE_SYSTEM_LIBS
|
||||||
|
2
externals/cmake-modules/FindOpenAL.cmake
vendored
2
externals/cmake-modules/FindOpenAL.cmake
vendored
@@ -1,5 +1,5 @@
|
|||||||
if(NOT OPENAL_FOUND)
|
if(NOT OPENAL_FOUND)
|
||||||
pkg_check_modules(OOPENAL_TMP libopanal)
|
pkg_check_modules(OPENAL_TMP openal)
|
||||||
|
|
||||||
find_path(OPENAL_INCLUDE_DIRS NAMES al.h
|
find_path(OPENAL_INCLUDE_DIRS NAMES al.h
|
||||||
PATHS
|
PATHS
|
||||||
|
@@ -76,13 +76,13 @@ const std::array<UISettings::Shortcut, 28> Config::default_hotkeys {{
|
|||||||
{QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}},
|
{QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}},
|
||||||
{QStringLiteral("Swap Screens"), QStringLiteral("Main Window"), {QStringLiteral("F9"), Qt::WindowShortcut}},
|
{QStringLiteral("Swap Screens"), QStringLiteral("Main Window"), {QStringLiteral("F9"), Qt::WindowShortcut}},
|
||||||
{QStringLiteral("Toggle 3D"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+3"), Qt::ApplicationShortcut}},
|
{QStringLiteral("Toggle 3D"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+3"), Qt::ApplicationShortcut}},
|
||||||
{QStringLiteral("Toggle Per-Game Speed"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), Qt::ApplicationShortcut}},
|
{QStringLiteral("Toggle Custom Textures"), QStringLiteral("Main Window"), {QStringLiteral("F7"), Qt::ApplicationShortcut}},
|
||||||
{QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}},
|
{QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}},
|
||||||
{QStringLiteral("Toggle Frame Advancing"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+A"), Qt::ApplicationShortcut}},
|
{QStringLiteral("Toggle Frame Advancing"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+A"), Qt::ApplicationShortcut}},
|
||||||
|
{QStringLiteral("Toggle Per-Game Speed"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), Qt::ApplicationShortcut}},
|
||||||
{QStringLiteral("Toggle Screen Layout"), QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::WindowShortcut}},
|
{QStringLiteral("Toggle Screen Layout"), QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::WindowShortcut}},
|
||||||
{QStringLiteral("Toggle Status Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), Qt::WindowShortcut}},
|
{QStringLiteral("Toggle Status Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), Qt::WindowShortcut}},
|
||||||
{QStringLiteral("Toggle Texture Dumping"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::ApplicationShortcut}},
|
{QStringLiteral("Toggle Texture Dumping"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::ApplicationShortcut}},
|
||||||
{QStringLiteral("Toggle Custom Textures"), QStringLiteral("Main Window"), {QStringLiteral("F7"), Qt::ApplicationShortcut}},
|
|
||||||
}};
|
}};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
|
@@ -24,6 +24,7 @@
|
|||||||
// windows.h needs to be included before other windows headers
|
// windows.h needs to be included before other windows headers
|
||||||
#include <direct.h> // getcwd
|
#include <direct.h> // getcwd
|
||||||
#include <io.h>
|
#include <io.h>
|
||||||
|
#include <share.h>
|
||||||
#include <shellapi.h>
|
#include <shellapi.h>
|
||||||
#include <shlobj.h> // for SHGetFolderPath
|
#include <shlobj.h> // for SHGetFolderPath
|
||||||
#include <tchar.h>
|
#include <tchar.h>
|
||||||
@@ -1058,14 +1059,13 @@ bool IOFile::Open() {
|
|||||||
Close();
|
Close();
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
if (flags != 0) {
|
if (flags == 0) {
|
||||||
m_file = _wfsopen(Common::UTF8ToUTF16W(filename).c_str(),
|
flags = _SH_DENYNO;
|
||||||
Common::UTF8ToUTF16W(openmode).c_str(), flags);
|
|
||||||
m_good = m_file != nullptr;
|
|
||||||
} else {
|
|
||||||
m_good = _wfopen_s(&m_file, Common::UTF8ToUTF16W(filename).c_str(),
|
|
||||||
Common::UTF8ToUTF16W(openmode).c_str()) == 0;
|
|
||||||
}
|
}
|
||||||
|
m_file = _wfsopen(Common::UTF8ToUTF16W(filename).c_str(),
|
||||||
|
Common::UTF8ToUTF16W(openmode).c_str(), flags);
|
||||||
|
m_good = m_file != nullptr;
|
||||||
|
|
||||||
#elif ANDROID
|
#elif ANDROID
|
||||||
// Check whether filepath is startsWith content
|
// Check whether filepath is startsWith content
|
||||||
AndroidStorage::AndroidOpenMode android_open_mode = AndroidStorage::ParseOpenmode(openmode);
|
AndroidStorage::AndroidOpenMode android_open_mode = AndroidStorage::ParseOpenmode(openmode);
|
||||||
|
@@ -455,7 +455,6 @@ add_library(citra_core STATIC
|
|||||||
loader/smdh.h
|
loader/smdh.h
|
||||||
memory.cpp
|
memory.cpp
|
||||||
memory.h
|
memory.h
|
||||||
mmio.h
|
|
||||||
movie.cpp
|
movie.cpp
|
||||||
movie.h
|
movie.h
|
||||||
nus_download.cpp
|
nus_download.cpp
|
||||||
|
@@ -50,6 +50,9 @@ public:
|
|||||||
|
|
||||||
Loader::ResultStatus Load(std::span<const u8> file_data, std::size_t offset = 0);
|
Loader::ResultStatus Load(std::span<const u8> file_data, std::size_t offset = 0);
|
||||||
std::optional<std::array<u8, 16>> GetTitleKey() const;
|
std::optional<std::array<u8, 16>> GetTitleKey() const;
|
||||||
|
u64 GetTitleID() const {
|
||||||
|
return ticket_body.title_id;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Body ticket_body;
|
Body ticket_body;
|
||||||
|
@@ -10,7 +10,6 @@
|
|||||||
#include "core/hle/kernel/vm_manager.h"
|
#include "core/hle/kernel/vm_manager.h"
|
||||||
#include "core/hle/service/plgldr/plgldr.h"
|
#include "core/hle/service/plgldr/plgldr.h"
|
||||||
#include "core/memory.h"
|
#include "core/memory.h"
|
||||||
#include "core/mmio.h"
|
|
||||||
|
|
||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
|
|
||||||
@@ -33,9 +32,6 @@ bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const {
|
|||||||
backing_memory.GetPtr() + size != next.backing_memory.GetPtr()) {
|
backing_memory.GetPtr() + size != next.backing_memory.GetPtr()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (type == VMAType::MMIO && paddr + size != next.paddr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,26 +114,6 @@ ResultVal<VMManager::VMAHandle> VMManager::MapBackingMemory(VAddr target, Memory
|
|||||||
return MergeAdjacent(vma_handle);
|
return MergeAdjacent(vma_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<VMManager::VMAHandle> VMManager::MapMMIO(VAddr target, PAddr paddr, u32 size,
|
|
||||||
MemoryState state,
|
|
||||||
Memory::MMIORegionPointer mmio_handler) {
|
|
||||||
ASSERT(!is_locked);
|
|
||||||
|
|
||||||
// This is the appropriately sized VMA that will turn into our allocation.
|
|
||||||
CASCADE_RESULT(VMAIter vma_handle, CarveVMA(target, size));
|
|
||||||
VirtualMemoryArea& final_vma = vma_handle->second;
|
|
||||||
ASSERT(final_vma.size == size);
|
|
||||||
|
|
||||||
final_vma.type = VMAType::MMIO;
|
|
||||||
final_vma.permissions = VMAPermission::ReadWrite;
|
|
||||||
final_vma.meminfo_state = state;
|
|
||||||
final_vma.paddr = paddr;
|
|
||||||
final_vma.mmio_handler = mmio_handler;
|
|
||||||
UpdatePageTableForVMA(final_vma);
|
|
||||||
|
|
||||||
return MergeAdjacent(vma_handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
ResultCode VMManager::ChangeMemoryState(VAddr target, u32 size, MemoryState expected_state,
|
ResultCode VMManager::ChangeMemoryState(VAddr target, u32 size, MemoryState expected_state,
|
||||||
VMAPermission expected_perms, MemoryState new_state,
|
VMAPermission expected_perms, MemoryState new_state,
|
||||||
VMAPermission new_perms) {
|
VMAPermission new_perms) {
|
||||||
@@ -185,9 +161,7 @@ VMManager::VMAIter VMManager::Unmap(VMAIter vma_handle) {
|
|||||||
vma.type = VMAType::Free;
|
vma.type = VMAType::Free;
|
||||||
vma.permissions = VMAPermission::None;
|
vma.permissions = VMAPermission::None;
|
||||||
vma.meminfo_state = MemoryState::Free;
|
vma.meminfo_state = MemoryState::Free;
|
||||||
|
|
||||||
vma.backing_memory = nullptr;
|
vma.backing_memory = nullptr;
|
||||||
vma.paddr = 0;
|
|
||||||
|
|
||||||
UpdatePageTableForVMA(vma);
|
UpdatePageTableForVMA(vma);
|
||||||
|
|
||||||
@@ -344,9 +318,6 @@ VMManager::VMAIter VMManager::SplitVMA(VMAIter vma_handle, u32 offset_in_vma) {
|
|||||||
case VMAType::BackingMemory:
|
case VMAType::BackingMemory:
|
||||||
new_vma.backing_memory += offset_in_vma;
|
new_vma.backing_memory += offset_in_vma;
|
||||||
break;
|
break;
|
||||||
case VMAType::MMIO:
|
|
||||||
new_vma.paddr += offset_in_vma;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ASSERT(old_vma.CanBeMergedWith(new_vma));
|
ASSERT(old_vma.CanBeMergedWith(new_vma));
|
||||||
@@ -381,9 +352,6 @@ void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) {
|
|||||||
case VMAType::BackingMemory:
|
case VMAType::BackingMemory:
|
||||||
memory.MapMemoryRegion(*page_table, vma.base, vma.size, vma.backing_memory);
|
memory.MapMemoryRegion(*page_table, vma.base, vma.size, vma.backing_memory);
|
||||||
break;
|
break;
|
||||||
case VMAType::MMIO:
|
|
||||||
memory.MapIoRegion(*page_table, vma.base, vma.size, vma.mmio_handler);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto plgldr = Service::PLGLDR::GetService(Core::System::GetInstance());
|
auto plgldr = Service::PLGLDR::GetService(Core::System::GetInstance());
|
||||||
|
@@ -6,8 +6,6 @@
|
|||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
#include <boost/serialization/map.hpp>
|
#include <boost/serialization/map.hpp>
|
||||||
#include <boost/serialization/shared_ptr.hpp>
|
#include <boost/serialization/shared_ptr.hpp>
|
||||||
#include <boost/serialization/split_member.hpp>
|
#include <boost/serialization/split_member.hpp>
|
||||||
@@ -16,7 +14,6 @@
|
|||||||
#include "core/hle/kernel/memory.h"
|
#include "core/hle/kernel/memory.h"
|
||||||
#include "core/hle/result.h"
|
#include "core/hle/result.h"
|
||||||
#include "core/memory.h"
|
#include "core/memory.h"
|
||||||
#include "core/mmio.h"
|
|
||||||
|
|
||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
|
|
||||||
@@ -25,8 +22,6 @@ enum class VMAType : u8 {
|
|||||||
Free,
|
Free,
|
||||||
/// VMA is backed by a raw, unmanaged pointer.
|
/// VMA is backed by a raw, unmanaged pointer.
|
||||||
BackingMemory,
|
BackingMemory,
|
||||||
/// VMA is mapped to MMIO registers at a fixed PAddr.
|
|
||||||
MMIO,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Permissions for mapped memory blocks
|
/// Permissions for mapped memory blocks
|
||||||
@@ -74,15 +69,10 @@ struct VirtualMemoryArea {
|
|||||||
/// Tag returned by svcQueryMemory. Not otherwise used.
|
/// Tag returned by svcQueryMemory. Not otherwise used.
|
||||||
MemoryState meminfo_state = MemoryState::Free;
|
MemoryState meminfo_state = MemoryState::Free;
|
||||||
|
|
||||||
// Settings for type = BackingMemory
|
/// Settings for type = BackingMemory
|
||||||
/// Pointer backing this VMA. It will not be destroyed or freed when the VMA is removed.
|
/// Pointer backing this VMA. It will not be destroyed or freed when the VMA is removed.
|
||||||
MemoryRef backing_memory{};
|
MemoryRef backing_memory{};
|
||||||
|
|
||||||
// Settings for type = MMIO
|
|
||||||
/// Physical address of the register area this VMA maps to.
|
|
||||||
PAddr paddr = 0;
|
|
||||||
Memory::MMIORegionPointer mmio_handler = nullptr;
|
|
||||||
|
|
||||||
/// Tests if this area can be merged to the right with `next`.
|
/// Tests if this area can be merged to the right with `next`.
|
||||||
bool CanBeMergedWith(const VirtualMemoryArea& next) const;
|
bool CanBeMergedWith(const VirtualMemoryArea& next) const;
|
||||||
|
|
||||||
@@ -96,8 +86,6 @@ private:
|
|||||||
ar& permissions;
|
ar& permissions;
|
||||||
ar& meminfo_state;
|
ar& meminfo_state;
|
||||||
ar& backing_memory;
|
ar& backing_memory;
|
||||||
ar& paddr;
|
|
||||||
ar& mmio_handler;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -166,18 +154,6 @@ public:
|
|||||||
ResultVal<VMAHandle> MapBackingMemory(VAddr target, MemoryRef memory, u32 size,
|
ResultVal<VMAHandle> MapBackingMemory(VAddr target, MemoryRef memory, u32 size,
|
||||||
MemoryState state);
|
MemoryState state);
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps a memory-mapped IO region at a given address.
|
|
||||||
*
|
|
||||||
* @param target The guest address to start the mapping at.
|
|
||||||
* @param paddr The physical address where the registers are present.
|
|
||||||
* @param size Size of the mapping.
|
|
||||||
* @param state MemoryState tag to attach to the VMA.
|
|
||||||
* @param mmio_handler The handler that will implement read and write for this MMIO region.
|
|
||||||
*/
|
|
||||||
ResultVal<VMAHandle> MapMMIO(VAddr target, PAddr paddr, u32 size, MemoryState state,
|
|
||||||
Memory::MMIORegionPointer mmio_handler);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the memory state and permissions of the specified range. The range's original memory
|
* Updates the memory state and permissions of the specified range. The range's original memory
|
||||||
* state and permissions must match the `expected` parameters.
|
* state and permissions must match the `expected` parameters.
|
||||||
|
@@ -374,6 +374,45 @@ bool CIAFile::Close() const {
|
|||||||
|
|
||||||
void CIAFile::Flush() const {}
|
void CIAFile::Flush() const {}
|
||||||
|
|
||||||
|
TicketFile::TicketFile() {}
|
||||||
|
|
||||||
|
TicketFile::~TicketFile() {
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<std::size_t> TicketFile::Read(u64 offset, std::size_t length, u8* buffer) const {
|
||||||
|
UNIMPLEMENTED();
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<std::size_t> TicketFile::Write(u64 offset, std::size_t length, bool flush,
|
||||||
|
const u8* buffer) {
|
||||||
|
written += length;
|
||||||
|
data.resize(written);
|
||||||
|
std::memcpy(data.data() + offset, buffer, length);
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 TicketFile::GetSize() const {
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TicketFile::SetSize(u64 size) const {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TicketFile::Close() const {
|
||||||
|
FileSys::Ticket ticket;
|
||||||
|
if (ticket.Load(data, 0) == Loader::ResultStatus::Success) {
|
||||||
|
LOG_WARNING(Service_AM, "Discarding ticket for {:#016X}.", ticket.GetTitleID());
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Service_AM, "Invalid ticket provided to TicketFile.");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TicketFile::Flush() const {}
|
||||||
|
|
||||||
InstallStatus InstallCIA(const std::string& path,
|
InstallStatus InstallCIA(const std::string& path,
|
||||||
std::function<ProgressCallback>&& update_callback) {
|
std::function<ProgressCallback>&& update_callback) {
|
||||||
LOG_INFO(Service_AM, "Installing {}...", path);
|
LOG_INFO(Service_AM, "Installing {}...", path);
|
||||||
@@ -942,6 +981,10 @@ void Module::Interface::GetProgramInfos(Kernel::HLERequestContext& ctx) {
|
|||||||
rb.PushMappedBuffer(title_info_out);
|
rb.PushMappedBuffer(title_info_out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Module::Interface::GetProgramInfosIgnorePlatform(Kernel::HLERequestContext& ctx) {
|
||||||
|
GetProgramInfos(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
void Module::Interface::DeleteUserProgram(Kernel::HLERequestContext& ctx) {
|
void Module::Interface::DeleteUserProgram(Kernel::HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp(ctx);
|
IPC::RequestParser rp(ctx);
|
||||||
auto media_type = rp.PopEnum<FS::MediaType>();
|
auto media_type = rp.PopEnum<FS::MediaType>();
|
||||||
@@ -1177,6 +1220,16 @@ void Module::Interface::NeedsCleanup(Kernel::HLERequestContext& ctx) {
|
|||||||
rb.Push<bool>(false);
|
rb.Push<bool>(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Module::Interface::DoCleanup(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp(ctx);
|
||||||
|
const auto media_type = rp.Pop<u8>();
|
||||||
|
|
||||||
|
LOG_DEBUG(Service_AM, "(STUBBED) called, media_type={:#02x}", media_type);
|
||||||
|
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
|
rb.Push(RESULT_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
void Module::Interface::QueryAvailableTitleDatabase(Kernel::HLERequestContext& ctx) {
|
void Module::Interface::QueryAvailableTitleDatabase(Kernel::HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp(ctx);
|
IPC::RequestParser rp(ctx);
|
||||||
u8 media_type = rp.Pop<u8>();
|
u8 media_type = rp.Pop<u8>();
|
||||||
@@ -1188,6 +1241,45 @@ void Module::Interface::QueryAvailableTitleDatabase(Kernel::HLERequestContext& c
|
|||||||
LOG_WARNING(Service_AM, "(STUBBED) media_type={}", media_type);
|
LOG_WARNING(Service_AM, "(STUBBED) media_type={}", media_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Module::Interface::GetPersonalizedTicketInfoList(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp(ctx);
|
||||||
|
[[maybe_unused]] u32 ticket_count = rp.Pop<u32>();
|
||||||
|
[[maybe_unused]] auto& buffer = rp.PopMappedBuffer();
|
||||||
|
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||||
|
rb.Push(RESULT_SUCCESS); // No error
|
||||||
|
rb.Push(0);
|
||||||
|
|
||||||
|
LOG_WARNING(Service_AM, "(STUBBED) called, ticket_count={}", ticket_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Module::Interface::GetNumImportTitleContextsFiltered(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp(ctx);
|
||||||
|
u8 media_type = rp.Pop<u8>();
|
||||||
|
u8 filter = rp.Pop<u8>();
|
||||||
|
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||||
|
rb.Push(RESULT_SUCCESS); // No error
|
||||||
|
rb.Push(0);
|
||||||
|
|
||||||
|
LOG_WARNING(Service_AM, "(STUBBED) called, media_type={}, filter={}", media_type, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Module::Interface::GetImportTitleContextListFiltered(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp(ctx);
|
||||||
|
[[maybe_unused]] const u32 count = rp.Pop<u32>();
|
||||||
|
const u8 media_type = rp.Pop<u8>();
|
||||||
|
const u8 filter = rp.Pop<u8>();
|
||||||
|
auto& buffer = rp.PopMappedBuffer();
|
||||||
|
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
|
||||||
|
rb.Push(RESULT_SUCCESS); // No error
|
||||||
|
rb.Push(0);
|
||||||
|
rb.PushMappedBuffer(buffer);
|
||||||
|
|
||||||
|
LOG_WARNING(Service_AM, "(STUBBED) called, media_type={}, filter={}", media_type, filter);
|
||||||
|
}
|
||||||
|
|
||||||
void Module::Interface::CheckContentRights(Kernel::HLERequestContext& ctx) {
|
void Module::Interface::CheckContentRights(Kernel::HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp(ctx);
|
IPC::RequestParser rp(ctx);
|
||||||
u64 tid = rp.Pop<u64>();
|
u64 tid = rp.Pop<u64>();
|
||||||
@@ -1674,6 +1766,30 @@ void Module::Interface::GetMetaDataFromCia(Kernel::HLERequestContext& ctx) {
|
|||||||
rb.PushMappedBuffer(output_buffer);
|
rb.PushMappedBuffer(output_buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Module::Interface::BeginImportTicket(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp(ctx);
|
||||||
|
|
||||||
|
// Create our TicketFile handle for the app to write to
|
||||||
|
auto file = std::make_shared<Service::FS::File>(am->kernel, std::make_unique<TicketFile>(),
|
||||||
|
FileSys::Path{});
|
||||||
|
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
||||||
|
rb.Push(RESULT_SUCCESS); // No error
|
||||||
|
rb.PushCopyObjects(file->Connect());
|
||||||
|
|
||||||
|
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Module::Interface::EndImportTicket(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp(ctx);
|
||||||
|
[[maybe_unused]] const auto ticket = rp.PopObject<Kernel::ClientSession>();
|
||||||
|
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
|
rb.Push(RESULT_SUCCESS);
|
||||||
|
|
||||||
|
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||||
|
}
|
||||||
|
|
||||||
Module::Module(Core::System& system) : kernel(system.Kernel()) {
|
Module::Module(Core::System& system) : kernel(system.Kernel()) {
|
||||||
ScanForAllTitles();
|
ScanForAllTitles();
|
||||||
system_updater_mutex = system.Kernel().CreateMutex(false, "AM::SystemUpdaterMutex");
|
system_updater_mutex = system.Kernel().CreateMutex(false, "AM::SystemUpdaterMutex");
|
||||||
|
@@ -109,6 +109,25 @@ private:
|
|||||||
std::unique_ptr<DecryptionState> decryption_state;
|
std::unique_ptr<DecryptionState> decryption_state;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A file handled returned for Tickets to be written into and subsequently installed.
|
||||||
|
class TicketFile final : public FileSys::FileBackend {
|
||||||
|
public:
|
||||||
|
explicit TicketFile();
|
||||||
|
~TicketFile();
|
||||||
|
|
||||||
|
ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override;
|
||||||
|
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush,
|
||||||
|
const u8* buffer) override;
|
||||||
|
u64 GetSize() const override;
|
||||||
|
bool SetSize(u64 size) const override;
|
||||||
|
bool Close() const override;
|
||||||
|
void Flush() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
u64 written = 0;
|
||||||
|
std::vector<u8> data;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Installs a CIA file from a specified file path.
|
* Installs a CIA file from a specified file path.
|
||||||
* @param path file path of the CIA file to install
|
* @param path file path of the CIA file to install
|
||||||
@@ -272,6 +291,18 @@ public:
|
|||||||
*/
|
*/
|
||||||
void GetProgramInfos(Kernel::HLERequestContext& ctx);
|
void GetProgramInfos(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AM::GetProgramInfosIgnorePlatform service function
|
||||||
|
* Inputs:
|
||||||
|
* 1 : u8 Mediatype
|
||||||
|
* 2 : Total titles
|
||||||
|
* 4 : TitleIDList pointer
|
||||||
|
* 6 : TitleList pointer
|
||||||
|
* Outputs:
|
||||||
|
* 1 : Result, 0 on success, otherwise error code
|
||||||
|
*/
|
||||||
|
void GetProgramInfosIgnorePlatform(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AM::DeleteUserProgram service function
|
* AM::DeleteUserProgram service function
|
||||||
* Deletes a user program
|
* Deletes a user program
|
||||||
@@ -389,6 +420,15 @@ public:
|
|||||||
*/
|
*/
|
||||||
void NeedsCleanup(Kernel::HLERequestContext& ctx);
|
void NeedsCleanup(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AM::DoCleanup service function
|
||||||
|
* Inputs:
|
||||||
|
* 1 : Media Type
|
||||||
|
* Outputs:
|
||||||
|
* 1 : Result, 0 on success, otherwise error code
|
||||||
|
*/
|
||||||
|
void DoCleanup(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AM::QueryAvailableTitleDatabase service function
|
* AM::QueryAvailableTitleDatabase service function
|
||||||
* Inputs:
|
* Inputs:
|
||||||
@@ -399,6 +439,42 @@ public:
|
|||||||
*/
|
*/
|
||||||
void QueryAvailableTitleDatabase(Kernel::HLERequestContext& ctx);
|
void QueryAvailableTitleDatabase(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AM::GetPersonalizedTicketInfoList service function
|
||||||
|
* Inputs:
|
||||||
|
* 1 : Count
|
||||||
|
* 2-3 : Buffer
|
||||||
|
* Outputs:
|
||||||
|
* 1 : Result, 0 on success, otherwise error code
|
||||||
|
* 2 : Out count
|
||||||
|
*/
|
||||||
|
void GetPersonalizedTicketInfoList(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AM::GetNumImportTitleContextsFiltered service function
|
||||||
|
* Inputs:
|
||||||
|
* 1 : Count
|
||||||
|
* 2 : Filter
|
||||||
|
* Outputs:
|
||||||
|
* 1 : Result, 0 on success, otherwise error code
|
||||||
|
* 2 : Num import titles
|
||||||
|
*/
|
||||||
|
void GetNumImportTitleContextsFiltered(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AM::GetImportTitleContextListFiltered service function
|
||||||
|
* Inputs:
|
||||||
|
* 1 : Count
|
||||||
|
* 2 : Media type
|
||||||
|
* 3 : filter
|
||||||
|
* 4-5 : Buffer
|
||||||
|
* Outputs:
|
||||||
|
* 1 : Result, 0 on success, otherwise error code
|
||||||
|
* 2 : Out count
|
||||||
|
* 3-4 : Out buffer
|
||||||
|
*/
|
||||||
|
void GetImportTitleContextListFiltered(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AM::CheckContentRights service function
|
* AM::CheckContentRights service function
|
||||||
* Inputs:
|
* Inputs:
|
||||||
@@ -602,6 +678,25 @@ public:
|
|||||||
*/
|
*/
|
||||||
void GetMetaDataFromCia(Kernel::HLERequestContext& ctx);
|
void GetMetaDataFromCia(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AM::BeginImportTicket service function
|
||||||
|
* Inputs:
|
||||||
|
* 1 : Media type to install title to
|
||||||
|
* Outputs:
|
||||||
|
* 1 : Result, 0 on success, otherwise error code
|
||||||
|
* 2-3 : TicketHandle handle for application to write to
|
||||||
|
*/
|
||||||
|
void BeginImportTicket(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AM::EndImportTicket service function
|
||||||
|
* Inputs:
|
||||||
|
* 1-2 : TicketHandle handle application wrote to
|
||||||
|
* Outputs:
|
||||||
|
* 1 : Result, 0 on success, otherwise error code
|
||||||
|
*/
|
||||||
|
void EndImportTicket(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::shared_ptr<Module> am;
|
std::shared_ptr<Module> am;
|
||||||
};
|
};
|
||||||
|
@@ -42,19 +42,19 @@ AM_NET::AM_NET(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "a
|
|||||||
{0x001E, nullptr, "ReadTwlBackupInfo"},
|
{0x001E, nullptr, "ReadTwlBackupInfo"},
|
||||||
{0x001F, nullptr, "DeleteAllExpiredUserPrograms"},
|
{0x001F, nullptr, "DeleteAllExpiredUserPrograms"},
|
||||||
{0x0020, nullptr, "GetTwlArchiveResourceInfo"},
|
{0x0020, nullptr, "GetTwlArchiveResourceInfo"},
|
||||||
{0x0021, nullptr, "GetPersonalizedTicketInfoList"},
|
{0x0021, &AM_NET::GetPersonalizedTicketInfoList, "GetPersonalizedTicketInfoList"},
|
||||||
{0x0022, nullptr, "DeleteAllImportContextsFiltered"},
|
{0x0022, nullptr, "DeleteAllImportContextsFiltered"},
|
||||||
{0x0023, nullptr, "GetNumImportTitleContextsFiltered"},
|
{0x0023, &AM_NET::GetNumImportTitleContextsFiltered, "GetNumImportTitleContextsFiltered"},
|
||||||
{0x0024, nullptr, "GetImportTitleContextListFiltered"},
|
{0x0024, &AM_NET::GetImportTitleContextListFiltered, "GetImportTitleContextListFiltered"},
|
||||||
{0x0025, nullptr, "CheckContentRights"},
|
{0x0025, &AM_NET::CheckContentRights, "CheckContentRights"},
|
||||||
{0x0026, nullptr, "GetTicketLimitInfos"},
|
{0x0026, nullptr, "GetTicketLimitInfos"},
|
||||||
{0x0027, nullptr, "GetDemoLaunchInfos"},
|
{0x0027, nullptr, "GetDemoLaunchInfos"},
|
||||||
{0x0028, nullptr, "ReadTwlBackupInfoEx"},
|
{0x0028, nullptr, "ReadTwlBackupInfoEx"},
|
||||||
{0x0029, nullptr, "DeleteUserProgramsAtomically"},
|
{0x0029, nullptr, "DeleteUserProgramsAtomically"},
|
||||||
{0x002A, nullptr, "GetNumExistingContentInfosSystem"},
|
{0x002A, nullptr, "GetNumExistingContentInfosSystem"},
|
||||||
{0x002B, nullptr, "ListExistingContentInfosSystem"},
|
{0x002B, nullptr, "ListExistingContentInfosSystem"},
|
||||||
{0x002C, nullptr, "GetProgramInfosIgnorePlatform"},
|
{0x002C, &AM_NET::GetProgramInfosIgnorePlatform, "GetProgramInfosIgnorePlatform"},
|
||||||
{0x002D, nullptr, "CheckContentRightsIgnorePlatform"},
|
{0x002D, &AM_NET::CheckContentRightsIgnorePlatform, "CheckContentRightsIgnorePlatform"},
|
||||||
{0x0401, nullptr, "UpdateFirmwareTo"},
|
{0x0401, nullptr, "UpdateFirmwareTo"},
|
||||||
{0x0402, &AM_NET::BeginImportProgram, "BeginImportProgram"},
|
{0x0402, &AM_NET::BeginImportProgram, "BeginImportProgram"},
|
||||||
{0x0403, nullptr, "BeginImportProgramTemporarily"},
|
{0x0403, nullptr, "BeginImportProgramTemporarily"},
|
||||||
@@ -72,7 +72,7 @@ AM_NET::AM_NET(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "a
|
|||||||
{0x040F, nullptr, "UpdateFirmwareAuto"},
|
{0x040F, nullptr, "UpdateFirmwareAuto"},
|
||||||
{0x0410, &AM_NET::DeleteProgram, "DeleteProgram"},
|
{0x0410, &AM_NET::DeleteProgram, "DeleteProgram"},
|
||||||
{0x0411, nullptr, "GetTwlProgramListForReboot"},
|
{0x0411, nullptr, "GetTwlProgramListForReboot"},
|
||||||
{0x0412, nullptr, "GetSystemUpdaterMutex"},
|
{0x0412, &AM_NET::GetSystemUpdaterMutex, "GetSystemUpdaterMutex"},
|
||||||
{0x0413, &AM_NET::GetMetaSizeFromCia, "GetMetaSizeFromCia"},
|
{0x0413, &AM_NET::GetMetaSizeFromCia, "GetMetaSizeFromCia"},
|
||||||
{0x0414, &AM_NET::GetMetaDataFromCia, "GetMetaDataFromCia"},
|
{0x0414, &AM_NET::GetMetaDataFromCia, "GetMetaDataFromCia"},
|
||||||
{0x0415, nullptr, "CheckDemoLaunchRights"},
|
{0x0415, nullptr, "CheckDemoLaunchRights"},
|
||||||
@@ -80,9 +80,9 @@ AM_NET::AM_NET(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "a
|
|||||||
{0x0417, nullptr, "PerpetuateAgbSaveData"},
|
{0x0417, nullptr, "PerpetuateAgbSaveData"},
|
||||||
{0x0418, nullptr, "BeginImportProgramForOverWrite"},
|
{0x0418, nullptr, "BeginImportProgramForOverWrite"},
|
||||||
{0x0419, nullptr, "BeginImportSystemProgram"},
|
{0x0419, nullptr, "BeginImportSystemProgram"},
|
||||||
{0x0801, nullptr, "BeginImportTicket"},
|
{0x0801, &AM_NET::BeginImportTicket, "BeginImportTicket"},
|
||||||
{0x0802, nullptr, "CancelImportTicket"},
|
{0x0802, nullptr, "CancelImportTicket"},
|
||||||
{0x0803, nullptr, "EndImportTicket"},
|
{0x0803, &AM_NET::EndImportTicket, "EndImportTicket"},
|
||||||
{0x0804, nullptr, "BeginImportTitle"},
|
{0x0804, nullptr, "BeginImportTitle"},
|
||||||
{0x0805, nullptr, "StopImportTitle"},
|
{0x0805, nullptr, "StopImportTitle"},
|
||||||
{0x0806, nullptr, "ResumeImportTitle"},
|
{0x0806, nullptr, "ResumeImportTitle"},
|
||||||
|
@@ -29,7 +29,7 @@ AM_SYS::AM_SYS(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "a
|
|||||||
{0x0011, nullptr, "GetImportContentContexts"},
|
{0x0011, nullptr, "GetImportContentContexts"},
|
||||||
{0x0012, nullptr, "DeleteImportContentContexts"},
|
{0x0012, nullptr, "DeleteImportContentContexts"},
|
||||||
{0x0013, &AM_SYS::NeedsCleanup, "NeedsCleanup"},
|
{0x0013, &AM_SYS::NeedsCleanup, "NeedsCleanup"},
|
||||||
{0x0014, nullptr, "DoCleanup"},
|
{0x0014, &AM_SYS::DoCleanup, "DoCleanup"},
|
||||||
{0x0015, nullptr, "DeleteAllImportContexts"},
|
{0x0015, nullptr, "DeleteAllImportContexts"},
|
||||||
{0x0016, nullptr, "DeleteAllTemporaryPrograms"},
|
{0x0016, nullptr, "DeleteAllTemporaryPrograms"},
|
||||||
{0x0017, nullptr, "ImportTwlBackupLegacy"},
|
{0x0017, nullptr, "ImportTwlBackupLegacy"},
|
||||||
|
@@ -875,7 +875,7 @@ void FS_USER::GetThisSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
|
|||||||
// TODO: Implement Secure Value Lookup & Generation
|
// TODO: Implement Secure Value Lookup & Generation
|
||||||
|
|
||||||
rb.Push<bool>(false); // indicates that the secure value doesn't exist
|
rb.Push<bool>(false); // indicates that the secure value doesn't exist
|
||||||
rb.Push<bool>(false); // looks like a boolean value, purpose unknown
|
rb.Push<bool>(true); // seems to override checking the validity so should be true
|
||||||
rb.Push<u64>(0); // the secure value
|
rb.Push<u64>(0); // the secure value
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -913,7 +913,7 @@ void FS_USER::GetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
|
|||||||
// TODO: Implement Secure Value Lookup & Generation
|
// TODO: Implement Secure Value Lookup & Generation
|
||||||
|
|
||||||
rb.Push<bool>(false); // indicates that the secure value doesn't exist
|
rb.Push<bool>(false); // indicates that the secure value doesn't exist
|
||||||
rb.Push<bool>(false); // looks like a boolean value, purpose unknown
|
rb.Push<bool>(true); // seems to override checking the validity so should be true
|
||||||
rb.Push<u64>(0); // the secure value
|
rb.Push<u64>(0); // the secure value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -298,7 +298,7 @@ struct MIC_U::Impl {
|
|||||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
rb.Push<u8>(state.gain);
|
rb.Push<u8>(state.gain);
|
||||||
LOG_TRACE(Service_MIC, "gain={}", gain);
|
LOG_TRACE(Service_MIC, "gain={}", state.gain);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetPower(Kernel::HLERequestContext& ctx) {
|
void SetPower(Kernel::HLERequestContext& ctx) {
|
||||||
|
@@ -9,6 +9,7 @@
|
|||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/hle/ipc_helpers.h"
|
#include "core/hle/ipc_helpers.h"
|
||||||
#include "core/hle/service/ps/ps_ps.h"
|
#include "core/hle/service/ps/ps_ps.h"
|
||||||
|
#include "core/hle/service/ssl/ssl_c.h"
|
||||||
#include "core/hw/aes/arithmetic128.h"
|
#include "core/hw/aes/arithmetic128.h"
|
||||||
#include "core/hw/aes/key.h"
|
#include "core/hw/aes/key.h"
|
||||||
|
|
||||||
@@ -146,6 +147,20 @@ void PS_PS::EncryptDecryptAes(Kernel::HLERequestContext& ctx) {
|
|||||||
rb.PushMappedBuffer(destination);
|
rb.PushMappedBuffer(destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PS_PS::GenerateRandomBytes(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp(ctx);
|
||||||
|
const u32 size = rp.Pop<u32>();
|
||||||
|
auto buffer = rp.PopMappedBuffer();
|
||||||
|
|
||||||
|
std::vector<u8> out_data(size);
|
||||||
|
SSL::GenerateRandomData(out_data);
|
||||||
|
buffer.Write(out_data.data(), 0, size);
|
||||||
|
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
||||||
|
rb.Push(RESULT_SUCCESS);
|
||||||
|
rb.PushMappedBuffer(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
PS_PS::PS_PS() : ServiceFramework("ps:ps", DefaultMaxSessions) {
|
PS_PS::PS_PS() : ServiceFramework("ps:ps", DefaultMaxSessions) {
|
||||||
static const FunctionInfo functions[] = {
|
static const FunctionInfo functions[] = {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
@@ -160,7 +175,7 @@ PS_PS::PS_PS() : ServiceFramework("ps:ps", DefaultMaxSessions) {
|
|||||||
{0x000A, nullptr, "GetLocalFriendCodeSeed"},
|
{0x000A, nullptr, "GetLocalFriendCodeSeed"},
|
||||||
{0x000B, nullptr, "GetDeviceId"},
|
{0x000B, nullptr, "GetDeviceId"},
|
||||||
{0x000C, nullptr, "SeedRNG"},
|
{0x000C, nullptr, "SeedRNG"},
|
||||||
{0x000D, nullptr, "GenerateRandomBytes"},
|
{0x000D, &PS_PS::GenerateRandomBytes, "GenerateRandomBytes"},
|
||||||
{0x000E, nullptr, "InterfaceForPXI_0x04010084"},
|
{0x000E, nullptr, "InterfaceForPXI_0x04010084"},
|
||||||
{0x000F, nullptr, "InterfaceForPXI_0x04020082"},
|
{0x000F, nullptr, "InterfaceForPXI_0x04020082"},
|
||||||
{0x0010, nullptr, "InterfaceForPXI_0x04030044"},
|
{0x0010, nullptr, "InterfaceForPXI_0x04030044"},
|
||||||
|
@@ -10,12 +10,24 @@ SERIALIZE_EXPORT_IMPL(Service::QTM::QTM_S)
|
|||||||
|
|
||||||
namespace Service::QTM {
|
namespace Service::QTM {
|
||||||
|
|
||||||
|
void QTM_S::GetHeadtrackingInfo(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp(ctx);
|
||||||
|
[[maybe_unused]] const u64 unknown = rp.Pop<u64>();
|
||||||
|
|
||||||
|
std::array<u8, 0x40> data{};
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(17, 0);
|
||||||
|
rb.Push(RESULT_SUCCESS);
|
||||||
|
rb.PushRaw<std::array<u8, 0x40>>(data);
|
||||||
|
|
||||||
|
LOG_DEBUG(Service, "(STUBBED) called");
|
||||||
|
}
|
||||||
|
|
||||||
QTM_S::QTM_S() : ServiceFramework("qtm:s", 2) {
|
QTM_S::QTM_S() : ServiceFramework("qtm:s", 2) {
|
||||||
static const FunctionInfo functions[] = {
|
static const FunctionInfo functions[] = {
|
||||||
// qtm common commands
|
// qtm common commands
|
||||||
// clang-format off
|
// clang-format off
|
||||||
{0x0001, nullptr, "GetHeadtrackingInfoRaw"},
|
{0x0001, nullptr, "GetHeadtrackingInfoRaw"},
|
||||||
{0x0002, nullptr, "GetHeadtrackingInfo"},
|
{0x0002, &QTM_S::GetHeadtrackingInfo, "GetHeadtrackingInfo"},
|
||||||
// clang-format on
|
// clang-format on
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -15,6 +15,8 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
SERVICE_SERIALIZATION_SIMPLE
|
SERVICE_SERIALIZATION_SIMPLE
|
||||||
|
|
||||||
|
void GetHeadtrackingInfo(Kernel::HLERequestContext& ctx);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Service::QTM
|
} // namespace Service::QTM
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <openssl/rand.h>
|
||||||
#include "common/archives.h"
|
#include "common/archives.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
@@ -16,10 +17,6 @@ void SSL_C::Initialize(Kernel::HLERequestContext& ctx) {
|
|||||||
IPC::RequestParser rp(ctx);
|
IPC::RequestParser rp(ctx);
|
||||||
rp.PopPID();
|
rp.PopPID();
|
||||||
|
|
||||||
// Seed random number generator when the SSL service is initialized
|
|
||||||
std::random_device rand_device;
|
|
||||||
rand_gen.seed(rand_device());
|
|
||||||
|
|
||||||
// Stub, return success
|
// Stub, return success
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
@@ -27,33 +24,13 @@ void SSL_C::Initialize(Kernel::HLERequestContext& ctx) {
|
|||||||
|
|
||||||
void SSL_C::GenerateRandomData(Kernel::HLERequestContext& ctx) {
|
void SSL_C::GenerateRandomData(Kernel::HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp(ctx);
|
IPC::RequestParser rp(ctx);
|
||||||
u32 size = rp.Pop<u32>();
|
const u32 size = rp.Pop<u32>();
|
||||||
auto buffer = rp.PopMappedBuffer();
|
auto buffer = rp.PopMappedBuffer();
|
||||||
|
|
||||||
// Fill the output buffer with random data.
|
std::vector<u8> out_data(size);
|
||||||
u32 data = 0;
|
SSL::GenerateRandomData(out_data);
|
||||||
u32 i = 0;
|
buffer.Write(out_data.data(), 0, size);
|
||||||
while (i < size) {
|
|
||||||
if ((i % 4) == 0) {
|
|
||||||
// The random number generator returns 4 bytes worth of data, so generate new random
|
|
||||||
// data when i == 0 and when i is divisible by 4
|
|
||||||
data = rand_gen();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (size > 4) {
|
|
||||||
// Use up the entire 4 bytes of the random data for as long as possible
|
|
||||||
buffer.Write(&data, i, 4);
|
|
||||||
i += 4;
|
|
||||||
} else if (size == 2) {
|
|
||||||
buffer.Write(&data, i, 2);
|
|
||||||
i += 2;
|
|
||||||
} else {
|
|
||||||
buffer.Write(&data, i, 1);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stub, return success
|
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
rb.PushMappedBuffer(buffer);
|
rb.PushMappedBuffer(buffer);
|
||||||
@@ -96,4 +73,9 @@ void InstallInterfaces(Core::System& system) {
|
|||||||
std::make_shared<SSL_C>()->InstallAsService(service_manager);
|
std::make_shared<SSL_C>()->InstallAsService(service_manager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GenerateRandomData(std::vector<u8>& out) {
|
||||||
|
// Fill the output buffer with random data.
|
||||||
|
RAND_bytes(out.data(), static_cast<int>(out.size()));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Service::SSL
|
} // namespace Service::SSL
|
||||||
|
@@ -21,14 +21,13 @@ private:
|
|||||||
void Initialize(Kernel::HLERequestContext& ctx);
|
void Initialize(Kernel::HLERequestContext& ctx);
|
||||||
void GenerateRandomData(Kernel::HLERequestContext& ctx);
|
void GenerateRandomData(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
// TODO: Implement a proper CSPRNG in the future when actual security is needed
|
|
||||||
std::mt19937 rand_gen;
|
|
||||||
|
|
||||||
SERVICE_SERIALIZATION_SIMPLE
|
SERVICE_SERIALIZATION_SIMPLE
|
||||||
};
|
};
|
||||||
|
|
||||||
void InstallInterfaces(Core::System& system);
|
void InstallInterfaces(Core::System& system);
|
||||||
|
|
||||||
|
void GenerateRandomData(std::vector<u8>& out);
|
||||||
|
|
||||||
} // namespace Service::SSL
|
} // namespace Service::SSL
|
||||||
|
|
||||||
BOOST_CLASS_EXPORT_KEY(Service::SSL::SSL_C)
|
BOOST_CLASS_EXPORT_KEY(Service::SSL::SSL_C)
|
||||||
|
@@ -156,20 +156,6 @@ public:
|
|||||||
return system.GetRunningCore().GetPC();
|
return system.GetRunningCore().GetPC();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This function should only be called for virtual addreses with attribute `PageType::Special`.
|
|
||||||
*/
|
|
||||||
MMIORegionPointer GetMMIOHandler(const PageTable& page_table, VAddr vaddr) {
|
|
||||||
for (const auto& region : page_table.special_regions) {
|
|
||||||
if (vaddr >= region.base && vaddr < (region.base + region.size)) {
|
|
||||||
return region.handler;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ASSERT_MSG(false, "Mapped IO page without a handler @ {:08X}", vaddr);
|
|
||||||
return nullptr; // Should never happen
|
|
||||||
}
|
|
||||||
|
|
||||||
template <bool UNSAFE>
|
template <bool UNSAFE>
|
||||||
void ReadBlockImpl(const Kernel::Process& process, const VAddr src_addr, void* dest_buffer,
|
void ReadBlockImpl(const Kernel::Process& process, const VAddr src_addr, void* dest_buffer,
|
||||||
const std::size_t size) {
|
const std::size_t size) {
|
||||||
@@ -201,12 +187,6 @@ public:
|
|||||||
std::memcpy(dest_buffer, src_ptr, copy_amount);
|
std::memcpy(dest_buffer, src_ptr, copy_amount);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PageType::Special: {
|
|
||||||
MMIORegionPointer handler = GetMMIOHandler(page_table, current_vaddr);
|
|
||||||
DEBUG_ASSERT(handler);
|
|
||||||
handler->ReadBlock(current_vaddr, dest_buffer, copy_amount);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PageType::RasterizerCachedMemory: {
|
case PageType::RasterizerCachedMemory: {
|
||||||
if constexpr (!UNSAFE) {
|
if constexpr (!UNSAFE) {
|
||||||
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
|
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
|
||||||
@@ -255,12 +235,6 @@ public:
|
|||||||
std::memcpy(dest_ptr, src_buffer, copy_amount);
|
std::memcpy(dest_ptr, src_buffer, copy_amount);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PageType::Special: {
|
|
||||||
MMIORegionPointer handler = GetMMIOHandler(page_table, current_vaddr);
|
|
||||||
DEBUG_ASSERT(handler);
|
|
||||||
handler->WriteBlock(current_vaddr, src_buffer, copy_amount);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PageType::RasterizerCachedMemory: {
|
case PageType::RasterizerCachedMemory: {
|
||||||
if constexpr (!UNSAFE) {
|
if constexpr (!UNSAFE) {
|
||||||
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
|
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
|
||||||
@@ -405,16 +379,6 @@ void MemorySystem::MapMemoryRegion(PageTable& page_table, VAddr base, u32 size,
|
|||||||
MapPages(page_table, base / CITRA_PAGE_SIZE, size / CITRA_PAGE_SIZE, target, PageType::Memory);
|
MapPages(page_table, base / CITRA_PAGE_SIZE, size / CITRA_PAGE_SIZE, target, PageType::Memory);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MemorySystem::MapIoRegion(PageTable& page_table, VAddr base, u32 size,
|
|
||||||
MMIORegionPointer mmio_handler) {
|
|
||||||
ASSERT_MSG((size & CITRA_PAGE_MASK) == 0, "non-page aligned size: {:08X}", size);
|
|
||||||
ASSERT_MSG((base & CITRA_PAGE_MASK) == 0, "non-page aligned base: {:08X}", base);
|
|
||||||
MapPages(page_table, base / CITRA_PAGE_SIZE, size / CITRA_PAGE_SIZE, nullptr,
|
|
||||||
PageType::Special);
|
|
||||||
|
|
||||||
page_table.special_regions.emplace_back(SpecialRegion{base, size, mmio_handler});
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemorySystem::UnmapRegion(PageTable& page_table, VAddr base, u32 size) {
|
void MemorySystem::UnmapRegion(PageTable& page_table, VAddr base, u32 size) {
|
||||||
ASSERT_MSG((size & CITRA_PAGE_MASK) == 0, "non-page aligned size: {:08X}", size);
|
ASSERT_MSG((size & CITRA_PAGE_MASK) == 0, "non-page aligned size: {:08X}", size);
|
||||||
ASSERT_MSG((base & CITRA_PAGE_MASK) == 0, "non-page aligned base: {:08X}", base);
|
ASSERT_MSG((base & CITRA_PAGE_MASK) == 0, "non-page aligned base: {:08X}", base);
|
||||||
@@ -437,9 +401,6 @@ void MemorySystem::UnregisterPageTable(std::shared_ptr<PageTable> page_table) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
T ReadMMIO(MMIORegionPointer mmio_handler, VAddr addr);
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T MemorySystem::Read(const VAddr vaddr) {
|
T MemorySystem::Read(const VAddr vaddr) {
|
||||||
const u8* page_pointer = impl->current_page_table->pointers[vaddr >> CITRA_PAGE_BITS];
|
const u8* page_pointer = impl->current_page_table->pointers[vaddr >> CITRA_PAGE_BITS];
|
||||||
@@ -482,8 +443,6 @@ T MemorySystem::Read(const VAddr vaddr) {
|
|||||||
std::memcpy(&value, GetPointerForRasterizerCache(vaddr), sizeof(T));
|
std::memcpy(&value, GetPointerForRasterizerCache(vaddr), sizeof(T));
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
case PageType::Special:
|
|
||||||
return ReadMMIO<T>(impl->GetMMIOHandler(*impl->current_page_table, vaddr), vaddr);
|
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
@@ -491,9 +450,6 @@ T MemorySystem::Read(const VAddr vaddr) {
|
|||||||
return T{};
|
return T{};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
void WriteMMIO(MMIORegionPointer mmio_handler, VAddr addr, const T data);
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void MemorySystem::Write(const VAddr vaddr, const T data) {
|
void MemorySystem::Write(const VAddr vaddr, const T data) {
|
||||||
u8* page_pointer = impl->current_page_table->pointers[vaddr >> CITRA_PAGE_BITS];
|
u8* page_pointer = impl->current_page_table->pointers[vaddr >> CITRA_PAGE_BITS];
|
||||||
@@ -531,9 +487,6 @@ void MemorySystem::Write(const VAddr vaddr, const T data) {
|
|||||||
std::memcpy(GetPointerForRasterizerCache(vaddr), &data, sizeof(T));
|
std::memcpy(GetPointerForRasterizerCache(vaddr), &data, sizeof(T));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PageType::Special:
|
|
||||||
WriteMMIO<T>(impl->GetMMIOHandler(*impl->current_page_table, vaddr), vaddr, data);
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
@@ -564,9 +517,6 @@ bool MemorySystem::WriteExclusive(const VAddr vaddr, const T data, const T expec
|
|||||||
reinterpret_cast<volatile T*>(GetPointerForRasterizerCache(vaddr).GetPtr());
|
reinterpret_cast<volatile T*>(GetPointerForRasterizerCache(vaddr).GetPtr());
|
||||||
return Common::AtomicCompareAndSwap(volatile_pointer, data, expected);
|
return Common::AtomicCompareAndSwap(volatile_pointer, data, expected);
|
||||||
}
|
}
|
||||||
case PageType::Special:
|
|
||||||
WriteMMIO<T>(impl->GetMMIOHandler(*impl->current_page_table, vaddr), vaddr, data);
|
|
||||||
return false;
|
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
@@ -577,18 +527,12 @@ bool MemorySystem::IsValidVirtualAddress(const Kernel::Process& process, const V
|
|||||||
auto& page_table = *process.vm_manager.page_table;
|
auto& page_table = *process.vm_manager.page_table;
|
||||||
|
|
||||||
auto page_pointer = page_table.pointers[vaddr >> CITRA_PAGE_BITS];
|
auto page_pointer = page_table.pointers[vaddr >> CITRA_PAGE_BITS];
|
||||||
if (page_pointer)
|
if (page_pointer) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (page_table.attributes[vaddr >> CITRA_PAGE_BITS] == PageType::RasterizerCachedMemory)
|
if (page_table.attributes[vaddr >> CITRA_PAGE_BITS] == PageType::RasterizerCachedMemory) {
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (page_table.attributes[vaddr >> CITRA_PAGE_BITS] != PageType::Special)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
MMIORegionPointer mmio_region = impl->GetMMIOHandler(page_table, vaddr);
|
|
||||||
if (mmio_region) {
|
|
||||||
return mmio_region->IsValidAddress(vaddr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -923,8 +867,6 @@ void MemorySystem::ZeroBlock(const Kernel::Process& process, const VAddr dest_ad
|
|||||||
std::size_t page_index = dest_addr >> CITRA_PAGE_BITS;
|
std::size_t page_index = dest_addr >> CITRA_PAGE_BITS;
|
||||||
std::size_t page_offset = dest_addr & CITRA_PAGE_MASK;
|
std::size_t page_offset = dest_addr & CITRA_PAGE_MASK;
|
||||||
|
|
||||||
static const std::array<u8, CITRA_PAGE_SIZE> zeros = {};
|
|
||||||
|
|
||||||
while (remaining_size > 0) {
|
while (remaining_size > 0) {
|
||||||
const std::size_t copy_amount = std::min(CITRA_PAGE_SIZE - page_offset, remaining_size);
|
const std::size_t copy_amount = std::min(CITRA_PAGE_SIZE - page_offset, remaining_size);
|
||||||
const VAddr current_vaddr =
|
const VAddr current_vaddr =
|
||||||
@@ -945,12 +887,6 @@ void MemorySystem::ZeroBlock(const Kernel::Process& process, const VAddr dest_ad
|
|||||||
std::memset(dest_ptr, 0, copy_amount);
|
std::memset(dest_ptr, 0, copy_amount);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PageType::Special: {
|
|
||||||
MMIORegionPointer handler = impl->GetMMIOHandler(page_table, current_vaddr);
|
|
||||||
DEBUG_ASSERT(handler);
|
|
||||||
handler->WriteBlock(current_vaddr, zeros.data(), copy_amount);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PageType::RasterizerCachedMemory: {
|
case PageType::RasterizerCachedMemory: {
|
||||||
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
|
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
|
||||||
FlushMode::Invalidate);
|
FlushMode::Invalidate);
|
||||||
@@ -1000,14 +936,6 @@ void MemorySystem::CopyBlock(const Kernel::Process& dest_process,
|
|||||||
WriteBlock(dest_process, dest_addr, src_ptr, copy_amount);
|
WriteBlock(dest_process, dest_addr, src_ptr, copy_amount);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PageType::Special: {
|
|
||||||
MMIORegionPointer handler = impl->GetMMIOHandler(page_table, current_vaddr);
|
|
||||||
DEBUG_ASSERT(handler);
|
|
||||||
std::vector<u8> buffer(copy_amount);
|
|
||||||
handler->ReadBlock(current_vaddr, buffer.data(), buffer.size());
|
|
||||||
WriteBlock(dest_process, dest_addr, buffer.data(), buffer.size());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PageType::RasterizerCachedMemory: {
|
case PageType::RasterizerCachedMemory: {
|
||||||
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
|
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
|
||||||
FlushMode::Flush);
|
FlushMode::Flush);
|
||||||
@@ -1027,46 +955,6 @@ void MemorySystem::CopyBlock(const Kernel::Process& dest_process,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
|
||||||
u8 ReadMMIO<u8>(MMIORegionPointer mmio_handler, VAddr addr) {
|
|
||||||
return mmio_handler->Read8(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
u16 ReadMMIO<u16>(MMIORegionPointer mmio_handler, VAddr addr) {
|
|
||||||
return mmio_handler->Read16(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
u32 ReadMMIO<u32>(MMIORegionPointer mmio_handler, VAddr addr) {
|
|
||||||
return mmio_handler->Read32(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
u64 ReadMMIO<u64>(MMIORegionPointer mmio_handler, VAddr addr) {
|
|
||||||
return mmio_handler->Read64(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
void WriteMMIO<u8>(MMIORegionPointer mmio_handler, VAddr addr, const u8 data) {
|
|
||||||
mmio_handler->Write8(addr, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
void WriteMMIO<u16>(MMIORegionPointer mmio_handler, VAddr addr, const u16 data) {
|
|
||||||
mmio_handler->Write16(addr, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
void WriteMMIO<u32>(MMIORegionPointer mmio_handler, VAddr addr, const u32 data) {
|
|
||||||
mmio_handler->Write32(addr, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
void WriteMMIO<u64>(MMIORegionPointer mmio_handler, VAddr addr, const u64 data) {
|
|
||||||
mmio_handler->Write64(addr, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 MemorySystem::GetFCRAMOffset(const u8* pointer) const {
|
u32 MemorySystem::GetFCRAMOffset(const u8* pointer) const {
|
||||||
ASSERT(pointer >= impl->fcram.get() && pointer <= impl->fcram.get() + Memory::FCRAM_N3DS_SIZE);
|
ASSERT(pointer >= impl->fcram.get() && pointer <= impl->fcram.get() + Memory::FCRAM_N3DS_SIZE);
|
||||||
return static_cast<u32>(pointer - impl->fcram.get());
|
return static_cast<u32>(pointer - impl->fcram.get());
|
||||||
|
@@ -10,7 +10,6 @@
|
|||||||
#include <boost/serialization/vector.hpp>
|
#include <boost/serialization/vector.hpp>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/memory_ref.h"
|
#include "common/memory_ref.h"
|
||||||
#include "core/mmio.h"
|
|
||||||
|
|
||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
class Process;
|
class Process;
|
||||||
@@ -43,23 +42,6 @@ enum class PageType {
|
|||||||
/// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and
|
/// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and
|
||||||
/// invalidation
|
/// invalidation
|
||||||
RasterizerCachedMemory,
|
RasterizerCachedMemory,
|
||||||
/// Page is mapped to a I/O region. Writing and reading to this page is handled by functions.
|
|
||||||
Special,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SpecialRegion {
|
|
||||||
VAddr base;
|
|
||||||
u32 size;
|
|
||||||
MMIORegionPointer handler;
|
|
||||||
|
|
||||||
private:
|
|
||||||
template <class Archive>
|
|
||||||
void serialize(Archive& ar, const unsigned int file_version) {
|
|
||||||
ar& base;
|
|
||||||
ar& size;
|
|
||||||
ar& handler;
|
|
||||||
}
|
|
||||||
friend class boost::serialization::access;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -108,12 +90,6 @@ struct PageTable {
|
|||||||
|
|
||||||
Pointers pointers;
|
Pointers pointers;
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains MMIO handlers that back memory regions whose entries in the `attribute` array is of
|
|
||||||
* type `Special`.
|
|
||||||
*/
|
|
||||||
std::vector<SpecialRegion> special_regions;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Array of fine grained page attributes. If it is set to any value other than `Memory`, then
|
* Array of fine grained page attributes. If it is set to any value other than `Memory`, then
|
||||||
* the corresponding entry in `pointers` MUST be set to null.
|
* the corresponding entry in `pointers` MUST be set to null.
|
||||||
@@ -130,7 +106,6 @@ private:
|
|||||||
template <class Archive>
|
template <class Archive>
|
||||||
void serialize(Archive& ar, const unsigned int) {
|
void serialize(Archive& ar, const unsigned int) {
|
||||||
ar& pointers.refs;
|
ar& pointers.refs;
|
||||||
ar& special_regions;
|
|
||||||
ar& attributes;
|
ar& attributes;
|
||||||
for (std::size_t i = 0; i < PAGE_TABLE_NUM_ENTRIES; i++) {
|
for (std::size_t i = 0; i < PAGE_TABLE_NUM_ENTRIES; i++) {
|
||||||
pointers.raw[i] = pointers.refs[i].GetPtr();
|
pointers.raw[i] = pointers.refs[i].GetPtr();
|
||||||
@@ -302,15 +277,6 @@ public:
|
|||||||
*/
|
*/
|
||||||
void MapMemoryRegion(PageTable& page_table, VAddr base, u32 size, MemoryRef target);
|
void MapMemoryRegion(PageTable& page_table, VAddr base, u32 size, MemoryRef target);
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps a region of the emulated process address space as a IO region.
|
|
||||||
* @param page_table The page table of the emulated process.
|
|
||||||
* @param base The address to start mapping at. Must be page-aligned.
|
|
||||||
* @param size The amount of bytes to map. Must be page-aligned.
|
|
||||||
* @param mmio_handler The handler that backs the mapping.
|
|
||||||
*/
|
|
||||||
void MapIoRegion(PageTable& page_table, VAddr base, u32 size, MMIORegionPointer mmio_handler);
|
|
||||||
|
|
||||||
void UnmapRegion(PageTable& page_table, VAddr base, u32 size);
|
void UnmapRegion(PageTable& page_table, VAddr base, u32 size);
|
||||||
|
|
||||||
/// Currently active page table
|
/// Currently active page table
|
||||||
|
@@ -1,43 +0,0 @@
|
|||||||
// Copyright 2016 Citra Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include "common/common_types.h"
|
|
||||||
|
|
||||||
namespace Memory {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a device with memory mapped IO.
|
|
||||||
* A device may be mapped to multiple regions of memory.
|
|
||||||
*/
|
|
||||||
class MMIORegion {
|
|
||||||
public:
|
|
||||||
virtual ~MMIORegion() = default;
|
|
||||||
|
|
||||||
virtual bool IsValidAddress(VAddr addr) = 0;
|
|
||||||
|
|
||||||
virtual u8 Read8(VAddr addr) = 0;
|
|
||||||
virtual u16 Read16(VAddr addr) = 0;
|
|
||||||
virtual u32 Read32(VAddr addr) = 0;
|
|
||||||
virtual u64 Read64(VAddr addr) = 0;
|
|
||||||
|
|
||||||
virtual bool ReadBlock(VAddr src_addr, void* dest_buffer, std::size_t size) = 0;
|
|
||||||
|
|
||||||
virtual void Write8(VAddr addr, u8 data) = 0;
|
|
||||||
virtual void Write16(VAddr addr, u16 data) = 0;
|
|
||||||
virtual void Write32(VAddr addr, u32 data) = 0;
|
|
||||||
virtual void Write64(VAddr addr, u64 data) = 0;
|
|
||||||
|
|
||||||
virtual bool WriteBlock(VAddr dest_addr, const void* src_buffer, std::size_t size) = 0;
|
|
||||||
|
|
||||||
private:
|
|
||||||
friend class boost::serialization::access;
|
|
||||||
template <class Archive>
|
|
||||||
void serialize(Archive& ar, const unsigned int file_version) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
using MMIORegionPointer = std::shared_ptr<MMIORegion>;
|
|
||||||
}; // namespace Memory
|
|
@@ -2,9 +2,6 @@ add_executable(tests
|
|||||||
common/bit_field.cpp
|
common/bit_field.cpp
|
||||||
common/file_util.cpp
|
common/file_util.cpp
|
||||||
common/param_package.cpp
|
common/param_package.cpp
|
||||||
core/arm/arm_test_common.cpp
|
|
||||||
core/arm/arm_test_common.h
|
|
||||||
core/arm/dyncom/arm_dyncom_vfp_tests.cpp
|
|
||||||
core/core_timing.cpp
|
core/core_timing.cpp
|
||||||
core/file_sys/path_parser.cpp
|
core/file_sys/path_parser.cpp
|
||||||
core/hle/kernel/hle_ipc.cpp
|
core/hle/kernel/hle_ipc.cpp
|
||||||
@@ -21,7 +18,7 @@ add_executable(tests
|
|||||||
create_target_directory_groups(tests)
|
create_target_directory_groups(tests)
|
||||||
|
|
||||||
target_link_libraries(tests PRIVATE citra_common citra_core video_core audio_core)
|
target_link_libraries(tests PRIVATE citra_common citra_core video_core audio_core)
|
||||||
target_link_libraries(tests PRIVATE ${PLATFORM_LIBRARIES} Catch2::Catch2WithMain nihstro-headers Threads::Threads)
|
target_link_libraries(tests PRIVATE ${PLATFORM_LIBRARIES} catch2 nihstro-headers Threads::Threads)
|
||||||
|
|
||||||
add_test(NAME tests COMMAND tests)
|
add_test(NAME tests COMMAND tests)
|
||||||
|
|
||||||
|
@@ -1,143 +0,0 @@
|
|||||||
// Copyright 2016 Citra Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#include "core/core.h"
|
|
||||||
#include "core/core_timing.h"
|
|
||||||
#include "core/hle/kernel/process.h"
|
|
||||||
#include "core/memory.h"
|
|
||||||
#include "tests/core/arm/arm_test_common.h"
|
|
||||||
|
|
||||||
namespace ArmTests {
|
|
||||||
|
|
||||||
static std::shared_ptr<Memory::PageTable> page_table = nullptr;
|
|
||||||
|
|
||||||
TestEnvironment::TestEnvironment(bool mutable_memory_)
|
|
||||||
: mutable_memory(mutable_memory_), test_memory(std::make_shared<TestMemory>(this)) {
|
|
||||||
|
|
||||||
timing = std::make_unique<Core::Timing>(1, 100);
|
|
||||||
system = std::make_unique<Core::System>();
|
|
||||||
memory = std::make_unique<Memory::MemorySystem>(*system);
|
|
||||||
kernel = std::make_unique<Kernel::KernelSystem>(
|
|
||||||
*memory, *timing, [] {}, Kernel::MemoryMode::Prod, 1,
|
|
||||||
Kernel::New3dsHwCapabilities{false, false, Kernel::New3dsMemoryMode::Legacy});
|
|
||||||
|
|
||||||
kernel->SetCurrentProcess(kernel->CreateProcess(kernel->CreateCodeSet("", 0)));
|
|
||||||
page_table = kernel->GetCurrentProcess()->vm_manager.page_table;
|
|
||||||
|
|
||||||
page_table->Clear();
|
|
||||||
|
|
||||||
memory->MapIoRegion(*page_table, 0x00000000, 0x80000000, test_memory);
|
|
||||||
memory->MapIoRegion(*page_table, 0x80000000, 0x80000000, test_memory);
|
|
||||||
|
|
||||||
kernel->SetCurrentMemoryPageTable(page_table);
|
|
||||||
}
|
|
||||||
|
|
||||||
TestEnvironment::~TestEnvironment() {
|
|
||||||
memory->UnmapRegion(*page_table, 0x80000000, 0x80000000);
|
|
||||||
memory->UnmapRegion(*page_table, 0x00000000, 0x80000000);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TestEnvironment::SetMemory64(VAddr vaddr, u64 value) {
|
|
||||||
SetMemory32(vaddr + 0, static_cast<u32>(value));
|
|
||||||
SetMemory32(vaddr + 4, static_cast<u32>(value >> 32));
|
|
||||||
}
|
|
||||||
|
|
||||||
void TestEnvironment::SetMemory32(VAddr vaddr, u32 value) {
|
|
||||||
SetMemory16(vaddr + 0, static_cast<u16>(value));
|
|
||||||
SetMemory16(vaddr + 2, static_cast<u16>(value >> 16));
|
|
||||||
}
|
|
||||||
|
|
||||||
void TestEnvironment::SetMemory16(VAddr vaddr, u16 value) {
|
|
||||||
SetMemory8(vaddr + 0, static_cast<u8>(value));
|
|
||||||
SetMemory8(vaddr + 1, static_cast<u8>(value >> 8));
|
|
||||||
}
|
|
||||||
|
|
||||||
void TestEnvironment::SetMemory8(VAddr vaddr, u8 value) {
|
|
||||||
test_memory->data[vaddr] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<WriteRecord> TestEnvironment::GetWriteRecords() const {
|
|
||||||
return write_records;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TestEnvironment::ClearWriteRecords() {
|
|
||||||
write_records.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
TestEnvironment::TestMemory::~TestMemory() {}
|
|
||||||
|
|
||||||
bool TestEnvironment::TestMemory::IsValidAddress(VAddr addr) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
u8 TestEnvironment::TestMemory::Read8(VAddr addr) {
|
|
||||||
auto iter = data.find(addr);
|
|
||||||
if (iter == data.end()) {
|
|
||||||
return addr; // Some arbitrary data
|
|
||||||
}
|
|
||||||
return iter->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
u16 TestEnvironment::TestMemory::Read16(VAddr addr) {
|
|
||||||
return Read8(addr) | static_cast<u16>(Read8(addr + 1)) << 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 TestEnvironment::TestMemory::Read32(VAddr addr) {
|
|
||||||
return Read16(addr) | static_cast<u32>(Read16(addr + 2)) << 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
u64 TestEnvironment::TestMemory::Read64(VAddr addr) {
|
|
||||||
return Read32(addr) | static_cast<u64>(Read32(addr + 4)) << 32;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TestEnvironment::TestMemory::ReadBlock(VAddr src_addr, void* dest_buffer, std::size_t size) {
|
|
||||||
VAddr addr = src_addr;
|
|
||||||
u8* data = static_cast<u8*>(dest_buffer);
|
|
||||||
|
|
||||||
for (std::size_t i = 0; i < size; i++, addr++, data++) {
|
|
||||||
*data = Read8(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TestEnvironment::TestMemory::Write8(VAddr addr, u8 data) {
|
|
||||||
env->write_records.emplace_back(8, addr, data);
|
|
||||||
if (env->mutable_memory)
|
|
||||||
env->SetMemory8(addr, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TestEnvironment::TestMemory::Write16(VAddr addr, u16 data) {
|
|
||||||
env->write_records.emplace_back(16, addr, data);
|
|
||||||
if (env->mutable_memory)
|
|
||||||
env->SetMemory16(addr, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TestEnvironment::TestMemory::Write32(VAddr addr, u32 data) {
|
|
||||||
env->write_records.emplace_back(32, addr, data);
|
|
||||||
if (env->mutable_memory)
|
|
||||||
env->SetMemory32(addr, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TestEnvironment::TestMemory::Write64(VAddr addr, u64 data) {
|
|
||||||
env->write_records.emplace_back(64, addr, data);
|
|
||||||
if (env->mutable_memory)
|
|
||||||
env->SetMemory64(addr, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TestEnvironment::TestMemory::WriteBlock(VAddr dest_addr, const void* src_buffer,
|
|
||||||
std::size_t size) {
|
|
||||||
VAddr addr = dest_addr;
|
|
||||||
const u8* data = static_cast<const u8*>(src_buffer);
|
|
||||||
|
|
||||||
for (std::size_t i = 0; i < size; i++, addr++, data++) {
|
|
||||||
env->write_records.emplace_back(8, addr, *data);
|
|
||||||
if (env->mutable_memory)
|
|
||||||
env->SetMemory8(addr, *data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ArmTests
|
|
@@ -1,93 +0,0 @@
|
|||||||
// Copyright 2016 Citra Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#include <tuple>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
#include "common/common_types.h"
|
|
||||||
#include "core/hle/kernel/kernel.h"
|
|
||||||
#include "core/mmio.h"
|
|
||||||
|
|
||||||
namespace ArmTests {
|
|
||||||
|
|
||||||
struct WriteRecord {
|
|
||||||
WriteRecord(std::size_t size, VAddr addr, u64 data) : size(size), addr(addr), data(data) {}
|
|
||||||
std::size_t size;
|
|
||||||
VAddr addr;
|
|
||||||
u64 data;
|
|
||||||
bool operator==(const WriteRecord& o) const {
|
|
||||||
return std::tie(size, addr, data) == std::tie(o.size, o.addr, o.data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class TestEnvironment final {
|
|
||||||
public:
|
|
||||||
/*
|
|
||||||
* Inititalise test environment
|
|
||||||
* @param mutable_memory If false, writes to memory can never be read back.
|
|
||||||
* (Memory is immutable.)
|
|
||||||
*/
|
|
||||||
explicit TestEnvironment(bool mutable_memory = false);
|
|
||||||
|
|
||||||
/// Shutdown test environment
|
|
||||||
~TestEnvironment();
|
|
||||||
|
|
||||||
/// Sets value at memory location vaddr.
|
|
||||||
void SetMemory8(VAddr vaddr, u8 value);
|
|
||||||
void SetMemory16(VAddr vaddr, u16 value);
|
|
||||||
void SetMemory32(VAddr vaddr, u32 value);
|
|
||||||
void SetMemory64(VAddr vaddr, u64 value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whenever Memory::Write{8,16,32,64} is called within the test environment,
|
|
||||||
* a new write-record is made.
|
|
||||||
* @returns A vector of write records made since they were last cleared.
|
|
||||||
*/
|
|
||||||
std::vector<WriteRecord> GetWriteRecords() const;
|
|
||||||
|
|
||||||
/// Empties the internal write-record store.
|
|
||||||
void ClearWriteRecords();
|
|
||||||
|
|
||||||
Memory::MemorySystem& GetMemory() {
|
|
||||||
return *memory;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
friend struct TestMemory;
|
|
||||||
struct TestMemory final : Memory::MMIORegion {
|
|
||||||
explicit TestMemory(TestEnvironment* env_) : env(env_) {}
|
|
||||||
TestEnvironment* env;
|
|
||||||
|
|
||||||
~TestMemory() override;
|
|
||||||
|
|
||||||
bool IsValidAddress(VAddr addr) override;
|
|
||||||
|
|
||||||
u8 Read8(VAddr addr) override;
|
|
||||||
u16 Read16(VAddr addr) override;
|
|
||||||
u32 Read32(VAddr addr) override;
|
|
||||||
u64 Read64(VAddr addr) override;
|
|
||||||
|
|
||||||
bool ReadBlock(VAddr src_addr, void* dest_buffer, std::size_t size) override;
|
|
||||||
|
|
||||||
void Write8(VAddr addr, u8 data) override;
|
|
||||||
void Write16(VAddr addr, u16 data) override;
|
|
||||||
void Write32(VAddr addr, u32 data) override;
|
|
||||||
void Write64(VAddr addr, u64 data) override;
|
|
||||||
|
|
||||||
bool WriteBlock(VAddr dest_addr, const void* src_buffer, std::size_t size) override;
|
|
||||||
|
|
||||||
std::unordered_map<VAddr, u8> data;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool mutable_memory;
|
|
||||||
std::shared_ptr<TestMemory> test_memory;
|
|
||||||
std::vector<WriteRecord> write_records;
|
|
||||||
|
|
||||||
std::unique_ptr<Core::Timing> timing;
|
|
||||||
std::unique_ptr<Core::System> system;
|
|
||||||
std::unique_ptr<Memory::MemorySystem> memory;
|
|
||||||
std::unique_ptr<Kernel::KernelSystem> kernel;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ArmTests
|
|
@@ -1,51 +0,0 @@
|
|||||||
// Copyright 2016 Citra Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#include <catch2/catch_test_macros.hpp>
|
|
||||||
#include "core/arm/dyncom/arm_dyncom.h"
|
|
||||||
#include "core/core.h"
|
|
||||||
#include "core/core_timing.h"
|
|
||||||
#include "tests/core/arm/arm_test_common.h"
|
|
||||||
|
|
||||||
namespace ArmTests {
|
|
||||||
|
|
||||||
struct VfpTestCase {
|
|
||||||
u32 initial_fpscr;
|
|
||||||
u32 a;
|
|
||||||
u32 b;
|
|
||||||
u32 result;
|
|
||||||
u32 final_fpscr;
|
|
||||||
};
|
|
||||||
|
|
||||||
TEST_CASE("ARM_DynCom (vfp): vadd", "[arm_dyncom]") {
|
|
||||||
TestEnvironment test_env(false);
|
|
||||||
test_env.SetMemory32(0, 0xEE321A03); // vadd.f32 s2, s4, s6
|
|
||||||
test_env.SetMemory32(4, 0xEAFFFFFE); // b +#0
|
|
||||||
|
|
||||||
Core::System system;
|
|
||||||
Core::ARM_DynCom dyncom(system, test_env.GetMemory(), USER32MODE, 0, nullptr);
|
|
||||||
|
|
||||||
std::vector<VfpTestCase> test_cases{{
|
|
||||||
#include "vfp_vadd_f32.inc"
|
|
||||||
}};
|
|
||||||
|
|
||||||
for (const auto& test_case : test_cases) {
|
|
||||||
dyncom.SetPC(0);
|
|
||||||
dyncom.SetVFPSystemReg(VFP_FPSCR, test_case.initial_fpscr);
|
|
||||||
dyncom.SetVFPReg(4, test_case.a);
|
|
||||||
dyncom.SetVFPReg(6, test_case.b);
|
|
||||||
dyncom.Step();
|
|
||||||
if (dyncom.GetVFPReg(2) != test_case.result ||
|
|
||||||
dyncom.GetVFPSystemReg(VFP_FPSCR) != test_case.final_fpscr) {
|
|
||||||
printf("f: %x\n", test_case.initial_fpscr);
|
|
||||||
printf("a: %x\n", test_case.a);
|
|
||||||
printf("b: %x\n", test_case.b);
|
|
||||||
printf("c: %x (%x)\n", dyncom.GetVFPReg(2), test_case.result);
|
|
||||||
printf("f: %x (%x)\n", dyncom.GetVFPSystemReg(VFP_FPSCR), test_case.final_fpscr);
|
|
||||||
FAIL();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ArmTests
|
|
File diff suppressed because it is too large
Load Diff
@@ -103,8 +103,8 @@ add_library(video_core STATIC
|
|||||||
renderer_vulkan/vk_blit_helper.h
|
renderer_vulkan/vk_blit_helper.h
|
||||||
renderer_vulkan/vk_common.cpp
|
renderer_vulkan/vk_common.cpp
|
||||||
renderer_vulkan/vk_common.h
|
renderer_vulkan/vk_common.h
|
||||||
renderer_vulkan/vk_descriptor_update.cpp
|
renderer_vulkan/vk_descriptor_pool.cpp
|
||||||
renderer_vulkan/vk_descriptor_update.h
|
renderer_vulkan/vk_descriptor_pool.h
|
||||||
renderer_vulkan/vk_graphics_pipeline.cpp
|
renderer_vulkan/vk_graphics_pipeline.cpp
|
||||||
renderer_vulkan/vk_graphics_pipeline.h
|
renderer_vulkan/vk_graphics_pipeline.h
|
||||||
renderer_vulkan/vk_master_semaphore.cpp
|
renderer_vulkan/vk_master_semaphore.cpp
|
||||||
|
@@ -176,23 +176,15 @@ struct TexturingRegs {
|
|||||||
INSERT_PADDING_WORDS(0x9);
|
INSERT_PADDING_WORDS(0x9);
|
||||||
|
|
||||||
struct FullTextureConfig {
|
struct FullTextureConfig {
|
||||||
u32 enabled;
|
const bool enabled;
|
||||||
TextureConfig config;
|
const TextureConfig config;
|
||||||
TextureFormat format;
|
const TextureFormat format;
|
||||||
|
|
||||||
bool operator==(const FullTextureConfig& other) const noexcept {
|
|
||||||
return std::memcmp(this, &other, sizeof(other)) == 0;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
static_assert(std::has_unique_object_representations_v<FullTextureConfig>);
|
const std::array<FullTextureConfig, 3> GetTextures() const {
|
||||||
|
|
||||||
using Textures = std::array<FullTextureConfig, 3>;
|
|
||||||
|
|
||||||
const Textures GetTextures() const {
|
|
||||||
return {{
|
return {{
|
||||||
{main_config.texture0_enable, texture0, texture0_format},
|
{static_cast<bool>(main_config.texture0_enable), texture0, texture0_format},
|
||||||
{main_config.texture1_enable, texture1, texture1_format},
|
{static_cast<bool>(main_config.texture1_enable), texture1, texture1_format},
|
||||||
{main_config.texture2_enable, texture2, texture2_format},
|
{static_cast<bool>(main_config.texture2_enable), texture2, texture2_format},
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,11 +381,11 @@ struct TexturingRegs {
|
|||||||
BitField<16, 2, u32> alpha_scale;
|
BitField<16, 2, u32> alpha_scale;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline u32 GetColorMultiplier() const {
|
inline unsigned GetColorMultiplier() const {
|
||||||
return (color_scale < 3) ? (1 << color_scale) : 1;
|
return (color_scale < 3) ? (1 << color_scale) : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline u32 GetAlphaMultiplier() const {
|
inline unsigned GetAlphaMultiplier() const {
|
||||||
return (alpha_scale < 3) ? (1 << alpha_scale) : 1;
|
return (alpha_scale < 3) ? (1 << alpha_scale) : 1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -7,8 +7,10 @@
|
|||||||
#include "common/memory_detect.h"
|
#include "common/memory_detect.h"
|
||||||
#include "common/microprofile.h"
|
#include "common/microprofile.h"
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
|
#include "common/texture.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/frontend/emu_window.h"
|
#include "core/frontend/emu_window.h"
|
||||||
|
#include "core/hw/gpu.h"
|
||||||
#include "core/hw/hw.h"
|
#include "core/hw/hw.h"
|
||||||
#include "core/hw/lcd.h"
|
#include "core/hw/lcd.h"
|
||||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||||
@@ -19,6 +21,7 @@
|
|||||||
#include "video_core/host_shaders/vulkan_present_frag_spv.h"
|
#include "video_core/host_shaders/vulkan_present_frag_spv.h"
|
||||||
#include "video_core/host_shaders/vulkan_present_interlaced_frag_spv.h"
|
#include "video_core/host_shaders/vulkan_present_interlaced_frag_spv.h"
|
||||||
#include "video_core/host_shaders/vulkan_present_vert_spv.h"
|
#include "video_core/host_shaders/vulkan_present_vert_spv.h"
|
||||||
|
#include "vulkan/vulkan_format_traits.hpp"
|
||||||
|
|
||||||
#include <vk_mem_alloc.h>
|
#include <vk_mem_alloc.h>
|
||||||
|
|
||||||
@@ -35,7 +38,6 @@ struct ScreenRectVertex {
|
|||||||
Common::Vec2f tex_coord;
|
Common::Vec2f tex_coord;
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr u32 MAX_IN_FLIGHT_FRAMES = 10;
|
|
||||||
constexpr u32 VERTEX_BUFFER_SIZE = sizeof(ScreenRectVertex) * 8192;
|
constexpr u32 VERTEX_BUFFER_SIZE = sizeof(ScreenRectVertex) * 8192;
|
||||||
|
|
||||||
constexpr std::array<f32, 4 * 4> MakeOrthographicMatrix(u32 width, u32 height) {
|
constexpr std::array<f32, 4 * 4> MakeOrthographicMatrix(u32 width, u32 height) {
|
||||||
@@ -47,12 +49,16 @@ constexpr std::array<f32, 4 * 4> MakeOrthographicMatrix(u32 width, u32 height) {
|
|||||||
// clang-format on
|
// clang-format on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr static std::array<vk::DescriptorSetLayoutBinding, 1> PRESENT_BINDINGS = {{
|
||||||
|
{0, vk::DescriptorType::eCombinedImageSampler, 3, vk::ShaderStageFlagBits::eFragment},
|
||||||
|
}};
|
||||||
|
|
||||||
RendererVulkan::RendererVulkan(Core::System& system, Frontend::EmuWindow& window,
|
RendererVulkan::RendererVulkan(Core::System& system, Frontend::EmuWindow& window,
|
||||||
Frontend::EmuWindow* secondary_window)
|
Frontend::EmuWindow* secondary_window)
|
||||||
: RendererBase{system, window, secondary_window}, memory{system.Memory()},
|
: RendererBase{system, window, secondary_window}, memory{system.Memory()},
|
||||||
instance{system.TelemetrySession(), window, Settings::values.physical_device.GetValue()},
|
instance{system.TelemetrySession(), window, Settings::values.physical_device.GetValue()},
|
||||||
scheduler{instance, renderpass_cache}, renderpass_cache{instance, scheduler},
|
scheduler{instance, renderpass_cache}, renderpass_cache{instance, scheduler}, pool{instance},
|
||||||
pool{instance, scheduler.GetMasterSemaphore()}, main_window{window, instance, scheduler},
|
main_window{window, instance, scheduler},
|
||||||
vertex_buffer{instance, scheduler, vk::BufferUsageFlagBits::eVertexBuffer,
|
vertex_buffer{instance, scheduler, vk::BufferUsageFlagBits::eVertexBuffer,
|
||||||
VERTEX_BUFFER_SIZE},
|
VERTEX_BUFFER_SIZE},
|
||||||
rasterizer{memory,
|
rasterizer{memory,
|
||||||
@@ -63,9 +69,10 @@ RendererVulkan::RendererVulkan(Core::System& system, Frontend::EmuWindow& window
|
|||||||
scheduler,
|
scheduler,
|
||||||
pool,
|
pool,
|
||||||
renderpass_cache,
|
renderpass_cache,
|
||||||
main_window.ImageCount()} {
|
main_window.ImageCount()},
|
||||||
|
present_set_provider{instance, pool, PRESENT_BINDINGS} {
|
||||||
CompileShaders();
|
CompileShaders();
|
||||||
BuildLayoutsAndDescriptors();
|
BuildLayouts();
|
||||||
BuildPipelines();
|
BuildPipelines();
|
||||||
if (secondary_window) {
|
if (secondary_window) {
|
||||||
second_window = std::make_unique<PresentWindow>(*secondary_window, instance, scheduler);
|
second_window = std::make_unique<PresentWindow>(*secondary_window, instance, scheduler);
|
||||||
@@ -73,21 +80,17 @@ RendererVulkan::RendererVulkan(Core::System& system, Frontend::EmuWindow& window
|
|||||||
}
|
}
|
||||||
|
|
||||||
RendererVulkan::~RendererVulkan() {
|
RendererVulkan::~RendererVulkan() {
|
||||||
const vk::Device device = instance.GetDevice();
|
vk::Device device = instance.GetDevice();
|
||||||
scheduler.Finish();
|
scheduler.Finish();
|
||||||
device.waitIdle();
|
device.waitIdle();
|
||||||
|
|
||||||
device.destroyPipelineLayout(pipeline_layout);
|
device.destroyShaderModule(present_vertex_shader);
|
||||||
device.destroyDescriptorSetLayout(descriptor_set_layout);
|
for (u32 i = 0; i < PRESENT_PIPELINES; i++) {
|
||||||
device.destroyDescriptorUpdateTemplate(update_template);
|
device.destroyPipeline(present_pipelines[i]);
|
||||||
device.destroyShaderModule(vert_shader);
|
device.destroyShaderModule(present_shaders[i]);
|
||||||
|
|
||||||
for (u32 i = 0; i < NUM_PIPELINES; i++) {
|
|
||||||
device.destroyPipeline(pipelines[i]);
|
|
||||||
device.destroyShaderModule(frag_shaders[i]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto sampler : samplers) {
|
for (auto& sampler : present_samplers) {
|
||||||
device.destroySampler(sampler);
|
device.destroySampler(sampler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,10 +105,9 @@ void RendererVulkan::Sync() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RendererVulkan::PrepareRendertarget() {
|
void RendererVulkan::PrepareRendertarget() {
|
||||||
for (u32 i = 0; i < NUM_SCREENS; i++) {
|
for (u32 i = 0; i < 3; i++) {
|
||||||
const u32 fb_id = i == 2 ? 1 : 0;
|
const u32 fb_id = i == 2 ? 1 : 0;
|
||||||
const auto& framebuffer = GPU::g_regs.framebuffer_config[fb_id];
|
const auto& framebuffer = GPU::g_regs.framebuffer_config[fb_id];
|
||||||
auto& texture = screen_infos[i].texture;
|
|
||||||
|
|
||||||
// Main LCD (0): 0x1ED02204, Sub LCD (1): 0x1ED02A04
|
// Main LCD (0): 0x1ED02204, Sub LCD (1): 0x1ED02A04
|
||||||
u32 lcd_color_addr =
|
u32 lcd_color_addr =
|
||||||
@@ -116,31 +118,37 @@ void RendererVulkan::PrepareRendertarget() {
|
|||||||
|
|
||||||
if (color_fill.is_enabled) {
|
if (color_fill.is_enabled) {
|
||||||
LoadColorToActiveVkTexture(color_fill.color_r, color_fill.color_g, color_fill.color_b,
|
LoadColorToActiveVkTexture(color_fill.color_r, color_fill.color_g, color_fill.color_b,
|
||||||
texture);
|
screen_infos[i].texture);
|
||||||
continue;
|
} else {
|
||||||
}
|
TextureInfo& texture = screen_infos[i].texture;
|
||||||
|
if (texture.width != framebuffer.width || texture.height != framebuffer.height ||
|
||||||
|
texture.format != framebuffer.color_format) {
|
||||||
|
|
||||||
if (texture.width != framebuffer.width || texture.height != framebuffer.height ||
|
// Reallocate texture if the framebuffer size has changed.
|
||||||
texture.format != framebuffer.color_format) {
|
// This is expected to not happen very often and hence should not be a
|
||||||
ConfigureFramebufferTexture(texture, framebuffer);
|
// performance problem.
|
||||||
}
|
ConfigureFramebufferTexture(texture, framebuffer);
|
||||||
|
}
|
||||||
|
|
||||||
LoadFBToScreenInfo(framebuffer, screen_infos[i], i == 1);
|
LoadFBToScreenInfo(framebuffer, screen_infos[i], i == 1);
|
||||||
|
|
||||||
|
// Resize the texture in case the framebuffer size has changed
|
||||||
|
texture.width = framebuffer.width;
|
||||||
|
texture.height = framebuffer.height;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererVulkan::PrepareDraw(Frame* frame, const Layout::FramebufferLayout& layout) {
|
void RendererVulkan::PrepareDraw(Frame* frame, const Layout::FramebufferLayout& layout) {
|
||||||
const auto sampler = samplers[!Settings::values.filter_mode.GetValue()];
|
const auto sampler = present_samplers[!Settings::values.filter_mode.GetValue()];
|
||||||
std::transform(screen_infos.begin(), screen_infos.end(), image_infos.begin(), [&](auto& info) {
|
std::transform(screen_infos.begin(), screen_infos.end(), present_textures.begin(),
|
||||||
return vk::DescriptorImageInfo{sampler, info.image_view, vk::ImageLayout::eGeneral};
|
[&](auto& info) {
|
||||||
});
|
return DescriptorData{vk::DescriptorImageInfo{sampler, info.image_view,
|
||||||
|
vk::ImageLayout::eGeneral}};
|
||||||
|
});
|
||||||
|
|
||||||
// Prepare the descriptor set with presentation images.
|
const auto descriptor_set = present_set_provider.Acquire(present_textures);
|
||||||
const auto descriptor_set = present_sets[frame->index];
|
|
||||||
const vk::Device device = instance.GetDevice();
|
|
||||||
device.updateDescriptorSetWithTemplate(descriptor_set, update_template, image_infos);
|
|
||||||
|
|
||||||
// Bind presentation pipeline, enter renderpass.
|
|
||||||
renderpass_cache.EndRendering();
|
renderpass_cache.EndRendering();
|
||||||
scheduler.Record([this, layout, frame, descriptor_set, renderpass = main_window.Renderpass(),
|
scheduler.Record([this, layout, frame, descriptor_set, renderpass = main_window.Renderpass(),
|
||||||
index = current_pipeline](vk::CommandBuffer cmdbuf) {
|
index = current_pipeline](vk::CommandBuffer cmdbuf) {
|
||||||
@@ -162,11 +170,12 @@ void RendererVulkan::PrepareDraw(Frame* frame, const Layout::FramebufferLayout&
|
|||||||
cmdbuf.setScissor(0, scissor);
|
cmdbuf.setScissor(0, scissor);
|
||||||
|
|
||||||
const vk::ClearValue clear{.color = clear_color};
|
const vk::ClearValue clear{.color = clear_color};
|
||||||
|
const vk::PipelineLayout layout{*present_pipeline_layout};
|
||||||
const vk::RenderPassBeginInfo renderpass_begin_info = {
|
const vk::RenderPassBeginInfo renderpass_begin_info = {
|
||||||
.renderPass = renderpass,
|
.renderPass = renderpass,
|
||||||
.framebuffer = frame->framebuffer,
|
.framebuffer = frame->framebuffer,
|
||||||
.renderArea =
|
.renderArea =
|
||||||
{
|
vk::Rect2D{
|
||||||
.offset = {0, 0},
|
.offset = {0, 0},
|
||||||
.extent = {frame->width, frame->height},
|
.extent = {frame->width, frame->height},
|
||||||
},
|
},
|
||||||
@@ -175,9 +184,8 @@ void RendererVulkan::PrepareDraw(Frame* frame, const Layout::FramebufferLayout&
|
|||||||
};
|
};
|
||||||
|
|
||||||
cmdbuf.beginRenderPass(renderpass_begin_info, vk::SubpassContents::eInline);
|
cmdbuf.beginRenderPass(renderpass_begin_info, vk::SubpassContents::eInline);
|
||||||
cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, pipelines[index]);
|
cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, present_pipelines[index]);
|
||||||
cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline_layout, 0,
|
cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, layout, 0, descriptor_set, {});
|
||||||
descriptor_set, {});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,13 +239,13 @@ void RendererVulkan::LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& fram
|
|||||||
|
|
||||||
void RendererVulkan::CompileShaders() {
|
void RendererVulkan::CompileShaders() {
|
||||||
vk::Device device = instance.GetDevice();
|
vk::Device device = instance.GetDevice();
|
||||||
vert_shader = CompileSPV(VULKAN_PRESENT_VERT_SPV, device);
|
present_vertex_shader = CompileSPV(VULKAN_PRESENT_VERT_SPV, device);
|
||||||
frag_shaders[0] = CompileSPV(VULKAN_PRESENT_FRAG_SPV, device);
|
present_shaders[0] = CompileSPV(VULKAN_PRESENT_FRAG_SPV, device);
|
||||||
frag_shaders[1] = CompileSPV(VULKAN_PRESENT_ANAGLYPH_FRAG_SPV, device);
|
present_shaders[1] = CompileSPV(VULKAN_PRESENT_ANAGLYPH_FRAG_SPV, device);
|
||||||
frag_shaders[2] = CompileSPV(VULKAN_PRESENT_INTERLACED_FRAG_SPV, device);
|
present_shaders[2] = CompileSPV(VULKAN_PRESENT_INTERLACED_FRAG_SPV, device);
|
||||||
|
|
||||||
const auto properties = instance.GetPhysicalDevice().getProperties();
|
auto properties = instance.GetPhysicalDevice().getProperties();
|
||||||
for (std::size_t i = 0; i < samplers.size(); i++) {
|
for (std::size_t i = 0; i < present_samplers.size(); i++) {
|
||||||
const vk::Filter filter_mode = i == 0 ? vk::Filter::eLinear : vk::Filter::eNearest;
|
const vk::Filter filter_mode = i == 0 ? vk::Filter::eLinear : vk::Filter::eNearest;
|
||||||
const vk::SamplerCreateInfo sampler_info = {
|
const vk::SamplerCreateInfo sampler_info = {
|
||||||
.magFilter = filter_mode,
|
.magFilter = filter_mode,
|
||||||
@@ -253,59 +261,25 @@ void RendererVulkan::CompileShaders() {
|
|||||||
.unnormalizedCoordinates = false,
|
.unnormalizedCoordinates = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
samplers[i] = device.createSampler(sampler_info);
|
present_samplers[i] = device.createSampler(sampler_info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererVulkan::BuildLayoutsAndDescriptors() {
|
void RendererVulkan::BuildLayouts() {
|
||||||
const vk::PushConstantRange push_range = {
|
const vk::PushConstantRange push_range = {
|
||||||
.stageFlags = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment,
|
.stageFlags = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment,
|
||||||
.offset = 0,
|
.offset = 0,
|
||||||
.size = sizeof(PresentUniformData),
|
.size = sizeof(PresentUniformData),
|
||||||
};
|
};
|
||||||
|
|
||||||
const vk::DescriptorSetLayoutBinding binding = {
|
const auto descriptor_set_layout = present_set_provider.Layout();
|
||||||
.binding = 0,
|
const vk::PipelineLayoutCreateInfo layout_info = {
|
||||||
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
|
||||||
.descriptorCount = 3,
|
|
||||||
.stageFlags = vk::ShaderStageFlagBits::eFragment,
|
|
||||||
};
|
|
||||||
|
|
||||||
const vk::DescriptorUpdateTemplateEntry update_entry = {
|
|
||||||
.dstBinding = 0,
|
|
||||||
.dstArrayElement = 0,
|
|
||||||
.descriptorCount = 3,
|
|
||||||
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
|
||||||
.offset = 0,
|
|
||||||
.stride = sizeof(vk::DescriptorImageInfo),
|
|
||||||
};
|
|
||||||
|
|
||||||
const vk::DescriptorSetLayoutCreateInfo layout_info = {
|
|
||||||
.bindingCount = 1,
|
|
||||||
.pBindings = &binding,
|
|
||||||
};
|
|
||||||
|
|
||||||
const vk::Device device = instance.GetDevice();
|
|
||||||
descriptor_set_layout = device.createDescriptorSetLayout(layout_info);
|
|
||||||
|
|
||||||
const vk::DescriptorUpdateTemplateCreateInfo template_info = {
|
|
||||||
.descriptorUpdateEntryCount = 1,
|
|
||||||
.pDescriptorUpdateEntries = &update_entry,
|
|
||||||
.templateType = vk::DescriptorUpdateTemplateType::eDescriptorSet,
|
|
||||||
.descriptorSetLayout = descriptor_set_layout,
|
|
||||||
};
|
|
||||||
update_template = device.createDescriptorUpdateTemplate(template_info);
|
|
||||||
|
|
||||||
const vk::PipelineLayoutCreateInfo pipeline_layout_info = {
|
|
||||||
.setLayoutCount = 1,
|
.setLayoutCount = 1,
|
||||||
.pSetLayouts = &descriptor_set_layout,
|
.pSetLayouts = &descriptor_set_layout,
|
||||||
.pushConstantRangeCount = 1,
|
.pushConstantRangeCount = 1,
|
||||||
.pPushConstantRanges = &push_range,
|
.pPushConstantRanges = &push_range,
|
||||||
};
|
};
|
||||||
pipeline_layout = device.createPipelineLayout(pipeline_layout_info);
|
present_pipeline_layout = instance.GetDevice().createPipelineLayoutUnique(layout_info);
|
||||||
|
|
||||||
const u32 image_count = main_window.ImageCount();
|
|
||||||
present_sets = pool.Commit(descriptor_set_layout, image_count);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererVulkan::BuildPipelines() {
|
void RendererVulkan::BuildPipelines() {
|
||||||
@@ -396,16 +370,16 @@ void RendererVulkan::BuildPipelines() {
|
|||||||
.stencilTestEnable = false,
|
.stencilTestEnable = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
for (u32 i = 0; i < NUM_PIPELINES; i++) {
|
for (u32 i = 0; i < PRESENT_PIPELINES; i++) {
|
||||||
const std::array shader_stages = {
|
const std::array shader_stages = {
|
||||||
vk::PipelineShaderStageCreateInfo{
|
vk::PipelineShaderStageCreateInfo{
|
||||||
.stage = vk::ShaderStageFlagBits::eVertex,
|
.stage = vk::ShaderStageFlagBits::eVertex,
|
||||||
.module = vert_shader,
|
.module = present_vertex_shader,
|
||||||
.pName = "main",
|
.pName = "main",
|
||||||
},
|
},
|
||||||
vk::PipelineShaderStageCreateInfo{
|
vk::PipelineShaderStageCreateInfo{
|
||||||
.stage = vk::ShaderStageFlagBits::eFragment,
|
.stage = vk::ShaderStageFlagBits::eFragment,
|
||||||
.module = frag_shaders[i],
|
.module = present_shaders[i],
|
||||||
.pName = "main",
|
.pName = "main",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -421,14 +395,14 @@ void RendererVulkan::BuildPipelines() {
|
|||||||
.pDepthStencilState = &depth_info,
|
.pDepthStencilState = &depth_info,
|
||||||
.pColorBlendState = &color_blending,
|
.pColorBlendState = &color_blending,
|
||||||
.pDynamicState = &dynamic_info,
|
.pDynamicState = &dynamic_info,
|
||||||
.layout = pipeline_layout,
|
.layout = *present_pipeline_layout,
|
||||||
.renderPass = main_window.Renderpass(),
|
.renderPass = main_window.Renderpass(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto [result, pipeline] =
|
const auto [result, pipeline] =
|
||||||
instance.GetDevice().createGraphicsPipeline({}, pipeline_info);
|
instance.GetDevice().createGraphicsPipeline({}, pipeline_info);
|
||||||
ASSERT_MSG(result == vk::Result::eSuccess, "Unable to build present pipelines");
|
ASSERT_MSG(result == vk::Result::eSuccess, "Unable to build present pipelines");
|
||||||
pipelines[i] = pipeline;
|
present_pipelines[i] = pipeline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,7 +416,8 @@ void RendererVulkan::ConfigureFramebufferTexture(TextureInfo& texture,
|
|||||||
vmaDestroyImage(instance.GetAllocator(), texture.image, texture.allocation);
|
vmaDestroyImage(instance.GetAllocator(), texture.image, texture.allocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto pixel_format = VideoCore::PixelFormatFromGPUPixelFormat(framebuffer.color_format);
|
const VideoCore::PixelFormat pixel_format =
|
||||||
|
VideoCore::PixelFormatFromGPUPixelFormat(framebuffer.color_format);
|
||||||
const vk::Format format = instance.GetTraits(pixel_format).native;
|
const vk::Format format = instance.GetTraits(pixel_format).native;
|
||||||
const vk::ImageCreateInfo image_info = {
|
const vk::ImageCreateInfo image_info = {
|
||||||
.imageType = vk::ImageType::e2D,
|
.imageType = vk::ImageType::e2D,
|
||||||
@@ -628,7 +603,7 @@ void RendererVulkan::DrawSingleScreen(u32 screen_id, float x, float y, float w,
|
|||||||
|
|
||||||
scheduler.Record([this, offset = offset, info = draw_info](vk::CommandBuffer cmdbuf) {
|
scheduler.Record([this, offset = offset, info = draw_info](vk::CommandBuffer cmdbuf) {
|
||||||
const u32 first_vertex = static_cast<u32>(offset) / sizeof(ScreenRectVertex);
|
const u32 first_vertex = static_cast<u32>(offset) / sizeof(ScreenRectVertex);
|
||||||
cmdbuf.pushConstants(pipeline_layout,
|
cmdbuf.pushConstants(*present_pipeline_layout,
|
||||||
vk::ShaderStageFlagBits::eFragment | vk::ShaderStageFlagBits::eVertex,
|
vk::ShaderStageFlagBits::eFragment | vk::ShaderStageFlagBits::eVertex,
|
||||||
0, sizeof(info), &info);
|
0, sizeof(info), &info);
|
||||||
|
|
||||||
@@ -701,7 +676,7 @@ void RendererVulkan::DrawSingleScreenStereo(u32 screen_id_l, u32 screen_id_r, fl
|
|||||||
|
|
||||||
scheduler.Record([this, offset = offset, info = draw_info](vk::CommandBuffer cmdbuf) {
|
scheduler.Record([this, offset = offset, info = draw_info](vk::CommandBuffer cmdbuf) {
|
||||||
const u32 first_vertex = static_cast<u32>(offset) / sizeof(ScreenRectVertex);
|
const u32 first_vertex = static_cast<u32>(offset) / sizeof(ScreenRectVertex);
|
||||||
cmdbuf.pushConstants(pipeline_layout,
|
cmdbuf.pushConstants(*present_pipeline_layout,
|
||||||
vk::ShaderStageFlagBits::eFragment | vk::ShaderStageFlagBits::eVertex,
|
vk::ShaderStageFlagBits::eFragment | vk::ShaderStageFlagBits::eVertex,
|
||||||
0, sizeof(info), &info);
|
0, sizeof(info), &info);
|
||||||
|
|
||||||
@@ -842,7 +817,29 @@ void RendererVulkan::DrawScreens(Frame* frame, const Layout::FramebufferLayout&
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduler.Record([](vk::CommandBuffer cmdbuf) { cmdbuf.endRenderPass(); });
|
scheduler.Record([image = frame->image](vk::CommandBuffer cmdbuf) {
|
||||||
|
const vk::ImageMemoryBarrier render_barrier = {
|
||||||
|
.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite,
|
||||||
|
.dstAccessMask = vk::AccessFlagBits::eTransferRead,
|
||||||
|
.oldLayout = vk::ImageLayout::eTransferSrcOptimal,
|
||||||
|
.newLayout = vk::ImageLayout::eTransferSrcOptimal,
|
||||||
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
|
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
|
.image = image,
|
||||||
|
.subresourceRange{
|
||||||
|
.aspectMask = vk::ImageAspectFlagBits::eColor,
|
||||||
|
.baseMipLevel = 0,
|
||||||
|
.levelCount = 1,
|
||||||
|
.baseArrayLayer = 0,
|
||||||
|
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
cmdbuf.endRenderPass();
|
||||||
|
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||||
|
vk::PipelineStageFlagBits::eTransfer,
|
||||||
|
vk::DependencyFlagBits::eByRegion, {}, {}, render_barrier);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererVulkan::SwapBuffers() {
|
void RendererVulkan::SwapBuffers() {
|
||||||
@@ -1035,8 +1032,9 @@ bool RendererVulkan::TryRenderScreenshotWithHostMemory() {
|
|||||||
.imageExtent = {width, height, 1},
|
.imageExtent = {width, height, 1},
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto import_properties = device.getMemoryHostPointerPropertiesEXT(
|
const vk::MemoryHostPointerPropertiesEXT import_properties =
|
||||||
vk::ExternalMemoryHandleTypeFlagBits::eHostAllocationEXT, aligned_pointer);
|
device.getMemoryHostPointerPropertiesEXT(
|
||||||
|
vk::ExternalMemoryHandleTypeFlagBits::eHostAllocationEXT, aligned_pointer);
|
||||||
|
|
||||||
if (!import_properties.memoryTypeBits) {
|
if (!import_properties.memoryTypeBits) {
|
||||||
// Could not import memory
|
// Could not import memory
|
||||||
@@ -1053,31 +1051,33 @@ bool RendererVulkan::TryRenderScreenshotWithHostMemory() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const vk::StructureChain allocation_chain = {
|
const vk::StructureChain<vk::MemoryAllocateInfo, vk::ImportMemoryHostPointerInfoEXT>
|
||||||
vk::MemoryAllocateInfo{
|
allocation_chain = {
|
||||||
.allocationSize = aligned_size,
|
vk::MemoryAllocateInfo{
|
||||||
.memoryTypeIndex = memory_type_index.value(),
|
.allocationSize = aligned_size,
|
||||||
},
|
.memoryTypeIndex = memory_type_index.value(),
|
||||||
vk::ImportMemoryHostPointerInfoEXT{
|
},
|
||||||
.handleType = vk::ExternalMemoryHandleTypeFlagBits::eHostAllocationEXT,
|
vk::ImportMemoryHostPointerInfoEXT{
|
||||||
.pHostPointer = aligned_pointer,
|
.handleType = vk::ExternalMemoryHandleTypeFlagBits::eHostAllocationEXT,
|
||||||
},
|
.pHostPointer = aligned_pointer,
|
||||||
};
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// Import host memory
|
// Import host memory
|
||||||
const vk::UniqueDeviceMemory imported_memory =
|
const vk::UniqueDeviceMemory imported_memory =
|
||||||
device.allocateMemoryUnique(allocation_chain.get());
|
device.allocateMemoryUnique(allocation_chain.get());
|
||||||
|
|
||||||
const vk::StructureChain buffer_info = {
|
const vk::StructureChain<vk::BufferCreateInfo, vk::ExternalMemoryBufferCreateInfo> buffer_info =
|
||||||
vk::BufferCreateInfo{
|
{
|
||||||
.size = aligned_size,
|
vk::BufferCreateInfo{
|
||||||
.usage = vk::BufferUsageFlagBits::eTransferDst,
|
.size = aligned_size,
|
||||||
.sharingMode = vk::SharingMode::eExclusive,
|
.usage = vk::BufferUsageFlagBits::eTransferDst,
|
||||||
},
|
.sharingMode = vk::SharingMode::eExclusive,
|
||||||
vk::ExternalMemoryBufferCreateInfo{
|
},
|
||||||
.handleTypes = vk::ExternalMemoryHandleTypeFlagBits::eHostAllocationEXT,
|
vk::ExternalMemoryBufferCreateInfo{
|
||||||
},
|
.handleTypes = vk::ExternalMemoryHandleTypeFlagBits::eHostAllocationEXT,
|
||||||
};
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// Bind imported memory to buffer
|
// Bind imported memory to buffer
|
||||||
const vk::UniqueBuffer imported_buffer = device.createBufferUnique(buffer_info.get());
|
const vk::UniqueBuffer imported_buffer = device.createBufferUnique(buffer_info.get());
|
||||||
|
@@ -4,15 +4,20 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <mutex>
|
||||||
|
#include "common/common_types.h"
|
||||||
#include "common/math_util.h"
|
#include "common/math_util.h"
|
||||||
#include "core/hw/gpu.h"
|
#include "core/hw/gpu.h"
|
||||||
#include "video_core/renderer_base.h"
|
#include "video_core/renderer_base.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
|
||||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||||
#include "video_core/renderer_vulkan/vk_present_window.h"
|
#include "video_core/renderer_vulkan/vk_present_window.h"
|
||||||
#include "video_core/renderer_vulkan/vk_rasterizer.h"
|
#include "video_core/renderer_vulkan/vk_rasterizer.h"
|
||||||
#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
|
#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
|
||||||
#include "video_core/renderer_vulkan/vk_resource_pool.h"
|
|
||||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_swapchain.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
class System;
|
class System;
|
||||||
@@ -56,8 +61,7 @@ static_assert(sizeof(PresentUniformData) == 112,
|
|||||||
"PresentUniformData does not structure in shader!");
|
"PresentUniformData does not structure in shader!");
|
||||||
|
|
||||||
class RendererVulkan : public VideoCore::RendererBase {
|
class RendererVulkan : public VideoCore::RendererBase {
|
||||||
static constexpr std::size_t NUM_PIPELINES = 3;
|
static constexpr std::size_t PRESENT_PIPELINES = 3;
|
||||||
static constexpr std::size_t NUM_SCREENS = 3;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit RendererVulkan(Core::System& system, Frontend::EmuWindow& window,
|
explicit RendererVulkan(Core::System& system, Frontend::EmuWindow& window,
|
||||||
@@ -79,7 +83,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
void ReloadPipeline();
|
void ReloadPipeline();
|
||||||
void CompileShaders();
|
void CompileShaders();
|
||||||
void BuildLayoutsAndDescriptors();
|
void BuildLayouts();
|
||||||
void BuildPipelines();
|
void BuildPipelines();
|
||||||
void ConfigureFramebufferTexture(TextureInfo& texture,
|
void ConfigureFramebufferTexture(TextureInfo& texture,
|
||||||
const GPU::Regs::FramebufferConfig& framebuffer);
|
const GPU::Regs::FramebufferConfig& framebuffer);
|
||||||
@@ -117,18 +121,16 @@ private:
|
|||||||
RasterizerVulkan rasterizer;
|
RasterizerVulkan rasterizer;
|
||||||
std::unique_ptr<PresentWindow> second_window;
|
std::unique_ptr<PresentWindow> second_window;
|
||||||
|
|
||||||
vk::PipelineLayout pipeline_layout;
|
vk::UniquePipelineLayout present_pipeline_layout;
|
||||||
vk::DescriptorSetLayout descriptor_set_layout;
|
DescriptorSetProvider present_set_provider;
|
||||||
vk::DescriptorUpdateTemplate update_template;
|
std::array<vk::Pipeline, PRESENT_PIPELINES> present_pipelines;
|
||||||
std::vector<vk::DescriptorSet> present_sets;
|
std::array<vk::ShaderModule, PRESENT_PIPELINES> present_shaders;
|
||||||
std::array<vk::Pipeline, NUM_PIPELINES> pipelines;
|
std::array<vk::Sampler, 2> present_samplers;
|
||||||
std::array<vk::ShaderModule, NUM_PIPELINES> frag_shaders;
|
vk::ShaderModule present_vertex_shader;
|
||||||
std::array<vk::Sampler, 2> samplers;
|
|
||||||
vk::ShaderModule vert_shader;
|
|
||||||
u32 current_pipeline = 0;
|
u32 current_pipeline = 0;
|
||||||
|
|
||||||
std::array<ScreenInfo, NUM_SCREENS> screen_infos{};
|
std::array<ScreenInfo, 3> screen_infos{};
|
||||||
std::array<vk::DescriptorImageInfo, NUM_SCREENS> image_infos{};
|
std::array<DescriptorData, 3> present_textures{};
|
||||||
PresentUniformData draw_info{};
|
PresentUniformData draw_info{};
|
||||||
vk::ClearColorValue clear_color{};
|
vk::ClearColorValue clear_color{};
|
||||||
};
|
};
|
||||||
|
@@ -177,9 +177,10 @@ constexpr vk::PipelineShaderStageCreateInfo MakeStages(vk::ShaderModule compute_
|
|||||||
|
|
||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
|
|
||||||
BlitHelper::BlitHelper(const Instance& instance_, Scheduler& scheduler_)
|
BlitHelper::BlitHelper(const Instance& instance_, Scheduler& scheduler_, DescriptorPool& pool,
|
||||||
: instance{instance_}, scheduler{scheduler_}, device{instance.GetDevice()},
|
RenderpassCache& renderpass_cache_)
|
||||||
compute_provider{instance, pool, COMPUTE_BINDINGS},
|
: instance{instance_}, scheduler{scheduler_}, renderpass_cache{renderpass_cache_},
|
||||||
|
device{instance.GetDevice()}, compute_provider{instance, pool, COMPUTE_BINDINGS},
|
||||||
compute_buffer_provider{instance, pool, COMPUTE_BUFFER_BINDINGS},
|
compute_buffer_provider{instance, pool, COMPUTE_BUFFER_BINDINGS},
|
||||||
two_textures_provider{instance, pool, TWO_TEXTURES_BINDINGS},
|
two_textures_provider{instance, pool, TWO_TEXTURES_BINDINGS},
|
||||||
compute_pipeline_layout{
|
compute_pipeline_layout{
|
||||||
@@ -313,6 +314,7 @@ bool BlitHelper::ConvertDS24S8ToRGBA8(Surface& source, Surface& dest,
|
|||||||
|
|
||||||
const auto descriptor_set = compute_provider.Acquire(textures);
|
const auto descriptor_set = compute_provider.Acquire(textures);
|
||||||
|
|
||||||
|
renderpass_cache.EndRendering();
|
||||||
scheduler.Record([this, descriptor_set, copy, src_image = source.Image(),
|
scheduler.Record([this, descriptor_set, copy, src_image = source.Image(),
|
||||||
dst_image = dest.Image()](vk::CommandBuffer cmdbuf) {
|
dst_image = dest.Image()](vk::CommandBuffer cmdbuf) {
|
||||||
const std::array pre_barriers = {
|
const std::array pre_barriers = {
|
||||||
@@ -416,18 +418,18 @@ bool BlitHelper::ConvertDS24S8ToRGBA8(Surface& source, Surface& dest,
|
|||||||
|
|
||||||
bool BlitHelper::DepthToBuffer(Surface& source, vk::Buffer buffer,
|
bool BlitHelper::DepthToBuffer(Surface& source, vk::Buffer buffer,
|
||||||
const VideoCore::BufferTextureCopy& copy) {
|
const VideoCore::BufferTextureCopy& copy) {
|
||||||
std::array<vk::DescriptorImageInfo, 3> image_infos;
|
std::array<DescriptorData, 3> textures{};
|
||||||
image_infos[0] = vk::DescriptorImageInfo{
|
textures[0].image_info = vk::DescriptorImageInfo{
|
||||||
.sampler = nearest_sampler,
|
.sampler = nearest_sampler,
|
||||||
.imageView = source.DepthView(),
|
.imageView = source.DepthView(),
|
||||||
.imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal,
|
.imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal,
|
||||||
};
|
};
|
||||||
image_infos[1] = vk::DescriptorImageInfo{
|
textures[1].image_info = vk::DescriptorImageInfo{
|
||||||
.sampler = nearest_sampler,
|
.sampler = nearest_sampler,
|
||||||
.imageView = source.StencilView(),
|
.imageView = source.StencilView(),
|
||||||
.imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal,
|
.imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal,
|
||||||
};
|
};
|
||||||
image_infos[2] = vk::DescriptorBufferInfo{
|
textures[2].buffer_info = vk::DescriptorBufferInfo{
|
||||||
.buffer = buffer,
|
.buffer = buffer,
|
||||||
.offset = copy.buffer_offset,
|
.offset = copy.buffer_offset,
|
||||||
.range = copy.buffer_size,
|
.range = copy.buffer_size,
|
||||||
@@ -435,6 +437,7 @@ bool BlitHelper::DepthToBuffer(Surface& source, vk::Buffer buffer,
|
|||||||
|
|
||||||
const auto descriptor_set = compute_buffer_provider.Acquire(textures);
|
const auto descriptor_set = compute_buffer_provider.Acquire(textures);
|
||||||
|
|
||||||
|
renderpass_cache.EndRendering();
|
||||||
scheduler.Record([this, descriptor_set, copy, src_image = source.Image(),
|
scheduler.Record([this, descriptor_set, copy, src_image = source.Image(),
|
||||||
extent = source.RealExtent(false)](vk::CommandBuffer cmdbuf) {
|
extent = source.RealExtent(false)](vk::CommandBuffer cmdbuf) {
|
||||||
const vk::ImageMemoryBarrier pre_barrier = {
|
const vk::ImageMemoryBarrier pre_barrier = {
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "video_core/renderer_vulkan/vk_common.h"
|
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
|
||||||
|
|
||||||
namespace VideoCore {
|
namespace VideoCore {
|
||||||
struct TextureBlit;
|
struct TextureBlit;
|
||||||
@@ -23,7 +23,8 @@ class BlitHelper {
|
|||||||
friend class TextureRuntime;
|
friend class TextureRuntime;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit BlitHelper(const Instance& instance, Scheduler& scheduler);
|
BlitHelper(const Instance& instance, Scheduler& scheduler, DescriptorPool& pool,
|
||||||
|
RenderpassCache& renderpass_cache);
|
||||||
~BlitHelper();
|
~BlitHelper();
|
||||||
|
|
||||||
bool BlitDepthStencil(Surface& source, Surface& dest, const VideoCore::TextureBlit& blit);
|
bool BlitDepthStencil(Surface& source, Surface& dest, const VideoCore::TextureBlit& blit);
|
||||||
@@ -34,16 +35,23 @@ public:
|
|||||||
const VideoCore::BufferTextureCopy& copy);
|
const VideoCore::BufferTextureCopy& copy);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// Creates compute pipelines used for blit
|
||||||
vk::Pipeline MakeComputePipeline(vk::ShaderModule shader, vk::PipelineLayout layout);
|
vk::Pipeline MakeComputePipeline(vk::ShaderModule shader, vk::PipelineLayout layout);
|
||||||
|
|
||||||
|
/// Creates graphics pipelines used for blit
|
||||||
vk::Pipeline MakeDepthStencilBlitPipeline();
|
vk::Pipeline MakeDepthStencilBlitPipeline();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const Instance& instance;
|
const Instance& instance;
|
||||||
Scheduler& scheduler;
|
Scheduler& scheduler;
|
||||||
|
RenderpassCache& renderpass_cache;
|
||||||
|
|
||||||
vk::Device device;
|
vk::Device device;
|
||||||
vk::RenderPass r32_renderpass;
|
vk::RenderPass r32_renderpass;
|
||||||
|
|
||||||
|
DescriptorSetProvider compute_provider;
|
||||||
|
DescriptorSetProvider compute_buffer_provider;
|
||||||
|
DescriptorSetProvider two_textures_provider;
|
||||||
vk::PipelineLayout compute_pipeline_layout;
|
vk::PipelineLayout compute_pipeline_layout;
|
||||||
vk::PipelineLayout compute_buffer_pipeline_layout;
|
vk::PipelineLayout compute_buffer_pipeline_layout;
|
||||||
vk::PipelineLayout two_textures_pipeline_layout;
|
vk::PipelineLayout two_textures_pipeline_layout;
|
||||||
|
@@ -9,6 +9,7 @@
|
|||||||
#define VK_NO_PROTOTYPES
|
#define VK_NO_PROTOTYPES
|
||||||
#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1
|
#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1
|
||||||
#define VULKAN_HPP_NO_CONSTRUCTORS
|
#define VULKAN_HPP_NO_CONSTRUCTORS
|
||||||
|
#define VULKAN_HPP_NO_UNION_CONSTRUCTORS
|
||||||
#define VULKAN_HPP_NO_STRUCT_SETTERS
|
#define VULKAN_HPP_NO_STRUCT_SETTERS
|
||||||
#include <vulkan/vulkan.hpp>
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
|
141
src/video_core/renderer_vulkan/vk_descriptor_pool.cpp
Normal file
141
src/video_core/renderer_vulkan/vk_descriptor_pool.cpp
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "common/microprofile.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
MICROPROFILE_DEFINE(Vulkan_DescriptorSetAcquire, "Vulkan", "Descriptor Set Acquire",
|
||||||
|
MP_RGB(64, 128, 256));
|
||||||
|
|
||||||
|
constexpr u32 MAX_BATCH_SIZE = 8;
|
||||||
|
|
||||||
|
DescriptorPool::DescriptorPool(const Instance& instance_) : instance{instance_} {
|
||||||
|
auto& pool = pools.emplace_back();
|
||||||
|
pool = CreatePool();
|
||||||
|
}
|
||||||
|
|
||||||
|
DescriptorPool::~DescriptorPool() = default;
|
||||||
|
|
||||||
|
std::vector<vk::DescriptorSet> DescriptorPool::Allocate(vk::DescriptorSetLayout layout,
|
||||||
|
u32 num_sets) {
|
||||||
|
std::array<vk::DescriptorSetLayout, MAX_BATCH_SIZE> layouts;
|
||||||
|
layouts.fill(layout);
|
||||||
|
|
||||||
|
u32 current_pool = 0;
|
||||||
|
vk::DescriptorSetAllocateInfo alloc_info = {
|
||||||
|
.descriptorPool = *pools[current_pool],
|
||||||
|
.descriptorSetCount = num_sets,
|
||||||
|
.pSetLayouts = layouts.data(),
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
return instance.GetDevice().allocateDescriptorSets(alloc_info);
|
||||||
|
} catch (const vk::OutOfPoolMemoryError&) {
|
||||||
|
current_pool++;
|
||||||
|
if (current_pool == pools.size()) {
|
||||||
|
LOG_INFO(Render_Vulkan, "Run out of pools, creating new one!");
|
||||||
|
auto& pool = pools.emplace_back();
|
||||||
|
pool = CreatePool();
|
||||||
|
}
|
||||||
|
alloc_info.descriptorPool = *pools[current_pool];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::DescriptorSet DescriptorPool::Allocate(vk::DescriptorSetLayout layout) {
|
||||||
|
const auto sets = Allocate(layout, 1);
|
||||||
|
return sets[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::UniqueDescriptorPool DescriptorPool::CreatePool() {
|
||||||
|
// Choose a sane pool size good for most games
|
||||||
|
static constexpr std::array<vk::DescriptorPoolSize, 6> pool_sizes = {{
|
||||||
|
{vk::DescriptorType::eUniformBufferDynamic, 64},
|
||||||
|
{vk::DescriptorType::eUniformTexelBuffer, 64},
|
||||||
|
{vk::DescriptorType::eCombinedImageSampler, 4096},
|
||||||
|
{vk::DescriptorType::eSampledImage, 256},
|
||||||
|
{vk::DescriptorType::eStorageImage, 256},
|
||||||
|
{vk::DescriptorType::eStorageBuffer, 32},
|
||||||
|
}};
|
||||||
|
|
||||||
|
const vk::DescriptorPoolCreateInfo descriptor_pool_info = {
|
||||||
|
.maxSets = 4098,
|
||||||
|
.poolSizeCount = static_cast<u32>(pool_sizes.size()),
|
||||||
|
.pPoolSizes = pool_sizes.data(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return instance.GetDevice().createDescriptorPoolUnique(descriptor_pool_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
DescriptorSetProvider::DescriptorSetProvider(
|
||||||
|
const Instance& instance, DescriptorPool& pool_,
|
||||||
|
std::span<const vk::DescriptorSetLayoutBinding> bindings)
|
||||||
|
: pool{pool_}, device{instance.GetDevice()} {
|
||||||
|
std::array<vk::DescriptorUpdateTemplateEntry, MAX_DESCRIPTORS> update_entries;
|
||||||
|
|
||||||
|
for (u32 i = 0; i < bindings.size(); i++) {
|
||||||
|
update_entries[i] = vk::DescriptorUpdateTemplateEntry{
|
||||||
|
.dstBinding = bindings[i].binding,
|
||||||
|
.dstArrayElement = 0,
|
||||||
|
.descriptorCount = bindings[i].descriptorCount,
|
||||||
|
.descriptorType = bindings[i].descriptorType,
|
||||||
|
.offset = i * sizeof(DescriptorData),
|
||||||
|
.stride = sizeof(DescriptorData),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const vk::DescriptorSetLayoutCreateInfo layout_info = {
|
||||||
|
.bindingCount = static_cast<u32>(bindings.size()),
|
||||||
|
.pBindings = bindings.data(),
|
||||||
|
};
|
||||||
|
layout = device.createDescriptorSetLayoutUnique(layout_info);
|
||||||
|
|
||||||
|
const vk::DescriptorUpdateTemplateCreateInfo template_info = {
|
||||||
|
.descriptorUpdateEntryCount = static_cast<u32>(bindings.size()),
|
||||||
|
.pDescriptorUpdateEntries = update_entries.data(),
|
||||||
|
.templateType = vk::DescriptorUpdateTemplateType::eDescriptorSet,
|
||||||
|
.descriptorSetLayout = *layout,
|
||||||
|
};
|
||||||
|
update_template = device.createDescriptorUpdateTemplateUnique(template_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
DescriptorSetProvider::~DescriptorSetProvider() = default;
|
||||||
|
|
||||||
|
vk::DescriptorSet DescriptorSetProvider::Acquire(std::span<const DescriptorData> data) {
|
||||||
|
MICROPROFILE_SCOPE(Vulkan_DescriptorSetAcquire);
|
||||||
|
DescriptorSetData key{};
|
||||||
|
std::memcpy(key.data(), data.data(), data.size_bytes());
|
||||||
|
const auto [it, new_set] = descriptor_set_map.try_emplace(key);
|
||||||
|
if (!new_set) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
if (free_sets.empty()) {
|
||||||
|
free_sets = pool.Allocate(*layout, MAX_BATCH_SIZE);
|
||||||
|
}
|
||||||
|
it.value() = free_sets.back();
|
||||||
|
free_sets.pop_back();
|
||||||
|
device.updateDescriptorSetWithTemplate(it->second, *update_template, data[0]);
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DescriptorSetProvider::FreeWithImage(vk::ImageView image_view) {
|
||||||
|
for (auto it = descriptor_set_map.begin(); it != descriptor_set_map.end();) {
|
||||||
|
const auto& [data, set] = *it;
|
||||||
|
const bool has_image = std::any_of(data.begin(), data.end(), [image_view](auto& info) {
|
||||||
|
return info.image_info.imageView == image_view;
|
||||||
|
});
|
||||||
|
if (has_image) {
|
||||||
|
free_sets.push_back(set);
|
||||||
|
it = descriptor_set_map.erase(it);
|
||||||
|
} else {
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
92
src/video_core/renderer_vulkan/vk_descriptor_pool.h
Normal file
92
src/video_core/renderer_vulkan/vk_descriptor_pool.h
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <span>
|
||||||
|
#include <vector>
|
||||||
|
#include <tsl/robin_map.h>
|
||||||
|
|
||||||
|
#include "common/hash.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_common.h"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
class Instance;
|
||||||
|
|
||||||
|
constexpr u32 MAX_DESCRIPTORS = 7;
|
||||||
|
|
||||||
|
union DescriptorData {
|
||||||
|
vk::DescriptorImageInfo image_info;
|
||||||
|
vk::DescriptorBufferInfo buffer_info;
|
||||||
|
vk::BufferView buffer_view;
|
||||||
|
|
||||||
|
bool operator==(const DescriptorData& other) const noexcept {
|
||||||
|
return std::memcmp(this, &other, sizeof(DescriptorData)) == 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using DescriptorSetData = std::array<DescriptorData, MAX_DESCRIPTORS>;
|
||||||
|
|
||||||
|
struct DataHasher {
|
||||||
|
u64 operator()(const DescriptorSetData& data) const noexcept {
|
||||||
|
return Common::ComputeHash64(data.data(), sizeof(data));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for allocating descriptor sets that manages a collection of descriptor pools.
|
||||||
|
*/
|
||||||
|
class DescriptorPool {
|
||||||
|
public:
|
||||||
|
explicit DescriptorPool(const Instance& instance);
|
||||||
|
~DescriptorPool();
|
||||||
|
|
||||||
|
std::vector<vk::DescriptorSet> Allocate(vk::DescriptorSetLayout layout, u32 num_sets);
|
||||||
|
|
||||||
|
vk::DescriptorSet Allocate(vk::DescriptorSetLayout layout);
|
||||||
|
|
||||||
|
private:
|
||||||
|
vk::UniqueDescriptorPool CreatePool();
|
||||||
|
|
||||||
|
private:
|
||||||
|
const Instance& instance;
|
||||||
|
std::vector<vk::UniqueDescriptorPool> pools;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocates and caches descriptor sets of a specific layout.
|
||||||
|
*/
|
||||||
|
class DescriptorSetProvider {
|
||||||
|
public:
|
||||||
|
explicit DescriptorSetProvider(const Instance& instance, DescriptorPool& pool,
|
||||||
|
std::span<const vk::DescriptorSetLayoutBinding> bindings);
|
||||||
|
~DescriptorSetProvider();
|
||||||
|
|
||||||
|
vk::DescriptorSet Acquire(std::span<const DescriptorData> data);
|
||||||
|
|
||||||
|
void FreeWithImage(vk::ImageView image_view);
|
||||||
|
|
||||||
|
[[nodiscard]] vk::DescriptorSetLayout Layout() const noexcept {
|
||||||
|
return *layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] vk::DescriptorSetLayout& Layout() noexcept {
|
||||||
|
return layout.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] vk::DescriptorUpdateTemplate UpdateTemplate() const noexcept {
|
||||||
|
return *update_template;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
DescriptorPool& pool;
|
||||||
|
vk::Device device;
|
||||||
|
vk::UniqueDescriptorSetLayout layout;
|
||||||
|
vk::UniqueDescriptorUpdateTemplate update_template;
|
||||||
|
std::vector<vk::DescriptorSet> free_sets;
|
||||||
|
tsl::robin_map<DescriptorSetData, vk::DescriptorSet, DataHasher> descriptor_set_map;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
@@ -1,75 +0,0 @@
|
|||||||
// Copyright 2023 Citra Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#include "video_core/renderer_vulkan/vk_descriptor_update.h"
|
|
||||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
|
||||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
|
||||||
|
|
||||||
namespace Vulkan {
|
|
||||||
|
|
||||||
constexpr size_t NUM_MAX_DESCRIPTORS = 5;
|
|
||||||
|
|
||||||
DescriptorSetSpec::DescriptorSetSpec(const Instance& instance,
|
|
||||||
std::span<const vk::DescriptorSetLayoutBinding> bindings) {
|
|
||||||
const vk::Device device = instance.GetDevice();
|
|
||||||
std::array<vk::DescriptorUpdateTemplateEntry, NUM_MAX_DESCRIPTORS> update_entries;
|
|
||||||
|
|
||||||
for (u32 i = 0; i < bindings.size(); i++) {
|
|
||||||
update_entries[i] = vk::DescriptorUpdateTemplateEntry{
|
|
||||||
.dstBinding = bindings[i].binding,
|
|
||||||
.dstArrayElement = 0,
|
|
||||||
.descriptorCount = bindings[i].descriptorCount,
|
|
||||||
.descriptorType = bindings[i].descriptorType,
|
|
||||||
.offset = i * sizeof(DescriptorUpdateEntry),
|
|
||||||
.stride = sizeof(DescriptorUpdateEntry),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const vk::DescriptorSetLayoutCreateInfo layout_info = {
|
|
||||||
.bindingCount = static_cast<u32>(bindings.size()),
|
|
||||||
.pBindings = bindings.data(),
|
|
||||||
};
|
|
||||||
descriptor_set_layout = device.createDescriptorSetLayoutUnique(layout_info);
|
|
||||||
|
|
||||||
const vk::DescriptorUpdateTemplateCreateInfo template_info = {
|
|
||||||
.descriptorUpdateEntryCount = static_cast<u32>(bindings.size()),
|
|
||||||
.pDescriptorUpdateEntries = update_entries.data(),
|
|
||||||
.templateType = vk::DescriptorUpdateTemplateType::eDescriptorSet,
|
|
||||||
.descriptorSetLayout = descriptor_set_layout.get(),
|
|
||||||
};
|
|
||||||
update_template = device.createDescriptorUpdateTemplateUnique(template_info);
|
|
||||||
}
|
|
||||||
|
|
||||||
DescriptorSetSpec::~DescriptorSetSpec() = default;
|
|
||||||
|
|
||||||
DescriptorUpdateQueue::DescriptorUpdateQueue(Scheduler& scheduler_, size_t num_frames_)
|
|
||||||
: scheduler{scheduler_}, num_frames{num_frames_} {
|
|
||||||
frame_payload_size = PAYLOAD_SIZE / num_frames;
|
|
||||||
payload_start = payload.data();
|
|
||||||
payload_cursor = payload.data();
|
|
||||||
}
|
|
||||||
|
|
||||||
DescriptorUpdateQueue::~DescriptorUpdateQueue() = default;
|
|
||||||
|
|
||||||
void DescriptorUpdateQueue::TickFrame() {
|
|
||||||
if (++frame_index >= num_frames) {
|
|
||||||
frame_index = 0;
|
|
||||||
}
|
|
||||||
payload_start = payload.data() + frame_index * frame_payload_size;
|
|
||||||
payload_cursor = payload_start;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DescriptorUpdateQueue::Acquire() {
|
|
||||||
// This is the maximum number of entries a single draw call might use.
|
|
||||||
static constexpr size_t MAX_ENTRIES = 0x14;
|
|
||||||
|
|
||||||
if (std::distance(payload_start, payload_cursor) + MAX_ENTRIES >= frame_payload_size) {
|
|
||||||
LOG_WARNING(Render_Vulkan, "Payload overflow, waiting for worker thread");
|
|
||||||
scheduler.WaitWorker();
|
|
||||||
payload_cursor = payload_start;
|
|
||||||
}
|
|
||||||
upload_start = payload_cursor;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Vulkan
|
|
@@ -1,96 +0,0 @@
|
|||||||
// Copyright 2023 Citra Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "common/literals.h"
|
|
||||||
#include "video_core/renderer_vulkan/vk_common.h"
|
|
||||||
|
|
||||||
namespace Vulkan {
|
|
||||||
|
|
||||||
class Instance;
|
|
||||||
class Scheduler;
|
|
||||||
|
|
||||||
union DescriptorUpdateEntry {
|
|
||||||
vk::DescriptorImageInfo image;
|
|
||||||
vk::DescriptorBufferInfo buffer;
|
|
||||||
vk::BufferView texel_buffer;
|
|
||||||
};
|
|
||||||
|
|
||||||
class DescriptorSetSpec {
|
|
||||||
public:
|
|
||||||
explicit DescriptorSetSpec(const Instance& instance,
|
|
||||||
std::span<const vk::DescriptorSetLayoutBinding> bindings);
|
|
||||||
~DescriptorSetSpec();
|
|
||||||
|
|
||||||
vk::DescriptorSetLayout Layout() const noexcept {
|
|
||||||
return descriptor_set_layout.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
vk::DescriptorUpdateTemplate Template() const noexcept {
|
|
||||||
return update_template.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
vk::UniqueDescriptorSetLayout descriptor_set_layout;
|
|
||||||
vk::UniqueDescriptorUpdateTemplate update_template;
|
|
||||||
};
|
|
||||||
|
|
||||||
using namespace Common::Literals;
|
|
||||||
|
|
||||||
class DescriptorUpdateQueue final {
|
|
||||||
static constexpr size_t PAYLOAD_SIZE = 1_MiB;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit DescriptorUpdateQueue(Scheduler& scheduler, size_t num_frames);
|
|
||||||
~DescriptorUpdateQueue();
|
|
||||||
|
|
||||||
void TickFrame();
|
|
||||||
|
|
||||||
void Acquire();
|
|
||||||
|
|
||||||
const DescriptorUpdateEntry* UpdateData() const noexcept {
|
|
||||||
return upload_start;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddImage(vk::ImageView image_view) {
|
|
||||||
(payload_cursor++)->image = vk::DescriptorImageInfo{
|
|
||||||
.sampler = VK_NULL_HANDLE,
|
|
||||||
.imageView = image_view,
|
|
||||||
.imageLayout = vk::ImageLayout::eGeneral,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddSampledImage(vk::ImageView image_view, vk::Sampler sampler) {
|
|
||||||
(payload_cursor++)->image = vk::DescriptorImageInfo{
|
|
||||||
.sampler = sampler,
|
|
||||||
.imageView = image_view,
|
|
||||||
.imageLayout = vk::ImageLayout::eGeneral,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddBuffer(vk::Buffer buffer, vk::DeviceSize offset, vk::DeviceSize size) {
|
|
||||||
(payload_cursor++)->buffer = vk::DescriptorBufferInfo{
|
|
||||||
.buffer = buffer,
|
|
||||||
.offset = offset,
|
|
||||||
.range = size,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddTexelBuffer(vk::BufferView texel_buffer) {
|
|
||||||
*(payload_cursor++)->texel_buffer = texel_buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Scheduler& scheduler;
|
|
||||||
size_t num_frames{};
|
|
||||||
size_t frame_payload_size{};
|
|
||||||
size_t frame_index{};
|
|
||||||
DescriptorUpdateEntry* payload_cursor{};
|
|
||||||
DescriptorUpdateEntry* payload_start{};
|
|
||||||
const DescriptorUpdateEntry* upload_start{};
|
|
||||||
std::array<DescriptorUpdateEntry, PAYLOAD_SIZE> payload;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Vulkan
|
|
@@ -272,10 +272,10 @@ bool GraphicsPipeline::Build(bool fail_on_compile_required) {
|
|||||||
pipeline_info.flags |= vk::PipelineCreateFlagBits::eFailOnPipelineCompileRequiredEXT;
|
pipeline_info.flags |= vk::PipelineCreateFlagBits::eFailOnPipelineCompileRequiredEXT;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto [result, handle] = device.createGraphicsPipelineUnique(pipeline_cache, pipeline_info);
|
auto result = device.createGraphicsPipelineUnique(pipeline_cache, pipeline_info);
|
||||||
if (result == vk::Result::eSuccess) {
|
if (result.result == vk::Result::eSuccess) {
|
||||||
pipeline = std::move(handle);
|
pipeline = std::move(result.value);
|
||||||
} else if (result == vk::Result::eErrorPipelineCompileRequiredEXT) {
|
} else if (result.result == vk::Result::eErrorPipelineCompileRequiredEXT) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
UNREACHABLE_MSG("Graphics pipeline creation failed!");
|
UNREACHABLE_MSG("Graphics pipeline creation failed!");
|
||||||
|
@@ -42,18 +42,22 @@ namespace Vulkan {
|
|||||||
class Instance;
|
class Instance;
|
||||||
class RenderpassCache;
|
class RenderpassCache;
|
||||||
|
|
||||||
constexpr size_t MAX_SHADER_STAGES = 3;
|
constexpr u32 MAX_SHADER_STAGES = 3;
|
||||||
constexpr size_t MAX_VERTEX_ATTRIBUTES = 16;
|
constexpr u32 MAX_VERTEX_ATTRIBUTES = 16;
|
||||||
constexpr size_t MAX_VERTEX_BINDINGS = 13;
|
constexpr u32 MAX_VERTEX_BINDINGS = 13;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pipeline state is tightly packed with bitfields to reduce
|
||||||
|
* the overhead of hashing as much as possible
|
||||||
|
*/
|
||||||
union RasterizationState {
|
union RasterizationState {
|
||||||
u32 raw;
|
u8 value = 0;
|
||||||
BitField<0, 2, Pica::PipelineRegs::TriangleTopology> topology;
|
BitField<0, 2, Pica::PipelineRegs::TriangleTopology> topology;
|
||||||
BitField<4, 2, Pica::RasterizerRegs::CullMode> cull_mode;
|
BitField<4, 2, Pica::RasterizerRegs::CullMode> cull_mode;
|
||||||
};
|
};
|
||||||
|
|
||||||
union DepthStencilState {
|
union DepthStencilState {
|
||||||
u32 raw;
|
u32 value = 0;
|
||||||
BitField<0, 1, u32> depth_test_enable;
|
BitField<0, 1, u32> depth_test_enable;
|
||||||
BitField<1, 1, u32> depth_write_enable;
|
BitField<1, 1, u32> depth_write_enable;
|
||||||
BitField<2, 1, u32> stencil_test_enable;
|
BitField<2, 1, u32> stencil_test_enable;
|
||||||
@@ -69,7 +73,7 @@ struct BlendingState {
|
|||||||
u16 color_write_mask;
|
u16 color_write_mask;
|
||||||
Pica::FramebufferRegs::LogicOp logic_op;
|
Pica::FramebufferRegs::LogicOp logic_op;
|
||||||
union {
|
union {
|
||||||
u32 raw;
|
u32 value = 0;
|
||||||
BitField<0, 4, Pica::FramebufferRegs::BlendFactor> src_color_blend_factor;
|
BitField<0, 4, Pica::FramebufferRegs::BlendFactor> src_color_blend_factor;
|
||||||
BitField<4, 4, Pica::FramebufferRegs::BlendFactor> dst_color_blend_factor;
|
BitField<4, 4, Pica::FramebufferRegs::BlendFactor> dst_color_blend_factor;
|
||||||
BitField<8, 3, Pica::FramebufferRegs::BlendEquation> color_blend_eq;
|
BitField<8, 3, Pica::FramebufferRegs::BlendEquation> color_blend_eq;
|
||||||
@@ -80,11 +84,10 @@ struct BlendingState {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct DynamicState {
|
struct DynamicState {
|
||||||
u32 blend_color;
|
u32 blend_color = 0;
|
||||||
u8 stencil_reference;
|
u8 stencil_reference;
|
||||||
u8 stencil_compare_mask;
|
u8 stencil_compare_mask;
|
||||||
u8 stencil_write_mask;
|
u8 stencil_write_mask;
|
||||||
INSERT_PADDING_BYTES(1);
|
|
||||||
|
|
||||||
Common::Rectangle<u32> scissor;
|
Common::Rectangle<u32> scissor;
|
||||||
Common::Rectangle<s32> viewport;
|
Common::Rectangle<s32> viewport;
|
||||||
@@ -126,19 +129,12 @@ struct AttachmentInfo {
|
|||||||
* Information about a graphics/compute pipeline
|
* Information about a graphics/compute pipeline
|
||||||
*/
|
*/
|
||||||
struct PipelineInfo {
|
struct PipelineInfo {
|
||||||
BlendingState blending{};
|
BlendingState blending;
|
||||||
AttachmentInfo attachments{};
|
AttachmentInfo attachments;
|
||||||
RasterizationState rasterization{};
|
RasterizationState rasterization;
|
||||||
DepthStencilState depth_stencil{};
|
DepthStencilState depth_stencil;
|
||||||
DynamicState dynamic{};
|
DynamicState dynamic;
|
||||||
VertexLayout vertex_layout{};
|
VertexLayout vertex_layout;
|
||||||
|
|
||||||
enum Type : u32 {
|
|
||||||
Normal,
|
|
||||||
ShadowPlane,
|
|
||||||
ShadowCube,
|
|
||||||
};
|
|
||||||
Type type{Type::Normal};
|
|
||||||
|
|
||||||
[[nodiscard]] u64 Hash(const Instance& instance) const;
|
[[nodiscard]] u64 Hash(const Instance& instance) const;
|
||||||
|
|
||||||
@@ -152,7 +148,6 @@ struct PipelineInfo {
|
|||||||
return depth_write || stencil_write;
|
return depth_write || stencil_write;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
static_assert(std::has_unique_object_representations_v<PipelineInfo>);
|
|
||||||
|
|
||||||
struct Shader : public Common::AsyncHandle {
|
struct Shader : public Common::AsyncHandle {
|
||||||
explicit Shader(const Instance& instance);
|
explicit Shader(const Instance& instance);
|
||||||
@@ -181,7 +176,7 @@ public:
|
|||||||
bool Build(bool fail_on_compile_required = false);
|
bool Build(bool fail_on_compile_required = false);
|
||||||
|
|
||||||
[[nodiscard]] vk::Pipeline Handle() const noexcept {
|
[[nodiscard]] vk::Pipeline Handle() const noexcept {
|
||||||
return pipeline.get();
|
return *pipeline;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@@ -8,7 +8,6 @@
|
|||||||
#include "common/file_util.h"
|
#include "common/file_util.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/microprofile.h"
|
#include "common/microprofile.h"
|
||||||
#include "common/scope_exit.h"
|
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
#include "video_core/renderer_vulkan/pica_to_vk.h"
|
#include "video_core/renderer_vulkan/pica_to_vk.h"
|
||||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||||
@@ -27,12 +26,6 @@ MICROPROFILE_DEFINE(Vulkan_Bind, "Vulkan", "Pipeline Bind", MP_RGB(192, 32, 32))
|
|||||||
|
|
||||||
namespace Vulkan {
|
namespace Vulkan {
|
||||||
|
|
||||||
enum DescriptorSet {
|
|
||||||
Buffer,
|
|
||||||
Texture,
|
|
||||||
Utility,
|
|
||||||
};
|
|
||||||
|
|
||||||
u32 AttribBytes(Pica::PipelineRegs::VertexAttributeFormat format, u32 size) {
|
u32 AttribBytes(Pica::PipelineRegs::VertexAttributeFormat format, u32 size) {
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case Pica::PipelineRegs::VertexAttributeFormat::FLOAT:
|
case Pica::PipelineRegs::VertexAttributeFormat::FLOAT:
|
||||||
@@ -68,33 +61,34 @@ constexpr std::array<vk::DescriptorSetLayoutBinding, 6> BUFFER_BINDINGS = {{
|
|||||||
{5, vk::DescriptorType::eUniformTexelBuffer, 1, vk::ShaderStageFlagBits::eFragment},
|
{5, vk::DescriptorType::eUniformTexelBuffer, 1, vk::ShaderStageFlagBits::eFragment},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
template <vk::DescriptorType tex0_type, u32 num_faces>
|
|
||||||
constexpr std::array<vk::DescriptorSetLayoutBinding, 3> TEXTURE_BINDINGS = {{
|
constexpr std::array<vk::DescriptorSetLayoutBinding, 3> TEXTURE_BINDINGS = {{
|
||||||
{0, tex0_type, num_faces, vk::ShaderStageFlagBits::eFragment},
|
{0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
|
||||||
{1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
|
{1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
|
||||||
{2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
|
{2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
constexpr std::array<vk::DescriptorSetLayoutBinding, 1> UTILITY_BINDINGS = {{
|
// TODO: Use descriptor array for shadow cube
|
||||||
|
constexpr std::array<vk::DescriptorSetLayoutBinding, 7> SHADOW_BINDINGS = {{
|
||||||
{0, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment},
|
{0, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment},
|
||||||
|
{1, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment},
|
||||||
|
{2, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment},
|
||||||
|
{3, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment},
|
||||||
|
{4, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment},
|
||||||
|
{5, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment},
|
||||||
|
{6, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_,
|
PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_,
|
||||||
RenderpassCache& renderpass_cache_, DescriptorPool& persistent_pool)
|
RenderpassCache& renderpass_cache_, DescriptorPool& pool_)
|
||||||
: instance{instance_}, scheduler{scheduler_},
|
: instance{instance_}, scheduler{scheduler_}, renderpass_cache{renderpass_cache_}, pool{pool_},
|
||||||
renderpass_cache{renderpass_cache_}, pool{instance, scheduler.GetMasterSemaphore()},
|
|
||||||
num_worker_threads{std::max(std::thread::hardware_concurrency(), 2U)},
|
num_worker_threads{std::max(std::thread::hardware_concurrency(), 2U)},
|
||||||
workers{num_worker_threads, "Pipeline workers"}, buffer_set_spec{instance, BUFFER_BINDINGS},
|
workers{num_worker_threads, "Pipeline workers"},
|
||||||
utility_set_spec{instance, UTILITY_BINDINGS},
|
descriptor_set_providers{DescriptorSetProvider{instance, pool, BUFFER_BINDINGS},
|
||||||
texture_set_specs{
|
DescriptorSetProvider{instance, pool, TEXTURE_BINDINGS},
|
||||||
DescriptorSetSpec{instance,
|
DescriptorSetProvider{instance, pool, SHADOW_BINDINGS}},
|
||||||
TEXTURE_BINDINGS<vk::DescriptorType::eCombinedImageSampler, 1>},
|
|
||||||
DescriptorSetSpec{instance, TEXTURE_BINDINGS<vk::DescriptorType::eStorageImage, 1>},
|
|
||||||
DescriptorSetSpec{instance, TEXTURE_BINDINGS<vk::DescriptorType::eStorageImage, 6>}},
|
|
||||||
trivial_vertex_shader{
|
trivial_vertex_shader{
|
||||||
instance, vk::ShaderStageFlagBits::eVertex,
|
instance, vk::ShaderStageFlagBits::eVertex,
|
||||||
GLSL::GenerateTrivialVertexShader(instance.IsShaderClipDistanceSupported(), true)} {
|
GLSL::GenerateTrivialVertexShader(instance.IsShaderClipDistanceSupported(), true)} {
|
||||||
// Create profile for driver assisted shader features.
|
|
||||||
profile = Pica::Shader::Profile{
|
profile = Pica::Shader::Profile{
|
||||||
.has_separable_shaders = true,
|
.has_separable_shaders = true,
|
||||||
.has_clip_planes = instance.IsShaderClipDistanceSupported(),
|
.has_clip_planes = instance.IsShaderClipDistanceSupported(),
|
||||||
@@ -107,10 +101,14 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_,
|
|||||||
.has_logic_op = !instance.NeedsLogicOpEmulation(),
|
.has_logic_op = !instance.NeedsLogicOpEmulation(),
|
||||||
.is_vulkan = true,
|
.is_vulkan = true,
|
||||||
};
|
};
|
||||||
|
BuildLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PipelineCache::BuildLayout() {
|
||||||
std::array<vk::DescriptorSetLayout, NUM_RASTERIZER_SETS> descriptor_set_layouts;
|
std::array<vk::DescriptorSetLayout, NUM_RASTERIZER_SETS> descriptor_set_layouts;
|
||||||
descriptor_set_layouts[DescriptorSet::Buffer] = buffer_set_spec.Layout();
|
std::transform(descriptor_set_providers.begin(), descriptor_set_providers.end(),
|
||||||
descriptor_set_layouts[DescriptorSet::Utility] = utility_set_spec.Layout();
|
descriptor_set_layouts.begin(),
|
||||||
|
[](const auto& provider) { return provider.Layout(); });
|
||||||
|
|
||||||
const vk::PipelineLayoutCreateInfo layout_info = {
|
const vk::PipelineLayoutCreateInfo layout_info = {
|
||||||
.setLayoutCount = NUM_RASTERIZER_SETS,
|
.setLayoutCount = NUM_RASTERIZER_SETS,
|
||||||
@@ -118,15 +116,7 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_,
|
|||||||
.pushConstantRangeCount = 0,
|
.pushConstantRangeCount = 0,
|
||||||
.pPushConstantRanges = nullptr,
|
.pPushConstantRanges = nullptr,
|
||||||
};
|
};
|
||||||
|
pipeline_layout = instance.GetDevice().createPipelineLayoutUnique(layout_info);
|
||||||
// Create rasterizer pipeline layouts.
|
|
||||||
for (size_t i = 0; i < NUM_PIPELINE_CONFIGS; i++) {
|
|
||||||
descriptor_set_layouts[DescriptorSet::Texture] = texture_set_specs[i].Layout();
|
|
||||||
pipeline_layouts[i] = instance.GetDevice().createPipelineLayoutUnique(layout_info);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allocate buffer descriptor set from the persistent pool
|
|
||||||
bound_descriptor_sets[DescriptorSet::Buffer] = persistent_pool.Commit(buffer_set_spec.Layout());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PipelineCache::~PipelineCache() {
|
PipelineCache::~PipelineCache() {
|
||||||
@@ -138,40 +128,34 @@ void PipelineCache::LoadDiskCache() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto cache_dir = GetPipelineCacheDir();
|
const std::string cache_file_path = fmt::format("{}{:x}{:x}.bin", GetPipelineCacheDir(),
|
||||||
const u32 vendor_id = instance.GetVendorID();
|
instance.GetVendorID(), instance.GetDeviceID());
|
||||||
const u32 device_id = instance.GetDeviceID();
|
vk::PipelineCacheCreateInfo cache_info = {
|
||||||
const auto cache_file_path = fmt::format("{}{:x}{:x}.bin", cache_dir, vendor_id, device_id);
|
.initialDataSize = 0,
|
||||||
|
.pInitialData = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
vk::PipelineCacheCreateInfo cache_info{};
|
|
||||||
std::vector<u8> cache_data;
|
std::vector<u8> cache_data;
|
||||||
|
FileUtil::IOFile cache_file{cache_file_path, "r"};
|
||||||
|
if (cache_file.IsOpen()) {
|
||||||
|
LOG_INFO(Render_Vulkan, "Loading pipeline cache");
|
||||||
|
|
||||||
SCOPE_EXIT({
|
const u64 cache_file_size = cache_file.GetSize();
|
||||||
const vk::Device device = instance.GetDevice();
|
cache_data.resize(cache_file_size);
|
||||||
pipeline_cache = device.createPipelineCacheUnique(cache_info);
|
if (cache_file.ReadBytes(cache_data.data(), cache_file_size)) {
|
||||||
});
|
if (!IsCacheValid(cache_data)) {
|
||||||
|
LOG_WARNING(Render_Vulkan, "Pipeline cache provided invalid, ignoring");
|
||||||
|
} else {
|
||||||
|
cache_info.initialDataSize = cache_file_size;
|
||||||
|
cache_info.pInitialData = cache_data.data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FileUtil::IOFile cache_file{cache_file_path, "rb"};
|
cache_file.Close();
|
||||||
if (!cache_file.IsOpen()) {
|
|
||||||
LOG_INFO(Render_Vulkan, "No pipeline cache found for device");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const u64 cache_file_size = cache_file.GetSize();
|
vk::Device device = instance.GetDevice();
|
||||||
cache_data.resize(cache_file_size);
|
pipeline_cache = device.createPipelineCacheUnique(cache_info);
|
||||||
if (cache_file.ReadBytes(cache_data.data(), cache_file_size) != cache_file_size) {
|
|
||||||
LOG_ERROR(Render_Vulkan, "Error during pipeline cache read, removing");
|
|
||||||
FileUtil::Delete(cache_file_path);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!IsCacheValid(cache_data)) {
|
|
||||||
LOG_WARNING(Render_Vulkan, "Pipeline cache provided invalid, ignoring");
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_INFO(Render_Vulkan, "Loading pipeline cache with size {} KB", cache_file_size / 1024);
|
|
||||||
cache_info.initialDataSize = cache_file_size;
|
|
||||||
cache_info.pInitialData = cache_data.data();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PipelineCache::SaveDiskCache() {
|
void PipelineCache::SaveDiskCache() {
|
||||||
@@ -179,23 +163,22 @@ void PipelineCache::SaveDiskCache() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto cache_dir = GetPipelineCacheDir();
|
const std::string cache_file_path = fmt::format("{}{:x}{:x}.bin", GetPipelineCacheDir(),
|
||||||
const u32 vendor_id = instance.GetVendorID();
|
instance.GetVendorID(), instance.GetDeviceID());
|
||||||
const u32 device_id = instance.GetDeviceID();
|
|
||||||
const auto cache_file_path = fmt::format("{}{:x}{:x}.bin", cache_dir, vendor_id, device_id);
|
|
||||||
|
|
||||||
FileUtil::IOFile cache_file{cache_file_path, "wb"};
|
FileUtil::IOFile cache_file{cache_file_path, "wb"};
|
||||||
if (!cache_file.IsOpen()) {
|
if (!cache_file.IsOpen()) {
|
||||||
LOG_ERROR(Render_Vulkan, "Unable to open pipeline cache for writing");
|
LOG_ERROR(Render_Vulkan, "Unable to open pipeline cache for writing");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const vk::Device device = instance.GetDevice();
|
vk::Device device = instance.GetDevice();
|
||||||
const auto cache_data = device.getPipelineCacheData(*pipeline_cache);
|
auto cache_data = device.getPipelineCacheData(*pipeline_cache);
|
||||||
if (cache_file.WriteBytes(cache_data.data(), cache_data.size()) != cache_data.size()) {
|
if (!cache_file.WriteBytes(cache_data.data(), cache_data.size())) {
|
||||||
LOG_ERROR(Render_Vulkan, "Error during pipeline cache write, removing");
|
LOG_ERROR(Render_Vulkan, "Error during pipeline cache write");
|
||||||
FileUtil::Delete(cache_file_path);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cache_file.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PipelineCache::BindPipeline(const PipelineInfo& info, bool wait_built) {
|
bool PipelineCache::BindPipeline(const PipelineInfo& info, bool wait_built) {
|
||||||
@@ -211,14 +194,12 @@ bool PipelineCache::BindPipeline(const PipelineInfo& info, bool wait_built) {
|
|||||||
|
|
||||||
auto [it, new_pipeline] = graphics_pipelines.try_emplace(pipeline_hash);
|
auto [it, new_pipeline] = graphics_pipelines.try_emplace(pipeline_hash);
|
||||||
if (new_pipeline) {
|
if (new_pipeline) {
|
||||||
const auto pipeline_layout = pipeline_layouts[info.type].get();
|
it.value() =
|
||||||
auto pipeline = std::make_unique<GraphicsPipeline>(instance, renderpass_cache, info,
|
std::make_unique<GraphicsPipeline>(instance, renderpass_cache, info, *pipeline_cache,
|
||||||
pipeline_cache.get(), pipeline_layout,
|
*pipeline_layout, current_shaders, &workers);
|
||||||
current_shaders, &workers);
|
|
||||||
it.value() = std::move(pipeline);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GraphicsPipeline* const pipeline = it->second.get();
|
GraphicsPipeline* const pipeline{it->second.get()};
|
||||||
if (!pipeline->IsDone() && !pipeline->TryBuild(wait_built)) {
|
if (!pipeline->IsDone() && !pipeline->TryBuild(wait_built)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -421,7 +402,7 @@ bool PipelineCache::UseProgrammableVertexShader(const Pica::Regs& regs,
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto [iter, new_program] = programmable_vertex_cache.try_emplace(program, instance);
|
auto [iter, new_program] = programmable_vertex_cache.try_emplace(program, instance);
|
||||||
auto& shader = iter.value();
|
auto& shader = iter->second;
|
||||||
|
|
||||||
if (new_program) {
|
if (new_program) {
|
||||||
shader.program = std::move(program);
|
shader.program = std::move(program);
|
||||||
@@ -432,7 +413,7 @@ bool PipelineCache::UseProgrammableVertexShader(const Pica::Regs& regs,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
it.value() = &shader;
|
it->second = &shader;
|
||||||
}
|
}
|
||||||
|
|
||||||
Shader* const shader{it->second};
|
Shader* const shader{it->second};
|
||||||
@@ -460,7 +441,7 @@ bool PipelineCache::UseFixedGeometryShader(const Pica::Regs& regs) {
|
|||||||
|
|
||||||
const PicaFixedGSConfig gs_config{regs, instance.IsShaderClipDistanceSupported()};
|
const PicaFixedGSConfig gs_config{regs, instance.IsShaderClipDistanceSupported()};
|
||||||
auto [it, new_shader] = fixed_geometry_shaders.try_emplace(gs_config, instance);
|
auto [it, new_shader] = fixed_geometry_shaders.try_emplace(gs_config, instance);
|
||||||
auto& shader = it.value();
|
auto& shader = it->second;
|
||||||
|
|
||||||
if (new_shader) {
|
if (new_shader) {
|
||||||
workers.QueueWork([gs_config, device = instance.GetDevice(), &shader]() {
|
workers.QueueWork([gs_config, device = instance.GetDevice(), &shader]() {
|
||||||
@@ -485,7 +466,7 @@ void PipelineCache::UseFragmentShader(const Pica::Regs& regs,
|
|||||||
const Pica::Shader::UserConfig& user) {
|
const Pica::Shader::UserConfig& user) {
|
||||||
const FSConfig fs_config{regs, user, profile};
|
const FSConfig fs_config{regs, user, profile};
|
||||||
const auto [it, new_shader] = fragment_shaders.try_emplace(fs_config, instance);
|
const auto [it, new_shader] = fragment_shaders.try_emplace(fs_config, instance);
|
||||||
auto& shader = it.value();
|
auto& shader = it->second;
|
||||||
|
|
||||||
if (new_shader) {
|
if (new_shader) {
|
||||||
const bool use_spirv = Settings::values.spirv_shader_gen.GetValue();
|
const bool use_spirv = Settings::values.spirv_shader_gen.GetValue();
|
||||||
@@ -507,6 +488,52 @@ void PipelineCache::UseFragmentShader(const Pica::Regs& regs,
|
|||||||
shader_hashes[ProgramType::FS] = fs_config.Hash();
|
shader_hashes[ProgramType::FS] = fs_config.Hash();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PipelineCache::BindTexture(u32 binding, vk::ImageView image_view, vk::Sampler sampler) {
|
||||||
|
auto& info = update_data[1][binding].image_info;
|
||||||
|
if (info.imageView == image_view && info.sampler == sampler) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
set_dirty[1] = true;
|
||||||
|
info = vk::DescriptorImageInfo{
|
||||||
|
.sampler = sampler,
|
||||||
|
.imageView = image_view,
|
||||||
|
.imageLayout = vk::ImageLayout::eGeneral,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void PipelineCache::BindStorageImage(u32 binding, vk::ImageView image_view) {
|
||||||
|
auto& info = update_data[2][binding].image_info;
|
||||||
|
if (info.imageView == image_view) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
set_dirty[2] = true;
|
||||||
|
info = vk::DescriptorImageInfo{
|
||||||
|
.imageView = image_view,
|
||||||
|
.imageLayout = vk::ImageLayout::eGeneral,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void PipelineCache::BindBuffer(u32 binding, vk::Buffer buffer, u32 offset, u32 size) {
|
||||||
|
auto& info = update_data[0][binding].buffer_info;
|
||||||
|
if (info.buffer == buffer && info.offset == offset && info.range == size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
set_dirty[0] = true;
|
||||||
|
info = vk::DescriptorBufferInfo{
|
||||||
|
.buffer = buffer,
|
||||||
|
.offset = offset,
|
||||||
|
.range = size,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void PipelineCache::BindTexelBuffer(u32 binding, vk::BufferView buffer_view) {
|
||||||
|
auto& view = update_data[0][binding].buffer_view;
|
||||||
|
if (view != buffer_view) {
|
||||||
|
set_dirty[0] = true;
|
||||||
|
view = buffer_view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PipelineCache::SetBufferOffset(u32 binding, size_t offset) {
|
void PipelineCache::SetBufferOffset(u32 binding, size_t offset) {
|
||||||
if (offsets[binding] != static_cast<u32>(offset)) {
|
if (offsets[binding] != static_cast<u32>(offset)) {
|
||||||
offsets[binding] = static_cast<u32>(offset);
|
offsets[binding] = static_cast<u32>(offset);
|
||||||
|
@@ -6,11 +6,9 @@
|
|||||||
|
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
#include <tsl/robin_map.h>
|
#include <tsl/robin_map.h>
|
||||||
#include "common/thread_worker.h"
|
|
||||||
|
|
||||||
#include "video_core/renderer_vulkan/vk_descriptor_update.h"
|
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
|
||||||
#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
|
#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
|
||||||
#include "video_core/renderer_vulkan/vk_resource_pool.h"
|
|
||||||
#include "video_core/shader/generator/pica_fs_config.h"
|
#include "video_core/shader/generator/pica_fs_config.h"
|
||||||
#include "video_core/shader/generator/profile.h"
|
#include "video_core/shader/generator/profile.h"
|
||||||
#include "video_core/shader/generator/shader_gen.h"
|
#include "video_core/shader/generator/shader_gen.h"
|
||||||
@@ -30,25 +28,22 @@ class Scheduler;
|
|||||||
class RenderpassCache;
|
class RenderpassCache;
|
||||||
class DescriptorPool;
|
class DescriptorPool;
|
||||||
|
|
||||||
enum class PipelineConfig {
|
constexpr u32 NUM_RASTERIZER_SETS = 3;
|
||||||
Normal,
|
constexpr u32 NUM_DYNAMIC_OFFSETS = 3;
|
||||||
ShadowPlane,
|
|
||||||
ShadowCube,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores a collection of rasterizer pipelines used during rendering.
|
* Stores a collection of rasterizer pipelines used during rendering.
|
||||||
*/
|
*/
|
||||||
class PipelineCache {
|
class PipelineCache {
|
||||||
static constexpr size_t NUM_RASTERIZER_SETS = 3;
|
|
||||||
static constexpr size_t NUM_PIPELINE_CONFIGS = 3;
|
|
||||||
static constexpr size_t NUM_DYNAMIC_OFFSETS = 3;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit PipelineCache(const Instance& instance, Scheduler& scheduler,
|
explicit PipelineCache(const Instance& instance, Scheduler& scheduler,
|
||||||
RenderpassCache& renderpass_cache, DescriptorPool& pool);
|
RenderpassCache& renderpass_cache, DescriptorPool& pool);
|
||||||
~PipelineCache();
|
~PipelineCache();
|
||||||
|
|
||||||
|
[[nodiscard]] DescriptorSetProvider& TextureProvider() noexcept {
|
||||||
|
return descriptor_set_providers[1];
|
||||||
|
}
|
||||||
|
|
||||||
/// Loads the pipeline cache stored to disk
|
/// Loads the pipeline cache stored to disk
|
||||||
void LoadDiskCache();
|
void LoadDiskCache();
|
||||||
|
|
||||||
@@ -74,10 +69,25 @@ public:
|
|||||||
/// Binds a fragment shader generated from PICA state
|
/// Binds a fragment shader generated from PICA state
|
||||||
void UseFragmentShader(const Pica::Regs& regs, const Pica::Shader::UserConfig& user);
|
void UseFragmentShader(const Pica::Regs& regs, const Pica::Shader::UserConfig& user);
|
||||||
|
|
||||||
|
/// Binds a texture to the specified binding
|
||||||
|
void BindTexture(u32 binding, vk::ImageView image_view, vk::Sampler sampler);
|
||||||
|
|
||||||
|
/// Binds a storage image to the specified binding
|
||||||
|
void BindStorageImage(u32 binding, vk::ImageView image_view);
|
||||||
|
|
||||||
|
/// Binds a buffer to the specified binding
|
||||||
|
void BindBuffer(u32 binding, vk::Buffer buffer, u32 offset, u32 size);
|
||||||
|
|
||||||
|
/// Binds a buffer to the specified binding
|
||||||
|
void BindTexelBuffer(u32 binding, vk::BufferView buffer_view);
|
||||||
|
|
||||||
/// Sets the dynamic offset for the uniform buffer at binding
|
/// Sets the dynamic offset for the uniform buffer at binding
|
||||||
void BindBufferRange(u32 binding, size_t offset);
|
void SetBufferOffset(u32 binding, size_t offset);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// Builds the rasterizer pipeline layout
|
||||||
|
void BuildLayout();
|
||||||
|
|
||||||
/// Returns true when the disk data can be used by the current driver
|
/// Returns true when the disk data can be used by the current driver
|
||||||
bool IsCacheValid(std::span<const u8> cache_data) const;
|
bool IsCacheValid(std::span<const u8> cache_data) const;
|
||||||
|
|
||||||
@@ -91,10 +101,11 @@ private:
|
|||||||
const Instance& instance;
|
const Instance& instance;
|
||||||
Scheduler& scheduler;
|
Scheduler& scheduler;
|
||||||
RenderpassCache& renderpass_cache;
|
RenderpassCache& renderpass_cache;
|
||||||
DescriptorPool pool;
|
DescriptorPool& pool;
|
||||||
|
|
||||||
Pica::Shader::Profile profile{};
|
Pica::Shader::Profile profile{};
|
||||||
vk::UniquePipelineCache pipeline_cache;
|
vk::UniquePipelineCache pipeline_cache;
|
||||||
|
vk::UniquePipelineLayout pipeline_layout;
|
||||||
std::size_t num_worker_threads;
|
std::size_t num_worker_threads;
|
||||||
Common::ThreadWorker workers;
|
Common::ThreadWorker workers;
|
||||||
PipelineInfo current_info{};
|
PipelineInfo current_info{};
|
||||||
@@ -102,21 +113,18 @@ private:
|
|||||||
tsl::robin_map<u64, std::unique_ptr<GraphicsPipeline>, Common::IdentityHash<u64>>
|
tsl::robin_map<u64, std::unique_ptr<GraphicsPipeline>, Common::IdentityHash<u64>>
|
||||||
graphics_pipelines;
|
graphics_pipelines;
|
||||||
|
|
||||||
DescriptorSetSpec buffer_set_spec;
|
std::array<DescriptorSetProvider, NUM_RASTERIZER_SETS> descriptor_set_providers;
|
||||||
DescriptorSetSpec utility_set_spec;
|
std::array<DescriptorSetData, NUM_RASTERIZER_SETS> update_data{};
|
||||||
std::array<DescriptorSetSpec, NUM_PIPELINE_CONFIGS> texture_set_specs;
|
std::array<vk::DescriptorSet, NUM_RASTERIZER_SETS> bound_descriptor_sets{};
|
||||||
std::array<vk::UniquePipelineLayout, NUM_PIPELINE_CONFIGS> pipeline_layouts;
|
|
||||||
|
|
||||||
std::array<vk::DescriptorSet, NUM_RASTERIZER_SETS> bound_descriptor_sets;
|
|
||||||
std::array<u32, NUM_DYNAMIC_OFFSETS> offsets{};
|
std::array<u32, NUM_DYNAMIC_OFFSETS> offsets{};
|
||||||
std::bitset<NUM_RASTERIZER_SETS> set_dirty{};
|
std::bitset<NUM_RASTERIZER_SETS> set_dirty{};
|
||||||
|
|
||||||
std::array<u64, MAX_SHADER_STAGES> shader_hashes;
|
std::array<u64, MAX_SHADER_STAGES> shader_hashes;
|
||||||
std::array<Shader*, MAX_SHADER_STAGES> current_shaders;
|
std::array<Shader*, MAX_SHADER_STAGES> current_shaders;
|
||||||
tsl::robin_map<Pica::Shader::Generator::PicaVSConfig, Shader*> programmable_vertex_map;
|
std::unordered_map<Pica::Shader::Generator::PicaVSConfig, Shader*> programmable_vertex_map;
|
||||||
tsl::robin_map<std::string, Shader> programmable_vertex_cache;
|
std::unordered_map<std::string, Shader> programmable_vertex_cache;
|
||||||
tsl::robin_map<Pica::Shader::Generator::PicaFixedGSConfig, Shader> fixed_geometry_shaders;
|
std::unordered_map<Pica::Shader::Generator::PicaFixedGSConfig, Shader> fixed_geometry_shaders;
|
||||||
tsl::robin_map<Pica::Shader::FSConfig, Shader> fragment_shaders;
|
std::unordered_map<Pica::Shader::FSConfig, Shader> fragment_shaders;
|
||||||
Shader trivial_vertex_shader;
|
Shader trivial_vertex_shader;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -106,7 +106,8 @@ PresentWindow::PresentWindow(Frontend::EmuWindow& emu_window_, const Instance& i
|
|||||||
vsync_enabled{Settings::values.use_vsync_new.GetValue()},
|
vsync_enabled{Settings::values.use_vsync_new.GetValue()},
|
||||||
blit_supported{
|
blit_supported{
|
||||||
CanBlitToSwapchain(instance.GetPhysicalDevice(), swapchain.GetSurfaceFormat().format)},
|
CanBlitToSwapchain(instance.GetPhysicalDevice(), swapchain.GetSurfaceFormat().format)},
|
||||||
use_present_thread{Settings::values.async_presentation.GetValue()} {
|
use_present_thread{Settings::values.async_presentation.GetValue()},
|
||||||
|
last_render_surface{emu_window.GetWindowInfo().render_surface} {
|
||||||
|
|
||||||
const u32 num_images = swapchain.GetImageCount();
|
const u32 num_images = swapchain.GetImageCount();
|
||||||
const vk::Device device = instance.GetDevice();
|
const vk::Device device = instance.GetDevice();
|
||||||
@@ -129,7 +130,6 @@ PresentWindow::PresentWindow(Frontend::EmuWindow& emu_window_, const Instance& i
|
|||||||
for (u32 i = 0; i < num_images; i++) {
|
for (u32 i = 0; i < num_images; i++) {
|
||||||
Frame& frame = swap_chain[i];
|
Frame& frame = swap_chain[i];
|
||||||
frame.cmdbuf = command_buffers[i];
|
frame.cmdbuf = command_buffers[i];
|
||||||
frame.index = i;
|
|
||||||
frame.render_ready = device.createSemaphore({});
|
frame.render_ready = device.createSemaphore({});
|
||||||
frame.present_done = device.createFence({.flags = vk::FenceCreateFlagBits::eSignaled});
|
frame.present_done = device.createFence({.flags = vk::FenceCreateFlagBits::eSignaled});
|
||||||
free_queue.push(&frame);
|
free_queue.push(&frame);
|
||||||
@@ -155,7 +155,7 @@ PresentWindow::~PresentWindow() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PresentWindow::RecreateFrame(Frame* frame, u32 width, u32 height) {
|
void PresentWindow::RecreateFrame(Frame* frame, u32 width, u32 height) {
|
||||||
const vk::Device device = instance.GetDevice();
|
vk::Device device = instance.GetDevice();
|
||||||
if (frame->framebuffer) {
|
if (frame->framebuffer) {
|
||||||
device.destroyFramebuffer(frame->framebuffer);
|
device.destroyFramebuffer(frame->framebuffer);
|
||||||
}
|
}
|
||||||
@@ -236,7 +236,7 @@ Frame* PresentWindow::GetRenderFrame() {
|
|||||||
Frame* frame = free_queue.front();
|
Frame* frame = free_queue.front();
|
||||||
free_queue.pop();
|
free_queue.pop();
|
||||||
|
|
||||||
const vk::Device device = instance.GetDevice();
|
vk::Device device = instance.GetDevice();
|
||||||
vk::Result result{};
|
vk::Result result{};
|
||||||
|
|
||||||
const auto wait = [&]() {
|
const auto wait = [&]() {
|
||||||
@@ -452,7 +452,7 @@ void PresentWindow::CopyToSwapchain(Frame* frame) {
|
|||||||
const vk::Semaphore image_acquired = swapchain.GetImageAcquiredSemaphore();
|
const vk::Semaphore image_acquired = swapchain.GetImageAcquiredSemaphore();
|
||||||
const std::array wait_semaphores = {image_acquired, frame->render_ready};
|
const std::array wait_semaphores = {image_acquired, frame->render_ready};
|
||||||
|
|
||||||
const vk::SubmitInfo submit_info = {
|
vk::SubmitInfo submit_info = {
|
||||||
.waitSemaphoreCount = static_cast<u32>(wait_semaphores.size()),
|
.waitSemaphoreCount = static_cast<u32>(wait_semaphores.size()),
|
||||||
.pWaitSemaphores = wait_semaphores.data(),
|
.pWaitSemaphores = wait_semaphores.data(),
|
||||||
.pWaitDstStageMask = wait_stage_masks.data(),
|
.pWaitDstStageMask = wait_stage_masks.data(),
|
||||||
@@ -467,7 +467,8 @@ void PresentWindow::CopyToSwapchain(Frame* frame) {
|
|||||||
try {
|
try {
|
||||||
graphics_queue.submit(submit_info, frame->present_done);
|
graphics_queue.submit(submit_info, frame->present_done);
|
||||||
} catch (vk::DeviceLostError& err) {
|
} catch (vk::DeviceLostError& err) {
|
||||||
UNREACHABLE_MSG("Device lost during present submit: {}", err.what());
|
LOG_CRITICAL(Render_Vulkan, "Device lost during present submit: {}", err.what());
|
||||||
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
|
|
||||||
swapchain.Present();
|
swapchain.Present();
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
|
#include <mutex>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include "common/polyfill_thread.h"
|
#include "common/polyfill_thread.h"
|
||||||
#include "video_core/renderer_vulkan/vk_swapchain.h"
|
#include "video_core/renderer_vulkan/vk_swapchain.h"
|
||||||
@@ -24,7 +25,6 @@ class RenderpassCache;
|
|||||||
struct Frame {
|
struct Frame {
|
||||||
u32 width;
|
u32 width;
|
||||||
u32 height;
|
u32 height;
|
||||||
u32 index;
|
|
||||||
VmaAllocation allocation;
|
VmaAllocation allocation;
|
||||||
vk::Framebuffer framebuffer;
|
vk::Framebuffer framebuffer;
|
||||||
vk::Image image;
|
vk::Image image;
|
||||||
@@ -55,12 +55,12 @@ public:
|
|||||||
/// This is called to notify the rendering backend of a surface change
|
/// This is called to notify the rendering backend of a surface change
|
||||||
void NotifySurfaceChanged();
|
void NotifySurfaceChanged();
|
||||||
|
|
||||||
vk::RenderPass Renderpass() const noexcept {
|
[[nodiscard]] vk::RenderPass Renderpass() const noexcept {
|
||||||
return present_renderpass;
|
return present_renderpass;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 ImageCount() const noexcept {
|
u32 ImageCount() const noexcept {
|
||||||
return swap_chain.size();
|
return swapchain.GetImageCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -94,6 +94,7 @@ private:
|
|||||||
bool vsync_enabled{};
|
bool vsync_enabled{};
|
||||||
bool blit_supported;
|
bool blit_supported;
|
||||||
bool use_present_thread{true};
|
bool use_present_thread{true};
|
||||||
|
void* last_render_surface{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
@@ -89,7 +89,6 @@ RasterizerVulkan::RasterizerVulkan(Memory::MemorySystem& memory,
|
|||||||
MakeSoftwareVertexLayout();
|
MakeSoftwareVertexLayout();
|
||||||
pipeline_info.vertex_layout = software_layout;
|
pipeline_info.vertex_layout = software_layout;
|
||||||
|
|
||||||
// Create texture buffer views for the lighting LUTs.
|
|
||||||
const vk::Device device = instance.GetDevice();
|
const vk::Device device = instance.GetDevice();
|
||||||
texture_lf_view = device.createBufferViewUnique({
|
texture_lf_view = device.createBufferViewUnique({
|
||||||
.buffer = texture_lf_buffer.Handle(),
|
.buffer = texture_lf_buffer.Handle(),
|
||||||
@@ -110,14 +109,24 @@ RasterizerVulkan::RasterizerVulkan(Memory::MemorySystem& memory,
|
|||||||
.range = VK_WHOLE_SIZE,
|
.range = VK_WHOLE_SIZE,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update persistent buffer descriptor set with our rasterizer buffers.
|
// Since we don't have access to VK_EXT_descriptor_indexing we need to intiallize
|
||||||
update_queue.Acquire();
|
// all descriptor sets even the ones we don't use.
|
||||||
update_queue.AddBuffer(uniform_buffer.Handle(), 0, sizeof(VSPicaUniformData));
|
pipeline_cache.BindBuffer(0, uniform_buffer.Handle(), 0, sizeof(VSPicaUniformData));
|
||||||
update_queue.AddBuffer(uniform_buffer.Handle(), 0, sizeof(VSPicaUniformData));
|
pipeline_cache.BindBuffer(1, uniform_buffer.Handle(), 0, sizeof(VSUniformData));
|
||||||
update_queue.AddBuffer(uniform_buffer.Handle(), 0, sizeof(VSUniformData));
|
pipeline_cache.BindBuffer(2, uniform_buffer.Handle(), 0, sizeof(FSUniformData));
|
||||||
update_queue.AddTexelBuffer(texture_lf_view.get());
|
pipeline_cache.BindTexelBuffer(3, *texture_lf_view);
|
||||||
update_queue.AddTexelBuffer(texture_rg_view.get());
|
pipeline_cache.BindTexelBuffer(4, *texture_rg_view);
|
||||||
update_queue.AddTexelBuffer(texture_rgba_view.get());
|
pipeline_cache.BindTexelBuffer(5, *texture_rgba_view);
|
||||||
|
|
||||||
|
Surface& null_surface = res_cache.GetSurface(VideoCore::NULL_SURFACE_ID);
|
||||||
|
Sampler& null_sampler = res_cache.GetSampler(VideoCore::NULL_SAMPLER_ID);
|
||||||
|
for (u32 i = 0; i < 3; i++) {
|
||||||
|
pipeline_cache.BindTexture(i, null_surface.ImageView(), null_sampler.Handle());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (u32 i = 0; i < 7; i++) {
|
||||||
|
pipeline_cache.BindStorageImage(i, null_surface.StorageView());
|
||||||
|
}
|
||||||
|
|
||||||
SyncEntireState();
|
SyncEntireState();
|
||||||
}
|
}
|
||||||
@@ -467,10 +476,16 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update attachment formats
|
|
||||||
pipeline_info.attachments.color = framebuffer->Format(SurfaceType::Color);
|
pipeline_info.attachments.color = framebuffer->Format(SurfaceType::Color);
|
||||||
pipeline_info.attachments.depth = framebuffer->Format(SurfaceType::Depth);
|
pipeline_info.attachments.depth = framebuffer->Format(SurfaceType::Depth);
|
||||||
|
|
||||||
|
if (shadow_rendering) {
|
||||||
|
pipeline_cache.BindStorageImage(6, framebuffer->ImageView(SurfaceType::Color));
|
||||||
|
} else {
|
||||||
|
Surface& null_surface = res_cache.GetSurface(VideoCore::NULL_SURFACE_ID);
|
||||||
|
pipeline_cache.BindStorageImage(6, null_surface.StorageView());
|
||||||
|
}
|
||||||
|
|
||||||
// Update scissor uniforms
|
// Update scissor uniforms
|
||||||
const auto [scissor_x1, scissor_y2, scissor_x2, scissor_y1] = fb_helper.Scissor();
|
const auto [scissor_x1, scissor_y2, scissor_x2, scissor_y1] = fb_helper.Scissor();
|
||||||
if (fs_uniform_block_data.data.scissor_x1 != scissor_x1 ||
|
if (fs_uniform_block_data.data.scissor_x1 != scissor_x1 ||
|
||||||
@@ -488,11 +503,6 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) {
|
|||||||
// Sync and bind the texture surfaces
|
// Sync and bind the texture surfaces
|
||||||
SyncTextureUnits(framebuffer);
|
SyncTextureUnits(framebuffer);
|
||||||
|
|
||||||
// Attach the framebuffer as storage image during shadow rendering.
|
|
||||||
if (shadow_rendering) {
|
|
||||||
update_queue.AddImage(framebuffer->ImageView(SurfaceType::Color));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sync and bind the shader
|
// Sync and bind the shader
|
||||||
if (shader_dirty) {
|
if (shader_dirty) {
|
||||||
pipeline_cache.UseFragmentShader(regs, user_config);
|
pipeline_cache.UseFragmentShader(regs, user_config);
|
||||||
@@ -545,17 +555,7 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) {
|
|||||||
void RasterizerVulkan::SyncTextureUnits(const Framebuffer* framebuffer) {
|
void RasterizerVulkan::SyncTextureUnits(const Framebuffer* framebuffer) {
|
||||||
using TextureType = Pica::TexturingRegs::TextureConfig::TextureType;
|
using TextureType = Pica::TexturingRegs::TextureConfig::TextureType;
|
||||||
|
|
||||||
// Check if the PICA texture configuration changed.
|
|
||||||
const auto pica_textures = regs.texturing.GetTextures();
|
const auto pica_textures = regs.texturing.GetTextures();
|
||||||
if (pica_textures == textures && textures[0].config.type != TextureType::TextureCube) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reserve space in the queue for incoming texture data.
|
|
||||||
// We will write the image data in the same order as defined
|
|
||||||
// in the rasterizer descriptor texture sets.
|
|
||||||
update_queue.Acquire();
|
|
||||||
|
|
||||||
for (u32 texture_index = 0; texture_index < pica_textures.size(); ++texture_index) {
|
for (u32 texture_index = 0; texture_index < pica_textures.size(); ++texture_index) {
|
||||||
const auto& texture = pica_textures[texture_index];
|
const auto& texture = pica_textures[texture_index];
|
||||||
|
|
||||||
@@ -563,7 +563,8 @@ void RasterizerVulkan::SyncTextureUnits(const Framebuffer* framebuffer) {
|
|||||||
if (!texture.enabled) {
|
if (!texture.enabled) {
|
||||||
const Surface& null_surface = res_cache.GetSurface(VideoCore::NULL_SURFACE_ID);
|
const Surface& null_surface = res_cache.GetSurface(VideoCore::NULL_SURFACE_ID);
|
||||||
const Sampler& null_sampler = res_cache.GetSampler(VideoCore::NULL_SAMPLER_ID);
|
const Sampler& null_sampler = res_cache.GetSampler(VideoCore::NULL_SAMPLER_ID);
|
||||||
update_queue.AddSampledImage(null_surface.ImageView(), null_sampler.Handle());
|
pipeline_cache.BindTexture(texture_index, null_surface.ImageView(),
|
||||||
|
null_sampler.Handle());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -573,7 +574,7 @@ void RasterizerVulkan::SyncTextureUnits(const Framebuffer* framebuffer) {
|
|||||||
case TextureType::Shadow2D: {
|
case TextureType::Shadow2D: {
|
||||||
Surface& surface = res_cache.GetTextureSurface(texture);
|
Surface& surface = res_cache.GetTextureSurface(texture);
|
||||||
surface.flags |= VideoCore::SurfaceFlagBits::ShadowMap;
|
surface.flags |= VideoCore::SurfaceFlagBits::ShadowMap;
|
||||||
update_queue.AddImage(surface.StorageView());
|
pipeline_cache.BindStorageImage(0, surface.StorageView());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
case TextureType::ShadowCube: {
|
case TextureType::ShadowCube: {
|
||||||
@@ -585,6 +586,7 @@ void RasterizerVulkan::SyncTextureUnits(const Framebuffer* framebuffer) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
UnbindSpecial();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -592,12 +594,10 @@ void RasterizerVulkan::SyncTextureUnits(const Framebuffer* framebuffer) {
|
|||||||
// Bind the texture provided by the rasterizer cache
|
// Bind the texture provided by the rasterizer cache
|
||||||
Surface& surface = res_cache.GetTextureSurface(texture);
|
Surface& surface = res_cache.GetTextureSurface(texture);
|
||||||
Sampler& sampler = res_cache.GetSampler(texture.config);
|
Sampler& sampler = res_cache.GetSampler(texture.config);
|
||||||
if (!IsFeedbackLoop(framebuffer, surface, sampler)) {
|
if (!IsFeedbackLoop(texture_index, framebuffer, surface, sampler)) {
|
||||||
update_queue.AddSampledImage(surface.ImageView(), sampler.Handle());
|
pipeline_cache.BindTexture(texture_index, surface.ImageView(), sampler.Handle());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textures = pica_textures;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RasterizerVulkan::BindShadowCube(const Pica::TexturingRegs::FullTextureConfig& texture) {
|
void RasterizerVulkan::BindShadowCube(const Pica::TexturingRegs::FullTextureConfig& texture) {
|
||||||
@@ -609,11 +609,13 @@ void RasterizerVulkan::BindShadowCube(const Pica::TexturingRegs::FullTextureConf
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (CubeFace face : faces) {
|
for (CubeFace face : faces) {
|
||||||
|
const u32 binding = static_cast<u32>(face);
|
||||||
info.physical_address = regs.texturing.GetCubePhysicalAddress(face);
|
info.physical_address = regs.texturing.GetCubePhysicalAddress(face);
|
||||||
const auto surface_id = res_cache.GetTextureSurface(info);
|
|
||||||
|
const VideoCore::SurfaceId surface_id = res_cache.GetTextureSurface(info);
|
||||||
Surface& surface = res_cache.GetSurface(surface_id);
|
Surface& surface = res_cache.GetSurface(surface_id);
|
||||||
surface.flags |= VideoCore::SurfaceFlagBits::ShadowMap;
|
surface.flags |= VideoCore::SurfaceFlagBits::ShadowMap;
|
||||||
update_queue.AddImage(surface.StorageView());
|
pipeline_cache.BindStorageImage(binding, surface.StorageView());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -631,13 +633,13 @@ void RasterizerVulkan::BindTextureCube(const Pica::TexturingRegs::FullTextureCon
|
|||||||
.format = texture.format,
|
.format = texture.format,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Surface& surface = res_cache.GetTextureCube(config);
|
Surface& surface = res_cache.GetTextureCube(config);
|
||||||
const Sampler& sampler = res_cache.GetSampler(texture.config);
|
Sampler& sampler = res_cache.GetSampler(texture.config);
|
||||||
update_queue.AddSampledImage(surface.ImageView(), sampler.Handle());
|
pipeline_cache.BindTexture(0, surface.ImageView(), sampler.Handle());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RasterizerVulkan::IsFeedbackLoop(const Framebuffer* framebuffer, Surface& surface,
|
bool RasterizerVulkan::IsFeedbackLoop(u32 texture_index, const Framebuffer* framebuffer,
|
||||||
Sampler& sampler) {
|
Surface& surface, Sampler& sampler) {
|
||||||
const vk::ImageView color_view = framebuffer->ImageView(SurfaceType::Color);
|
const vk::ImageView color_view = framebuffer->ImageView(SurfaceType::Color);
|
||||||
const bool is_feedback_loop = color_view == surface.ImageView();
|
const bool is_feedback_loop = color_view == surface.ImageView();
|
||||||
if (!is_feedback_loop) {
|
if (!is_feedback_loop) {
|
||||||
@@ -645,10 +647,17 @@ bool RasterizerVulkan::IsFeedbackLoop(const Framebuffer* framebuffer, Surface& s
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make a temporary copy of the framebuffer to sample from
|
// Make a temporary copy of the framebuffer to sample from
|
||||||
update_queue.AddSampledImage(surface.CopyImageView(), sampler.Handle());
|
pipeline_cache.BindTexture(texture_index, surface.CopyImageView(), sampler.Handle());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RasterizerVulkan::UnbindSpecial() {
|
||||||
|
Surface& null_surface = res_cache.GetSurface(VideoCore::NULL_SURFACE_ID);
|
||||||
|
for (u32 i = 0; i < 6; i++) {
|
||||||
|
pipeline_cache.BindStorageImage(i, null_surface.StorageView());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void RasterizerVulkan::NotifyFixedFunctionPicaRegisterChanged(u32 id) {
|
void RasterizerVulkan::NotifyFixedFunctionPicaRegisterChanged(u32 id) {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
// Culling
|
// Culling
|
||||||
@@ -1089,24 +1098,24 @@ void RasterizerVulkan::UploadUniforms(bool accelerate_draw) {
|
|||||||
auto [uniforms, offset, invalidate] =
|
auto [uniforms, offset, invalidate] =
|
||||||
uniform_buffer.Map(uniform_size, uniform_buffer_alignment);
|
uniform_buffer.Map(uniform_size, uniform_buffer_alignment);
|
||||||
|
|
||||||
size_t used_bytes = 0;
|
u32 used_bytes = 0;
|
||||||
|
|
||||||
if (sync_vs || invalidate) {
|
if (sync_vs || invalidate) {
|
||||||
std::memcpy(uniforms + used_bytes, &vs_uniform_block_data.data,
|
std::memcpy(uniforms + used_bytes, &vs_uniform_block_data.data,
|
||||||
sizeof(vs_uniform_block_data.data));
|
sizeof(vs_uniform_block_data.data));
|
||||||
|
|
||||||
pipeline_cache.BindBufferRange(1, offset + used_bytes);
|
pipeline_cache.SetBufferOffset(1, offset + used_bytes);
|
||||||
vs_uniform_block_data.dirty = false;
|
vs_uniform_block_data.dirty = false;
|
||||||
used_bytes += uniform_size_aligned_vs;
|
used_bytes += static_cast<u32>(uniform_size_aligned_vs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sync_fs || invalidate) {
|
if (sync_fs || invalidate) {
|
||||||
std::memcpy(uniforms + used_bytes, &fs_uniform_block_data.data,
|
std::memcpy(uniforms + used_bytes, &fs_uniform_block_data.data,
|
||||||
sizeof(fs_uniform_block_data.data));
|
sizeof(fs_uniform_block_data.data));
|
||||||
|
|
||||||
pipeline_cache.BindBufferRange(2, offset + used_bytes);
|
pipeline_cache.SetBufferOffset(2, offset + used_bytes);
|
||||||
fs_uniform_block_data.dirty = false;
|
fs_uniform_block_data.dirty = false;
|
||||||
used_bytes += uniform_size_aligned_fs;
|
used_bytes += static_cast<u32>(uniform_size_aligned_fs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sync_vs_pica) {
|
if (sync_vs_pica) {
|
||||||
@@ -1114,8 +1123,8 @@ void RasterizerVulkan::UploadUniforms(bool accelerate_draw) {
|
|||||||
vs_uniforms.uniforms.SetFromRegs(regs.vs, Pica::g_state.vs);
|
vs_uniforms.uniforms.SetFromRegs(regs.vs, Pica::g_state.vs);
|
||||||
std::memcpy(uniforms + used_bytes, &vs_uniforms, sizeof(vs_uniforms));
|
std::memcpy(uniforms + used_bytes, &vs_uniforms, sizeof(vs_uniforms));
|
||||||
|
|
||||||
pipeline_cache.BindBufferRange(0, offset + used_bytes);
|
pipeline_cache.SetBufferOffset(0, offset + used_bytes);
|
||||||
used_bytes += uniform_size_aligned_vs_pica;
|
used_bytes += static_cast<u32>(uniform_size_aligned_vs_pica);
|
||||||
}
|
}
|
||||||
|
|
||||||
uniform_buffer.Commit(used_bytes);
|
uniform_buffer.Commit(used_bytes);
|
||||||
|
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
#include "core/hw/gpu.h"
|
#include "core/hw/gpu.h"
|
||||||
#include "video_core/rasterizer_accelerated.h"
|
#include "video_core/rasterizer_accelerated.h"
|
||||||
#include "video_core/renderer_vulkan/vk_descriptor_update.h"
|
|
||||||
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
|
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
|
||||||
#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
|
#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
|
||||||
#include "video_core/renderer_vulkan/vk_stream_buffer.h"
|
#include "video_core/renderer_vulkan/vk_stream_buffer.h"
|
||||||
@@ -105,7 +104,11 @@ private:
|
|||||||
void BindTextureCube(const Pica::TexturingRegs::FullTextureConfig& texture);
|
void BindTextureCube(const Pica::TexturingRegs::FullTextureConfig& texture);
|
||||||
|
|
||||||
/// Makes a temporary copy of the framebuffer if a feedback loop is detected
|
/// Makes a temporary copy of the framebuffer if a feedback loop is detected
|
||||||
bool IsFeedbackLoop(const Framebuffer* framebuffer, Surface& surface, Sampler& sampler);
|
bool IsFeedbackLoop(u32 texture_index, const Framebuffer* framebuffer, Surface& surface,
|
||||||
|
Sampler& sampler);
|
||||||
|
|
||||||
|
/// Unbinds all special texture unit 0 texture configurations
|
||||||
|
void UnbindSpecial();
|
||||||
|
|
||||||
/// Upload the uniform blocks to the uniform buffer object
|
/// Upload the uniform blocks to the uniform buffer object
|
||||||
void UploadUniforms(bool accelerate_draw);
|
void UploadUniforms(bool accelerate_draw);
|
||||||
@@ -141,7 +144,6 @@ private:
|
|||||||
PipelineCache pipeline_cache;
|
PipelineCache pipeline_cache;
|
||||||
TextureRuntime runtime;
|
TextureRuntime runtime;
|
||||||
RasterizerCache res_cache;
|
RasterizerCache res_cache;
|
||||||
DescriptorUpdateQueue update_queue;
|
|
||||||
|
|
||||||
VertexLayout software_layout;
|
VertexLayout software_layout;
|
||||||
std::array<u32, 16> binding_offsets{};
|
std::array<u32, 16> binding_offsets{};
|
||||||
@@ -157,7 +159,6 @@ private:
|
|||||||
vk::UniqueBufferView texture_lf_view;
|
vk::UniqueBufferView texture_lf_view;
|
||||||
vk::UniqueBufferView texture_rg_view;
|
vk::UniqueBufferView texture_rg_view;
|
||||||
vk::UniqueBufferView texture_rgba_view;
|
vk::UniqueBufferView texture_rgba_view;
|
||||||
Pica::TexturingRegs::Textures textures{};
|
|
||||||
u64 uniform_buffer_alignment;
|
u64 uniform_buffer_alignment;
|
||||||
u64 uniform_size_aligned_vs_pica;
|
u64 uniform_size_aligned_vs_pica;
|
||||||
u64 uniform_size_aligned_vs;
|
u64 uniform_size_aligned_vs;
|
||||||
|
@@ -1,147 +1,113 @@
|
|||||||
// Copyright 2023 Citra Emulator Project
|
// Copyright 2020 yuzu Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <optional>
|
||||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||||
#include "video_core/renderer_vulkan/vk_master_semaphore.h"
|
#include "video_core/renderer_vulkan/vk_master_semaphore.h"
|
||||||
#include "video_core/renderer_vulkan/vk_resource_pool.h"
|
#include "video_core/renderer_vulkan/vk_resource_pool.h"
|
||||||
|
|
||||||
namespace Vulkan {
|
namespace Vulkan {
|
||||||
|
|
||||||
ResourcePool::ResourcePool(MasterSemaphore* master_semaphore_)
|
ResourcePool::ResourcePool(MasterSemaphore* master_semaphore_, size_t grow_step_)
|
||||||
: master_semaphore{master_semaphore_} {}
|
: master_semaphore{master_semaphore_}, grow_step{grow_step_} {}
|
||||||
|
|
||||||
ResourcePool::~ResourcePool() = default;
|
std::size_t ResourcePool::CommitResource() {
|
||||||
|
|
||||||
s64 ResourcePool::CommitResource() {
|
|
||||||
// Refresh semaphore to query updated results
|
// Refresh semaphore to query updated results
|
||||||
master_semaphore->Refresh();
|
master_semaphore->Refresh();
|
||||||
const u64 gpu_tick = master_semaphore->KnownGpuTick();
|
const u64 gpu_tick = master_semaphore->KnownGpuTick();
|
||||||
|
const auto search = [this, gpu_tick](std::size_t begin,
|
||||||
|
std::size_t end) -> std::optional<std::size_t> {
|
||||||
|
for (std::size_t iterator = begin; iterator < end; ++iterator) {
|
||||||
|
if (gpu_tick >= ticks[iterator]) {
|
||||||
|
ticks[iterator] = master_semaphore->CurrentTick();
|
||||||
|
return iterator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
};
|
||||||
|
|
||||||
// Update the last used tick of the previous resource.
|
// Try to find a free resource from the hinted position to the end.
|
||||||
if (last_index != -1) {
|
std::optional<std::size_t> found = search(hint_iterator, ticks.size());
|
||||||
ticks[last_index] = master_semaphore->CurrentTick();
|
if (!found) {
|
||||||
}
|
// Search from beginning to the hinted position.
|
||||||
|
found = search(0, hint_iterator);
|
||||||
|
if (!found) {
|
||||||
|
// Both searches failed, the pool is full; handle it.
|
||||||
|
const std::size_t free_resource = ManageOverflow();
|
||||||
|
|
||||||
// Try to find a free resource.
|
ticks[free_resource] = master_semaphore->CurrentTick();
|
||||||
size_t found = ticks.size();
|
found = free_resource;
|
||||||
for (size_t index = 0; index < ticks.size(); index++) {
|
|
||||||
if (gpu_tick >= ticks[index]) {
|
|
||||||
found = index;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The pool is full; handle it.
|
// Free iterator is hinted to the resource after the one that's been commited.
|
||||||
if (found == ticks.size()) {
|
hint_iterator = (*found + 1) % ticks.size();
|
||||||
ticks.resize(found + 1);
|
return *found;
|
||||||
Allocate();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return found resource.
|
|
||||||
last_index = found;
|
|
||||||
return found;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::size_t ResourcePool::ManageOverflow() {
|
||||||
|
const std::size_t old_capacity = ticks.size();
|
||||||
|
Grow();
|
||||||
|
|
||||||
|
// The last entry is guaranted to be free, since it's the first element of the freshly
|
||||||
|
// allocated resources.
|
||||||
|
return old_capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourcePool::Grow() {
|
||||||
|
const size_t old_capacity = ticks.size();
|
||||||
|
ticks.resize(old_capacity + grow_step);
|
||||||
|
Allocate(old_capacity, old_capacity + grow_step);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr size_t COMMAND_BUFFER_POOL_SIZE = 4;
|
||||||
|
|
||||||
|
struct CommandPool::Pool {
|
||||||
|
vk::CommandPool handle;
|
||||||
|
std::array<vk::CommandBuffer, COMMAND_BUFFER_POOL_SIZE> cmdbufs;
|
||||||
|
};
|
||||||
|
|
||||||
CommandPool::CommandPool(const Instance& instance, MasterSemaphore* master_semaphore)
|
CommandPool::CommandPool(const Instance& instance, MasterSemaphore* master_semaphore)
|
||||||
: ResourcePool{master_semaphore}, device{instance.GetDevice()} {
|
: ResourcePool{master_semaphore, COMMAND_BUFFER_POOL_SIZE}, instance{instance} {}
|
||||||
|
|
||||||
|
CommandPool::~CommandPool() {
|
||||||
|
vk::Device device = instance.GetDevice();
|
||||||
|
for (Pool& pool : pools) {
|
||||||
|
device.destroyCommandPool(pool.handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandPool::Allocate(std::size_t begin, std::size_t end) {
|
||||||
|
// Command buffers are going to be commited, recorded, executed every single usage cycle.
|
||||||
|
// They are also going to be reseted when commited.
|
||||||
|
Pool& pool = pools.emplace_back();
|
||||||
|
|
||||||
const vk::CommandPoolCreateInfo pool_create_info = {
|
const vk::CommandPoolCreateInfo pool_create_info = {
|
||||||
.flags = vk::CommandPoolCreateFlagBits::eTransient |
|
.flags = vk::CommandPoolCreateFlagBits::eTransient |
|
||||||
vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
|
vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
|
||||||
.queueFamilyIndex = instance.GetGraphicsQueueFamilyIndex(),
|
.queueFamilyIndex = instance.GetGraphicsQueueFamilyIndex(),
|
||||||
};
|
};
|
||||||
cmdpool = device.createCommandPoolUnique(pool_create_info);
|
|
||||||
}
|
|
||||||
|
|
||||||
CommandPool::~CommandPool() = default;
|
vk::Device device = instance.GetDevice();
|
||||||
|
pool.handle = device.createCommandPool(pool_create_info);
|
||||||
void CommandPool::Allocate() {
|
|
||||||
// Command buffers are going to be commited, recorded, executed every single usage cycle.
|
|
||||||
// They are also going to be reseted when commited.
|
|
||||||
auto& cmdbuf = cmdbuffers.emplace_back();
|
|
||||||
|
|
||||||
const vk::CommandBufferAllocateInfo buffer_alloc_info = {
|
const vk::CommandBufferAllocateInfo buffer_alloc_info = {
|
||||||
.commandPool = cmdpool.get(),
|
.commandPool = pool.handle,
|
||||||
.level = vk::CommandBufferLevel::ePrimary,
|
.level = vk::CommandBufferLevel::ePrimary,
|
||||||
.commandBufferCount = 1,
|
.commandBufferCount = COMMAND_BUFFER_POOL_SIZE,
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto buffers = device.allocateCommandBuffers(buffer_alloc_info);
|
auto buffers = device.allocateCommandBuffers(buffer_alloc_info);
|
||||||
cmdbuf = buffers[0];
|
std::copy(buffers.begin(), buffers.end(), pool.cmdbufs.begin());
|
||||||
}
|
}
|
||||||
|
|
||||||
vk::CommandBuffer CommandPool::Commit() {
|
vk::CommandBuffer CommandPool::Commit() {
|
||||||
const size_t index = CommitResource();
|
const std::size_t index = CommitResource();
|
||||||
return cmdbuffers[index];
|
const auto pool_index = index / COMMAND_BUFFER_POOL_SIZE;
|
||||||
}
|
const auto sub_index = index % COMMAND_BUFFER_POOL_SIZE;
|
||||||
|
return pools[pool_index].cmdbufs[sub_index];
|
||||||
constexpr size_t MAX_BATCH_SIZE = 8;
|
|
||||||
|
|
||||||
DescriptorPool::DescriptorPool(const Instance& instance, MasterSemaphore* master_semaphore)
|
|
||||||
: ResourcePool{master_semaphore}, device{instance.GetDevice()} {
|
|
||||||
// Ensure we have at least one pool available.
|
|
||||||
CommitResource();
|
|
||||||
}
|
|
||||||
|
|
||||||
DescriptorPool::~DescriptorPool() = default;
|
|
||||||
|
|
||||||
void DescriptorPool::Allocate() {
|
|
||||||
// Descriptor pools are going to be commited and used for descriptor allocation.
|
|
||||||
// When out of memory, a new pool is allocated or an old is reused
|
|
||||||
auto& pool = pools.emplace_back();
|
|
||||||
|
|
||||||
static constexpr std::array<vk::DescriptorPoolSize, 6> pool_sizes = {{
|
|
||||||
{vk::DescriptorType::eUniformBufferDynamic, 64},
|
|
||||||
{vk::DescriptorType::eUniformTexelBuffer, 64},
|
|
||||||
{vk::DescriptorType::eCombinedImageSampler, 4096},
|
|
||||||
{vk::DescriptorType::eSampledImage, 256},
|
|
||||||
{vk::DescriptorType::eStorageImage, 256},
|
|
||||||
{vk::DescriptorType::eStorageBuffer, 32},
|
|
||||||
}};
|
|
||||||
|
|
||||||
const vk::DescriptorPoolCreateInfo descriptor_pool_info = {
|
|
||||||
.maxSets = 4098,
|
|
||||||
.poolSizeCount = static_cast<u32>(pool_sizes.size()),
|
|
||||||
.pPoolSizes = pool_sizes.data(),
|
|
||||||
};
|
|
||||||
|
|
||||||
pool = device.createDescriptorPoolUnique(descriptor_pool_info);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<vk::DescriptorSet> DescriptorPool::Commit(vk::DescriptorSetLayout layout,
|
|
||||||
u32 num_sets) {
|
|
||||||
ASSERT_MSG(num_sets <= MAX_BATCH_SIZE, "Cannot allocate more than {} descriptor sets",
|
|
||||||
MAX_BATCH_SIZE);
|
|
||||||
|
|
||||||
// Fill array with the layout handle
|
|
||||||
std::array<vk::DescriptorSetLayout, MAX_BATCH_SIZE> layouts;
|
|
||||||
layouts.fill(layout);
|
|
||||||
|
|
||||||
vk::DescriptorSetAllocateInfo alloc_info = {
|
|
||||||
.descriptorPool = pools[last_index].get(),
|
|
||||||
.descriptorSetCount = num_sets,
|
|
||||||
.pSetLayouts = layouts.data(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Attempt to allocate the descriptor sets.
|
|
||||||
try {
|
|
||||||
return device.allocateDescriptorSets(alloc_info);
|
|
||||||
} catch (const vk::OutOfPoolMemoryError&) {
|
|
||||||
// If out of memory switch to a new pool.
|
|
||||||
const size_t index = CommitResource();
|
|
||||||
const auto new_pool = pools[index].get();
|
|
||||||
device.resetDescriptorPool(new_pool);
|
|
||||||
alloc_info.descriptorPool = new_pool;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This time the allocation should succeed.
|
|
||||||
return device.allocateDescriptorSets(alloc_info);
|
|
||||||
}
|
|
||||||
|
|
||||||
vk::DescriptorSet DescriptorPool::Commit(vk::DescriptorSetLayout layout) {
|
|
||||||
const auto sets = Commit(layout, 1);
|
|
||||||
return sets[0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 Citra Emulator Project
|
// Copyright 2020 yuzu Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
@@ -13,19 +13,40 @@ namespace Vulkan {
|
|||||||
class Instance;
|
class Instance;
|
||||||
class MasterSemaphore;
|
class MasterSemaphore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles a pool of resources protected by fences. Manages resource overflow allocating more
|
||||||
|
* resources.
|
||||||
|
*/
|
||||||
class ResourcePool {
|
class ResourcePool {
|
||||||
public:
|
public:
|
||||||
explicit ResourcePool(MasterSemaphore* master_semaphore);
|
explicit ResourcePool() = default;
|
||||||
virtual ~ResourcePool();
|
explicit ResourcePool(MasterSemaphore* master_semaphore, std::size_t grow_step);
|
||||||
|
virtual ~ResourcePool() = default;
|
||||||
|
|
||||||
|
ResourcePool& operator=(ResourcePool&&) noexcept = default;
|
||||||
|
ResourcePool(ResourcePool&&) noexcept = default;
|
||||||
|
|
||||||
|
ResourcePool& operator=(const ResourcePool&) = default;
|
||||||
|
ResourcePool(const ResourcePool&) = default;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
s64 CommitResource();
|
std::size_t CommitResource();
|
||||||
virtual void Allocate() = 0;
|
|
||||||
|
/// Called when a chunk of resources have to be allocated.
|
||||||
|
virtual void Allocate(std::size_t begin, std::size_t end) = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Manages pool overflow allocating new resources.
|
||||||
|
std::size_t ManageOverflow();
|
||||||
|
|
||||||
|
/// Allocates a new page of resources.
|
||||||
|
void Grow();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
MasterSemaphore* master_semaphore{nullptr};
|
MasterSemaphore* master_semaphore{nullptr};
|
||||||
s64 last_index = -1; ///< Hint to where the last commited resource was found
|
std::size_t grow_step = 0; ///< Number of new resources created after an overflow
|
||||||
std::vector<u64> ticks; ///< Ticks each resource was last used
|
std::size_t hint_iterator = 0; ///< Hint to where the next free resources is likely to be found
|
||||||
|
std::vector<u64> ticks; ///< Ticks for each resource
|
||||||
};
|
};
|
||||||
|
|
||||||
class CommandPool final : public ResourcePool {
|
class CommandPool final : public ResourcePool {
|
||||||
@@ -33,31 +54,14 @@ public:
|
|||||||
explicit CommandPool(const Instance& instance, MasterSemaphore* master_semaphore);
|
explicit CommandPool(const Instance& instance, MasterSemaphore* master_semaphore);
|
||||||
~CommandPool() override;
|
~CommandPool() override;
|
||||||
|
|
||||||
void Allocate() override;
|
void Allocate(std::size_t begin, std::size_t end) override;
|
||||||
|
|
||||||
vk::CommandBuffer Commit();
|
vk::CommandBuffer Commit();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
vk::Device device;
|
struct Pool;
|
||||||
vk::UniqueCommandPool cmdpool;
|
const Instance& instance;
|
||||||
std::vector<vk::CommandBuffer> cmdbuffers;
|
std::vector<Pool> pools;
|
||||||
};
|
|
||||||
|
|
||||||
class DescriptorPool : public ResourcePool {
|
|
||||||
public:
|
|
||||||
explicit DescriptorPool(const Instance& instance, MasterSemaphore* master_semaphore);
|
|
||||||
~DescriptorPool();
|
|
||||||
|
|
||||||
std::vector<vk::DescriptorSet> Commit(vk::DescriptorSetLayout layout, u32 num_sets);
|
|
||||||
|
|
||||||
vk::DescriptorSet Commit(vk::DescriptorSetLayout layout);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void Allocate() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
vk::Device device;
|
|
||||||
std::vector<vk::UniqueDescriptorPool> pools;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
@@ -56,10 +56,6 @@ public:
|
|||||||
return image_count;
|
return image_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 GetImageIndex() const {
|
|
||||||
return image_index;
|
|
||||||
}
|
|
||||||
|
|
||||||
vk::Extent2D GetExtent() const {
|
vk::Extent2D GetExtent() const {
|
||||||
return extent;
|
return extent;
|
||||||
}
|
}
|
||||||
|
@@ -11,6 +11,7 @@
|
|||||||
#include "video_core/rasterizer_cache/texture_codec.h"
|
#include "video_core/rasterizer_cache/texture_codec.h"
|
||||||
#include "video_core/rasterizer_cache/utils.h"
|
#include "video_core/rasterizer_cache/utils.h"
|
||||||
#include "video_core/renderer_vulkan/pica_to_vk.h"
|
#include "video_core/renderer_vulkan/pica_to_vk.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
|
||||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||||
#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
|
#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
|
||||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||||
@@ -252,9 +253,9 @@ constexpr u64 DOWNLOAD_BUFFER_SIZE = 16_MiB;
|
|||||||
|
|
||||||
TextureRuntime::TextureRuntime(const Instance& instance, Scheduler& scheduler,
|
TextureRuntime::TextureRuntime(const Instance& instance, Scheduler& scheduler,
|
||||||
RenderpassCache& renderpass_cache, DescriptorPool& pool,
|
RenderpassCache& renderpass_cache, DescriptorPool& pool,
|
||||||
u32 num_swapchain_images_)
|
DescriptorSetProvider& texture_provider_, u32 num_swapchain_images_)
|
||||||
: instance{instance}, scheduler{scheduler}, renderpass_cache{renderpass_cache},
|
: instance{instance}, scheduler{scheduler}, renderpass_cache{renderpass_cache},
|
||||||
blit_helper{instance, scheduler, pool, renderpass_cache},
|
texture_provider{texture_provider_}, blit_helper{instance, scheduler, pool, renderpass_cache},
|
||||||
upload_buffer{instance, scheduler, vk::BufferUsageFlagBits::eTransferSrc, UPLOAD_BUFFER_SIZE,
|
upload_buffer{instance, scheduler, vk::BufferUsageFlagBits::eTransferSrc, UPLOAD_BUFFER_SIZE,
|
||||||
BufferType::Upload},
|
BufferType::Upload},
|
||||||
download_buffer{instance, scheduler,
|
download_buffer{instance, scheduler,
|
||||||
@@ -696,6 +697,13 @@ bool TextureRuntime::NeedsConversion(VideoCore::PixelFormat format) const {
|
|||||||
traits.aspect != (vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil);
|
traits.aspect != (vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TextureRuntime::FreeDescriptorSetsWithImage(vk::ImageView image_view) {
|
||||||
|
texture_provider.FreeWithImage(image_view);
|
||||||
|
blit_helper.compute_provider.FreeWithImage(image_view);
|
||||||
|
blit_helper.compute_buffer_provider.FreeWithImage(image_view);
|
||||||
|
blit_helper.two_textures_provider.FreeWithImage(image_view);
|
||||||
|
}
|
||||||
|
|
||||||
Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& params)
|
Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& params)
|
||||||
: SurfaceBase{params}, runtime{&runtime_}, instance{&runtime_.GetInstance()},
|
: SurfaceBase{params}, runtime{&runtime_}, instance{&runtime_.GetInstance()},
|
||||||
scheduler{&runtime_.GetScheduler()}, traits{instance->GetTraits(pixel_format)} {
|
scheduler{&runtime_.GetScheduler()}, traits{instance->GetTraits(pixel_format)} {
|
||||||
@@ -795,6 +803,9 @@ Surface::~Surface() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (const auto& [alloc, image, image_view] : handles) {
|
for (const auto& [alloc, image, image_view] : handles) {
|
||||||
|
if (image_view) {
|
||||||
|
runtime->FreeDescriptorSetsWithImage(*image_view);
|
||||||
|
}
|
||||||
if (image) {
|
if (image) {
|
||||||
vmaDestroyImage(instance->GetAllocator(), image, alloc);
|
vmaDestroyImage(instance->GetAllocator(), image, alloc);
|
||||||
}
|
}
|
||||||
@@ -1547,13 +1558,13 @@ Sampler::Sampler(TextureRuntime& runtime, const VideoCore::SamplerParams& params
|
|||||||
|
|
||||||
Sampler::~Sampler() = default;
|
Sampler::~Sampler() = default;
|
||||||
|
|
||||||
DebugScope::DebugScope(TextureRuntime& runtime, Common::Vec4f color, std::string&& label)
|
DebugScope::DebugScope(TextureRuntime& runtime, Common::Vec4f color, std::string_view label)
|
||||||
: scheduler{runtime.GetScheduler()}, has_debug_tool{
|
: scheduler{runtime.GetScheduler()}, has_debug_tool{
|
||||||
runtime.GetInstance().HasDebuggingToolAttached()} {
|
runtime.GetInstance().HasDebuggingToolAttached()} {
|
||||||
if (!has_debug_tool) {
|
if (!has_debug_tool) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
scheduler.Record([color, label = std::move(label)](vk::CommandBuffer cmdbuf) {
|
scheduler.Record([color, label = std::string(label)](vk::CommandBuffer cmdbuf) {
|
||||||
const vk::DebugUtilsLabelEXT debug_label = {
|
const vk::DebugUtilsLabelEXT debug_label = {
|
||||||
.pLabelName = label.data(),
|
.pLabelName = label.data(),
|
||||||
.color = std::array{color[0], color[1], color[2], color[3]},
|
.color = std::array{color[0], color[1], color[2], color[3]},
|
||||||
|
@@ -42,8 +42,8 @@ class TextureRuntime {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
explicit TextureRuntime(const Instance& instance, Scheduler& scheduler,
|
explicit TextureRuntime(const Instance& instance, Scheduler& scheduler,
|
||||||
RenderpassCache& renderpass_cache,
|
RenderpassCache& renderpass_cache, DescriptorPool& pool,
|
||||||
u32 num_swapchain_images);
|
DescriptorSetProvider& texture_provider, u32 num_swapchain_images);
|
||||||
~TextureRuntime();
|
~TextureRuntime();
|
||||||
|
|
||||||
const Instance& GetInstance() const {
|
const Instance& GetInstance() const {
|
||||||
@@ -85,6 +85,9 @@ public:
|
|||||||
/// Returns true if the provided pixel format needs convertion
|
/// Returns true if the provided pixel format needs convertion
|
||||||
bool NeedsConversion(VideoCore::PixelFormat format) const;
|
bool NeedsConversion(VideoCore::PixelFormat format) const;
|
||||||
|
|
||||||
|
/// Removes any descriptor sets that contain the provided image view.
|
||||||
|
void FreeDescriptorSetsWithImage(vk::ImageView image_view);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Clears a partial texture rect using a clear rectangle
|
/// Clears a partial texture rect using a clear rectangle
|
||||||
void ClearTextureWithRenderpass(Surface& surface, const VideoCore::TextureClear& clear);
|
void ClearTextureWithRenderpass(Surface& surface, const VideoCore::TextureClear& clear);
|
||||||
@@ -93,6 +96,7 @@ private:
|
|||||||
const Instance& instance;
|
const Instance& instance;
|
||||||
Scheduler& scheduler;
|
Scheduler& scheduler;
|
||||||
RenderpassCache& renderpass_cache;
|
RenderpassCache& renderpass_cache;
|
||||||
|
DescriptorSetProvider& texture_provider;
|
||||||
BlitHelper blit_helper;
|
BlitHelper blit_helper;
|
||||||
StreamBuffer upload_buffer;
|
StreamBuffer upload_buffer;
|
||||||
StreamBuffer download_buffer;
|
StreamBuffer download_buffer;
|
||||||
@@ -273,7 +277,7 @@ public:
|
|||||||
explicit DebugScope(TextureRuntime& runtime, Common::Vec4f color,
|
explicit DebugScope(TextureRuntime& runtime, Common::Vec4f color,
|
||||||
fmt::format_string<T...> format, T... args)
|
fmt::format_string<T...> format, T... args)
|
||||||
: DebugScope{runtime, color, fmt::format(format, std::forward<T>(args)...)} {}
|
: DebugScope{runtime, color, fmt::format(format, std::forward<T>(args)...)} {}
|
||||||
explicit DebugScope(TextureRuntime& runtime, Common::Vec4f color, std::string&& label);
|
explicit DebugScope(TextureRuntime& runtime, Common::Vec4f color, std::string_view label);
|
||||||
~DebugScope();
|
~DebugScope();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Reference in New Issue
Block a user