mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-10-16 08:45:21 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 09a035264f |
+2
-1
@@ -17,6 +17,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||
USE_CUSTOM_CPU_TICKS("use_custom_cpu_ticks"),
|
||||
SKIP_CPU_INNER_INVALIDATION("skip_cpu_inner_invalidation"),
|
||||
NCE_INVALIDATION_GPU_READBACK("nce_invalidation_gpu_readback"),
|
||||
NCE_RUNTIME_NRO_PATCH("nce_runtime_nro_patch"),
|
||||
FIX_BLOOM_EFFECTS("fix_bloom_effects"),
|
||||
EMULATE_BGR565("emulate_bgr565"),
|
||||
RESCALE_HACK("rescale_hack"),
|
||||
@@ -94,7 +95,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||
|
||||
ENABLE_QUICK_SETTINGS("enable_quick_settings");
|
||||
|
||||
//external fun isFrameSkippingEnabled(): Boolean
|
||||
// external fun isFrameSkippingEnabled(): Boolean
|
||||
external fun isFrameInterpolationEnabled(): Boolean
|
||||
|
||||
override fun getBoolean(needsGlobal: Boolean): Boolean =
|
||||
|
||||
+7
@@ -866,6 +866,13 @@ abstract class SettingsItem(
|
||||
descriptionId = R.string.nce_invalidation_gpu_readback_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.NCE_RUNTIME_NRO_PATCH,
|
||||
titleId = R.string.nce_runtime_nro_patch,
|
||||
descriptionId = R.string.nce_runtime_nro_patch_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.FIX_BLOOM_EFFECTS,
|
||||
|
||||
+1
@@ -555,6 +555,7 @@ class SettingsFragmentPresenter(
|
||||
|
||||
add(BooleanSetting.SKIP_CPU_INNER_INVALIDATION.key)
|
||||
add(BooleanSetting.NCE_INVALIDATION_GPU_READBACK.key)
|
||||
add(BooleanSetting.NCE_RUNTIME_NRO_PATCH.key)
|
||||
add(BooleanSetting.FIX_BLOOM_EFFECTS.key)
|
||||
add(BooleanSetting.EMULATE_BGR565.key)
|
||||
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
|
||||
|
||||
@@ -589,6 +589,8 @@
|
||||
<string name="skip_cpu_inner_invalidation_description">يتخطى بعض عمليات إبطال ذاكرة التخزين المؤقتة من جانب وحدة المعالجة المركزية أثناء تحديثات الذاكرة، مما يقلل من استخدام وحدة المعالجة المركزية ويحسن أداءها. قد يتسبب ذلك في حدوث أعطال أو تعطل في بعض الألعاب.</string>
|
||||
<string name="nce_invalidation_gpu_readback">إعادة قراءة وحدة معالجة الرسومات قبل إبطال صلاحية NCE</string>
|
||||
<string name="nce_invalidation_gpu_readback_description">إصلاح مؤقت. يعالج مشكلة تعطل لعبة Ender Magnolia (وغيرها من ألعاب UE). قد يؤدي إلى انخفاض في الأداء. يجري العمل على تحسينه.</string>
|
||||
<string name="nce_runtime_nro_patch">تصحيح حالات NRO في وقت التشغيل لـ NCE</string>
|
||||
<string name="nce_runtime_nro_patch_description">حل مؤقت. إصلاح مشكلة تشغيل لعبة Biomutant. قد يؤثر ذلك على الأداء. يجري العمل على تحسينه.</string>
|
||||
<string name="fix_bloom_effects">إصلاح تأثيرات التوهج</string>
|
||||
<string name="fix_bloom_effects_description">يقلل من ضبابية التوهج في LA/EOW (Adreno A6XX - A7XX/ Turnip)، ويزيل التوهج في Burnout. تحذير: قد يسبب تشوهات رسومية في ألعاب أخرى.</string>
|
||||
<string name="emulate_bgr565">محاكاة BGR565</string>
|
||||
|
||||
@@ -579,6 +579,8 @@
|
||||
<string name="skip_cpu_inner_invalidation_description">在記憶體更新期間跳過某些 CPU 端快取的失效處理,減少 CPU 使用率並提高其性能。可能會導致某些遊戲出現故障或當機</string>
|
||||
<string name="nce_invalidation_gpu_readback">在 NCE 無效化前進行 GPU 回讀</string>
|
||||
<string name="nce_invalidation_gpu_readback_description">(暫時性功能)修復 \"終結者瑪格諾莉亞:霧中綻放\" 及其它使用虛幻引擎之遊戲中的當機問題,可能會降低效能。此功能正在改善中</string>
|
||||
<string name="nce_runtime_nro_patch">對 NCE Runtime 的 NRO 檔案進行修補</string>
|
||||
<string name="nce_runtime_nro_patch_description">(暫時性功能)修復 \"Biomutant\" 的啟動問題,可能會對效能造成影響。此功能正在改善中</string>
|
||||
<string name="fix_bloom_effects">修正光暈特效</string>
|
||||
<string name="fix_bloom_effects_description">在 \"薩爾達傳說:織夢島\" 和 \"薩爾達傳說:智慧的再現\" 中減少光暈模糊的問題並移除 \"橫衝直撞:狂飆樂園重製版\" 中的光暈效果(適用於 Adreno A6XX - A7XX/ Turnip)。警告:可能會在其它遊戲中導致圖形異常</string>
|
||||
<string name="emulate_bgr565">模擬 BGR565</string>
|
||||
@@ -1195,4 +1197,4 @@
|
||||
<!-- Licenses screen strings -->
|
||||
<string name="licenses">授權</string>
|
||||
<string name="license_fidelityfx_fsr_description">來自 AMD 的升級圖像品質</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -595,6 +595,8 @@
|
||||
<string name="skip_cpu_inner_invalidation_description">Skips certain CPU-side cache invalidations during memory updates, reducing CPU usage and improving it\'s performance. This may cause glitches or crashes on some games.</string>
|
||||
<string name="nce_invalidation_gpu_readback">GPU Readback Before NCE Invalidation</string>
|
||||
<string name="nce_invalidation_gpu_readback_description">Temporary. Fix Ender Magnolia (and other UE titles) crashes. May reduce performance. Improvement in progress.</string>
|
||||
<string name="nce_runtime_nro_patch">Patch Runtime NROs for NCE</string>
|
||||
<string name="nce_runtime_nro_patch_description">Temporary. Fix Biomutant boot. May impact performance. Improvement in progress.</string>
|
||||
<string name="fix_bloom_effects">Fix Bloom Effects</string>
|
||||
<string name="fix_bloom_effects_description">Reduces bloom blur in LA/EOW (Adreno A6XX - A7XX/ Turnip), removes bloom in Burnout. Warning: may cause graphical artifacts in other games.</string>
|
||||
<string name="emulate_bgr565">Emulate BGR565</string>
|
||||
|
||||
@@ -641,6 +641,8 @@ struct Values {
|
||||
true};
|
||||
SwitchableSetting<bool> nce_invalidation_gpu_readback{
|
||||
linkage, false, "nce_invalidation_gpu_readback", Category::RendererHacks};
|
||||
SwitchableSetting<bool> nce_runtime_nro_patch{
|
||||
linkage, false, "nce_runtime_nro_patch", Category::RendererHacks};
|
||||
SwitchableSetting<bool> async_presentation{linkage,
|
||||
#ifdef __ANDROID__
|
||||
true,
|
||||
|
||||
@@ -45,23 +45,6 @@ Patcher::Patcher() : c(m_patch_instructions), c_pre(m_patch_instructions_pre) {
|
||||
WriteLoadContext(c_pre);
|
||||
}
|
||||
|
||||
Patcher::Patcher(std::span<const u8> text) : c(m_patch_instructions), c_pre(m_patch_instructions_pre) {
|
||||
modules.emplace_back();
|
||||
curr_patch = &modules.back();
|
||||
const auto words = std::span<const u32>{reinterpret_cast<const u32*>(text.data()), text.size() / sizeof(u32)};
|
||||
for (u32 i = ModuleCodeIndex; i < static_cast<u32>(words.size()); ++i) {
|
||||
const uintptr_t offset = i * sizeof(u32);
|
||||
if (auto mrs = MRS{words[i]}; mrs.Verify() && mrs.GetRt() != 31 && (mrs.GetSystemReg() == TpidrEl0 || mrs.GetSystemReg() == TpidrroEl0)) {
|
||||
BranchToPatch(offset);
|
||||
const auto src_reg = mrs.GetSystemReg() == TpidrroEl0 ? oaknut::SystemReg::TPIDRRO_EL0 : oaknut::SystemReg::TPIDR_EL0;
|
||||
WriteMrsHandler(offset + sizeof(u32), oaknut::XReg{static_cast<int>(mrs.GetRt())}, src_reg);
|
||||
} else if (auto msr = MSR{words[i]}; msr.Verify() && msr.GetSystemReg() == TpidrEl0) {
|
||||
BranchToPatch(offset);
|
||||
WriteMsrHandler(offset + sizeof(u32), oaknut::XReg{static_cast<int>(msr.GetRt())});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Patcher::~Patcher() = default;
|
||||
|
||||
bool Patcher::PatchText(std::span<const u8> program_image, const Kernel::CodeSet::Segment& code) {
|
||||
@@ -199,31 +182,6 @@ bool Patcher::PatchText(std::span<const u8> program_image, const Kernel::CodeSet
|
||||
return true;
|
||||
}
|
||||
|
||||
std::span<const u32> Patcher::RelocateRuntimeTls(
|
||||
s64 patch_offset, std::vector<InstructionPatch>& out_patches) {
|
||||
const auto& patch = modules.front();
|
||||
const auto can_branch = [](s64 offset) {
|
||||
return offset % 4 == 0 && offset >= -static_cast<s64>(MaxRelativeBranch) && offset < static_cast<s64>(MaxRelativeBranch);
|
||||
};
|
||||
out_patches.clear();
|
||||
out_patches.reserve(patch.m_branch_to_patch_relocations.size());
|
||||
for (const auto& rel : patch.m_branch_to_patch_relocations) {
|
||||
const s64 offset = patch_offset + rel.patch_offset - static_cast<s64>(rel.module_offset);
|
||||
if (!can_branch(offset)) {return {};}
|
||||
u32 replacement;
|
||||
oaknut::CodeGenerator rc{&replacement};
|
||||
rc.B(offset);
|
||||
out_patches.push_back({rel.module_offset, replacement});
|
||||
}
|
||||
for (const auto& rel : patch.m_branch_to_module_relocations) {
|
||||
const s64 offset = static_cast<s64>(rel.module_offset) - patch_offset - rel.patch_offset;
|
||||
if (!can_branch(offset)) {return {};}
|
||||
oaknut::CodeGenerator rc{m_patch_instructions.data() + rel.patch_offset / sizeof(u32)};
|
||||
rc.B(offset);
|
||||
}
|
||||
return m_patch_instructions;
|
||||
}
|
||||
|
||||
bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code, std::vector<u8>& program_image, EntryTrampolines* out_trampolines) {
|
||||
const size_t patch_size = GetSectionSize();
|
||||
const size_t pre_patch_size = GetPreSectionSize();
|
||||
|
||||
@@ -48,22 +48,15 @@ using ModuleTextAddress = u64;
|
||||
using PatchTextAddress = u64;
|
||||
using EntryTrampolines = ::Common::unordered_map<ModuleTextAddress, PatchTextAddress>;
|
||||
|
||||
struct InstructionPatch {
|
||||
u64 offset;
|
||||
u32 replacement;
|
||||
};
|
||||
|
||||
class Patcher {
|
||||
public:
|
||||
void SetModuleID(const ModuleID& id) {
|
||||
module_id = id;
|
||||
}
|
||||
explicit Patcher();
|
||||
explicit Patcher(std::span<const u8> text);
|
||||
~Patcher();
|
||||
bool PatchText(std::span<const u8> program_image, const Kernel::CodeSet::Segment& code);
|
||||
bool RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code, std::vector<u8>& program_image, EntryTrampolines* out_trampolines);
|
||||
std::span<const u32> RelocateRuntimeTls(s64 patch_offset, std::vector<InstructionPatch>& out_patches);
|
||||
size_t GetSectionSize() const noexcept;
|
||||
size_t GetPreSectionSize() const noexcept;
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
#include "common/scope_exit.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
|
||||
#include "core/hle/service/cmif_serialization.h"
|
||||
@@ -426,51 +425,36 @@ public:
|
||||
R_TRY(context->ValidateNro(std::addressof(nro_info->module_id), std::addressof(rx_size),
|
||||
std::addressof(ro_size), std::addressof(rw_size),
|
||||
nro_info->base_address, nro_size, bss_size));
|
||||
R_TRY(SetNroPerms(context->GetProcess(), nro_info->base_address, rx_size, ro_size, rw_size + bss_size));
|
||||
#ifdef HAS_NCE
|
||||
if (Settings::IsNceEnabled()) {
|
||||
auto* process = context->GetProcess();
|
||||
auto& memory = process->GetMemory();
|
||||
std::vector<u8> text(rx_size);
|
||||
memory.ReadBlock(nro_info->base_address, text.data(), text.size());
|
||||
Core::NCE::Patcher patch{text};
|
||||
const size_t patch_size = patch.GetSectionSize();
|
||||
if (patch_size != 0) {
|
||||
if (Settings::values.nce_runtime_nro_patch.GetValue()) {
|
||||
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);
|
||||
SCOPE_EXIT { patch_memory->Close(kernel); };
|
||||
R_TRY(patch_memory->Initialize(kernel, kernel.System().DeviceMemory(), process, permission, permission, patch_size));
|
||||
Kernel::KSharedMemory::Register(kernel, patch_memory);
|
||||
const std::array<s64, 2> offsets{static_cast<s64>(total_size + Kernel::PageSize),-static_cast<s64>(patch_size + Kernel::PageSize)};
|
||||
std::vector<Core::NCE::InstructionPatch> tls_patches;
|
||||
std::span<const u32> instructions;
|
||||
u64 patch_address = 0;
|
||||
for (const s64 offset : offsets) {
|
||||
instructions = patch.RelocateRuntimeTls(offset, tls_patches);
|
||||
if (instructions.empty()) {continue;}
|
||||
const u64 address = static_cast<u64>(static_cast<s64>(nro_info->base_address) + offset);
|
||||
if (R_FAILED(patch_memory->Map(*process, address, patch_size, permission))) {continue;}
|
||||
patch_address = address;
|
||||
break;
|
||||
}
|
||||
R_UNLESS(patch_address != 0, RO::ResultOutOfAddressSpace);
|
||||
const Result add_shared_memory_result = process->AddSharedMemory(kernel, patch_memory, patch_address, patch_size);
|
||||
if (R_FAILED(add_shared_memory_result)) {
|
||||
R_ASSERT(patch_memory->Unmap(*process, patch_address, patch_size));
|
||||
}
|
||||
R_TRY(add_shared_memory_result);
|
||||
std::memcpy(patch_memory->GetPointer(), instructions.data(), instructions.size_bytes());
|
||||
auto* ptr = reinterpret_cast<char*>(patch_address);
|
||||
__builtin___clear_cache(ptr, ptr + instructions.size_bytes());
|
||||
for (const auto& instruction : tls_patches) {
|
||||
const u64 address = nro_info->base_address + instruction.offset;
|
||||
memory.Write32(address, instruction.replacement);
|
||||
ptr = reinterpret_cast<char*>(address);
|
||||
__builtin___clear_cache(ptr, ptr + sizeof(u32));
|
||||
}
|
||||
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.
|
||||
R_TRY(SetNroPerms(context->GetProcess(), nro_info->base_address, rx_size, ro_size,
|
||||
rw_size + bss_size));
|
||||
|
||||
context->SetNroInfoInUse(nro_info, true);
|
||||
nro_info->code_size = rx_size + ro_size;
|
||||
|
||||
@@ -40,12 +40,6 @@ bool IsConnectionBased(Type type) {
|
||||
}
|
||||
}
|
||||
|
||||
// Size of a real BSD sockaddr_in. The guest SockAddrIn struct is padded to 0x100 so that longer
|
||||
// input addresses can be read, but an address handed back to the game is always this long.
|
||||
// Reporting 0x100 lets a game that trusts the length copy 240 bytes past its 16-byte sockaddr
|
||||
// (suspected cause of Crysis Remastered crashing ~30 s after its getsockname() call).
|
||||
constexpr size_t GuestSockAddrInSize = 16;
|
||||
|
||||
template <typename T>
|
||||
T GetValue(std::span<const u8> buffer) {
|
||||
T t{};
|
||||
@@ -661,9 +655,6 @@ std::pair<s32, Errno> BSD_USA::AcceptImpl(s32 fd, std::vector<u8>& write_buffer)
|
||||
new_descriptor.is_connection_based = descriptor.is_connection_based;
|
||||
|
||||
const SockAddrIn guest_addr_in = Translate(result.sockaddr_in);
|
||||
if (write_buffer.size() > GuestSockAddrInSize) {
|
||||
write_buffer.resize(GuestSockAddrInSize);
|
||||
}
|
||||
PutValue(write_buffer, guest_addr_in);
|
||||
|
||||
return {new_fd, Errno::SUCCESS};
|
||||
@@ -723,8 +714,8 @@ Errno BSD_USA::GetPeerNameImpl(s32 fd, std::vector<u8>& write_buffer) {
|
||||
}
|
||||
const SockAddrIn guest_addrin = Translate(addr_in);
|
||||
|
||||
// The reported length must be that of a real sockaddr_in, not of the padded guest struct.
|
||||
write_buffer.resize(GuestSockAddrInSize);
|
||||
ASSERT(write_buffer.size() >= sizeof(guest_addrin));
|
||||
write_buffer.resize(sizeof(guest_addrin));
|
||||
PutValue(write_buffer, guest_addrin);
|
||||
return Translate(bsd_errno);
|
||||
}
|
||||
@@ -745,8 +736,8 @@ Errno BSD_USA::GetSockNameImpl(s32 fd, std::vector<u8>& write_buffer) {
|
||||
}
|
||||
const SockAddrIn guest_addrin = Translate(addr_in);
|
||||
|
||||
// The reported length must be that of a real sockaddr_in, not of the padded guest struct.
|
||||
write_buffer.resize(GuestSockAddrInSize);
|
||||
ASSERT(write_buffer.size() >= sizeof(guest_addrin));
|
||||
write_buffer.resize(sizeof(guest_addrin));
|
||||
PutValue(write_buffer, guest_addrin);
|
||||
return Translate(bsd_errno);
|
||||
}
|
||||
@@ -958,9 +949,8 @@ std::pair<s32, Errno> BSD_USA::RecvFromImpl(s32 fd, u32 flags, std::vector<u8>&
|
||||
if (ret < 0) {
|
||||
addr.clear();
|
||||
} else {
|
||||
ASSERT(addr.size() >= GuestSockAddrInSize);
|
||||
ASSERT(addr.size() >= 16);
|
||||
const SockAddrIn result = Translate(addr_in);
|
||||
addr.resize(GuestSockAddrInSize);
|
||||
PutValue(addr, result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,6 +198,10 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
|
||||
tr("GPU Readback Before NCE Invalidation"),
|
||||
tr("Flushes GPU buffer data back to guest memory before NCE invalidates the whole "
|
||||
"guest page, preserving it while the CPU store proceeds."));
|
||||
INSERT(Settings, nce_runtime_nro_patch,
|
||||
tr("Patch Runtime NROs for NCE"),
|
||||
tr("Applies NCE code patches to runtime-loaded NRO modules."
|
||||
"This is required for some games to run correctly."));
|
||||
INSERT(Settings, vsync_mode, tr("VSync Mode:"),
|
||||
tr("FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen "
|
||||
"refresh rate.\nFIFO Relaxed allows tearing as it recovers from a slow down.\n"
|
||||
|
||||
@@ -1738,36 +1738,64 @@ void BufferCache<P>::TouchBuffer(Buffer& buffer, BufferId buffer_id) noexcept {
|
||||
|
||||
template <class P>
|
||||
bool BufferCache<P>::SynchronizeBuffer(Buffer& buffer, DAddr device_addr, u32 size) {
|
||||
upload_copies.clear();
|
||||
u64 total_size_bytes = 0;
|
||||
u64 largest_copy = 0;
|
||||
const DAddr buffer_start = buffer.cpu_addr_cached;
|
||||
const auto add_upload = [&](DAddr start, DAddr end) {
|
||||
if (start == end) return;
|
||||
const u64 range_size = end - start;
|
||||
upload_copies.push_back(BufferCopy{
|
||||
.src_offset = total_size_bytes,
|
||||
.dst_offset = start - buffer_start,
|
||||
.size = range_size,
|
||||
#ifdef __ANDROID__
|
||||
const bool use_optimized_vertex_buffers = Settings::values.use_optimized_vertex_buffers.GetValue();
|
||||
#else
|
||||
constexpr bool use_optimized_vertex_buffers = true;
|
||||
#endif
|
||||
|
||||
if (use_optimized_vertex_buffers) {
|
||||
upload_copies.clear();
|
||||
u64 total_size_bytes = 0;
|
||||
u64 largest_copy = 0;
|
||||
const DAddr buffer_start = buffer.cpu_addr_cached;
|
||||
const auto add_upload = [&](DAddr start, DAddr end) {
|
||||
if (start == end) return;
|
||||
const u64 range_size = end - start;
|
||||
upload_copies.push_back(BufferCopy{
|
||||
.src_offset = total_size_bytes,
|
||||
.dst_offset = start - buffer_start,
|
||||
.size = range_size,
|
||||
});
|
||||
total_size_bytes += range_size;
|
||||
largest_copy = (std::max)(largest_copy, range_size);
|
||||
};
|
||||
memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) {
|
||||
DAddr upload_start = device_addr_out;
|
||||
gpu_modified_ranges.ForEachInRange(device_addr_out, range_size, [&](DAddr gpu_start, DAddr gpu_end) {
|
||||
add_upload(upload_start, gpu_start);
|
||||
upload_start = gpu_end;
|
||||
});
|
||||
add_upload(upload_start, device_addr_out + range_size);
|
||||
});
|
||||
total_size_bytes += range_size;
|
||||
largest_copy = (std::max)(largest_copy, range_size);
|
||||
};
|
||||
memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) {
|
||||
DAddr upload_start = device_addr_out;
|
||||
gpu_modified_ranges.ForEachInRange(device_addr_out, range_size, [&](DAddr gpu_start, DAddr gpu_end) {
|
||||
add_upload(upload_start, gpu_start);
|
||||
upload_start = gpu_end;
|
||||
if (total_size_bytes == 0) {
|
||||
return true;
|
||||
}
|
||||
const std::span<BufferCopy> copies_span(upload_copies.data(), upload_copies.size());
|
||||
UploadMemory(buffer, total_size_bytes, largest_copy, copies_span);
|
||||
any_buffer_uploaded = true;
|
||||
return false;
|
||||
} else {
|
||||
boost::container::small_vector<BufferCopy, 4> copies;
|
||||
u64 total_size_bytes = 0;
|
||||
u64 largest_copy = 0;
|
||||
DAddr buffer_start = buffer.CpuAddr();
|
||||
memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) {
|
||||
copies.push_back(BufferCopy{
|
||||
.src_offset = total_size_bytes,
|
||||
.dst_offset = device_addr_out - buffer_start,
|
||||
.size = range_size,
|
||||
});
|
||||
total_size_bytes += range_size;
|
||||
largest_copy = (std::max)(largest_copy, range_size);
|
||||
});
|
||||
add_upload(upload_start, device_addr_out + range_size);
|
||||
});
|
||||
if (total_size_bytes == 0) {
|
||||
return true;
|
||||
if (total_size_bytes == 0) {
|
||||
return true;
|
||||
}
|
||||
const std::span<BufferCopy> copies_span(copies.data(), copies.size());
|
||||
UploadMemory(buffer, total_size_bytes, largest_copy, copies_span);
|
||||
return false;
|
||||
}
|
||||
const std::span<BufferCopy> copies_span(upload_copies.data(), upload_copies.size());
|
||||
UploadMemory(buffer, total_size_bytes, largest_copy, copies_span);
|
||||
any_buffer_uploaded = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <class P>
|
||||
|
||||
@@ -563,8 +563,10 @@ void RecordShaderReadBarrier(Scheduler& scheduler, const ImageView& image_view)
|
||||
});
|
||||
}
|
||||
|
||||
void BeginRenderPass(vk::CommandBuffer& cmdbuf, VkRenderPass render_pass,
|
||||
VkFramebuffer framebuffer_handle, VkExtent2D render_area) {
|
||||
void BeginRenderPass(vk::CommandBuffer& cmdbuf, const Framebuffer* framebuffer) {
|
||||
const VkRenderPass render_pass = framebuffer->RenderPass();
|
||||
const VkFramebuffer framebuffer_handle = framebuffer->Handle();
|
||||
const VkExtent2D render_area = framebuffer->RenderArea();
|
||||
const VkRenderPassBeginInfo renderpass_bi{
|
||||
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
||||
.pNext = nullptr,
|
||||
@@ -677,67 +679,27 @@ void BlitImageHelper::BlitColor(const Framebuffer* dst_framebuffer, const ImageV
|
||||
void BlitImageHelper::BlitColor(const Framebuffer* dst_framebuffer, VkImageView src_image_view,
|
||||
VkImage src_image, VkSampler src_sampler,
|
||||
const Region2D& dst_region, const Region2D& src_region,
|
||||
const Extent3D& src_size,
|
||||
const std::optional<VkRect2D>& scissor) {
|
||||
const Extent3D& src_size) {
|
||||
const BlitImagePipelineKey key{
|
||||
.renderpass = dst_framebuffer->RenderPass(),
|
||||
.operation = Tegra::Engines::Fermi2D::Operation::SrcCopy,
|
||||
};
|
||||
const VkPipelineLayout layout = *one_texture_pipeline_layout;
|
||||
const VkPipeline pipeline = FindOrEmplaceColorPipeline(key);
|
||||
|
||||
// Clip the destination rectangle to the scissor; the viewport keeps the full rectangle so
|
||||
// the texture coordinates are unchanged.
|
||||
std::optional<VkRect2D> clip;
|
||||
if (scissor) {
|
||||
const s64 x0 = (std::max)(s64{(std::min)(dst_region.start.x, dst_region.end.x)},
|
||||
s64{scissor->offset.x});
|
||||
const s64 y0 = (std::max)(s64{(std::min)(dst_region.start.y, dst_region.end.y)},
|
||||
s64{scissor->offset.y});
|
||||
const s64 x1 = (std::min)(s64{(std::max)(dst_region.start.x, dst_region.end.x)},
|
||||
s64{scissor->offset.x} + scissor->extent.width);
|
||||
const s64 y1 = (std::min)(s64{(std::max)(dst_region.start.y, dst_region.end.y)},
|
||||
s64{scissor->offset.y} + scissor->extent.height);
|
||||
if (x1 <= x0 || y1 <= y0) {
|
||||
return; // Fully scissored out.
|
||||
}
|
||||
clip = VkRect2D{
|
||||
.offset = {static_cast<s32>(x0), static_cast<s32>(y0)},
|
||||
.extent = {static_cast<u32>(x1 - x0), static_cast<u32>(y1 - y0)},
|
||||
};
|
||||
}
|
||||
|
||||
// Read the framebuffer now: by the time the worker runs this, the texture cache may have
|
||||
// retired it (moved into its delayed-destruction ring), leaving a null handle behind - Turnip
|
||||
// then crashed in vkCmdBeginRenderPass (Crysis Remastered, DrawTexture).
|
||||
const VkRenderPass render_pass = dst_framebuffer->RenderPass();
|
||||
const VkFramebuffer framebuffer_handle = dst_framebuffer->Handle();
|
||||
const VkExtent2D render_area = dst_framebuffer->RenderArea();
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([this, render_pass, framebuffer_handle, render_area, src_image_view,
|
||||
src_image, src_sampler, dst_region, src_region, src_size, pipeline, layout,
|
||||
clip](vk::CommandBuffer cmdbuf) {
|
||||
// Images stay in GENERAL everywhere (the descriptor below says so too): only make the
|
||||
// earlier writes visible. Moving the source to SHADER_READ_ONLY_OPTIMAL left it there
|
||||
// for good, so every later render pass used it in the wrong layout.
|
||||
TransitionImageLayout(cmdbuf, src_image, VK_IMAGE_LAYOUT_GENERAL);
|
||||
BeginRenderPass(cmdbuf, render_pass, framebuffer_handle, render_area);
|
||||
scheduler.Record([this, dst_framebuffer, src_image_view, src_image, src_sampler, dst_region,
|
||||
src_region, src_size, pipeline, layout](vk::CommandBuffer cmdbuf) {
|
||||
TransitionImageLayout(cmdbuf, src_image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
BeginRenderPass(cmdbuf, dst_framebuffer);
|
||||
const VkDescriptorSet descriptor_set = one_texture_descriptor_allocator.Commit();
|
||||
UpdateOneTextureDescriptorSet(device, descriptor_set, src_sampler, src_image_view);
|
||||
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set,
|
||||
nullptr);
|
||||
BindBlitState(cmdbuf, layout, dst_region, src_region, src_size);
|
||||
if (clip) {
|
||||
cmdbuf.SetScissor(0, *clip);
|
||||
}
|
||||
cmdbuf.Draw(3, 1, 0, 0);
|
||||
cmdbuf.EndRenderPass();
|
||||
});
|
||||
// The blit set its own pipeline, viewport and scissor: make the next draw set its own again,
|
||||
// as every other helper here does. Without this the next draw kept the blit's viewport and
|
||||
// scissor: Crysis Remastered drew its 256x256 minimap map over the whole screen.
|
||||
scheduler.InvalidateState();
|
||||
}
|
||||
|
||||
void BlitImageHelper::BlitImpl(const Framebuffer* dst_framebuffer,
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <deque>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
|
||||
#include "video_core/engines/fermi_2d.h"
|
||||
@@ -79,12 +78,9 @@ public:
|
||||
Tegra::Engines::Fermi2D::Filter filter,
|
||||
Tegra::Engines::Fermi2D::Operation operation);
|
||||
|
||||
/// @param scissor Optional clip rectangle in framebuffer pixels (DrawTexture honours the
|
||||
/// guest scissor like any other draw).
|
||||
void BlitColor(const Framebuffer* dst_framebuffer, VkImageView src_image_view,
|
||||
VkImage src_image, VkSampler src_sampler, const Region2D& dst_region,
|
||||
const Region2D& src_region, const Extent3D& src_size,
|
||||
const std::optional<VkRect2D>& scissor = std::nullopt);
|
||||
const Region2D& src_region, const Extent3D& src_size);
|
||||
|
||||
void BlitColorMSAA(const Framebuffer* dst_framebuffer, const ImageView& src_image_view,
|
||||
const Region2D& dst_region, const Region2D& src_region);
|
||||
|
||||
@@ -393,35 +393,8 @@ void RasterizerVulkan::DrawTexture() {
|
||||
.y = ScaleSrc(draw_texture_state.src_y1)}};
|
||||
Extent3D src_size = {static_cast<u32>(ScaleSrc(texture.size.width)),
|
||||
static_cast<u32>(ScaleSrc(texture.size.height)), texture.size.depth};
|
||||
// DrawTexture rectangles go through the fragment pipeline, scissor included.
|
||||
std::optional<VkRect2D> scissor;
|
||||
if (maxwell3d->regs.scissor_test[0].enable) {
|
||||
u32 up_scale = 1;
|
||||
u32 down_shift = 0;
|
||||
if (dst_rescaling) {
|
||||
up_scale = Settings::values.resolution_info.up_scale;
|
||||
down_shift = Settings::values.resolution_info.down_shift;
|
||||
}
|
||||
scissor = GetScissorState(maxwell3d->regs, 0, up_scale, down_shift);
|
||||
}
|
||||
|
||||
// Drawing a texture 1:1 onto itself without blending changes nothing on hardware, but here
|
||||
// it samples the image the render pass is writing to: undefined in Vulkan, and on tiled GPUs
|
||||
// it reads whatever the image memory last held. Crysis Remastered does this every frame for
|
||||
// its post-processing buffers (suspected cause of its map covering the whole screen).
|
||||
const auto& fb_images = framebuffer->Images();
|
||||
const bool src_is_target =
|
||||
std::find(fb_images.begin(), fb_images.begin() + framebuffer->NumImages(),
|
||||
texture.ImageHandle()) != fb_images.begin() + framebuffer->NumImages();
|
||||
if (src_is_target && src_rescaling == dst_rescaling &&
|
||||
src_region.start.x == dst_region.start.x && src_region.start.y == dst_region.start.y &&
|
||||
src_region.end.x == dst_region.end.x && src_region.end.y == dst_region.end.y &&
|
||||
maxwell3d->regs.blend.enable[0] == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
blit_image.BlitColor(framebuffer, texture.RenderTarget(), texture.ImageHandle(),
|
||||
sampler->Handle(), dst_region, src_region, src_size, scissor);
|
||||
sampler->Handle(), dst_region, src_region, src_size);
|
||||
}
|
||||
|
||||
void RasterizerVulkan::Clear(u32 layer_count) {
|
||||
|
||||
Reference in New Issue
Block a user