mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-10-06 06:00:12 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 355f9d84ef | |||
| d16735f5b6 | |||
| d3550c4571 | |||
| 963119e7cc | |||
| 2a7e9b53b1 | |||
| 8e2d26c272 | |||
| f8b7651104 | |||
| 3f78512c83 | |||
| a82f31a310 | |||
| 815325ccec |
+6
-1
@@ -79,7 +79,7 @@
|
|||||||
"name": "ffmpeg",
|
"name": "ffmpeg",
|
||||||
"package": "FFmpeg",
|
"package": "FFmpeg",
|
||||||
"repo": "crueter-ci/FFmpeg",
|
"repo": "crueter-ci/FFmpeg",
|
||||||
"version": "9.0.1-1788120736-bf1b838f2a"
|
"version": "9.0.2-1790726865-946fcce07b"
|
||||||
},
|
},
|
||||||
"fmt": {
|
"fmt": {
|
||||||
"hash": "f0da82c545b01692e9fd30fdfb613dbb8dd9716983dcd0ff19ac2a8d36f74beb5540ef38072fdecc1e34191b3682a8542ecbf3a61ef287dbba0a2679d4e023f2",
|
"hash": "f0da82c545b01692e9fd30fdfb613dbb8dd9716983dcd0ff19ac2a8d36f74beb5540ef38072fdecc1e34191b3682a8542ecbf3a61ef287dbba0a2679d4e023f2",
|
||||||
@@ -333,6 +333,11 @@
|
|||||||
"repo": "herumi/xbyak",
|
"repo": "herumi/xbyak",
|
||||||
"version": "v7.40.1"
|
"version": "v7.40.1"
|
||||||
},
|
},
|
||||||
|
"zbic": {
|
||||||
|
"hash": "fbe2f37986377d7f0d96ae3224c80b5971df6e8b6961f68061f1975ebb2e8cb78f07b90f014b007be4023dbf5513581504e7f7d61a5237cb2a8a7a61ae11e482",
|
||||||
|
"repo": "kinnay/zbic",
|
||||||
|
"version": "11b08f2712264bbed731545085cbd9702096ceb7"
|
||||||
|
},
|
||||||
"zlib": {
|
"zlib": {
|
||||||
"hash": "16fea4df307a68cf0035858abe2fd550250618a97590e202037acd18a666f57afc10f8836cbbd472d54a0e76539d0e558cb26f059d53de52ff90634bbf4f47d4",
|
"hash": "16fea4df307a68cf0035858abe2fd550250618a97590e202037acd18a666f57afc10f8836cbbd472d54a0e76539d0e558cb26f059d53de52ff90634bbf4f47d4",
|
||||||
"min_version": "1.2",
|
"min_version": "1.2",
|
||||||
|
|||||||
Vendored
+455
-473
File diff suppressed because it is too large
Load Diff
Vendored
+439
-456
File diff suppressed because it is too large
Load Diff
Vendored
+439
-456
File diff suppressed because it is too large
Load Diff
Vendored
+439
-456
File diff suppressed because it is too large
Load Diff
Vendored
+440
-463
File diff suppressed because it is too large
Load Diff
Vendored
+439
-456
File diff suppressed because it is too large
Load Diff
Vendored
+439
-456
File diff suppressed because it is too large
Load Diff
Vendored
+439
-456
File diff suppressed because it is too large
Load Diff
Vendored
+439
-456
File diff suppressed because it is too large
Load Diff
Vendored
+439
-456
File diff suppressed because it is too large
Load Diff
Vendored
+439
-456
File diff suppressed because it is too large
Load Diff
Vendored
+439
-456
File diff suppressed because it is too large
Load Diff
Vendored
+439
-456
File diff suppressed because it is too large
Load Diff
Vendored
+440
-463
File diff suppressed because it is too large
Load Diff
Vendored
+439
-456
File diff suppressed because it is too large
Load Diff
Vendored
+439
-456
File diff suppressed because it is too large
Load Diff
Vendored
+439
-456
File diff suppressed because it is too large
Load Diff
Vendored
+439
-456
File diff suppressed because it is too large
Load Diff
Vendored
+439
-456
File diff suppressed because it is too large
Load Diff
Vendored
+440
-463
File diff suppressed because it is too large
Load Diff
Vendored
+440
-463
File diff suppressed because it is too large
Load Diff
Vendored
+439
-456
File diff suppressed because it is too large
Load Diff
Vendored
+439
-456
File diff suppressed because it is too large
Load Diff
Vendored
+439
-456
File diff suppressed because it is too large
Load Diff
Vendored
+439
-456
File diff suppressed because it is too large
Load Diff
Vendored
+440
-463
File diff suppressed because it is too large
Load Diff
Vendored
+439
-456
File diff suppressed because it is too large
Load Diff
Vendored
+5
@@ -48,6 +48,11 @@ if (NOT TARGET stb::headers)
|
|||||||
add_library(stb::headers ALIAS stb)
|
add_library(stb::headers ALIAS stb)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
AddJsonPackage(NAME zbic DOWNLOAD_ONLY)
|
||||||
|
set(ZBIC_INCLUDE_DIR
|
||||||
|
"${zbic_SOURCE_DIR}/src"
|
||||||
|
PARENT_SCOPE)
|
||||||
|
|
||||||
# ItaniumDemangle (Windows only)
|
# ItaniumDemangle (Windows only)
|
||||||
if (WIN32 AND NOT TARGET LLVM::Demangle)
|
if (WIN32 AND NOT TARGET LLVM::Demangle)
|
||||||
add_library(demangle demangle/ItaniumDemangle.cpp)
|
add_library(demangle demangle/ItaniumDemangle.cpp)
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import org.yuzu.yuzu_emu.model.Driver.Companion.toDriver
|
|||||||
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
||||||
import org.yuzu.yuzu_emu.NativeLibrary
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
|
import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
|
||||||
|
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@@ -146,11 +147,11 @@ class DriverViewModel : ViewModel() {
|
|||||||
private fun wipeGameShaders(game: Game) {
|
private fun wipeGameShaders(game: Game) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val externalFilesDir = YuzuApplication.appContext.getExternalFilesDir(null)
|
val userDirectory = DirectoryInitialization.userDirectory
|
||||||
?: return@withContext
|
?: return@withContext
|
||||||
val shaderDir = File(
|
val shaderDir = File(
|
||||||
externalFilesDir.absolutePath +
|
userDirectory +
|
||||||
"/shader/" + game.settingsName.lowercase()
|
"/cache/shader/" + game.settingsName.lowercase()
|
||||||
)
|
)
|
||||||
if (shaderDir.exists()) {
|
if (shaderDir.exists()) {
|
||||||
shaderDir.deleteRecursively()
|
shaderDir.deleteRecursively()
|
||||||
|
|||||||
@@ -137,6 +137,8 @@ add_library(
|
|||||||
uuid.cpp
|
uuid.cpp
|
||||||
uuid.h
|
uuid.h
|
||||||
vector_math.h
|
vector_math.h
|
||||||
|
zbic_compression.cpp
|
||||||
|
zbic_compression.h
|
||||||
zstd_compression.cpp
|
zstd_compression.cpp
|
||||||
zstd_compression.h
|
zstd_compression.h
|
||||||
fs/ryujinx_compat.h fs/ryujinx_compat.cpp
|
fs/ryujinx_compat.h fs/ryujinx_compat.cpp
|
||||||
@@ -147,6 +149,10 @@ add_library(
|
|||||||
net/net.h net/net.cpp
|
net/net.h net/net.cpp
|
||||||
container/unordered_map.h container/unordered_set.h)
|
container/unordered_map.h container/unordered_set.h)
|
||||||
|
|
||||||
|
set_source_files_properties(zbic_compression.cpp PROPERTIES
|
||||||
|
INCLUDE_DIRECTORIES "${ZBIC_INCLUDE_DIR}"
|
||||||
|
COMPILE_OPTIONS "$<$<CXX_COMPILER_ID:Clang,GNU>:-Wno-unused-function;-Wno-missing-declarations;-Wno-shadow>")
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_sources(common PRIVATE windows/timer_resolution.cpp
|
target_sources(common PRIVATE windows/timer_resolution.cpp
|
||||||
windows/timer_resolution.h)
|
windows/timer_resolution.h)
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ ENUM(ScalingFilter, NearestNeighbor, Bilinear, Bicubic, Gaussian, Lanczos, Scale
|
|||||||
ENUM(AntiAliasing, None, Fxaa, Smaa);
|
ENUM(AntiAliasing, None, Fxaa, Smaa);
|
||||||
ENUM(AspectRatio, R16_9, R4_3, R21_9, R16_10, Stretch);
|
ENUM(AspectRatio, R16_9, R4_3, R21_9, R16_10, Stretch);
|
||||||
ENUM(ConsoleMode, Handheld, Docked);
|
ENUM(ConsoleMode, Handheld, Docked);
|
||||||
ENUM(AppletMode, HLE, LLE);
|
ENUM(AppletMode, HLE, LLE, Disabled);
|
||||||
ENUM(SpirvOptimizeMode, Never, OnLoad, Always);
|
ENUM(SpirvOptimizeMode, Never, OnLoad, Always);
|
||||||
ENUM(GpuClock, Normal, Boost, Overclock)
|
ENUM(GpuClock, Normal, Boost, Overclock)
|
||||||
ENUM(GpuUnswizzleSize, VerySmall, Small, Normal, Large, VeryLarge)
|
ENUM(GpuUnswizzleSize, VerySmall, Small, Normal, Large, VeryLarge)
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "common/zbic_compression.h"
|
||||||
|
|
||||||
|
#define ZSTD_ZBIC_SUPPORT 1
|
||||||
|
#define ZSTDLIB_VISIBLE static
|
||||||
|
#define ZSTDLIB_HIDDEN static
|
||||||
|
#define ZSTDERRORLIB_VISIBLE static
|
||||||
|
#define ZSTDERRORLIB_HIDDEN static
|
||||||
|
#undef ZSTD_MULTITHREAD
|
||||||
|
|
||||||
|
#if defined(__ANDROID__)
|
||||||
|
#undef _GNU_SOURCE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "zstd.h"
|
||||||
|
#define g_ZSTD_threading_useless_symbol g_ZSTD_zbic_threading_useless_symbol
|
||||||
|
#include "zstd.c"
|
||||||
|
#undef g_ZSTD_threading_useless_symbol
|
||||||
|
|
||||||
|
namespace Common::Compression {
|
||||||
|
|
||||||
|
bool IsZBIC(std::span<const u8> src) {
|
||||||
|
if (src.size() < sizeof(u32)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
u32 magic = 0;
|
||||||
|
std::memcpy(&magic, src.data(), sizeof(u32));
|
||||||
|
return magic == ZSTD_MAGICNUMBER; // 0x4349425A ("ZBIC")
|
||||||
|
}
|
||||||
|
|
||||||
|
int DecompressDataZBIC(std::span<u8> dst, std::span<const u8> src) {
|
||||||
|
if (dst.empty() || src.empty()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
const size_t res = ZSTD_decompress(dst.data(), dst.size(), src.data(), src.size());
|
||||||
|
if (ZSTD_isError(res)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return static_cast<int>(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Common::Compression
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <span>
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace Common::Compression {
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsZBIC(std::span<const u8> src);
|
||||||
|
|
||||||
|
[[nodiscard]] int DecompressDataZBIC(std::span<u8> dst, std::span<const u8> src);
|
||||||
|
|
||||||
|
} // namespace Common::Compression
|
||||||
@@ -250,9 +250,10 @@ std::shared_ptr<FrontendApplet> FrontendAppletHolder::GetApplet(std::shared_ptr<
|
|||||||
return std::make_shared<PhotoViewer>(system, applet, mode, *frontend.photo_viewer);
|
return std::make_shared<PhotoViewer>(system, applet, mode, *frontend.photo_viewer);
|
||||||
case AppletId::NetConnect:
|
case AppletId::NetConnect:
|
||||||
return std::make_shared<NetConnect>(system, applet, mode, *frontend.net_connect);
|
return std::make_shared<NetConnect>(system, applet, mode, *frontend.net_connect);
|
||||||
|
case AppletId::None:
|
||||||
|
return nullptr;
|
||||||
default:
|
default:
|
||||||
LOG_ERROR(Service_AM, "No backend implementation exists for applet_id={:02X} program_id={:016X}"
|
UNIMPLEMENTED_MSG("No frontend implementation exists for applet_id={:02X} program_id={:016X}! Using stub applet.", static_cast<u8>(id), applet->program_id);
|
||||||
"Falling back to stub applet", static_cast<u8>(id), applet->program_id);
|
|
||||||
return std::make_shared<StubApplet>(system, applet, id, mode);
|
return std::make_shared<StubApplet>(system, applet, id, mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,10 @@
|
|||||||
#include "core/hle/service/ro/ro_types.h"
|
#include "core/hle/service/ro/ro_types.h"
|
||||||
#include "core/hle/service/server_manager.h"
|
#include "core/hle/service/server_manager.h"
|
||||||
#include "core/hle/service/service.h"
|
#include "core/hle/service/service.h"
|
||||||
|
#ifdef HAS_NCE
|
||||||
|
#include "core/arm/nce/patcher.h"
|
||||||
|
#include "core/hle/kernel/k_shared_memory.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Service::RO {
|
namespace Service::RO {
|
||||||
|
|
||||||
@@ -386,7 +390,7 @@ public:
|
|||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
Result MapManualLoadModuleMemory(u64* out_address, size_t context_id, u64 nro_address,
|
Result MapManualLoadModuleMemory(Kernel::KernelCore& kernel, u64* out_address, size_t context_id, u64 nro_address,
|
||||||
u64 nro_size, u64 bss_address, u64 bss_size) {
|
u64 nro_size, u64 bss_address, u64 bss_size) {
|
||||||
// Get context.
|
// Get context.
|
||||||
ProcessContext* context = this->GetContextById(context_id);
|
ProcessContext* context = this->GetContextById(context_id);
|
||||||
@@ -421,7 +425,31 @@ public:
|
|||||||
R_TRY(context->ValidateNro(std::addressof(nro_info->module_id), std::addressof(rx_size),
|
R_TRY(context->ValidateNro(std::addressof(nro_info->module_id), std::addressof(rx_size),
|
||||||
std::addressof(ro_size), std::addressof(rw_size),
|
std::addressof(ro_size), std::addressof(rw_size),
|
||||||
nro_info->base_address, nro_size, bss_size));
|
nro_info->base_address, nro_size, bss_size));
|
||||||
|
#ifdef HAS_NCE
|
||||||
|
if (Settings::IsNceEnabled()) {
|
||||||
|
auto* process = context->GetProcess();
|
||||||
|
auto& memory = process->GetMemory();
|
||||||
|
|
||||||
|
std::vector<u8> image(total_size);
|
||||||
|
memory.ReadBlock(nro_info->base_address, image.data(), rx_size);
|
||||||
|
|
||||||
|
Kernel::CodeSet::Segment code{.size = static_cast<u32>(rx_size)};
|
||||||
|
Core::NCE::Patcher patch;
|
||||||
|
patch.PatchText(image, code);
|
||||||
|
patch.RelocateAndCopy(nro_info->base_address, code, image, nullptr);
|
||||||
|
|
||||||
|
const u64 patch_address = nro_info->base_address + total_size;
|
||||||
|
const size_t patch_size = patch.GetSectionSize();
|
||||||
|
constexpr auto permission = Kernel::Svc::MemoryPermission::ReadExecute;
|
||||||
|
|
||||||
|
auto* patch_memory = Kernel::KSharedMemory::Create(kernel);
|
||||||
|
R_TRY(patch_memory->Initialize(kernel, kernel.System().DeviceMemory(), process, permission, permission, patch_size));
|
||||||
|
std::memcpy(patch_memory->GetPointer(), image.data() + total_size, patch_size);
|
||||||
|
R_TRY(process->AddSharedMemory(kernel, patch_memory, patch_address, patch_size));
|
||||||
|
R_TRY(patch_memory->Map(*process, patch_address, patch_size, permission));
|
||||||
|
memory.WriteBlock(nro_info->base_address, image.data(), rx_size);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
// Set NRO perms.
|
// Set NRO perms.
|
||||||
R_TRY(SetNroPerms(context->GetProcess(), nro_info->base_address, rx_size, ro_size,
|
R_TRY(SetNroPerms(context->GetProcess(), nro_info->base_address, rx_size, ro_size,
|
||||||
rw_size + bss_size));
|
rw_size + bss_size));
|
||||||
@@ -531,7 +559,7 @@ public:
|
|||||||
Result MapManualLoadModuleMemory(Out<u64> out_load_address, ClientProcessId client_pid,
|
Result MapManualLoadModuleMemory(Out<u64> out_load_address, ClientProcessId client_pid,
|
||||||
u64 nro_address, u64 nro_size, u64 bss_address, u64 bss_size) {
|
u64 nro_address, u64 nro_size, u64 bss_address, u64 bss_size) {
|
||||||
R_TRY(m_ro->ValidateProcess(m_context_id, *client_pid));
|
R_TRY(m_ro->ValidateProcess(m_context_id, *client_pid));
|
||||||
R_RETURN(m_ro->MapManualLoadModuleMemory(out_load_address.Get(), m_context_id, nro_address,
|
R_RETURN(m_ro->MapManualLoadModuleMemory(system.Kernel(), out_load_address.Get(), m_context_id, nro_address,
|
||||||
nro_size, bss_address, bss_size));
|
nro_size, bss_address, bss_size));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+31
-4
@@ -7,12 +7,14 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <span>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "common/common_funcs.h"
|
#include "common/common_funcs.h"
|
||||||
#include "common/hex_util.h"
|
#include "common/hex_util.h"
|
||||||
#include "common/logging.h"
|
#include "common/logging.h"
|
||||||
#include "common/lz4_compression.h"
|
#include "common/lz4_compression.h"
|
||||||
|
#include "common/zbic_compression.h"
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
#include "common/swap.h"
|
#include "common/swap.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
@@ -104,11 +106,36 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
|
|||||||
for (std::size_t i = 0; i < nso_header.segments.size(); ++i) {
|
for (std::size_t i = 0; i < nso_header.segments.size(); ++i) {
|
||||||
nso_file.Read(compressed_data.data(), nso_header.segments_compressed_size[i], nso_header.segments[i].offset);
|
nso_file.Read(compressed_data.data(), nso_header.segments_compressed_size[i], nso_header.segments[i].offset);
|
||||||
if (nso_header.IsSegmentCompressed(i)) {
|
if (nso_header.IsSegmentCompressed(i)) {
|
||||||
int r = Common::Compression::DecompressDataLZ4(decompressed_size.data(), nso_header.segments[i].size, compressed_data.data(), nso_header.segments_compressed_size[i]);
|
if (nso_header.IsZBICCompressed()) {
|
||||||
ASSERT(r == int(nso_header.segments[i].size));
|
// ZBIC compression
|
||||||
std::memcpy(codeset.memory.data() + module_start + nso_header.segments[i].location, decompressed_size.data(), nso_header.segments[i].size);
|
const int r = Common::Compression::DecompressDataZBIC(
|
||||||
|
std::span<u8>{decompressed_size}.first(nso_header.segments[i].size),
|
||||||
|
std::span<const u8>{compressed_data}.first(nso_header.segments_compressed_size[i])
|
||||||
|
);
|
||||||
|
ASSERT(r > 0);
|
||||||
|
} else {
|
||||||
|
// LZ4 compression
|
||||||
|
int r = Common::Compression::DecompressDataLZ4(
|
||||||
|
decompressed_size.data(),
|
||||||
|
nso_header.segments[i].size,
|
||||||
|
compressed_data.data(),
|
||||||
|
nso_header.segments_compressed_size[i]
|
||||||
|
);
|
||||||
|
ASSERT(r == int(nso_header.segments[i].size));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::memcpy(
|
||||||
|
codeset.memory.data() + module_start + nso_header.segments[i].location,
|
||||||
|
decompressed_size.data(),
|
||||||
|
nso_header.segments[i].size
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
std::memcpy(codeset.memory.data() + module_start + nso_header.segments[i].location, compressed_data.data(), nso_header.segments[i].size);
|
// Not compressed
|
||||||
|
std::memcpy(
|
||||||
|
codeset.memory.data() + module_start + nso_header.segments[i].location,
|
||||||
|
compressed_data.data(),
|
||||||
|
nso_header.segments[i].size
|
||||||
|
);
|
||||||
}
|
}
|
||||||
codeset.segments[i].addr = module_start + nso_header.segments[i].location;
|
codeset.segments[i].addr = module_start + nso_header.segments[i].location;
|
||||||
codeset.segments[i].offset = module_start + nso_header.segments[i].location;
|
codeset.segments[i].offset = module_start + nso_header.segments[i].location;
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -58,6 +61,9 @@ struct NSOHeader {
|
|||||||
std::array<SHA256Hash, 3> segment_hashes;
|
std::array<SHA256Hash, 3> segment_hashes;
|
||||||
|
|
||||||
bool IsSegmentCompressed(size_t segment_num) const;
|
bool IsSegmentCompressed(size_t segment_num) const;
|
||||||
|
bool IsZBICCompressed() const {
|
||||||
|
return ((flags >> 7) & 1) != 0;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
static_assert(sizeof(NSOHeader) == 0x100, "NSOHeader has incorrect size.");
|
static_assert(sizeof(NSOHeader) == 0x100, "NSOHeader has incorrect size.");
|
||||||
static_assert(std::is_trivially_copyable_v<NSOHeader>, "NSOHeader must be trivially copyable.");
|
static_assert(std::is_trivially_copyable_v<NSOHeader>, "NSOHeader must be trivially copyable.");
|
||||||
|
|||||||
+1
-1
@@ -757,7 +757,7 @@ struct Memory::Impl {
|
|||||||
};
|
};
|
||||||
auto& gpu = system.GPU();
|
auto& gpu = system.GPU();
|
||||||
gpu_device_memory->ApplyOpOnPointer(
|
gpu_device_memory->ApplyOpOnPointer(
|
||||||
p, scratch_buffers[core], [&](DAddr address) { gpu.InvalidateRegion(address, size); });
|
p, scratch_buffers[core], [&](DAddr address) { gpu.InvalidateRegion(address, size, true); });
|
||||||
}
|
}
|
||||||
|
|
||||||
Core::System& system;
|
Core::System& system;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||||
@@ -83,21 +83,20 @@ void Joycons::ScanThread(std::stop_token stop_token) {
|
|||||||
constexpr u16 nintendo_vendor_id = 0x057e;
|
constexpr u16 nintendo_vendor_id = 0x057e;
|
||||||
Common::SetCurrentThreadName("JoyconScanThread");
|
Common::SetCurrentThreadName("JoyconScanThread");
|
||||||
|
|
||||||
auto last_ret = 0u;
|
|
||||||
do {
|
do {
|
||||||
auto ret = SDL_hid_device_change_count();
|
SDL_hid_device_info* devs = SDL_hid_enumerate(nintendo_vendor_id, 0x0);
|
||||||
if (last_ret == 0u || last_ret != ret) {
|
SDL_hid_device_info* cur_dev = devs;
|
||||||
last_ret = ret;
|
|
||||||
SDL_hid_device_info* devs = SDL_hid_enumerate(nintendo_vendor_id, 0x0);
|
while (cur_dev) {
|
||||||
for (auto* cur_dev = devs; cur_dev; cur_dev = cur_dev->next) {
|
if (IsDeviceNew(cur_dev)) {
|
||||||
if (IsDeviceNew(cur_dev)) {
|
LOG_DEBUG(Input, "Device Found,type : {:04X} {:04X}", cur_dev->vendor_id,
|
||||||
LOG_DEBUG(Input, "Device Found,type : {:04X} {:04X}", cur_dev->vendor_id, cur_dev->product_id);
|
cur_dev->product_id);
|
||||||
RegisterNewDevice(cur_dev);
|
RegisterNewDevice(cur_dev);
|
||||||
}
|
|
||||||
cur_dev = cur_dev->next;
|
|
||||||
}
|
}
|
||||||
SDL_hid_free_enumeration(devs);
|
cur_dev = cur_dev->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SDL_hid_free_enumeration(devs);
|
||||||
} while (Common::StoppableTimedWait(stop_token, std::chrono::seconds{5}));
|
} while (Common::StoppableTimedWait(stop_token, std::chrono::seconds{5}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -342,9 +342,6 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
|
|||||||
"it bypasses such prompts and directly exits the emulation."));
|
"it bypasses such prompts and directly exits the emulation."));
|
||||||
INSERT(UISettings, hide_mouse, tr("Hide mouse on inactivity"),
|
INSERT(UISettings, hide_mouse, tr("Hide mouse on inactivity"),
|
||||||
tr("Hides the mouse after 2.5s of inactivity."));
|
tr("Hides the mouse after 2.5s of inactivity."));
|
||||||
INSERT(UISettings, controller_applet_disabled, tr("Disable controller applet"),
|
|
||||||
tr("Forcibly disables the use of the controller applet in emulated programs.\n"
|
|
||||||
"When a program attempts to open the controller applet, it is immediately closed."));
|
|
||||||
INSERT(UISettings, check_for_updates, tr("Check for updates"),
|
INSERT(UISettings, check_for_updates, tr("Check for updates"),
|
||||||
tr("Whether or not to check for updates upon startup."));
|
tr("Whether or not to check for updates upon startup."));
|
||||||
|
|
||||||
@@ -380,6 +377,7 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent) {
|
|||||||
{
|
{
|
||||||
PAIR(AppletMode, HLE, tr("Custom frontend")),
|
PAIR(AppletMode, HLE, tr("Custom frontend")),
|
||||||
PAIR(AppletMode, LLE, tr("Real applet")),
|
PAIR(AppletMode, LLE, tr("Real applet")),
|
||||||
|
PAIR(AppletMode, Disabled, tr("Disabled")),
|
||||||
}});
|
}});
|
||||||
|
|
||||||
translations->insert({Settings::EnumMetadata<Settings::SpirvOptimizeMode>::Index(),
|
translations->insert({Settings::EnumMetadata<Settings::SpirvOptimizeMode>::Index(),
|
||||||
|
|||||||
@@ -127,8 +127,6 @@ struct Values {
|
|||||||
Setting<bool> hide_mouse{
|
Setting<bool> hide_mouse{
|
||||||
linkage, true, "hideInactiveMouse", Category::UiGeneral, Settings::Specialization::Default,
|
linkage, true, "hideInactiveMouse", Category::UiGeneral, Settings::Specialization::Default,
|
||||||
true, true};
|
true, true};
|
||||||
Setting<bool> controller_applet_disabled{linkage, false, "disableControllerApplet",
|
|
||||||
Category::UiGeneral};
|
|
||||||
// Set when Vulkan is known to crash the application
|
// Set when Vulkan is known to crash the application
|
||||||
bool has_broken_vulkan = false;
|
bool has_broken_vulkan = false;
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ struct HostTranslateInfo {
|
|||||||
///< passthrough shaders
|
///< passthrough shaders
|
||||||
bool support_conditional_barrier{}; ///< True when the device supports barriers in conditional
|
bool support_conditional_barrier{}; ///< True when the device supports barriers in conditional
|
||||||
///< control flow
|
///< control flow
|
||||||
|
bool support_attribute_texture_handle{}; ///< True when the host binds texture handles that
|
||||||
|
///< arrive as vertex attributes
|
||||||
|
|
||||||
void ApplyDescriptorLimitPolicy() noexcept {
|
void ApplyDescriptorLimitPolicy() noexcept {
|
||||||
if (min_ssbo_alignment == 0) {
|
if (min_ssbo_alignment == 0) {
|
||||||
|
|||||||
@@ -268,6 +268,10 @@ static inline u32 ReadCbufCached(Environment& env, u32 index, u32 offset) {
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline bool IsAttributeHandle(const ConstBufferAddr& cbuf) {
|
||||||
|
return cbuf.index == ATTRIBUTE_HANDLE_CBUF_INDEX;
|
||||||
|
}
|
||||||
|
|
||||||
static inline u32 GetTextureHandleCached(Environment& env, const ConstBufferAddr& cbuf) {
|
static inline u32 GetTextureHandleCached(Environment& env, const ConstBufferAddr& cbuf) {
|
||||||
// Must all be uniquely different variables
|
// Must all be uniquely different variables
|
||||||
// If has secondary, then it will be cbuf.secondary_{index|offset}, else its 0.
|
// If has secondary, then it will be cbuf.secondary_{index|offset}, else its 0.
|
||||||
@@ -287,14 +291,23 @@ static inline u32 GetTextureHandleCached(Environment& env, const ConstBufferAddr
|
|||||||
|
|
||||||
// Cached variants of existing helpers
|
// Cached variants of existing helpers
|
||||||
static inline TextureType ReadTextureTypeCached(Environment& env, const ConstBufferAddr& cbuf) {
|
static inline TextureType ReadTextureTypeCached(Environment& env, const ConstBufferAddr& cbuf) {
|
||||||
|
if (IsAttributeHandle(cbuf)) {
|
||||||
|
return TextureType::Color2D;
|
||||||
|
}
|
||||||
return env.ReadTextureType(GetTextureHandleCached(env, cbuf));
|
return env.ReadTextureType(GetTextureHandleCached(env, cbuf));
|
||||||
}
|
}
|
||||||
static inline TexturePixelFormat ReadTexturePixelFormatCached(Environment& env,
|
static inline TexturePixelFormat ReadTexturePixelFormatCached(Environment& env,
|
||||||
const ConstBufferAddr& cbuf) {
|
const ConstBufferAddr& cbuf) {
|
||||||
|
if (IsAttributeHandle(cbuf)) {
|
||||||
|
return TexturePixelFormat::A8B8G8R8_UNORM;
|
||||||
|
}
|
||||||
return env.ReadTexturePixelFormat(GetTextureHandleCached(env, cbuf));
|
return env.ReadTexturePixelFormat(GetTextureHandleCached(env, cbuf));
|
||||||
}
|
}
|
||||||
static inline bool IsTexturePixelFormatIntegerCached(Environment& env,
|
static inline bool IsTexturePixelFormatIntegerCached(Environment& env,
|
||||||
const ConstBufferAddr& cbuf) {
|
const ConstBufferAddr& cbuf) {
|
||||||
|
if (IsAttributeHandle(cbuf)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return env.IsTexturePixelFormatInteger(GetTextureHandleCached(env, cbuf));
|
return env.IsTexturePixelFormatInteger(GetTextureHandleCached(env, cbuf));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -466,15 +479,43 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environme
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsAttributeSourcedHandle(const IR::Value& handle) {
|
||||||
|
if (handle.IsImmediate()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const IR::Inst* inst{handle.InstRecursive()};
|
||||||
|
if (inst->GetOpcode() == IR::Opcode::BitCastU32F32 && !inst->Arg(0).IsImmediate()) {
|
||||||
|
inst = inst->Arg(0).InstRecursive();
|
||||||
|
}
|
||||||
|
return inst->GetOpcode() == IR::Opcode::GetAttribute ||
|
||||||
|
inst->GetOpcode() == IR::Opcode::GetAttributeU32;
|
||||||
|
}
|
||||||
|
|
||||||
TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst, const HostTranslateInfo& host_info) {
|
TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst, const HostTranslateInfo& host_info) {
|
||||||
ConstBufferAddr addr;
|
ConstBufferAddr addr;
|
||||||
if (IsBindless(inst)) {
|
if (IsBindless(inst)) {
|
||||||
const std::optional<ConstBufferAddr> track_addr{TrackCached(inst.Arg(0), env, host_info)};
|
const std::optional<ConstBufferAddr> track_addr{TrackCached(inst.Arg(0), env, host_info)};
|
||||||
|
|
||||||
if (!track_addr) {
|
if (track_addr) {
|
||||||
throw NotImplementedException("Failed to track bindless texture constant buffer");
|
|
||||||
} else {
|
|
||||||
addr = *track_addr;
|
addr = *track_addr;
|
||||||
|
} else if (host_info.support_attribute_texture_handle &&
|
||||||
|
IsAttributeSourcedHandle(inst.Arg(0))) {
|
||||||
|
// The handle travels with the vertices (sprite batches pick the texture per quad), so
|
||||||
|
// there is no constant buffer to read it from. The host binds the handle of each run
|
||||||
|
// of vertices that share one.
|
||||||
|
addr = ConstBufferAddr{
|
||||||
|
.index = ATTRIBUTE_HANDLE_CBUF_INDEX,
|
||||||
|
.offset = 0,
|
||||||
|
.shift_left = 0,
|
||||||
|
.secondary_index = 0,
|
||||||
|
.secondary_offset = 0,
|
||||||
|
.secondary_shift_left = 0,
|
||||||
|
.dynamic_offset = {},
|
||||||
|
.count = 1,
|
||||||
|
.has_secondary = false,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw NotImplementedException("Failed to track bindless texture constant buffer");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
addr = ConstBufferAddr{
|
addr = ConstBufferAddr{
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ enum class TextureType : u32 {
|
|||||||
};
|
};
|
||||||
constexpr u32 NUM_TEXTURE_TYPES = 9;
|
constexpr u32 NUM_TEXTURE_TYPES = 9;
|
||||||
|
|
||||||
|
/// Constant buffer index marking a texture handle that the shader receives as a vertex attribute
|
||||||
|
/// instead of reading it from a constant buffer. The host supplies the handle of each draw run.
|
||||||
|
constexpr u32 ATTRIBUTE_HANDLE_CBUF_INDEX = 0xFFFF;
|
||||||
|
|
||||||
enum class TexturePixelFormat {
|
enum class TexturePixelFormat {
|
||||||
A8B8G8R8_UNORM,
|
A8B8G8R8_UNORM,
|
||||||
A8B8G8R8_SNORM,
|
A8B8G8R8_SNORM,
|
||||||
|
|||||||
+13
-3
@@ -226,7 +226,17 @@ struct GPU::Impl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Notify rasterizer that any caches of the specified region should be invalidated
|
/// Notify rasterizer that any caches of the specified region should be invalidated
|
||||||
void InvalidateRegion(DAddr addr, u64 size) {
|
void InvalidateRegion(DAddr addr, u64 size, bool preserve_gpu_writes) {
|
||||||
|
VideoCore::RasterizerInterface* rasterizer = renderer->ReadRasterizer();
|
||||||
|
if (preserve_gpu_writes && rasterizer->MustFlushRegion(addr, size, VideoCommon::CacheType::BufferCache)) {
|
||||||
|
const u64 fence = RequestSyncOperation([rasterizer, addr, size] {
|
||||||
|
rasterizer->FlushRegion(addr, size, VideoCommon::CacheType::BufferCache);
|
||||||
|
rasterizer->OnCacheInvalidation(addr, size);
|
||||||
|
});
|
||||||
|
gpu_thread.TickGPU(is_async);
|
||||||
|
WaitForSyncOperation(fence);
|
||||||
|
return;
|
||||||
|
}
|
||||||
gpu_thread.InvalidateRegion(addr, size);
|
gpu_thread.InvalidateRegion(addr, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,8 +528,8 @@ void GPU::FlushRegion(DAddr addr, u64 size) {
|
|||||||
impl->FlushRegion(addr, size);
|
impl->FlushRegion(addr, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPU::InvalidateRegion(DAddr addr, u64 size) {
|
void GPU::InvalidateRegion(DAddr addr, u64 size, bool preserve_gpu_writes) {
|
||||||
impl->InvalidateRegion(addr, size);
|
impl->InvalidateRegion(addr, size, preserve_gpu_writes);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GPU::OnCPUWrite(DAddr addr, u64 size) {
|
bool GPU::OnCPUWrite(DAddr addr, u64 size) {
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ public:
|
|||||||
void FlushRegion(DAddr addr, u64 size);
|
void FlushRegion(DAddr addr, u64 size);
|
||||||
|
|
||||||
/// Notify rasterizer that any caches of the specified region should be invalidated
|
/// Notify rasterizer that any caches of the specified region should be invalidated
|
||||||
void InvalidateRegion(DAddr addr, u64 size);
|
void InvalidateRegion(DAddr addr, u64 size, bool preserve_gpu_writes = false);
|
||||||
|
|
||||||
/// Notify rasterizer that CPU is trying to write this area. It returns true if the area is
|
/// Notify rasterizer that CPU is trying to write this area. It returns true if the area is
|
||||||
/// sensible, false otherwise, addr and size must be a valid combination
|
/// sensible, false otherwise, addr and size must be a valid combination
|
||||||
|
|||||||
@@ -276,6 +276,9 @@ GraphicsPipeline::GraphicsPipeline(
|
|||||||
num_textures += Shader::NumDescriptors(info->texture_descriptors);
|
num_textures += Shader::NumDescriptors(info->texture_descriptors);
|
||||||
num_image_elements += Shader::NumDescriptors(info->texture_descriptors);
|
num_image_elements += Shader::NumDescriptors(info->texture_descriptors);
|
||||||
num_image_elements += Shader::NumDescriptors(info->image_descriptors);
|
num_image_elements += Shader::NumDescriptors(info->image_descriptors);
|
||||||
|
for (const auto& desc : info->texture_descriptors) {
|
||||||
|
uses_attribute_handle |= desc.cbuf_index == Shader::ATTRIBUTE_HANDLE_CBUF_INDEX;
|
||||||
|
}
|
||||||
num_descriptor_entries += NumDescriptorEntries(*info);
|
num_descriptor_entries += NumDescriptorEntries(*info);
|
||||||
}
|
}
|
||||||
fragment_has_color0_output = stage_infos[NUM_STAGES - 1].stores_frag_color[0];
|
fragment_has_color0_output = stage_infos[NUM_STAGES - 1].stores_frag_color[0];
|
||||||
@@ -375,6 +378,9 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
|||||||
}
|
}
|
||||||
const auto& cbufs{maxwell3d->state.shader_stages[stage].const_buffers};
|
const auto& cbufs{maxwell3d->state.shader_stages[stage].const_buffers};
|
||||||
const auto read_handle{[&](const auto& desc, u32 index) {
|
const auto read_handle{[&](const auto& desc, u32 index) {
|
||||||
|
if (desc.cbuf_index == Shader::ATTRIBUTE_HANDLE_CBUF_INDEX) {
|
||||||
|
return TexturePair(attribute_handle, via_header_index);
|
||||||
|
}
|
||||||
ASSERT(cbufs[desc.cbuf_index].enabled);
|
ASSERT(cbufs[desc.cbuf_index].enabled);
|
||||||
const u32 index_offset{index << desc.size_shift};
|
const u32 index_offset{index << desc.size_shift};
|
||||||
const u32 offset{desc.cbuf_offset + index_offset};
|
const u32 offset{desc.cbuf_offset + index_offset};
|
||||||
|
|||||||
@@ -110,6 +110,16 @@ public:
|
|||||||
return configure_func(this, is_indexed);
|
return configure_func(this, is_indexed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// True when a shader samples a texture whose handle arrives as a vertex attribute.
|
||||||
|
[[nodiscard]] bool UsesAttributeHandle() const noexcept {
|
||||||
|
return uses_attribute_handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle bound to those textures by the next Configure.
|
||||||
|
void SetAttributeHandle(u32 handle) noexcept {
|
||||||
|
attribute_handle = handle;
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] GraphicsPipeline* Next(const GraphicsPipelineCacheKey& current_key) noexcept {
|
[[nodiscard]] GraphicsPipeline* Next(const GraphicsPipelineCacheKey& current_key) noexcept {
|
||||||
if (key == current_key) {
|
if (key == current_key) {
|
||||||
return this;
|
return this;
|
||||||
@@ -168,6 +178,8 @@ private:
|
|||||||
u32 num_descriptor_entries{};
|
u32 num_descriptor_entries{};
|
||||||
size_t num_image_elements{};
|
size_t num_image_elements{};
|
||||||
u32 num_textures{};
|
u32 num_textures{};
|
||||||
|
u32 attribute_handle{};
|
||||||
|
bool uses_attribute_handle{};
|
||||||
bool fragment_has_color0_output{};
|
bool fragment_has_color0_output{};
|
||||||
|
|
||||||
vk::DescriptorSetLayout descriptor_set_layout;
|
vk::DescriptorSetLayout descriptor_set_layout;
|
||||||
|
|||||||
@@ -479,6 +479,7 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
|
|||||||
.support_viewport_index_layer = device.IsExtShaderViewportIndexLayerSupported(),
|
.support_viewport_index_layer = device.IsExtShaderViewportIndexLayerSupported(),
|
||||||
.support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(),
|
.support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(),
|
||||||
.support_conditional_barrier = device.SupportsConditionalBarriers(),
|
.support_conditional_barrier = device.SupportsConditionalBarriers(),
|
||||||
|
.support_attribute_texture_handle = true,
|
||||||
};
|
};
|
||||||
host_info.ApplyDescriptorLimitPolicy();
|
host_info.ApplyDescriptorLimitPolicy();
|
||||||
|
|
||||||
|
|||||||
@@ -248,6 +248,15 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) {
|
|||||||
std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
|
std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
|
||||||
// update engine as channel may be different.
|
// update engine as channel may be different.
|
||||||
pipeline->SetEngine(maxwell3d, gpu_memory);
|
pipeline->SetEngine(maxwell3d, gpu_memory);
|
||||||
|
attribute_handle_pipeline = nullptr;
|
||||||
|
attribute_handle_runs.clear();
|
||||||
|
if (split_attribute_handles && pipeline->UsesAttributeHandle()) {
|
||||||
|
CollectAttributeHandleRuns(is_indexed);
|
||||||
|
if (!attribute_handle_runs.empty()) {
|
||||||
|
attribute_handle_pipeline = pipeline;
|
||||||
|
pipeline->SetAttributeHandle(attribute_handle_runs.front().handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!pipeline->Configure(is_indexed))
|
if (!pipeline->Configure(is_indexed))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -259,22 +268,110 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) {
|
|||||||
draw_func();
|
draw_func();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RasterizerVulkan::CollectAttributeHandleRuns(bool is_indexed) {
|
||||||
|
using VertexAttribute = Maxwell::VertexAttribute;
|
||||||
|
const auto& regs = maxwell3d->regs;
|
||||||
|
const auto& draw_state = maxwell3d->draw_manager.draw_state;
|
||||||
|
const u32 total = is_indexed ? draw_state.index_buffer.count : draw_state.vertex_buffer.count;
|
||||||
|
if (total == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// The handle is an integer attribute; sprite batches repeat it on every vertex of a quad.
|
||||||
|
const VertexAttribute* source{};
|
||||||
|
for (const auto& attr : regs.vertex_attrib_format) {
|
||||||
|
if (attr.constant || attr.size == VertexAttribute::Size::Invalid) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (attr.type == VertexAttribute::Type::UInt || attr.type == VertexAttribute::Type::SInt) {
|
||||||
|
source = &attr;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto& stream = regs.vertex_streams[source ? source->buffer.Value() : 0];
|
||||||
|
if (!source || !stream.IsEnabled()) {
|
||||||
|
attribute_handle_runs.push_back({0, total, 0});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Only plain triangle lists are split; anything else keeps the handle of its first vertex.
|
||||||
|
const u32 primitive_size =
|
||||||
|
draw_state.topology == Maxwell::PrimitiveTopology::Triangles ? 3 : total;
|
||||||
|
const GPUVAddr base_address = stream.Address() + source->offset;
|
||||||
|
const auto read_handle = [&](u32 position) {
|
||||||
|
u32 vertex;
|
||||||
|
if (is_indexed) {
|
||||||
|
const auto& index_buffer = draw_state.index_buffer;
|
||||||
|
const GPUVAddr address =
|
||||||
|
index_buffer.IndexStart() + size_t{position} * index_buffer.FormatSizeInBytes();
|
||||||
|
u32 index{};
|
||||||
|
gpu_memory->ReadBlockUnsafe(address, &index, index_buffer.FormatSizeInBytes());
|
||||||
|
vertex = static_cast<u32>(draw_state.base_index + index);
|
||||||
|
} else {
|
||||||
|
vertex = draw_state.vertex_buffer.first + position;
|
||||||
|
}
|
||||||
|
return gpu_memory->Read<u32>(base_address + static_cast<GPUVAddr>(vertex) * stream.stride);
|
||||||
|
};
|
||||||
|
for (u32 position = 0; position + primitive_size <= total; position += primitive_size) {
|
||||||
|
const u32 handle = read_handle(position);
|
||||||
|
if (!attribute_handle_runs.empty() && attribute_handle_runs.back().handle == handle) {
|
||||||
|
attribute_handle_runs.back().count += primitive_size;
|
||||||
|
} else {
|
||||||
|
attribute_handle_runs.push_back({position, primitive_size, handle});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (attribute_handle_runs.empty()) {
|
||||||
|
attribute_handle_runs.push_back({0, total, read_handle(0)});
|
||||||
|
} else {
|
||||||
|
const u32 covered = attribute_handle_runs.back().first + attribute_handle_runs.back().count;
|
||||||
|
if (covered < total) {
|
||||||
|
attribute_handle_runs.back().count += total - covered;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) {
|
void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) {
|
||||||
|
split_attribute_handles = true;
|
||||||
|
SCOPE_EXIT {
|
||||||
|
split_attribute_handles = false;
|
||||||
|
};
|
||||||
PrepareDraw(is_indexed, [this, is_indexed, instance_count] {
|
PrepareDraw(is_indexed, [this, is_indexed, instance_count] {
|
||||||
const auto& draw_state = maxwell3d->draw_manager.draw_state;
|
const auto& draw_state = maxwell3d->draw_manager.draw_state;
|
||||||
const u32 num_instances{instance_count};
|
const u32 num_instances{instance_count};
|
||||||
const DrawParams draw_params{MakeDrawParams(draw_state, num_instances, is_indexed)};
|
const DrawParams draw_params{MakeDrawParams(draw_state, num_instances, is_indexed)};
|
||||||
|
|
||||||
scheduler.Record([draw_params](vk::CommandBuffer cmdbuf) {
|
const auto record_draw = [this](const DrawParams& params) {
|
||||||
if (draw_params.is_indexed) {
|
scheduler.Record([params](vk::CommandBuffer cmdbuf) {
|
||||||
cmdbuf.DrawIndexed(draw_params.num_vertices, draw_params.num_instances,
|
if (params.is_indexed) {
|
||||||
draw_params.first_index, draw_params.base_vertex,
|
cmdbuf.DrawIndexed(params.num_vertices, params.num_instances,
|
||||||
draw_params.base_instance);
|
params.first_index, params.base_vertex,
|
||||||
} else {
|
params.base_instance);
|
||||||
cmdbuf.Draw(draw_params.num_vertices, draw_params.num_instances,
|
} else {
|
||||||
draw_params.base_vertex, draw_params.base_instance);
|
cmdbuf.Draw(params.num_vertices, params.num_instances, params.base_vertex,
|
||||||
|
params.base_instance);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
if (attribute_handle_pipeline && attribute_handle_runs.size() > 1) {
|
||||||
|
// The first run is already configured, each further one binds its own texture.
|
||||||
|
for (size_t i = 0; i < attribute_handle_runs.size(); ++i) {
|
||||||
|
const AttributeHandleRun& run = attribute_handle_runs[i];
|
||||||
|
if (i != 0) {
|
||||||
|
attribute_handle_pipeline->SetAttributeHandle(run.handle);
|
||||||
|
if (!attribute_handle_pipeline->Configure(is_indexed)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DrawParams run_params = draw_params;
|
||||||
|
run_params.num_vertices = run.count;
|
||||||
|
if (is_indexed) {
|
||||||
|
run_params.first_index += run.first;
|
||||||
|
} else {
|
||||||
|
run_params.base_vertex += run.first;
|
||||||
|
}
|
||||||
|
record_draw(run_params);
|
||||||
}
|
}
|
||||||
});
|
} else {
|
||||||
|
record_draw(draw_params);
|
||||||
|
}
|
||||||
|
|
||||||
// Log draw call
|
// Log draw call
|
||||||
if (GPU::Logging::IsActive() &&
|
if (GPU::Logging::IsActive() &&
|
||||||
|
|||||||
@@ -157,6 +157,20 @@ private:
|
|||||||
template <typename Func>
|
template <typename Func>
|
||||||
void PrepareDraw(bool is_indexed, Func&&);
|
void PrepareDraw(bool is_indexed, Func&&);
|
||||||
|
|
||||||
|
/// A stretch of a draw whose vertices all carry the same texture handle.
|
||||||
|
struct AttributeHandleRun {
|
||||||
|
u32 first;
|
||||||
|
u32 count;
|
||||||
|
u32 handle;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Splits the current draw wherever the texture handle carried in the vertex data changes.
|
||||||
|
void CollectAttributeHandleRuns(bool is_indexed);
|
||||||
|
|
||||||
|
std::vector<AttributeHandleRun> attribute_handle_runs;
|
||||||
|
GraphicsPipeline* attribute_handle_pipeline{};
|
||||||
|
bool split_attribute_handles{};
|
||||||
|
|
||||||
void FlushWork();
|
void FlushWork();
|
||||||
|
|
||||||
void UpdateDynamicStates();
|
void UpdateDynamicStates();
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -8,7 +11,6 @@
|
|||||||
|
|
||||||
class QCheckBox;
|
class QCheckBox;
|
||||||
class QLineEdit;
|
class QLineEdit;
|
||||||
class QComboBox;
|
|
||||||
class QDateTimeEdit;
|
class QDateTimeEdit;
|
||||||
namespace Core {
|
namespace Core {
|
||||||
class System;
|
class System;
|
||||||
|
|||||||
@@ -140,7 +140,12 @@ QWidget* Widget::CreateCombobox(std::function<std::string()>& serializer,
|
|||||||
if (combobox_enumerations.contains(type)) {
|
if (combobox_enumerations.contains(type)) {
|
||||||
enumeration = &combobox_enumerations.at(type);
|
enumeration = &combobox_enumerations.at(type);
|
||||||
for (const auto& [id, name] : *enumeration) {
|
for (const auto& [id, name] : *enumeration) {
|
||||||
combobox->addItem(name);
|
if (type == Settings::EnumMetadata<Settings::AppletMode>::Index() &&
|
||||||
|
id == static_cast<u32>(Settings::AppletMode::Disabled) &&
|
||||||
|
setting.Id() != Settings::values.controller_applet_mode.Id()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
combobox->addItem(name, id);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return combobox;
|
return combobox;
|
||||||
@@ -158,9 +163,8 @@ QWidget* Widget::CreateCombobox(std::function<std::string()>& serializer,
|
|||||||
const u32 setting_value = std::strtoul(setting.ToString().c_str(), nullptr, 0);
|
const u32 setting_value = std::strtoul(setting.ToString().c_str(), nullptr, 0);
|
||||||
combobox->setCurrentIndex(find_index(setting_value));
|
combobox->setCurrentIndex(find_index(setting_value));
|
||||||
|
|
||||||
serializer = [this, enumeration]() {
|
serializer = [this]() {
|
||||||
int current = combobox->currentIndex();
|
return std::to_string(combobox->currentData().toUInt());
|
||||||
return std::to_string(enumeration->at(current).first);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
restore_func = [this, find_index]() {
|
restore_func = [this, find_index]() {
|
||||||
|
|||||||
+15
-11
@@ -12,25 +12,29 @@ namespace Yuzu {
|
|||||||
|
|
||||||
inline bool ContainsAllWords(const QString& haystack, const QString& userinput) {
|
inline bool ContainsAllWords(const QString& haystack, const QString& userinput) {
|
||||||
const QStringList userinput_split = userinput.split(QLatin1Char{' '}, Qt::SkipEmptyParts);
|
const QStringList userinput_split = userinput.split(QLatin1Char{' '}, Qt::SkipEmptyParts);
|
||||||
return std::all_of(userinput_split.begin(), userinput_split.end(),
|
return std::all_of(userinput_split.begin(), userinput_split.end(), [&haystack](const QString& s) {
|
||||||
[&haystack](const QString& s) { return haystack.contains(s); });
|
return haystack.contains(s);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool FilterMatches(const QString& filter, const QStandardItem* item) {
|
inline bool FilterMatches(const QString& filter, const QStandardItem* item) {
|
||||||
if (filter.isEmpty())
|
if (filter.isEmpty())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
auto const sluggify = [](const QString& s) {
|
||||||
|
QString o(s.normalized(QString::NormalizationForm_D).toLower());
|
||||||
|
return o.replace(QRegularExpression(QStringLiteral("[^a-z\\s]")), QStringLiteral(""));
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto norm_filter = sluggify(filter);
|
||||||
const auto program_id = item->data(GameListItemPath::ProgramIdRole).toULongLong();
|
const auto program_id = item->data(GameListItemPath::ProgramIdRole).toULongLong();
|
||||||
|
const QString file_path = sluggify(item->data(GameListItemPath::FullPathRole).toString());
|
||||||
const QString file_path = item->data(GameListItemPath::FullPathRole).toString().toLower();
|
const QString file_title = sluggify(item->data(GameListItemPath::TitleRole).toString());
|
||||||
const QString file_title = item->data(GameListItemPath::TitleRole).toString().toLower();
|
|
||||||
const QString file_program_id = QStringLiteral("%1").arg(program_id, 16, 16, QLatin1Char{'0'});
|
const QString file_program_id = QStringLiteral("%1").arg(program_id, 16, 16, QLatin1Char{'0'});
|
||||||
|
const QString file_name = sluggify(file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} + file_title);
|
||||||
const QString file_name =
|
return (ContainsAllWords(file_name, norm_filter)
|
||||||
file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} + file_title;
|
|| ContainsAllWords(file_title, norm_filter))
|
||||||
|
|| (file_program_id.size() == 16 && file_program_id.contains(norm_filter));
|
||||||
return Yuzu::ContainsAllWords(file_name, filter) ||
|
|
||||||
(file_program_id.size() == 16 && file_program_id.contains(filter));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Yuzu
|
} // namespace Yuzu
|
||||||
|
|||||||
@@ -1786,7 +1786,7 @@ bool MainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletPar
|
|||||||
|
|
||||||
QtCommon::system->SetFrontendAppletSet({
|
QtCommon::system->SetFrontendAppletSet({
|
||||||
std::make_unique<QtAmiiboSettings>(*this), // Amiibo Settings
|
std::make_unique<QtAmiiboSettings>(*this), // Amiibo Settings
|
||||||
(UISettings::values.controller_applet_disabled.GetValue() == true)
|
(Settings::values.controller_applet_mode.GetValue() == Settings::AppletMode::Disabled)
|
||||||
? nullptr
|
? nullptr
|
||||||
: std::make_unique<QtControllerSelector>(*this), // Controller Selector
|
: std::make_unique<QtControllerSelector>(*this), // Controller Selector
|
||||||
std::make_unique<QtErrorDisplay>(*this), // Error Display
|
std::make_unique<QtErrorDisplay>(*this), // Error Display
|
||||||
|
|||||||
Reference in New Issue
Block a user