/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2013 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include #include #include #include using namespace xe; using namespace xe::gpu; using namespace xe::gpu::xenos; RingBufferWorker::RingBufferWorker(xe_memory_ref memory) : memory_(memory), driver_(0) { write_ptr_index_event_ = CreateEvent( NULL, FALSE, FALSE, NULL); primary_buffer_ptr_ = 0; primary_buffer_size_ = 0; read_ptr_index_ = 0; read_ptr_update_freq_ = 0; read_ptr_writeback_ptr_ = 0; write_ptr_index_ = 0; } RingBufferWorker::~RingBufferWorker() { SetEvent(write_ptr_index_event_); CloseHandle(write_ptr_index_event_); } void RingBufferWorker::Initialize(GraphicsDriver* driver, uint32_t ptr, uint32_t page_count) { driver_ = driver; primary_buffer_ptr_ = ptr; primary_buffer_size_ = page_count * 4 * 1024; read_ptr_index_ = 0; } void RingBufferWorker::EnableReadPointerWriteBack(uint32_t ptr, uint32_t block_size) { // CP_RB_RPTR_ADDR Ring Buffer Read Pointer Address 0x70C // ptr = RB_RPTR_ADDR, pointer to write back the address to. read_ptr_writeback_ptr_ = (primary_buffer_ptr_ & ~0x1FFFFFFF) + ptr; // CP_RB_CNTL Ring Buffer Control 0x704 // block_size = RB_BLKSZ, number of quadwords read between updates of the // read pointer. read_ptr_update_freq_ = (uint32_t)pow(2.0, (double)block_size) / 4; } void RingBufferWorker::UpdateWritePointer(uint32_t value) { write_ptr_index_ = value; SetEvent(write_ptr_index_event_); } void RingBufferWorker::Pump() { uint8_t* p = xe_memory_addr(memory_); if (write_ptr_index_ == 0xBAADF00D || read_ptr_index_ == write_ptr_index_) { // Check if the pointer has moved. // We wait a short bit here to yield time. Since we are also running the // main window display we don't want to pause too long, though. const int wait_time_ms = 1; if (WaitForSingleObject(write_ptr_index_event_, wait_time_ms) == WAIT_TIMEOUT) { return; } } if (read_ptr_index_ == write_ptr_index_) { return; } // Process the new commands. XELOGGPU("Ring buffer thread work"); // TODO(benvanik): handle wrapping around // read_ptr_index_ = (read_ptr_index_ + 1) % (primary_buffer_size_ / 4); XEASSERT(write_ptr_index_ > read_ptr_index_); uint32_t length = write_ptr_index_ - read_ptr_index_; if (length) { ExecuteSegment(primary_buffer_ptr_ + read_ptr_index_ * 4, length); read_ptr_index_ = write_ptr_index_; } // TODO(benvanik): use read_ptr_update_freq_ and only issue after moving // that many indices. if (read_ptr_writeback_ptr_) { XESETUINT32BE(p + read_ptr_writeback_ptr_, read_ptr_index_); } } void RingBufferWorker::ExecuteSegment(uint32_t ptr, uint32_t length) { uint8_t* p = xe_memory_addr(memory_); RegisterFile* regs = driver_->register_file(); // Adjust pointer base. ptr = (primary_buffer_ptr_ & ~0x1FFFFFFF) | (ptr & 0x1FFFFFFF); // Tell the driver what to use for translation. driver_->set_address_translation(primary_buffer_ptr_ & ~0x1FFFFFFF); #define LOG_DATA(count) \ for (uint32_t __m = 0; __m < count; __m++) { \ XELOGGPU(" %.8X", XEGETUINT32BE(packet_base + 1 * 4 + __m * 4)); \ } #define TRANSLATE_ADDR(p) \ ((p & ~0x3) + (primary_buffer_ptr_ & ~0x1FFFFFFF)) XELOGGPU("CommandList(%.8X): executing %dw", ptr, length); // Execute commands! for (uint32_t n = 0; n < length;) { const uint8_t* packet_base = p + ptr + n * 4; const uint32_t packet = XEGETUINT32BE(packet_base); const uint32_t packet_type = packet >> 30; if (packet == 0) { n++; continue; } switch (packet_type) { case 0x00: { // Type-0 packet. // Write count registers in sequence to the registers starting at // (base_index << 2). XELOGGPU("Packet(%.8X): set registers:", packet); uint32_t count = ((packet >> 16) & 0x3FFF) + 1; uint32_t base_index = (packet & 0xFFFF); for (uint32_t m = 0; m < count; m++) { uint32_t reg_data = XEGETUINT32BE(packet_base + 1 * 4 + m * 4); const char* reg_name = xenos::GetRegisterName(base_index + m); XELOGGPU(" %.8X -> %.4X %s", reg_data, base_index + m, reg_name ? reg_name : ""); // TODO(benvanik): exec write handler (if special). if (base_index + m < kXEGpuRegisterCount) { regs->values[base_index + m].u32 = reg_data; } } n += 1 + count; } break; case 0x01: { // Type-1 packet. // Contains two registers of data. Type-0 should be more common. XELOGGPU("Packet(%.8X): set registers:", packet); uint32_t reg_index_1 = packet & 0x7FF; uint32_t reg_index_2 = (packet >> 11) & 0x7FF; uint32_t reg_data_1 = XEGETUINT32BE(packet_base + 1 * 4); uint32_t reg_data_2 = XEGETUINT32BE(packet_base + 2 * 4); const char* reg_name_1 = xenos::GetRegisterName(reg_index_1); const char* reg_name_2 = xenos::GetRegisterName(reg_index_2); XELOGGPU(" %.8X -> %.4X %s", reg_data_1, reg_index_1, reg_name_1 ? reg_name_1 : ""); XELOGGPU(" %.8X -> %.4X %s", reg_data_2, reg_index_2, reg_name_2 ? reg_name_2 : ""); // TODO(benvanik): exec write handler (if special). if (reg_index_1 < kXEGpuRegisterCount) { regs->values[reg_index_1].u32 = reg_data_1; } if (reg_index_2 < kXEGpuRegisterCount) { regs->values[reg_index_2].u32 = reg_data_2; } n += 1 + 2; } break; case 0x02: // Type-2 packet. // No-op. Do nothing. n++; break; case 0x03: { // Type-3 packet. uint32_t count = ((packet >> 16) & 0x3FFF) + 1; uint32_t opcode = (packet >> 8) & 0x7F; // & 1 == predicate, maybe? switch (opcode) { case PM4_ME_INIT: // initialize CP's micro-engine XELOGGPU("Packet(%.8X): PM4_ME_INIT", packet); LOG_DATA(count); break; case PM4_NOP: // skip N 32-bit words to get to the next packet // No-op, ignore some data. XELOGGPU("Packet(%.8X): PM4_NOP", packet); LOG_DATA(count); break; case PM4_INDIRECT_BUFFER: // indirect buffer dispatch { uint32_t list_ptr = XEGETUINT32BE(packet_base + 1 * 4); uint32_t list_length = XEGETUINT32BE(packet_base + 2 * 4); XELOGGPU("Packet(%.8X): PM4_INDIRECT_BUFFER %.8X (%dw)", packet, list_ptr, list_length); ExecuteSegment(list_ptr, list_length); driver_->set_address_translation(primary_buffer_ptr_ & ~0x1FFFFFFF); } break; case PM4_WAIT_REG_MEM: // wait until a register or memory location is a specific value XELOGGPU("Packet(%.8X): PM4_WAIT_REG_MEM", packet); LOG_DATA(count); break; case PM4_REG_RMW: // register read/modify/write // ? (used during shader upload and edram setup) XELOGGPU("Packet(%.8X): PM4_REG_RMW", packet); LOG_DATA(count); break; case PM4_COND_WRITE: // conditional write to memory or register XELOGGPU("Packet(%.8X): PM4_COND_WRITE", packet); LOG_DATA(count); break; case PM4_EVENT_WRITE: // generate an event that creates a write to memory when completed XELOGGPU("Packet(%.8X): PM4_EVENT_WRITE", packet); LOG_DATA(count); break; case PM4_EVENT_WRITE_SHD: // generate a VS|PS_done event { XELOGGPU("Packet(%.8X): PM4_EVENT_WRITE_SHD", packet); LOG_DATA(count); // 3? uint32_t d0 = XEGETUINT32BE(packet_base + 1 * 4); // ptr uint32_t d1 = XEGETUINT32BE(packet_base + 2 * 4); // value? uint32_t d2 = XEGETUINT32BE(packet_base + 3 * 4); XESETUINT32BE(p + TRANSLATE_ADDR(d1), d2); } break; case PM4_DRAW_INDX: // initiate fetch of index buffer and draw { XELOGGPU("Packet(%.8X): PM4_DRAW_INDX", packet); LOG_DATA(count); // d0 = viz query info uint32_t d0 = XEGETUINT32BE(packet_base + 1 * 4); uint32_t d1 = XEGETUINT32BE(packet_base + 2 * 4); uint32_t index_count = d1 >> 16; uint32_t prim_type = d1 & 0x3F; uint32_t src_sel = (d1 >> 6) & 0x3; XEASSERT(src_sel == 0x2); // 'SrcSel=AutoIndex' driver_->DrawIndexAuto( (XE_GPU_PRIMITIVE_TYPE)prim_type, index_count); } break; case PM4_DRAW_INDX_2: // draw using supplied indices in packet { XELOGGPU("Packet(%.8X): PM4_DRAW_INDX_2", packet); LOG_DATA(count); uint32_t d0 = XEGETUINT32BE(packet_base + 1 * 4); uint32_t index_count = d0 >> 16; uint32_t prim_type = d0 & 0x3F; uint32_t src_sel = (d0 >> 6) & 0x3; XEASSERT(src_sel == 0x2); // 'SrcSel=AutoIndex' driver_->DrawIndexAuto( (XE_GPU_PRIMITIVE_TYPE)prim_type, index_count); } break; case PM4_IM_LOAD: // load sequencer instruction memory (pointer-based) { XELOGGPU("Packet(%.8X): PM4_IM_LOAD", packet); LOG_DATA(count); uint32_t addr_type = XEGETUINT32BE(packet_base + 1 * 4); uint32_t type = addr_type & 0x3; uint32_t addr = addr_type & ~0x3; uint32_t start_size = XEGETUINT32BE(packet_base + 2 * 4); uint32_t start = start_size >> 16; uint32_t size = start_size & 0xFFFF; // dwords XEASSERT(start == 0); driver_->SetShader( (XE_GPU_SHADER_TYPE)type, TRANSLATE_ADDR(addr), start, size * 4); } break; case PM4_IM_LOAD_IMMEDIATE: // load sequencer instruction memory (code embedded in packet) { XELOGGPU("Packet(%.8X): PM4_IM_LOAD_IMMEDIATE", packet); uint32_t type = XEGETUINT32BE(packet_base + 1 * 4); uint32_t start_size = XEGETUINT32BE(packet_base + 2 * 4); uint32_t start = start_size >> 16; uint32_t size = start_size & 0xFFFF; // dwords XEASSERT(start == 0); LOG_DATA(count); driver_->SetShader( (XE_GPU_SHADER_TYPE)type, ptr + n * 4 + 3 * 4, start, size * 4); } break; case PM4_INVALIDATE_STATE: // selective invalidation of state pointers { XELOGGPU("Packet(%.8X): PM4_INVALIDATE_STATE", packet); LOG_DATA(count); uint32_t mask = XEGETUINT32BE(packet_base + 1 * 4); driver_->InvalidateState(mask); } break; default: XELOGGPU("Packet(%.8X): unknown!", packet); LOG_DATA(count); break; } n += 1 + count; } break; } } }