diff --git a/src/poly/fs.cc b/src/poly/fs.cc index fed8e177b..23b917c60 100644 --- a/src/poly/fs.cc +++ b/src/poly/fs.cc @@ -17,86 +17,81 @@ namespace poly { namespace fs { std::string CanonicalizePath(const std::string& original_path) { - char path_separator('\\'); - std::string path(poly::fix_path_separators(original_path, path_separator)); + char path_sep('\\'); + std::string path(poly::fix_path_separators(original_path, path_sep)); std::vector path_breaks; - std::string::size_type pos(path.find_first_of(path_separator)); + std::string::size_type pos(path.find_first_of(path_sep)); std::string::size_type pos_n(std::string::npos); while (pos != std::string::npos) { - if ((pos_n = path.find_first_of(path_separator, pos + 1)) == std::string::npos) { + if ((pos_n = path.find_first_of(path_sep, pos + 1)) == std::string::npos) { pos_n = path.size(); } auto diff(pos_n - pos); - switch (diff) - { - case 0: - pos_n = std::string::npos; - break; - case 1: - // Duplicate separators - path.erase(pos, 1); - pos_n -= 1; - break; - case 2: - // Potential marker for current directory - if (path[pos + 1] == '.') { - path.erase(pos, 2); - pos_n -= 2; - } - else { - path_breaks.push_back(pos); - } - break; - case 3: - // Potential marker for parent directory - if (path[pos + 1] == '.' && path[pos + 2] == '.'){ - if (path_breaks.empty()) { - // Ensure we don't override the device name - std::string::size_type loc(path.find_first_of(':')); - auto req(pos + 3); - if (loc == std::string::npos || loc > req) { - path.erase(0, req); - pos_n -= req; - } - else { - path.erase(loc + 1, req - (loc + 1)); - pos_n -= req - (loc + 1); - } + switch (diff) { + case 0: + pos_n = std::string::npos; + break; + case 1: + // Duplicate separators + path.erase(pos, 1); + pos_n -= 1; + break; + case 2: + // Potential marker for current directory + if (path[pos + 1] == '.') { + path.erase(pos, 2); + pos_n -= 2; + } else { + path_breaks.push_back(pos); } - else { - auto last(path_breaks.back()); - auto last_diff((pos + 3) - last); - path.erase(last, last_diff); - pos_n = last; - // Also remove path reference - path_breaks.erase(path_breaks.end() - 1); + break; + case 3: + // Potential marker for parent directory + if (path[pos + 1] == '.' && path[pos + 2] == '.') { + if (path_breaks.empty()) { + // Ensure we don't override the device name + std::string::size_type loc(path.find_first_of(':')); + auto req(pos + 3); + if (loc == std::string::npos || loc > req) { + path.erase(0, req); + pos_n -= req; + } else { + path.erase(loc + 1, req - (loc + 1)); + pos_n -= req - (loc + 1); + } + } else { + auto last(path_breaks.back()); + auto last_diff((pos + 3) - last); + path.erase(last, last_diff); + pos_n = last; + // Also remove path reference + path_breaks.erase(path_breaks.end() - 1); + } + } else { + path_breaks.push_back(pos); } - } - else { - path_breaks.push_back(pos); - } - break; + break; - default: - path_breaks.push_back(pos); - break; + default: + path_breaks.push_back(pos); + break; } pos = pos_n; } // Remove trailing seperator - if (!path.empty() && path.back() == path_separator) { + if (!path.empty() && path.back() == path_sep) { path.erase(path.size() - 1); } // Final sanity check for dead paths - if ((path.size() == 1 && (path[0] == '.' || path[0] == path_separator)) - || (path.size() == 2 && path[0] == '.' && path[1] == '.')) { + if ((path.size() == 1 && (path[0] == '.' || path[0] == path_sep)) || + (path.size() == 2 && path[0] == '.' && path[1] == '.')) { return ""; } @@ -106,25 +101,19 @@ std::string CanonicalizePath(const std::string& original_path) { WildcardFlags WildcardFlags::FIRST(true, false); WildcardFlags WildcardFlags::LAST(false, true); -WildcardFlags::WildcardFlags() - : FromStart(false) - , ToEnd(false) -{ } +WildcardFlags::WildcardFlags() : FromStart(false), ToEnd(false) {} WildcardFlags::WildcardFlags(bool start, bool end) - : FromStart(start) - , ToEnd(end) -{ } + : FromStart(start), ToEnd(end) {} -WildcardRule::WildcardRule(const std::string& str_match, const WildcardFlags& flags) - : match(str_match) - , rules(flags) -{ +WildcardRule::WildcardRule(const std::string& str_match, + const WildcardFlags& flags) + : match(str_match), rules(flags) { std::transform(match.begin(), match.end(), match.begin(), tolower); } -bool WildcardRule::Check(const std::string& str_lower, std::string::size_type& offset) const -{ +bool WildcardRule::Check(const std::string& str_lower, + std::string::size_type& offset) const { if (match.empty()) { return true; } @@ -151,8 +140,7 @@ bool WildcardRule::Check(const std::string& str_lower, std::string::size_type& o return false; } -void WildcardEngine::PreparePattern(const std::string& pattern) -{ +void WildcardEngine::PreparePattern(const std::string& pattern) { rules.clear(); WildcardFlags flags(WildcardFlags::FIRST); @@ -172,13 +160,11 @@ void WildcardEngine::PreparePattern(const std::string& pattern) } } -void WildcardEngine::SetRule(const std::string& pattern) -{ +void WildcardEngine::SetRule(const std::string& pattern) { PreparePattern(pattern); } -bool WildcardEngine::Match(const std::string& str) const -{ +bool WildcardEngine::Match(const std::string& str) const { std::string str_lc; std::transform(str.begin(), str.end(), std::back_inserter(str_lc), tolower); diff --git a/src/poly/string.cc b/src/poly/string.cc index de2b0b254..6d214cde7 100644 --- a/src/poly/string.cc +++ b/src/poly/string.cc @@ -126,16 +126,13 @@ std::string fix_path_separators(const std::string& source, char new_sep) { return dest; } -std::string find_name_from_path(const std::string& path) -{ +std::string find_name_from_path(const std::string& path) { std::string name(path); - if (!path.empty()) { std::string::size_type from(std::string::npos); if (path.back() == '\\') { from = path.size() - 2; } - auto pos(path.find_last_of('\\', from)); if (pos != std::string::npos) { if (from == std::string::npos) { @@ -146,7 +143,26 @@ std::string find_name_from_path(const std::string& path) } } } + return name; +} +std::wstring find_name_from_path(const std::wstring& path) { + std::wstring name(path); + if (!path.empty()) { + std::wstring::size_type from(std::wstring::npos); + if (path.back() == '\\') { + from = path.size() - 2; + } + auto pos(path.find_last_of('\\', from)); + if (pos != std::wstring::npos) { + if (from == std::wstring::npos) { + name = path.substr(pos + 1); + } else { + auto len(from - pos); + name = path.substr(pos + 1, len); + } + } + } return name; } diff --git a/src/poly/string.h b/src/poly/string.h index 7609d2f8e..07b2e4c3a 100644 --- a/src/poly/string.h +++ b/src/poly/string.h @@ -50,6 +50,7 @@ std::string fix_path_separators(const std::string& source, // Find the top directory name or filename from a path std::string find_name_from_path(const std::string& path); +std::wstring find_name_from_path(const std::wstring& path); } // namespace poly diff --git a/src/poly/threading.h b/src/poly/threading.h index 2d103c07a..9d3c16efa 100644 --- a/src/poly/threading.h +++ b/src/poly/threading.h @@ -46,6 +46,7 @@ class Fence { // Gets the current high-performance tick count. uint64_t ticks(); +uint64_t ticks_per_second(); // TODO(benvanik): processor info API. diff --git a/src/poly/threading_win.cc b/src/poly/threading_win.cc index 8af078aff..c27a757bf 100644 --- a/src/poly/threading_win.cc +++ b/src/poly/threading_win.cc @@ -23,6 +23,14 @@ uint64_t ticks() { return time; } +uint64_t ticks_per_second() { + static LARGE_INTEGER freq = {0}; + if (!freq.QuadPart) { + QueryPerformanceFrequency(&freq); + } + return freq.QuadPart; +} + uint32_t current_thread_id() { return static_cast(GetCurrentThreadId()); } diff --git a/src/poly/ui/control.cc b/src/poly/ui/control.cc index e7a55901b..9fa6de5a7 100644 --- a/src/poly/ui/control.cc +++ b/src/poly/ui/control.cc @@ -69,6 +69,8 @@ void Control::OnResize(UIEvent& e) { on_resize(e); } void Control::OnLayout(UIEvent& e) { on_layout(e); } +void Control::OnPaint(UIEvent& e) { on_paint(e); } + void Control::OnVisible(UIEvent& e) { on_visible(e); } void Control::OnHidden(UIEvent& e) { on_hidden(e); } diff --git a/src/poly/ui/control.h b/src/poly/ui/control.h index 0abc72ab2..08248e5ab 100644 --- a/src/poly/ui/control.h +++ b/src/poly/ui/control.h @@ -31,6 +31,8 @@ class Control { Control* parent() const { return parent_; } + size_t child_count() const { return children_.size(); } + Control* child(size_t i) const { return children_[i].get(); } void AddChild(Control* child_control); void AddChild(std::unique_ptr child_control); void AddChild(ControlPtr child_control); @@ -65,6 +67,7 @@ class Control { public: poly::Delegate on_resize; poly::Delegate on_layout; + poly::Delegate on_paint; poly::Delegate on_visible; poly::Delegate on_hidden; @@ -94,6 +97,7 @@ class Control { virtual void OnResize(UIEvent& e); virtual void OnLayout(UIEvent& e); + virtual void OnPaint(UIEvent& e); virtual void OnVisible(UIEvent& e); virtual void OnHidden(UIEvent& e); diff --git a/src/xenia/gpu/gl4/command_processor.cc b/src/xenia/gpu/gl4/command_processor.cc index 39204fd58..482b2c34c 100644 --- a/src/xenia/gpu/gl4/command_processor.cc +++ b/src/xenia/gpu/gl4/command_processor.cc @@ -93,8 +93,6 @@ uint64_t CommandProcessor::QueryTime() { bool CommandProcessor::Initialize(std::unique_ptr context) { context_ = std::move(context); - pending_fn_event_ = CreateEvent(nullptr, TRUE, FALSE, nullptr); - worker_running_ = true; worker_thread_ = std::thread([this]() { poly::threading::set_name("GL4 Worker"); @@ -118,8 +116,6 @@ void CommandProcessor::Shutdown() { shader_cache_.clear(); context_.reset(); - - CloseHandle(pending_fn_event_); } void CommandProcessor::BeginTracing(const std::wstring& root_path) { @@ -130,10 +126,12 @@ void CommandProcessor::BeginTracing(const std::wstring& root_path) { void CommandProcessor::EndTracing() { trace_writer_.Close(); } void CommandProcessor::CallInThread(std::function fn) { - assert_null(pending_fn_); - pending_fn_ = std::move(fn); - WaitForSingleObject(pending_fn_event_, INFINITE); - ResetEvent(pending_fn_event_); + if (pending_fns_.empty() && + worker_thread_.get_id() == std::this_thread::get_id()) { + fn(); + } else { + pending_fns_.push(std::move(fn)); + } } void CommandProcessor::WorkerMain() { @@ -144,11 +142,10 @@ void CommandProcessor::WorkerMain() { } while (worker_running_) { - if (pending_fn_) { - auto fn = std::move(pending_fn_); - pending_fn_ = nullptr; + while (!pending_fns_.empty()) { + auto fn = std::move(pending_fns_.front()); + pending_fns_.pop(); fn(); - SetEvent(pending_fn_event_); } uint32_t write_ptr_index = write_ptr_index_.load(); @@ -166,10 +163,10 @@ void CommandProcessor::WorkerMain() { SwitchToThread(); MemoryBarrier(); write_ptr_index = write_ptr_index_.load(); - } while (!pending_fn_ && (write_ptr_index == 0xBAADF00D || - read_ptr_index_ == write_ptr_index)); + } while (pending_fns_.empty() && (write_ptr_index == 0xBAADF00D || + read_ptr_index_ == write_ptr_index)); // ReturnFromWait(); - if (pending_fn_) { + if (!pending_fns_.empty()) { continue; } } diff --git a/src/xenia/gpu/gl4/command_processor.h b/src/xenia/gpu/gl4/command_processor.h index 18735371e..3223dd608 100644 --- a/src/xenia/gpu/gl4/command_processor.h +++ b/src/xenia/gpu/gl4/command_processor.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -212,8 +213,7 @@ class CommandProcessor { std::atomic worker_running_; std::unique_ptr context_; SwapHandler swap_handler_; - std::function pending_fn_; - HANDLE pending_fn_event_; + std::queue> pending_fns_; SwapMode swap_mode_; diff --git a/src/xenia/gpu/gl4/gl4_graphics_system.cc b/src/xenia/gpu/gl4/gl4_graphics_system.cc index 8884b7070..cc5390102 100644 --- a/src/xenia/gpu/gl4/gl4_graphics_system.cc +++ b/src/xenia/gpu/gl4/gl4_graphics_system.cc @@ -126,99 +126,102 @@ void GL4GraphicsSystem::RequestSwap() { command_processor_->CallInThread([&]() { command_processor_->IssueSwap(); }); } -const uint8_t* GL4GraphicsSystem::PlayTrace(const uint8_t* trace_data, - size_t trace_size, - TracePlaybackMode playback_mode) { - auto trace_ptr = trace_data; - command_processor_->CallInThread([&]() { - bool pending_break = false; - const PacketStartCommand* pending_packet = nullptr; - while (trace_ptr < trace_data + trace_size) { - auto type = - static_cast(poly::load(trace_ptr)); - switch (type) { - case TraceCommandType::kPrimaryBufferStart: { - auto cmd = - reinterpret_cast(trace_ptr); - // - trace_ptr += sizeof(*cmd) + cmd->count * 4; - break; - } - case TraceCommandType::kPrimaryBufferEnd: { - auto cmd = - reinterpret_cast(trace_ptr); - // - trace_ptr += sizeof(*cmd); - break; - } - case TraceCommandType::kIndirectBufferStart: { - auto cmd = - reinterpret_cast(trace_ptr); - // - trace_ptr += sizeof(*cmd) + cmd->count * 4; - break; - } - case TraceCommandType::kIndirectBufferEnd: { - auto cmd = - reinterpret_cast(trace_ptr); - // - trace_ptr += sizeof(*cmd); - break; - } - case TraceCommandType::kPacketStart: { - auto cmd = reinterpret_cast(trace_ptr); - trace_ptr += sizeof(*cmd); - std::memcpy(memory()->Translate(cmd->base_ptr), trace_ptr, - cmd->count * 4); - trace_ptr += cmd->count * 4; - pending_packet = cmd; - break; - } - case TraceCommandType::kPacketEnd: { - auto cmd = reinterpret_cast(trace_ptr); - trace_ptr += sizeof(*cmd); - if (pending_packet) { - command_processor_->ExecutePacket(pending_packet->base_ptr, - pending_packet->count); - pending_packet = nullptr; - } - if (pending_break) { - return; - } - break; - } - case TraceCommandType::kMemoryRead: { - auto cmd = reinterpret_cast(trace_ptr); - trace_ptr += sizeof(*cmd); - std::memcpy(memory()->Translate(cmd->base_ptr), trace_ptr, - cmd->length); - trace_ptr += cmd->length; - break; - } - case TraceCommandType::kMemoryWrite: { - auto cmd = reinterpret_cast(trace_ptr); - trace_ptr += sizeof(*cmd); - // ? - trace_ptr += cmd->length; - break; - } - case TraceCommandType::kEvent: { - auto cmd = reinterpret_cast(trace_ptr); - trace_ptr += sizeof(*cmd); - switch (cmd->event_type) { - case EventType::kSwap: { - if (playback_mode == TracePlaybackMode::kBreakOnSwap) { - pending_break = true; +void GL4GraphicsSystem::PlayTrace(const uint8_t* trace_data, size_t trace_size, + TracePlaybackMode playback_mode) { + command_processor_->CallInThread( + [this, trace_data, trace_size, playback_mode]() { + command_processor_->set_swap_mode(SwapMode::kIgnored); + + auto trace_ptr = trace_data; + bool pending_break = false; + const PacketStartCommand* pending_packet = nullptr; + while (trace_ptr < trace_data + trace_size) { + auto type = + static_cast(poly::load(trace_ptr)); + switch (type) { + case TraceCommandType::kPrimaryBufferStart: { + auto cmd = + reinterpret_cast(trace_ptr); + // + trace_ptr += sizeof(*cmd) + cmd->count * 4; + break; + } + case TraceCommandType::kPrimaryBufferEnd: { + auto cmd = + reinterpret_cast(trace_ptr); + // + trace_ptr += sizeof(*cmd); + break; + } + case TraceCommandType::kIndirectBufferStart: { + auto cmd = reinterpret_cast( + trace_ptr); + // + trace_ptr += sizeof(*cmd) + cmd->count * 4; + break; + } + case TraceCommandType::kIndirectBufferEnd: { + auto cmd = + reinterpret_cast(trace_ptr); + // + trace_ptr += sizeof(*cmd); + break; + } + case TraceCommandType::kPacketStart: { + auto cmd = reinterpret_cast(trace_ptr); + trace_ptr += sizeof(*cmd); + std::memcpy(memory()->Translate(cmd->base_ptr), trace_ptr, + cmd->count * 4); + trace_ptr += cmd->count * 4; + pending_packet = cmd; + break; + } + case TraceCommandType::kPacketEnd: { + auto cmd = reinterpret_cast(trace_ptr); + trace_ptr += sizeof(*cmd); + if (pending_packet) { + command_processor_->ExecutePacket(pending_packet->base_ptr, + pending_packet->count); + pending_packet = nullptr; + } + if (pending_break) { + return; + } + break; + } + case TraceCommandType::kMemoryRead: { + auto cmd = reinterpret_cast(trace_ptr); + trace_ptr += sizeof(*cmd); + std::memcpy(memory()->Translate(cmd->base_ptr), trace_ptr, + cmd->length); + trace_ptr += cmd->length; + break; + } + case TraceCommandType::kMemoryWrite: { + auto cmd = reinterpret_cast(trace_ptr); + trace_ptr += sizeof(*cmd); + // ? + trace_ptr += cmd->length; + break; + } + case TraceCommandType::kEvent: { + auto cmd = reinterpret_cast(trace_ptr); + trace_ptr += sizeof(*cmd); + switch (cmd->event_type) { + case EventType::kSwap: { + if (playback_mode == TracePlaybackMode::kBreakOnSwap) { + pending_break = true; + } + break; + } } break; } } - break; } - } - } - }); - return trace_ptr; + + command_processor_->set_swap_mode(SwapMode::kNormal); + }); } void GL4GraphicsSystem::MarkVblank() { diff --git a/src/xenia/gpu/gl4/gl4_graphics_system.h b/src/xenia/gpu/gl4/gl4_graphics_system.h index d969bb660..3d35cbdde 100644 --- a/src/xenia/gpu/gl4/gl4_graphics_system.h +++ b/src/xenia/gpu/gl4/gl4_graphics_system.h @@ -38,8 +38,8 @@ class GL4GraphicsSystem : public GraphicsSystem { void RequestSwap() override; - const uint8_t* PlayTrace(const uint8_t* trace_data, size_t trace_size, - TracePlaybackMode playback_mode) override; + void PlayTrace(const uint8_t* trace_data, size_t trace_size, + TracePlaybackMode playback_mode) override; private: void MarkVblank(); diff --git a/src/xenia/gpu/gl4/wgl_control.cc b/src/xenia/gpu/gl4/wgl_control.cc index 5b1c56c4d..164020331 100644 --- a/src/xenia/gpu/gl4/wgl_control.cc +++ b/src/xenia/gpu/gl4/wgl_control.cc @@ -91,6 +91,9 @@ LRESULT WGLControl::WndProc(HWND hWnd, UINT message, WPARAM wParam, current_paint_callback_ = nullptr; } + poly::ui::UIEvent e(this); + OnPaint(e); + // TODO(benvanik): profiler present. Profiler::Present(); } diff --git a/src/xenia/gpu/graphics_system.h b/src/xenia/gpu/graphics_system.h index 50be9f7c0..48d700616 100644 --- a/src/xenia/gpu/graphics_system.h +++ b/src/xenia/gpu/graphics_system.h @@ -47,10 +47,8 @@ class GraphicsSystem { kUntilEnd, kBreakOnSwap, }; - virtual const uint8_t* PlayTrace(const uint8_t* trace_data, size_t trace_size, - TracePlaybackMode playback_mode) { - return nullptr; - } + virtual void PlayTrace(const uint8_t* trace_data, size_t trace_size, + TracePlaybackMode playback_mode) {} protected: GraphicsSystem(); diff --git a/src/xenia/gpu/trace_viewer_main.cc b/src/xenia/gpu/trace_viewer_main.cc index 63c3d476c..6b5cc2378 100644 --- a/src/xenia/gpu/trace_viewer_main.cc +++ b/src/xenia/gpu/trace_viewer_main.cc @@ -8,8 +8,11 @@ */ #include + #include "poly/main.h" #include "poly/mapped_memory.h" +#include "third_party/imgui/imgui.h" +#include "xenia/gpu/gl4/gl_context.h" #include "xenia/gpu/graphics_system.h" #include "xenia/gpu/tracing.h" #include "xenia/emulator.h" @@ -20,6 +23,271 @@ DEFINE_string(target_trace_file, "", "Specifies the trace file to load."); namespace xe { namespace gpu { +// TODO(benvanik): move to tracing.h/cc + +class TraceReader { + public: + struct Frame { + const uint8_t* start_ptr; + const uint8_t* end_ptr; + int command_count; + }; + + 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(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(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, + }; + bool pending_break = false; + while (trace_ptr < trace_data_ + trace_size_) { + ++current_frame.command_count; + auto type = + static_cast(poly::load(trace_ptr)); + switch (type) { + case TraceCommandType::kPrimaryBufferStart: { + auto cmd = + reinterpret_cast(trace_ptr); + trace_ptr += sizeof(*cmd) + cmd->count * 4; + break; + } + case TraceCommandType::kPrimaryBufferEnd: { + auto cmd = + reinterpret_cast(trace_ptr); + trace_ptr += sizeof(*cmd); + break; + } + case TraceCommandType::kIndirectBufferStart: { + auto cmd = + reinterpret_cast(trace_ptr); + trace_ptr += sizeof(*cmd) + cmd->count * 4; + break; + } + case TraceCommandType::kIndirectBufferEnd: { + auto cmd = + reinterpret_cast(trace_ptr); + trace_ptr += sizeof(*cmd); + break; + } + case TraceCommandType::kPacketStart: { + auto cmd = reinterpret_cast(trace_ptr); + trace_ptr += sizeof(*cmd) + cmd->count * 4; + break; + } + case TraceCommandType::kPacketEnd: { + auto cmd = reinterpret_cast(trace_ptr); + trace_ptr += sizeof(*cmd); + 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(trace_ptr); + trace_ptr += sizeof(*cmd) + cmd->length; + break; + } + case TraceCommandType::kMemoryWrite: { + auto cmd = reinterpret_cast(trace_ptr); + trace_ptr += sizeof(*cmd) + cmd->length; + break; + } + case TraceCommandType::kEvent: { + auto cmd = reinterpret_cast(trace_ptr); + trace_ptr += sizeof(*cmd); + switch (cmd->event_type) { + case EventType::kSwap: { + pending_break = true; + break; + } + } + break; + } + } + } + if (pending_break) { + current_frame.end_ptr = trace_ptr; + frames_.push_back(std::move(current_frame)); + } + } + + std::unique_ptr mmap_; + const uint8_t* trace_data_; + size_t trace_size_; + std::vector 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; + + 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_ = frame->command_count - 1; + + 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) { + current_command_index_ = target_command; + } + + private: + poly::ui::Loop* loop_; + GraphicsSystem* graphics_system_; + int current_frame_index_; + int current_command_index_; +}; + +void DrawUI(xe::ui::MainWindow* window, TracePlayer& player) { + ImGui::ShowTestWindow(); + + ImGui::SetNextWindowPos(ImVec2(5, 5), ImGuiSetCondition_FirstUseEver); + if (ImGui::Begin("Controller", nullptr, ImVec2(0, 0), -1.0f, + ImGuiWindowFlags_AlwaysAutoResize)) { + int target_frame = player.current_frame_index(); + if (ImGui::Button("|<<")) { + target_frame = 0; + } + ImGui::SameLine(); + if (ImGui::Button(">>", ImVec2(0, 0), true)) { + if (target_frame + 1 < player.frame_count()) { + ++target_frame; + } + } + ImGui::SameLine(); + if (ImGui::Button(">>|")) { + target_frame = player.frame_count() - 1; + } + ImGui::SameLine(); + ImGui::SliderInt("", &target_frame, 0, player.frame_count() - 1); + if (target_frame != player.current_frame_index()) { + player.SeekFrame(target_frame); + } + } + ImGui::End(); + + ImGui::SetNextWindowPos(ImVec2(float(window->width()) - 500 - 5, 5), + ImGuiSetCondition_FirstUseEver); + if (ImGui::Begin("Frame Inspector", nullptr, ImVec2(500, 300))) { + ImGui::Columns(2, "frame_inspector"); + ImGui::Text("Frame #%d", player.current_frame_index()); + ImGui::Separator(); + ImGui::BeginChild("Sub1"); + ImGui::PushID(-1); + bool is_selected = player.current_command_index() == -1; + if (ImGui::Selectable("", &is_selected)) { + player.SeekCommand(-1); + } + ImGui::PopID(); + int column_width = int(ImGui::GetContentRegionMax().x); + auto frame = player.current_frame(); + for (int i = 0; i < frame->command_count; ++i) { + ImGui::PushID(i); + is_selected = i == player.current_command_index(); + if (ImGui::Selectable("command", &is_selected)) { + player.SeekCommand(i); + } + ImGui::SameLine(column_width - 30); + ImGui::Text("bar"); + ImGui::PopID(); + } + ImGui::EndChild(); + ImGui::NextColumn(); + ImGui::Text("right side"); + ImGui::Columns(1); + } + ImGui::End(); +} + +void ImImpl_Setup(); +void ImImpl_Shutdown(); + int trace_viewer_main(std::vector& args) { // Create the emulator. auto emulator = std::make_unique(L""); @@ -42,30 +310,336 @@ int trace_viewer_main(std::vector& args) { path = args[1]; } // Normalize the path and make absolute. - std::wstring abs_path = poly::to_absolute_path(path); + 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); - // TODO(benvanik): UI? replay control on graphics system? auto graphics_system = emulator->graphics_system(); - auto mmap = - poly::MappedMemory::Open(abs_path, poly::MappedMemory::Mode::kRead); - auto trace_data = reinterpret_cast(mmap->data()); - auto trace_size = mmap->size(); - auto trace_ptr = trace_data; - while (trace_ptr < trace_data + trace_size) { - trace_ptr = graphics_system->PlayTrace( - trace_ptr, trace_size - (trace_ptr - trace_data), - GraphicsSystem::TracePlaybackMode::kBreakOnSwap); + 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_down.AddListener([](poly::ui::KeyEvent& e) {}); + control->on_key_up.AddListener([](poly::ui::KeyEvent& e) {}); + 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())); + + ImGui::NewFrame(); + + DrawUI(window, player); + + 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); +} +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(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