Files
Xenia-Canary/src/xenia/gpu/pm4_command_processor_implement.h
chss95cs@gmail.com f931c34ecb Cleaned up for commit, moved WriteRegistersFromMemCommonSense code into WriteRegistersFromMem
optimized copy_and_swap_32_unaligned further
2022-12-14 11:34:33 -08:00

1203 lines
43 KiB
C++

#pragma once
using namespace xe::gpu::xenos;
void COMMAND_PROCESSOR::ExecuteIndirectBuffer(uint32_t ptr,
uint32_t count) XE_RESTRICT {
SCOPE_profile_cpu_f("gpu");
trace_writer_.WriteIndirectBufferStart(ptr, count * sizeof(uint32_t));
if (count != 0) {
RingBuffer old_reader = reader_;
// Execute commands!
new (&reader_)
RingBuffer(memory_->TranslatePhysical(ptr), count * sizeof(uint32_t));
reader_.set_write_offset(count * sizeof(uint32_t));
// prefetch the wraparound range
// it likely is already in L3 cache, but in a zen system it may be another
// chiplets l3
reader_.BeginPrefetchedRead<swcache::PrefetchTag::Level2>(
COMMAND_PROCESSOR::GetCurrentRingReadCount());
do {
if (COMMAND_PROCESSOR::ExecutePacket()) {
continue;
} else {
// Return up a level if we encounter a bad packet.
XELOGE("**** INDIRECT RINGBUFFER: Failed to execute packet.");
assert_always();
// break;
}
} while (reader_.read_count());
trace_writer_.WriteIndirectBufferEnd();
reader_ = old_reader;
} else {
// rare, but i've seen it happen! (and then a division by 0 occurs)
return;
}
}
bool COMMAND_PROCESSOR::ExecutePacket() {
const uint32_t packet = reader_.ReadAndSwap<uint32_t>();
const uint32_t packet_type = packet >> 30;
XE_LIKELY_IF(packet && packet != 0x0BADF00D) {
XE_LIKELY_IF((packet != 0xCDCDCDCD)) {
actually_execute_packet:
// chrispy: reorder checks by probability
XE_LIKELY_IF(packet_type == 3) {
return COMMAND_PROCESSOR::ExecutePacketType3(packet);
}
else {
if (packet_type ==
0) { // dont know whether 0 or 1 are the next most frequent
return COMMAND_PROCESSOR::ExecutePacketType0(packet);
} else {
if (packet_type == 1) {
return COMMAND_PROCESSOR::ExecutePacketType1(packet);
} else {
// originally there was a default case that msvc couldn't optimize
// away because it doesnt have value range analysis but in reality
// there is no default, a uint32_t >> 30 only has 4 possible values
// and all are covered here
// return COMMAND_PROCESSOR::ExecutePacketType2(packet);
// executepackettype2 is identical
goto handle_bad_packet;
}
}
}
}
else {
XELOGW("GPU packet is CDCDCDCD - probably read uninitialized memory!");
goto actually_execute_packet;
}
}
else {
handle_bad_packet:
trace_writer_.WritePacketStart(uint32_t(reader_.read_ptr() - 4), 1);
trace_writer_.WritePacketEnd();
return true;
}
}
XE_NOINLINE
XE_COLD
bool COMMAND_PROCESSOR::ExecutePacketType0_CountOverflow(uint32_t count) {
XELOGE("ExecutePacketType0 overflow (read count {:08X}, packet count {:08X})",
COMMAND_PROCESSOR::GetCurrentRingReadCount(),
count * sizeof(uint32_t));
return false;
}
/*
Todo: optimize this function this one along with execute packet type III are
the most frequently called functions for PM4
*/
XE_NOINLINE
bool COMMAND_PROCESSOR::ExecutePacketType0(uint32_t packet) XE_RESTRICT {
// Type-0 packet.
// Write count registers in sequence to the registers starting at
// (base_index << 2).
uint32_t count = ((packet >> 16) & 0x3FFF) + 1;
if (COMMAND_PROCESSOR::GetCurrentRingReadCount() >=
count * sizeof(uint32_t)) {
trace_writer_.WritePacketStart(uint32_t(reader_.read_ptr() - 4), 1 + count);
uint32_t base_index = (packet & 0x7FFF);
uint32_t write_one_reg = (packet >> 15) & 0x1;
if (!write_one_reg) {
COMMAND_PROCESSOR::WriteRegisterRangeFromRing(&reader_, base_index,
count);
} else {
COMMAND_PROCESSOR::WriteOneRegisterFromRing(base_index, count);
}
trace_writer_.WritePacketEnd();
return true;
} else {
return COMMAND_PROCESSOR::ExecutePacketType0_CountOverflow(count);
}
}
XE_NOINLINE
bool COMMAND_PROCESSOR::ExecutePacketType1(uint32_t packet) XE_RESTRICT {
// Type-1 packet.
// Contains two registers of data. Type-0 should be more common.
trace_writer_.WritePacketStart(uint32_t(reader_.read_ptr() - 4), 3);
uint32_t reg_index_1 = packet & 0x7FF;
uint32_t reg_index_2 = (packet >> 11) & 0x7FF;
uint32_t reg_data_1 = reader_.ReadAndSwap<uint32_t>();
uint32_t reg_data_2 = reader_.ReadAndSwap<uint32_t>();
COMMAND_PROCESSOR::WriteRegister(reg_index_1, reg_data_1);
COMMAND_PROCESSOR::WriteRegister(reg_index_2, reg_data_2);
trace_writer_.WritePacketEnd();
return true;
}
bool COMMAND_PROCESSOR::ExecutePacketType2(uint32_t packet) XE_RESTRICT {
// Type-2 packet.
// No-op. Do nothing.
trace_writer_.WritePacketStart(uint32_t(reader_.read_ptr() - 4), 1);
trace_writer_.WritePacketEnd();
return true;
}
XE_FORCEINLINE
XE_NOALIAS
uint32_t COMMAND_PROCESSOR::GetCurrentRingReadCount() {
return reader_.read_count();
}
XE_NOINLINE
XE_COLD
bool COMMAND_PROCESSOR::ExecutePacketType3_CountOverflow(uint32_t count) {
XELOGE("ExecutePacketType3 overflow (read count {:08X}, packet count {:08X})",
COMMAND_PROCESSOR::GetCurrentRingReadCount(),
count * sizeof(uint32_t));
return false;
}
XE_NOINLINE
bool COMMAND_PROCESSOR::ExecutePacketType3(uint32_t packet) XE_RESTRICT {
// Type-3 packet.
uint32_t opcode = (packet >> 8) & 0x7F;
uint32_t count = ((packet >> 16) & 0x3FFF) + 1;
auto data_start_offset = reader_.read_offset();
if (COMMAND_PROCESSOR::GetCurrentRingReadCount() >=
count * sizeof(uint32_t)) {
// To handle nesting behavior when tracing we special case indirect buffers.
if (opcode == PM4_INDIRECT_BUFFER) {
trace_writer_.WritePacketStart(uint32_t(reader_.read_ptr() - 4), 2);
} else {
trace_writer_.WritePacketStart(uint32_t(reader_.read_ptr() - 4),
1 + count);
}
// & 1 == predicate - when set, we do bin check to see if we should execute
// the packet. Only type 3 packets are affected.
// We also skip predicated swaps, as they are never valid (probably?).
if (packet & 1) {
bool any_pass = (bin_select_ & bin_mask_) != 0;
if (!any_pass || opcode == PM4_XE_SWAP) {
reader_.AdvanceRead(count * sizeof(uint32_t));
trace_writer_.WritePacketEnd();
return true;
}
}
bool result = false;
switch (opcode) {
case PM4_ME_INIT:
result = COMMAND_PROCESSOR::ExecutePacketType3_ME_INIT(packet, count);
break;
case PM4_NOP:
result = COMMAND_PROCESSOR::ExecutePacketType3_NOP(packet, count);
break;
case PM4_INTERRUPT:
result = COMMAND_PROCESSOR::ExecutePacketType3_INTERRUPT(packet, count);
break;
case PM4_XE_SWAP:
result = COMMAND_PROCESSOR::ExecutePacketType3_XE_SWAP(packet, count);
break;
case PM4_INDIRECT_BUFFER:
case PM4_INDIRECT_BUFFER_PFD:
result = COMMAND_PROCESSOR::ExecutePacketType3_INDIRECT_BUFFER(packet,
count);
break;
case PM4_WAIT_REG_MEM:
result =
COMMAND_PROCESSOR::ExecutePacketType3_WAIT_REG_MEM(packet, count);
break;
case PM4_REG_RMW:
result = COMMAND_PROCESSOR::ExecutePacketType3_REG_RMW(packet, count);
break;
case PM4_REG_TO_MEM:
result =
COMMAND_PROCESSOR::ExecutePacketType3_REG_TO_MEM(packet, count);
break;
case PM4_MEM_WRITE:
result = COMMAND_PROCESSOR::ExecutePacketType3_MEM_WRITE(packet, count);
break;
case PM4_COND_WRITE:
result =
COMMAND_PROCESSOR::ExecutePacketType3_COND_WRITE(packet, count);
break;
case PM4_EVENT_WRITE:
result =
COMMAND_PROCESSOR::ExecutePacketType3_EVENT_WRITE(packet, count);
break;
case PM4_EVENT_WRITE_SHD:
result = COMMAND_PROCESSOR::ExecutePacketType3_EVENT_WRITE_SHD(packet,
count);
break;
case PM4_EVENT_WRITE_EXT:
result = COMMAND_PROCESSOR::ExecutePacketType3_EVENT_WRITE_EXT(packet,
count);
break;
case PM4_EVENT_WRITE_ZPD:
result = COMMAND_PROCESSOR::ExecutePacketType3_EVENT_WRITE_ZPD(packet,
count);
break;
case PM4_DRAW_INDX:
result = COMMAND_PROCESSOR::ExecutePacketType3_DRAW_INDX(packet, count);
break;
case PM4_DRAW_INDX_2:
result =
COMMAND_PROCESSOR::ExecutePacketType3_DRAW_INDX_2(packet, count);
break;
case PM4_SET_CONSTANT:
result =
COMMAND_PROCESSOR::ExecutePacketType3_SET_CONSTANT(packet, count);
break;
case PM4_SET_CONSTANT2:
result =
COMMAND_PROCESSOR::ExecutePacketType3_SET_CONSTANT2(packet, count);
break;
case PM4_LOAD_ALU_CONSTANT:
result = COMMAND_PROCESSOR::ExecutePacketType3_LOAD_ALU_CONSTANT(packet,
count);
break;
case PM4_SET_SHADER_CONSTANTS:
result = COMMAND_PROCESSOR::ExecutePacketType3_SET_SHADER_CONSTANTS(
packet, count);
break;
case PM4_IM_LOAD:
result = COMMAND_PROCESSOR::ExecutePacketType3_IM_LOAD(packet, count);
break;
case PM4_IM_LOAD_IMMEDIATE:
result = COMMAND_PROCESSOR::ExecutePacketType3_IM_LOAD_IMMEDIATE(packet,
count);
break;
case PM4_INVALIDATE_STATE:
result = COMMAND_PROCESSOR::ExecutePacketType3_INVALIDATE_STATE(packet,
count);
break;
case PM4_VIZ_QUERY:
result = COMMAND_PROCESSOR::ExecutePacketType3_VIZ_QUERY(packet, count);
break;
case PM4_SET_BIN_MASK_LO: {
uint32_t value = reader_.ReadAndSwap<uint32_t>();
bin_mask_ = (bin_mask_ & 0xFFFFFFFF00000000ull) | value;
result = true;
} break;
case PM4_SET_BIN_MASK_HI: {
uint32_t value = reader_.ReadAndSwap<uint32_t>();
bin_mask_ =
(bin_mask_ & 0xFFFFFFFFull) | (static_cast<uint64_t>(value) << 32);
result = true;
} break;
case PM4_SET_BIN_SELECT_LO: {
uint32_t value = reader_.ReadAndSwap<uint32_t>();
bin_select_ = (bin_select_ & 0xFFFFFFFF00000000ull) | value;
result = true;
} break;
case PM4_SET_BIN_SELECT_HI: {
uint32_t value = reader_.ReadAndSwap<uint32_t>();
bin_select_ = (bin_select_ & 0xFFFFFFFFull) |
(static_cast<uint64_t>(value) << 32);
result = true;
} break;
case PM4_SET_BIN_MASK: {
assert_true(count == 2);
uint64_t val_hi = reader_.ReadAndSwap<uint32_t>();
uint64_t val_lo = reader_.ReadAndSwap<uint32_t>();
bin_mask_ = (val_hi << 32) | val_lo;
result = true;
} break;
case PM4_SET_BIN_SELECT: {
assert_true(count == 2);
uint64_t val_hi = reader_.ReadAndSwap<uint32_t>();
uint64_t val_lo = reader_.ReadAndSwap<uint32_t>();
bin_select_ = (val_hi << 32) | val_lo;
result = true;
} break;
case PM4_CONTEXT_UPDATE: {
assert_true(count == 1);
uint32_t value = reader_.ReadAndSwap<uint32_t>();
XELOGGPU("GPU context update = {:08X}", value);
assert_true(value == 0);
result = true;
break;
}
case PM4_WAIT_FOR_IDLE: {
// This opcode is used by 5454084E while going / being ingame.
assert_true(count == 1);
uint32_t value = reader_.ReadAndSwap<uint32_t>();
XELOGGPU("GPU wait for idle = {:08X}", value);
result = true;
break;
}
default:
return COMMAND_PROCESSOR::HitUnimplementedOpcode(opcode, count);
}
trace_writer_.WritePacketEnd();
#if XE_ENABLE_TRACE_WRITER_INSTRUMENTATION == 1
if (opcode == PM4_XE_SWAP) {
// End the trace writer frame.
if (trace_writer_.is_open()) {
trace_writer_.WriteEvent(EventCommand::Type::kSwap);
trace_writer_.Flush();
if (trace_state_ == TraceState::kSingleFrame) {
trace_state_ = TraceState::kDisabled;
trace_writer_.Close();
}
} else if (trace_state_ == TraceState::kSingleFrame) {
// New trace request - we only start tracing at the beginning of a
// frame.
uint32_t title_id = kernel_state_->GetExecutableModule()->title_id();
auto file_name = fmt::format("{:08X}_{}.xtr", title_id, counter_ - 1);
auto path = trace_frame_path_ / file_name;
trace_writer_.Open(path, title_id);
InitializeTrace();
}
}
#endif
assert_true(reader_.read_offset() ==
(data_start_offset + (count * sizeof(uint32_t))) %
reader_.capacity());
return result;
} else {
return COMMAND_PROCESSOR::ExecutePacketType3_CountOverflow(count);
}
}
XE_NOINLINE
XE_COLD
bool COMMAND_PROCESSOR::HitUnimplementedOpcode(uint32_t opcode,
uint32_t count) XE_RESTRICT {
XELOGGPU("Unimplemented GPU OPCODE: 0x{:02X}\t\tCOUNT: {}\n", opcode, count);
assert_always();
reader_.AdvanceRead(count * sizeof(uint32_t));
trace_writer_.WritePacketEnd();
return false;
}
XE_NOINLINE
bool COMMAND_PROCESSOR::ExecutePacketType3_ME_INIT(uint32_t packet,
uint32_t count) XE_RESTRICT {
// initialize CP's micro-engine
me_bin_.resize(count);
for (uint32_t i = 0; i < count; i++) {
me_bin_[i] = reader_.ReadAndSwap<uint32_t>();
}
return true;
}
bool COMMAND_PROCESSOR::ExecutePacketType3_NOP(uint32_t packet,
uint32_t count) XE_RESTRICT {
// skip N 32-bit words to get to the next packet
// No-op, ignore some data.
reader_.AdvanceRead(count * sizeof(uint32_t));
return true;
}
XE_NOINLINE
bool COMMAND_PROCESSOR::ExecutePacketType3_INTERRUPT(
uint32_t packet, uint32_t count) XE_RESTRICT {
SCOPE_profile_cpu_f("gpu");
// generate interrupt from the command stream
uint32_t cpu_mask = reader_.ReadAndSwap<uint32_t>();
for (int n = 0; n < 6; n++) {
if (cpu_mask & (1 << n)) {
graphics_system_->DispatchInterruptCallback(1, n);
}
}
return true;
}
XE_NOINLINE
bool COMMAND_PROCESSOR::ExecutePacketType3_XE_SWAP(uint32_t packet,
uint32_t count) XE_RESTRICT {
SCOPE_profile_cpu_f("gpu");
Profiler::Flip();
// Xenia-specific VdSwap hook.
// VdSwap will post this to tell us we need to swap the screen/fire an
// interrupt.
// 63 words here, but only the first has any data.
uint32_t magic = reader_.ReadAndSwap<fourcc_t>();
assert_true(magic == kSwapSignature);
// TODO(benvanik): only swap frontbuffer ptr.
uint32_t frontbuffer_ptr = reader_.ReadAndSwap<uint32_t>();
uint32_t frontbuffer_width = reader_.ReadAndSwap<uint32_t>();
uint32_t frontbuffer_height = reader_.ReadAndSwap<uint32_t>();
reader_.AdvanceRead((count - 4) * sizeof(uint32_t));
COMMAND_PROCESSOR::IssueSwap(frontbuffer_ptr, frontbuffer_width,
frontbuffer_height);
++counter_;
return true;
}
bool COMMAND_PROCESSOR::ExecutePacketType3_INDIRECT_BUFFER(
uint32_t packet, uint32_t count) XE_RESTRICT {
// indirect buffer dispatch
uint32_t list_ptr = CpuToGpu(reader_.ReadAndSwap<uint32_t>());
uint32_t list_length = reader_.ReadAndSwap<uint32_t>();
assert_zero(list_length & ~0xFFFFF);
list_length &= 0xFFFFF;
COMMAND_PROCESSOR::ExecuteIndirectBuffer(GpuToCpu(list_ptr), list_length);
return true;
}
/*
chrispy: this is fine to inline, as a noinline function it compiled down
to 54 bytes
*/
static bool MatchValueAndRef(uint32_t value, uint32_t ref, uint32_t wait_info) {
// smaller code is generated than the #else path, although whether it is faster
// i do not know. i don't think games do an enormous number of cond_write
// though, so we have picked
// the path with the smaller codegen.
// we do technically have more instructions executed vs the switch case method,
// but we have no mispredicts and most of our instructions are 0.25/0.3
// throughput
#if 1
uint32_t value_minus_ref =
static_cast<uint32_t>(static_cast<int32_t>(value - ref) >> 31);
uint32_t ref_minus_value =
static_cast<uint32_t>(static_cast<int32_t>(ref - value) >> 31);
uint32_t eqmask = ~(value_minus_ref | ref_minus_value);
uint32_t nemask = (value_minus_ref | ref_minus_value);
uint32_t value_lt_mask = value_minus_ref;
uint32_t value_gt_mask = ref_minus_value;
uint32_t value_lte_mask = value_lt_mask | eqmask;
uint32_t value_gte_mask = value_gt_mask | eqmask;
uint32_t bits_for_selecting =
(value_lt_mask & (1 << 1)) | (value_lte_mask & (1 << 2)) |
(eqmask & (1 << 3)) | (nemask & (1 << 4)) | (value_gte_mask & (1 << 5)) |
(value_gt_mask & (1 << 6)) | (1 << 7);
return (bits_for_selecting >> (wait_info & 7)) & 1;
#else
return ((((value < ref) << 1) | ((value <= ref) << 2) |
((value == ref) << 3) | ((value != ref) << 4) |
((value >= ref) << 5) | ((value > ref) << 6) | (1 << 7)) >>
(wait_info & 7)) &
1;
#endif
}
XE_NOINLINE
bool COMMAND_PROCESSOR::ExecutePacketType3_WAIT_REG_MEM(
uint32_t packet, uint32_t count) XE_RESTRICT {
SCOPE_profile_cpu_f("gpu");
// wait until a register or memory location is a specific value
uint32_t wait_info = reader_.ReadAndSwap<uint32_t>();
uint32_t poll_reg_addr = reader_.ReadAndSwap<uint32_t>();
uint32_t ref = reader_.ReadAndSwap<uint32_t>();
uint32_t mask = reader_.ReadAndSwap<uint32_t>();
uint32_t wait = reader_.ReadAndSwap<uint32_t>();
bool matched = false;
do {
uint32_t value;
if (wait_info & 0x10) {
// Memory.
auto endianness = static_cast<xenos::Endian>(poll_reg_addr & 0x3);
poll_reg_addr &= ~0x3;
value = xe::load<uint32_t>(memory_->TranslatePhysical(poll_reg_addr));
value = GpuSwap(value, endianness);
trace_writer_.WriteMemoryRead(CpuToGpu(poll_reg_addr), 4);
} else {
// Register.
assert_true(poll_reg_addr < RegisterFile::kRegisterCount);
value = register_file_->values[poll_reg_addr].u32;
if (poll_reg_addr == XE_GPU_REG_COHER_STATUS_HOST) {
MakeCoherent();
value = register_file_->values[poll_reg_addr].u32;
}
}
matched = MatchValueAndRef(value & mask, ref, wait_info);
if (!matched) {
// Wait.
if (wait >= 0x100) {
PrepareForWait();
if (!cvars::vsync) {
// User wants it fast and dangerous.
// do nothing
} else {
xe::threading::Sleep(std::chrono::milliseconds(wait / 0x100));
ReturnFromWait();
}
if (!worker_running_) {
// Short-circuited exit.
return false;
}
} else {
}
}
} while (!matched);
return true;
}
XE_NOINLINE
bool COMMAND_PROCESSOR::ExecutePacketType3_REG_RMW(uint32_t packet,
uint32_t count) XE_RESTRICT {
// register read/modify/write
// ? (used during shader upload and edram setup)
uint32_t rmw_info = reader_.ReadAndSwap<uint32_t>();
uint32_t and_mask = reader_.ReadAndSwap<uint32_t>();
uint32_t or_mask = reader_.ReadAndSwap<uint32_t>();
uint32_t value = register_file_->values[rmw_info & 0x1FFF].u32;
if ((rmw_info >> 31) & 0x1) {
// & reg
value &= register_file_->values[and_mask & 0x1FFF].u32;
} else {
// & imm
value &= and_mask;
}
if ((rmw_info >> 30) & 0x1) {
// | reg
value |= register_file_->values[or_mask & 0x1FFF].u32;
} else {
// | imm
value |= or_mask;
}
COMMAND_PROCESSOR::WriteRegister(rmw_info & 0x1FFF, value);
return true;
}
bool COMMAND_PROCESSOR::ExecutePacketType3_REG_TO_MEM(
uint32_t packet, uint32_t count) XE_RESTRICT {
// Copy Register to Memory (?)
// Count is 2, assuming a Register Addr and a Memory Addr.
uint32_t reg_addr = reader_.ReadAndSwap<uint32_t>();
uint32_t mem_addr = reader_.ReadAndSwap<uint32_t>();
uint32_t reg_val;
assert_true(reg_addr < RegisterFile::kRegisterCount);
reg_val = register_file_->values[reg_addr].u32;
auto endianness = static_cast<xenos::Endian>(mem_addr & 0x3);
mem_addr &= ~0x3;
reg_val = GpuSwap(reg_val, endianness);
xe::store(memory_->TranslatePhysical(mem_addr), reg_val);
trace_writer_.WriteMemoryWrite(CpuToGpu(mem_addr), 4);
return true;
}
XE_NOINLINE
bool COMMAND_PROCESSOR::ExecutePacketType3_MEM_WRITE(
uint32_t packet, uint32_t count) XE_RESTRICT {
uint32_t write_addr = reader_.ReadAndSwap<uint32_t>();
for (uint32_t i = 0; i < count - 1; i++) {
uint32_t write_data = reader_.ReadAndSwap<uint32_t>();
auto endianness = static_cast<xenos::Endian>(write_addr & 0x3);
auto addr = write_addr & ~0x3;
write_data = GpuSwap(write_data, endianness);
xe::store(memory_->TranslatePhysical(addr), write_data);
trace_writer_.WriteMemoryWrite(CpuToGpu(addr), 4);
write_addr += 4;
}
return true;
}
XE_NOINLINE
bool COMMAND_PROCESSOR::ExecutePacketType3_COND_WRITE(
uint32_t packet, uint32_t count) XE_RESTRICT {
// conditional write to memory or register
uint32_t wait_info = reader_.ReadAndSwap<uint32_t>();
uint32_t poll_reg_addr = reader_.ReadAndSwap<uint32_t>();
uint32_t ref = reader_.ReadAndSwap<uint32_t>();
uint32_t mask = reader_.ReadAndSwap<uint32_t>();
uint32_t write_reg_addr = reader_.ReadAndSwap<uint32_t>();
uint32_t write_data = reader_.ReadAndSwap<uint32_t>();
uint32_t value;
if (wait_info & 0x10) {
// Memory.
auto endianness = static_cast<xenos::Endian>(poll_reg_addr & 0x3);
poll_reg_addr &= ~0x3;
trace_writer_.WriteMemoryRead(CpuToGpu(poll_reg_addr), 4);
value = xe::load<uint32_t>(memory_->TranslatePhysical(poll_reg_addr));
value = GpuSwap(value, endianness);
} else {
// Register.
assert_true(poll_reg_addr < RegisterFile::kRegisterCount);
value = register_file_->values[poll_reg_addr].u32;
}
bool matched = MatchValueAndRef(value & mask, ref, wait_info);
if (matched) {
// Write.
if (wait_info & 0x100) {
// Memory.
auto endianness = static_cast<xenos::Endian>(write_reg_addr & 0x3);
write_reg_addr &= ~0x3;
write_data = GpuSwap(write_data, endianness);
xe::store(memory_->TranslatePhysical(write_reg_addr), write_data);
trace_writer_.WriteMemoryWrite(CpuToGpu(write_reg_addr), 4);
} else {
// Register.
COMMAND_PROCESSOR::WriteRegister(write_reg_addr, write_data);
}
}
return true;
}
XE_FORCEINLINE
void COMMAND_PROCESSOR::WriteEventInitiator(uint32_t value) XE_RESTRICT {
register_file_->values[XE_GPU_REG_VGT_EVENT_INITIATOR].u32 = value;
}
bool COMMAND_PROCESSOR::ExecutePacketType3_EVENT_WRITE(
uint32_t packet, uint32_t count) XE_RESTRICT {
// generate an event that creates a write to memory when completed
uint32_t initiator = reader_.ReadAndSwap<uint32_t>();
// Writeback initiator.
COMMAND_PROCESSOR::WriteEventInitiator(initiator & 0x3f);
if (count == 1) {
// Just an event flag? Where does this write?
} else {
// Write to an address.
assert_always();
reader_.AdvanceRead((count - 1) * sizeof(uint32_t));
}
return true;
}
XE_NOINLINE
bool COMMAND_PROCESSOR::ExecutePacketType3_EVENT_WRITE_SHD(
uint32_t packet, uint32_t count) XE_RESTRICT {
// generate a VS|PS_done event
uint32_t initiator = reader_.ReadAndSwap<uint32_t>();
uint32_t address = reader_.ReadAndSwap<uint32_t>();
uint32_t value = reader_.ReadAndSwap<uint32_t>();
// Writeback initiator.
COMMAND_PROCESSOR::WriteEventInitiator(initiator & 0x3F);
uint32_t data_value;
if ((initiator >> 31) & 0x1) {
// Write counter (GPU vblank counter?).
data_value = counter_;
} else {
// Write value.
data_value = value;
}
auto endianness = static_cast<xenos::Endian>(address & 0x3);
address &= ~0x3;
data_value = GpuSwap(data_value, endianness);
xe::store(memory_->TranslatePhysical(address), data_value);
trace_writer_.WriteMemoryWrite(CpuToGpu(address), 4);
return true;
}
bool COMMAND_PROCESSOR::ExecutePacketType3_EVENT_WRITE_EXT(
uint32_t packet, uint32_t count) XE_RESTRICT {
// generate a screen extent event
uint32_t initiator = reader_.ReadAndSwap<uint32_t>();
uint32_t address = reader_.ReadAndSwap<uint32_t>();
// Writeback initiator.
COMMAND_PROCESSOR::WriteEventInitiator(initiator & 0x3F);
auto endianness = static_cast<xenos::Endian>(address & 0x3);
address &= ~0x3;
// Let us hope we can fake this.
// This callback tells the driver the xy coordinates affected by a previous
// drawcall.
// https://www.google.com/patents/US20060055701
uint16_t extents[] = {
byte_swap<unsigned short>(0 >> 3), // min x
byte_swap<unsigned short>(xenos::kTexture2DCubeMaxWidthHeight >>
3), // max x
byte_swap<unsigned short>(0 >> 3), // min y
byte_swap<unsigned short>(xenos::kTexture2DCubeMaxWidthHeight >>
3), // max y
byte_swap<unsigned short>(0), // min z
byte_swap<unsigned short>(1), // max z
};
assert_true(endianness == xenos::Endian::k8in16);
uint16_t* destination = (uint16_t*)memory_->TranslatePhysical(address);
for (unsigned i = 0; i < 6; ++i) {
destination[i] = extents[i];
}
trace_writer_.WriteMemoryWrite(CpuToGpu(address), sizeof(extents));
return true;
}
XE_NOINLINE
bool COMMAND_PROCESSOR::ExecutePacketType3_EVENT_WRITE_ZPD(
uint32_t packet, uint32_t count) XE_RESTRICT {
// Set by D3D as BE but struct ABI is LE
const uint32_t kQueryFinished = xe::byte_swap(0xFFFFFEED);
assert_true(count == 1);
uint32_t initiator = reader_.ReadAndSwap<uint32_t>();
// Writeback initiator.
COMMAND_PROCESSOR::WriteEventInitiator(initiator & 0x3F);
// Occlusion queries:
// This command is send on query begin and end.
// As a workaround report some fixed amount of passed samples.
auto fake_sample_count = cvars::query_occlusion_fake_sample_count;
if (fake_sample_count >= 0) {
auto* pSampleCounts =
memory_->TranslatePhysical<xe_gpu_depth_sample_counts*>(
register_file_->values[XE_GPU_REG_RB_SAMPLE_COUNT_ADDR].u32);
// 0xFFFFFEED is written to this two locations by D3D only on D3DISSUE_END
// and used to detect a finished query.
bool is_end_via_z_pass = pSampleCounts->ZPass_A == kQueryFinished &&
pSampleCounts->ZPass_B == kQueryFinished;
// Older versions of D3D also checks for ZFail (4D5307D5).
bool is_end_via_z_fail = pSampleCounts->ZFail_A == kQueryFinished &&
pSampleCounts->ZFail_B == kQueryFinished;
std::memset(pSampleCounts, 0, sizeof(xe_gpu_depth_sample_counts));
if (is_end_via_z_pass || is_end_via_z_fail) {
pSampleCounts->ZPass_A = fake_sample_count;
pSampleCounts->Total_A = fake_sample_count;
}
}
return true;
}
bool COMMAND_PROCESSOR::ExecutePacketType3Draw(
uint32_t packet, const char* opcode_name, uint32_t viz_query_condition,
uint32_t count_remaining) XE_RESTRICT {
// if viz_query_condition != 0, this is a conditional draw based on viz query.
// This ID matches the one issued in PM4_VIZ_QUERY
// uint32_t viz_id = viz_query_condition & 0x3F;
// when true, render conditionally based on query result
// uint32_t viz_use = viz_query_condition & 0x100;
assert_not_zero(count_remaining);
if (!count_remaining) {
XELOGE("{}: Packet too small, can't read VGT_DRAW_INITIATOR", opcode_name);
return false;
}
reg::VGT_DRAW_INITIATOR vgt_draw_initiator;
vgt_draw_initiator.value = reader_.ReadAndSwap<uint32_t>();
--count_remaining;
register_file_->values[XE_GPU_REG_VGT_DRAW_INITIATOR].u32 =
vgt_draw_initiator.value;
bool draw_succeeded = true;
// TODO(Triang3l): Remove IndexBufferInfo and replace handling of all this
// with PrimitiveProcessor when the old Vulkan renderer is removed.
bool is_indexed = false;
IndexBufferInfo index_buffer_info;
switch (vgt_draw_initiator.source_select) {
case xenos::SourceSelect::kDMA: {
// Indexed draw.
is_indexed = true;
// Two separate bounds checks so if there's only one missing register
// value out of two, one uint32_t will be skipped in the command buffer,
// not two.
assert_not_zero(count_remaining);
if (!count_remaining) {
XELOGE("{}: Packet too small, can't read VGT_DMA_BASE", opcode_name);
return false;
}
uint32_t vgt_dma_base = reader_.ReadAndSwap<uint32_t>();
--count_remaining;
register_file_->values[XE_GPU_REG_VGT_DMA_BASE].u32 = vgt_dma_base;
reg::VGT_DMA_SIZE vgt_dma_size;
assert_not_zero(count_remaining);
if (!count_remaining) {
XELOGE("{}: Packet too small, can't read VGT_DMA_SIZE", opcode_name);
return false;
}
vgt_dma_size.value = reader_.ReadAndSwap<uint32_t>();
--count_remaining;
register_file_->values[XE_GPU_REG_VGT_DMA_SIZE].u32 = vgt_dma_size.value;
uint32_t index_size_bytes =
vgt_draw_initiator.index_size == xenos::IndexFormat::kInt16
? sizeof(uint16_t)
: sizeof(uint32_t);
// The base address must already be word-aligned according to the R6xx
// documentation, but for safety.
index_buffer_info.guest_base = vgt_dma_base & ~(index_size_bytes - 1);
index_buffer_info.endianness = vgt_dma_size.swap_mode;
index_buffer_info.format = vgt_draw_initiator.index_size;
index_buffer_info.length = vgt_dma_size.num_words * index_size_bytes;
index_buffer_info.count = vgt_draw_initiator.num_indices;
} break;
case xenos::SourceSelect::kImmediate: {
// TODO(Triang3l): VGT_IMMED_DATA.
XELOGE(
"{}: Using immediate vertex indices, which are not supported yet. "
"Report the game to Xenia developers!",
opcode_name, uint32_t(vgt_draw_initiator.source_select));
draw_succeeded = false;
assert_always();
} break;
case xenos::SourceSelect::kAutoIndex: {
// Auto draw.
index_buffer_info.guest_base = 0;
index_buffer_info.length = 0;
} break;
default: {
// Invalid source selection.
draw_succeeded = false;
assert_unhandled_case(vgt_draw_initiator.source_select);
} break;
}
// Skip to the next command, for example, if there are immediate indexes that
// we don't support yet.
reader_.AdvanceRead(count_remaining * sizeof(uint32_t));
if (draw_succeeded) {
auto viz_query = register_file_->Get<reg::PA_SC_VIZ_QUERY>();
if (!(viz_query.viz_query_ena && viz_query.kill_pix_post_hi_z)) {
// TODO(Triang3l): Don't drop the draw call completely if the vertex
// shader has memexport.
// TODO(Triang3l || JoelLinn): Handle this properly in the render
// backends.
draw_succeeded = COMMAND_PROCESSOR::IssueDraw(
vgt_draw_initiator.prim_type, vgt_draw_initiator.num_indices,
is_indexed ? &index_buffer_info : nullptr,
xenos::IsMajorModeExplicit(vgt_draw_initiator.major_mode,
vgt_draw_initiator.prim_type));
if (!draw_succeeded) {
XELOGE("{}({}, {}, {}): Failed in backend", opcode_name,
vgt_draw_initiator.num_indices,
uint32_t(vgt_draw_initiator.prim_type),
uint32_t(vgt_draw_initiator.source_select));
}
}
}
// If read the packed correctly, but merely couldn't execute it (because of,
// for instance, features not supported by the host), don't terminate command
// buffer processing as that would leave rendering in a way more inconsistent
// state than just a single dropped draw command.
return true;
}
bool COMMAND_PROCESSOR::ExecutePacketType3_DRAW_INDX(
uint32_t packet, uint32_t count) XE_RESTRICT {
// "initiate fetch of index buffer and draw"
// Generally used by Xbox 360 Direct3D 9 for kDMA and kAutoIndex sources.
// With a viz query token as the first one.
uint32_t count_remaining = count;
assert_not_zero(count_remaining);
if (!count_remaining) {
XELOGE("PM4_DRAW_INDX: Packet too small, can't read the viz query token");
return false;
}
uint32_t viz_query_condition = reader_.ReadAndSwap<uint32_t>();
--count_remaining;
return COMMAND_PROCESSOR::ExecutePacketType3Draw(
packet, "PM4_DRAW_INDX", viz_query_condition, count_remaining);
}
bool COMMAND_PROCESSOR::ExecutePacketType3_DRAW_INDX_2(
uint32_t packet, uint32_t count) XE_RESTRICT {
// "draw using supplied indices in packet"
// Generally used by Xbox 360 Direct3D 9 for kAutoIndex source.
// No viz query token.
return COMMAND_PROCESSOR::ExecutePacketType3Draw(packet, "PM4_DRAW_INDX_2", 0,
count);
}
XE_FORCEINLINE
bool COMMAND_PROCESSOR::ExecutePacketType3_SET_CONSTANT(
uint32_t packet, uint32_t count) XE_RESTRICT {
// load constant into chip and to memory
// PM4_REG(reg) ((0x4 << 16) | (GSL_HAL_SUBBLOCK_OFFSET(reg)))
// reg - 0x2000
uint32_t offset_type = reader_.ReadAndSwap<uint32_t>();
uint32_t index = offset_type & 0x7FF;
uint32_t type = (offset_type >> 16) & 0xFF;
uint32_t countm1 = count - 1;
switch (type) {
case 0: // ALU
// index += 0x4000;
// COMMAND_PROCESSOR::WriteRegisterRangeFromRing( index, countm1);
COMMAND_PROCESSOR::WriteALURangeFromRing(&reader_, index, countm1);
break;
case 1: // FETCH
COMMAND_PROCESSOR::WriteFetchRangeFromRing(&reader_, index, countm1);
break;
case 2: // BOOL
COMMAND_PROCESSOR::WriteBoolRangeFromRing(&reader_, index, countm1);
break;
case 3: // LOOP
COMMAND_PROCESSOR::WriteLoopRangeFromRing(&reader_, index, countm1);
break;
case 4: // REGISTERS
COMMAND_PROCESSOR::WriteREGISTERSRangeFromRing(&reader_, index, countm1);
break;
default:
assert_always();
reader_.AdvanceRead((count - 1) * sizeof(uint32_t));
return true;
}
return true;
}
XE_NOINLINE
bool COMMAND_PROCESSOR::ExecutePacketType3_SET_CONSTANT2(
uint32_t packet, uint32_t count) XE_RESTRICT {
uint32_t offset_type = reader_.ReadAndSwap<uint32_t>();
uint32_t index = offset_type & 0xFFFF;
uint32_t countm1 = count - 1;
COMMAND_PROCESSOR::WriteRegisterRangeFromRing(&reader_, index, countm1);
return true;
}
XE_FORCEINLINE
bool COMMAND_PROCESSOR::ExecutePacketType3_LOAD_ALU_CONSTANT(
uint32_t packet, uint32_t count) XE_RESTRICT {
// load constants from memory
uint32_t address = reader_.ReadAndSwap<uint32_t>();
address &= 0x3FFFFFFF;
uint32_t offset_type = reader_.ReadAndSwap<uint32_t>();
uint32_t index = offset_type & 0x7FF;
uint32_t size_dwords = reader_.ReadAndSwap<uint32_t>();
size_dwords &= 0xFFF;
uint32_t type = (offset_type >> 16) & 0xFF;
auto xlat_address = (uint32_t*)memory_->TranslatePhysical(address);
switch (type) {
case 0: // ALU
trace_writer_.WriteMemoryRead(CpuToGpu(address), size_dwords * 4);
COMMAND_PROCESSOR::WriteALURangeFromMem(index, xlat_address, size_dwords);
break;
case 1: // FETCH
trace_writer_.WriteMemoryRead(CpuToGpu(address), size_dwords * 4);
COMMAND_PROCESSOR::WriteFetchRangeFromMem(index, xlat_address,
size_dwords);
break;
case 2: // BOOL
trace_writer_.WriteMemoryRead(CpuToGpu(address), size_dwords * 4);
COMMAND_PROCESSOR::WriteBoolRangeFromMem(index, xlat_address,
size_dwords);
break;
case 3: // LOOP
trace_writer_.WriteMemoryRead(CpuToGpu(address), size_dwords * 4);
COMMAND_PROCESSOR::WriteLoopRangeFromMem(index, xlat_address,
size_dwords);
break;
case 4: // REGISTERS
// chrispy: todo, REGISTERS cannot write any special regs, so optimize for
// that
trace_writer_.WriteMemoryRead(CpuToGpu(address), size_dwords * 4);
COMMAND_PROCESSOR::WriteREGISTERSRangeFromMem(index, xlat_address,
size_dwords);
break;
default:
assert_always();
return true;
}
return true;
}
bool COMMAND_PROCESSOR::ExecutePacketType3_SET_SHADER_CONSTANTS(
uint32_t packet, uint32_t count) XE_RESTRICT {
uint32_t offset_type = reader_.ReadAndSwap<uint32_t>();
uint32_t index = offset_type & 0xFFFF;
uint32_t countm1 = count - 1;
COMMAND_PROCESSOR::WriteRegisterRangeFromRing(&reader_, index, countm1);
return true;
}
bool COMMAND_PROCESSOR::ExecutePacketType3_IM_LOAD(uint32_t packet,
uint32_t count) XE_RESTRICT {
SCOPE_profile_cpu_f("gpu");
// load sequencer instruction memory (pointer-based)
uint32_t addr_type = reader_.ReadAndSwap<uint32_t>();
auto shader_type = static_cast<xenos::ShaderType>(addr_type & 0x3);
uint32_t addr = addr_type & ~0x3;
uint32_t start_size = reader_.ReadAndSwap<uint32_t>();
uint32_t start = start_size >> 16;
uint32_t size_dwords = start_size & 0xFFFF; // dwords
assert_true(start == 0);
trace_writer_.WriteMemoryRead(CpuToGpu(addr), size_dwords * 4);
auto shader = COMMAND_PROCESSOR::LoadShader(
shader_type, addr, memory_->TranslatePhysical<uint32_t*>(addr),
size_dwords);
switch (shader_type) {
case xenos::ShaderType::kVertex:
active_vertex_shader_ = shader;
break;
case xenos::ShaderType::kPixel:
active_pixel_shader_ = shader;
break;
default:
assert_unhandled_case(shader_type);
return false;
}
return true;
}
bool COMMAND_PROCESSOR::ExecutePacketType3_IM_LOAD_IMMEDIATE(
uint32_t packet, uint32_t count) XE_RESTRICT {
SCOPE_profile_cpu_f("gpu");
// load sequencer instruction memory (code embedded in packet)
uint32_t dword0 = reader_.ReadAndSwap<uint32_t>();
uint32_t dword1 = reader_.ReadAndSwap<uint32_t>();
auto shader_type = static_cast<xenos::ShaderType>(dword0);
uint32_t start_size = dword1;
uint32_t start = start_size >> 16;
uint32_t size_dwords = start_size & 0xFFFF; // dwords
assert_true(start == 0);
assert_true(reader_.read_count() >= size_dwords * 4);
assert_true(count - 2 >= size_dwords);
auto shader = COMMAND_PROCESSOR::LoadShader(
shader_type, uint32_t(reader_.read_ptr()),
reinterpret_cast<uint32_t*>(reader_.read_ptr()), size_dwords);
switch (shader_type) {
case xenos::ShaderType::kVertex:
active_vertex_shader_ = shader;
break;
case xenos::ShaderType::kPixel:
active_pixel_shader_ = shader;
break;
default:
assert_unhandled_case(shader_type);
return false;
}
reader_.AdvanceRead(size_dwords * sizeof(uint32_t));
return true;
}
/*
todo: shouldn't this do something?
*/
bool COMMAND_PROCESSOR::ExecutePacketType3_INVALIDATE_STATE(
uint32_t packet, uint32_t count) XE_RESTRICT {
// selective invalidation of state pointers
/*uint32_t mask =*/reader_.ReadAndSwap<uint32_t>();
// driver_->InvalidateState(mask);
return true;
}
bool COMMAND_PROCESSOR::ExecutePacketType3_VIZ_QUERY(
uint32_t packet, uint32_t count) XE_RESTRICT {
// begin/end initiator for viz query extent processing
// https://www.google.com/patents/US20050195186
assert_true(count == 1);
uint32_t dword0 = reader_.ReadAndSwap<uint32_t>();
uint32_t id = dword0 & 0x3F;
uint32_t end = dword0 & 0x100;
if (!end) {
// begin a new viz query @ id
// On hardware this clears the internal state of the scan converter (which
// is different to the register)
COMMAND_PROCESSOR::WriteEventInitiator(VIZQUERY_START);
// XELOGGPU("Begin viz query ID {:02X}", id);
} else {
// end the viz query
COMMAND_PROCESSOR::WriteEventInitiator(VIZQUERY_END);
// XELOGGPU("End viz query ID {:02X}", id);
// The scan converter writes the internal result back to the register here.
// We just fake it and say it was visible in case it is read back.
if (id < 32) {
register_file_->values[XE_GPU_REG_PA_SC_VIZ_QUERY_STATUS_0].u32 |=
uint32_t(1) << id;
} else {
register_file_->values[XE_GPU_REG_PA_SC_VIZ_QUERY_STATUS_1].u32 |=
uint32_t(1) << (id - 32);
}
}
return true;
}
uint32_t COMMAND_PROCESSOR::ExecutePrimaryBuffer(uint32_t read_index,
uint32_t write_index) {
SCOPE_profile_cpu_f("gpu");
#if XE_ENABLE_TRACE_WRITER_INSTRUMENTATION == 1
// If we have a pending trace stream open it now. That way we ensure we get
// all commands.
if (!trace_writer_.is_open() && trace_state_ == TraceState::kStreaming) {
uint32_t title_id = kernel_state_->GetExecutableModule()
? kernel_state_->GetExecutableModule()->title_id()
: 0;
auto file_name = fmt::format("{:08X}_stream.xtr", title_id);
auto path = trace_stream_path_ / file_name;
trace_writer_.Open(path, title_id);
InitializeTrace();
}
#endif
// Adjust pointer base.
uint32_t start_ptr = primary_buffer_ptr_ + read_index * sizeof(uint32_t);
start_ptr = (primary_buffer_ptr_ & ~0x1FFFFFFF) | (start_ptr & 0x1FFFFFFF);
uint32_t end_ptr = primary_buffer_ptr_ + write_index * sizeof(uint32_t);
end_ptr = (primary_buffer_ptr_ & ~0x1FFFFFFF) | (end_ptr & 0x1FFFFFFF);
trace_writer_.WritePrimaryBufferStart(start_ptr, write_index - read_index);
// Execute commands!
RingBuffer old_reader = reader_;
new (&reader_) RingBuffer(memory_->TranslatePhysical(primary_buffer_ptr_),
primary_buffer_size_);
reader_.set_read_offset(read_index * sizeof(uint32_t));
reader_.set_write_offset(write_index * sizeof(uint32_t));
// prefetch the wraparound range
// it likely is already in L3 cache, but in a zen system it may be another
// chiplets l3
reader_.BeginPrefetchedRead<swcache::PrefetchTag::Level2>(
GetCurrentRingReadCount());
do {
if (!COMMAND_PROCESSOR::ExecutePacket()) {
// This probably should be fatal - but we're going to continue anyways.
XELOGE("**** PRIMARY RINGBUFFER: Failed to execute packet.");
assert_always();
break;
}
} while (reader_.read_count());
COMMAND_PROCESSOR::OnPrimaryBufferEnd();
trace_writer_.WritePrimaryBufferEnd();
reader_ = old_reader;
return write_index;
}
void COMMAND_PROCESSOR::ExecutePacket(uint32_t ptr, uint32_t count) {
// Execute commands!
RingBuffer old_reader = reader_;
new (&reader_)
RingBuffer{memory_->TranslatePhysical(ptr), count * sizeof(uint32_t)};
reader_.set_write_offset(count * sizeof(uint32_t));
do {
if (!COMMAND_PROCESSOR::ExecutePacket()) {
XELOGE("**** ExecutePacket: Failed to execute packet.");
assert_always();
break;
}
} while (reader_.read_count());
reader_ = old_reader;
}