// Copyright 2014 Citra Emulator Project // Licensed under GPLv2 // Refer to the license.txt file included. #pragma once #include #include #include #include "common/log.h" #include "core/hle/service/gsp.h" #include "pica.h" class GraphicsDebugger { public: // A few utility structs used to expose data // A vector of commands represented by their raw byte sequence struct PicaCommand : public std::vector { const Pica::CommandHeader& GetHeader() const { const u32& val = at(1); return *(Pica::CommandHeader*)&val; } }; typedef std::vector PicaCommandList; // Base class for all objects which need to be notified about GPU events class DebuggerObserver { public: DebuggerObserver() : observed(nullptr) { } virtual ~DebuggerObserver() { if (observed) observed->UnregisterObserver(this); } /** * Called when a GX command has been processed and is ready for being * read via GraphicsDebugger::ReadGXCommandHistory. * @param total_command_count Total number of commands in the GX history * @note All methods in this class are called from the GSP thread */ virtual void GXCommandProcessed(int total_command_count) { const GSP_GPU::GXCommand& cmd = observed->ReadGXCommandHistory(total_command_count-1); ERROR_LOG(GSP, "Received command: id=%x", cmd.id); } /** * @param lst command list which triggered this call * @param is_new true if the command list was called for the first time * @todo figure out how to make sure called functions don't keep references around beyond their life time */ virtual void OnCommandListCalled(const PicaCommandList& lst, bool is_new) { ERROR_LOG(GSP, "Command list called: %d", (int)is_new); } protected: const GraphicsDebugger* GetDebugger() const { return observed; } private: GraphicsDebugger* observed; bool in_destruction; friend class GraphicsDebugger; }; void GXCommandProcessed(u8* command_data) { gx_command_history.push_back(GSP_GPU::GXCommand()); GSP_GPU::GXCommand& cmd = gx_command_history[gx_command_history.size()-1]; const int cmd_length = sizeof(GSP_GPU::GXCommand); memcpy(cmd.data, command_data, cmd_length); ForEachObserver([this](DebuggerObserver* observer) { observer->GXCommandProcessed(this->gx_command_history.size()); } ); } void CommandListCalled(u32 address, u32* command_list, u32 size_in_words) { PicaCommandList cmdlist; for (u32* parse_pointer = command_list; parse_pointer < command_list + size_in_words;) { const Pica::CommandHeader header = static_cast(parse_pointer[1]); cmdlist.push_back(PicaCommand()); auto& cmd = cmdlist.back(); size_t size = 2 + header.extra_data_length; size = (size + 1) / 2 * 2; // align to 8 bytes cmd.reserve(size); std::copy(parse_pointer, parse_pointer + size, std::back_inserter(cmd)); parse_pointer += size; } auto obj = std::pair(address, cmdlist); auto it = std::find(command_lists.begin(), command_lists.end(), obj); bool is_new = (it == command_lists.end()); if (is_new) command_lists.push_back(obj); ForEachObserver([&](DebuggerObserver* observer) { observer->OnCommandListCalled(obj.second, is_new); } ); } const GSP_GPU::GXCommand& ReadGXCommandHistory(int index) const { // TODO: Is this thread-safe? return gx_command_history[index]; } const std::vector>& GetCommandLists() const { return command_lists; } void RegisterObserver(DebuggerObserver* observer) { // TODO: Check for duplicates observers.push_back(observer); observer->observed = this; } void UnregisterObserver(DebuggerObserver* observer) { std::remove(observers.begin(), observers.end(), observer); observer->observed = nullptr; } private: void ForEachObserver(std::function func) { std::for_each(observers.begin(),observers.end(), func); } std::vector observers; std::vector gx_command_history; // vector of pairs of command lists and their storage address std::vector> command_lists; };