Browse Source

[video_core] cpu buffer fix + kepler uploads / maxwell macro dirty tracking fixes (#4473)

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------

Aimed to fix two known UE5 crashes: Kepler uploads and Maxwell macros, both caused by CPU/GPU races due dirty tracking issues.

Kepler ComputeInline: preserved dirty tracking across dma continuations and async readback.
Maxwell macros: preserved gpu owned subranges during page granular cpu uploads.
DiscardWrite: stopped clearing neighboring macro arguments by rounding up ranges.
DMA Step: improved continuation aware dirty sampling.

To the Ender Magnolia crew (maybe 1 or 2 persons): This will fix the dash crash, and the random / shackled beast vaper crashes.

There are some more UE5 issues to go next.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4473
Reviewed-by: lizzie <lizzie@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
lizzie/sparse-vectoriz
xbzk 2 days ago
committed by crueter
parent
commit
dbeb73ee01
No known key found for this signature in database GPG Key ID: 425ACD2D4830EBC6
  1. 23
      src/video_core/buffer_cache/buffer_cache.h
  2. 15
      src/video_core/dma_pusher.cpp
  3. 15
      src/video_core/engines/kepler_compute.cpp
  4. 2
      src/video_core/engines/kepler_compute.h

23
src/video_core/buffer_cache/buffer_cache.h

@ -121,7 +121,7 @@ void BufferCache<P>::UnmapGPUMemory(size_t as_id, GPUVAddr gpu_addr, size_t size
template <class P> template <class P>
void BufferCache<P>::WriteMemory(DAddr device_addr, u64 size) { void BufferCache<P>::WriteMemory(DAddr device_addr, u64 size) {
if (memory_tracker.IsRegionGpuModified(device_addr, size)) {
if (IsRegionGpuModified(device_addr, size)) {
ClearDownload(device_addr, size); ClearDownload(device_addr, size);
gpu_modified_ranges.Subtract(device_addr, size); gpu_modified_ranges.Subtract(device_addr, size);
} }
@ -311,11 +311,8 @@ std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainCPUBuffer(
MarkWrittenBuffer(buffer_id, device_addr, size); MarkWrittenBuffer(buffer_id, device_addr, size);
break; break;
case ObtainBufferOperation::DiscardWrite: { case ObtainBufferOperation::DiscardWrite: {
const DAddr device_addr_start = Common::AlignDown(device_addr, 64);
const DAddr device_addr_end = Common::AlignUp(device_addr + size, 64);
const size_t new_size = device_addr_end - device_addr_start;
ClearDownload(device_addr_start, new_size);
gpu_modified_ranges.Subtract(device_addr_start, new_size);
ClearDownload(device_addr, size);
gpu_modified_ranges.Subtract(device_addr, size);
break; break;
} }
default: default:
@ -1742,14 +1739,24 @@ bool BufferCache<P>::SynchronizeBuffer(Buffer& buffer, DAddr device_addr, u32 si
u64 total_size_bytes = 0; u64 total_size_bytes = 0;
u64 largest_copy = 0; u64 largest_copy = 0;
const DAddr buffer_start = buffer.cpu_addr_cached; const DAddr buffer_start = buffer.cpu_addr_cached;
memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) {
const auto add_upload = [&](DAddr start, DAddr end) {
if (start == end) return;
const u64 range_size = end - start;
upload_copies.push_back(BufferCopy{ upload_copies.push_back(BufferCopy{
.src_offset = total_size_bytes, .src_offset = total_size_bytes,
.dst_offset = device_addr_out - buffer_start,
.dst_offset = start - buffer_start,
.size = range_size, .size = range_size,
}); });
total_size_bytes += range_size; total_size_bytes += range_size;
largest_copy = (std::max)(largest_copy, 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);
}); });
if (total_size_bytes == 0) { if (total_size_bytes == 0) {
return true; return true;

15
src/video_core/dma_pusher.cpp

@ -16,7 +16,7 @@
namespace Tegra { namespace Tegra {
constexpr u32 MacroRegistersStart = 0xE00; constexpr u32 MacroRegistersStart = 0xE00;
[[maybe_unused]] constexpr u32 ComputeInline = 0x6D;
constexpr u32 ComputeInline = 0x6D;
DmaPusher::DmaPusher(Core::System& system_, MemoryManager& memory_manager_, Control::ChannelState& channel_state_) DmaPusher::DmaPusher(Core::System& system_, MemoryManager& memory_manager_, Control::ChannelState& channel_state_)
: system{system_} : system{system_}
@ -73,11 +73,16 @@ bool DmaPusher::Step() {
synced = false; synced = false;
} }
if (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 (header.size > 0) {
if (subchannels[dma_state.subchannel] && dma_state.method_count) {
const auto engine = subchannel_type[dma_state.subchannel];
const bool kepler_payload = engine == Engines::EngineTypes::KeplerCompute && dma_state.method == ComputeInline && dma_state.non_incrementing;
const bool macro_payload = engine == Engines::EngineTypes::Maxwell3D && dma_state.method >= MacroRegistersStart;
if (kepler_payload || macro_payload) {
const size_t words = std::min<size_t>(dma_state.method_count, header.size);
subchannels[dma_state.subchannel]->current_dirty = memory_manager.IsMemoryDirty(dma_state.dma_get, words * sizeof(u32));
}
}
const bool use_safe = Settings::IsDMALevelDefault() ? Settings::IsGPULevelHigh() : Settings::IsDMALevelSafe(); const bool use_safe = Settings::IsDMALevelDefault() ? Settings::IsGPULevelHigh() : Settings::IsDMALevelSafe();
if (use_safe) { if (use_safe) {
Tegra::Memory::GpuGuestMemory<Tegra::CommandHeader, Tegra::Memory::GuestMemoryFlags::SafeRead>headers(memory_manager, dma_state.dma_get, header.size, &command_headers); Tegra::Memory::GpuGuestMemory<Tegra::CommandHeader, Tegra::Memory::GuestMemoryFlags::SafeRead>headers(memory_manager, dma_state.dma_get, header.size, &command_headers);

15
src/video_core/engines/kepler_compute.cpp

@ -49,13 +49,16 @@ void KeplerCompute::CallMethod(Core::System& system, u32 method, u32 method_argu
case KEPLER_COMPUTE_REG_INDEX(exec_upload): { case KEPLER_COMPUTE_REG_INDEX(exec_upload): {
UploadInfo info{.upload_address = upload_address, UploadInfo info{.upload_address = upload_address,
.exec_address = upload_state.ExecTargetAddress(), .exec_address = upload_state.ExecTargetAddress(),
.copy_size = upload_state.GetUploadSize()};
.copy_size = upload_state.GetUploadSize(),
.was_dirty = upload_dirty};
uploads.push_back(info); uploads.push_back(info);
upload_state.ProcessExec(regs.exec_upload.linear != 0); upload_state.ProcessExec(regs.exec_upload.linear != 0);
break; break;
} }
case KEPLER_COMPUTE_REG_INDEX(data_upload): { case KEPLER_COMPUTE_REG_INDEX(data_upload): {
upload_address = current_dma_segment; upload_address = current_dma_segment;
upload_dirty = current_dirty;
current_dirty = false;
upload_state.ProcessData(method_argument, is_last_call); upload_state.ProcessData(method_argument, is_last_call);
break; break;
} }
@ -64,9 +67,11 @@ void KeplerCompute::CallMethod(Core::System& system, u32 method, u32 method_argu
for (auto& data : uploads) { for (auto& data : uploads) {
const GPUVAddr offset = data.exec_address - launch_desc_loc; const GPUVAddr offset = data.exec_address - launch_desc_loc;
if (offset / sizeof(u32) == LAUNCH_REG_INDEX(grid_dim_x) &&
memory_manager.IsMemoryDirty(data.upload_address, data.copy_size)) {
indirect_compute = {data.upload_address};
if (offset / sizeof(u32) == LAUNCH_REG_INDEX(grid_dim_x)) {
const bool source_dirty = memory_manager.IsMemoryDirty(data.upload_address, data.copy_size);
if (data.was_dirty || source_dirty) {
indirect_compute = {data.upload_address};
}
} }
} }
uploads.clear(); uploads.clear();
@ -83,6 +88,8 @@ void KeplerCompute::CallMultiMethod(Core::System& system, u32 method, const u32*
switch (method) { switch (method) {
case KEPLER_COMPUTE_REG_INDEX(data_upload): case KEPLER_COMPUTE_REG_INDEX(data_upload):
upload_address = current_dma_segment; upload_address = current_dma_segment;
upload_dirty = current_dirty;
current_dirty = false;
upload_state.ProcessData(base_start, amount); upload_state.ProcessData(base_start, amount);
return; return;
default: default:

2
src/video_core/engines/kepler_compute.h

@ -226,11 +226,13 @@ private:
VideoCore::RasterizerInterface* rasterizer = nullptr; VideoCore::RasterizerInterface* rasterizer = nullptr;
Upload::State upload_state; Upload::State upload_state;
GPUVAddr upload_address; GPUVAddr upload_address;
bool upload_dirty{};
struct UploadInfo { struct UploadInfo {
GPUVAddr upload_address; GPUVAddr upload_address;
GPUVAddr exec_address; GPUVAddr exec_address;
u32 copy_size; u32 copy_size;
bool was_dirty;
}; };
std::vector<UploadInfo> uploads; std::vector<UploadInfo> uploads;
std::optional<GPUVAddr> indirect_compute{}; std::optional<GPUVAddr> indirect_compute{};

Loading…
Cancel
Save