aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp4
-rw-r--r--src/core/hle/service/nx2_gsv.h10
-rw-r--r--src/shader_recompiler/frontend/ampere/lower_to_maxwell.h144
-rw-r--r--src/video_core/dma_pusher.cpp28
-rw-r--r--src/video_core/dma_pusher.h1
-rw-r--r--src/video_core/engines/kepler_compute.cpp63
-rw-r--r--src/video_core/engines/kepler_compute.h1
-rw-r--r--src/video_core/engines/maxwell_3d.h5
-rw-r--r--src/video_core/engines/puller.cpp22
-rw-r--r--src/video_core/macro.cpp35
-rw-r--r--src/video_core/macro_turing.h4
-rw-r--r--src/video_core/native_qmd.h20
-rw-r--r--src/video_core/query_cache/query_cache.h7
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp8
-rw-r--r--src/yuzu_cmd/yuzu.cpp46
15 files changed, 334 insertions, 64 deletions
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
index cf3948c321..2a144cc7ab 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
@@ -421,7 +421,7 @@ NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, Tegra::CommandL
if (g_nx2_driver) {
for (const auto& entry : entries.command_lists) {
- LOG_DEBUG(Service_NVDRV, "nx2 GPFIFO entry raw={:#018x} addr={:#x} size={:#x}", entry.raw,
+ LOG_TRACE(Service_NVDRV, "nx2 GPFIFO entry raw={:#018x} addr={:#x} size={:#x}", entry.raw,
entry.addr.Value(), entry.size.Value());
}
static const bool check = std::getenv("EDEN_NX2_PB_CHECK") != nullptr;
@@ -471,7 +471,7 @@ NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, Tegra::CommandL
}
if (g_nx2_driver) {
- LOG_DEBUG(Service_NVDRV,
+ LOG_TRACE(Service_NVDRV,
"nx2 SubmitGPFIFO entries={} flags={:#x} fence_out=({}, {}) guest_value={}",
params.num_entries, flags.raw, params.fence.id, params.fence.value,
system.Host1x().GetSyncpointManager().GetGuestSyncpointValue(params.fence.id));
diff --git a/src/core/hle/service/nx2_gsv.h b/src/core/hle/service/nx2_gsv.h
index 8073e0bd96..89f3a9fd50 100644
--- a/src/core/hle/service/nx2_gsv.h
+++ b/src/core/hle/service/nx2_gsv.h
@@ -580,17 +580,25 @@ private:
// free, so pace the game here instead: wait (bounded, in case the GPU waits on the CPU) until
// Eden's GPU thread has processed the frame. Otherwise the game laps the GPU thread and
// rewrites command memory it hasn't read yet.
+ u32 m_wait_timeouts{};
+
void WaitForFrame(u32 fence_id, u32 fence_value) {
if (fence_id >= 192) {
return;
}
auto& syncpoints = system.Host1x().GetSyncpointManager();
const auto start = std::chrono::steady_clock::now();
- const auto deadline = start + std::chrono::milliseconds{250};
+ // Long enough to cover shader/pipeline compile stalls: a game that gets more than a few
+ // command buffers ahead misses the equal-value gates its other channels acquire.
+ const auto deadline = start + std::chrono::milliseconds{4000};
while (!syncpoints.IsReadyGuest(fence_id, fence_value) &&
std::chrono::steady_clock::now() < deadline && system.IsPoweredOn()) {
std::this_thread::sleep_for(std::chrono::microseconds{200});
}
+ if (!syncpoints.IsReadyGuest(fence_id, fence_value) && m_wait_timeouts++ < 64) {
+ LOG_WARNING(Service, "nx2:vi2 present #{} fence ({}, {}) not reached in 4 s (now {})",
+ m_presents, fence_id, fence_value, syncpoints.GetGuestSyncpointValue(fence_id));
+ }
if (m_presents <= 8 || (m_presents % 600) == 0) {
LOG_INFO(Service, "nx2:vi2 present #{} fence ({}, {}) now {}, waited {} us",
m_presents, fence_id, fence_value, syncpoints.GetGuestSyncpointValue(fence_id),
diff --git a/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h b/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h
index 2c3ef28751..01dc1d8425 100644
--- a/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h
+++ b/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h
@@ -53,7 +53,7 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
for (const auto& instruction : code) {
const u32 op = instruction.Bits(0, 12);
uses_uniform_bank |= op == 0x3c4 || op == 0x2bd || op == 0xc0c || op == 0x986 || op == 0x9a8 || op == 0xc12 || op == 0xc17 || op == 0xe24 || op == 0xc24 || op == 0xd00 || op == 0xd09 || op == 0x9c3 || op == 0xc45 || op == 0x2a4 || op == 0x891 || op == 0x890 || op == 0x290 || op == 0x28c || op == 0x88c || op == 0x287 || op == 0x887 || op == 0x958 || op == 0xab9 || op == 0xabb || op == 0x899 || op == 0xc20 || op == 0xe21 || op == 0xe23 || op == 0xc02 ||
- op == 0x886 || op == 0x3c2 || op == 0xc82 || op == 0x882 || op == 0x292 || op == 0x892 || op == 0x291 || op == 0xc10 || op == 0xc23 || op == 0x526 || op == 0x2be || (op == 0x81c && instruction.Bits(67, 1));
+ op == 0x886 || op == 0x3c2 || op == 0xc82 || op == 0x882 || op == 0x292 || op == 0x892 || op == 0x291 || op == 0x981 || op == 0xc13 || op == 0xc10 || op == 0xc23 || op == 0x526 || op == 0x2be || (op == 0x81c && instruction.Bits(67, 1));
}
const auto address_of = [](u32 index) { return 0x50U + index * 8 + ((index + 1) / 3) * 8; };
const auto fail = [&](u32 index, std::string_view reason) {
@@ -454,16 +454,23 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
}
break;
}
- case 0x824: { // Captured low-word IMAD forms
- if (insn.words[1] == 1 && (insn.words[2] & 0xffffff00U) == 0x078e0200) {
+ case 0x824: { // Captured low-word IMAD forms; bit 75 negates the addend.
+ const bool negate_c = insn.Bits(75, 1) != 0;
+ if (insn.words[1] == 1 && (insn.words[2] & 0xfffff700U) == 0x078e0200) {
emit(base(0x5c10000000000000ULL) | (u64{insn.Bits(24, 8)} << 8) |
- (u64{insn.Bits(64, 8)} << 20));
+ (u64{insn.Bits(64, 8)} << 20) | (u64{negate_c} << 48));
break;
}
if (insn.words[1] != 48 || insn.words[2] != 0x078e02ff) {
- if ((insn.words[2] & ~0x2ffU) != 0x078e0000)
+ if ((insn.words[2] & ~0xaffU) != 0x078e0000)
return fail(index, "native integer multiply mode");
- multiply_low(insn.Bits(24, 8), insn.words[1], insn.Bits(64, 8), true);
+ u32 c = insn.Bits(64, 8);
+ if (negate_c && c != 255) {
+ emit(0x5c10000000000000ULL | predicate | 252 | (u64{255} << 8) | (u64{c} << 20) |
+ (u64{1} << 48));
+ c = 252;
+ }
+ multiply_low(insn.Bits(24, 8), insn.words[1], c, true);
break;
}
const u64 source = insn.Bits(24, 8);
@@ -631,6 +638,14 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
(u64{offset} << 36) | (u64{insn.Bits(90, 1)} << 49) | (u64{7} << 51));
break;
}
+ case 0x989: { // SHFL, immediate lane and register clamp/segmentation mask, any mode.
+ if ((insn.words[1] & 0xf01fffffU) || (insn.words[2] & ~0x000e00ffU))
+ return fail(index, "native register-mask shuffle mode");
+ emit(base(0xef10000000000000ULL) | (u64{insn.Bits(24, 8)} << 8) |
+ (u64{insn.Bits(53, 5)} << 20) | (u64{1} << 28) | (u64{insn.Bits(58, 2)} << 30) |
+ (u64{insn.Bits(64, 8)} << 39) | (u64{insn.Bits(81, 3)} << 48));
+ break;
+ }
case 0x589: { // SHFL.IDX, register lane and immediate segmentation mask.
if ((insn.words[2] & ~0x000e0000U) || insn.Bits(53, 11))
return fail(index, "native shuffle mode");
@@ -882,22 +897,50 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
emit(0x38f0000000070000ULL | (u64{253} << 8) | (u64{mask} << 20));
break;
}
- case 0x381: { // LDG.E of 32/64/128 bits through a 64-bit register address.
+ case 0x381: case 0x981: { // LDG.E of 32/64/128 bits, register or uniform 64-bit address.
const u32 size = insn.Bits(73, 3);
- if ((insn.words[2] & ~0xe00U) != 0x001e8100 || size < 4 || size > 6 ||
- insn.Bits(32, 8) || (insn.Bits(24, 8) & 1) || insn.Bits(24, 8) >= 246 ||
+ u32 address = insn.Bits(24, 8);
+ if (size < 4 || size > 6 || (opcode == 0x381 && insn.Bits(32, 8)) ||
(size == 5 && (dest & 1)) || (size == 6 && (dest & 3)))
return fail(index, "native global load mode");
- emit(base(0xeed0000000000000ULL) | (u64{insn.Bits(24, 8)} << 8) |
+ if (opcode == 0x381) {
+ if ((insn.words[2] & ~0xe00U) != 0x001e8100 || (address & 1) || address >= 246)
+ return fail(index, "native global load mode");
+ } else {
+ // The uniform base sits where a register source would (bits 32-37).
+ const u32 uniform_address =
+ insn.Bits(38, 2) ? ~0U : uniform_register(insn.Bits(32, 6));
+ if ((insn.words[2] & ~(0x04000000U | 0x3fU | 0xe00U | 0x2000U)) != 0x081e8100 ||
+ uniform_address == ~0U || (uniform_address & 1))
+ return fail(index, "native global load uniform addressing mode");
+ if (address == 255) {
+ address = uniform_address;
+ } else {
+ if (insn.Bits(90, 1) && ((address & 1) || address >= 246))
+ return fail(index, "native global load register address pair");
+ emit(0x5c10000000000000ULL | predicate | 250 | (u64{uniform_address} << 8) |
+ (u64{address} << 20) | (u64{1} << 47));
+ emit(0x5c10000000000000ULL | predicate | 251 | (u64{uniform_address + 1} << 8) |
+ (u64{insn.Bits(90, 1) ? address + 1 : 255} << 20) | (u64{1} << 43));
+ address = 250;
+ }
+ }
+ emit(base(0xeed0000000000000ULL) | (u64{address} << 8) |
(u64{insn.Bits(40, 24)} << 20) | (u64{1} << 45) | (u64{size} << 48));
break;
}
case 0x386: case 0x986: { // Global store of 32/64/128 bits, 64-bit address.
- const u32 data = insn.Bits(32, 8);
+ u32 data = insn.Bits(32, 8);
const u32 size = insn.Bits(73, 3);
- if (size < 4 || size > 6 || (size == 5 && (data & 1)) || (size == 6 && (data & 3)) ||
- data + (1U << (size - 4)) > 246)
+ if (size < 4 || size > 6 ||
+ (data != 255 && ((size == 5 && (data & 1)) || (size == 6 && (data & 3)) ||
+ data + (1U << (size - 4)) > 246)))
return fail(index, "native global store size");
+ if (data == 255 && size > 4) {
+ // Zeros wider than one register: store from zeroed scratch registers.
+ for (u32 i = 0; i < (1U << (size - 4)); ++i) mov_imm(250 + i, 0);
+ data = 250;
+ }
u32 address = insn.Bits(24, 8);
bool extended = insn.Bits(72, 1) != 0;
if (opcode == 0x986) {
@@ -1060,13 +1103,14 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
const auto general_tex = [&]() -> bool {
constexpr std::array<u32, 8> coordinate_count{1, 2, 3, 3, 2, 3, 0, 4};
constexpr std::array<u32, 8> maxwell_type{0, 2, 4, 6, 1, 3, 0, 7};
- if (dimension == 6 || insn.Bits(76, 2) || insn.Bits(81, 3) != 7 || insn.Bits(59, 1) ||
+ const bool offsets = insn.Bits(76, 1) != 0;
+ if (dimension == 6 || insn.Bits(77, 1) || insn.Bits(81, 3) != 7 || insn.Bits(59, 1) ||
insn.Bits(40, 14) > 0x1fff || insn.Bits(54, 5) >= 18 || (lod > 3 && lod != 6 && lod != 7) ||
(depth_compare && mask != 1) || mask == 0)
return false;
const u32 coordinates = coordinate_count[dimension];
const bool has_lod = lod == 2 || lod == 3 || lod == 6 || lod == 7;
- const u32 count = coordinates + (has_lod ? 1 : 0) + (depth_compare ? 1 : 0);
+ const u32 count = coordinates + (has_lod ? 1 : 0) + (offsets ? 1 : 0) + (depth_compare ? 1 : 0);
const u32 in_a = count <= 4 ? (count + 1) / 2 : 4;
const auto value = [&](u32 i) -> u32 {
if (i < in_a) return source_a == 255 ? 255U : source_a + i;
@@ -1082,16 +1126,18 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
const auto copy = [&](u32 target, u32 source) {
emit(0x5c98000000000000ULL | predicate | target | (u64{15} << 39) | (u64{source} << 20));
};
- // Coordinates to R250.., LOD/bias then reference to R246/R247.
+ // Coordinates to R250.., then LOD/bias, offsets and reference to R246...
for (u32 i = 0; i < coordinates; ++i) copy(250 + i, value(i));
u32 meta = 246;
- if (has_lod) copy(meta++, value(coordinates));
- if (depth_compare) copy(meta++, value(coordinates + (has_lod ? 1 : 0)));
+ u32 next = coordinates;
+ if (has_lod) copy(meta++, value(next++));
+ if (offsets) copy(meta++, value(next++));
+ if (depth_compare) copy(meta++, value(next++));
emit(0xc000000000000000ULL | predicate | 250 | (u64{250} << 8) |
(u64{meta == 246 ? 255U : 246U} << 20) | (u64{maxwell_type[dimension]} << 28) |
(u64{mask} << 31) | (u64{insn.Bits(40, 14)} << 36) |
(u64{insn.Bits(90, 1)} << 49) | (u64{depth_compare} << 50) | (u64{7} << 51) |
- (u64{lod} << 55));
+ (u64{offsets} << 54) | (u64{lod} << 55));
const u32 components = static_cast<u32>(std::popcount(mask));
if (insn.Bits(79, 1)) {
// FP16 results: (c0, c1) packed into the first destination, (c2, c3) into
@@ -1320,9 +1366,11 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
break;
}
case 0x245: case 0xa45: case 0xc45: { // I2FP.F32.U32
- if ((insn.words[2] & ~(0x400U | (opcode == 0xc45 ? 0x08000000U : 0U))) != 0x00201000) return fail(index, "native packed integer-to-float format");
+ // Bits 78-79: rounding (RN/RM/RP/RZ), as Maxwell I2F's bits 39-40.
+ if ((insn.words[2] & ~(0x400U | 0xc000U | (opcode == 0xc45 ? 0x08000000U : 0U))) != 0x00201000) return fail(index, "native packed integer-to-float format");
const u32 src = operand(1, false);
- emit(base(0x5cb8000000000000ULL) | (u64{2} << 8) | (u64{2} << 10) | (u64{insn.Bits(74, 1)} << 13) | (u64{src} << 20));
+ emit(base(0x5cb8000000000000ULL) | (u64{2} << 8) | (u64{2} << 10) | (u64{insn.Bits(74, 1)} << 13) | (u64{src} << 20) |
+ (u64{insn.Bits(78, 2)} << 39));
break;
}
case 0x207: case 0x807: case 0xa07:
@@ -1357,38 +1405,38 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
emit(out);
break;
}
- case 0x212: case 0x812: case 0xc12: { // Bounded LOP3 bitwise forms, no predicate output
+ case 0x212: case 0x812: case 0xa12: case 0xc12: { // LOP3.LUT, any table, optional P = (result != 0)
const u32 lut = insn.Bits(72, 8);
- if ((lut != 0xc0 && lut != 0xfc && lut != 0xf8 && lut != 0x48 && lut != 0xe2) || insn.Bits(80, 1) ||
- insn.Bits(87, 4) != 15 ||
- (opcode == 0x212 && insn.Bits(40, 24))) return fail(index, "native LUT/predicate mode");
- u32 b = opcode == 0xc12 ? uniform_register(insn.Bits(32, 8)) : insn.Bits(32, 8);
- if (b == ~0U) return fail(index, "native uniform logic source");
- if (opcode == 0x812) {
+ const u32 pred_target = insn.Bits(81, 3);
+ if (insn.Bits(80, 1) || insn.Bits(87, 4) != 15 ||
+ (opcode == 0x212 && insn.Bits(40, 24)) || (opcode == 0xa12 && insn.Bits(62, 2)))
+ return fail(index, "native LUT/predicate mode");
+ u32 b = 255;
+ if (opcode == 0x212) {
+ b = insn.Bits(32, 8);
+ } else if (opcode == 0x812) {
mov_imm(251, insn.words[1]);
b = 251;
- }
- const u32 a = insn.Bits(24, 8), c = insn.Bits(64, 8);
- const auto logical = [&](u32 target, u32 lhs, u32 rhs, u32 operation, u32 pred_target = 7) {
- emit(0x5c40000000000000ULL | predicate | target | (u64{lhs} << 8) |
- (u64{rhs} << 20) | (u64{operation} << 41) |
- (u64{pred_target == 7 ? 0U : 3U} << 44) | (u64{pred_target} << 48));
- };
- const u32 pred_target = insn.Bits(81, 3);
- if (lut == 0xe2) {
- logical(250, a, b, 0);
- logical(252, c, b, 0);
- result.instructions.back() |= u64{1} << 40;
- logical(static_cast<u32>(dest), 250, 252, 1, pred_target);
- } else if (lut == 0xf8) {
- logical(250, b, c, false);
- logical(static_cast<u32>(dest), a, 250, 1, pred_target);
- } else if (lut == 0x48) {
- logical(250, a, c, 2);
- logical(static_cast<u32>(dest), 250, b, 0, pred_target);
+ } else if (opcode == 0xc12) {
+ b = uniform_register(insn.Bits(32, 8));
+ if (b == ~0U) return fail(index, "native uniform logic source");
} else {
- logical(static_cast<u32>(dest), a, b, lut == 0xfc ? 1U : 0U, pred_target);
+ b = operand(1, false);
}
+ const u32 a = insn.Bits(24, 8), c = insn.Bits(64, 8);
+ emit(0x5be0000000000000ULL | predicate | dest | (u64{a} << 8) | (u64{b} << 20) |
+ (u64{lut} << 28) | (u64{pred_target == 7 ? 0U : 3U} << 36) | (u64{c} << 39) |
+ (u64{pred_target} << 48));
+ break;
+ }
+ case 0x213: case 0xa13: case 0xc13: { // IABS: max(x, 0 - x), signed.
+ if (insn.words[2] & ~(opcode == 0xc13 ? 0x08000000U : 0U))
+ return fail(index, "native integer absolute mode");
+ const u32 source = operand(1, false);
+ emit(0x5c10000000000000ULL | predicate | 250 | (u64{255} << 8) | (u64{source} << 20) |
+ (u64{1} << 48));
+ emit(base(0x5c20000000000000ULL) | (u64{source} << 8) | (u64{250} << 20) |
+ (u64{15} << 39) | (u64{1} << 48));
break;
}
case 0x217: case 0x817: case 0xa17: case 0xc17: { // IMNMX register, immediate, cbuf or uniform operand.
diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp
index 2c95b6de03..040bf18362 100644
--- a/src/video_core/dma_pusher.cpp
+++ b/src/video_core/dma_pusher.cpp
@@ -13,6 +13,7 @@
#include "core/core.h"
#include "core/memory.h"
#include "video_core/dma_pusher.h"
+#include "video_core/control/channel_state.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/gpu.h"
#include "video_core/guest_memory.h"
@@ -134,6 +135,16 @@ bool DmaPusher::Step() {
if (++dma_pushbuffer_subindex >= command_list_size) {
// A split method resumes within the submission that split it; ring addresses repeat
// every frame, so a stale one could otherwise swallow the headers of a later entry.
+ for (const auto& parked : parked_methods) {
+ static std::atomic<u32> reported{};
+ if (reported.fetch_add(1) < 16) {
+ LOG_WARNING(HW_GPU,
+ "Submission ended with parked method {:#x} (sub {}, {} words left, "
+ "resume {:#x})",
+ parked.method, parked.subchannel, parked.method_count,
+ parked.resume_address);
+ }
+ }
parked_methods.clear();
dma_pushbuffer.pop();
dma_pushbuffer_subindex = 0;
@@ -264,7 +275,21 @@ void DmaPusher::SetState(const CommandHeader& command_header) {
dma_state.method_count = command_header.method_count;
}
+// EDEN_NX2_SEM_WATCH=<gpu address>: log every method whose argument is the low word of an
+// address in the 16 bytes there (finds the engine and method that release a semaphore).
+void DmaPusher::WatchArgument(u32 method, u32 argument) const {
+ static const u32 watch = [] {
+ const char* value = std::getenv("EDEN_NX2_SEM_WATCH");
+ return value ? static_cast<u32>(std::strtoull(value, nullptr, 16)) : 0U;
+ }();
+ if (watch != 0 && (argument & ~0xfU) == watch) {
+ LOG_INFO(HW_GPU, "Semaphore watch: channel {} subchannel {} method {:#x} argument {:#x}",
+ channel_state.bind_id, dma_state.subchannel, method, argument);
+ }
+}
+
void DmaPusher::CallMethod(u32 argument) {
+ WatchArgument(dma_state.method, argument);
if (dma_state.method < non_puller_methods) {
puller.CallPullerMethod(*this, Engines::Puller::MethodCall{
dma_state.method,
@@ -289,6 +314,9 @@ void DmaPusher::CallMethod(u32 argument) {
}
void DmaPusher::CallMultiMethod(const u32* base_start, u32 num_methods) {
+ for (u32 i = 0; i < num_methods; ++i) {
+ WatchArgument(dma_state.method + (dma_state.non_incrementing ? 0 : i), base_start[i]);
+ }
if (dma_state.method < non_puller_methods) {
puller.CallMultiMethod(*this, dma_state.method, dma_state.subchannel, base_start, num_methods, dma_state.method_count);
} else {
diff --git a/src/video_core/dma_pusher.h b/src/video_core/dma_pusher.h
index 24fedba21b..bbcc345c84 100644
--- a/src/video_core/dma_pusher.h
+++ b/src/video_core/dma_pusher.h
@@ -173,6 +173,7 @@ private:
void CallMethod(u32 argument);
void CallMultiMethod(const u32* base_start, u32 num_methods);
void ReportUnboundSubchannel();
+ void WatchArgument(u32 method, u32 argument) const;
public:
/// Logs the GPFIFO entry being processed (and the one before it), read through the GPU MMU
diff --git a/src/video_core/engines/kepler_compute.cpp b/src/video_core/engines/kepler_compute.cpp
index 7710ea7dd2..689f85a1d3 100644
--- a/src/video_core/engines/kepler_compute.cpp
+++ b/src/video_core/engines/kepler_compute.cpp
@@ -4,6 +4,7 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <algorithm>
#include <bitset>
#include <cstdlib>
#include <fstream>
@@ -46,6 +47,46 @@ void KeplerCompute::ConsumeSinkImpl(Core::System& system) {
method_sink.clear();
}
+// SET_REPORT_SEMAPHORE_A-D (0x6c0-0x6c3): address high/low, payload, then operation in bits 1:0
+// (release / acquire / report only / trap), reduction enable bit 3 with the op in bits 11:9, and
+// a one-word (no timestamp) structure in bit 28. NVN2 releases queue syncs from compute this way.
+void KeplerCompute::ProcessNativeReportSemaphore(u32 argument) {
+ const GPUVAddr address = (GPUVAddr{native_registers[0x6c0] & 0x1ffffff} << 32) |
+ native_registers[0x6c1];
+ const u32 payload = native_registers[0x6c2];
+ const u32 operation = argument & 3;
+ const bool one_word = (argument >> 28) & 1;
+ static u32 logged{};
+ if (logged++ < 32) {
+ LOG_INFO(HW_GPU, "Native compute report semaphore {:#x} = {:#x}, control {:#x}", address,
+ payload, argument);
+ }
+ if (operation != 0 && operation != 2) {
+ LOG_WARNING(HW_GPU, "Native compute report semaphore operation {} not supported", operation);
+ return;
+ }
+ if ((argument >> 3) & 1) {
+ const u32 reduction = (argument >> 9) & 7;
+ rasterizer->SignalFence([this, address, payload, reduction] {
+ const u32 old_value = memory_manager.Read<u32>(address);
+ const u32 value = reduction == 0 ? old_value + payload
+ : reduction == 1 ? std::min(old_value, payload)
+ : reduction == 2 ? std::max(old_value, payload)
+ : reduction == 3 ? (old_value >= payload ? 0 : old_value + 1)
+ : reduction == 4 ? (old_value == 0 || old_value > payload ? payload : old_value - 1)
+ : reduction == 5 ? (old_value & payload)
+ : reduction == 6 ? (old_value | payload)
+ : (old_value ^ payload);
+ memory_manager.Write<u32>(address, value);
+ });
+ return;
+ }
+ rasterizer->Query(address, VideoCommon::QueryType::Payload,
+ one_word ? VideoCommon::QueryPropertiesFlags::IsAFence
+ : VideoCommon::QueryPropertiesFlags::HasTimeout,
+ payload, 0);
+}
+
bool KeplerCompute::ProcessNativeMethod(Core::System& system, u32 method, u32 argument, bool is_last_call) {
if (native_context) {
if (method == 0x45 || method == 0x47) native_compute_uploaded = true;
@@ -65,11 +106,22 @@ bool KeplerCompute::ProcessNativeMethod(Core::System& system, u32 method, u32 ar
if (!is_last_call) return true;
const u32 entry = ((*native_executing_macro - 0xe00) / 2) % native_macro_positions.size();
native_executing_macro.reset();
+ static const u32 watch = [] {
+ const char* value = std::getenv("EDEN_NX2_SEM_WATCH");
+ return value ? static_cast<u32>(std::strtoull(value, nullptr, 16)) : 0U;
+ }();
+ const bool watched = watch != 0 && std::ranges::any_of(native_parameters,
+ [](u32 parameter) { return (parameter & ~0xfU) == watch; });
+ if (watched) {
+ LOG_INFO(HW_GPU, "Semaphore watch: compute macro start={:#x} parameters={:08x}",
+ native_macro_positions[entry], fmt::join(native_parameters, ","));
+ }
const auto result = native_macro->native_interpreter.Execute(native_macro->native_code,
native_macro_positions[entry], native_parameters,
[&](u32 address) { return address < native_registers.size() ? native_registers[address] : 0U; },
[&](u32 address, u32 value) {
static u32 diagnostic_writes{};
+ if (watched) LOG_INFO(HW_GPU, "Semaphore watch: compute macro emit {:#x} = {:#x}", address, value);
if (diagnostic_writes++ < 4096) LOG_DEBUG(HW_GPU, "Native compute MME emit method={:#x}, value={:#x}", address, value);
if (address >= 0xe00) return false;
CallMethod(system, address, value, true);
@@ -89,6 +141,10 @@ bool KeplerCompute::ProcessNativeMethod(Core::System& system, u32 method, u32 ar
return true;
}
native_registers[method] = argument;
+ if (method == 0x6c3) { // SET_REPORT_SEMAPHORE_D: the compute class's semaphore release
+ ProcessNativeReportSemaphore(argument);
+ return true;
+ }
if (method == 0x5a || (method >= 0xc6 && method <= 0x108)) {
VideoCommon::TraceNativeQuery(12, memory_manager.GetID(), method, 0, 0, argument, 0);
}
@@ -157,6 +213,13 @@ bool KeplerCompute::ProcessNativeMethod(Core::System& system, u32 method, u32 ar
[&](u64 address, NativeQmdScheduler::Descriptor& descriptor) {
if (!memory_manager.IsFullyMappedRange(address, sizeof(descriptor))) return false;
memory_manager.ReadBlockUnsafe(address, descriptor.data(), sizeof(descriptor));
+ if (static u32 logged{}; (descriptor[53] & (1U << 23)) && logged < 8) {
+ ++logged;
+ LOG_INFO(HW_GPU, "Native QMD {:#x} releases: {:08x} | {:08x} | {:08x}", address,
+ fmt::join(std::span{descriptor}.subspan(24, 4), ","),
+ fmt::join(std::span{descriptor}.subspan(28, 4), ","),
+ fmt::join(std::span{descriptor}.subspan(52, 4), ","));
+ }
for (u32 slot = 0; slot < 2; ++slot) {
const u32 flags = descriptor[25 + slot * 4];
if (!(flags & (1U << 23))) continue;
diff --git a/src/video_core/engines/kepler_compute.h b/src/video_core/engines/kepler_compute.h
index 99cf74d1e8..51d31b9c95 100644
--- a/src/video_core/engines/kepler_compute.h
+++ b/src/video_core/engines/kepler_compute.h
@@ -233,6 +233,7 @@ public:
private:
bool DispatchNativeCompute(const NativeQmdScheduler::Descriptor& descriptor);
+ void ProcessNativeReportSemaphore(u32 argument);
bool ProcessNativeMethod(Core::System& system, u32 method, u32 argument, bool is_last_call);
NativeQmdScheduler native_qmd;
bool native_mode{};
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index 8cbc4f4274..29db8022e5 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -3135,6 +3135,11 @@ public:
/// Reads a register value located at the input method address
u32 GetRegisterValue(u32 method) const;
+ /// GPU memory, for Turing macros that read memory into their FIFO or data RAM.
+ MemoryManager& GetMemoryManager() {
+ return memory_manager;
+ }
+
void SetNativeTuringMacroMode(bool enabled) {
GetNativeMacroEngine().native_turing = enabled;
upload_state.SetNativePartialTransfers(enabled);
diff --git a/src/video_core/engines/puller.cpp b/src/video_core/engines/puller.cpp
index 97a58903fe..047e383b85 100644
--- a/src/video_core/engines/puller.cpp
+++ b/src/video_core/engines/puller.cpp
@@ -68,9 +68,16 @@ bool Puller::CanResumeAcquire(DmaPusher& dma_pusher) {
if (!pending_acquire->reported &&
std::chrono::steady_clock::now() - pending_acquire->since > std::chrono::seconds{2}) {
pending_acquire->reported = true;
- LOG_WARNING(HW_GPU, "Channel {} waits on semaphore {:#x} (op {}, {:#x}, now {:#x}) for 2 s",
+ const auto device = dma_pusher.memory_manager.GpuToCpuAddress(pending_acquire->address);
+ LOG_WARNING(HW_GPU, "Channel {} waits on semaphore {:#x} (op {}, {:#x}, now {:#x}) for 2 s; "
+ "device {:#x}, buffer cache holds GPU data: {}, words {:08x} {:08x} {:08x} {:08x}",
dma_pusher.channel_state.bind_id, pending_acquire->address,
- pending_acquire->operation, pending_acquire->value, current);
+ pending_acquire->operation, pending_acquire->value, current, device.value_or(0),
+ device && dma_pusher.rasterizer->MustFlushRegion(*device, 16, VideoCommon::CacheType::All),
+ dma_pusher.memory_manager.Read<u32>(pending_acquire->address & ~GPUVAddr{0xf}),
+ dma_pusher.memory_manager.Read<u32>((pending_acquire->address & ~GPUVAddr{0xf}) + 4),
+ dma_pusher.memory_manager.Read<u32>((pending_acquire->address & ~GPUVAddr{0xf}) + 8),
+ dma_pusher.memory_manager.Read<u32>((pending_acquire->address & ~GPUVAddr{0xf}) + 12));
}
return false;
}
@@ -157,6 +164,17 @@ void Puller::ProcessFenceActionMethod(DmaPusher& dma_pusher) {
void Puller::ProcessSemaphoreTriggerMethod(DmaPusher& dma_pusher) {
const auto semaphoreOperationMask = 0xF;
const auto op = GpuSemaphoreOperation(regs.semaphore_trigger & semaphoreOperationMask);
+ // EDEN_NX2_SEM_WATCH=<gpu address>: log every release/acquire on the 16 bytes there.
+ static const GPUVAddr watch = [] {
+ const char* value = std::getenv("EDEN_NX2_SEM_WATCH");
+ return value ? GPUVAddr{std::strtoull(value, nullptr, 16)} : GPUVAddr{};
+ }();
+ if (watch != 0 && (regs.semaphore_address.SemaphoreAddress() & ~GPUVAddr{0xf}) == watch) {
+ LOG_INFO(HW_GPU, "Semaphore watch: channel {} op {} {:#x} value {:#x} (now {:#x})",
+ dma_pusher.channel_state.bind_id, static_cast<u32>(op),
+ regs.semaphore_address.SemaphoreAddress(), regs.semaphore_sequence,
+ dma_pusher.memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress()));
+ }
if (op == GpuSemaphoreOperation::WriteLong) {
const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()};
const u32 payload = regs.semaphore_sequence;
diff --git a/src/video_core/macro.cpp b/src/video_core/macro.cpp
index a8acd81682..06c649260d 100644
--- a/src/video_core/macro.cpp
+++ b/src/video_core/macro.cpp
@@ -27,6 +27,7 @@
#include "video_core/engines/maxwell_3d.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/dirty_flags.h"
+#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/macro.h"
@@ -1351,11 +1352,43 @@ void MacroEngine::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, u
}
LOG_DEBUG(HW_GPU, "Native MME context={:#x}", context);
LOG_DEBUG(HW_GPU, "Native MME start={:#x}, parameters={}, data={:08x}", method, parameters.size(), fmt::join(parameters, ","));
- const auto result = native_interpreter.Execute(native_code, method, parameters,
+ std::vector<u32> fifo(parameters.begin(), parameters.end());
+ const auto result = native_interpreter.Execute(native_code, method, fifo,
[&](u32 address) { return address < Maxwell3D::Regs::NUM_REGS ? maxwell3d.GetRegisterValue(address) : 0U; },
[&](u32 address, u32 value) {
if (address >= 0xe00) return false;
+ if (address == 0x556) {
+ static u32 enable_logs{};
+ if (enable_logs++ < 400) LOG_INFO(HW_GPU, "Native MME start={:#x} render enable mode={}", method, value);
+ }
maxwell3d.CallMethod(system, address, value, true);
+ // Turing MME memory reads (methods 0x550 SET_MME_MEM_ADDRESS_A/B, 0x558 data RAM
+ // address, 0x55c MME_DMA_READ into data RAM, 0x560 MME_DMA_READ_FIFOED into
+ // the load FIFO, which the macro then reads after a load barrier).
+ constexpr u32 MemAddressA = 0x154, MemAddressB = 0x155, DataRamAddress = 0x156;
+ constexpr u32 DmaRead = 0x157, DmaReadFifoed = 0x158;
+ if (address == DmaRead || address == DmaReadFifoed) {
+ const GPUVAddr source = (GPUVAddr{maxwell3d.GetRegisterValue(MemAddressA) & 0x1ffffff} << 32) |
+ maxwell3d.GetRegisterValue(MemAddressB);
+ auto& memory = maxwell3d.GetMemoryManager();
+ const u32 ram = maxwell3d.GetRegisterValue(DataRamAddress);
+ static u32 dma_logs{};
+ if (dma_logs < 400) {
+ ++dma_logs;
+ std::array<u32, 8> words{};
+ for (u32 i = 0; i < std::min<u32>(value, 8); ++i) words[i] = memory.Read<u32>(source + u64{i} * 4);
+ LOG_INFO(HW_GPU, "Native MME DMA read{} start={:#x} source={:#x} count={} words={:08x}",
+ address == DmaReadFifoed ? " fifoed" : "", method, source, value, fmt::join(words, ","));
+ }
+ for (u32 i = 0; i < std::min<u32>(value, 0x1000); ++i) {
+ const u32 word = memory.Read<u32>(source + u64{i} * 4);
+ if (address == DmaReadFifoed) {
+ fifo.push_back(word);
+ } else if (ram + i < native_interpreter.dram.size()) {
+ native_interpreter.dram[ram + i] = word;
+ }
+ }
+ }
return true;
});
if (!result.success) {
diff --git a/src/video_core/macro_turing.h b/src/video_core/macro_turing.h
index fe82ce4a4f..a0a40000c2 100644
--- a/src/video_core/macro_turing.h
+++ b/src/video_core/macro_turing.h
@@ -10,6 +10,7 @@
#include <cstdint>
#include <span>
#include <string_view>
+#include <vector>
namespace Tegra {
@@ -25,8 +26,9 @@ public:
};
template <typename Read, typename Emit>
+ // `parameters` is the load FIFO; `emit` may append to it (MME_DMA_READ_FIFOED).
Result Execute(std::span<const Word> code, Word start,
- std::span<const Word> parameters, Read read, Emit emit) {
+ std::vector<Word>& parameters, Read read, Emit emit) {
std::array<Word, 24> regs{};
Word initialized{}, carry{}, method{}, increment{};
Word pc = start, loop_count{}, loop_start{}, loop_end{};
diff --git a/src/video_core/native_qmd.h b/src/video_core/native_qmd.h
index c9ae3b8cc0..c06f14e6b2 100644
--- a/src/video_core/native_qmd.h
+++ b/src/video_core/native_qmd.h
@@ -61,9 +61,9 @@ public:
if (!read(request.address, state.descriptor)) return "unmapped QMD";
const auto qmd = state.descriptor;
if ((qmd[18] & 0xff) != 0x30 || (qmd[4] & 0x100)) return "unsupported QMD version or queue";
- // Validate both release slots before making either visible.
- for (Word slot = 0; slot < 2; ++slot) {
- const Word flags = qmd[25 + slot * 4];
+ // Validate the release slots before making any visible.
+ for (const Word base : ReleaseSlots) {
+ const Word flags = qmd[base + 1];
if (!(flags & (1U << 23))) continue;
const bool add = (flags & (1U << 26)) != 0;
if ((flags & (0x3bU << 24)) || (flags >> 30) != 1 ||
@@ -76,15 +76,15 @@ public:
if (qmd[12] && (qmd[13] & 0xffff) && (qmd[14] & 0xffff) && !dispatch(qmd))
return "native shader dispatch required";
state.completed = true;
- for (Word slot = 0; slot < 2; ++slot) {
- const Word flags = qmd[25 + slot * 4];
+ for (const Word base : ReleaseSlots) {
+ const Word flags = qmd[base + 1];
if (!(flags & (1U << 23))) continue;
- const Address destination = (Address{flags & 0xff} << 32) | qmd[24 + slot * 4];
+ const Address destination = (Address{flags & 0xff} << 32) | qmd[base];
bool released{};
if constexpr (std::is_invocable_r_v<bool, Release, Address, Word, Word>) {
- released = release(destination, qmd[26 + slot * 4], flags);
+ released = release(destination, qmd[base + 2], flags);
} else {
- released = release(destination, qmd[26 + slot * 4]);
+ released = release(destination, qmd[base + 2]);
}
if (!released) return "unmapped QMD release";
}
@@ -99,6 +99,10 @@ public:
return {};
}
private:
+ // Release slots (address low, flags | address high, payload). NVN2 patches a third one at
+ // word 52 for queue syncs (e.g. a compute queue releasing a semaphore another queue waits on).
+ static constexpr std::array<Word, 3> ReleaseSlots{24, 28, 52};
+
struct State {
Descriptor descriptor{};
std::int32_t dependencies{};
diff --git a/src/video_core/query_cache/query_cache.h b/src/video_core/query_cache/query_cache.h
index 11b2f631a5..930e5626aa 100644
--- a/src/video_core/query_cache/query_cache.h
+++ b/src/video_core/query_cache/query_cache.h
@@ -234,7 +234,12 @@ void QueryCacheBase<Traits>::CounterReport(GPUVAddr addr, QueryType counter_type
QueryPropertiesFlags flags, u32 payload, u32 subreport) {
const bool has_timestamp = True(flags & QueryPropertiesFlags::HasTimeout);
const bool is_fence = True(flags & QueryPropertiesFlags::IsAFence);
- if (static const bool trace = std::getenv("EDEN_NX2_QUERY_TRACE") != nullptr; trace) {
+ static const GPUVAddr watch = [] {
+ const char* value = std::getenv("EDEN_NX2_SEM_WATCH");
+ return value ? GPUVAddr{std::strtoull(value, nullptr, 16)} : GPUVAddr{};
+ }();
+ if (static const bool trace = std::getenv("EDEN_NX2_QUERY_TRACE") != nullptr;
+ trace || (watch != 0 && (addr & ~GPUVAddr{0xf}) == watch)) {
LOG_INFO(HW_GPU, "nx2 report addr={:#x} type={} flags={:#x} payload={:#x}", addr,
static_cast<u32>(counter_type), static_cast<u32>(flags), payload);
}
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 1d9965a9a5..044c783a99 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -1103,6 +1103,14 @@ void RasterizerVulkan::AccelerateInlineToMemory(GPUVAddr address, size_t copy_si
if (memory.size() >= sizeof(first_word)) std::memcpy(&first_word, memory.data(), sizeof(first_word));
VideoCommon::TraceNativeQuery(11, gpu_memory->GetID(), address, cpu_addr.value_or(0),
0, first_word, static_cast<u32>(copy_size));
+ static const GPUVAddr watch = [] {
+ const char* value = std::getenv("EDEN_NX2_SEM_WATCH");
+ return value ? GPUVAddr{std::strtoull(value, nullptr, 16)} : GPUVAddr{};
+ }();
+ if (watch != 0 && address < watch + 16 && address + copy_size > watch) {
+ LOG_INFO(Render_Vulkan, "Semaphore watch: inline write {:#x} size {} first word {:#x}",
+ address, copy_size, first_word);
+ }
}
if (!cpu_addr) [[unlikely]] {
gpu_memory->WriteBlock(address, memory.data(), copy_size);
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 3aaba78fd1..b46534ec9d 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -4,6 +4,9 @@
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <vector>
+#include <cstdlib>
+#include <chrono>
#include <cstdio>
#include <iostream>
#include <memory>
@@ -34,6 +37,7 @@
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
#include "frontend_common/config.h"
+#include "input_common/drivers/keyboard.h"
#include "input_common/main.h"
#include "network/network.h"
#include "sdl_config.h"
@@ -327,8 +331,50 @@ extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
state->system.InitializeDebugger();
return SDL_APP_CONTINUE;
}
+// EDEN_NX2_KEY_SCRIPT="seconds:code,seconds:-code,...": press (code) or release (-code) keyboard
+// keys, by SDL scancode, that many seconds after start. Lets hidden test runs drive menus.
+static void RunKeyScript(InputCommon::InputSubsystem& input_subsystem) {
+ struct KeyEvent {
+ double seconds;
+ int code;
+ };
+ static const std::vector<KeyEvent> script = [] {
+ std::vector<KeyEvent> events;
+ if (const char* text = std::getenv("EDEN_NX2_KEY_SCRIPT")) {
+ std::string list{text};
+ size_t start = 0;
+ while (start < list.size()) {
+ const size_t end = std::min(list.find(',', start), list.size());
+ const std::string item = list.substr(start, end - start);
+ if (const size_t colon = item.find(':'); colon != std::string::npos) {
+ events.push_back({std::stod(item.substr(0, colon)),
+ std::stoi(item.substr(colon + 1))});
+ }
+ start = end + 1;
+ }
+ }
+ return events;
+ }();
+ static const auto begin = std::chrono::steady_clock::now();
+ static size_t next = 0;
+ const double elapsed =
+ std::chrono::duration<double>(std::chrono::steady_clock::now() - begin).count();
+ while (next < script.size() && script[next].seconds <= elapsed) {
+ const int code = script[next].code;
+ LOG_INFO(Frontend, "Key script: {} key {} at {:.1f} s", code < 0 ? "release" : "press",
+ code < 0 ? -code : code, elapsed);
+ if (code < 0) {
+ input_subsystem.GetKeyboard()->ReleaseKey(static_cast<std::size_t>(-code));
+ } else {
+ input_subsystem.GetKeyboard()->PressKey(static_cast<std::size_t>(code));
+ }
+ ++next;
+ }
+}
+
extern "C" SDL_AppResult SDL_AppIterate(void *appstate) {
SdlState *state = (SdlState *)appstate;
+ RunKeyScript(state->input_subsystem);
return state->emu_window && state->emu_window->IsOpen() ? SDL_APP_CONTINUE : SDL_APP_SUCCESS;
}
extern "C" SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) {