Factoring out a lot of reusable GPU code from gl4/.

This commit is contained in:
Ben Vanik 2015-11-08 11:54:36 -08:00
parent b26f4a5719
commit b5a18b5462
23 changed files with 3074 additions and 2746 deletions

View File

@ -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

View File

@ -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_

View File

@ -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_;

View File

@ -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

View File

@ -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 &register_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

View File

@ -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);
} }

View File

@ -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",
}) })

View File

@ -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

View File

@ -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 &register_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

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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);