Compare commits

..

8 Commits

28 changed files with 395 additions and 457 deletions
-79
View File
@@ -14,17 +14,12 @@
#include <mutex>
#include <vector>
#include <span>
#include <utility>
#include "common/common_types.h"
#include "common/range_mutex.h"
#include "common/scratch_buffer.h"
#include "common/virtual_buffer.h"
#if defined(__linux__)
#include <sys/mman.h>
#endif
namespace Core {
constexpr size_t DEVICE_PAGEBITS = 12ULL;
@@ -50,74 +45,6 @@ class DeviceMemoryManager {
using DeviceMethods = typename Traits::DeviceMethods;
public:
class MirrorMapping {
public:
MirrorMapping() = default;
MirrorMapping(u8* mapped_base_, size_t mapped_size_, size_t data_offset_)
: mapped_base{mapped_base_}, mapped_size{mapped_size_}, data_offset{data_offset_} {}
MirrorMapping(const MirrorMapping&) = delete;
MirrorMapping& operator=(const MirrorMapping&) = delete;
MirrorMapping(MirrorMapping&& other) noexcept {
MoveFrom(other);
}
MirrorMapping& operator=(MirrorMapping&& other) noexcept {
if (this != &other) {
Release();
MoveFrom(other);
}
return *this;
}
~MirrorMapping() {
Release();
}
[[nodiscard]] bool IsValid() const noexcept {
return mapped_base != nullptr;
}
[[nodiscard]] explicit operator bool() const noexcept {
return IsValid();
}
[[nodiscard]] u8* Data() noexcept {
return mapped_base ? mapped_base + data_offset : nullptr;
}
[[nodiscard]] const u8* Data() const noexcept {
return mapped_base ? mapped_base + data_offset : nullptr;
}
[[nodiscard]] size_t Size() const noexcept {
return mapped_size >= data_offset ? mapped_size - data_offset : 0;
}
private:
void MoveFrom(MirrorMapping& other) noexcept {
mapped_base = std::exchange(other.mapped_base, nullptr);
mapped_size = std::exchange(other.mapped_size, 0);
data_offset = std::exchange(other.data_offset, 0);
}
void Release() noexcept {
#if defined(__linux__)
if (mapped_base) {
munmap(mapped_base, mapped_size);
}
#endif
mapped_base = nullptr;
mapped_size = 0;
data_offset = 0;
}
u8* mapped_base{};
size_t mapped_size{};
size_t data_offset{};
};
DeviceMemoryManager(const DeviceMemory& device_memory);
~DeviceMemoryManager();
@@ -191,11 +118,6 @@ public:
void WriteBlock(DAddr address, const void* src_pointer, size_t size);
void WriteBlockUnsafe(DAddr address, const void* src_pointer, size_t size);
[[nodiscard]] MirrorMapping CreateMirrorMapping(DAddr address, size_t size) const;
[[nodiscard]] u64 GetMappingVersion() const noexcept {
return mapping_version.load(std::memory_order_acquire);
}
Asid RegisterProcess(Memory::Memory* memory);
void UnregisterProcess(Asid id);
@@ -314,7 +236,6 @@ private:
std::unique_ptr<CachedPages> cached_pages;
Common::RangeMutex counter_guard;
std::mutex mapping_guard;
std::atomic<u64> mapping_version{1};
};
-89
View File
@@ -4,10 +4,6 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#if defined(__linux__) && !defined(_GNU_SOURCE)
#define _GNU_SOURCE
#endif
#include <atomic>
#include <limits>
#include <memory>
@@ -15,17 +11,6 @@
#include <algorithm>
#include <vector>
#if defined(__linux__)
#include <sys/mman.h>
#ifndef MREMAP_MAYMOVE
#define MREMAP_MAYMOVE 1
#endif
#ifndef MREMAP_FIXED
#define MREMAP_FIXED 2
#endif
extern "C" void* mremap(void* old_address, size_t old_size, size_t new_size, int flags, ...);
#endif
#include "common/address_space.h"
#include "common/address_space.inc"
#include "common/alignment.h"
@@ -255,7 +240,6 @@ void DeviceMemoryManager<Traits>::Map(DAddr address, VAddr virtual_address, size
impl->multi_dev_address.Register(new_dev, start_id);
}
t_slot = {};
mapping_version.fetch_add(1, std::memory_order_release);
if (track) {
TrackContinuityImpl(address, virtual_address, size, asid);
}
@@ -288,7 +272,6 @@ void DeviceMemoryManager<Traits>::Unmap(DAddr address, size_t size) {
}
}
t_slot = {};
mapping_version.fetch_add(1, std::memory_order_release);
}
template <typename Traits>
void DeviceMemoryManager<Traits>::TrackContinuityImpl(DAddr address, VAddr virtual_address,
@@ -332,78 +315,6 @@ const u8* DeviceMemoryManager<Traits>::GetSpan(const DAddr src_addr, const std::
return nullptr;
}
template <typename Traits>
typename DeviceMemoryManager<Traits>::MirrorMapping DeviceMemoryManager<Traits>::CreateMirrorMapping(
DAddr address, size_t size) const {
#if !defined(__linux__)
return {};
#else
if (size == 0) {
return {};
}
const DAddr aligned_address = Common::AlignDown(address, DAddr{page_size});
const size_t data_offset = static_cast<size_t>(address - aligned_address);
const size_t mapped_size = Common::AlignUp(size + data_offset, page_size);
struct Segment {
const u8* source;
size_t size;
};
std::vector<Segment> segments;
segments.reserve(Common::DivCeil(mapped_size, page_size));
size_t remaining_size = mapped_size;
size_t page_index = aligned_address >> page_bits;
while (remaining_size > 0) {
const size_t next_pages = std::size_t(tracked_entries[page_index].continuity_tracker);
const size_t copy_amount = (std::min)(next_pages << page_bits, remaining_size);
const auto phys_addr = tracked_entries[page_index].compressed_physical_ptr;
if (phys_addr == 0) {
return {};
}
const auto* source =
GetPointerFromRaw<u8>(PAddr(phys_addr - 1U) << Memory::YUZU_PAGEBITS);
if (!segments.empty() && segments.back().source + segments.back().size == source) {
segments.back().size += copy_amount;
} else {
segments.push_back({source, copy_amount});
}
page_index += next_pages;
remaining_size -= copy_amount;
}
void* const mirror_base =
mmap(nullptr, mapped_size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (mirror_base == MAP_FAILED) {
return {};
}
size_t mirror_offset = 0;
for (const auto& segment : segments) {
void* const target = static_cast<u8*>(mirror_base) + mirror_offset;
void* const result = mremap(const_cast<u8*>(segment.source), 0, segment.size,
MREMAP_MAYMOVE | MREMAP_FIXED, target);
if (result == MAP_FAILED) {
munmap(mirror_base, mapped_size);
return {};
}
if (mprotect(result, segment.size, PROT_READ | PROT_WRITE) != 0) {
munmap(mirror_base, mapped_size);
return {};
}
mirror_offset += segment.size;
}
return MirrorMapping{static_cast<u8*>(mirror_base), mapped_size, data_offset};
#endif
}
template <typename Traits>
void DeviceMemoryManager<Traits>::InnerGatherDeviceAddresses(Common::ScratchBuffer<u32>& buffer,
PAddr address) {
+4 -4
View File
@@ -629,7 +629,7 @@ Errno BSD::BindImpl(s32 fd, std::span<const u8> addr) {
if (!IsFileDescriptorValid(fd)) {
return Errno::BADF;
}
ASSERT(addr.size() == sizeof(SockAddrIn));
ASSERT(addr.size() >= 16);
auto addr_in = GetValue<SockAddrIn>(addr);
return Translate(file_descriptors[fd]->socket->Bind(Translate(addr_in)));
@@ -640,7 +640,7 @@ Errno BSD::ConnectImpl(s32 fd, std::span<const u8> addr) {
return Errno::BADF;
}
UNIMPLEMENTED_IF(addr.size() != sizeof(SockAddrIn));
ASSERT(addr.size() >= 16);
auto addr_in = GetValue<SockAddrIn>(addr);
const Errno result = Translate(file_descriptors[fd]->socket->Connect(Translate(addr_in)));
@@ -874,7 +874,7 @@ std::pair<s32, Errno> BSD::RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& mess
if (ret < 0) {
addr.clear();
} else {
ASSERT(addr.size() == sizeof(SockAddrIn));
ASSERT(addr.size() >= 16);
const SockAddrIn result = Translate(addr_in);
PutValue(addr, result);
}
@@ -899,7 +899,7 @@ std::pair<s32, Errno> BSD::SendToImpl(s32 fd, u32 flags, std::span<const u8> mes
Network::SockAddrIn addr_in;
Network::SockAddrIn* p_addr_in = nullptr;
if (!addr.empty()) {
ASSERT(addr.size() == sizeof(SockAddrIn));
ASSERT(addr.size() >= 16);
auto guest_addr_in = GetValue<SockAddrIn>(addr);
addr_in = Translate(guest_addr_in);
p_addr_in = &addr_in;
+1 -1
View File
@@ -238,7 +238,7 @@ static std::vector<u8> SerializeAddrInfo(const std::vector<Network::AddrInfo>& v
Append<u32_be>(data, static_cast<u32>(Translate(addrinfo.family))); // ai_family
Append<u32_be>(data, static_cast<u32>(Translate(addrinfo.socket_type))); // ai_socktype
Append<u32_be>(data, static_cast<u32>(Translate(addrinfo.protocol))); // ai_protocol
Append<u32_be>(data, sizeof(SockAddrIn)); // ai_addrlen
Append<u32_be>(data, 16); // ai_addrlen
// ^ *not* sizeof(SerializedSockAddrIn), not that it matters since they're the same size
// ai_addr:
+2 -1
View File
@@ -110,8 +110,9 @@ struct SockAddrIn {
u8 family;
u16 portno;
std::array<u8, 4> ip;
std::array<u8, 8> zeroes;
std::array<u8, 248> zeroes;
};
static_assert(sizeof(SockAddrIn) == 0x100);
enum class PollEvents : u16 {
// Using Pascal case because IN is a macro on Windows.
@@ -265,13 +265,9 @@ PollEvents Translate(Network::PollEvents flags) {
}
Network::SockAddrIn Translate(SockAddrIn value) {
if (value.len != 0 && value.len != sizeof(value) && value.len != 6) {
LOG_WARNING(Service, "Unexpected SockAddrIn len={}, expected 0, {}, or 6",
value.len, sizeof(value));
}
// All lengths are valid, from [0 upto 256]
return {
.family = Translate(static_cast<Domain>(value.family)),
.family = Translate(Domain(value.family)),
.ip = value.ip,
.portno = static_cast<u16>(value.portno >> 8 | value.portno << 8),
};
@@ -279,7 +275,7 @@ Network::SockAddrIn Translate(SockAddrIn value) {
SockAddrIn Translate(Network::SockAddrIn value) {
return {
.len = sizeof(SockAddrIn),
.len = 16,
.family = static_cast<u8>(Translate(value.family)),
.portno = static_cast<u16>(value.portno >> 8 | value.portno << 8),
.ip = value.ip,
+35 -224
View File
@@ -104,51 +104,6 @@ void BufferCache<P>::TickFrame() {
RunGarbageCollector();
}
++frame_tick;
static constexpr u64 mirror_stats_log_interval = 300;
if ((frame_tick % mirror_stats_log_interval) == 0) {
const u64 upload_hit_copies = mirror_upload_hit_copies - mirror_upload_hit_copies_last;
const u64 upload_miss_copies = mirror_upload_miss_copies - mirror_upload_miss_copies_last;
const u64 upload_hit_bytes = mirror_upload_hit_bytes - mirror_upload_hit_bytes_last;
const u64 upload_miss_bytes = mirror_upload_miss_bytes - mirror_upload_miss_bytes_last;
const u64 download_hit_copies =
mirror_download_hit_copies - mirror_download_hit_copies_last;
const u64 download_miss_copies =
mirror_download_miss_copies - mirror_download_miss_copies_last;
const u64 download_hit_bytes = mirror_download_hit_bytes - mirror_download_hit_bytes_last;
const u64 download_miss_bytes =
mirror_download_miss_bytes - mirror_download_miss_bytes_last;
const u64 upload_total_copies = upload_hit_copies + upload_miss_copies;
const u64 download_total_copies = download_hit_copies + download_miss_copies;
if (upload_total_copies > 0 || download_total_copies > 0) {
const double upload_hit_ratio = upload_total_copies > 0
? (100.0 * static_cast<double>(upload_hit_copies) /
static_cast<double>(upload_total_copies))
: 0.0;
const double download_hit_ratio =
download_total_copies > 0
? (100.0 * static_cast<double>(download_hit_copies) /
static_cast<double>(download_total_copies))
: 0.0;
LOG_INFO(HW_GPU,
"Buffer mirror counters (last {} frames): upload hit/miss copies = {}/{}, "
"hit ratio = {:.2f}%, bytes hit/miss = {}/{}, download hit/miss copies = "
"{}/{}, hit ratio = {:.2f}%, bytes hit/miss = {}/{}",
mirror_stats_log_interval, upload_hit_copies, upload_miss_copies,
upload_hit_ratio, upload_hit_bytes, upload_miss_bytes, download_hit_copies,
download_miss_copies, download_hit_ratio, download_hit_bytes,
download_miss_bytes);
}
mirror_upload_hit_copies_last = mirror_upload_hit_copies;
mirror_upload_miss_copies_last = mirror_upload_miss_copies;
mirror_upload_hit_bytes_last = mirror_upload_hit_bytes;
mirror_upload_miss_bytes_last = mirror_upload_miss_bytes;
mirror_download_hit_copies_last = mirror_download_hit_copies;
mirror_download_miss_copies_last = mirror_download_miss_copies;
mirror_download_hit_bytes_last = mirror_download_hit_bytes;
mirror_download_miss_bytes_last = mirror_download_miss_bytes;
}
delayed_destruction_ring.Tick();
for (auto& buffer : async_buffers_death_ring) {
@@ -166,6 +121,15 @@ void BufferCache<P>::WriteMemory(DAddr device_addr, u64 size) {
memory_tracker.MarkRegionAsCpuModified(device_addr, size);
}
template <class P>
void BufferCache<P>::UnmapMemory(DAddr device_addr, u64 size) {
if (memory_tracker.IsRegionGpuModified(device_addr, size)) {
ClearDownload(device_addr, size);
gpu_modified_ranges.Subtract(device_addr, size);
}
memory_tracker.UnmarkRegionAsCpuModified(device_addr, size);
}
template <class P>
void BufferCache<P>::CachedWriteMemory(DAddr device_addr, u64 size) {
const bool is_dirty = IsRegionRegistered(device_addr, size);
@@ -369,6 +333,7 @@ void BufferCache<P>::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr
const std::optional<DAddr> device_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
const Binding binding{
.device_addr = *device_addr,
.gpu_addr = gpu_addr,
.size = size,
.buffer_id = BufferId{},
};
@@ -985,12 +950,23 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
return alignment > 1 && (offset % alignment) != 0;
}
}();
const bool use_fast_buffer = needs_alignment_stream
|| (has_host_buffer && size <= channel_state->uniform_buffer_skip_cache_size
&& !memory_tracker.IsRegionGpuModified(device_addr, size));
const bool has_gpu_range = binding.gpu_addr != 0 && size != 0;
const bool gpu_fully_mapped =
has_gpu_range && gpu_memory->IsFullyMappedRange(binding.gpu_addr, size);
const bool gpu_continuous =
gpu_fully_mapped && gpu_memory->IsContinuousRange(binding.gpu_addr, size);
const bool needs_virtual_uniform_stream =
has_gpu_range && (!gpu_fully_mapped || !gpu_continuous);
const bool region_gpu_modified =
has_host_buffer && memory_tracker.IsRegionGpuModified(device_addr, size);
const bool use_fast_buffer = needs_alignment_stream ||
needs_virtual_uniform_stream ||
(has_host_buffer &&
size <= channel_state->uniform_buffer_skip_cache_size &&
!region_gpu_modified);
if (use_fast_buffer) {
if constexpr (IS_OPENGL) {
if (runtime.HasFastBufferSubData()) {
if (!needs_virtual_uniform_stream && runtime.HasFastBufferSubData()) {
// Fast path for Nvidia
const bool should_fast_bind =
!HasFastUniformBufferBound(stage, binding_index) ||
@@ -1299,11 +1275,16 @@ void BufferCache<P>::UpdateIndexBuffer() {
const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin);
const u32 draw_size =
(index_buffer_ref.count + index_buffer_ref.first) * index_buffer_ref.FormatSizeInBytes();
const u32 size = (std::min)(address_size, draw_size);
u32 size = (std::min)(address_size, draw_size);
if (size == 0 || !device_addr) {
channel_state->index_buffer = NULL_BINDING;
return;
}
size = static_cast<u32>(gpu_memory->MaxContinuousRange(gpu_addr_begin, size));
if (size == 0) {
channel_state->index_buffer = NULL_BINDING;
return;
}
channel_state->index_buffer = Binding{
.device_addr = *device_addr,
.size = size,
@@ -1341,9 +1322,7 @@ void BufferCache<P>::UpdateVertexBuffer(u32 index) {
UpdateVertexBufferSlot(index, NULL_BINDING);
return;
}
if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end) || size >= 64_MiB) {
size = static_cast<u32>(gpu_memory->MaxContinuousRange(gpu_addr_begin, size));
}
const BufferId buffer_id = FindBuffer(*device_addr, size);
const Binding binding{
.device_addr = *device_addr,
@@ -1609,21 +1588,6 @@ BufferId BufferCache<P>::CreateBuffer(DAddr device_addr, u32 wanted_size) {
const u32 size = static_cast<u32>(overlap.end - overlap.begin);
const BufferId new_buffer_id = slot_buffers.insert(runtime, overlap.begin, size);
auto& new_buffer = slot_buffers[new_buffer_id];
const u64 current_mapping_version = device_memory.GetMappingVersion();
if (mirror_mapping_version != current_mapping_version) {
buffer_mirrors.clear();
mirror_mapping_version = current_mapping_version;
}
buffer_mirrors.erase(new_buffer.CpuAddr());
if (auto mirror =
device_memory.CreateMirrorMapping(new_buffer.CpuAddr(), new_buffer.SizeBytes());
mirror) {
buffer_mirrors.emplace(new_buffer.CpuAddr(), std::move(mirror));
if (!mirror_creation_logged) [[unlikely]] {
LOG_INFO(HW_GPU, "Buffer mirror mapping enabled (first successful mapping)");
mirror_creation_logged = true;
}
}
const size_t size_bytes = new_buffer.SizeBytes();
runtime.ClearBuffer(new_buffer, 0, size_bytes, 0);
new_buffer.MarkUsage(0, size_bytes);
@@ -1717,52 +1681,15 @@ void BufferCache<P>::ImmediateUploadMemory([[maybe_unused]] Buffer& buffer,
[[maybe_unused]] std::span<const BufferCopy> copies) {
if constexpr (!USE_MEMORY_MAPS_FOR_UPLOADS) {
std::span<u8> immediate_buffer;
const auto resolve_mirror_pointer = [&]() -> const u8* {
const u64 current_mapping_version = device_memory.GetMappingVersion();
if (mirror_mapping_version != current_mapping_version) {
buffer_mirrors.clear();
mirror_mapping_version = current_mapping_version;
}
auto mirror_it = buffer_mirrors.find(buffer.CpuAddr());
if (mirror_it == buffer_mirrors.end()) {
if (auto mirror =
device_memory.CreateMirrorMapping(buffer.CpuAddr(), buffer.SizeBytes());
mirror) {
auto [it, inserted] =
buffer_mirrors.emplace(buffer.CpuAddr(), std::move(mirror));
mirror_it = it;
if (inserted && !mirror_creation_logged) [[unlikely]] {
LOG_INFO(HW_GPU, "Buffer mirror mapping enabled (first successful mapping)");
mirror_creation_logged = true;
}
}
}
return mirror_it != buffer_mirrors.end() ? mirror_it->second.Data() : nullptr;
};
const u8* const mirror_pointer = resolve_mirror_pointer();
for (const BufferCopy& copy : copies) {
std::span<const u8> upload_span;
const DAddr device_addr = buffer.CpuAddr() + copy.dst_offset;
if (mirror_pointer != nullptr) {
mirror_upload_hit_copies++;
mirror_upload_hit_bytes += copy.size;
if (!mirror_upload_logged) [[unlikely]] {
LOG_INFO(HW_GPU, "Buffer mirror fast path active for upload sync");
mirror_upload_logged = true;
}
upload_span =
std::span(mirror_pointer + static_cast<size_t>(copy.dst_offset), copy.size);
} else if (IsRangeGranular(device_addr, copy.size)) {
mirror_upload_miss_copies++;
mirror_upload_miss_bytes += copy.size;
if (IsRangeGranular(device_addr, copy.size)) {
auto* const ptr = device_memory.GetPointer<u8>(device_addr);
if (ptr != nullptr) {
upload_span = std::span(ptr, copy.size);
}
} else {
mirror_upload_miss_copies++;
mirror_upload_miss_bytes += copy.size;
if (immediate_buffer.empty()) {
immediate_buffer = ImmediateBuffer(largest_copy);
}
@@ -1781,47 +1708,10 @@ void BufferCache<P>::MappedUploadMemory([[maybe_unused]] Buffer& buffer,
if constexpr (USE_MEMORY_MAPS) {
auto upload_staging = runtime.UploadStagingBuffer(total_size_bytes);
const std::span<u8> staging_pointer = upload_staging.mapped_span;
const auto resolve_mirror_pointer = [&]() -> const u8* {
const u64 current_mapping_version = device_memory.GetMappingVersion();
if (mirror_mapping_version != current_mapping_version) {
buffer_mirrors.clear();
mirror_mapping_version = current_mapping_version;
}
auto mirror_it = buffer_mirrors.find(buffer.CpuAddr());
if (mirror_it == buffer_mirrors.end()) {
if (auto mirror =
device_memory.CreateMirrorMapping(buffer.CpuAddr(), buffer.SizeBytes());
mirror) {
auto [it, inserted] =
buffer_mirrors.emplace(buffer.CpuAddr(), std::move(mirror));
mirror_it = it;
if (inserted && !mirror_creation_logged) [[unlikely]] {
LOG_INFO(HW_GPU, "Buffer mirror mapping enabled (first successful mapping)");
mirror_creation_logged = true;
}
}
}
return mirror_it != buffer_mirrors.end() ? mirror_it->second.Data() : nullptr;
};
const u8* const mirror_pointer = resolve_mirror_pointer();
for (BufferCopy& copy : copies) {
u8* const src_pointer = staging_pointer.data() + copy.src_offset;
const DAddr device_addr = buffer.CpuAddr() + copy.dst_offset;
if (mirror_pointer != nullptr) {
mirror_upload_hit_copies++;
mirror_upload_hit_bytes += copy.size;
if (!mirror_upload_logged) [[unlikely]] {
LOG_INFO(HW_GPU, "Buffer mirror fast path active for upload sync");
mirror_upload_logged = true;
}
std::memcpy(src_pointer, mirror_pointer + static_cast<size_t>(copy.dst_offset),
copy.size);
} else {
mirror_upload_miss_copies++;
mirror_upload_miss_bytes += copy.size;
device_memory.ReadBlockUnsafe(device_addr, src_pointer, copy.size);
}
device_memory.ReadBlockUnsafe(device_addr, src_pointer, copy.size);
// Apply the staging offset
copy.src_offset += upload_staging.offset;
@@ -1914,30 +1804,6 @@ void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, DAddr device_addr, u64
if constexpr (USE_MEMORY_MAPS) {
auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes);
const u8* const mapped_memory = download_staging.mapped_span.data();
const auto resolve_mirror_pointer = [&]() -> u8* {
const u64 current_mapping_version = device_memory.GetMappingVersion();
if (mirror_mapping_version != current_mapping_version) {
buffer_mirrors.clear();
mirror_mapping_version = current_mapping_version;
}
auto mirror_it = buffer_mirrors.find(buffer.CpuAddr());
if (mirror_it == buffer_mirrors.end()) {
if (auto mirror =
device_memory.CreateMirrorMapping(buffer.CpuAddr(), buffer.SizeBytes());
mirror) {
auto [it, inserted] =
buffer_mirrors.emplace(buffer.CpuAddr(), std::move(mirror));
mirror_it = it;
if (inserted && !mirror_creation_logged) [[unlikely]] {
LOG_INFO(HW_GPU, "Buffer mirror mapping enabled (first successful mapping)");
mirror_creation_logged = true;
}
}
}
return mirror_it != buffer_mirrors.end() ? mirror_it->second.Data() : nullptr;
};
u8* const mirror_pointer = resolve_mirror_pointer();
const std::span<BufferCopy> copies_span(copies.data(), copies.data() + copies.size());
for (BufferCopy& copy : copies) {
// Modify copies to have the staging offset in mind
@@ -1951,65 +1817,14 @@ void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, DAddr device_addr, u64
// Undo the modified offset
const u64 dst_offset = copy.dst_offset - download_staging.offset;
const u8* copy_mapped_memory = mapped_memory + dst_offset;
if (mirror_pointer != nullptr) {
mirror_download_hit_copies++;
mirror_download_hit_bytes += copy.size;
if (!mirror_download_logged) [[unlikely]] {
LOG_INFO(HW_GPU, "Buffer mirror fast path active for download sync");
mirror_download_logged = true;
}
std::memcpy(mirror_pointer + static_cast<size_t>(copy.src_offset),
copy_mapped_memory, copy.size);
} else {
mirror_download_miss_copies++;
mirror_download_miss_bytes += copy.size;
device_memory.WriteBlockUnsafe(copy_device_addr, copy_mapped_memory, copy.size);
}
device_memory.WriteBlockUnsafe(copy_device_addr, copy_mapped_memory, copy.size);
}
} else {
const std::span<u8> immediate_buffer = ImmediateBuffer(largest_copy);
const auto resolve_mirror_pointer = [&]() -> u8* {
const u64 current_mapping_version = device_memory.GetMappingVersion();
if (mirror_mapping_version != current_mapping_version) {
buffer_mirrors.clear();
mirror_mapping_version = current_mapping_version;
}
auto mirror_it = buffer_mirrors.find(buffer.CpuAddr());
if (mirror_it == buffer_mirrors.end()) {
if (auto mirror =
device_memory.CreateMirrorMapping(buffer.CpuAddr(), buffer.SizeBytes());
mirror) {
auto [it, inserted] =
buffer_mirrors.emplace(buffer.CpuAddr(), std::move(mirror));
mirror_it = it;
if (inserted && !mirror_creation_logged) [[unlikely]] {
LOG_INFO(HW_GPU, "Buffer mirror mapping enabled (first successful mapping)");
mirror_creation_logged = true;
}
}
}
return mirror_it != buffer_mirrors.end() ? mirror_it->second.Data() : nullptr;
};
u8* const mirror_pointer = resolve_mirror_pointer();
for (const BufferCopy& copy : copies) {
buffer.ImmediateDownload(copy.src_offset, immediate_buffer.subspan(0, copy.size));
const DAddr copy_device_addr = buffer.CpuAddr() + copy.src_offset;
if (mirror_pointer != nullptr) {
mirror_download_hit_copies++;
mirror_download_hit_bytes += copy.size;
if (!mirror_download_logged) [[unlikely]] {
LOG_INFO(HW_GPU, "Buffer mirror fast path active for download sync");
mirror_download_logged = true;
}
std::memcpy(mirror_pointer + static_cast<size_t>(copy.src_offset),
immediate_buffer.data(), copy.size);
} else {
mirror_download_miss_copies++;
mirror_download_miss_bytes += copy.size;
device_memory.WriteBlockUnsafe(copy_device_addr, immediate_buffer.data(),
copy.size);
}
device_memory.WriteBlockUnsafe(copy_device_addr, immediate_buffer.data(), copy.size);
}
}
}
@@ -2050,10 +1865,6 @@ void BufferCache<P>::DeleteBuffer(BufferId buffer_id, bool do_not_mark) {
if (!do_not_mark) {
Buffer& buffer = slot_buffers[buffer_id];
memory_tracker.MarkRegionAsCpuModified(buffer.CpuAddr(), buffer.SizeBytes());
buffer_mirrors.erase(buffer.CpuAddr());
} else {
const Buffer& buffer = slot_buffers[buffer_id];
buffer_mirrors.erase(buffer.CpuAddr());
}
Unregister(buffer_id);
@@ -81,6 +81,7 @@ static constexpr u32 DEFAULT_SKIP_CACHE_SIZE = static_cast<u32>(4_KiB);
struct Binding {
DAddr device_addr{};
GPUVAddr gpu_addr{};
u32 size{};
BufferId buffer_id;
};
@@ -91,6 +92,7 @@ struct TextureBufferBinding : Binding {
static constexpr Binding NULL_BINDING{
.device_addr = 0,
.gpu_addr = 0,
.size = 0,
.buffer_id = NULL_BUFFER_ID,
};
@@ -217,6 +219,8 @@ public:
void WriteMemory(DAddr device_addr, u64 size);
void UnmapMemory(DAddr device_addr, u64 size);
void CachedWriteMemory(DAddr device_addr, u64 size);
bool OnCPUWrite(DAddr device_addr, u64 size);
@@ -473,8 +477,6 @@ private:
Tegra::MaxwellDeviceMemoryManager& device_memory;
Common::SlotVector<Buffer> slot_buffers;
ankerl::unordered_dense::map<DAddr, Tegra::MaxwellDeviceMemoryManager::MirrorMapping>
buffer_mirrors;
#ifdef YUZU_LEGACY
static constexpr size_t TICKS_TO_DESTROY = 6;
#else
@@ -524,26 +526,6 @@ private:
std::array<BufferId, ((1ULL << 34) >> CACHING_PAGEBITS)> page_table;
Common::ScratchBuffer<u8> tmp_buffer;
bool mirror_creation_logged = false;
bool mirror_upload_logged = false;
bool mirror_download_logged = false;
u64 mirror_mapping_version = 0;
u64 mirror_upload_hit_copies = 0;
u64 mirror_upload_miss_copies = 0;
u64 mirror_upload_hit_bytes = 0;
u64 mirror_upload_miss_bytes = 0;
u64 mirror_download_hit_copies = 0;
u64 mirror_download_miss_copies = 0;
u64 mirror_download_hit_bytes = 0;
u64 mirror_download_miss_bytes = 0;
u64 mirror_upload_hit_copies_last = 0;
u64 mirror_upload_miss_copies_last = 0;
u64 mirror_upload_hit_bytes_last = 0;
u64 mirror_upload_miss_bytes_last = 0;
u64 mirror_download_hit_copies_last = 0;
u64 mirror_download_miss_copies_last = 0;
u64 mirror_download_hit_bytes_last = 0;
u64 mirror_download_miss_bytes_last = 0;
};
} // namespace VideoCommon
+25 -5
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@@ -21,7 +21,7 @@
namespace Tegra {
constexpr u32 MacroRegistersStart = 0xE00;
[[maybe_unused]] constexpr u32 ComputeInline = 0x6D;
constexpr u32 ComputeInline = 0x6D;
DmaPusher::DmaPusher(Core::System& system_, GPU& gpu_, MemoryManager& memory_manager_,
Control::ChannelState& channel_state_)
@@ -62,6 +62,8 @@ bool DmaPusher::Step() {
}
if (prefetch_size > 0) {
processing_dma_segment = false;
dma_segment_safe_read = false;
ProcessCommands(command_list.prefetch_command_list);
dma_pushbuffer.pop();
return true;
@@ -78,18 +80,24 @@ bool DmaPusher::Step() {
synced = false;
}
if (header.size > 0 && dma_state.method >= MacroRegistersStart && subchannels[dma_state.subchannel]) {
if (!Settings::getDebugKnobAt(1) && header.size > 0 && dma_state.method >= MacroRegistersStart && subchannels[dma_state.subchannel]) {
subchannels[dma_state.subchannel]->current_dirty = memory_manager.IsMemoryDirty(dma_state.dma_get, header.size * sizeof(u32));
}
if (header.size > 0) {
if (Settings::IsDMALevelDefault() ? (Settings::IsGPULevelMedium() || Settings::IsGPULevelHigh()) : Settings::IsDMALevelSafe()) {
processing_dma_segment = true;
dma_segment_safe_read = Settings::IsDMALevelDefault()
? !Settings::IsGPULevelLow()
: Settings::IsDMALevelSafe();
if (dma_segment_safe_read) {
Tegra::Memory::GpuGuestMemory<Tegra::CommandHeader, Tegra::Memory::GuestMemoryFlags::SafeRead>headers(memory_manager, dma_state.dma_get, header.size, &command_headers);
ProcessCommands(headers);
} else {
Tegra::Memory::GpuGuestMemory<Tegra::CommandHeader, Tegra::Memory::GuestMemoryFlags::UnsafeRead>headers(memory_manager, dma_state.dma_get, header.size, &command_headers);
ProcessCommands(headers);
}
processing_dma_segment = false;
dma_segment_safe_read = false;
}
if (++dma_pushbuffer_subindex >= command_list_size) {
@@ -117,7 +125,19 @@ void DmaPusher::ProcessCommands(std::span<const CommandHeader> commands) {
auto const& command_header = commands[index]; //must ref (MUltiMethod re)
dma_state.dma_word_offset = u32(index * sizeof(u32));
const u32 max_write = u32(std::min<std::size_t>(index + dma_state.method_count, commands.size()) - index);
CallMultiMethod(&command_header.argument, max_write);
const auto engine = subchannel_type[dma_state.subchannel];
const bool is_kepler_payload = engine == Engines::EngineTypes::KeplerCompute && dma_state.method == ComputeInline;
const bool is_macro_payload = engine == Engines::EngineTypes::Maxwell3D && dma_state.method >= MacroRegistersStart;
const bool refresh_payload = !dma_segment_safe_read && processing_dma_segment && (is_kepler_payload || is_macro_payload);
if (refresh_payload && Settings::getDebugKnobAt(1)) {
const GPUVAddr payload_addr = dma_state.dma_get + dma_state.dma_word_offset;
Tegra::Memory::GpuGuestMemory<Tegra::CommandHeader,
Tegra::Memory::GuestMemoryFlags::SafeRead>
payload(memory_manager, payload_addr, max_write, &refreshed_command_payload);
CallMultiMethod(&payload[0].argument, max_write);
} else {
CallMultiMethod(&command_header.argument, max_write);
}
dma_state.method_count -= max_write;
dma_state.is_last_call = true;
index += max_write;
+5 -1
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@@ -157,6 +157,8 @@ private:
Common::ScratchBuffer<CommandHeader>
command_headers; ///< Buffer for list of commands fetched at once
Common::ScratchBuffer<CommandHeader>
refreshed_command_payload; ///< Buffer for small payload refreshes inside unsafe DMA segments
std::queue<CommandList> dma_pushbuffer; ///< Queue of command lists to be processed
std::size_t dma_pushbuffer_subindex{}; ///< Index within a command list within the pushbuffer
@@ -174,6 +176,8 @@ private:
DmaState dma_state{};
bool dma_increment_once{};
bool processing_dma_segment{};
bool dma_segment_safe_read{};
const bool ib_enable{true}; ///< IB mode enabled
+202
View File
@@ -0,0 +1,202 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/*
Discards invalid segments to avoid issues
(Ender Magnolia)
dma_method subch mode segment_method(s) payload_size guard guard_point guard_point_alt
0x06 1-kepler 3-NonIncreasing 0x220(?) 3 Compute kepler_compute.cpp::ProcessLaunch vk_rasterizer.cpp::DispatchCompute
0xE3B 0-macro 5-IncreaseOnce 0xE3A 5(?) Draw macro.cpp::HLE_Draw* draw_manager.cpp::ProcessDraw*
Usage:
#include "video_core/engines/crash_guard.h"
//in Maxwell3D::DrawManager::ProcessDraw (after UpdateTopology)
if (ShouldDiscardCorruptedDraw(draw_state, nullptr, draw_indexed, instance_count)) {
return;
}
//in Maxwell3D::DrawManager::ProcessDrawIndirect (after UpdateTopology)
if (ShouldDiscardCorruptedDraw(draw_state, &indirect_state, indirect_state.is_indexed, 1)) {
return;
}
//in KeplerCompute::ProcessLaunch (before DispatchCompute)
if (ShouldDiscardCorruptedCompute(regs)) {
return;
}
*/
#pragma once
#include <atomic>
#include <limits>
#include "common/logging.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/engines/kepler_compute.h"
//#include "video_core/engines/draw_manager.h"
namespace Tegra::Engines {
[[nodiscard]] inline bool ShouldDiscardCorruptedCompute(const KeplerCompute::LaunchParams& launch_params) {
// Similar to draw validation, but for compute dispatches. Culling these can fix some compute shader crashes.
// The main check here is that the grid dimensions are not unreasonably large, which is a common result of corrupted register state.
constexpr u32 grid_dim_limit = (1u << 16) - 1; // 65536 in each dimension for vulkan, narrower than QMD's X dimension limit of 2^31-1 (Y/Z are equal for both)
const u32 grid_dim_x = launch_params.grid_dim_x;
const u32 grid_dim_y = launch_params.grid_dim_y;
const u32 grid_dim_z = launch_params.grid_dim_z;
const bool x_exceeded = grid_dim_x > grid_dim_limit;
const bool y_exceeded = grid_dim_y > grid_dim_limit;
const bool z_exceeded = grid_dim_z > grid_dim_limit;
if (x_exceeded || y_exceeded || z_exceeded) {
LOG_WARNING(HW_GPU, "Discarding compute dispatch with invalid grid dimensions: ({}, {}, {})", grid_dim_x,
grid_dim_y, grid_dim_z);
return true;
}
return false;
}
[[nodiscard]] inline bool ShouldDiscardCorruptedDraw(const Maxwell3D::DrawManager::State& draw_state,
const Maxwell3D::DrawManager::IndirectParams* indirect_state,
bool draw_indexed, u32 instance_count) {
constexpr u32 draw_count_limit = 1u << 22; //endermag save screen has > 2^19 valid counts; some titles legitimately exceed 2^20 on indexed indirect (index_buffer.count is an upper bound, not per-draw)
constexpr u32 instance_count_limit = 1u << 17;
constexpr u64 draw_span_limit_bytes = 1u << 27;
// first/base_index/base_instance limits: values above these are garbage register state
// (e.g. float bit-patterns like 0x3F038000 written into integer fields).
// No real draw skips >128M indices (first), offsets >16M vertices (base_index),
// or starts past >1M instances (base_instance).
constexpr u64 first_limit = 1u << 27; // 128M index/vertex offset
constexpr u64 base_index_limit = 1u << 24; // 16M vertex index offset
constexpr u64 base_instance_limit = 1u << 20; // 1M instance offset
constexpr size_t indirect_draw_count_limit = 1u << 18;
constexpr size_t indirect_buffer_limit = 1u << 24;
const u64 count = draw_indexed ? static_cast<u64>(draw_state.index_buffer.count)
: static_cast<u64>(draw_state.vertex_buffer.count);
const u64 first = draw_indexed ? static_cast<u64>(draw_state.index_buffer.first)
: static_cast<u64>(draw_state.vertex_buffer.first);
const u64 base_index = static_cast<u64>(draw_state.base_index);
const u64 base_instance = static_cast<u64>(draw_state.base_instance);
bool index_end_overflow = false;
bool span_overflow = false;
bool bounds_invalid = false;
bool span_exceeds_available = false;
u64 span_bytes = 0;
u64 available = 0;
u64 index_end = first;
if (draw_indexed) {
index_end_overflow = first > (std::numeric_limits<u64>::max() - count);
index_end = index_end_overflow ? std::numeric_limits<u64>::max() : (first + count);
const u64 format_size = static_cast<u64>(draw_state.index_buffer.FormatSizeInBytes());
span_overflow = format_size != 0 &&
index_end > (std::numeric_limits<u64>::max() / format_size);
span_bytes = span_overflow ? std::numeric_limits<u64>::max() : (index_end * format_size);
const GPUVAddr start = draw_state.index_buffer.StartAddress();
const GPUVAddr end = draw_state.index_buffer.EndAddress();
bounds_invalid = end < start;
if (!bounds_invalid) {
available = (end - start) + 1;
span_exceeds_available = span_bytes > available;
}
}
// For indexed draws, span_exceeded and span_exceeds_available are the real safety nets —
// applying a count limit here causes false positives on large legitimate index draws.
// For non-indexed draws there is no span to check, so count is the only guard.
const bool validate_count = (indirect_state == nullptr) ? !draw_indexed : draw_indexed;
const bool count_exceeded = validate_count && count > draw_count_limit;
const bool instance_exceeded =
(indirect_state == nullptr) && (static_cast<u64>(instance_count) > instance_count_limit);
const bool span_exceeded = draw_indexed && span_bytes > draw_span_limit_bytes;
const bool first_exceeded = first > first_limit;
const bool base_index_exceeded = base_index > base_index_limit;
const bool base_instance_exceeded = base_instance > base_instance_limit;
size_t max_draw_count = 0;
size_t buffer_size = 0;
size_t stride = 0;
bool indirect_count_exceeded = false;
bool indirect_buffer_exceeded = false;
bool indirect_stride_exceeded = false;
bool indirect_shape_invalid = false;
if (indirect_state != nullptr) {
max_draw_count = indirect_state->max_draw_counts;
buffer_size = indirect_state->buffer_size;
stride = indirect_state->stride;
indirect_count_exceeded = max_draw_count > indirect_draw_count_limit;
indirect_buffer_exceeded = buffer_size > indirect_buffer_limit;
indirect_stride_exceeded = stride > draw_span_limit_bytes;
if (!indirect_state->is_byte_count && max_draw_count > 1) {
if (stride == 0) {
indirect_shape_invalid = true;
} else {
const size_t command_size =
indirect_state->is_indexed ? (5 * sizeof(u32)) : (4 * sizeof(u32));
const bool draw_tail_overflow =
(max_draw_count - 1) > (std::numeric_limits<size_t>::max() / stride);
const size_t draw_tail =
draw_tail_overflow ? std::numeric_limits<size_t>::max()
: ((max_draw_count - 1) * stride);
const bool needed_overflow =
draw_tail_overflow ||
draw_tail > (std::numeric_limits<size_t>::max() - command_size);
const size_t needed_size =
needed_overflow ? std::numeric_limits<size_t>::max()
: (draw_tail + command_size);
indirect_shape_invalid = needed_overflow || needed_size > buffer_size;
}
}
}
const bool discard = count_exceeded || instance_exceeded || span_exceeded ||
index_end_overflow || span_overflow || bounds_invalid ||
span_exceeds_available || first_exceeded || base_index_exceeded ||
base_instance_exceeded || indirect_count_exceeded ||
indirect_buffer_exceeded || indirect_stride_exceeded ||
indirect_shape_invalid;
if (!discard) {
return false;
}
LOG_WARNING(
HW_GPU,
"DrawManager: blocked {} draw path={} count={} limit={} first={:#x} "
"base_index={:#x} base_instance={:#x} span_bytes={} available={} "
"flags(count_exceeded={} instance_exceeded={} span_exceeded={} "
"index_end_overflow={} span_overflow={} bounds_invalid={} span_exceeds_available={} "
"first_exceeded={} base_index_exceeded={} base_instance_exceeded={} "
"indirect_count_exceeded={} indirect_buffer_exceeded={} "
"indirect_stride_exceeded={} indirect_shape_invalid={}) "
"limits(count={} first={:#x} base_index={:#x} base_instance={:#x}) "
"max_draw_count={} buffer_size={} indirect_limits(count={} buffer={})",
draw_indexed ? "indexed" : "vertex",
indirect_state != nullptr ? "indirect" : "direct",
count, draw_count_limit, first, base_index, base_instance, span_bytes, available,
count_exceeded ? 1 : 0, instance_exceeded ? 1 : 0, span_exceeded ? 1 : 0,
index_end_overflow ? 1 : 0, span_overflow ? 1 : 0, bounds_invalid ? 1 : 0,
span_exceeds_available ? 1 : 0,
first_exceeded ? 1 : 0, base_index_exceeded ? 1 : 0, base_instance_exceeded ? 1 : 0,
indirect_count_exceeded ? 1 : 0, indirect_buffer_exceeded ? 1 : 0,
indirect_stride_exceeded ? 1 : 0, indirect_shape_invalid ? 1 : 0,
draw_span_limit_bytes, first_limit, base_index_limit, base_instance_limit,
max_draw_count, buffer_size, indirect_draw_count_limit, indirect_buffer_limit);
return true;
}
} // namespace Tegra::Engines
+3
View File
@@ -8,6 +8,7 @@
#include "video_core/dirty_flags.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/engines/crash_guard.h"
namespace Tegra::Engines {
@@ -236,6 +237,7 @@ void Maxwell3D::DrawManager::UpdateTopology(Maxwell3D& maxwell3d) {
void Maxwell3D::DrawManager::ProcessDraw(Maxwell3D& maxwell3d, bool draw_indexed, u32 instance_count) {
LOG_TRACE(HW_GPU, "called, topology={}, count={}", draw_state.topology, draw_indexed ? draw_state.index_buffer.count : draw_state.vertex_buffer.count);
UpdateTopology(maxwell3d);
if (Settings::getDebugKnobAt(2) && ShouldDiscardCorruptedDraw(draw_state, nullptr, draw_indexed, instance_count)) return;
if (maxwell3d.ShouldExecute()) {
maxwell3d.rasterizer->Draw(draw_indexed, instance_count);
}
@@ -244,6 +246,7 @@ void Maxwell3D::DrawManager::ProcessDraw(Maxwell3D& maxwell3d, bool draw_indexed
void Maxwell3D::DrawManager::ProcessDrawIndirect(Maxwell3D& maxwell3d) {
LOG_TRACE(HW_GPU, "called, topology={}, is_indexed={}, includes_count={}, buffer_size={}, max_draw_count={}", draw_state.topology, indirect_state.is_indexed, indirect_state.include_count, indirect_state.buffer_size, indirect_state.max_draw_counts);
UpdateTopology(maxwell3d);
if (Settings::getDebugKnobAt(2) && ShouldDiscardCorruptedDraw(draw_state, &indirect_state, indirect_state.is_indexed, 1)) return;
if (maxwell3d.ShouldExecute()) {
maxwell3d.rasterizer->DrawIndirect();
}
@@ -13,6 +13,7 @@
#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/textures/decoders.h"
#include "video_core/engines/crash_guard.h"
namespace Tegra::Engines {
@@ -97,6 +98,10 @@ void KeplerCompute::ProcessLaunch() {
const GPUVAddr launch_desc_loc = regs.launch_desc_loc.Address();
memory_manager.ReadBlockUnsafe(launch_desc_loc, &launch_description,
LaunchParams::NUM_LAUNCH_PARAMETERS * sizeof(u32));
if (Settings::getDebugKnobAt(2) && ShouldDiscardCorruptedCompute(launch_description)) {
return;
}
rasterizer->DispatchCompute();
}
+2 -1
View File
@@ -229,7 +229,8 @@ void Maxwell3D::ProcessMacro(u32 method, const u32* base_start, u32 amount, bool
}
void Maxwell3D::RefreshParametersImpl() {
if (!Settings::IsGPULevelHigh()) {
//if (!Settings::IsGPULevelHigh()) {
if (Settings::getDebugKnobAt(1)) {
return;
}
size_t current_index = 0;
+12 -5
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
@@ -16,6 +16,7 @@
#include <queue>
#include "common/common_types.h"
#include "common/logging.h"
#include "common/settings.h"
#include "common/thread.h"
#include "video_core/delayed_destruction_ring.h"
@@ -71,14 +72,15 @@ public:
uncommitted_operations.emplace_back(std::move(func));
}
void SignalFence(std::function<void()>&& func) {
void SignalFence(std::function<void()>&& func, bool ordered = false) {
if constexpr (!can_async_check) {
TryReleasePendingFences<false>();
}
const bool should_flush = ShouldFlush();
const bool delay_fence = Settings::IsGPULevelHigh() || (Settings::IsGPULevelMedium() && should_flush);
const bool delay_fence = ordered || Settings::IsGPULevelHigh() || Settings::getDebugKnobAt(4) ||
(Settings::IsGPULevelMedium() && should_flush);
CommitAsyncFlushes();
TFence new_fence = CreateFence(!should_flush);
TFence new_fence = CreateFence(!should_flush && !ordered);
if constexpr (can_async_check) {
guard.lock();
}
@@ -213,7 +215,12 @@ private:
if (!current_fence->IsStubbed()) {
WaitFence(current_fence);
}
PopAsyncFlushes();
try {
PopAsyncFlushes();
} catch (const std::exception& e) {
LOG_CRITICAL(Render_Vulkan, "GPUFencingThread: exception in PopAsyncFlushes: {}", e.what());
throw;
}
for (auto& operation : current_operations) {
operation();
}
+17 -1
View File
@@ -246,6 +246,23 @@ void QueryCacheBase<Traits>::CounterReport(GPUVAddr addr, QueryType counter_type
return;
}
DAddr cpu_addr = *cpu_addr_opt;
u8* pointer = impl->device_memory.template GetPointer<u8>(cpu_addr);
if (is_fence && Settings::getDebugKnobAt(0)) { //xbzk: toggle/knob here to control antiflicker
u8* pointer_timestamp = impl->device_memory.template GetPointer<u8>(cpu_addr + 8);
std::function<void()> operation([this, pointer, pointer_timestamp, payload, has_timestamp] {
if (has_timestamp) {
const u64 timestamp = impl->gpu.GetTicks();
const u64 value = static_cast<u64>(payload);
std::memcpy(pointer_timestamp, &timestamp, sizeof(timestamp));
std::memcpy(pointer, &value, sizeof(value));
} else {
std::memcpy(pointer, &payload, sizeof(payload));
}
});
const bool impose_ordering_for_fences = true;
impl->rasterizer.SignalFence(std::move(operation), impose_ordering_for_fences);
return;
}
const size_t new_query_id = streamer->WriteCounter(cpu_addr, has_timestamp, payload, subreport);
auto* query = streamer->GetQuery(new_query_id);
if (is_fence) {
@@ -258,7 +275,6 @@ void QueryCacheBase<Traits>::CounterReport(GPUVAddr addr, QueryType counter_type
return std::make_pair<u64, u32>(cur_addr >> Core::DEVICE_PAGEBITS,
static_cast<u32>(cur_addr & Core::DEVICE_PAGEMASK));
};
u8* pointer = impl->device_memory.template GetPointer<u8>(cpu_addr);
u8* pointer_timestamp = impl->device_memory.template GetPointer<u8>(cpu_addr + 8);
bool is_synced = !Settings::IsGPULevelHigh() && is_fence;
std::function<void()> operation([this, is_synced, streamer, query_base = query, query_location,
+4 -1
View File
@@ -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-License-Identifier: GPL-2.0-or-later
@@ -69,7 +72,7 @@ public:
virtual void DisableGraphicsUniformBuffer(size_t stage, u32 index) = 0;
/// Signal a GPU based semaphore as a fence
virtual void SignalFence(std::function<void()>&& func) = 0;
virtual void SignalFence(std::function<void()>&& func, bool ordered = false) = 0;
/// Send an operation to be done after a certain amount of flushes.
virtual void SyncOperation(std::function<void()>&& func) = 0;
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -63,7 +66,7 @@ VideoCore::RasterizerDownloadArea RasterizerNull::GetFlushArea(PAddr addr, u64 s
void RasterizerNull::InvalidateGPUCache() {}
void RasterizerNull::UnmapMemory(DAddr addr, u64 size) {}
void RasterizerNull::ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) {}
void RasterizerNull::SignalFence(std::function<void()>&& func) {
void RasterizerNull::SignalFence(std::function<void()>&& func, bool ordered) {
func();
}
void RasterizerNull::SyncOperation(std::function<void()>&& func) {
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -59,7 +62,7 @@ public:
void InvalidateGPUCache() override;
void UnmapMemory(DAddr addr, u64 size) override;
void ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) override;
void SignalFence(std::function<void()>&& func) override;
void SignalFence(std::function<void()>&& func, bool ordered = false) override;
void SyncOperation(std::function<void()>&& func) override;
void SignalSyncPoint(u32 value) override;
void SignalReference() override;
@@ -595,7 +595,7 @@ void RasterizerOpenGL::UnmapMemory(DAddr addr, u64 size) {
}
{
std::scoped_lock lock{buffer_cache.mutex};
buffer_cache.WriteMemory(addr, size);
buffer_cache.UnmapMemory(addr, size);
}
shader_cache.OnCacheInvalidation(addr, size);
}
@@ -607,8 +607,8 @@ void RasterizerOpenGL::ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) {
}
}
void RasterizerOpenGL::SignalFence(std::function<void()>&& func) {
fence_manager.SignalFence(std::move(func));
void RasterizerOpenGL::SignalFence(std::function<void()>&& func, bool ordered) {
fence_manager.SignalFence(std::move(func), ordered);
}
void RasterizerOpenGL::SyncOperation(std::function<void()>&& func) {
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2015 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -104,7 +107,7 @@ public:
void InvalidateGPUCache() override;
void UnmapMemory(DAddr addr, u64 size) override;
void ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) override;
void SignalFence(std::function<void()>&& func) override;
void SignalFence(std::function<void()>&& func, bool ordered = false) override;
void SyncOperation(std::function<void()>&& func) override;
void SignalSyncPoint(u32 value) override;
void SignalReference() override;
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -21,9 +24,7 @@ void InnerFence::Queue() {
if (is_stubbed) {
return;
}
// Get the current tick so we can wait for it
wait_tick = scheduler.CurrentTick();
scheduler.Flush();
wait_tick = scheduler.Flush();
}
bool InnerFence::IsSignaled() const {
@@ -7,6 +7,7 @@
#include <thread>
#include <ranges>
#include "common/logging.h"
#include "common/settings.h"
#include "video_core/renderer_vulkan/vk_master_semaphore.h"
#include "video_core/vulkan_common/vulkan_device.h"
@@ -221,7 +222,18 @@ void MasterSemaphore::WaitThread(std::stop_token token) {
wait_queue.pop();
}
fence.Wait();
const VkResult wait_result = fence.Wait();
if (wait_result == VK_ERROR_DEVICE_LOST) {
LOG_CRITICAL(Render_Vulkan,
"Fence wait returned VK_ERROR_DEVICE_LOST on infinite wait (driver GPU hang).");
device.ReportLoss();
return;
}
if (wait_result != VK_SUCCESS) {
LOG_CRITICAL(Render_Vulkan, "Fence wait failed: result={}",
static_cast<int>(wait_result));
vk::Check(wait_result);
}
fence.Reset();
{
@@ -4,6 +4,7 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <atomic>
#include <cstddef>
#include <limits>
#include <map>
@@ -74,10 +75,20 @@ public:
switch (query_result) {
case VK_SUCCESS:
return;
case VK_TIMEOUT: {
LOG_WARNING(Render_Vulkan,
"VK_TIMEOUT pool={} start={} size={}. Attempting to get over...",
index, start, size);
std::fill_n(&host_results[start], size, 0ULL);
return;
}
case VK_ERROR_DEVICE_LOST:
device.ReportLoss();
[[fallthrough]];
default:
LOG_CRITICAL(Render_Vulkan,
"GetQueryResults failed: result={} pool={} start={} size={}",
static_cast<int>(query_result), index, start, size);
throw vk::Exception(query_result);
}
}
@@ -758,7 +758,7 @@ void RasterizerVulkan::UnmapMemory(DAddr addr, u64 size) {
}
{
std::scoped_lock lock{buffer_cache.mutex};
buffer_cache.WriteMemory(addr, size);
buffer_cache.UnmapMemory(addr, size);
}
pipeline_cache.OnCacheInvalidation(addr, size);
}
@@ -770,8 +770,8 @@ void RasterizerVulkan::ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) {
}
}
void RasterizerVulkan::SignalFence(std::function<void()>&& func) {
fence_manager.SignalFence(std::move(func));
void RasterizerVulkan::SignalFence(std::function<void()>&& func, bool ordered) {
fence_manager.SignalFence(std::move(func), ordered);
}
void RasterizerVulkan::SyncOperation(std::function<void()>&& func) {
@@ -109,7 +109,7 @@ public:
void InvalidateGPUCache() override;
void UnmapMemory(DAddr addr, u64 size) override;
void ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) override;
void SignalFence(std::function<void()>&& func) override;
void SignalFence(std::function<void()>&& func, bool ordered = false) override;
void SyncOperation(std::function<void()>&& func) override;
void SignalSyncPoint(u32 value) override;
void SignalReference() override;
@@ -65,6 +65,10 @@ Scheduler::Scheduler(const Device& device_, StateTracker& state_tracker_)
Scheduler::~Scheduler() = default;
u64 Scheduler::Flush(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {
if (Settings::getDebugKnobAt(3) && chunk->Empty() && !signal_semaphore && !wait_semaphore) {//xbzk: improves fps by skipping flushes
const u64 current = CurrentTick();
return current > 0 ? current - 1 : 0;
}
// When flushing, we only send data to the worker thread; no waiting is necessary.
const u64 signal_value = SubmitExecution(signal_semaphore, wait_semaphore);
AllocateNewContext();
@@ -223,7 +227,16 @@ void Scheduler::WorkerThread(std::stop_token stop_token) {
// Perform the work, tracking whether the chunk was a submission
// before executing.
const bool has_submit = work->HasSubmit();
work->ExecuteAll(current_cmdbuf, current_upload_cmdbuf);
try {
work->ExecuteAll(current_cmdbuf, current_upload_cmdbuf);
} catch (const vk::Exception& e) {
LOG_CRITICAL(Render_Vulkan, "VulkanWorker: vk::Exception in ExecuteAll: result={}",
static_cast<int>(e.GetResult()));
throw;
} catch (const std::exception& e) {
LOG_CRITICAL(Render_Vulkan, "VulkanWorker: exception in ExecuteAll: {}", e.what());
throw;
}
// If the chunk was a submission, reallocate the command buffer.
if (has_submit) {
+10 -1
View File
@@ -5,6 +5,8 @@
#pragma once
#undef PIXEL_FORMAT_LIST
#include <climits>
#include <utility>
#include "common/assert.h"
@@ -128,7 +130,7 @@ namespace VideoCore::Surface {
PIXEL_FORMAT_ELEM(S8_UINT_D24_UNORM, 1, 1, 32) \
PIXEL_FORMAT_ELEM(D32_FLOAT_S8_UINT, 1, 1, 64)
enum class PixelFormat {
enum class PixelFormat : u32 {
#define PIXEL_FORMAT_ELEM(name, ...) name,
PIXEL_FORMAT_LIST
#undef PIXEL_FORMAT_ELEM
@@ -192,6 +194,13 @@ constexpr u32 BytesPerBlock(PixelFormat pixel_format) {
return BitsPerBlock(pixel_format) / CHAR_BIT;
}
}
#include "video_core/gpu.h"
#include "video_core/textures/texture.h"
namespace VideoCore::Surface {
SurfaceTarget SurfaceTargetFromTextureType(Tegra::Texture::TextureType texture_type);
bool SurfaceTargetIsLayered(SurfaceTarget target);
bool SurfaceTargetIsArray(SurfaceTarget target);