Factoring out a lot of reusable GPU code from gl4/.
This commit is contained in:
parent
b26f4a5719
commit
b5a18b5462
|
@ -117,7 +117,7 @@ X_STATUS Emulator::Setup(ui::Window* display_window) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the GPU.
|
// Initialize the GPU.
|
||||||
graphics_system_ = xe::gpu::GraphicsSystem::Create(this);
|
graphics_system_ = xe::gpu::GraphicsSystem::Create();
|
||||||
if (!graphics_system_) {
|
if (!graphics_system_) {
|
||||||
return X_STATUS_NOT_IMPLEMENTED;
|
return X_STATUS_NOT_IMPLEMENTED;
|
||||||
}
|
}
|
||||||
|
@ -144,7 +144,7 @@ X_STATUS Emulator::Setup(ui::Window* display_window) {
|
||||||
kernel_state_ = std::make_unique<xe::kernel::KernelState>(this);
|
kernel_state_ = std::make_unique<xe::kernel::KernelState>(this);
|
||||||
|
|
||||||
// Setup the core components.
|
// Setup the core components.
|
||||||
result = graphics_system_->Setup(processor_.get(), display_window_->loop(),
|
result = graphics_system_->Setup(processor_.get(), kernel_state_.get(),
|
||||||
display_window_);
|
display_window_);
|
||||||
if (result) {
|
if (result) {
|
||||||
return result;
|
return result;
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,213 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* 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. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XENIA_GPU_COMMAND_PROCESSOR_H_
|
||||||
|
#define XENIA_GPU_COMMAND_PROCESSOR_H_
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstring>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <queue>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "xenia/base/threading.h"
|
||||||
|
#include "xenia/gpu/register_file.h"
|
||||||
|
#include "xenia/gpu/trace_writer.h"
|
||||||
|
#include "xenia/gpu/xenos.h"
|
||||||
|
#include "xenia/kernel/xthread.h"
|
||||||
|
#include "xenia/memory.h"
|
||||||
|
#include "xenia/ui/graphics_context.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace gpu {
|
||||||
|
|
||||||
|
class GraphicsSystem;
|
||||||
|
|
||||||
|
struct SwapState {
|
||||||
|
// Lock must be held when changing data in this structure.
|
||||||
|
std::mutex mutex;
|
||||||
|
// Dimensions of the framebuffer textures. Should match window size.
|
||||||
|
uint32_t width = 0;
|
||||||
|
uint32_t height = 0;
|
||||||
|
// Current front buffer, being drawn to the screen.
|
||||||
|
uintptr_t front_buffer_texture = 0;
|
||||||
|
// Current back buffer, being updated by the CP.
|
||||||
|
uintptr_t back_buffer_texture = 0;
|
||||||
|
// Whether the back buffer is dirty and a swap is pending.
|
||||||
|
bool pending = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class SwapMode {
|
||||||
|
kNormal,
|
||||||
|
kIgnored,
|
||||||
|
};
|
||||||
|
|
||||||
|
class CommandProcessor {
|
||||||
|
public:
|
||||||
|
CommandProcessor(GraphicsSystem* graphics_system,
|
||||||
|
kernel::KernelState* kernel_state);
|
||||||
|
virtual ~CommandProcessor();
|
||||||
|
|
||||||
|
uint32_t counter() const { return counter_; }
|
||||||
|
void increment_counter() { counter_++; }
|
||||||
|
|
||||||
|
virtual bool Initialize(std::unique_ptr<xe::ui::GraphicsContext> context);
|
||||||
|
virtual void Shutdown();
|
||||||
|
|
||||||
|
void CallInThread(std::function<void()> fn);
|
||||||
|
|
||||||
|
virtual void ClearCaches();
|
||||||
|
|
||||||
|
SwapState& swap_state() { return swap_state_; }
|
||||||
|
void set_swap_mode(SwapMode swap_mode) { swap_mode_ = swap_mode; }
|
||||||
|
void IssueSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
|
||||||
|
uint32_t frontbuffer_height);
|
||||||
|
|
||||||
|
void set_swap_request_handler(std::function<void()> fn) {
|
||||||
|
swap_request_handler_ = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RequestFrameTrace(const std::wstring& root_path);
|
||||||
|
void BeginTracing(const std::wstring& root_path);
|
||||||
|
void EndTracing();
|
||||||
|
|
||||||
|
void InitializeRingBuffer(uint32_t ptr, uint32_t page_count);
|
||||||
|
void EnableReadPointerWriteBack(uint32_t ptr, uint32_t block_size);
|
||||||
|
|
||||||
|
void UpdateWritePointer(uint32_t value);
|
||||||
|
|
||||||
|
void ExecutePacket(uint32_t ptr, uint32_t count);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
class RingbufferReader;
|
||||||
|
|
||||||
|
struct IndexBufferInfo {
|
||||||
|
xenos::IndexFormat format = xenos::IndexFormat::kInt16;
|
||||||
|
xenos::Endian endianness = xenos::Endian::kUnspecified;
|
||||||
|
uint32_t count = 0;
|
||||||
|
uint32_t guest_base = 0;
|
||||||
|
size_t length = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
void WorkerThreadMain();
|
||||||
|
virtual bool SetupContext() = 0;
|
||||||
|
virtual void ShutdownContext() = 0;
|
||||||
|
|
||||||
|
void WriteRegister(uint32_t index, uint32_t value);
|
||||||
|
|
||||||
|
virtual void MakeCoherent();
|
||||||
|
virtual void PrepareForWait();
|
||||||
|
virtual void ReturnFromWait();
|
||||||
|
|
||||||
|
virtual void PerformSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
|
||||||
|
uint32_t frontbuffer_height) = 0;
|
||||||
|
|
||||||
|
void ExecutePrimaryBuffer(uint32_t start_index, uint32_t end_index);
|
||||||
|
void ExecuteIndirectBuffer(uint32_t ptr, uint32_t length);
|
||||||
|
bool ExecutePacket(RingbufferReader* reader);
|
||||||
|
bool ExecutePacketType0(RingbufferReader* reader, uint32_t packet);
|
||||||
|
bool ExecutePacketType1(RingbufferReader* reader, uint32_t packet);
|
||||||
|
bool ExecutePacketType2(RingbufferReader* reader, uint32_t packet);
|
||||||
|
bool ExecutePacketType3(RingbufferReader* reader, uint32_t packet);
|
||||||
|
bool ExecutePacketType3_ME_INIT(RingbufferReader* reader, uint32_t packet,
|
||||||
|
uint32_t count);
|
||||||
|
bool ExecutePacketType3_NOP(RingbufferReader* reader, uint32_t packet,
|
||||||
|
uint32_t count);
|
||||||
|
bool ExecutePacketType3_INTERRUPT(RingbufferReader* reader, uint32_t packet,
|
||||||
|
uint32_t count);
|
||||||
|
bool ExecutePacketType3_XE_SWAP(RingbufferReader* reader, uint32_t packet,
|
||||||
|
uint32_t count);
|
||||||
|
bool ExecutePacketType3_INDIRECT_BUFFER(RingbufferReader* reader,
|
||||||
|
uint32_t packet, uint32_t count);
|
||||||
|
bool ExecutePacketType3_WAIT_REG_MEM(RingbufferReader* reader,
|
||||||
|
uint32_t packet, uint32_t count);
|
||||||
|
bool ExecutePacketType3_REG_RMW(RingbufferReader* reader, uint32_t packet,
|
||||||
|
uint32_t count);
|
||||||
|
bool ExecutePacketType3_COND_WRITE(RingbufferReader* reader, uint32_t packet,
|
||||||
|
uint32_t count);
|
||||||
|
bool ExecutePacketType3_EVENT_WRITE(RingbufferReader* reader, uint32_t packet,
|
||||||
|
uint32_t count);
|
||||||
|
bool ExecutePacketType3_EVENT_WRITE_SHD(RingbufferReader* reader,
|
||||||
|
uint32_t packet, uint32_t count);
|
||||||
|
bool ExecutePacketType3_EVENT_WRITE_EXT(RingbufferReader* reader,
|
||||||
|
uint32_t packet, uint32_t count);
|
||||||
|
bool ExecutePacketType3_DRAW_INDX(RingbufferReader* reader, uint32_t packet,
|
||||||
|
uint32_t count);
|
||||||
|
bool ExecutePacketType3_DRAW_INDX_2(RingbufferReader* reader, uint32_t packet,
|
||||||
|
uint32_t count);
|
||||||
|
bool ExecutePacketType3_SET_CONSTANT(RingbufferReader* reader,
|
||||||
|
uint32_t packet, uint32_t count);
|
||||||
|
bool ExecutePacketType3_SET_CONSTANT2(RingbufferReader* reader,
|
||||||
|
uint32_t packet, uint32_t count);
|
||||||
|
bool ExecutePacketType3_LOAD_ALU_CONSTANT(RingbufferReader* reader,
|
||||||
|
uint32_t packet, uint32_t count);
|
||||||
|
bool ExecutePacketType3_SET_SHADER_CONSTANTS(RingbufferReader* reader,
|
||||||
|
uint32_t packet, uint32_t count);
|
||||||
|
bool ExecutePacketType3_IM_LOAD(RingbufferReader* reader, uint32_t packet,
|
||||||
|
uint32_t count);
|
||||||
|
bool ExecutePacketType3_IM_LOAD_IMMEDIATE(RingbufferReader* reader,
|
||||||
|
|
||||||
|
uint32_t packet, uint32_t count);
|
||||||
|
bool ExecutePacketType3_INVALIDATE_STATE(RingbufferReader* reader,
|
||||||
|
uint32_t packet, uint32_t count);
|
||||||
|
|
||||||
|
virtual bool LoadShader(ShaderType shader_type, uint32_t guest_address,
|
||||||
|
const uint32_t* host_address,
|
||||||
|
uint32_t dword_count) = 0;
|
||||||
|
|
||||||
|
virtual bool IssueDraw(PrimitiveType prim_type, uint32_t index_count,
|
||||||
|
IndexBufferInfo* index_buffer_info) = 0;
|
||||||
|
virtual bool IssueCopy() = 0;
|
||||||
|
|
||||||
|
Memory* memory_ = nullptr;
|
||||||
|
kernel::KernelState* kernel_state_ = nullptr;
|
||||||
|
GraphicsSystem* graphics_system_ = nullptr;
|
||||||
|
RegisterFile* register_file_ = nullptr;
|
||||||
|
|
||||||
|
TraceWriter trace_writer_;
|
||||||
|
enum class TraceState {
|
||||||
|
kDisabled,
|
||||||
|
kStreaming,
|
||||||
|
kSingleFrame,
|
||||||
|
};
|
||||||
|
TraceState trace_state_ = TraceState::kDisabled;
|
||||||
|
std::wstring trace_frame_path_;
|
||||||
|
|
||||||
|
std::atomic<bool> worker_running_;
|
||||||
|
kernel::object_ref<kernel::XHostThread> worker_thread_;
|
||||||
|
|
||||||
|
std::unique_ptr<xe::ui::GraphicsContext> context_;
|
||||||
|
SwapMode swap_mode_ = SwapMode::kNormal;
|
||||||
|
SwapState swap_state_;
|
||||||
|
std::function<void()> swap_request_handler_;
|
||||||
|
std::queue<std::function<void()>> pending_fns_;
|
||||||
|
|
||||||
|
uint32_t counter_ = 0;
|
||||||
|
|
||||||
|
uint32_t primary_buffer_ptr_ = 0;
|
||||||
|
uint32_t primary_buffer_size_ = 0;
|
||||||
|
|
||||||
|
uint32_t read_ptr_index_ = 0;
|
||||||
|
uint32_t read_ptr_update_freq_ = 0;
|
||||||
|
uint32_t read_ptr_writeback_ptr_ = 0;
|
||||||
|
|
||||||
|
std::unique_ptr<xe::threading::Event> write_ptr_index_event_;
|
||||||
|
std::atomic<uint32_t> write_ptr_index_;
|
||||||
|
|
||||||
|
uint64_t bin_select_ = 0xFFFFFFFFull;
|
||||||
|
uint64_t bin_mask_ = 0xFFFFFFFFull;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace gpu
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_GPU_COMMAND_PROCESSOR_H_
|
File diff suppressed because it is too large
Load Diff
|
@ -21,82 +21,31 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "xenia/base/threading.h"
|
#include "xenia/base/threading.h"
|
||||||
|
#include "xenia/gpu/command_processor.h"
|
||||||
#include "xenia/gpu/gl4/draw_batcher.h"
|
#include "xenia/gpu/gl4/draw_batcher.h"
|
||||||
#include "xenia/gpu/gl4/gl4_shader.h"
|
#include "xenia/gpu/gl4/gl4_shader.h"
|
||||||
#include "xenia/gpu/gl4/gl4_shader_translator.h"
|
#include "xenia/gpu/gl4/gl4_shader_translator.h"
|
||||||
#include "xenia/gpu/gl4/texture_cache.h"
|
#include "xenia/gpu/gl4/texture_cache.h"
|
||||||
#include "xenia/gpu/register_file.h"
|
#include "xenia/gpu/register_file.h"
|
||||||
#include "xenia/gpu/tracing.h"
|
|
||||||
#include "xenia/gpu/xenos.h"
|
#include "xenia/gpu/xenos.h"
|
||||||
#include "xenia/kernel/xthread.h"
|
#include "xenia/kernel/xthread.h"
|
||||||
#include "xenia/memory.h"
|
#include "xenia/memory.h"
|
||||||
#include "xenia/ui/gl/circular_buffer.h"
|
#include "xenia/ui/gl/circular_buffer.h"
|
||||||
#include "xenia/ui/gl/gl_context.h"
|
#include "xenia/ui/gl/gl_context.h"
|
||||||
|
|
||||||
namespace xe {
|
|
||||||
namespace kernel {
|
|
||||||
class XHostThread;
|
|
||||||
} // namespace kernel
|
|
||||||
} // namespace xe
|
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace gpu {
|
namespace gpu {
|
||||||
namespace gl4 {
|
namespace gl4 {
|
||||||
|
|
||||||
class GL4GraphicsSystem;
|
class GL4GraphicsSystem;
|
||||||
|
|
||||||
struct SwapState {
|
class GL4CommandProcessor : public CommandProcessor {
|
||||||
// Lock must be held when changing data in this structure.
|
|
||||||
std::mutex mutex;
|
|
||||||
// Dimensions of the framebuffer textures. Should match window size.
|
|
||||||
uint32_t width = 0;
|
|
||||||
uint32_t height = 0;
|
|
||||||
// Current front buffer, being drawn to the screen.
|
|
||||||
GLuint front_buffer_texture = 0;
|
|
||||||
// Current back buffer, being updated by the CP.
|
|
||||||
GLuint back_buffer_texture = 0;
|
|
||||||
// Whether the back buffer is dirty and a swap is pending.
|
|
||||||
bool pending = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class SwapMode {
|
|
||||||
kNormal,
|
|
||||||
kIgnored,
|
|
||||||
};
|
|
||||||
|
|
||||||
class CommandProcessor {
|
|
||||||
public:
|
public:
|
||||||
explicit CommandProcessor(GL4GraphicsSystem* graphics_system);
|
GL4CommandProcessor(GL4GraphicsSystem* graphics_system,
|
||||||
~CommandProcessor();
|
kernel::KernelState* kernel_state);
|
||||||
|
~GL4CommandProcessor() override;
|
||||||
|
|
||||||
uint32_t counter() const { return counter_; }
|
void ClearCaches() override;
|
||||||
void increment_counter() { counter_++; }
|
|
||||||
|
|
||||||
bool Initialize(std::unique_ptr<xe::ui::GraphicsContext> context);
|
|
||||||
void Shutdown();
|
|
||||||
void CallInThread(std::function<void()> fn);
|
|
||||||
|
|
||||||
void ClearCaches();
|
|
||||||
|
|
||||||
SwapState& swap_state() { return swap_state_; }
|
|
||||||
void set_swap_mode(SwapMode swap_mode) { swap_mode_ = swap_mode; }
|
|
||||||
void IssueSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
|
|
||||||
uint32_t frontbuffer_height);
|
|
||||||
|
|
||||||
void set_swap_request_handler(std::function<void()> fn) {
|
|
||||||
swap_request_handler_ = fn;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RequestFrameTrace(const std::wstring& root_path);
|
|
||||||
void BeginTracing(const std::wstring& root_path);
|
|
||||||
void EndTracing();
|
|
||||||
|
|
||||||
void InitializeRingBuffer(uint32_t ptr, uint32_t page_count);
|
|
||||||
void EnableReadPointerWriteBack(uint32_t ptr, uint32_t block_size);
|
|
||||||
|
|
||||||
void UpdateWritePointer(uint32_t value);
|
|
||||||
|
|
||||||
void ExecutePacket(uint32_t ptr, uint32_t count);
|
|
||||||
|
|
||||||
// HACK: for debugging; would be good to have this in a base type.
|
// HACK: for debugging; would be good to have this in a base type.
|
||||||
TextureCache* texture_cache() { return &texture_cache_; }
|
TextureCache* texture_cache() { return &texture_cache_; }
|
||||||
|
@ -111,8 +60,6 @@ class CommandProcessor {
|
||||||
xenos::DepthRenderTargetFormat format);
|
xenos::DepthRenderTargetFormat format);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class RingbufferReader;
|
|
||||||
|
|
||||||
enum class UpdateStatus {
|
enum class UpdateStatus {
|
||||||
kCompatible,
|
kCompatible,
|
||||||
kMismatch,
|
kMismatch,
|
||||||
|
@ -153,69 +100,22 @@ class CommandProcessor {
|
||||||
} handles;
|
} handles;
|
||||||
};
|
};
|
||||||
|
|
||||||
void WorkerThreadMain();
|
bool SetupContext() override;
|
||||||
bool SetupGL();
|
void ShutdownContext() override;
|
||||||
void ShutdownGL();
|
|
||||||
GLuint CreateGeometryProgram(const std::string& source);
|
GLuint CreateGeometryProgram(const std::string& source);
|
||||||
|
|
||||||
void WriteRegister(uint32_t index, uint32_t value);
|
void MakeCoherent() override;
|
||||||
void MakeCoherent();
|
void PrepareForWait() override;
|
||||||
void PrepareForWait();
|
void ReturnFromWait() override;
|
||||||
void ReturnFromWait();
|
|
||||||
|
|
||||||
void ExecutePrimaryBuffer(uint32_t start_index, uint32_t end_index);
|
void PerformSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
|
||||||
void ExecuteIndirectBuffer(uint32_t ptr, uint32_t length);
|
uint32_t frontbuffer_height) override;
|
||||||
bool ExecutePacket(RingbufferReader* reader);
|
|
||||||
bool ExecutePacketType0(RingbufferReader* reader, uint32_t packet);
|
|
||||||
bool ExecutePacketType1(RingbufferReader* reader, uint32_t packet);
|
|
||||||
bool ExecutePacketType2(RingbufferReader* reader, uint32_t packet);
|
|
||||||
bool ExecutePacketType3(RingbufferReader* reader, uint32_t packet);
|
|
||||||
bool ExecutePacketType3_ME_INIT(RingbufferReader* reader, uint32_t packet,
|
|
||||||
uint32_t count);
|
|
||||||
bool ExecutePacketType3_NOP(RingbufferReader* reader, uint32_t packet,
|
|
||||||
uint32_t count);
|
|
||||||
bool ExecutePacketType3_INTERRUPT(RingbufferReader* reader, uint32_t packet,
|
|
||||||
uint32_t count);
|
|
||||||
bool ExecutePacketType3_XE_SWAP(RingbufferReader* reader, uint32_t packet,
|
|
||||||
uint32_t count);
|
|
||||||
bool ExecutePacketType3_INDIRECT_BUFFER(RingbufferReader* reader,
|
|
||||||
uint32_t packet, uint32_t count);
|
|
||||||
bool ExecutePacketType3_WAIT_REG_MEM(RingbufferReader* reader,
|
|
||||||
uint32_t packet, uint32_t count);
|
|
||||||
bool ExecutePacketType3_REG_RMW(RingbufferReader* reader, uint32_t packet,
|
|
||||||
uint32_t count);
|
|
||||||
bool ExecutePacketType3_COND_WRITE(RingbufferReader* reader, uint32_t packet,
|
|
||||||
uint32_t count);
|
|
||||||
bool ExecutePacketType3_EVENT_WRITE(RingbufferReader* reader, uint32_t packet,
|
|
||||||
uint32_t count);
|
|
||||||
bool ExecutePacketType3_EVENT_WRITE_SHD(RingbufferReader* reader,
|
|
||||||
uint32_t packet, uint32_t count);
|
|
||||||
bool ExecutePacketType3_EVENT_WRITE_EXT(RingbufferReader* reader,
|
|
||||||
uint32_t packet, uint32_t count);
|
|
||||||
bool ExecutePacketType3_DRAW_INDX(RingbufferReader* reader, uint32_t packet,
|
|
||||||
uint32_t count);
|
|
||||||
bool ExecutePacketType3_DRAW_INDX_2(RingbufferReader* reader, uint32_t packet,
|
|
||||||
uint32_t count);
|
|
||||||
bool ExecutePacketType3_SET_CONSTANT(RingbufferReader* reader,
|
|
||||||
uint32_t packet, uint32_t count);
|
|
||||||
bool ExecutePacketType3_SET_CONSTANT2(RingbufferReader* reader,
|
|
||||||
uint32_t packet, uint32_t count);
|
|
||||||
bool ExecutePacketType3_LOAD_ALU_CONSTANT(RingbufferReader* reader,
|
|
||||||
uint32_t packet, uint32_t count);
|
|
||||||
bool ExecutePacketType3_SET_SHADER_CONSTANTS(RingbufferReader* reader,
|
|
||||||
uint32_t packet, uint32_t count);
|
|
||||||
bool ExecutePacketType3_IM_LOAD(RingbufferReader* reader, uint32_t packet,
|
|
||||||
uint32_t count);
|
|
||||||
bool ExecutePacketType3_IM_LOAD_IMMEDIATE(RingbufferReader* reader,
|
|
||||||
|
|
||||||
uint32_t packet, uint32_t count);
|
|
||||||
bool ExecutePacketType3_INVALIDATE_STATE(RingbufferReader* reader,
|
|
||||||
uint32_t packet, uint32_t count);
|
|
||||||
|
|
||||||
bool LoadShader(ShaderType shader_type, uint32_t guest_address,
|
bool LoadShader(ShaderType shader_type, uint32_t guest_address,
|
||||||
const uint32_t* host_address, uint32_t dword_count);
|
const uint32_t* host_address, uint32_t dword_count) override;
|
||||||
|
|
||||||
bool IssueDraw();
|
bool IssueDraw(PrimitiveType prim_type, uint32_t index_count,
|
||||||
|
IndexBufferInfo* index_buffer_info) override;
|
||||||
UpdateStatus UpdateShaders(PrimitiveType prim_type);
|
UpdateStatus UpdateShaders(PrimitiveType prim_type);
|
||||||
UpdateStatus UpdateRenderTargets();
|
UpdateStatus UpdateRenderTargets();
|
||||||
UpdateStatus UpdateState();
|
UpdateStatus UpdateState();
|
||||||
|
@ -223,77 +123,32 @@ class CommandProcessor {
|
||||||
UpdateStatus UpdateRasterizerState();
|
UpdateStatus UpdateRasterizerState();
|
||||||
UpdateStatus UpdateBlendState();
|
UpdateStatus UpdateBlendState();
|
||||||
UpdateStatus UpdateDepthStencilState();
|
UpdateStatus UpdateDepthStencilState();
|
||||||
UpdateStatus PopulateIndexBuffer();
|
UpdateStatus PopulateIndexBuffer(IndexBufferInfo* index_buffer_info);
|
||||||
UpdateStatus PopulateVertexBuffers();
|
UpdateStatus PopulateVertexBuffers();
|
||||||
UpdateStatus PopulateSamplers();
|
UpdateStatus PopulateSamplers();
|
||||||
UpdateStatus PopulateSampler(const Shader::SamplerDesc& desc);
|
UpdateStatus PopulateSampler(const Shader::SamplerDesc& desc);
|
||||||
bool IssueCopy();
|
bool IssueCopy() override;
|
||||||
|
|
||||||
CachedFramebuffer* GetFramebuffer(GLuint color_targets[4],
|
CachedFramebuffer* GetFramebuffer(GLuint color_targets[4],
|
||||||
GLuint depth_target);
|
GLuint depth_target);
|
||||||
|
|
||||||
Memory* memory_;
|
|
||||||
GL4GraphicsSystem* graphics_system_;
|
|
||||||
RegisterFile* register_file_;
|
|
||||||
|
|
||||||
TraceWriter trace_writer_;
|
|
||||||
enum class TraceState {
|
|
||||||
kDisabled,
|
|
||||||
kStreaming,
|
|
||||||
kSingleFrame,
|
|
||||||
};
|
|
||||||
TraceState trace_state_;
|
|
||||||
std::wstring trace_frame_path_;
|
|
||||||
|
|
||||||
std::atomic<bool> worker_running_;
|
|
||||||
kernel::object_ref<kernel::XHostThread> worker_thread_;
|
|
||||||
|
|
||||||
std::unique_ptr<xe::ui::GraphicsContext> context_;
|
|
||||||
SwapMode swap_mode_;
|
|
||||||
SwapState swap_state_;
|
|
||||||
std::function<void()> swap_request_handler_;
|
|
||||||
std::queue<std::function<void()>> pending_fns_;
|
|
||||||
|
|
||||||
uint32_t counter_;
|
|
||||||
|
|
||||||
uint32_t primary_buffer_ptr_;
|
|
||||||
uint32_t primary_buffer_size_;
|
|
||||||
|
|
||||||
uint32_t read_ptr_index_;
|
|
||||||
uint32_t read_ptr_update_freq_;
|
|
||||||
uint32_t read_ptr_writeback_ptr_;
|
|
||||||
|
|
||||||
std::unique_ptr<xe::threading::Event> write_ptr_index_event_;
|
|
||||||
std::atomic<uint32_t> write_ptr_index_;
|
|
||||||
|
|
||||||
uint64_t bin_select_;
|
|
||||||
uint64_t bin_mask_;
|
|
||||||
|
|
||||||
GL4ShaderTranslator shader_translator_;
|
GL4ShaderTranslator shader_translator_;
|
||||||
std::vector<std::unique_ptr<GL4Shader>> all_shaders_;
|
std::vector<std::unique_ptr<GL4Shader>> all_shaders_;
|
||||||
std::unordered_map<uint64_t, GL4Shader*> shader_cache_;
|
std::unordered_map<uint64_t, GL4Shader*> shader_cache_;
|
||||||
GL4Shader* active_vertex_shader_;
|
GL4Shader* active_vertex_shader_ = nullptr;
|
||||||
GL4Shader* active_pixel_shader_;
|
GL4Shader* active_pixel_shader_ = nullptr;
|
||||||
CachedFramebuffer* active_framebuffer_;
|
CachedFramebuffer* active_framebuffer_ = nullptr;
|
||||||
GLuint last_framebuffer_texture_;
|
GLuint last_framebuffer_texture_ = 0;
|
||||||
|
|
||||||
std::vector<CachedFramebuffer> cached_framebuffers_;
|
std::vector<CachedFramebuffer> cached_framebuffers_;
|
||||||
std::vector<CachedColorRenderTarget> cached_color_render_targets_;
|
std::vector<CachedColorRenderTarget> cached_color_render_targets_;
|
||||||
std::vector<CachedDepthRenderTarget> cached_depth_render_targets_;
|
std::vector<CachedDepthRenderTarget> cached_depth_render_targets_;
|
||||||
std::vector<std::unique_ptr<CachedPipeline>> all_pipelines_;
|
std::vector<std::unique_ptr<CachedPipeline>> all_pipelines_;
|
||||||
std::unordered_map<uint64_t, CachedPipeline*> cached_pipelines_;
|
std::unordered_map<uint64_t, CachedPipeline*> cached_pipelines_;
|
||||||
GLuint point_list_geometry_program_;
|
GLuint point_list_geometry_program_ = 0;
|
||||||
GLuint rect_list_geometry_program_;
|
GLuint rect_list_geometry_program_ = 0;
|
||||||
GLuint quad_list_geometry_program_;
|
GLuint quad_list_geometry_program_ = 0;
|
||||||
GLuint line_quad_list_geometry_program_;
|
GLuint line_quad_list_geometry_program_ = 0;
|
||||||
struct {
|
|
||||||
xenos::IndexFormat format;
|
|
||||||
xenos::Endian endianness;
|
|
||||||
uint32_t count;
|
|
||||||
uint32_t guest_base;
|
|
||||||
size_t length;
|
|
||||||
} index_buffer_info_;
|
|
||||||
uint32_t draw_index_count_;
|
|
||||||
|
|
||||||
TextureCache texture_cache_;
|
TextureCache texture_cache_;
|
||||||
|
|
|
@ -12,41 +12,20 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#include "xenia/base/clock.h"
|
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/profiling.h"
|
#include "xenia/base/profiling.h"
|
||||||
#include "xenia/base/threading.h"
|
|
||||||
#include "xenia/cpu/processor.h"
|
#include "xenia/cpu/processor.h"
|
||||||
#include "xenia/emulator.h"
|
#include "xenia/gpu/gl4/gl4_command_processor.h"
|
||||||
#include "xenia/gpu/gl4/gl4_gpu_flags.h"
|
#include "xenia/gpu/gl4/gl4_gpu_flags.h"
|
||||||
#include "xenia/gpu/gpu_flags.h"
|
#include "xenia/gpu/gpu_flags.h"
|
||||||
#include "xenia/gpu/tracing.h"
|
|
||||||
#include "xenia/ui/window.h"
|
#include "xenia/ui/window.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace gpu {
|
namespace gpu {
|
||||||
namespace gl4 {
|
namespace gl4 {
|
||||||
|
|
||||||
void InitializeIfNeeded();
|
std::unique_ptr<GraphicsSystem> Create() {
|
||||||
void CleanupOnShutdown();
|
return std::make_unique<GL4GraphicsSystem>();
|
||||||
|
|
||||||
void InitializeIfNeeded() {
|
|
||||||
static bool has_initialized = false;
|
|
||||||
if (has_initialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
has_initialized = true;
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
atexit(CleanupOnShutdown);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CleanupOnShutdown() {}
|
|
||||||
|
|
||||||
std::unique_ptr<GraphicsSystem> Create(Emulator* emulator) {
|
|
||||||
InitializeIfNeeded();
|
|
||||||
return std::make_unique<GL4GraphicsSystem>(emulator);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<ui::GraphicsContext> GL4GraphicsSystem::CreateContext(
|
std::unique_ptr<ui::GraphicsContext> GL4GraphicsSystem::CreateContext(
|
||||||
|
@ -57,15 +36,14 @@ std::unique_ptr<ui::GraphicsContext> GL4GraphicsSystem::CreateContext(
|
||||||
return xe::ui::gl::GLContext::Create(target_window);
|
return xe::ui::gl::GLContext::Create(target_window);
|
||||||
}
|
}
|
||||||
|
|
||||||
GL4GraphicsSystem::GL4GraphicsSystem(Emulator* emulator)
|
GL4GraphicsSystem::GL4GraphicsSystem() : GraphicsSystem() {}
|
||||||
: GraphicsSystem(emulator), worker_running_(false) {}
|
|
||||||
|
|
||||||
GL4GraphicsSystem::~GL4GraphicsSystem() = default;
|
GL4GraphicsSystem::~GL4GraphicsSystem() = default;
|
||||||
|
|
||||||
X_STATUS GL4GraphicsSystem::Setup(cpu::Processor* processor,
|
X_STATUS GL4GraphicsSystem::Setup(cpu::Processor* processor,
|
||||||
ui::Loop* target_loop,
|
kernel::KernelState* kernel_state,
|
||||||
ui::Window* target_window) {
|
ui::Window* target_window) {
|
||||||
auto result = GraphicsSystem::Setup(processor, target_loop, target_window);
|
auto result = GraphicsSystem::Setup(processor, kernel_state, target_window);
|
||||||
if (result) {
|
if (result) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -73,224 +51,14 @@ X_STATUS GL4GraphicsSystem::Setup(cpu::Processor* processor,
|
||||||
display_context_ =
|
display_context_ =
|
||||||
reinterpret_cast<xe::ui::gl::GLContext*>(target_window->context());
|
reinterpret_cast<xe::ui::gl::GLContext*>(target_window->context());
|
||||||
|
|
||||||
// Watch for paint requests to do our swap.
|
|
||||||
target_window->on_painting.AddListener(
|
|
||||||
[this](xe::ui::UIEvent* e) { Swap(e); });
|
|
||||||
|
|
||||||
// Create rendering control.
|
|
||||||
// This must happen on the UI thread.
|
|
||||||
std::unique_ptr<xe::ui::GraphicsContext> processor_context;
|
|
||||||
target_loop_->PostSynchronous([&]() {
|
|
||||||
// Setup the GL context the command processor will do all its drawing in.
|
|
||||||
// It's shared with the display context so that we can resolve framebuffers
|
|
||||||
// from it.
|
|
||||||
processor_context = display_context_->CreateShared();
|
|
||||||
processor_context->ClearCurrent();
|
|
||||||
});
|
|
||||||
if (!processor_context) {
|
|
||||||
xe::FatalError(
|
|
||||||
"Unable to initialize GL context. Xenia requires OpenGL 4.5. Ensure "
|
|
||||||
"you have the latest drivers for your GPU and that it supports OpenGL "
|
|
||||||
"4.5. See http://xenia.jp/faq/ for more information.");
|
|
||||||
return X_STATUS_UNSUCCESSFUL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create command processor. This will spin up a thread to process all
|
|
||||||
// incoming ringbuffer packets.
|
|
||||||
command_processor_ = std::make_unique<CommandProcessor>(this);
|
|
||||||
if (!command_processor_->Initialize(std::move(processor_context))) {
|
|
||||||
XELOGE("Unable to initialize command processor");
|
|
||||||
return X_STATUS_UNSUCCESSFUL;
|
|
||||||
}
|
|
||||||
command_processor_->set_swap_request_handler(
|
|
||||||
[this]() { target_window_->Invalidate(); });
|
|
||||||
|
|
||||||
// Let the processor know we want register access callbacks.
|
|
||||||
memory_->AddVirtualMappedRange(
|
|
||||||
0x7FC80000, 0xFFFF0000, 0x0000FFFF, this,
|
|
||||||
reinterpret_cast<cpu::MMIOReadCallback>(MMIOReadRegisterThunk),
|
|
||||||
reinterpret_cast<cpu::MMIOWriteCallback>(MMIOWriteRegisterThunk));
|
|
||||||
|
|
||||||
// 60hz vsync timer.
|
|
||||||
worker_running_ = true;
|
|
||||||
worker_thread_ =
|
|
||||||
kernel::object_ref<kernel::XHostThread>(new kernel::XHostThread(
|
|
||||||
emulator()->kernel_state(), 128 * 1024, 0, [this]() {
|
|
||||||
uint64_t vsync_duration = FLAGS_vsync ? 16 : 1;
|
|
||||||
uint64_t last_frame_time = Clock::QueryGuestTickCount();
|
|
||||||
while (worker_running_) {
|
|
||||||
uint64_t current_time = Clock::QueryGuestTickCount();
|
|
||||||
uint64_t elapsed = (current_time - last_frame_time) /
|
|
||||||
(Clock::guest_tick_frequency() / 1000);
|
|
||||||
if (elapsed >= vsync_duration) {
|
|
||||||
MarkVblank();
|
|
||||||
last_frame_time = current_time;
|
|
||||||
}
|
|
||||||
xe::threading::Sleep(std::chrono::milliseconds(1));
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}));
|
|
||||||
// As we run vblank interrupts the debugger must be able to suspend us.
|
|
||||||
worker_thread_->set_can_debugger_suspend(true);
|
|
||||||
worker_thread_->set_name("GL4 Vsync");
|
|
||||||
worker_thread_->Create();
|
|
||||||
|
|
||||||
if (FLAGS_trace_gpu_stream) {
|
|
||||||
BeginTracing();
|
|
||||||
}
|
|
||||||
|
|
||||||
return X_STATUS_SUCCESS;
|
return X_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GL4GraphicsSystem::Shutdown() {
|
void GL4GraphicsSystem::Shutdown() { GraphicsSystem::Shutdown(); }
|
||||||
EndTracing();
|
|
||||||
|
|
||||||
worker_running_ = false;
|
std::unique_ptr<CommandProcessor> GL4GraphicsSystem::CreateCommandProcessor() {
|
||||||
worker_thread_->Wait(0, 0, 0, nullptr);
|
return std::unique_ptr<CommandProcessor>(
|
||||||
worker_thread_.reset();
|
new GL4CommandProcessor(this, kernel_state_));
|
||||||
|
|
||||||
command_processor_->Shutdown();
|
|
||||||
|
|
||||||
// TODO(benvanik): remove mapped range.
|
|
||||||
|
|
||||||
command_processor_.reset();
|
|
||||||
|
|
||||||
GraphicsSystem::Shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GL4GraphicsSystem::InitializeRingBuffer(uint32_t ptr,
|
|
||||||
uint32_t page_count) {
|
|
||||||
command_processor_->InitializeRingBuffer(ptr, page_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GL4GraphicsSystem::EnableReadPointerWriteBack(uint32_t ptr,
|
|
||||||
uint32_t block_size) {
|
|
||||||
command_processor_->EnableReadPointerWriteBack(ptr, block_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GL4GraphicsSystem::RequestFrameTrace() {
|
|
||||||
command_processor_->RequestFrameTrace(xe::to_wstring(FLAGS_trace_gpu_prefix));
|
|
||||||
}
|
|
||||||
|
|
||||||
void GL4GraphicsSystem::BeginTracing() {
|
|
||||||
command_processor_->BeginTracing(xe::to_wstring(FLAGS_trace_gpu_prefix));
|
|
||||||
}
|
|
||||||
|
|
||||||
void GL4GraphicsSystem::EndTracing() { command_processor_->EndTracing(); }
|
|
||||||
|
|
||||||
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<TraceCommandType>(xe::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);
|
|
||||||
trace_ptr += sizeof(*cmd);
|
|
||||||
std::memcpy(memory()->TranslatePhysical(cmd->base_ptr), trace_ptr,
|
|
||||||
cmd->count * 4);
|
|
||||||
trace_ptr += 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) {
|
|
||||||
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<const MemoryReadCommand*>(trace_ptr);
|
|
||||||
trace_ptr += sizeof(*cmd);
|
|
||||||
std::memcpy(memory()->TranslatePhysical(cmd->base_ptr), trace_ptr,
|
|
||||||
cmd->length);
|
|
||||||
trace_ptr += cmd->length;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case TraceCommandType::kMemoryWrite: {
|
|
||||||
auto cmd = reinterpret_cast<const MemoryWriteCommand*>(trace_ptr);
|
|
||||||
trace_ptr += sizeof(*cmd);
|
|
||||||
// ?
|
|
||||||
trace_ptr += 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: {
|
|
||||||
if (playback_mode == TracePlaybackMode::kBreakOnSwap) {
|
|
||||||
pending_break = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
command_processor_->set_swap_mode(SwapMode::kNormal);
|
|
||||||
command_processor_->IssueSwap(0, 1280, 720);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void GL4GraphicsSystem::ClearCaches() {
|
|
||||||
command_processor_->CallInThread(
|
|
||||||
[&]() { command_processor_->ClearCaches(); });
|
|
||||||
}
|
|
||||||
|
|
||||||
void GL4GraphicsSystem::MarkVblank() {
|
|
||||||
SCOPE_profile_cpu_f("gpu");
|
|
||||||
|
|
||||||
// Increment vblank counter (so the game sees us making progress).
|
|
||||||
command_processor_->increment_counter();
|
|
||||||
|
|
||||||
// TODO(benvanik): we shouldn't need to do the dispatch here, but there's
|
|
||||||
// something wrong and the CP will block waiting for code that
|
|
||||||
// needs to be run in the interrupt.
|
|
||||||
DispatchInterruptCallback(0, 2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GL4GraphicsSystem::Swap(xe::ui::UIEvent* e) {
|
void GL4GraphicsSystem::Swap(xe::ui::UIEvent* e) {
|
||||||
|
@ -315,51 +83,12 @@ void GL4GraphicsSystem::Swap(xe::ui::UIEvent* e) {
|
||||||
|
|
||||||
// Blit the frontbuffer.
|
// Blit the frontbuffer.
|
||||||
display_context_->blitter()->BlitTexture2D(
|
display_context_->blitter()->BlitTexture2D(
|
||||||
swap_state.front_buffer_texture,
|
static_cast<GLuint>(swap_state.front_buffer_texture),
|
||||||
Rect2D(0, 0, swap_state.width, swap_state.height),
|
Rect2D(0, 0, swap_state.width, swap_state.height),
|
||||||
Rect2D(0, 0, target_window_->width(), target_window_->height()),
|
Rect2D(0, 0, target_window_->width(), target_window_->height()),
|
||||||
GL_LINEAR);
|
GL_LINEAR);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t GL4GraphicsSystem::ReadRegister(uint32_t addr) {
|
|
||||||
uint32_t r = addr & 0xFFFF;
|
|
||||||
|
|
||||||
switch (r) {
|
|
||||||
case 0x3C00: // ?
|
|
||||||
return 0x08100748;
|
|
||||||
case 0x3C04: // ?
|
|
||||||
return 0x0000200E;
|
|
||||||
case 0x6530: // Scanline?
|
|
||||||
return 0x000002D0;
|
|
||||||
case 0x6544: // ? vblank pending?
|
|
||||||
return 1;
|
|
||||||
case 0x6584: // Screen res - 1280x720
|
|
||||||
return 0x050002D0;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_true(r < RegisterFile::kRegisterCount);
|
|
||||||
return register_file_.values[r].u32;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GL4GraphicsSystem::WriteRegister(uint32_t addr, uint32_t value) {
|
|
||||||
uint32_t r = addr & 0xFFFF;
|
|
||||||
|
|
||||||
switch (r) {
|
|
||||||
case 0x0714: // CP_RB_WPTR
|
|
||||||
command_processor_->UpdateWritePointer(value);
|
|
||||||
break;
|
|
||||||
case 0x6110: // ? swap related?
|
|
||||||
XELOGW("Unimplemented GPU register %.4X write: %.8X", r, value);
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
XELOGW("Unknown GPU register %.4X write: %.8X", r, value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_true(r < RegisterFile::kRegisterCount);
|
|
||||||
register_file_.values[r].u32 = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace gl4
|
} // namespace gl4
|
||||||
} // namespace gpu
|
} // namespace gpu
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
|
@ -12,10 +12,7 @@
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "xenia/gpu/gl4/command_processor.h"
|
|
||||||
#include "xenia/gpu/graphics_system.h"
|
#include "xenia/gpu/graphics_system.h"
|
||||||
#include "xenia/gpu/register_file.h"
|
|
||||||
#include "xenia/kernel/xthread.h"
|
|
||||||
#include "xenia/ui/gl/gl_context.h"
|
#include "xenia/ui/gl/gl_context.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
@ -24,53 +21,22 @@ namespace gl4 {
|
||||||
|
|
||||||
class GL4GraphicsSystem : public GraphicsSystem {
|
class GL4GraphicsSystem : public GraphicsSystem {
|
||||||
public:
|
public:
|
||||||
explicit GL4GraphicsSystem(Emulator* emulator);
|
GL4GraphicsSystem();
|
||||||
~GL4GraphicsSystem() override;
|
~GL4GraphicsSystem() override;
|
||||||
|
|
||||||
std::unique_ptr<ui::GraphicsContext> CreateContext(
|
std::unique_ptr<ui::GraphicsContext> CreateContext(
|
||||||
ui::Window* target_window) override;
|
ui::Window* target_window) override;
|
||||||
|
|
||||||
X_STATUS Setup(cpu::Processor* processor, ui::Loop* target_loop,
|
X_STATUS Setup(cpu::Processor* processor, kernel::KernelState* kernel_state,
|
||||||
ui::Window* target_window) override;
|
ui::Window* target_window) override;
|
||||||
void Shutdown() override;
|
void Shutdown() override;
|
||||||
|
|
||||||
RegisterFile* register_file() { return ®ister_file_; }
|
|
||||||
CommandProcessor* command_processor() const {
|
|
||||||
return command_processor_.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
void InitializeRingBuffer(uint32_t ptr, uint32_t page_count) override;
|
|
||||||
void EnableReadPointerWriteBack(uint32_t ptr, uint32_t block_size) override;
|
|
||||||
|
|
||||||
void RequestFrameTrace() override;
|
|
||||||
void BeginTracing() override;
|
|
||||||
void EndTracing() override;
|
|
||||||
void PlayTrace(const uint8_t* trace_data, size_t trace_size,
|
|
||||||
TracePlaybackMode playback_mode) override;
|
|
||||||
void ClearCaches() override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void MarkVblank();
|
std::unique_ptr<CommandProcessor> CreateCommandProcessor() override;
|
||||||
void Swap(xe::ui::UIEvent* e);
|
|
||||||
uint32_t ReadRegister(uint32_t addr);
|
|
||||||
void WriteRegister(uint32_t addr, uint32_t value);
|
|
||||||
|
|
||||||
static uint32_t MMIOReadRegisterThunk(void* ppc_context,
|
void Swap(xe::ui::UIEvent* e) override;
|
||||||
GL4GraphicsSystem* gs, uint32_t addr) {
|
|
||||||
return gs->ReadRegister(addr);
|
|
||||||
}
|
|
||||||
static void MMIOWriteRegisterThunk(void* ppc_context, GL4GraphicsSystem* gs,
|
|
||||||
uint32_t addr, uint32_t value) {
|
|
||||||
gs->WriteRegister(addr, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
RegisterFile register_file_;
|
|
||||||
std::unique_ptr<CommandProcessor> command_processor_;
|
|
||||||
|
|
||||||
xe::ui::gl::GLContext* display_context_ = nullptr;
|
xe::ui::gl::GLContext* display_context_ = nullptr;
|
||||||
|
|
||||||
std::atomic<bool> worker_running_;
|
|
||||||
kernel::object_ref<kernel::XHostThread> worker_thread_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace gl4
|
} // namespace gl4
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <gflags/gflags.h>
|
#include <gflags/gflags.h>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#include "third_party/imgui/imgui.h"
|
#include "third_party/imgui/imgui.h"
|
||||||
|
@ -19,16 +20,21 @@
|
||||||
#include "xenia/base/platform_win.h"
|
#include "xenia/base/platform_win.h"
|
||||||
#include "xenia/base/profiling.h"
|
#include "xenia/base/profiling.h"
|
||||||
#include "xenia/emulator.h"
|
#include "xenia/emulator.h"
|
||||||
|
#include "xenia/gpu/command_processor.h"
|
||||||
#include "xenia/gpu/graphics_system.h"
|
#include "xenia/gpu/graphics_system.h"
|
||||||
|
#include "xenia/gpu/packet_disassembler.h"
|
||||||
#include "xenia/gpu/register_file.h"
|
#include "xenia/gpu/register_file.h"
|
||||||
#include "xenia/gpu/tracing.h"
|
#include "xenia/gpu/sampler_info.h"
|
||||||
|
#include "xenia/gpu/texture_info.h"
|
||||||
|
#include "xenia/gpu/trace_player.h"
|
||||||
|
#include "xenia/gpu/trace_protocol.h"
|
||||||
#include "xenia/gpu/xenos.h"
|
#include "xenia/gpu/xenos.h"
|
||||||
#include "xenia/ui/gl/gl_context.h"
|
#include "xenia/ui/gl/gl_context.h"
|
||||||
#include "xenia/ui/imgui_drawer.h"
|
#include "xenia/ui/imgui_drawer.h"
|
||||||
#include "xenia/ui/window.h"
|
#include "xenia/ui/window.h"
|
||||||
|
|
||||||
// HACK: until we have another impl, we just use gl4 directly.
|
// HACK: until we have another impl, we just use gl4 directly.
|
||||||
#include "xenia/gpu/gl4/command_processor.h"
|
#include "xenia/gpu/gl4/gl4_command_processor.h"
|
||||||
#include "xenia/gpu/gl4/gl4_graphics_system.h"
|
#include "xenia/gpu/gl4/gl4_graphics_system.h"
|
||||||
#include "xenia/gpu/gl4/gl4_shader.h"
|
#include "xenia/gpu/gl4/gl4_shader.h"
|
||||||
|
|
||||||
|
@ -37,810 +43,7 @@ DEFINE_string(target_trace_file, "", "Specifies the trace file to load.");
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace gpu {
|
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 = xe::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 = xe::load_and_swap<uint32_t>(ptr);
|
|
||||||
uint32_t reg_data_2 = xe::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;
|
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 = xe::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 = xe::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 = xe::load_and_swap<uint32_t>(ptr + 0);
|
|
||||||
uint32_t list_length = xe::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 = xe::load_and_swap<uint32_t>(ptr + 0);
|
|
||||||
uint32_t poll_reg_addr = xe::load_and_swap<uint32_t>(ptr + 4);
|
|
||||||
uint32_t ref = xe::load_and_swap<uint32_t>(ptr + 8);
|
|
||||||
uint32_t mask = xe::load_and_swap<uint32_t>(ptr + 12);
|
|
||||||
uint32_t wait = xe::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 = xe::load_and_swap<uint32_t>(ptr + 0);
|
|
||||||
uint32_t and_mask = xe::load_and_swap<uint32_t>(ptr + 4);
|
|
||||||
uint32_t or_mask = xe::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 = xe::load_and_swap<uint32_t>(ptr + 0);
|
|
||||||
uint32_t poll_reg_addr = xe::load_and_swap<uint32_t>(ptr + 4);
|
|
||||||
uint32_t ref = xe::load_and_swap<uint32_t>(ptr + 8);
|
|
||||||
uint32_t mask = xe::load_and_swap<uint32_t>(ptr + 12);
|
|
||||||
uint32_t write_reg_addr = xe::load_and_swap<uint32_t>(ptr + 16);
|
|
||||||
uint32_t write_data = xe::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 = xe::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 = xe::load_and_swap<uint32_t>(ptr + 0);
|
|
||||||
uint32_t address = xe::load_and_swap<uint32_t>(ptr + 4);
|
|
||||||
uint32_t value = xe::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 = xe::load_and_swap<uint32_t>(ptr + 0);
|
|
||||||
uint32_t unk1 = xe::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 = xe::load_and_swap<uint32_t>(ptr + 0);
|
|
||||||
uint32_t dword1 = xe::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 = xe::load_and_swap<uint32_t>(ptr + 8);
|
|
||||||
uint32_t index_size = xe::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 = xe::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 = xe::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 = xe::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 = xe::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 = xe::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 = xe::load_and_swap<uint32_t>(ptr + 0);
|
|
||||||
address &= 0x3FFFFFFF;
|
|
||||||
uint32_t offset_type = xe::load_and_swap<uint32_t>(ptr + 4);
|
|
||||||
uint32_t index = offset_type & 0x7FF;
|
|
||||||
uint32_t size_dwords = xe::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, ?
|
|
||||||
// xe::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 = xe::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 = xe::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 = xe::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 = xe::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 = xe::load_and_swap<uint32_t>(ptr + 0);
|
|
||||||
uint32_t dword1 = xe::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 = xe::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 = xe::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 = xe::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 = xe::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 = xe::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) {
|
|
||||||
const uint32_t packet = xe::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 = xe::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_ = MappedMemory::Open(path, 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>(xe::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<MappedMemory> mmap_;
|
|
||||||
const uint8_t* trace_data_;
|
|
||||||
size_t trace_size_;
|
|
||||||
std::vector<Frame> frames_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class TracePlayer : public TraceReader {
|
|
||||||
public:
|
|
||||||
TracePlayer(xe::ui::Loop* loop, GraphicsSystem* graphics_system)
|
|
||||||
: loop_(loop),
|
|
||||||
graphics_system_(graphics_system),
|
|
||||||
current_frame_index_(0),
|
|
||||||
current_command_index_(-1) {
|
|
||||||
// Need to allocate all of physical memory so that we can write to it
|
|
||||||
// during playback.
|
|
||||||
graphics_system_->memory()
|
|
||||||
->LookupHeapByType(true, 4096)
|
|
||||||
->AllocFixed(0, 0x1FFFFFFF, 4096,
|
|
||||||
kMemoryAllocationReserve | kMemoryAllocationCommit,
|
|
||||||
kMemoryProtectRead | kMemoryProtectWrite);
|
|
||||||
}
|
|
||||||
~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:
|
|
||||||
xe::ui::Loop* loop_;
|
|
||||||
GraphicsSystem* graphics_system_;
|
|
||||||
int current_frame_index_;
|
|
||||||
int current_command_index_;
|
|
||||||
};
|
|
||||||
|
|
||||||
void DrawControllerUI(xe::ui::Window* window, TracePlayer& player,
|
void DrawControllerUI(xe::ui::Window* window, TracePlayer& player,
|
||||||
Memory* memory) {
|
Memory* memory) {
|
||||||
|
@ -1126,7 +329,7 @@ void DrawFailedTextureInfo(const Shader::SamplerDesc& desc,
|
||||||
}
|
}
|
||||||
void DrawTextureInfo(TracePlayer& player, const Shader::SamplerDesc& desc) {
|
void DrawTextureInfo(TracePlayer& player, const Shader::SamplerDesc& desc) {
|
||||||
auto gs = static_cast<gl4::GL4GraphicsSystem*>(player.graphics_system());
|
auto gs = static_cast<gl4::GL4GraphicsSystem*>(player.graphics_system());
|
||||||
auto cp = gs->command_processor();
|
auto cp = static_cast<gl4::GL4CommandProcessor*>(gs->command_processor());
|
||||||
auto& regs = *gs->register_file();
|
auto& regs = *gs->register_file();
|
||||||
|
|
||||||
int r = XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0 + desc.fetch_slot * 6;
|
int r = XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0 + desc.fetch_slot * 6;
|
||||||
|
@ -1394,7 +597,7 @@ static const char* kEndiannessNames[] = {
|
||||||
|
|
||||||
void DrawStateUI(xe::ui::Window* window, TracePlayer& player, Memory* memory) {
|
void DrawStateUI(xe::ui::Window* window, TracePlayer& player, Memory* memory) {
|
||||||
auto gs = static_cast<gl4::GL4GraphicsSystem*>(player.graphics_system());
|
auto gs = static_cast<gl4::GL4GraphicsSystem*>(player.graphics_system());
|
||||||
auto cp = gs->command_processor();
|
auto cp = static_cast<gl4::GL4CommandProcessor*>(gs->command_processor());
|
||||||
auto& regs = *gs->register_file();
|
auto& regs = *gs->register_file();
|
||||||
|
|
||||||
ImGui::SetNextWindowPos(ImVec2(float(window->width()) - 500 - 5, 30),
|
ImGui::SetNextWindowPos(ImVec2(float(window->width()) - 500 - 5, 30),
|
||||||
|
@ -2097,9 +1300,10 @@ void DrawPacketDisassemblerUI(xe::ui::Window* window, TracePlayer& player,
|
||||||
trace_ptr += sizeof(*cmd);
|
trace_ptr += sizeof(*cmd);
|
||||||
if (pending_packet) {
|
if (pending_packet) {
|
||||||
PacketInfo packet_info = {0};
|
PacketInfo packet_info = {0};
|
||||||
if (DisasmPacket(reinterpret_cast<const uint8_t*>(pending_packet) +
|
if (PacketDisassembler::DisasmPacket(
|
||||||
sizeof(PacketStartCommand),
|
reinterpret_cast<const uint8_t*>(pending_packet) +
|
||||||
&packet_info)) {
|
sizeof(PacketStartCommand),
|
||||||
|
&packet_info)) {
|
||||||
if (packet_info.predicated) {
|
if (packet_info.predicated) {
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text, kColorIgnored);
|
ImGui::PushStyleColor(ImGuiCol_Text, kColorIgnored);
|
||||||
}
|
}
|
|
@ -66,7 +66,7 @@ project("xenia-gpu-gl4-trace-viewer")
|
||||||
project_root.."/build_tools/third_party/gflags/src",
|
project_root.."/build_tools/third_party/gflags/src",
|
||||||
})
|
})
|
||||||
files({
|
files({
|
||||||
"trace_viewer_main.cc",
|
"gl4_trace_viewer_main.cc",
|
||||||
"../../base/main_"..platform_suffix..".cc",
|
"../../base/main_"..platform_suffix..".cc",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -9,27 +9,30 @@
|
||||||
|
|
||||||
#include "xenia/gpu/graphics_system.h"
|
#include "xenia/gpu/graphics_system.h"
|
||||||
|
|
||||||
|
#include "xenia/base/clock.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/math.h"
|
#include "xenia/base/math.h"
|
||||||
#include "xenia/cpu/processor.h"
|
#include "xenia/base/profiling.h"
|
||||||
|
#include "xenia/base/threading.h"
|
||||||
|
#include "xenia/gpu/command_processor.h"
|
||||||
#include "xenia/gpu/gpu_flags.h"
|
#include "xenia/gpu/gpu_flags.h"
|
||||||
#include "xenia/kernel/xthread.h"
|
#include "xenia/ui/loop.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace gpu {
|
namespace gpu {
|
||||||
|
|
||||||
namespace gl4 {
|
namespace gl4 {
|
||||||
std::unique_ptr<GraphicsSystem> Create(Emulator* emulator);
|
std::unique_ptr<GraphicsSystem> Create();
|
||||||
} // namespace gl4
|
} // namespace gl4
|
||||||
|
|
||||||
std::unique_ptr<GraphicsSystem> GraphicsSystem::Create(Emulator* emulator) {
|
std::unique_ptr<GraphicsSystem> GraphicsSystem::Create() {
|
||||||
if (FLAGS_gpu.compare("gl4") == 0) {
|
if (FLAGS_gpu.compare("gl4") == 0) {
|
||||||
return xe::gpu::gl4::Create(emulator);
|
return xe::gpu::gl4::Create();
|
||||||
} else {
|
} else {
|
||||||
// Create best available.
|
// Create best available.
|
||||||
std::unique_ptr<GraphicsSystem> best;
|
std::unique_ptr<GraphicsSystem> best;
|
||||||
|
|
||||||
best = xe::gpu::gl4::Create(emulator);
|
best = xe::gpu::gl4::Create();
|
||||||
if (best) {
|
if (best) {
|
||||||
return best;
|
return best;
|
||||||
}
|
}
|
||||||
|
@ -39,21 +42,157 @@ std::unique_ptr<GraphicsSystem> GraphicsSystem::Create(Emulator* emulator) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GraphicsSystem::GraphicsSystem(Emulator* emulator) : emulator_(emulator) {}
|
GraphicsSystem::GraphicsSystem() : vsync_worker_running_(false) {}
|
||||||
|
|
||||||
GraphicsSystem::~GraphicsSystem() = default;
|
GraphicsSystem::~GraphicsSystem() = default;
|
||||||
|
|
||||||
X_STATUS GraphicsSystem::Setup(cpu::Processor* processor, ui::Loop* target_loop,
|
X_STATUS GraphicsSystem::Setup(cpu::Processor* processor,
|
||||||
|
kernel::KernelState* kernel_state,
|
||||||
ui::Window* target_window) {
|
ui::Window* target_window) {
|
||||||
processor_ = processor;
|
|
||||||
memory_ = processor->memory();
|
memory_ = processor->memory();
|
||||||
target_loop_ = target_loop;
|
processor_ = processor;
|
||||||
|
kernel_state_ = kernel_state;
|
||||||
target_window_ = target_window;
|
target_window_ = target_window;
|
||||||
|
|
||||||
|
// Initialize rendering context.
|
||||||
|
// This must happen on the UI thread.
|
||||||
|
std::unique_ptr<xe::ui::GraphicsContext> processor_context;
|
||||||
|
target_window_->loop()->PostSynchronous([&]() {
|
||||||
|
// Setup the GL context the command processor will do all its drawing in.
|
||||||
|
// It's shared with the display context so that we can resolve framebuffers
|
||||||
|
// from it.
|
||||||
|
processor_context = target_window->context()->CreateShared();
|
||||||
|
processor_context->ClearCurrent();
|
||||||
|
});
|
||||||
|
if (!processor_context) {
|
||||||
|
xe::FatalError(
|
||||||
|
"Unable to initialize GL context. Xenia requires OpenGL 4.5. Ensure "
|
||||||
|
"you have the latest drivers for your GPU and that it supports OpenGL "
|
||||||
|
"4.5. See http://xenia.jp/faq/ for more information.");
|
||||||
|
return X_STATUS_UNSUCCESSFUL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create command processor. This will spin up a thread to process all
|
||||||
|
// incoming ringbuffer packets.
|
||||||
|
command_processor_ = CreateCommandProcessor();
|
||||||
|
if (!command_processor_->Initialize(std::move(processor_context))) {
|
||||||
|
XELOGE("Unable to initialize command processor");
|
||||||
|
return X_STATUS_UNSUCCESSFUL;
|
||||||
|
}
|
||||||
|
command_processor_->set_swap_request_handler(
|
||||||
|
[this]() { target_window_->Invalidate(); });
|
||||||
|
|
||||||
|
// Watch for paint requests to do our swap.
|
||||||
|
target_window->on_painting.AddListener(
|
||||||
|
[this](xe::ui::UIEvent* e) { Swap(e); });
|
||||||
|
|
||||||
|
// Let the processor know we want register access callbacks.
|
||||||
|
memory_->AddVirtualMappedRange(
|
||||||
|
0x7FC80000, 0xFFFF0000, 0x0000FFFF, this,
|
||||||
|
reinterpret_cast<cpu::MMIOReadCallback>(ReadRegisterThunk),
|
||||||
|
reinterpret_cast<cpu::MMIOWriteCallback>(WriteRegisterThunk));
|
||||||
|
|
||||||
|
// 60hz vsync timer.
|
||||||
|
vsync_worker_running_ = true;
|
||||||
|
vsync_worker_thread_ = kernel::object_ref<kernel::XHostThread>(
|
||||||
|
new kernel::XHostThread(kernel_state_, 128 * 1024, 0, [this]() {
|
||||||
|
uint64_t vsync_duration = FLAGS_vsync ? 16 : 1;
|
||||||
|
uint64_t last_frame_time = Clock::QueryGuestTickCount();
|
||||||
|
while (vsync_worker_running_) {
|
||||||
|
uint64_t current_time = Clock::QueryGuestTickCount();
|
||||||
|
uint64_t elapsed = (current_time - last_frame_time) /
|
||||||
|
(Clock::guest_tick_frequency() / 1000);
|
||||||
|
if (elapsed >= vsync_duration) {
|
||||||
|
MarkVblank();
|
||||||
|
last_frame_time = current_time;
|
||||||
|
}
|
||||||
|
xe::threading::Sleep(std::chrono::milliseconds(1));
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}));
|
||||||
|
// As we run vblank interrupts the debugger must be able to suspend us.
|
||||||
|
vsync_worker_thread_->set_can_debugger_suspend(true);
|
||||||
|
vsync_worker_thread_->set_name("GraphicsSystem Vsync");
|
||||||
|
vsync_worker_thread_->Create();
|
||||||
|
|
||||||
|
if (FLAGS_trace_gpu_stream) {
|
||||||
|
BeginTracing();
|
||||||
|
}
|
||||||
|
|
||||||
return X_STATUS_SUCCESS;
|
return X_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsSystem::Shutdown() {}
|
void GraphicsSystem::Shutdown() {
|
||||||
|
EndTracing();
|
||||||
|
|
||||||
|
vsync_worker_running_ = false;
|
||||||
|
vsync_worker_thread_->Wait(0, 0, 0, nullptr);
|
||||||
|
vsync_worker_thread_.reset();
|
||||||
|
|
||||||
|
command_processor_->Shutdown();
|
||||||
|
|
||||||
|
// TODO(benvanik): remove mapped range.
|
||||||
|
|
||||||
|
command_processor_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t GraphicsSystem::ReadRegisterThunk(void* ppc_context,
|
||||||
|
GraphicsSystem* gs, uint32_t addr) {
|
||||||
|
return gs->ReadRegister(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsSystem::WriteRegisterThunk(void* ppc_context, GraphicsSystem* gs,
|
||||||
|
uint32_t addr, uint32_t value) {
|
||||||
|
gs->WriteRegister(addr, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t GraphicsSystem::ReadRegister(uint32_t addr) {
|
||||||
|
uint32_t r = addr & 0xFFFF;
|
||||||
|
|
||||||
|
switch (r) {
|
||||||
|
case 0x3C00: // ?
|
||||||
|
return 0x08100748;
|
||||||
|
case 0x3C04: // ?
|
||||||
|
return 0x0000200E;
|
||||||
|
case 0x6530: // Scanline?
|
||||||
|
return 0x000002D0;
|
||||||
|
case 0x6544: // ? vblank pending?
|
||||||
|
return 1;
|
||||||
|
case 0x6584: // Screen res - 1280x720
|
||||||
|
return 0x050002D0;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_true(r < RegisterFile::kRegisterCount);
|
||||||
|
return register_file_.values[r].u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsSystem::WriteRegister(uint32_t addr, uint32_t value) {
|
||||||
|
uint32_t r = addr & 0xFFFF;
|
||||||
|
|
||||||
|
switch (r) {
|
||||||
|
case 0x0714: // CP_RB_WPTR
|
||||||
|
command_processor_->UpdateWritePointer(value);
|
||||||
|
break;
|
||||||
|
case 0x6110: // ? swap related?
|
||||||
|
XELOGW("Unimplemented GPU register %.4X write: %.8X", r, value);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
XELOGW("Unknown GPU register %.4X write: %.8X", r, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_true(r < RegisterFile::kRegisterCount);
|
||||||
|
register_file_.values[r].u32 = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsSystem::InitializeRingBuffer(uint32_t ptr, uint32_t page_count) {
|
||||||
|
command_processor_->InitializeRingBuffer(ptr, page_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsSystem::EnableReadPointerWriteBack(uint32_t ptr,
|
||||||
|
uint32_t block_size) {
|
||||||
|
command_processor_->EnableReadPointerWriteBack(ptr, block_size);
|
||||||
|
}
|
||||||
|
|
||||||
void GraphicsSystem::SetInterruptCallback(uint32_t callback,
|
void GraphicsSystem::SetInterruptCallback(uint32_t callback,
|
||||||
uint32_t user_data) {
|
uint32_t user_data) {
|
||||||
|
@ -84,5 +223,32 @@ void GraphicsSystem::DispatchInterruptCallback(uint32_t source, uint32_t cpu) {
|
||||||
args, xe::countof(args));
|
args, xe::countof(args));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GraphicsSystem::MarkVblank() {
|
||||||
|
SCOPE_profile_cpu_f("gpu");
|
||||||
|
|
||||||
|
// Increment vblank counter (so the game sees us making progress).
|
||||||
|
command_processor_->increment_counter();
|
||||||
|
|
||||||
|
// TODO(benvanik): we shouldn't need to do the dispatch here, but there's
|
||||||
|
// something wrong and the CP will block waiting for code that
|
||||||
|
// needs to be run in the interrupt.
|
||||||
|
DispatchInterruptCallback(0, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsSystem::ClearCaches() {
|
||||||
|
command_processor_->CallInThread(
|
||||||
|
[&]() { command_processor_->ClearCaches(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsSystem::RequestFrameTrace() {
|
||||||
|
command_processor_->RequestFrameTrace(xe::to_wstring(FLAGS_trace_gpu_prefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsSystem::BeginTracing() {
|
||||||
|
command_processor_->BeginTracing(xe::to_wstring(FLAGS_trace_gpu_prefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsSystem::EndTracing() { command_processor_->EndTracing(); }
|
||||||
|
|
||||||
} // namespace gpu
|
} // namespace gpu
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
|
@ -15,8 +15,9 @@
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#include "xenia/cpu/processor.h"
|
#include "xenia/cpu/processor.h"
|
||||||
|
#include "xenia/gpu/register_file.h"
|
||||||
|
#include "xenia/kernel/xthread.h"
|
||||||
#include "xenia/memory.h"
|
#include "xenia/memory.h"
|
||||||
#include "xenia/ui/loop.h"
|
|
||||||
#include "xenia/ui/window.h"
|
#include "xenia/ui/window.h"
|
||||||
#include "xenia/xbox.h"
|
#include "xenia/xbox.h"
|
||||||
|
|
||||||
|
@ -27,51 +28,70 @@ class Emulator;
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace gpu {
|
namespace gpu {
|
||||||
|
|
||||||
|
class CommandProcessor;
|
||||||
|
|
||||||
class GraphicsSystem {
|
class GraphicsSystem {
|
||||||
public:
|
public:
|
||||||
virtual ~GraphicsSystem();
|
virtual ~GraphicsSystem();
|
||||||
|
|
||||||
static std::unique_ptr<GraphicsSystem> Create(Emulator* emulator);
|
static std::unique_ptr<GraphicsSystem> Create();
|
||||||
virtual std::unique_ptr<ui::GraphicsContext> CreateContext(
|
virtual std::unique_ptr<ui::GraphicsContext> CreateContext(
|
||||||
ui::Window* target_window) = 0;
|
ui::Window* target_window) = 0;
|
||||||
|
|
||||||
Emulator* emulator() const { return emulator_; }
|
|
||||||
Memory* memory() const { return memory_; }
|
Memory* memory() const { return memory_; }
|
||||||
cpu::Processor* processor() const { return processor_; }
|
cpu::Processor* processor() const { return processor_; }
|
||||||
|
kernel::KernelState* kernel_state() const { return kernel_state_; }
|
||||||
|
|
||||||
virtual X_STATUS Setup(cpu::Processor* processor, ui::Loop* target_loop,
|
virtual X_STATUS Setup(cpu::Processor* processor,
|
||||||
|
kernel::KernelState* kernel_state,
|
||||||
ui::Window* target_window);
|
ui::Window* target_window);
|
||||||
virtual void Shutdown();
|
virtual void Shutdown();
|
||||||
|
|
||||||
void SetInterruptCallback(uint32_t callback, uint32_t user_data);
|
RegisterFile* register_file() { return ®ister_file_; }
|
||||||
virtual void InitializeRingBuffer(uint32_t ptr, uint32_t page_count) = 0;
|
CommandProcessor* command_processor() const {
|
||||||
virtual void EnableReadPointerWriteBack(uint32_t ptr,
|
return command_processor_.get();
|
||||||
uint32_t block_size) = 0;
|
}
|
||||||
|
|
||||||
|
void InitializeRingBuffer(uint32_t ptr, uint32_t page_count);
|
||||||
|
void EnableReadPointerWriteBack(uint32_t ptr, uint32_t block_size);
|
||||||
|
|
||||||
|
void SetInterruptCallback(uint32_t callback, uint32_t user_data);
|
||||||
void DispatchInterruptCallback(uint32_t source, uint32_t cpu);
|
void DispatchInterruptCallback(uint32_t source, uint32_t cpu);
|
||||||
|
|
||||||
virtual void RequestFrameTrace() {}
|
virtual void ClearCaches();
|
||||||
virtual void BeginTracing() {}
|
|
||||||
virtual void EndTracing() {}
|
void RequestFrameTrace();
|
||||||
enum class TracePlaybackMode {
|
void BeginTracing();
|
||||||
kUntilEnd,
|
void EndTracing();
|
||||||
kBreakOnSwap,
|
|
||||||
};
|
|
||||||
virtual void PlayTrace(const uint8_t* trace_data, size_t trace_size,
|
|
||||||
TracePlaybackMode playback_mode) {}
|
|
||||||
virtual void ClearCaches() {}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
explicit GraphicsSystem(Emulator* emulator);
|
GraphicsSystem();
|
||||||
|
|
||||||
|
virtual std::unique_ptr<CommandProcessor> CreateCommandProcessor() = 0;
|
||||||
|
|
||||||
|
static uint32_t ReadRegisterThunk(void* ppc_context, GraphicsSystem* gs,
|
||||||
|
uint32_t addr);
|
||||||
|
static void WriteRegisterThunk(void* ppc_context, GraphicsSystem* gs,
|
||||||
|
uint32_t addr, uint32_t value);
|
||||||
|
uint32_t ReadRegister(uint32_t addr);
|
||||||
|
void WriteRegister(uint32_t addr, uint32_t value);
|
||||||
|
|
||||||
|
void MarkVblank();
|
||||||
|
virtual void Swap(xe::ui::UIEvent* e) = 0;
|
||||||
|
|
||||||
Emulator* emulator_ = nullptr;
|
|
||||||
Memory* memory_ = nullptr;
|
Memory* memory_ = nullptr;
|
||||||
cpu::Processor* processor_ = nullptr;
|
cpu::Processor* processor_ = nullptr;
|
||||||
ui::Loop* target_loop_ = nullptr;
|
kernel::KernelState* kernel_state_ = nullptr;
|
||||||
ui::Window* target_window_ = nullptr;
|
ui::Window* target_window_ = nullptr;
|
||||||
|
|
||||||
uint32_t interrupt_callback_ = 0;
|
uint32_t interrupt_callback_ = 0;
|
||||||
uint32_t interrupt_callback_data_ = 0;
|
uint32_t interrupt_callback_data_ = 0;
|
||||||
|
|
||||||
|
std::atomic<bool> vsync_worker_running_;
|
||||||
|
kernel::object_ref<kernel::XHostThread> vsync_worker_thread_;
|
||||||
|
|
||||||
|
RegisterFile register_file_;
|
||||||
|
std::unique_ptr<CommandProcessor> command_processor_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace gpu
|
} // namespace gpu
|
||||||
|
|
|
@ -0,0 +1,498 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* 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 "xenia/gpu/packet_disassembler.h"
|
||||||
|
|
||||||
|
#include "xenia/gpu/xenos.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace gpu {
|
||||||
|
|
||||||
|
using namespace xe::gpu::xenos;
|
||||||
|
|
||||||
|
PacketCategory PacketDisassembler::GetPacketCategory(const uint8_t* base_ptr) {
|
||||||
|
const uint32_t packet = xe::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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PacketDisassembler::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 = xe::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 PacketDisassembler::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 = xe::load_and_swap<uint32_t>(ptr);
|
||||||
|
uint32_t reg_data_2 = xe::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 PacketDisassembler::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;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PacketDisassembler::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 = xe::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 = xe::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 = xe::load_and_swap<uint32_t>(ptr + 0);
|
||||||
|
uint32_t list_length = xe::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 = xe::load_and_swap<uint32_t>(ptr + 0);
|
||||||
|
uint32_t poll_reg_addr = xe::load_and_swap<uint32_t>(ptr + 4);
|
||||||
|
uint32_t ref = xe::load_and_swap<uint32_t>(ptr + 8);
|
||||||
|
uint32_t mask = xe::load_and_swap<uint32_t>(ptr + 12);
|
||||||
|
uint32_t wait = xe::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 = xe::load_and_swap<uint32_t>(ptr + 0);
|
||||||
|
uint32_t and_mask = xe::load_and_swap<uint32_t>(ptr + 4);
|
||||||
|
uint32_t or_mask = xe::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 = xe::load_and_swap<uint32_t>(ptr + 0);
|
||||||
|
uint32_t poll_reg_addr = xe::load_and_swap<uint32_t>(ptr + 4);
|
||||||
|
uint32_t ref = xe::load_and_swap<uint32_t>(ptr + 8);
|
||||||
|
uint32_t mask = xe::load_and_swap<uint32_t>(ptr + 12);
|
||||||
|
uint32_t write_reg_addr = xe::load_and_swap<uint32_t>(ptr + 16);
|
||||||
|
uint32_t write_data = xe::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 = xe::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 = xe::load_and_swap<uint32_t>(ptr + 0);
|
||||||
|
uint32_t address = xe::load_and_swap<uint32_t>(ptr + 4);
|
||||||
|
uint32_t value = xe::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 = xe::load_and_swap<uint32_t>(ptr + 0);
|
||||||
|
uint32_t unk1 = xe::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 = xe::load_and_swap<uint32_t>(ptr + 0);
|
||||||
|
uint32_t dword1 = xe::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 = xe::load_and_swap<uint32_t>(ptr + 8);
|
||||||
|
uint32_t index_size = xe::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 = xe::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 = xe::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 = xe::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 = xe::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 = xe::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 = xe::load_and_swap<uint32_t>(ptr + 0);
|
||||||
|
address &= 0x3FFFFFFF;
|
||||||
|
uint32_t offset_type = xe::load_and_swap<uint32_t>(ptr + 4);
|
||||||
|
uint32_t index = offset_type & 0x7FF;
|
||||||
|
uint32_t size_dwords = xe::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, ?
|
||||||
|
// xe::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 = xe::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 = xe::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 = xe::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 = xe::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 = xe::load_and_swap<uint32_t>(ptr + 0);
|
||||||
|
uint32_t dword1 = xe::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 = xe::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 = xe::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 = xe::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 = xe::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 = xe::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 PacketDisassembler::DisasmPacket(const uint8_t* base_ptr,
|
||||||
|
PacketInfo* out_info) {
|
||||||
|
const uint32_t packet = xe::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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace gpu
|
||||||
|
} // namespace xe
|
|
@ -0,0 +1,103 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* 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. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XENIA_GPU_PACKET_DISASSEMBLER_H_
|
||||||
|
#define XENIA_GPU_PACKET_DISASSEMBLER_H_
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "xenia/gpu/register_file.h"
|
||||||
|
#include "xenia/gpu/trace_protocol.h"
|
||||||
|
#include "xenia/gpu/trace_reader.h"
|
||||||
|
#include "xenia/memory.h"
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PacketDisassembler {
|
||||||
|
public:
|
||||||
|
static PacketCategory GetPacketCategory(const uint8_t* base_ptr);
|
||||||
|
|
||||||
|
static bool DisasmPacketType0(const uint8_t* base_ptr, uint32_t packet,
|
||||||
|
PacketInfo* out_info);
|
||||||
|
static bool DisasmPacketType1(const uint8_t* base_ptr, uint32_t packet,
|
||||||
|
PacketInfo* out_info);
|
||||||
|
static bool DisasmPacketType2(const uint8_t* base_ptr, uint32_t packet,
|
||||||
|
PacketInfo* out_info);
|
||||||
|
static bool DisasmPacketType3(const uint8_t* base_ptr, uint32_t packet,
|
||||||
|
PacketInfo* out_info);
|
||||||
|
static bool DisasmPacket(const uint8_t* base_ptr, PacketInfo* out_info);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace gpu
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_GPU_PACKET_DISASSEMBLER_H_
|
|
@ -0,0 +1,186 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* 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 "xenia/gpu/trace_player.h"
|
||||||
|
|
||||||
|
#include "xenia/gpu/command_processor.h"
|
||||||
|
#include "xenia/gpu/graphics_system.h"
|
||||||
|
#include "xenia/memory.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace gpu {
|
||||||
|
|
||||||
|
TracePlayer::TracePlayer(xe::ui::Loop* loop, GraphicsSystem* graphics_system)
|
||||||
|
: loop_(loop),
|
||||||
|
graphics_system_(graphics_system),
|
||||||
|
current_frame_index_(0),
|
||||||
|
current_command_index_(-1) {
|
||||||
|
// Need to allocate all of physical memory so that we can write to it
|
||||||
|
// during playback.
|
||||||
|
graphics_system_->memory()
|
||||||
|
->LookupHeapByType(true, 4096)
|
||||||
|
->AllocFixed(0, 0x1FFFFFFF, 4096,
|
||||||
|
kMemoryAllocationReserve | kMemoryAllocationCommit,
|
||||||
|
kMemoryProtectRead | kMemoryProtectWrite);
|
||||||
|
}
|
||||||
|
|
||||||
|
TracePlayer::~TracePlayer() = default;
|
||||||
|
|
||||||
|
const TraceReader::Frame* TracePlayer::current_frame() const {
|
||||||
|
if (current_frame_index_ > frame_count()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return frame(current_frame_index_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TracePlayer::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);
|
||||||
|
PlayTrace(frame->start_ptr, frame->end_ptr - frame->start_ptr,
|
||||||
|
TracePlaybackMode::kBreakOnSwap);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TracePlayer::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];
|
||||||
|
PlayTrace(previous_command.end_ptr,
|
||||||
|
command.end_ptr - previous_command.end_ptr,
|
||||||
|
TracePlaybackMode::kBreakOnSwap);
|
||||||
|
} else {
|
||||||
|
// Full playback from frame start.
|
||||||
|
PlayTrace(frame->start_ptr, command.end_ptr - frame->start_ptr,
|
||||||
|
TracePlaybackMode::kBreakOnSwap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TracePlayer::PlayTrace(const uint8_t* trace_data, size_t trace_size,
|
||||||
|
TracePlaybackMode playback_mode) {
|
||||||
|
graphics_system_->command_processor()->CallInThread(
|
||||||
|
[this, trace_data, trace_size, playback_mode]() {
|
||||||
|
PlayTraceOnThread(trace_data, trace_size, playback_mode);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void TracePlayer::PlayTraceOnThread(const uint8_t* trace_data,
|
||||||
|
size_t trace_size,
|
||||||
|
TracePlaybackMode playback_mode) {
|
||||||
|
auto memory = graphics_system_->memory();
|
||||||
|
auto command_processor = graphics_system_->command_processor();
|
||||||
|
|
||||||
|
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<TraceCommandType>(xe::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);
|
||||||
|
trace_ptr += sizeof(*cmd);
|
||||||
|
std::memcpy(memory->TranslatePhysical(cmd->base_ptr), trace_ptr,
|
||||||
|
cmd->count * 4);
|
||||||
|
trace_ptr += 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) {
|
||||||
|
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<const MemoryReadCommand*>(trace_ptr);
|
||||||
|
trace_ptr += sizeof(*cmd);
|
||||||
|
std::memcpy(memory->TranslatePhysical(cmd->base_ptr), trace_ptr,
|
||||||
|
cmd->length);
|
||||||
|
trace_ptr += cmd->length;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TraceCommandType::kMemoryWrite: {
|
||||||
|
auto cmd = reinterpret_cast<const MemoryWriteCommand*>(trace_ptr);
|
||||||
|
trace_ptr += sizeof(*cmd);
|
||||||
|
// ?
|
||||||
|
trace_ptr += 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: {
|
||||||
|
if (playback_mode == TracePlaybackMode::kBreakOnSwap) {
|
||||||
|
pending_break = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
command_processor->set_swap_mode(SwapMode::kNormal);
|
||||||
|
command_processor->IssueSwap(0, 1280, 720);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace gpu
|
||||||
|
} // namespace xe
|
|
@ -0,0 +1,57 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* 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. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XENIA_GPU_TRACE_PLAYER_H_
|
||||||
|
#define XENIA_GPU_TRACE_PLAYER_H_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "xenia/gpu/trace_protocol.h"
|
||||||
|
#include "xenia/gpu/trace_reader.h"
|
||||||
|
#include "xenia/ui/loop.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace gpu {
|
||||||
|
|
||||||
|
class GraphicsSystem;
|
||||||
|
|
||||||
|
enum class TracePlaybackMode {
|
||||||
|
kUntilEnd,
|
||||||
|
kBreakOnSwap,
|
||||||
|
};
|
||||||
|
|
||||||
|
class TracePlayer : public TraceReader {
|
||||||
|
public:
|
||||||
|
TracePlayer(xe::ui::Loop* loop, GraphicsSystem* graphics_system);
|
||||||
|
~TracePlayer() override;
|
||||||
|
|
||||||
|
GraphicsSystem* graphics_system() const { return graphics_system_; }
|
||||||
|
int current_frame_index() const { return current_frame_index_; }
|
||||||
|
int current_command_index() const { return current_command_index_; }
|
||||||
|
const Frame* current_frame() const;
|
||||||
|
|
||||||
|
void SeekFrame(int target_frame);
|
||||||
|
void SeekCommand(int target_command);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void PlayTrace(const uint8_t* trace_data, size_t trace_size,
|
||||||
|
TracePlaybackMode playback_mode);
|
||||||
|
void PlayTraceOnThread(const uint8_t* trace_data, size_t trace_size,
|
||||||
|
TracePlaybackMode playback_mode);
|
||||||
|
|
||||||
|
xe::ui::Loop* loop_;
|
||||||
|
GraphicsSystem* graphics_system_;
|
||||||
|
int current_frame_index_;
|
||||||
|
int current_command_index_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace gpu
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_GPU_TRACE_PLAYER_H_
|
|
@ -0,0 +1,84 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* 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. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XENIA_GPU_TRACE_PROTOCOL_H_
|
||||||
|
#define XENIA_GPU_TRACE_PROTOCOL_H_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace gpu {
|
||||||
|
|
||||||
|
enum class TraceCommandType : uint32_t {
|
||||||
|
kPrimaryBufferStart,
|
||||||
|
kPrimaryBufferEnd,
|
||||||
|
kIndirectBufferStart,
|
||||||
|
kIndirectBufferEnd,
|
||||||
|
kPacketStart,
|
||||||
|
kPacketEnd,
|
||||||
|
kMemoryRead,
|
||||||
|
kMemoryWrite,
|
||||||
|
kEvent,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PrimaryBufferStartCommand {
|
||||||
|
TraceCommandType type;
|
||||||
|
uint32_t base_ptr;
|
||||||
|
uint32_t count;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PrimaryBufferEndCommand {
|
||||||
|
TraceCommandType type;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IndirectBufferStartCommand {
|
||||||
|
TraceCommandType type;
|
||||||
|
uint32_t base_ptr;
|
||||||
|
uint32_t count;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IndirectBufferEndCommand {
|
||||||
|
TraceCommandType type;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PacketStartCommand {
|
||||||
|
TraceCommandType type;
|
||||||
|
uint32_t base_ptr;
|
||||||
|
uint32_t count;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PacketEndCommand {
|
||||||
|
TraceCommandType type;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MemoryReadCommand {
|
||||||
|
TraceCommandType type;
|
||||||
|
uint32_t base_ptr;
|
||||||
|
uint32_t length;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MemoryWriteCommand {
|
||||||
|
TraceCommandType type;
|
||||||
|
uint32_t base_ptr;
|
||||||
|
uint32_t length;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class EventType {
|
||||||
|
kSwap,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EventCommand {
|
||||||
|
TraceCommandType type;
|
||||||
|
EventType event_type;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace gpu
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_GPU_TRACE_PROTOCOL_H_
|
|
@ -0,0 +1,152 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* 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 "xenia/gpu/trace_reader.h"
|
||||||
|
|
||||||
|
#include "xenia/base/mapped_memory.h"
|
||||||
|
#include "xenia/gpu/packet_disassembler.h"
|
||||||
|
#include "xenia/gpu/trace_protocol.h"
|
||||||
|
#include "xenia/memory.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace gpu {
|
||||||
|
|
||||||
|
bool TraceReader::Open(const std::wstring& path) {
|
||||||
|
Close();
|
||||||
|
|
||||||
|
mmap_ = MappedMemory::Open(path, MappedMemory::Mode::kRead);
|
||||||
|
if (!mmap_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
trace_data_ = reinterpret_cast<const uint8_t*>(mmap_->data());
|
||||||
|
trace_size_ = mmap_->size();
|
||||||
|
|
||||||
|
ParseTrace();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TraceReader::Close() {
|
||||||
|
mmap_.reset();
|
||||||
|
trace_data_ = nullptr;
|
||||||
|
trace_size_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TraceReader::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>(xe::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 = PacketDisassembler::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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace gpu
|
||||||
|
} // namespace xe
|
|
@ -0,0 +1,102 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* 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. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XENIA_GPU_TRACE_READER_H_
|
||||||
|
#define XENIA_GPU_TRACE_READER_H_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "xenia/base/mapped_memory.h"
|
||||||
|
#include "xenia/gpu/trace_protocol.h"
|
||||||
|
#include "xenia/memory.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace gpu {
|
||||||
|
|
||||||
|
// 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);*/
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
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() = default;
|
||||||
|
virtual ~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);
|
||||||
|
|
||||||
|
void Close();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void ParseTrace();
|
||||||
|
|
||||||
|
std::unique_ptr<MappedMemory> mmap_;
|
||||||
|
const uint8_t* trace_data_ = nullptr;
|
||||||
|
size_t trace_size_ = 0;
|
||||||
|
std::vector<Frame> frames_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace gpu
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_GPU_TRACE_READER_H_
|
|
@ -0,0 +1,141 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* 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 "xenia/gpu/trace_writer.h"
|
||||||
|
|
||||||
|
#include "xenia/base/string.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace gpu {
|
||||||
|
|
||||||
|
TraceWriter::TraceWriter(uint8_t* membase)
|
||||||
|
: membase_(membase), file_(nullptr) {}
|
||||||
|
|
||||||
|
TraceWriter::~TraceWriter() = default;
|
||||||
|
|
||||||
|
bool TraceWriter::Open(const std::wstring& path) {
|
||||||
|
Close();
|
||||||
|
|
||||||
|
auto canonical_path = xe::to_absolute_path(path);
|
||||||
|
auto base_path = xe::find_base_path(canonical_path);
|
||||||
|
xe::filesystem::CreateFolder(base_path);
|
||||||
|
|
||||||
|
file_ = xe::filesystem::OpenFile(canonical_path, "wb");
|
||||||
|
return file_ != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TraceWriter::Flush() {
|
||||||
|
if (file_) {
|
||||||
|
fflush(file_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TraceWriter::Close() {
|
||||||
|
if (file_) {
|
||||||
|
fflush(file_);
|
||||||
|
fclose(file_);
|
||||||
|
file_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TraceWriter::WritePrimaryBufferStart(uint32_t base_ptr, uint32_t count) {
|
||||||
|
if (!file_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto cmd = PrimaryBufferStartCommand({
|
||||||
|
TraceCommandType::kPrimaryBufferStart, base_ptr, 0,
|
||||||
|
});
|
||||||
|
fwrite(&cmd, 1, sizeof(cmd), file_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TraceWriter::WritePrimaryBufferEnd() {
|
||||||
|
if (!file_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto cmd = PrimaryBufferEndCommand({
|
||||||
|
TraceCommandType::kPrimaryBufferEnd,
|
||||||
|
});
|
||||||
|
fwrite(&cmd, 1, sizeof(cmd), file_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TraceWriter::WriteIndirectBufferStart(uint32_t base_ptr, uint32_t count) {
|
||||||
|
if (!file_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto cmd = IndirectBufferStartCommand({
|
||||||
|
TraceCommandType::kIndirectBufferStart, base_ptr, 0,
|
||||||
|
});
|
||||||
|
fwrite(&cmd, 1, sizeof(cmd), file_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TraceWriter::WriteIndirectBufferEnd() {
|
||||||
|
if (!file_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto cmd = IndirectBufferEndCommand({
|
||||||
|
TraceCommandType::kIndirectBufferEnd,
|
||||||
|
});
|
||||||
|
fwrite(&cmd, 1, sizeof(cmd), file_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TraceWriter::WritePacketStart(uint32_t base_ptr, uint32_t count) {
|
||||||
|
if (!file_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto cmd = PacketStartCommand({
|
||||||
|
TraceCommandType::kPacketStart, base_ptr, count,
|
||||||
|
});
|
||||||
|
fwrite(&cmd, 1, sizeof(cmd), file_);
|
||||||
|
fwrite(membase_ + base_ptr, 4, count, file_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TraceWriter::WritePacketEnd() {
|
||||||
|
if (!file_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto cmd = PacketEndCommand({
|
||||||
|
TraceCommandType::kPacketEnd,
|
||||||
|
});
|
||||||
|
fwrite(&cmd, 1, sizeof(cmd), file_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TraceWriter::WriteMemoryRead(uint32_t base_ptr, size_t length) {
|
||||||
|
if (!file_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto cmd = MemoryReadCommand({
|
||||||
|
TraceCommandType::kMemoryRead, base_ptr, uint32_t(length),
|
||||||
|
});
|
||||||
|
fwrite(&cmd, 1, sizeof(cmd), file_);
|
||||||
|
fwrite(membase_ + base_ptr, 1, length, file_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TraceWriter::WriteMemoryWrite(uint32_t base_ptr, size_t length) {
|
||||||
|
if (!file_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto cmd = MemoryWriteCommand({
|
||||||
|
TraceCommandType::kMemoryWrite, base_ptr, uint32_t(length),
|
||||||
|
});
|
||||||
|
fwrite(&cmd, 1, sizeof(cmd), file_);
|
||||||
|
fwrite(membase_ + base_ptr, 1, length, file_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TraceWriter::WriteEvent(EventType event_type) {
|
||||||
|
if (!file_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto cmd = EventCommand({
|
||||||
|
TraceCommandType::kEvent, event_type,
|
||||||
|
});
|
||||||
|
fwrite(&cmd, 1, sizeof(cmd), file_);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace gpu
|
||||||
|
} // namespace xe
|
|
@ -0,0 +1,50 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* 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. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XENIA_GPU_TRACE_WRITER_H_
|
||||||
|
#define XENIA_GPU_TRACE_WRITER_H_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "xenia/base/filesystem.h"
|
||||||
|
#include "xenia/gpu/trace_protocol.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace gpu {
|
||||||
|
|
||||||
|
class TraceWriter {
|
||||||
|
public:
|
||||||
|
explicit TraceWriter(uint8_t* membase);
|
||||||
|
~TraceWriter();
|
||||||
|
|
||||||
|
bool is_open() const { return file_ != nullptr; }
|
||||||
|
|
||||||
|
bool Open(const std::wstring& path);
|
||||||
|
void Flush();
|
||||||
|
void Close();
|
||||||
|
|
||||||
|
void WritePrimaryBufferStart(uint32_t base_ptr, uint32_t count);
|
||||||
|
void WritePrimaryBufferEnd();
|
||||||
|
void WriteIndirectBufferStart(uint32_t base_ptr, uint32_t count);
|
||||||
|
void WriteIndirectBufferEnd();
|
||||||
|
void WritePacketStart(uint32_t base_ptr, uint32_t count);
|
||||||
|
void WritePacketEnd();
|
||||||
|
void WriteMemoryRead(uint32_t base_ptr, size_t length);
|
||||||
|
void WriteMemoryWrite(uint32_t base_ptr, size_t length);
|
||||||
|
void WriteEvent(EventType event_type);
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t* membase_;
|
||||||
|
FILE* file_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace gpu
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_GPU_TRACE_WRITER_H_
|
|
@ -1,49 +0,0 @@
|
||||||
/**
|
|
||||||
******************************************************************************
|
|
||||||
* 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 "xenia/gpu/tracing.h"
|
|
||||||
|
|
||||||
#include "xenia/base/filesystem.h"
|
|
||||||
#include "xenia/base/string.h"
|
|
||||||
|
|
||||||
namespace xe {
|
|
||||||
namespace gpu {
|
|
||||||
|
|
||||||
TraceWriter::TraceWriter(uint8_t* membase)
|
|
||||||
: membase_(membase), file_(nullptr) {}
|
|
||||||
|
|
||||||
TraceWriter::~TraceWriter() = default;
|
|
||||||
|
|
||||||
bool TraceWriter::Open(const std::wstring& path) {
|
|
||||||
Close();
|
|
||||||
|
|
||||||
auto canonical_path = xe::to_absolute_path(path);
|
|
||||||
auto base_path = xe::find_base_path(canonical_path);
|
|
||||||
xe::filesystem::CreateFolder(base_path);
|
|
||||||
|
|
||||||
file_ = xe::filesystem::OpenFile(canonical_path, "wb");
|
|
||||||
return file_ != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TraceWriter::Flush() {
|
|
||||||
if (file_) {
|
|
||||||
fflush(file_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TraceWriter::Close() {
|
|
||||||
if (file_) {
|
|
||||||
fflush(file_);
|
|
||||||
fclose(file_);
|
|
||||||
file_ = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace gpu
|
|
||||||
} // namespace xe
|
|
|
@ -1,195 +0,0 @@
|
||||||
/**
|
|
||||||
******************************************************************************
|
|
||||||
* 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. *
|
|
||||||
******************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef XENIA_GPU_TRACING_H_
|
|
||||||
#define XENIA_GPU_TRACING_H_
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "xenia/memory.h"
|
|
||||||
|
|
||||||
namespace xe {
|
|
||||||
namespace gpu {
|
|
||||||
|
|
||||||
enum class TraceCommandType : uint32_t {
|
|
||||||
kPrimaryBufferStart,
|
|
||||||
kPrimaryBufferEnd,
|
|
||||||
kIndirectBufferStart,
|
|
||||||
kIndirectBufferEnd,
|
|
||||||
kPacketStart,
|
|
||||||
kPacketEnd,
|
|
||||||
kMemoryRead,
|
|
||||||
kMemoryWrite,
|
|
||||||
kEvent,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PrimaryBufferStartCommand {
|
|
||||||
TraceCommandType type;
|
|
||||||
uint32_t base_ptr;
|
|
||||||
uint32_t count;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PrimaryBufferEndCommand {
|
|
||||||
TraceCommandType type;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct IndirectBufferStartCommand {
|
|
||||||
TraceCommandType type;
|
|
||||||
uint32_t base_ptr;
|
|
||||||
uint32_t count;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct IndirectBufferEndCommand {
|
|
||||||
TraceCommandType type;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PacketStartCommand {
|
|
||||||
TraceCommandType type;
|
|
||||||
uint32_t base_ptr;
|
|
||||||
uint32_t count;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PacketEndCommand {
|
|
||||||
TraceCommandType type;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct MemoryReadCommand {
|
|
||||||
TraceCommandType type;
|
|
||||||
uint32_t base_ptr;
|
|
||||||
uint32_t length;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct MemoryWriteCommand {
|
|
||||||
TraceCommandType type;
|
|
||||||
uint32_t base_ptr;
|
|
||||||
uint32_t length;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class EventType {
|
|
||||||
kSwap,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct EventCommand {
|
|
||||||
TraceCommandType type;
|
|
||||||
EventType event_type;
|
|
||||||
};
|
|
||||||
|
|
||||||
class TraceWriter {
|
|
||||||
public:
|
|
||||||
explicit TraceWriter(uint8_t* membase);
|
|
||||||
~TraceWriter();
|
|
||||||
|
|
||||||
bool is_open() const { return file_ != nullptr; }
|
|
||||||
|
|
||||||
bool Open(const std::wstring& path);
|
|
||||||
void Flush();
|
|
||||||
void Close();
|
|
||||||
|
|
||||||
void WritePrimaryBufferStart(uint32_t base_ptr, uint32_t count) {
|
|
||||||
if (!file_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto cmd = PrimaryBufferStartCommand({
|
|
||||||
TraceCommandType::kPrimaryBufferStart, base_ptr, 0,
|
|
||||||
});
|
|
||||||
fwrite(&cmd, 1, sizeof(cmd), file_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WritePrimaryBufferEnd() {
|
|
||||||
if (!file_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto cmd = PrimaryBufferEndCommand({
|
|
||||||
TraceCommandType::kPrimaryBufferEnd,
|
|
||||||
});
|
|
||||||
fwrite(&cmd, 1, sizeof(cmd), file_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WriteIndirectBufferStart(uint32_t base_ptr, uint32_t count) {
|
|
||||||
if (!file_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto cmd = IndirectBufferStartCommand({
|
|
||||||
TraceCommandType::kIndirectBufferStart, base_ptr, 0,
|
|
||||||
});
|
|
||||||
fwrite(&cmd, 1, sizeof(cmd), file_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WriteIndirectBufferEnd() {
|
|
||||||
if (!file_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto cmd = IndirectBufferEndCommand({
|
|
||||||
TraceCommandType::kIndirectBufferEnd,
|
|
||||||
});
|
|
||||||
fwrite(&cmd, 1, sizeof(cmd), file_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WritePacketStart(uint32_t base_ptr, uint32_t count) {
|
|
||||||
if (!file_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto cmd = PacketStartCommand({
|
|
||||||
TraceCommandType::kPacketStart, base_ptr, count,
|
|
||||||
});
|
|
||||||
fwrite(&cmd, 1, sizeof(cmd), file_);
|
|
||||||
fwrite(membase_ + base_ptr, 4, count, file_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WritePacketEnd() {
|
|
||||||
if (!file_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto cmd = PacketEndCommand({
|
|
||||||
TraceCommandType::kPacketEnd,
|
|
||||||
});
|
|
||||||
fwrite(&cmd, 1, sizeof(cmd), file_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WriteMemoryRead(uint32_t base_ptr, size_t length) {
|
|
||||||
if (!file_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto cmd = MemoryReadCommand({
|
|
||||||
TraceCommandType::kMemoryRead, base_ptr, uint32_t(length),
|
|
||||||
});
|
|
||||||
fwrite(&cmd, 1, sizeof(cmd), file_);
|
|
||||||
fwrite(membase_ + base_ptr, 1, length, file_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WriteMemoryWrite(uint32_t base_ptr, size_t length) {
|
|
||||||
if (!file_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto cmd = MemoryWriteCommand({
|
|
||||||
TraceCommandType::kMemoryWrite, base_ptr, uint32_t(length),
|
|
||||||
});
|
|
||||||
fwrite(&cmd, 1, sizeof(cmd), file_);
|
|
||||||
fwrite(membase_ + base_ptr, 1, length, file_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WriteEvent(EventType event_type) {
|
|
||||||
if (!file_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto cmd = EventCommand({
|
|
||||||
TraceCommandType::kEvent, event_type,
|
|
||||||
});
|
|
||||||
fwrite(&cmd, 1, sizeof(cmd), file_);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
uint8_t* membase_;
|
|
||||||
FILE* file_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace gpu
|
|
||||||
} // namespace xe
|
|
||||||
|
|
||||||
#endif // XENIA_GPU_TRACING_H_
|
|
|
@ -161,8 +161,6 @@ std::unique_ptr<ImmediateTexture> GLImmediateDrawer::CreateTexture(
|
||||||
GraphicsContextLock lock(graphics_context_);
|
GraphicsContextLock lock(graphics_context_);
|
||||||
auto texture =
|
auto texture =
|
||||||
std::make_unique<GLImmediateTexture>(width, height, filter, repeat);
|
std::make_unique<GLImmediateTexture>(width, height, filter, repeat);
|
||||||
glTextureStorage2D(static_cast<GLuint>(texture->handle), 1, GL_RGBA8, width,
|
|
||||||
height);
|
|
||||||
if (data) {
|
if (data) {
|
||||||
UpdateTexture(texture.get(), data);
|
UpdateTexture(texture.get(), data);
|
||||||
}
|
}
|
||||||
|
@ -188,8 +186,8 @@ void GLImmediateDrawer::Begin(int render_target_width,
|
||||||
glEnablei(GL_BLEND, 0);
|
glEnablei(GL_BLEND, 0);
|
||||||
glBlendEquationi(0, GL_FUNC_ADD);
|
glBlendEquationi(0, GL_FUNC_ADD);
|
||||||
glBlendFunci(0, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
glBlendFunci(0, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
glDisablei(GL_DEPTH_TEST, 0);
|
glDisable(GL_DEPTH_TEST);
|
||||||
glDisablei(GL_SCISSOR_TEST, 0);
|
glDisable(GL_SCISSOR_TEST);
|
||||||
|
|
||||||
// Prepare drawing resources.
|
// Prepare drawing resources.
|
||||||
glUseProgram(program_);
|
glUseProgram(program_);
|
||||||
|
@ -223,11 +221,11 @@ void GLImmediateDrawer::BeginDrawBatch(const ImmediateDrawBatch& batch) {
|
||||||
|
|
||||||
void GLImmediateDrawer::Draw(const ImmediateDraw& draw) {
|
void GLImmediateDrawer::Draw(const ImmediateDraw& draw) {
|
||||||
if (draw.scissor) {
|
if (draw.scissor) {
|
||||||
glEnablei(GL_SCISSOR_TEST, 0);
|
glEnable(GL_SCISSOR_TEST);
|
||||||
glScissorIndexed(0, draw.scissor_rect[0], draw.scissor_rect[1],
|
glScissorIndexed(0, draw.scissor_rect[0], draw.scissor_rect[1],
|
||||||
draw.scissor_rect[2], draw.scissor_rect[3]);
|
draw.scissor_rect[2], draw.scissor_rect[3]);
|
||||||
} else {
|
} else {
|
||||||
glDisablei(GL_SCISSOR_TEST, 0);
|
glDisable(GL_SCISSOR_TEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (draw.texture_handle) {
|
if (draw.texture_handle) {
|
||||||
|
@ -261,7 +259,7 @@ void GLImmediateDrawer::EndDrawBatch() { glFlush(); }
|
||||||
|
|
||||||
void GLImmediateDrawer::End() {
|
void GLImmediateDrawer::End() {
|
||||||
// Restore modified state.
|
// Restore modified state.
|
||||||
glDisablei(GL_SCISSOR_TEST, 0);
|
glDisable(GL_SCISSOR_TEST);
|
||||||
glBindTextureUnit(0, 0);
|
glBindTextureUnit(0, 0);
|
||||||
glUseProgram(0);
|
glUseProgram(0);
|
||||||
glBindVertexArray(0);
|
glBindVertexArray(0);
|
||||||
|
|
Loading…
Reference in New Issue