Files
Xenia-Canary/src/xenia/gpu/trace_viewer_main.cc
2015-03-20 20:12:27 -07:00

2170 lines
77 KiB
C++

/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include <gflags/gflags.h>
#include "poly/main.h"
#include "poly/mapped_memory.h"
#include "poly/math.h"
#include "third_party/imgui/imgui.h"
#include "xenia/gpu/gl4/gl_context.h"
#include "xenia/gpu/graphics_system.h"
#include "xenia/gpu/register_file.h"
#include "xenia/gpu/tracing.h"
#include "xenia/gpu/xenos.h"
#include "xenia/emulator.h"
#include "xenia/ui/main_window.h"
// HACK: until we have another impl, we just use gl4 directly.
#include "xenia/gpu/gl4/command_processor.h"
#include "xenia/gpu/gl4/gl4_graphics_system.h"
#include "xenia/gpu/gl4/gl4_shader.h"
DEFINE_string(target_trace_file, "", "Specifies the trace file to load.");
namespace xe {
namespace gpu {
enum class PacketCategory {
kGeneric,
kDraw,
kSwap,
};
struct PacketTypeInfo {
PacketCategory category;
const char* name;
};
struct PacketAction {
enum class Type {
kRegisterWrite,
kSetBinMask,
kSetBinSelect,
};
Type type;
union {
struct {
uint32_t index;
RegisterFile::RegisterValue value;
} register_write;
struct {
uint64_t value;
} set_bin_mask;
struct {
uint64_t value;
} set_bin_select;
};
static PacketAction RegisterWrite(uint32_t index, uint32_t value) {
PacketAction action;
action.type = Type::kRegisterWrite;
action.register_write.index = index;
action.register_write.value.u32 = value;
return action;
}
static PacketAction SetBinMask(uint64_t value) {
PacketAction action;
action.type = Type::kSetBinMask;
action.set_bin_mask.value = value;
return action;
}
static PacketAction SetBinSelect(uint64_t value) {
PacketAction action;
action.type = Type::kSetBinSelect;
action.set_bin_select.value = value;
return action;
}
};
struct PacketInfo {
const PacketTypeInfo* type_info;
bool predicated;
uint32_t count;
std::vector<PacketAction> actions;
};
bool DisasmPacketType0(const uint8_t* base_ptr, uint32_t packet,
PacketInfo* out_info) {
static const PacketTypeInfo type_0_info = {PacketCategory::kGeneric,
"PM4_TYPE0"};
out_info->type_info = &type_0_info;
uint32_t count = ((packet >> 16) & 0x3FFF) + 1;
out_info->count = 1 + count;
auto ptr = base_ptr + 4;
uint32_t base_index = (packet & 0x7FFF);
uint32_t write_one_reg = (packet >> 15) & 0x1;
for (uint32_t m = 0; m < count; m++) {
uint32_t reg_data = poly::load_and_swap<uint32_t>(ptr);
uint32_t target_index = write_one_reg ? base_index : base_index + m;
out_info->actions.emplace_back(
PacketAction::RegisterWrite(target_index, reg_data));
ptr += 4;
}
return true;
}
bool DisasmPacketType1(const uint8_t* base_ptr, uint32_t packet,
PacketInfo* out_info) {
static const PacketTypeInfo type_1_info = {PacketCategory::kGeneric,
"PM4_TYPE1"};
out_info->type_info = &type_1_info;
out_info->count = 1 + 2;
auto ptr = base_ptr + 4;
uint32_t reg_index_1 = packet & 0x7FF;
uint32_t reg_index_2 = (packet >> 11) & 0x7FF;
uint32_t reg_data_1 = poly::load_and_swap<uint32_t>(ptr);
uint32_t reg_data_2 = poly::load_and_swap<uint32_t>(ptr + 4);
out_info->actions.emplace_back(
PacketAction::RegisterWrite(reg_index_1, reg_data_1));
out_info->actions.emplace_back(
PacketAction::RegisterWrite(reg_index_2, reg_data_2));
return true;
}
bool DisasmPacketType2(const uint8_t* base_ptr, uint32_t packet,
PacketInfo* out_info) {
static const PacketTypeInfo type_2_info = {PacketCategory::kGeneric,
"PM4_TYPE2"};
out_info->type_info = &type_2_info;
out_info->count = 1;
return true;
}
using namespace xe::gpu::xenos;
bool DisasmPacketType3(const uint8_t* base_ptr, uint32_t packet,
PacketInfo* out_info) {
static const PacketTypeInfo type_3_unknown_info = {PacketCategory::kGeneric,
"PM4_TYPE3_UNKNOWN"};
out_info->type_info = &type_3_unknown_info;
uint32_t opcode = (packet >> 8) & 0x7F;
uint32_t count = ((packet >> 16) & 0x3FFF) + 1;
out_info->count = 1 + count;
auto ptr = base_ptr + 4;
if (packet & 1) {
out_info->predicated = true;
}
bool result = true;
switch (opcode) {
case PM4_ME_INIT: {
// initialize CP's micro-engine
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
"PM4_ME_INIT"};
out_info->type_info = &op_info;
break;
}
case PM4_NOP: {
// skip N 32-bit words to get to the next packet
// No-op, ignore some data.
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
"PM4_NOP"};
out_info->type_info = &op_info;
break;
}
case PM4_INTERRUPT: {
// generate interrupt from the command stream
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
"PM4_INTERRUPT"};
out_info->type_info = &op_info;
uint32_t cpu_mask = poly::load_and_swap<uint32_t>(ptr + 0);
for (int n = 0; n < 6; n++) {
if (cpu_mask & (1 << n)) {
// graphics_system_->DispatchInterruptCallback(1, n);
}
}
break;
}
case PM4_XE_SWAP: {
// 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.
static const PacketTypeInfo op_info = {PacketCategory::kSwap,
"PM4_XE_SWAP"};
out_info->type_info = &op_info;
uint32_t frontbuffer_ptr = poly::load_and_swap<uint32_t>(ptr + 0);
break;
}
case PM4_INDIRECT_BUFFER: {
// indirect buffer dispatch
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
"PM4_INDIRECT_BUFFER"};
out_info->type_info = &op_info;
uint32_t list_ptr = poly::load_and_swap<uint32_t>(ptr + 0);
uint32_t list_length = poly::load_and_swap<uint32_t>(ptr + 4);
break;
}
case PM4_WAIT_REG_MEM: {
// wait until a register or memory location is a specific value
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
"PM4_WAIT_REG_MEM"};
out_info->type_info = &op_info;
uint32_t wait_info = poly::load_and_swap<uint32_t>(ptr + 0);
uint32_t poll_reg_addr = poly::load_and_swap<uint32_t>(ptr + 4);
uint32_t ref = poly::load_and_swap<uint32_t>(ptr + 8);
uint32_t mask = poly::load_and_swap<uint32_t>(ptr + 12);
uint32_t wait = poly::load_and_swap<uint32_t>(ptr + 16);
break;
}
case PM4_REG_RMW: {
// register read/modify/write
// ? (used during shader upload and edram setup)
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
"PM4_REG_RMW"};
out_info->type_info = &op_info;
uint32_t rmw_info = poly::load_and_swap<uint32_t>(ptr + 0);
uint32_t and_mask = poly::load_and_swap<uint32_t>(ptr + 4);
uint32_t or_mask = poly::load_and_swap<uint32_t>(ptr + 8);
break;
}
case PM4_COND_WRITE: {
// conditional write to memory or register
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
"PM4_COND_WRITE"};
out_info->type_info = &op_info;
uint32_t wait_info = poly::load_and_swap<uint32_t>(ptr + 0);
uint32_t poll_reg_addr = poly::load_and_swap<uint32_t>(ptr + 4);
uint32_t ref = poly::load_and_swap<uint32_t>(ptr + 8);
uint32_t mask = poly::load_and_swap<uint32_t>(ptr + 12);
uint32_t write_reg_addr = poly::load_and_swap<uint32_t>(ptr + 16);
uint32_t write_data = poly::load_and_swap<uint32_t>(ptr + 20);
break;
}
case PM4_EVENT_WRITE: {
// generate an event that creates a write to memory when completed
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
"PM4_EVENT_WRITE"};
out_info->type_info = &op_info;
uint32_t initiator = poly::load_and_swap<uint32_t>(ptr + 0);
break;
}
case PM4_EVENT_WRITE_SHD: {
// generate a VS|PS_done event
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
"PM4_EVENT_WRITE_SHD"};
out_info->type_info = &op_info;
uint32_t initiator = poly::load_and_swap<uint32_t>(ptr + 0);
uint32_t address = poly::load_and_swap<uint32_t>(ptr + 4);
uint32_t value = poly::load_and_swap<uint32_t>(ptr + 8);
break;
}
case PM4_EVENT_WRITE_EXT: {
// generate a screen extent event
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
"PM4_EVENT_WRITE_EXT"};
out_info->type_info = &op_info;
uint32_t unk0 = poly::load_and_swap<uint32_t>(ptr + 0);
uint32_t unk1 = poly::load_and_swap<uint32_t>(ptr + 4);
break;
}
case PM4_DRAW_INDX: {
// initiate fetch of index buffer and draw
// dword0 = viz query info
static const PacketTypeInfo op_info = {PacketCategory::kDraw,
"PM4_DRAW_INDX"};
out_info->type_info = &op_info;
uint32_t dword0 = poly::load_and_swap<uint32_t>(ptr + 0);
uint32_t dword1 = poly::load_and_swap<uint32_t>(ptr + 4);
uint32_t index_count = dword1 >> 16;
auto prim_type = static_cast<PrimitiveType>(dword1 & 0x3F);
uint32_t src_sel = (dword1 >> 6) & 0x3;
if (src_sel == 0x0) {
// Indexed draw.
uint32_t guest_base = poly::load_and_swap<uint32_t>(ptr + 8);
uint32_t index_size = poly::load_and_swap<uint32_t>(ptr + 12);
auto endianness = static_cast<Endian>(index_size >> 30);
index_size &= 0x00FFFFFF;
bool index_32bit = (dword1 >> 11) & 0x1;
index_size *= index_32bit ? 4 : 2;
} else if (src_sel == 0x2) {
// Auto draw.
} else {
// Unknown source select.
assert_always();
}
break;
}
case PM4_DRAW_INDX_2: {
// draw using supplied indices in packet
static const PacketTypeInfo op_info = {PacketCategory::kDraw,
"PM4_DRAW_INDX_2"};
out_info->type_info = &op_info;
uint32_t dword0 = poly::load_and_swap<uint32_t>(ptr + 0);
uint32_t index_count = dword0 >> 16;
auto prim_type = static_cast<PrimitiveType>(dword0 & 0x3F);
uint32_t src_sel = (dword0 >> 6) & 0x3;
assert_true(src_sel == 0x2); // 'SrcSel=AutoIndex'
bool index_32bit = (dword0 >> 11) & 0x1;
uint32_t indices_size = index_count * (index_32bit ? 4 : 2);
auto index_ptr = ptr + 4;
break;
}
case PM4_SET_CONSTANT: {
// load constant into chip and to memory
// PM4_REG(reg) ((0x4 << 16) | (GSL_HAL_SUBBLOCK_OFFSET(reg)))
// reg - 0x2000
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
"PM4_SET_CONSTANT"};
out_info->type_info = &op_info;
uint32_t offset_type = poly::load_and_swap<uint32_t>(ptr + 0);
uint32_t index = offset_type & 0x7FF;
uint32_t type = (offset_type >> 16) & 0xFF;
switch (type) {
case 0: // ALU
index += 0x4000;
break;
case 1: // FETCH
index += 0x4800;
break;
case 2: // BOOL
index += 0x4900;
break;
case 3: // LOOP
index += 0x4908;
break;
case 4: // REGISTERS
index += 0x2000;
break;
default:
assert_always();
result = false;
break;
}
for (uint32_t n = 0; n < count - 1; n++, index++) {
uint32_t data = poly::load_and_swap<uint32_t>(ptr + 4 + n * 4);
out_info->actions.emplace_back(
PacketAction::RegisterWrite(index, data));
}
break;
}
case PM4_SET_CONSTANT2: {
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
"PM4_SET_CONSTANT2"};
out_info->type_info = &op_info;
uint32_t offset_type = poly::load_and_swap<uint32_t>(ptr + 0);
uint32_t index = offset_type & 0xFFFF;
for (uint32_t n = 0; n < count - 1; n++, index++) {
uint32_t data = poly::load_and_swap<uint32_t>(ptr + 4 + n * 4);
out_info->actions.emplace_back(
PacketAction::RegisterWrite(index, data));
}
return true;
break;
}
case PM4_LOAD_ALU_CONSTANT: {
// load constants from memory
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
"PM4_LOAD_ALU_CONSTANT"};
out_info->type_info = &op_info;
uint32_t address = poly::load_and_swap<uint32_t>(ptr + 0);
address &= 0x3FFFFFFF;
uint32_t offset_type = poly::load_and_swap<uint32_t>(ptr + 4);
uint32_t index = offset_type & 0x7FF;
uint32_t size_dwords = poly::load_and_swap<uint32_t>(ptr + 8);
size_dwords &= 0xFFF;
uint32_t type = (offset_type >> 16) & 0xFF;
switch (type) {
case 0: // ALU
index += 0x4000;
break;
case 1: // FETCH
index += 0x4800;
break;
case 2: // BOOL
index += 0x4900;
break;
case 3: // LOOP
index += 0x4908;
break;
case 4: // REGISTERS
index += 0x2000;
break;
default:
assert_always();
return true;
}
for (uint32_t n = 0; n < size_dwords; n++, index++) {
// Hrm, ?
// poly::load_and_swap<uint32_t>(membase_ + GpuToCpu(address + n * 4));
uint32_t data = 0xDEADBEEF;
out_info->actions.emplace_back(
PacketAction::RegisterWrite(index, data));
}
break;
}
case PM4_SET_SHADER_CONSTANTS: {
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
"PM4_SET_SHADER_CONSTANTS"};
out_info->type_info = &op_info;
uint32_t offset_type = poly::load_and_swap<uint32_t>(ptr + 0);
uint32_t index = offset_type & 0xFFFF;
for (uint32_t n = 0; n < count - 1; n++, index++) {
uint32_t data = poly::load_and_swap<uint32_t>(ptr + 4 + n * 4);
out_info->actions.emplace_back(
PacketAction::RegisterWrite(index, data));
}
return true;
}
case PM4_IM_LOAD: {
// load sequencer instruction memory (pointer-based)
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
"PM4_IM_LOAD"};
out_info->type_info = &op_info;
uint32_t addr_type = poly::load_and_swap<uint32_t>(ptr + 0);
auto shader_type = static_cast<ShaderType>(addr_type & 0x3);
uint32_t addr = addr_type & ~0x3;
uint32_t start_size = poly::load_and_swap<uint32_t>(ptr + 4);
uint32_t start = start_size >> 16;
uint32_t size_dwords = start_size & 0xFFFF; // dwords
assert_true(start == 0);
break;
}
case PM4_IM_LOAD_IMMEDIATE: {
// load sequencer instruction memory (code embedded in packet)
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
"PM4_IM_LOAD_IMMEDIATE"};
out_info->type_info = &op_info;
uint32_t dword0 = poly::load_and_swap<uint32_t>(ptr + 0);
uint32_t dword1 = poly::load_and_swap<uint32_t>(ptr + 4);
auto shader_type = static_cast<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);
break;
}
case PM4_INVALIDATE_STATE: {
// selective invalidation of state pointers
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
"PM4_INVALIDATE_STATE"};
out_info->type_info = &op_info;
uint32_t mask = poly::load_and_swap<uint32_t>(ptr + 0);
break;
}
case PM4_SET_BIN_MASK_LO: {
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
"PM4_SET_BIN_MASK_LO"};
out_info->type_info = &op_info;
uint32_t value = poly::load_and_swap<uint32_t>(ptr);
// bin_mask_ = (bin_mask_ & 0xFFFFFFFF00000000ull) | value;
out_info->actions.emplace_back(PacketAction::SetBinMask(value));
break;
}
case PM4_SET_BIN_MASK_HI: {
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
"PM4_SET_BIN_MASK_HI"};
out_info->type_info = &op_info;
uint32_t value = poly::load_and_swap<uint32_t>(ptr);
// bin_mask_ =
// (bin_mask_ & 0xFFFFFFFFull) | (static_cast<uint64_t>(value) << 32);
break;
}
case PM4_SET_BIN_SELECT_LO: {
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
"PM4_SET_BIN_SELECT_LO"};
out_info->type_info = &op_info;
uint32_t value = poly::load_and_swap<uint32_t>(ptr);
// bin_select_ = (bin_select_ & 0xFFFFFFFF00000000ull) | value;
out_info->actions.emplace_back(PacketAction::SetBinSelect(value));
break;
}
case PM4_SET_BIN_SELECT_HI: {
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
"PM4_SET_BIN_SELECT_HI"};
out_info->type_info = &op_info;
uint32_t value = poly::load_and_swap<uint32_t>(ptr);
// bin_select_ =
// (bin_select_ & 0xFFFFFFFFull) | (static_cast<uint64_t>(value) << 32);
break;
}
// Ignored packets - useful if breaking on the default handler below.
case 0x50: { // 0xC0015000 usually 2 words, 0xFFFFFFFF / 0x00000000
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
"PM4_TYPE3_0x50"};
out_info->type_info = &op_info;
break;
}
case 0x51: { // 0xC0015100 usually 2 words, 0xFFFFFFFF / 0xFFFFFFFF
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
"PM4_TYPE3_0x51"};
out_info->type_info = &op_info;
break;
}
default: {
result = false;
break;
}
}
return result;
}
bool DisasmPacket(const uint8_t* base_ptr, PacketInfo* out_info) {
std::memset(out_info, 0, sizeof(PacketInfo));
const uint32_t packet = poly::load_and_swap<uint32_t>(base_ptr);
const uint32_t packet_type = packet >> 30;
switch (packet_type) {
case 0x00:
return DisasmPacketType0(base_ptr, packet, out_info);
case 0x01:
return DisasmPacketType1(base_ptr, packet, out_info);
case 0x02:
return DisasmPacketType2(base_ptr, packet, out_info);
case 0x03:
return DisasmPacketType3(base_ptr, packet, out_info);
default:
assert_unhandled_case(packet_type);
return false;
}
}
PacketCategory GetPacketCategory(const uint8_t* base_ptr) {
const uint32_t packet = poly::load_and_swap<uint32_t>(base_ptr);
const uint32_t packet_type = packet >> 30;
switch (packet_type) {
case 0x00:
case 0x01:
case 0x02: {
return PacketCategory::kGeneric;
}
case 0x03: {
uint32_t opcode = (packet >> 8) & 0x7F;
switch (opcode) {
case PM4_DRAW_INDX:
case PM4_DRAW_INDX_2:
return PacketCategory::kDraw;
case PM4_XE_SWAP:
return PacketCategory::kSwap;
default:
return PacketCategory::kGeneric;
}
}
default: {
assert_unhandled_case(packet_type);
return PacketCategory::kGeneric;
}
}
}
// TODO(benvanik): move to tracing.h/cc
class TraceReader {
public:
struct Frame {
struct Command {
enum class Type {
kDraw,
kSwap,
};
const uint8_t* head_ptr;
const uint8_t* start_ptr;
const uint8_t* end_ptr;
Type type;
union {
struct {
//
} draw;
struct {
//
} swap;
};
};
const uint8_t* start_ptr;
const uint8_t* end_ptr;
int command_count;
std::vector<Command> commands;
};
TraceReader() : trace_data_(nullptr), trace_size_(0) {}
~TraceReader() = default;
const Frame* frame(int n) const { return &frames_[n]; }
int frame_count() const { return int(frames_.size()); }
bool Open(const std::wstring& path) {
Close();
mmap_ = poly::MappedMemory::Open(path, poly::MappedMemory::Mode::kRead);
if (!mmap_) {
return false;
}
trace_data_ = reinterpret_cast<const uint8_t*>(mmap_->data());
trace_size_ = mmap_->size();
ParseTrace();
return true;
}
void Close() {
mmap_.reset();
trace_data_ = nullptr;
trace_size_ = 0;
}
// void Foo() {
// auto trace_ptr = trace_data;
// while (trace_ptr < trace_data + trace_size) {
// auto cmd_type = *reinterpret_cast<const TraceCommandType*>(trace_ptr);
// switch (cmd_type) {
// case TraceCommandType::kPrimaryBufferStart:
// break;
// case TraceCommandType::kPrimaryBufferEnd:
// break;
// case TraceCommandType::kIndirectBufferStart:
// break;
// case TraceCommandType::kIndirectBufferEnd:
// break;
// case TraceCommandType::kPacketStart:
// break;
// case TraceCommandType::kPacketEnd:
// break;
// case TraceCommandType::kMemoryRead:
// break;
// case TraceCommandType::kMemoryWrite:
// break;
// case TraceCommandType::kEvent:
// break;
// }
// /*trace_ptr = graphics_system->PlayTrace(
// trace_ptr, trace_size - (trace_ptr - trace_data),
// GraphicsSystem::TracePlaybackMode::kBreakOnSwap);*/
// }
//}
protected:
void ParseTrace() {
auto trace_ptr = trace_data_;
Frame current_frame = {
trace_ptr, nullptr, 0,
};
const PacketStartCommand* packet_start = nullptr;
const uint8_t* packet_start_ptr = nullptr;
const uint8_t* last_ptr = trace_ptr;
bool pending_break = false;
while (trace_ptr < trace_data_ + trace_size_) {
++current_frame.command_count;
auto type =
static_cast<TraceCommandType>(poly::load<uint32_t>(trace_ptr));
switch (type) {
case TraceCommandType::kPrimaryBufferStart: {
auto cmd =
reinterpret_cast<const PrimaryBufferStartCommand*>(trace_ptr);
trace_ptr += sizeof(*cmd) + cmd->count * 4;
break;
}
case TraceCommandType::kPrimaryBufferEnd: {
auto cmd =
reinterpret_cast<const PrimaryBufferEndCommand*>(trace_ptr);
trace_ptr += sizeof(*cmd);
break;
}
case TraceCommandType::kIndirectBufferStart: {
auto cmd =
reinterpret_cast<const IndirectBufferStartCommand*>(trace_ptr);
trace_ptr += sizeof(*cmd) + cmd->count * 4;
break;
}
case TraceCommandType::kIndirectBufferEnd: {
auto cmd =
reinterpret_cast<const IndirectBufferEndCommand*>(trace_ptr);
trace_ptr += sizeof(*cmd);
break;
}
case TraceCommandType::kPacketStart: {
auto cmd = reinterpret_cast<const PacketStartCommand*>(trace_ptr);
packet_start_ptr = trace_ptr;
packet_start = cmd;
trace_ptr += sizeof(*cmd) + cmd->count * 4;
break;
}
case TraceCommandType::kPacketEnd: {
auto cmd = reinterpret_cast<const PacketEndCommand*>(trace_ptr);
trace_ptr += sizeof(*cmd);
if (!packet_start_ptr) {
continue;
}
auto packet_category =
GetPacketCategory(packet_start_ptr + sizeof(*packet_start));
switch (packet_category) {
case PacketCategory::kDraw: {
Frame::Command command;
command.type = Frame::Command::Type::kDraw;
command.head_ptr = packet_start_ptr;
command.start_ptr = last_ptr;
command.end_ptr = trace_ptr;
current_frame.commands.push_back(std::move(command));
last_ptr = trace_ptr;
break;
}
case PacketCategory::kSwap: {
//
break;
}
}
if (pending_break) {
current_frame.end_ptr = trace_ptr;
frames_.push_back(std::move(current_frame));
current_frame.start_ptr = trace_ptr;
current_frame.end_ptr = nullptr;
current_frame.command_count = 0;
pending_break = false;
}
break;
}
case TraceCommandType::kMemoryRead: {
auto cmd = reinterpret_cast<const MemoryReadCommand*>(trace_ptr);
trace_ptr += sizeof(*cmd) + cmd->length;
break;
}
case TraceCommandType::kMemoryWrite: {
auto cmd = reinterpret_cast<const MemoryWriteCommand*>(trace_ptr);
trace_ptr += sizeof(*cmd) + cmd->length;
break;
}
case TraceCommandType::kEvent: {
auto cmd = reinterpret_cast<const EventCommand*>(trace_ptr);
trace_ptr += sizeof(*cmd);
switch (cmd->event_type) {
case EventType::kSwap: {
pending_break = true;
break;
}
}
break;
}
default:
// Broken trace file?
assert_unhandled_case(type);
break;
}
}
if (pending_break || current_frame.command_count) {
current_frame.end_ptr = trace_ptr;
frames_.push_back(std::move(current_frame));
}
}
std::unique_ptr<poly::MappedMemory> mmap_;
const uint8_t* trace_data_;
size_t trace_size_;
std::vector<Frame> frames_;
};
class TracePlayer : public TraceReader {
public:
TracePlayer(poly::ui::Loop* loop, GraphicsSystem* graphics_system)
: loop_(loop),
graphics_system_(graphics_system),
current_frame_index_(0),
current_command_index_(-1) {}
~TracePlayer() = default;
GraphicsSystem* graphics_system() const { return graphics_system_; }
int current_frame_index() const { return current_frame_index_; }
const Frame* current_frame() const {
if (current_frame_index_ > frame_count()) {
return nullptr;
}
return frame(current_frame_index_);
}
void SeekFrame(int target_frame) {
if (current_frame_index_ == target_frame) {
return;
}
current_frame_index_ = target_frame;
auto frame = current_frame();
current_command_index_ = int(frame->commands.size()) - 1;
assert_true(frame->start_ptr <= frame->end_ptr);
graphics_system_->PlayTrace(
frame->start_ptr, frame->end_ptr - frame->start_ptr,
GraphicsSystem::TracePlaybackMode::kBreakOnSwap);
}
int current_command_index() const { return current_command_index_; }
void SeekCommand(int target_command) {
if (current_command_index_ == target_command) {
return;
}
int previous_command_index = current_command_index_;
current_command_index_ = target_command;
if (current_command_index_ == -1) {
return;
}
auto frame = current_frame();
const auto& command = frame->commands[target_command];
assert_true(frame->start_ptr <= command.end_ptr);
if (target_command && previous_command_index == target_command - 1) {
// Seek forward.
const auto& previous_command = frame->commands[target_command - 1];
graphics_system_->PlayTrace(
previous_command.end_ptr, command.end_ptr - previous_command.end_ptr,
GraphicsSystem::TracePlaybackMode::kBreakOnSwap);
} else {
// Full playback from frame start.
graphics_system_->PlayTrace(
frame->start_ptr, command.end_ptr - frame->start_ptr,
GraphicsSystem::TracePlaybackMode::kBreakOnSwap);
}
}
private:
poly::ui::Loop* loop_;
GraphicsSystem* graphics_system_;
int current_frame_index_;
int current_command_index_;
};
void DrawControllerUI(xe::ui::MainWindow* window, TracePlayer& player,
uint8_t* membase) {
ImGui::SetNextWindowPos(ImVec2(5, 5), ImGuiSetCondition_FirstUseEver);
if (!ImGui::Begin("Controller", nullptr, ImVec2(340, 60))) {
ImGui::End();
return;
}
int target_frame = player.current_frame_index();
if (ImGui::Button("|<<")) {
target_frame = 0;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Reset to first frame");
}
ImGui::SameLine();
if (ImGui::Button(">>", ImVec2(0, 0), true)) {
if (target_frame + 1 < player.frame_count()) {
++target_frame;
}
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Next frame (hold for continuous)");
}
ImGui::SameLine();
if (ImGui::Button(">>|")) {
target_frame = player.frame_count() - 1;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Skip to last frame");
}
ImGui::SameLine();
ImGui::SliderInt("", &target_frame, 0, player.frame_count() - 1);
if (target_frame != player.current_frame_index()) {
player.SeekFrame(target_frame);
}
ImGui::End();
}
void DrawCommandListUI(xe::ui::MainWindow* window, TracePlayer& player,
uint8_t* membase) {
ImGui::SetNextWindowPos(ImVec2(5, 70), ImGuiSetCondition_FirstUseEver);
if (!ImGui::Begin("Command List", nullptr, ImVec2(200, 640))) {
ImGui::End();
return;
}
static const TracePlayer::Frame* previous_frame = nullptr;
auto frame = player.current_frame();
if (!frame) {
ImGui::End();
return;
}
bool did_seek = false;
if (previous_frame != frame) {
did_seek = true;
previous_frame = frame;
}
int command_count = int(frame->commands.size());
int target_command = player.current_command_index();
int column_width = int(ImGui::GetContentRegionMax().x);
ImGui::Text("Frame #%d", player.current_frame_index());
ImGui::Separator();
if (ImGui::Button("reset")) {
target_command = -1;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Reset to before any frame commands");
}
ImGui::SameLine();
if (ImGui::Button("prev", ImVec2(0, 0), true)) {
if (target_command >= 0) {
--target_command;
}
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Move to the previous command (hold)");
}
ImGui::SameLine();
if (ImGui::Button("next", ImVec2(0, 0), true)) {
if (target_command < command_count - 1) {
++target_command;
}
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Move to the next command (hold)");
}
ImGui::SameLine();
if (ImGui::Button("end")) {
target_command = command_count - 1;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Move to the last command");
}
ImGui::PushItemWidth(float(column_width - 15));
ImGui::SliderInt("", &target_command, -1, command_count - 1);
ImGui::PopItemWidth();
if (target_command != player.current_command_index()) {
did_seek = true;
player.SeekCommand(target_command);
}
ImGui::Separator();
ImGui::BeginChild("command_list");
ImGui::PushID(-1);
bool is_selected = player.current_command_index() == -1;
if (ImGui::Selectable("<start>", &is_selected)) {
player.SeekCommand(-1);
}
ImGui::PopID();
if (did_seek && target_command == -1) {
ImGui::SetScrollPosHere();
}
for (int i = 0; i < int(frame->commands.size()); ++i) {
ImGui::PushID(i);
is_selected = i == player.current_command_index();
const auto& command = frame->commands[i];
const char* label;
switch (command.type) {
case TraceReader::Frame::Command::Type::kDraw:
label = "Draw";
break;
case TraceReader::Frame::Command::Type::kSwap:
label = "Swap";
break;
}
if (ImGui::Selectable(label, &is_selected)) {
player.SeekCommand(i);
}
ImGui::SameLine(column_width - 60);
ImGui::Text("%d", i);
ImGui::PopID();
if (did_seek && target_command == i) {
ImGui::SetScrollPosHere();
}
}
ImGui::EndChild();
ImGui::End();
}
static const ImVec4 kColorError =
ImVec4(255 / 255.0f, 0 / 255.0f, 0 / 255.0f, 255 / 255.0f);
static const ImVec4 kColorComment =
ImVec4(42 / 255.0f, 179 / 255.0f, 0 / 255.0f, 255 / 255.0f);
static const ImVec4 kColorIgnored =
ImVec4(100 / 255.0f, 100 / 255.0f, 100 / 255.0f, 255 / 255.0f);
void DrawMultilineString(const std::string& str) {
size_t i = 0;
bool done = false;
while (!done && i < str.size()) {
size_t next_i = str.find('\n', i);
if (next_i == std::string::npos) {
done = true;
next_i = str.size() - 1;
}
auto line = str.substr(i, next_i - i);
ImGui::Text("%s", line.c_str());
i = next_i + 1;
}
}
enum class ShaderDisplayType : int {
kUcode,
kTranslated,
kHostDisasm,
};
ShaderDisplayType DrawShaderTypeUI() {
static ShaderDisplayType shader_display_type = ShaderDisplayType::kUcode;
ImGui::RadioButton("ucode", reinterpret_cast<int*>(&shader_display_type),
static_cast<int>(ShaderDisplayType::kUcode));
ImGui::SameLine();
ImGui::RadioButton("translated", reinterpret_cast<int*>(&shader_display_type),
static_cast<int>(ShaderDisplayType::kTranslated));
ImGui::SameLine();
ImGui::RadioButton("disasm", reinterpret_cast<int*>(&shader_display_type),
static_cast<int>(ShaderDisplayType::kHostDisasm));
return shader_display_type;
}
void DrawShaderUI(xe::ui::MainWindow* window, TracePlayer& player,
uint8_t* membase, gl4::GL4Shader* shader,
ShaderDisplayType display_type) {
// Must be prepared for advanced display modes.
if (display_type != ShaderDisplayType::kUcode) {
if (!shader->has_prepared()) {
ImGui::TextColored(kColorError,
"ERROR: shader not prepared (not used this frame?)");
return;
}
}
switch (display_type) {
case ShaderDisplayType::kUcode: {
DrawMultilineString(shader->ucode_disassembly());
break;
}
case ShaderDisplayType::kTranslated: {
const auto& str = shader->translated_disassembly();
size_t i = 0;
bool done = false;
while (!done && i < str.size()) {
size_t next_i = str.find('\n', i);
if (next_i == std::string::npos) {
done = true;
next_i = str.size() - 1;
}
auto line = str.substr(i, next_i - i);
if (line.find("//") != std::string::npos) {
ImGui::TextColored(kColorComment, "%s", line.c_str());
} else {
ImGui::Text("%s", line.c_str());
}
i = next_i + 1;
}
break;
}
case ShaderDisplayType::kHostDisasm: {
DrawMultilineString(shader->host_disassembly());
break;
}
}
}
// glBlendEquationSeparatei(i, blend_op, blend_op_alpha);
// glBlendFuncSeparatei(i, src_blend, dest_blend, src_blend_alpha,
// dest_blend_alpha);
void DrawBlendMode(uint32_t src_blend, uint32_t dest_blend, uint32_t blend_op) {
static const char* kBlendNames[] = {
/* 0 */ "ZERO",
/* 1 */ "ONE",
/* 2 */ "UNK2", // ?
/* 3 */ "UNK3", // ?
/* 4 */ "SRC_COLOR",
/* 5 */ "ONE_MINUS_SRC_COLOR",
/* 6 */ "SRC_ALPHA",
/* 7 */ "ONE_MINUS_SRC_ALPHA",
/* 8 */ "DST_COLOR",
/* 9 */ "ONE_MINUS_DST_COLOR",
/* 10 */ "DST_ALPHA",
/* 11 */ "ONE_MINUS_DST_ALPHA",
/* 12 */ "CONSTANT_COLOR",
/* 13 */ "ONE_MINUS_CONSTANT_COLOR",
/* 14 */ "CONSTANT_ALPHA",
/* 15 */ "ONE_MINUS_CONSTANT_ALPHA",
/* 16 */ "SRC_ALPHA_SATURATE",
};
const char* src_str = kBlendNames[src_blend];
const char* dest_str = kBlendNames[dest_blend];
const char* op_template;
switch (blend_op) {
case 0: // add
op_template = "%s + %s";
break;
case 1: // subtract
op_template = "%s - %s";
break;
case 2: // min
op_template = "min(%s, %s)";
break;
case 3: // max
op_template = "max(%s, %s)";
break;
case 4: // reverse subtract
op_template = "-(%s) + %s";
break;
default:
op_template = "%s ? %s";
break;
}
ImGui::Text(op_template, src_str, dest_str);
}
void DrawFailedTextureInfo(const Shader::SamplerDesc& desc,
const char* message) {
// TODO(benvanik): better error info/etc.
ImGui::TextColored(kColorError, "ERROR: %s", message);
}
void DrawTextureInfo(TracePlayer& player, const Shader::SamplerDesc& desc) {
auto gs = static_cast<gl4::GL4GraphicsSystem*>(player.graphics_system());
auto cp = gs->command_processor();
auto& regs = *gs->register_file();
int r = XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0 + desc.fetch_slot * 6;
auto group = reinterpret_cast<const xe_gpu_fetch_group_t*>(&regs.values[r]);
auto& fetch = group->texture_fetch;
if (fetch.type != 0x2) {
DrawFailedTextureInfo(desc, "Invalid fetch type");
return;
}
TextureInfo texture_info;
if (!TextureInfo::Prepare(fetch, &texture_info)) {
DrawFailedTextureInfo(desc, "Unable to parse texture fetcher info");
return;
}
SamplerInfo sampler_info;
if (!SamplerInfo::Prepare(fetch, desc.tex_fetch, &sampler_info)) {
DrawFailedTextureInfo(desc, "Unable to parse sampler info");
return;
}
auto entry_view = cp->texture_cache()->Demand(texture_info, sampler_info);
if (!entry_view) {
DrawFailedTextureInfo(desc, "Failed to demand texture");
return;
}
auto texture = entry_view->texture;
ImGui::Columns(2);
ImVec2 button_size(256, 256);
if (ImGui::ImageButton(ImTextureID(GLuint64(texture->handle)), button_size,
ImVec2(0, 0), ImVec2(1, 1))) {
// show viewer
}
ImGui::NextColumn();
ImGui::Text("Fetch Slot: %d", desc.fetch_slot);
switch (texture_info.dimension) {
case Dimension::k1D:
ImGui::Text("1D: %dpx", texture_info.width + 1);
break;
case Dimension::k2D:
ImGui::Text("2D: %dx%dpx", texture_info.width + 1,
texture_info.height + 1);
break;
case Dimension::k3D:
ImGui::Text("3D: %dx%dx%dpx", texture_info.width + 1,
texture_info.height + 1, texture_info.depth + 1);
break;
case Dimension::kCube:
ImGui::Text("Cube: ?");
break;
}
ImGui::Columns(1);
}
static const char* kCompareFuncNames[] = {
"<false>", "<", "==", "<=", ">", "!=", ">=", "<true>",
};
void DrawStateUI(xe::ui::MainWindow* window, TracePlayer& player,
uint8_t* membase) {
auto gs = static_cast<gl4::GL4GraphicsSystem*>(player.graphics_system());
auto cp = gs->command_processor();
auto& regs = *gs->register_file();
ImGui::SetNextWindowPos(ImVec2(float(window->width()) - 500 - 5, 30),
ImGuiSetCondition_FirstUseEver);
if (!ImGui::Begin("State", nullptr, ImVec2(500, 680))) {
ImGui::End();
return;
}
if (!player.current_frame() || player.current_command_index() == -1) {
ImGui::Text("No frame/command selected");
ImGui::End();
return;
}
auto frame = player.current_frame();
const auto& command = frame->commands[player.current_command_index()];
auto packet_head = command.head_ptr + sizeof(PacketStartCommand);
uint32_t packet = poly::load_and_swap<uint32_t>(packet_head);
uint32_t packet_type = packet >> 30;
assert_true(packet_type == 0x03);
uint32_t opcode = (packet >> 8) & 0x7F;
struct {
PrimitiveType prim_type;
bool is_auto_index;
uint32_t index_count;
uint32_t index_buffer_ptr;
uint32_t index_buffer_size;
Endian index_endianness;
IndexFormat index_format;
} draw_info;
std::memset(&draw_info, 0, sizeof(draw_info));
switch (opcode) {
case PM4_DRAW_INDX: {
uint32_t dword0 = poly::load_and_swap<uint32_t>(packet_head + 4);
uint32_t dword1 = poly::load_and_swap<uint32_t>(packet_head + 8);
draw_info.index_count = dword1 >> 16;
draw_info.prim_type = static_cast<PrimitiveType>(dword1 & 0x3F);
uint32_t src_sel = (dword1 >> 6) & 0x3;
if (src_sel == 0x0) {
// Indexed draw.
draw_info.is_auto_index = false;
draw_info.index_buffer_ptr =
poly::load_and_swap<uint32_t>(packet_head + 12);
uint32_t index_size = poly::load_and_swap<uint32_t>(packet_head + 16);
draw_info.index_endianness = static_cast<Endian>(index_size >> 30);
index_size &= 0x00FFFFFF;
bool index_32bit = (dword1 >> 11) & 0x1;
draw_info.index_format =
index_32bit ? IndexFormat::kInt32 : IndexFormat::kInt16;
draw_info.index_buffer_size = index_size * (index_32bit ? 4 : 2);
} else if (src_sel == 0x2) {
// Auto draw.
draw_info.is_auto_index = true;
} else {
// Unknown source select.
assert_always();
}
break;
}
case PM4_DRAW_INDX_2: {
uint32_t dword0 = poly::load_and_swap<uint32_t>(packet_head + 4);
uint32_t src_sel = (dword0 >> 6) & 0x3;
assert_true(src_sel == 0x2); // 'SrcSel=AutoIndex'
draw_info.prim_type = static_cast<PrimitiveType>(dword0 & 0x3F);
draw_info.is_auto_index = true;
draw_info.index_count = dword0 >> 16;
break;
}
}
auto enable_mode =
static_cast<ModeControl>(regs[XE_GPU_REG_RB_MODECONTROL].u32 & 0x7);
const char* mode_name = "Unknown";
switch (enable_mode) {
case ModeControl::kIgnore:
mode_name = "Ignored";
break;
case ModeControl::kColorDepth:
mode_name = "Color + Depth";
break;
case ModeControl::kDepth:
mode_name = "Depth-only";
break;
case ModeControl::kCopy:
mode_name = "Copy";
break;
}
ImGui::Text("%s Command %d", mode_name, player.current_command_index());
static const char* kPrimNames[] = {
"<none>", "point list", "line list", "line strip",
"triangle list", "triangle fan", "triangle strip", "unknown 0x7",
"rectangle list", "unknown 0x9", "unknown 0xA", "unknown 0xB",
"line loop", "quad list", "quad strip", "unknown 0xF",
};
ImGui::Text("Primitive Type: %s", kPrimNames[int(draw_info.prim_type)]);
ImGui::Text("Indices: %d", draw_info.index_count);
ImGui::SameLine();
if (draw_info.is_auto_index) {
ImGui::Text("auto-indexed");
} else {
static const char* kIndexFormatNames[] = {
"uint16", "uint32",
};
static const char* kEndiannessNames[] = {
"unspecified endianness", "8-in-16", "8-in-32", "16-in-32",
};
ImGui::Text("from buffer %.8X (%db), %s, %s", draw_info.index_buffer_ptr,
draw_info.index_buffer_size,
kIndexFormatNames[int(draw_info.index_format)],
kEndiannessNames[int(draw_info.index_endianness)]);
}
if (ImGui::TreeNode("Viewport State")) {
uint32_t pa_su_sc_mode_cntl = regs[XE_GPU_REG_PA_SU_SC_MODE_CNTL].u32;
if ((pa_su_sc_mode_cntl >> 16) & 1) {
uint32_t window_offset = regs[XE_GPU_REG_PA_SC_WINDOW_OFFSET].u32;
int16_t window_offset_x = window_offset & 0x7FFF;
int16_t window_offset_y = (window_offset >> 16) & 0x7FFF;
if (window_offset_x & 0x4000) {
window_offset_x |= 0x8000;
}
if (window_offset_y & 0x4000) {
window_offset_y |= 0x8000;
}
ImGui::BulletText("Window Offset: %d, %d", window_offset_x,
window_offset_y);
} else {
ImGui::BulletText("Window Offset: disabled");
}
uint32_t window_scissor_tl = regs[XE_GPU_REG_PA_SC_WINDOW_SCISSOR_TL].u32;
uint32_t window_scissor_br = regs[XE_GPU_REG_PA_SC_WINDOW_SCISSOR_BR].u32;
ImGui::BulletText(
"Window Scissor: %d,%d to %d,%d (%d x %d)", window_scissor_tl & 0x7FFF,
(window_scissor_tl >> 16) & 0x7FFF, window_scissor_br & 0x7FFF,
(window_scissor_br >> 16) & 0x7FFF,
(window_scissor_br & 0x7FFF) - (window_scissor_tl & 0x7FFF),
((window_scissor_br >> 16) & 0x7FFF) -
((window_scissor_tl >> 16) & 0x7FFF));
uint32_t surface_info = regs[XE_GPU_REG_RB_SURFACE_INFO].u32;
uint32_t surface_pitch = surface_info & 0x3FFF;
auto surface_msaa = (surface_info >> 16) & 0x3;
static const char* kMsaaNames[] = {
"1X", "2X", "4X",
};
ImGui::BulletText("Surface MSAA: %s", kMsaaNames[surface_msaa]);
uint32_t vte_control = regs[XE_GPU_REG_PA_CL_VTE_CNTL].u32;
bool vport_xscale_enable = (vte_control & (1 << 0)) > 0;
bool vport_xoffset_enable = (vte_control & (1 << 1)) > 0;
bool vport_yscale_enable = (vte_control & (1 << 2)) > 0;
bool vport_yoffset_enable = (vte_control & (1 << 3)) > 0;
bool vport_zscale_enable = (vte_control & (1 << 4)) > 0;
bool vport_zoffset_enable = (vte_control & (1 << 5)) > 0;
assert_true(vport_xscale_enable == vport_yscale_enable ==
vport_zscale_enable == vport_xoffset_enable ==
vport_yoffset_enable == vport_zoffset_enable);
ImGui::BulletText(
"Viewport Offset: %f, %f, %f",
vport_xoffset_enable ? regs[XE_GPU_REG_PA_CL_VPORT_XOFFSET].f32 : 0,
vport_yoffset_enable ? regs[XE_GPU_REG_PA_CL_VPORT_YOFFSET].f32 : 0,
vport_zoffset_enable ? regs[XE_GPU_REG_PA_CL_VPORT_ZOFFSET].f32 : 0);
ImGui::BulletText(
"Viewport Scale: %f, %f, %f",
vport_xscale_enable ? regs[XE_GPU_REG_PA_CL_VPORT_XSCALE].f32 : 1,
vport_yscale_enable ? regs[XE_GPU_REG_PA_CL_VPORT_YSCALE].f32 : 1,
vport_zscale_enable ? regs[XE_GPU_REG_PA_CL_VPORT_ZSCALE].f32 : 1);
ImGui::BulletText("XY Vertex Format: %s", ((vte_control >> 8) & 0x1)
? "premultipied by 1/w0"
: "unmultiplied");
ImGui::BulletText("Z Vertex Format: %s", ((vte_control >> 9) & 0x1)
? "premultipied by 1/w0"
: "unmultiplied");
ImGui::BulletText("W0 Vertex Format: %s", ((vte_control >> 10) & 0x1)
? "w0 is not 1/w0"
: "already divided");
uint32_t clip_control = regs[XE_GPU_REG_PA_CL_CLIP_CNTL].u32;
bool clip_enabled = ((clip_control >> 17) & 0x1) == 0;
ImGui::BulletText("Clip Enabled: %s", clip_enabled ? "true" : "false");
bool dx_clip = ((clip_control >> 20) & 0x1) == 0x1;
ImGui::BulletText("DX Clip: %s", dx_clip ? "true" : "false");
ImGui::TreePop();
}
if (ImGui::TreeNode("Rasterizer State")) {
uint32_t pa_su_sc_mode_cntl = regs[XE_GPU_REG_PA_SU_SC_MODE_CNTL].u32;
uint32_t pa_sc_screen_scissor_tl =
regs[XE_GPU_REG_PA_SC_SCREEN_SCISSOR_TL].u32;
uint32_t pa_sc_screen_scissor_br =
regs[XE_GPU_REG_PA_SC_SCREEN_SCISSOR_BR].u32;
if (pa_sc_screen_scissor_tl != 0 && pa_sc_screen_scissor_br != 0x20002000) {
int32_t screen_scissor_x = pa_sc_screen_scissor_tl & 0x7FFF;
int32_t screen_scissor_y = (pa_sc_screen_scissor_tl >> 16) & 0x7FFF;
int32_t screen_scissor_w =
(pa_sc_screen_scissor_br & 0x7FFF) - screen_scissor_x;
int32_t screen_scissor_h =
((pa_sc_screen_scissor_br >> 16) & 0x7FFF) - screen_scissor_y;
ImGui::BulletText("Scissor: %d,%d to %d,%d (%d x %d)", screen_scissor_x,
screen_scissor_y, screen_scissor_x + screen_scissor_w,
screen_scissor_y + screen_scissor_h, screen_scissor_w,
screen_scissor_h);
} else {
ImGui::BulletText("Scissor: disabled");
}
switch (pa_su_sc_mode_cntl & 0x3) {
case 0:
ImGui::BulletText("Culling: disabled");
break;
case 1:
ImGui::BulletText("Culling: front-face");
break;
case 2:
ImGui::BulletText("Culling: back-face");
break;
}
if (pa_su_sc_mode_cntl & 0x4) {
ImGui::BulletText("Front-face: clockwise");
} else {
ImGui::BulletText("Front-face: counter-clockwise");
}
static const char* kFillModeNames[3] = {
"point", "line", "fill",
};
bool poly_mode = ((pa_su_sc_mode_cntl >> 3) & 0x3) != 0;
if (poly_mode) {
uint32_t front_poly_mode = (pa_su_sc_mode_cntl >> 5) & 0x7;
uint32_t back_poly_mode = (pa_su_sc_mode_cntl >> 8) & 0x7;
// GL only supports both matching.
assert_true(front_poly_mode == back_poly_mode);
ImGui::BulletText("Polygon Mode: %s", kFillModeNames[front_poly_mode]);
} else {
ImGui::BulletText("Polygon Mode: fill");
}
if (pa_su_sc_mode_cntl & (1 << 20)) {
ImGui::BulletText("Provoking Vertex: last");
} else {
ImGui::BulletText("Provoking Vertex: first");
}
ImGui::TreePop();
}
if (ImGui::TreeNode("Blend State")) {
uint32_t rb_blendcontrol[4] = {
regs[XE_GPU_REG_RB_BLENDCONTROL_0].u32,
regs[XE_GPU_REG_RB_BLENDCONTROL_1].u32,
regs[XE_GPU_REG_RB_BLENDCONTROL_2].u32,
regs[XE_GPU_REG_RB_BLENDCONTROL_3].u32,
};
for (int i = 0; i < poly::countof(rb_blendcontrol); ++i) {
uint32_t blend_control = rb_blendcontrol[i];
// A2XX_RB_BLEND_CONTROL_COLOR_SRCBLEND
auto src_blend = (blend_control & 0x0000001F) >> 0;
// A2XX_RB_BLEND_CONTROL_COLOR_DESTBLEND
auto dest_blend = (blend_control & 0x00001F00) >> 8;
// A2XX_RB_BLEND_CONTROL_COLOR_COMB_FCN
auto blend_op = (blend_control & 0x000000E0) >> 5;
// A2XX_RB_BLEND_CONTROL_ALPHA_SRCBLEND
auto src_blend_alpha = (blend_control & 0x001F0000) >> 16;
// A2XX_RB_BLEND_CONTROL_ALPHA_DESTBLEND
auto dest_blend_alpha = (blend_control & 0x1F000000) >> 24;
// A2XX_RB_BLEND_CONTROL_ALPHA_COMB_FCN
auto blend_op_alpha = (blend_control & 0x00E00000) >> 21;
// A2XX_RB_COLORCONTROL_BLEND_DISABLE ?? Can't find this!
// Just guess based on actions.
bool blend_enable = !((src_blend == 1) && (dest_blend == 0) &&
(blend_op == 0) && (src_blend_alpha == 1) &&
(dest_blend_alpha == 0) && (blend_op_alpha == 0));
if (blend_enable) {
if (src_blend == src_blend_alpha && dest_blend == dest_blend_alpha &&
blend_op == blend_op_alpha) {
ImGui::BulletText("Blend %d: ", i);
ImGui::SameLine();
DrawBlendMode(src_blend, dest_blend, blend_op);
} else {
ImGui::BulletText("Blend %d:", i);
ImGui::BulletText(" Color: ");
ImGui::SameLine();
DrawBlendMode(src_blend, dest_blend, blend_op);
ImGui::BulletText(" Alpha: ");
ImGui::SameLine();
DrawBlendMode(src_blend_alpha, dest_blend_alpha, blend_op_alpha);
}
} else {
ImGui::PushStyleColor(ImGuiCol_Text, kColorIgnored);
ImGui::BulletText("Blend %d: disabled", i);
ImGui::PopStyleColor();
}
}
auto blend_color = ImVec4(regs[XE_GPU_REG_RB_BLEND_RED].f32,
regs[XE_GPU_REG_RB_BLEND_GREEN].f32,
regs[XE_GPU_REG_RB_BLEND_BLUE].f32,
regs[XE_GPU_REG_RB_BLEND_ALPHA].f32);
ImGui::BulletText("Blend Color: (%.2f,%.2f,%.2f,%.2f)", blend_color.x,
blend_color.y, blend_color.z, blend_color.w);
ImGui::SameLine();
ImGui::ColorButton(blend_color, true);
// Alpha testing -- ALPHAREF, ALPHAFUNC, ALPHATESTENABLE
// if(ALPHATESTENABLE && frag_out.a [<=/ALPHAFUNC] ALPHAREF) discard;
uint32_t color_control = regs[XE_GPU_REG_RB_COLORCONTROL].u32;
if ((color_control & 0x4) != 0) {
ImGui::BulletText("Alpha Test: discard if %s %.2f",
kCompareFuncNames[color_control & 0x3],
regs[XE_GPU_REG_RB_ALPHA_REF].f32);
} else {
ImGui::PushStyleColor(ImGuiCol_Text, kColorIgnored);
ImGui::BulletText("Alpha Test: disabled");
ImGui::PopStyleColor();
}
ImGui::TreePop();
}
if (ImGui::TreeNode("Depth-Stencil State")) {
auto rb_depthcontrol = regs[XE_GPU_REG_RB_DEPTHCONTROL].u32;
auto rb_stencilrefmask = regs[XE_GPU_REG_RB_STENCILREFMASK].u32;
if (rb_depthcontrol & 0x00000002) {
ImGui::BulletText("Depth Test: enabled");
} else {
ImGui::PushStyleColor(ImGuiCol_Text, kColorIgnored);
ImGui::BulletText("Depth Test: disabled");
}
ImGui::BulletText("Depth Func: %s",
kCompareFuncNames[(rb_depthcontrol & 0x00000070) >> 4]);
if (!(rb_depthcontrol & 0x00000002)) {
ImGui::PopStyleColor();
}
if (rb_depthcontrol & 0x00000004) {
ImGui::BulletText("Depth Write: enabled");
} else {
ImGui::PushStyleColor(ImGuiCol_Text, kColorIgnored);
ImGui::BulletText("Depth Write: disabled");
ImGui::PopStyleColor();
}
if (rb_depthcontrol & 0x00000001) {
ImGui::BulletText("Stencil Test: enabled");
} else {
ImGui::PushStyleColor(ImGuiCol_Text, kColorIgnored);
ImGui::BulletText("Stencil Test: disabled");
}
// TODO(benvanik): stencil stuff.
ImGui::BulletText("TODO: stencil stuff");
if (!(rb_depthcontrol & 0x00000001)) {
ImGui::PopStyleColor();
}
ImGui::TreePop();
}
if (ImGui::CollapsingHeader("Vertex Shader")) {
ShaderDisplayType shader_display_type = DrawShaderTypeUI();
ImGui::BeginChild("#vertex_shader_text", ImVec2(0, 400));
auto shader = cp->active_vertex_shader();
if (shader) {
DrawShaderUI(window, player, membase, shader, shader_display_type);
} else {
ImGui::TextColored(kColorError, "ERROR: no vertex shader set");
}
ImGui::EndChild();
}
if (ImGui::CollapsingHeader("Pixel Shader")) {
ShaderDisplayType shader_display_type = DrawShaderTypeUI();
ImGui::BeginChild("#pixel_shader_text", ImVec2(0, 400));
auto shader = cp->active_pixel_shader();
if (shader) {
DrawShaderUI(window, player, membase, shader, shader_display_type);
} else {
ImGui::TextColored(kColorError, "ERROR: no pixel shader set");
}
ImGui::EndChild();
}
if (ImGui::CollapsingHeader("Buffers")) {
ImGui::Text("vertex/index buffers");
}
if (ImGui::CollapsingHeader("Vertex Textures")) {
auto shader = cp->active_vertex_shader();
if (shader) {
const auto& sampler_inputs = shader->sampler_inputs();
if (sampler_inputs.count) {
for (size_t i = 0; i < sampler_inputs.count; ++i) {
DrawTextureInfo(player, sampler_inputs.descs[i]);
}
} else {
ImGui::Text("No vertex shader samplers");
}
} else {
ImGui::TextColored(kColorError, "ERROR: no vertex shader set");
}
}
if (ImGui::CollapsingHeader("Textures")) {
auto shader = cp->active_pixel_shader();
if (shader) {
const auto& sampler_inputs = shader->sampler_inputs();
if (sampler_inputs.count) {
for (size_t i = 0; i < sampler_inputs.count; ++i) {
DrawTextureInfo(player, sampler_inputs.descs[i]);
}
} else {
ImGui::Text("No pixel shader samplers");
}
} else {
ImGui::TextColored(kColorError, "ERROR: no pixel shader set");
}
}
if (ImGui::CollapsingHeader("Fetch Constants (raw)")) {
ImGui::Columns(2);
ImGui::SetColumnOffset(1, 85.0f);
for (int i = XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0;
i <= XE_GPU_REG_SHADER_CONSTANT_FETCH_31_5; ++i) {
ImGui::Text("f%02d_%d", (i - XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0) / 6,
(i - XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0) % 6);
ImGui::NextColumn();
ImGui::Text("%.8X", regs[i].u32);
ImGui::NextColumn();
}
ImGui::Columns(1);
}
if (ImGui::CollapsingHeader("ALU Constants")) {
ImGui::Columns(2);
for (int i = XE_GPU_REG_SHADER_CONSTANT_000_X;
i <= XE_GPU_REG_SHADER_CONSTANT_511_X; i += 4) {
ImGui::Text("c%d", (i - XE_GPU_REG_SHADER_CONSTANT_000_X) / 4);
ImGui::NextColumn();
ImGui::Text("%f, %f, %f, %f", regs[i + 0].f32, regs[i + 1].f32,
regs[i + 2].f32, regs[i + 3].f32);
ImGui::NextColumn();
}
ImGui::Columns(1);
}
if (ImGui::CollapsingHeader("Bool Constants")) {
ImGui::Columns(2);
for (int i = XE_GPU_REG_SHADER_CONSTANT_BOOL_000_031;
i <= XE_GPU_REG_SHADER_CONSTANT_BOOL_224_255; ++i) {
ImGui::Text("b%03d-%03d",
(i - XE_GPU_REG_SHADER_CONSTANT_BOOL_000_031) * 32,
(i - XE_GPU_REG_SHADER_CONSTANT_BOOL_000_031) * 32 + 31);
ImGui::NextColumn();
ImGui::Text("%.8X", regs[i].u32);
ImGui::NextColumn();
}
ImGui::Columns(1);
}
if (ImGui::CollapsingHeader("Loop Constants")) {
ImGui::Columns(2);
for (int i = XE_GPU_REG_SHADER_CONSTANT_LOOP_00;
i <= XE_GPU_REG_SHADER_CONSTANT_LOOP_31; ++i) {
ImGui::Text("l%d", i - XE_GPU_REG_SHADER_CONSTANT_LOOP_00);
ImGui::NextColumn();
ImGui::Text("%.8X", regs[i].u32);
ImGui::NextColumn();
}
ImGui::Columns(1);
}
ImGui::End();
}
void DrawPacketDisassemblerUI(xe::ui::MainWindow* window, TracePlayer& player,
uint8_t* membase) {
ImGui::SetNextWindowCollapsed(true, ImGuiSetCondition_FirstUseEver);
ImGui::SetNextWindowPos(ImVec2(float(window->width()) - 500 - 5, 5),
ImGuiSetCondition_FirstUseEver);
if (!ImGui::Begin("Packet Disassembler", nullptr, ImVec2(500, 300))) {
ImGui::End();
return;
}
if (!player.current_frame() || player.current_command_index() == -1) {
ImGui::Text("No frame/command selected");
ImGui::End();
return;
}
auto frame = player.current_frame();
const auto& command = frame->commands[player.current_command_index()];
const uint8_t* start_ptr = command.start_ptr;
const uint8_t* end_ptr = command.end_ptr;
ImGui::Text("Frame #%d, command %d", player.current_frame_index(),
player.current_command_index());
ImGui::Separator();
ImGui::BeginChild("packet_disassembler_list");
const PacketStartCommand* pending_packet = nullptr;
auto trace_ptr = start_ptr;
while (trace_ptr < end_ptr) {
auto type = static_cast<TraceCommandType>(poly::load<uint32_t>(trace_ptr));
switch (type) {
case TraceCommandType::kPrimaryBufferStart: {
auto cmd =
reinterpret_cast<const PrimaryBufferStartCommand*>(trace_ptr);
trace_ptr += sizeof(*cmd) + cmd->count * 4;
ImGui::BulletText("PrimaryBufferStart");
break;
}
case TraceCommandType::kPrimaryBufferEnd: {
auto cmd = reinterpret_cast<const PrimaryBufferEndCommand*>(trace_ptr);
trace_ptr += sizeof(*cmd);
ImGui::BulletText("PrimaryBufferEnd");
break;
}
case TraceCommandType::kIndirectBufferStart: {
auto cmd =
reinterpret_cast<const IndirectBufferStartCommand*>(trace_ptr);
trace_ptr += sizeof(*cmd) + cmd->count * 4;
ImGui::BulletText("IndirectBufferStart");
break;
}
case TraceCommandType::kIndirectBufferEnd: {
auto cmd = reinterpret_cast<const IndirectBufferEndCommand*>(trace_ptr);
trace_ptr += sizeof(*cmd);
ImGui::BulletText("IndirectBufferEnd");
break;
}
case TraceCommandType::kPacketStart: {
auto cmd = reinterpret_cast<const PacketStartCommand*>(trace_ptr);
trace_ptr += sizeof(*cmd) + cmd->count * 4;
pending_packet = cmd;
break;
}
case TraceCommandType::kPacketEnd: {
auto cmd = reinterpret_cast<const PacketEndCommand*>(trace_ptr);
trace_ptr += sizeof(*cmd);
if (pending_packet) {
PacketInfo packet_info;
if (DisasmPacket(reinterpret_cast<const uint8_t*>(pending_packet) +
sizeof(PacketStartCommand),
&packet_info)) {
if (packet_info.predicated) {
ImGui::PushStyleColor(ImGuiCol_Text, kColorIgnored);
}
ImGui::BulletText(packet_info.type_info->name);
ImGui::TreePush((const char*)0);
for (auto& action : packet_info.actions) {
switch (action.type) {
case PacketAction::Type::kRegisterWrite: {
auto register_info = xe::gpu::RegisterFile::GetRegisterInfo(
action.register_write.index);
ImGui::Columns(2);
ImGui::Text("%.4X %s", action.register_write.index,
register_info ? register_info->name : "???");
ImGui::NextColumn();
if (!register_info ||
register_info->type == RegisterInfo::Type::kDword) {
ImGui::Text("%.8X", action.register_write.value.u32);
} else {
ImGui::Text("%8f", action.register_write.value.f32);
}
ImGui::Columns(1);
break;
}
case PacketAction::Type::kSetBinMask: {
ImGui::Text("%.16llX", action.set_bin_mask.value);
break;
}
case PacketAction::Type::kSetBinSelect: {
ImGui::Text("%.16llX", action.set_bin_select.value);
break;
}
}
}
ImGui::TreePop();
if (packet_info.predicated) {
ImGui::PopStyleColor();
}
} else {
ImGui::BulletText("<invalid packet>");
}
pending_packet = nullptr;
}
break;
}
case TraceCommandType::kMemoryRead: {
auto cmd = reinterpret_cast<const MemoryReadCommand*>(trace_ptr);
trace_ptr += sizeof(*cmd) + cmd->length;
// ImGui::BulletText("MemoryRead");
break;
}
case TraceCommandType::kMemoryWrite: {
auto cmd = reinterpret_cast<const MemoryWriteCommand*>(trace_ptr);
trace_ptr += sizeof(*cmd) + cmd->length;
// ImGui::BulletText("MemoryWrite");
break;
}
case TraceCommandType::kEvent: {
auto cmd = reinterpret_cast<const EventCommand*>(trace_ptr);
trace_ptr += sizeof(*cmd);
switch (cmd->event_type) {
case EventType::kSwap: {
ImGui::BulletText("<swap>");
break;
}
}
break;
}
}
}
ImGui::EndChild();
ImGui::End();
}
void DrawUI(xe::ui::MainWindow* window, TracePlayer& player, uint8_t* membase) {
ImGui::ShowTestWindow();
DrawControllerUI(window, player, membase);
DrawCommandListUI(window, player, membase);
DrawStateUI(window, player, membase);
DrawPacketDisassemblerUI(window, player, membase);
}
void ImImpl_Setup();
void ImImpl_Shutdown();
int trace_viewer_main(std::vector<std::wstring>& args) {
// Create the emulator.
auto emulator = std::make_unique<Emulator>(L"");
X_STATUS result = emulator->Setup();
if (XFAILED(result)) {
XELOGE("Failed to setup emulator: %.8X", result);
return 1;
}
// Grab path from the flag or unnamed argument.
if (!FLAGS_target_trace_file.empty() || args.size() >= 2) {
std::wstring path;
if (!FLAGS_target_trace_file.empty()) {
// Passed as a named argument.
// TODO(benvanik): find something better than gflags that supports
// unicode.
path = poly::to_wstring(FLAGS_target_trace_file);
} else {
// Passed as an unnamed argument.
path = args[1];
}
// Normalize the path and make absolute.
auto abs_path = poly::to_absolute_path(path);
auto window = emulator->main_window();
auto loop = window->loop();
auto file_name = poly::find_name_from_path(path);
window->set_title(std::wstring(L"Xenia GPU Trace Viewer: ") + file_name);
auto graphics_system = emulator->graphics_system();
Profiler::set_display(nullptr);
TracePlayer player(loop, emulator->graphics_system());
if (!player.Open(abs_path)) {
XELOGE("Could not load trace file");
return 1;
}
auto control = window->child(0);
control->on_key_char.AddListener([](poly::ui::KeyEvent& e) {
auto& io = ImGui::GetIO();
if (e.key_code() > 0 && e.key_code() < 0x10000) {
io.AddInputCharacter(e.key_code());
}
e.set_handled(true);
});
control->on_mouse_down.AddListener([](poly::ui::MouseEvent& e) {
auto& io = ImGui::GetIO();
io.MousePos = ImVec2(float(e.x()), float(e.y()));
switch (e.button()) {
case poly::ui::MouseEvent::Button::kLeft:
io.MouseDown[0] = true;
break;
case poly::ui::MouseEvent::Button::kRight:
io.MouseDown[1] = true;
break;
}
});
control->on_mouse_move.AddListener([](poly::ui::MouseEvent& e) {
auto& io = ImGui::GetIO();
io.MousePos = ImVec2(float(e.x()), float(e.y()));
});
control->on_mouse_up.AddListener([](poly::ui::MouseEvent& e) {
auto& io = ImGui::GetIO();
io.MousePos = ImVec2(float(e.x()), float(e.y()));
switch (e.button()) {
case poly::ui::MouseEvent::Button::kLeft:
io.MouseDown[0] = false;
break;
case poly::ui::MouseEvent::Button::kRight:
io.MouseDown[1] = false;
break;
}
});
control->on_mouse_wheel.AddListener([](poly::ui::MouseEvent& e) {
auto& io = ImGui::GetIO();
io.MousePos = ImVec2(float(e.x()), float(e.y()));
io.MouseWheel += float(e.dy() / 120.0f);
});
control->on_paint.AddListener([&](poly::ui::UIEvent& e) {
static bool imgui_setup = false;
if (!imgui_setup) {
ImImpl_Setup();
imgui_setup = true;
}
auto& io = ImGui::GetIO();
auto current_ticks = poly::threading::ticks();
static uint64_t last_ticks = 0;
io.DeltaTime = (current_ticks - last_ticks) /
float(poly::threading::ticks_per_second());
last_ticks = current_ticks;
io.DisplaySize =
ImVec2(float(e.control()->width()), float(e.control()->height()));
BYTE keystate[256];
GetKeyboardState(keystate);
for (int i = 0; i < 256; i++) io.KeysDown[i] = (keystate[i] & 0x80) != 0;
io.KeyCtrl = (keystate[VK_CONTROL] & 0x80) != 0;
io.KeyShift = (keystate[VK_SHIFT] & 0x80) != 0;
ImGui::NewFrame();
DrawUI(window, player, emulator->memory()->membase());
glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y);
ImGui::Render();
graphics_system->RequestSwap();
});
graphics_system->RequestSwap();
// Wait until we are exited.
emulator->main_window()->loop()->AwaitQuit();
ImImpl_Shutdown();
}
emulator.reset();
return 0;
}
// TODO(benvanik): move to another file.
extern "C" GLEWContext* glewGetContext();
extern "C" WGLEWContext* wglewGetContext();
static int shader_handle, vert_handle, frag_handle;
static int texture_location, proj_mtx_location;
static int position_location, uv_location, colour_location;
static size_t vbo_max_size = 20000;
static unsigned int vbo_handle, vao_handle;
void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count);
void ImImpl_Setup() {
ImGuiIO& io = ImGui::GetIO();
const GLchar* vertex_shader =
"#version 330\n"
"uniform mat4 ProjMtx;\n"
"in vec2 Position;\n"
"in vec2 UV;\n"
"in vec4 Color;\n"
"out vec2 Frag_UV;\n"
"out vec4 Frag_Color;\n"
"void main()\n"
"{\n"
" Frag_UV = UV;\n"
" Frag_Color = Color;\n"
" gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
"}\n";
const GLchar* fragment_shader =
"#version 330\n"
"uniform sampler2D Texture;\n"
"in vec2 Frag_UV;\n"
"in vec4 Frag_Color;\n"
"out vec4 Out_Color;\n"
"void main()\n"
"{\n"
" Out_Color = Frag_Color * texture( Texture, Frag_UV.st);\n"
"}\n";
shader_handle = glCreateProgram();
vert_handle = glCreateShader(GL_VERTEX_SHADER);
frag_handle = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(vert_handle, 1, &vertex_shader, 0);
glShaderSource(frag_handle, 1, &fragment_shader, 0);
glCompileShader(vert_handle);
glCompileShader(frag_handle);
glAttachShader(shader_handle, vert_handle);
glAttachShader(shader_handle, frag_handle);
glLinkProgram(shader_handle);
texture_location = glGetUniformLocation(shader_handle, "Texture");
proj_mtx_location = glGetUniformLocation(shader_handle, "ProjMtx");
position_location = glGetAttribLocation(shader_handle, "Position");
uv_location = glGetAttribLocation(shader_handle, "UV");
colour_location = glGetAttribLocation(shader_handle, "Color");
glGenBuffers(1, &vbo_handle);
glBindBuffer(GL_ARRAY_BUFFER, vbo_handle);
glBufferData(GL_ARRAY_BUFFER, vbo_max_size, NULL, GL_DYNAMIC_DRAW);
glGenVertexArrays(1, &vao_handle);
glBindVertexArray(vao_handle);
glBindBuffer(GL_ARRAY_BUFFER, vbo_handle);
glEnableVertexAttribArray(position_location);
glEnableVertexAttribArray(uv_location);
glEnableVertexAttribArray(colour_location);
glVertexAttribPointer(position_location, 2, GL_FLOAT, GL_FALSE,
sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, pos));
glVertexAttribPointer(uv_location, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert),
(GLvoid*)offsetof(ImDrawVert, uv));
glVertexAttribPointer(colour_location, 4, GL_UNSIGNED_BYTE, GL_TRUE,
sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, col));
glBindVertexArray(0);
glDisableVertexAttribArray(position_location);
glDisableVertexAttribArray(uv_location);
glDisableVertexAttribArray(colour_location);
glBindBuffer(GL_ARRAY_BUFFER, 0);
unsigned char* pixels;
int width, height;
io.Fonts->GetTexDataAsRGBA32(
&pixels, &width, &height); // Load as RGBA 32-bits for OpenGL3 demo
// because it is more likely to be compatible
// with user's existing shader.
GLuint tex_id;
glCreateTextures(GL_TEXTURE_2D, 1, &tex_id);
glTextureParameteri(tex_id, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTextureParameteri(tex_id, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTextureStorage2D(tex_id, 1, GL_RGBA8, width, height);
glTextureSubImage2D(tex_id, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE,
pixels);
// Store our identifier
io.Fonts->TexID = (void*)(intptr_t)tex_id;
io.DeltaTime = 1.0f / 60.0f;
io.RenderDrawListsFn = ImImpl_RenderDrawLists;
auto& style = ImGui::GetStyle();
style.WindowRounding = 0;
style.Colors[ImGuiCol_Text] = ImVec4(0.89f, 0.90f, 0.90f, 1.00f);
style.Colors[ImGuiCol_WindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
style.Colors[ImGuiCol_ChildWindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
style.Colors[ImGuiCol_Border] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
style.Colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.60f);
style.Colors[ImGuiCol_FrameBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.22f);
style.Colors[ImGuiCol_TitleBg] = ImVec4(0.00f, 1.00f, 0.00f, 0.78f);
style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.58f, 0.00f, 0.61f);
style.Colors[ImGuiCol_ScrollbarBg] = ImVec4(0.00f, 0.40f, 0.11f, 0.59f);
style.Colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.00f, 0.68f, 0.00f, 0.68f);
style.Colors[ImGuiCol_ScrollbarGrabHovered] =
ImVec4(0.00f, 1.00f, 0.15f, 0.62f);
style.Colors[ImGuiCol_ScrollbarGrabActive] =
ImVec4(0.00f, 0.91f, 0.09f, 0.40f);
style.Colors[ImGuiCol_ComboBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.99f);
style.Colors[ImGuiCol_CheckHovered] = ImVec4(0.23f, 0.64f, 0.13f, 0.45f);
style.Colors[ImGuiCol_CheckActive] = ImVec4(0.21f, 0.93f, 0.13f, 0.55f);
style.Colors[ImGuiCol_CheckMark] = ImVec4(0.74f, 0.90f, 0.72f, 0.50f);
style.Colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f);
style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(0.34f, 0.75f, 0.11f, 1.00f);
style.Colors[ImGuiCol_Button] = ImVec4(0.15f, 0.56f, 0.11f, 0.60f);
style.Colors[ImGuiCol_ButtonHovered] = ImVec4(0.19f, 0.72f, 0.09f, 1.00f);
style.Colors[ImGuiCol_ButtonActive] = ImVec4(0.19f, 0.60f, 0.09f, 1.00f);
style.Colors[ImGuiCol_Header] = ImVec4(0.00f, 0.40f, 0.00f, 0.71f);
style.Colors[ImGuiCol_HeaderHovered] = ImVec4(0.00f, 0.60f, 0.26f, 0.80f);
style.Colors[ImGuiCol_HeaderActive] = ImVec4(0.00f, 0.75f, 0.00f, 0.80f);
style.Colors[ImGuiCol_Column] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
style.Colors[ImGuiCol_ColumnHovered] = ImVec4(0.36f, 0.89f, 0.38f, 1.00f);
style.Colors[ImGuiCol_ColumnActive] = ImVec4(0.13f, 0.50f, 0.11f, 1.00f);
style.Colors[ImGuiCol_ResizeGrip] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f);
style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(1.00f, 1.00f, 1.00f, 0.60f);
style.Colors[ImGuiCol_ResizeGripActive] = ImVec4(1.00f, 1.00f, 1.00f, 0.90f);
style.Colors[ImGuiCol_CloseButton] = ImVec4(0.00f, 0.72f, 0.00f, 0.96f);
style.Colors[ImGuiCol_CloseButtonHovered] =
ImVec4(0.38f, 1.00f, 0.42f, 0.60f);
style.Colors[ImGuiCol_CloseButtonActive] = ImVec4(0.56f, 1.00f, 0.64f, 1.00f);
style.Colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
style.Colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
style.Colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
style.Colors[ImGuiCol_PlotHistogramHovered] =
ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
style.Colors[ImGuiCol_TextSelectedBg] = ImVec4(0.00f, 0.00f, 1.00f, 0.35f);
style.Colors[ImGuiCol_TooltipBg] = ImVec4(0.05f, 0.05f, 0.10f, 0.90f);
io.KeyMap[ImGuiKey_Tab] = VK_TAB;
io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT;
io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT;
io.KeyMap[ImGuiKey_UpArrow] = VK_UP;
io.KeyMap[ImGuiKey_DownArrow] = VK_UP;
io.KeyMap[ImGuiKey_Home] = VK_HOME;
io.KeyMap[ImGuiKey_End] = VK_END;
io.KeyMap[ImGuiKey_Delete] = VK_DELETE;
io.KeyMap[ImGuiKey_Backspace] = VK_BACK;
io.KeyMap[ImGuiKey_Enter] = VK_RETURN;
io.KeyMap[ImGuiKey_Escape] = VK_ESCAPE;
io.KeyMap[ImGuiKey_A] = 'A';
io.KeyMap[ImGuiKey_C] = 'C';
io.KeyMap[ImGuiKey_V] = 'V';
io.KeyMap[ImGuiKey_X] = 'X';
io.KeyMap[ImGuiKey_Y] = 'Y';
io.KeyMap[ImGuiKey_Z] = 'Z';
}
void ImImpl_Shutdown() {
ImGuiIO& io = ImGui::GetIO();
if (vao_handle) glDeleteVertexArrays(1, &vao_handle);
if (vbo_handle) glDeleteBuffers(1, &vbo_handle);
glDetachShader(shader_handle, vert_handle);
glDetachShader(shader_handle, frag_handle);
glDeleteShader(vert_handle);
glDeleteShader(frag_handle);
glDeleteProgram(shader_handle);
auto tex_id = static_cast<GLuint>(intptr_t(io.Fonts->TexID));
glDeleteTextures(1, &tex_id);
ImGui::Shutdown();
}
void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) {
if (cmd_lists_count == 0) return;
// Setup render state: alpha-blending enabled, no face culling, no depth
// testing, scissor enabled
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glEnable(GL_SCISSOR_TEST);
glActiveTexture(GL_TEXTURE0);
// Setup orthographic projection matrix
const float width = ImGui::GetIO().DisplaySize.x;
const float height = ImGui::GetIO().DisplaySize.y;
const float ortho_projection[4][4] = {
{2.0f / width, 0.0f, 0.0f, 0.0f},
{0.0f, 2.0f / -height, 0.0f, 0.0f},
{0.0f, 0.0f, -1.0f, 0.0f},
{-1.0f, 1.0f, 0.0f, 1.0f},
};
glProgramUniform1i(shader_handle, texture_location, 0);
glProgramUniformMatrix4fv(shader_handle, proj_mtx_location, 1, GL_FALSE,
&ortho_projection[0][0]);
// Grow our buffer according to what we need
size_t total_vtx_count = 0;
for (int n = 0; n < cmd_lists_count; n++)
total_vtx_count += cmd_lists[n]->vtx_buffer.size();
glBindBuffer(GL_ARRAY_BUFFER, vbo_handle);
size_t neededBufferSize = total_vtx_count * sizeof(ImDrawVert);
if (neededBufferSize > vbo_max_size) {
vbo_max_size = neededBufferSize + 5000; // Grow buffer
glBufferData(GL_ARRAY_BUFFER, vbo_max_size, NULL, GL_STREAM_DRAW);
}
// Copy and convert all vertices into a single contiguous buffer
unsigned char* buffer_data =
(unsigned char*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
if (!buffer_data) return;
for (int n = 0; n < cmd_lists_count; n++) {
const ImDrawList* cmd_list = cmd_lists[n];
memcpy(buffer_data, &cmd_list->vtx_buffer[0],
cmd_list->vtx_buffer.size() * sizeof(ImDrawVert));
buffer_data += cmd_list->vtx_buffer.size() * sizeof(ImDrawVert);
}
glUnmapBuffer(GL_ARRAY_BUFFER);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(vao_handle);
glUseProgram(shader_handle);
int cmd_offset = 0;
ImTextureID prev_texture_id = 0;
for (int n = 0; n < cmd_lists_count; n++) {
const ImDrawList* cmd_list = cmd_lists[n];
int vtx_offset = cmd_offset;
const ImDrawCmd* pcmd_end = cmd_list->commands.end();
for (const ImDrawCmd* pcmd = cmd_list->commands.begin(); pcmd != pcmd_end;
pcmd++) {
if (pcmd->texture_id != prev_texture_id) {
glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->texture_id);
prev_texture_id = pcmd->texture_id;
}
glScissor((int)pcmd->clip_rect.x, (int)(height - pcmd->clip_rect.w),
(int)(pcmd->clip_rect.z - pcmd->clip_rect.x),
(int)(pcmd->clip_rect.w - pcmd->clip_rect.y));
glDrawArrays(GL_TRIANGLES, vtx_offset, pcmd->vtx_count);
vtx_offset += pcmd->vtx_count;
}
cmd_offset = vtx_offset;
}
// Restore modified state
glBindVertexArray(0);
glUseProgram(0);
glDisable(GL_SCISSOR_TEST);
glBindTexture(GL_TEXTURE_2D, 0);
}
} // namespace gpu
} // namespace xe
DEFINE_ENTRY_POINT(L"gpu_trace_viewer", L"gpu_trace_viewer some.trace",
xe::gpu::trace_viewer_main);