Factoring out a lot of reusable GPU code from gl4/.
This commit is contained in:
parent
b26f4a5719
commit
b5a18b5462
|
@ -117,7 +117,7 @@ X_STATUS Emulator::Setup(ui::Window* display_window) {
|
|||
}
|
||||
|
||||
// Initialize the GPU.
|
||||
graphics_system_ = xe::gpu::GraphicsSystem::Create(this);
|
||||
graphics_system_ = xe::gpu::GraphicsSystem::Create();
|
||||
if (!graphics_system_) {
|
||||
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);
|
||||
|
||||
// Setup the core components.
|
||||
result = graphics_system_->Setup(processor_.get(), display_window_->loop(),
|
||||
result = graphics_system_->Setup(processor_.get(), kernel_state_.get(),
|
||||
display_window_);
|
||||
if (result) {
|
||||
return result;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,213 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_GPU_COMMAND_PROCESSOR_H_
|
||||
#define XENIA_GPU_COMMAND_PROCESSOR_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/threading.h"
|
||||
#include "xenia/gpu/register_file.h"
|
||||
#include "xenia/gpu/trace_writer.h"
|
||||
#include "xenia/gpu/xenos.h"
|
||||
#include "xenia/kernel/xthread.h"
|
||||
#include "xenia/memory.h"
|
||||
#include "xenia/ui/graphics_context.h"
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
|
||||
class GraphicsSystem;
|
||||
|
||||
struct SwapState {
|
||||
// Lock must be held when changing data in this structure.
|
||||
std::mutex mutex;
|
||||
// Dimensions of the framebuffer textures. Should match window size.
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
// Current front buffer, being drawn to the screen.
|
||||
uintptr_t front_buffer_texture = 0;
|
||||
// Current back buffer, being updated by the CP.
|
||||
uintptr_t back_buffer_texture = 0;
|
||||
// Whether the back buffer is dirty and a swap is pending.
|
||||
bool pending = false;
|
||||
};
|
||||
|
||||
enum class SwapMode {
|
||||
kNormal,
|
||||
kIgnored,
|
||||
};
|
||||
|
||||
class CommandProcessor {
|
||||
public:
|
||||
CommandProcessor(GraphicsSystem* graphics_system,
|
||||
kernel::KernelState* kernel_state);
|
||||
virtual ~CommandProcessor();
|
||||
|
||||
uint32_t counter() const { return counter_; }
|
||||
void increment_counter() { counter_++; }
|
||||
|
||||
virtual bool Initialize(std::unique_ptr<xe::ui::GraphicsContext> context);
|
||||
virtual void Shutdown();
|
||||
|
||||
void CallInThread(std::function<void()> fn);
|
||||
|
||||
virtual void ClearCaches();
|
||||
|
||||
SwapState& swap_state() { return swap_state_; }
|
||||
void set_swap_mode(SwapMode swap_mode) { swap_mode_ = swap_mode; }
|
||||
void IssueSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
|
||||
uint32_t frontbuffer_height);
|
||||
|
||||
void set_swap_request_handler(std::function<void()> fn) {
|
||||
swap_request_handler_ = fn;
|
||||
}
|
||||
|
||||
void RequestFrameTrace(const std::wstring& root_path);
|
||||
void BeginTracing(const std::wstring& root_path);
|
||||
void EndTracing();
|
||||
|
||||
void InitializeRingBuffer(uint32_t ptr, uint32_t page_count);
|
||||
void EnableReadPointerWriteBack(uint32_t ptr, uint32_t block_size);
|
||||
|
||||
void UpdateWritePointer(uint32_t value);
|
||||
|
||||
void ExecutePacket(uint32_t ptr, uint32_t count);
|
||||
|
||||
protected:
|
||||
class RingbufferReader;
|
||||
|
||||
struct IndexBufferInfo {
|
||||
xenos::IndexFormat format = xenos::IndexFormat::kInt16;
|
||||
xenos::Endian endianness = xenos::Endian::kUnspecified;
|
||||
uint32_t count = 0;
|
||||
uint32_t guest_base = 0;
|
||||
size_t length = 0;
|
||||
};
|
||||
|
||||
void WorkerThreadMain();
|
||||
virtual bool SetupContext() = 0;
|
||||
virtual void ShutdownContext() = 0;
|
||||
|
||||
void WriteRegister(uint32_t index, uint32_t value);
|
||||
|
||||
virtual void MakeCoherent();
|
||||
virtual void PrepareForWait();
|
||||
virtual void ReturnFromWait();
|
||||
|
||||
virtual void PerformSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
|
||||
uint32_t frontbuffer_height) = 0;
|
||||
|
||||
void ExecutePrimaryBuffer(uint32_t start_index, uint32_t end_index);
|
||||
void ExecuteIndirectBuffer(uint32_t ptr, uint32_t length);
|
||||
bool ExecutePacket(RingbufferReader* reader);
|
||||
bool ExecutePacketType0(RingbufferReader* reader, uint32_t packet);
|
||||
bool ExecutePacketType1(RingbufferReader* reader, uint32_t packet);
|
||||
bool ExecutePacketType2(RingbufferReader* reader, uint32_t packet);
|
||||
bool ExecutePacketType3(RingbufferReader* reader, uint32_t packet);
|
||||
bool ExecutePacketType3_ME_INIT(RingbufferReader* reader, uint32_t packet,
|
||||
uint32_t count);
|
||||
bool ExecutePacketType3_NOP(RingbufferReader* reader, uint32_t packet,
|
||||
uint32_t count);
|
||||
bool ExecutePacketType3_INTERRUPT(RingbufferReader* reader, uint32_t packet,
|
||||
uint32_t count);
|
||||
bool ExecutePacketType3_XE_SWAP(RingbufferReader* reader, uint32_t packet,
|
||||
uint32_t count);
|
||||
bool ExecutePacketType3_INDIRECT_BUFFER(RingbufferReader* reader,
|
||||
uint32_t packet, uint32_t count);
|
||||
bool ExecutePacketType3_WAIT_REG_MEM(RingbufferReader* reader,
|
||||
uint32_t packet, uint32_t count);
|
||||
bool ExecutePacketType3_REG_RMW(RingbufferReader* reader, uint32_t packet,
|
||||
uint32_t count);
|
||||
bool ExecutePacketType3_COND_WRITE(RingbufferReader* reader, uint32_t packet,
|
||||
uint32_t count);
|
||||
bool ExecutePacketType3_EVENT_WRITE(RingbufferReader* reader, uint32_t packet,
|
||||
uint32_t count);
|
||||
bool ExecutePacketType3_EVENT_WRITE_SHD(RingbufferReader* reader,
|
||||
uint32_t packet, uint32_t count);
|
||||
bool ExecutePacketType3_EVENT_WRITE_EXT(RingbufferReader* reader,
|
||||
uint32_t packet, uint32_t count);
|
||||
bool ExecutePacketType3_DRAW_INDX(RingbufferReader* reader, uint32_t packet,
|
||||
uint32_t count);
|
||||
bool ExecutePacketType3_DRAW_INDX_2(RingbufferReader* reader, uint32_t packet,
|
||||
uint32_t count);
|
||||
bool ExecutePacketType3_SET_CONSTANT(RingbufferReader* reader,
|
||||
uint32_t packet, uint32_t count);
|
||||
bool ExecutePacketType3_SET_CONSTANT2(RingbufferReader* reader,
|
||||
uint32_t packet, uint32_t count);
|
||||
bool ExecutePacketType3_LOAD_ALU_CONSTANT(RingbufferReader* reader,
|
||||
uint32_t packet, uint32_t count);
|
||||
bool ExecutePacketType3_SET_SHADER_CONSTANTS(RingbufferReader* reader,
|
||||
uint32_t packet, uint32_t count);
|
||||
bool ExecutePacketType3_IM_LOAD(RingbufferReader* reader, uint32_t packet,
|
||||
uint32_t count);
|
||||
bool ExecutePacketType3_IM_LOAD_IMMEDIATE(RingbufferReader* reader,
|
||||
|
||||
uint32_t packet, uint32_t count);
|
||||
bool ExecutePacketType3_INVALIDATE_STATE(RingbufferReader* reader,
|
||||
uint32_t packet, uint32_t count);
|
||||
|
||||
virtual bool LoadShader(ShaderType shader_type, uint32_t guest_address,
|
||||
const uint32_t* host_address,
|
||||
uint32_t dword_count) = 0;
|
||||
|
||||
virtual bool IssueDraw(PrimitiveType prim_type, uint32_t index_count,
|
||||
IndexBufferInfo* index_buffer_info) = 0;
|
||||
virtual bool IssueCopy() = 0;
|
||||
|
||||
Memory* memory_ = nullptr;
|
||||
kernel::KernelState* kernel_state_ = nullptr;
|
||||
GraphicsSystem* graphics_system_ = nullptr;
|
||||
RegisterFile* register_file_ = nullptr;
|
||||
|
||||
TraceWriter trace_writer_;
|
||||
enum class TraceState {
|
||||
kDisabled,
|
||||
kStreaming,
|
||||
kSingleFrame,
|
||||
};
|
||||
TraceState trace_state_ = TraceState::kDisabled;
|
||||
std::wstring trace_frame_path_;
|
||||
|
||||
std::atomic<bool> worker_running_;
|
||||
kernel::object_ref<kernel::XHostThread> worker_thread_;
|
||||
|
||||
std::unique_ptr<xe::ui::GraphicsContext> context_;
|
||||
SwapMode swap_mode_ = SwapMode::kNormal;
|
||||
SwapState swap_state_;
|
||||
std::function<void()> swap_request_handler_;
|
||||
std::queue<std::function<void()>> pending_fns_;
|
||||
|
||||
uint32_t counter_ = 0;
|
||||
|
||||
uint32_t primary_buffer_ptr_ = 0;
|
||||
uint32_t primary_buffer_size_ = 0;
|
||||
|
||||
uint32_t read_ptr_index_ = 0;
|
||||
uint32_t read_ptr_update_freq_ = 0;
|
||||
uint32_t read_ptr_writeback_ptr_ = 0;
|
||||
|
||||
std::unique_ptr<xe::threading::Event> write_ptr_index_event_;
|
||||
std::atomic<uint32_t> write_ptr_index_;
|
||||
|
||||
uint64_t bin_select_ = 0xFFFFFFFFull;
|
||||
uint64_t bin_mask_ = 0xFFFFFFFFull;
|
||||
};
|
||||
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_GPU_COMMAND_PROCESSOR_H_
|
File diff suppressed because it is too large
Load Diff
|
@ -21,82 +21,31 @@
|
|||
#include <vector>
|
||||
|
||||
#include "xenia/base/threading.h"
|
||||
#include "xenia/gpu/command_processor.h"
|
||||
#include "xenia/gpu/gl4/draw_batcher.h"
|
||||
#include "xenia/gpu/gl4/gl4_shader.h"
|
||||
#include "xenia/gpu/gl4/gl4_shader_translator.h"
|
||||
#include "xenia/gpu/gl4/texture_cache.h"
|
||||
#include "xenia/gpu/register_file.h"
|
||||
#include "xenia/gpu/tracing.h"
|
||||
#include "xenia/gpu/xenos.h"
|
||||
#include "xenia/kernel/xthread.h"
|
||||
#include "xenia/memory.h"
|
||||
#include "xenia/ui/gl/circular_buffer.h"
|
||||
#include "xenia/ui/gl/gl_context.h"
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
class XHostThread;
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
namespace gl4 {
|
||||
|
||||
class GL4GraphicsSystem;
|
||||
|
||||
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.
|
||||
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 {
|
||||
class GL4CommandProcessor : public CommandProcessor {
|
||||
public:
|
||||
explicit CommandProcessor(GL4GraphicsSystem* graphics_system);
|
||||
~CommandProcessor();
|
||||
GL4CommandProcessor(GL4GraphicsSystem* graphics_system,
|
||||
kernel::KernelState* kernel_state);
|
||||
~GL4CommandProcessor() override;
|
||||
|
||||
uint32_t counter() const { return counter_; }
|
||||
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);
|
||||
void ClearCaches() override;
|
||||
|
||||
// HACK: for debugging; would be good to have this in a base type.
|
||||
TextureCache* texture_cache() { return &texture_cache_; }
|
||||
|
@ -111,8 +60,6 @@ class CommandProcessor {
|
|||
xenos::DepthRenderTargetFormat format);
|
||||
|
||||
private:
|
||||
class RingbufferReader;
|
||||
|
||||
enum class UpdateStatus {
|
||||
kCompatible,
|
||||
kMismatch,
|
||||
|
@ -153,69 +100,22 @@ class CommandProcessor {
|
|||
} handles;
|
||||
};
|
||||
|
||||
void WorkerThreadMain();
|
||||
bool SetupGL();
|
||||
void ShutdownGL();
|
||||
bool SetupContext() override;
|
||||
void ShutdownContext() override;
|
||||
GLuint CreateGeometryProgram(const std::string& source);
|
||||
|
||||
void WriteRegister(uint32_t index, uint32_t value);
|
||||
void MakeCoherent();
|
||||
void PrepareForWait();
|
||||
void ReturnFromWait();
|
||||
void MakeCoherent() override;
|
||||
void PrepareForWait() override;
|
||||
void ReturnFromWait() override;
|
||||
|
||||
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);
|
||||
void PerformSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
|
||||
uint32_t frontbuffer_height) override;
|
||||
|
||||
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 UpdateRenderTargets();
|
||||
UpdateStatus UpdateState();
|
||||
|
@ -223,77 +123,32 @@ class CommandProcessor {
|
|||
UpdateStatus UpdateRasterizerState();
|
||||
UpdateStatus UpdateBlendState();
|
||||
UpdateStatus UpdateDepthStencilState();
|
||||
UpdateStatus PopulateIndexBuffer();
|
||||
UpdateStatus PopulateIndexBuffer(IndexBufferInfo* index_buffer_info);
|
||||
UpdateStatus PopulateVertexBuffers();
|
||||
UpdateStatus PopulateSamplers();
|
||||
UpdateStatus PopulateSampler(const Shader::SamplerDesc& desc);
|
||||
bool IssueCopy();
|
||||
bool IssueCopy() override;
|
||||
|
||||
CachedFramebuffer* GetFramebuffer(GLuint color_targets[4],
|
||||
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_;
|
||||
std::vector<std::unique_ptr<GL4Shader>> all_shaders_;
|
||||
std::unordered_map<uint64_t, GL4Shader*> shader_cache_;
|
||||
GL4Shader* active_vertex_shader_;
|
||||
GL4Shader* active_pixel_shader_;
|
||||
CachedFramebuffer* active_framebuffer_;
|
||||
GLuint last_framebuffer_texture_;
|
||||
GL4Shader* active_vertex_shader_ = nullptr;
|
||||
GL4Shader* active_pixel_shader_ = nullptr;
|
||||
CachedFramebuffer* active_framebuffer_ = nullptr;
|
||||
GLuint last_framebuffer_texture_ = 0;
|
||||
|
||||
std::vector<CachedFramebuffer> cached_framebuffers_;
|
||||
std::vector<CachedColorRenderTarget> cached_color_render_targets_;
|
||||
std::vector<CachedDepthRenderTarget> cached_depth_render_targets_;
|
||||
std::vector<std::unique_ptr<CachedPipeline>> all_pipelines_;
|
||||
std::unordered_map<uint64_t, CachedPipeline*> cached_pipelines_;
|
||||
GLuint point_list_geometry_program_;
|
||||
GLuint rect_list_geometry_program_;
|
||||
GLuint quad_list_geometry_program_;
|
||||
GLuint line_quad_list_geometry_program_;
|
||||
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_;
|
||||
GLuint point_list_geometry_program_ = 0;
|
||||
GLuint rect_list_geometry_program_ = 0;
|
||||
GLuint quad_list_geometry_program_ = 0;
|
||||
GLuint line_quad_list_geometry_program_ = 0;
|
||||
|
||||
TextureCache texture_cache_;
|
||||
|
|
@ -12,41 +12,20 @@
|
|||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include "xenia/base/clock.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/profiling.h"
|
||||
#include "xenia/base/threading.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/gpu_flags.h"
|
||||
#include "xenia/gpu/tracing.h"
|
||||
#include "xenia/ui/window.h"
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
namespace gl4 {
|
||||
|
||||
void InitializeIfNeeded();
|
||||
void CleanupOnShutdown();
|
||||
|
||||
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<GraphicsSystem> Create() {
|
||||
return std::make_unique<GL4GraphicsSystem>();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
GL4GraphicsSystem::GL4GraphicsSystem(Emulator* emulator)
|
||||
: GraphicsSystem(emulator), worker_running_(false) {}
|
||||
GL4GraphicsSystem::GL4GraphicsSystem() : GraphicsSystem() {}
|
||||
|
||||
GL4GraphicsSystem::~GL4GraphicsSystem() = default;
|
||||
|
||||
X_STATUS GL4GraphicsSystem::Setup(cpu::Processor* processor,
|
||||
ui::Loop* target_loop,
|
||||
kernel::KernelState* kernel_state,
|
||||
ui::Window* target_window) {
|
||||
auto result = GraphicsSystem::Setup(processor, target_loop, target_window);
|
||||
auto result = GraphicsSystem::Setup(processor, kernel_state, target_window);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
@ -73,224 +51,14 @@ X_STATUS GL4GraphicsSystem::Setup(cpu::Processor* processor,
|
|||
display_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;
|
||||
}
|
||||
|
||||
void GL4GraphicsSystem::Shutdown() {
|
||||
EndTracing();
|
||||
void GL4GraphicsSystem::Shutdown() { GraphicsSystem::Shutdown(); }
|
||||
|
||||
worker_running_ = false;
|
||||
worker_thread_->Wait(0, 0, 0, nullptr);
|
||||
worker_thread_.reset();
|
||||
|
||||
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);
|
||||
std::unique_ptr<CommandProcessor> GL4GraphicsSystem::CreateCommandProcessor() {
|
||||
return std::unique_ptr<CommandProcessor>(
|
||||
new GL4CommandProcessor(this, kernel_state_));
|
||||
}
|
||||
|
||||
void GL4GraphicsSystem::Swap(xe::ui::UIEvent* e) {
|
||||
|
@ -315,51 +83,12 @@ void GL4GraphicsSystem::Swap(xe::ui::UIEvent* e) {
|
|||
|
||||
// Blit the frontbuffer.
|
||||
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, target_window_->width(), target_window_->height()),
|
||||
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 gpu
|
||||
} // namespace xe
|
||||
|
|
|
@ -12,10 +12,7 @@
|
|||
|
||||
#include <memory>
|
||||
|
||||
#include "xenia/gpu/gl4/command_processor.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"
|
||||
|
||||
namespace xe {
|
||||
|
@ -24,53 +21,22 @@ namespace gl4 {
|
|||
|
||||
class GL4GraphicsSystem : public GraphicsSystem {
|
||||
public:
|
||||
explicit GL4GraphicsSystem(Emulator* emulator);
|
||||
GL4GraphicsSystem();
|
||||
~GL4GraphicsSystem() override;
|
||||
|
||||
std::unique_ptr<ui::GraphicsContext> CreateContext(
|
||||
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;
|
||||
void Shutdown() override;
|
||||
|
||||
RegisterFile* register_file() { return ®ister_file_; }
|
||||
CommandProcessor* command_processor() const {
|
||||
return command_processor_.get();
|
||||
}
|
||||
|
||||
void InitializeRingBuffer(uint32_t ptr, uint32_t page_count) override;
|
||||
void EnableReadPointerWriteBack(uint32_t ptr, uint32_t block_size) override;
|
||||
|
||||
void RequestFrameTrace() override;
|
||||
void BeginTracing() override;
|
||||
void EndTracing() override;
|
||||
void PlayTrace(const uint8_t* trace_data, size_t trace_size,
|
||||
TracePlaybackMode playback_mode) override;
|
||||
void ClearCaches() override;
|
||||
|
||||
private:
|
||||
void MarkVblank();
|
||||
void Swap(xe::ui::UIEvent* e);
|
||||
uint32_t ReadRegister(uint32_t addr);
|
||||
void WriteRegister(uint32_t addr, uint32_t value);
|
||||
std::unique_ptr<CommandProcessor> CreateCommandProcessor() override;
|
||||
|
||||
static uint32_t MMIOReadRegisterThunk(void* ppc_context,
|
||||
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_;
|
||||
void Swap(xe::ui::UIEvent* e) override;
|
||||
|
||||
xe::ui::gl::GLContext* display_context_ = nullptr;
|
||||
|
||||
std::atomic<bool> worker_running_;
|
||||
kernel::object_ref<kernel::XHostThread> worker_thread_;
|
||||
};
|
||||
|
||||
} // namespace gl4
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
*/
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "third_party/imgui/imgui.h"
|
||||
|
@ -19,16 +20,21 @@
|
|||
#include "xenia/base/platform_win.h"
|
||||
#include "xenia/base/profiling.h"
|
||||
#include "xenia/emulator.h"
|
||||
#include "xenia/gpu/command_processor.h"
|
||||
#include "xenia/gpu/graphics_system.h"
|
||||
#include "xenia/gpu/packet_disassembler.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/ui/gl/gl_context.h"
|
||||
#include "xenia/ui/imgui_drawer.h"
|
||||
#include "xenia/ui/window.h"
|
||||
|
||||
// HACK: until we have another impl, we just use gl4 directly.
|
||||
#include "xenia/gpu/gl4/command_processor.h"
|
||||
#include "xenia/gpu/gl4/gl4_command_processor.h"
|
||||
#include "xenia/gpu/gl4/gl4_graphics_system.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 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;
|
||||
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,
|
||||
Memory* memory) {
|
||||
|
@ -1126,7 +329,7 @@ void DrawFailedTextureInfo(const Shader::SamplerDesc& desc,
|
|||
}
|
||||
void DrawTextureInfo(TracePlayer& player, const Shader::SamplerDesc& desc) {
|
||||
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();
|
||||
|
||||
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) {
|
||||
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();
|
||||
|
||||
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);
|
||||
if (pending_packet) {
|
||||
PacketInfo packet_info = {0};
|
||||
if (DisasmPacket(reinterpret_cast<const uint8_t*>(pending_packet) +
|
||||
sizeof(PacketStartCommand),
|
||||
&packet_info)) {
|
||||
if (PacketDisassembler::DisasmPacket(
|
||||
reinterpret_cast<const uint8_t*>(pending_packet) +
|
||||
sizeof(PacketStartCommand),
|
||||
&packet_info)) {
|
||||
if (packet_info.predicated) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, kColorIgnored);
|
||||
}
|
|
@ -66,7 +66,7 @@ project("xenia-gpu-gl4-trace-viewer")
|
|||
project_root.."/build_tools/third_party/gflags/src",
|
||||
})
|
||||
files({
|
||||
"trace_viewer_main.cc",
|
||||
"gl4_trace_viewer_main.cc",
|
||||
"../../base/main_"..platform_suffix..".cc",
|
||||
})
|
||||
|
||||
|
|
|
@ -9,27 +9,30 @@
|
|||
|
||||
#include "xenia/gpu/graphics_system.h"
|
||||
|
||||
#include "xenia/base/clock.h"
|
||||
#include "xenia/base/logging.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/kernel/xthread.h"
|
||||
#include "xenia/ui/loop.h"
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
|
||||
namespace gl4 {
|
||||
std::unique_ptr<GraphicsSystem> Create(Emulator* emulator);
|
||||
std::unique_ptr<GraphicsSystem> Create();
|
||||
} // namespace gl4
|
||||
|
||||
std::unique_ptr<GraphicsSystem> GraphicsSystem::Create(Emulator* emulator) {
|
||||
std::unique_ptr<GraphicsSystem> GraphicsSystem::Create() {
|
||||
if (FLAGS_gpu.compare("gl4") == 0) {
|
||||
return xe::gpu::gl4::Create(emulator);
|
||||
return xe::gpu::gl4::Create();
|
||||
} else {
|
||||
// Create best available.
|
||||
std::unique_ptr<GraphicsSystem> best;
|
||||
|
||||
best = xe::gpu::gl4::Create(emulator);
|
||||
best = xe::gpu::gl4::Create();
|
||||
if (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;
|
||||
|
||||
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) {
|
||||
processor_ = processor;
|
||||
memory_ = processor->memory();
|
||||
target_loop_ = target_loop;
|
||||
processor_ = processor;
|
||||
kernel_state_ = kernel_state;
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
uint32_t user_data) {
|
||||
|
@ -84,5 +223,32 @@ void GraphicsSystem::DispatchInterruptCallback(uint32_t source, uint32_t cpu) {
|
|||
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 xe
|
||||
|
|
|
@ -15,8 +15,9 @@
|
|||
#include <thread>
|
||||
|
||||
#include "xenia/cpu/processor.h"
|
||||
#include "xenia/gpu/register_file.h"
|
||||
#include "xenia/kernel/xthread.h"
|
||||
#include "xenia/memory.h"
|
||||
#include "xenia/ui/loop.h"
|
||||
#include "xenia/ui/window.h"
|
||||
#include "xenia/xbox.h"
|
||||
|
||||
|
@ -27,51 +28,70 @@ class Emulator;
|
|||
namespace xe {
|
||||
namespace gpu {
|
||||
|
||||
class CommandProcessor;
|
||||
|
||||
class GraphicsSystem {
|
||||
public:
|
||||
virtual ~GraphicsSystem();
|
||||
|
||||
static std::unique_ptr<GraphicsSystem> Create(Emulator* emulator);
|
||||
static std::unique_ptr<GraphicsSystem> Create();
|
||||
virtual std::unique_ptr<ui::GraphicsContext> CreateContext(
|
||||
ui::Window* target_window) = 0;
|
||||
|
||||
Emulator* emulator() const { return emulator_; }
|
||||
Memory* memory() const { return memory_; }
|
||||
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);
|
||||
virtual void Shutdown();
|
||||
|
||||
void SetInterruptCallback(uint32_t callback, uint32_t user_data);
|
||||
virtual void InitializeRingBuffer(uint32_t ptr, uint32_t page_count) = 0;
|
||||
virtual void EnableReadPointerWriteBack(uint32_t ptr,
|
||||
uint32_t block_size) = 0;
|
||||
RegisterFile* register_file() { return ®ister_file_; }
|
||||
CommandProcessor* command_processor() const {
|
||||
return command_processor_.get();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
virtual void RequestFrameTrace() {}
|
||||
virtual void BeginTracing() {}
|
||||
virtual void EndTracing() {}
|
||||
enum class TracePlaybackMode {
|
||||
kUntilEnd,
|
||||
kBreakOnSwap,
|
||||
};
|
||||
virtual void PlayTrace(const uint8_t* trace_data, size_t trace_size,
|
||||
TracePlaybackMode playback_mode) {}
|
||||
virtual void ClearCaches() {}
|
||||
virtual void ClearCaches();
|
||||
|
||||
void RequestFrameTrace();
|
||||
void BeginTracing();
|
||||
void EndTracing();
|
||||
|
||||
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;
|
||||
cpu::Processor* processor_ = nullptr;
|
||||
ui::Loop* target_loop_ = nullptr;
|
||||
kernel::KernelState* kernel_state_ = nullptr;
|
||||
ui::Window* target_window_ = nullptr;
|
||||
|
||||
uint32_t interrupt_callback_ = 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
|
||||
|
|
|
@ -0,0 +1,498 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/gpu/packet_disassembler.h"
|
||||
|
||||
#include "xenia/gpu/xenos.h"
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
|
||||
using namespace xe::gpu::xenos;
|
||||
|
||||
PacketCategory PacketDisassembler::GetPacketCategory(const uint8_t* base_ptr) {
|
||||
const uint32_t packet = xe::load_and_swap<uint32_t>(base_ptr);
|
||||
const uint32_t packet_type = packet >> 30;
|
||||
switch (packet_type) {
|
||||
case 0x00:
|
||||
case 0x01:
|
||||
case 0x02: {
|
||||
return PacketCategory::kGeneric;
|
||||
}
|
||||
case 0x03: {
|
||||
uint32_t opcode = (packet >> 8) & 0x7F;
|
||||
switch (opcode) {
|
||||
case PM4_DRAW_INDX:
|
||||
case PM4_DRAW_INDX_2:
|
||||
return PacketCategory::kDraw;
|
||||
case PM4_XE_SWAP:
|
||||
return PacketCategory::kSwap;
|
||||
default:
|
||||
return PacketCategory::kGeneric;
|
||||
}
|
||||
}
|
||||
default: {
|
||||
assert_unhandled_case(packet_type);
|
||||
return PacketCategory::kGeneric;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PacketDisassembler::DisasmPacketType0(const uint8_t* base_ptr,
|
||||
uint32_t packet,
|
||||
PacketInfo* out_info) {
|
||||
static const PacketTypeInfo type_0_info = {PacketCategory::kGeneric,
|
||||
"PM4_TYPE0"};
|
||||
out_info->type_info = &type_0_info;
|
||||
|
||||
uint32_t count = ((packet >> 16) & 0x3FFF) + 1;
|
||||
out_info->count = 1 + count;
|
||||
auto ptr = base_ptr + 4;
|
||||
|
||||
uint32_t base_index = (packet & 0x7FFF);
|
||||
uint32_t write_one_reg = (packet >> 15) & 0x1;
|
||||
for (uint32_t m = 0; m < count; m++) {
|
||||
uint32_t reg_data = xe::load_and_swap<uint32_t>(ptr);
|
||||
uint32_t target_index = write_one_reg ? base_index : base_index + m;
|
||||
out_info->actions.emplace_back(
|
||||
PacketAction::RegisterWrite(target_index, reg_data));
|
||||
ptr += 4;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PacketDisassembler::DisasmPacketType1(const uint8_t* base_ptr,
|
||||
uint32_t packet,
|
||||
PacketInfo* out_info) {
|
||||
static const PacketTypeInfo type_1_info = {PacketCategory::kGeneric,
|
||||
"PM4_TYPE1"};
|
||||
out_info->type_info = &type_1_info;
|
||||
|
||||
out_info->count = 1 + 2;
|
||||
auto ptr = base_ptr + 4;
|
||||
|
||||
uint32_t reg_index_1 = packet & 0x7FF;
|
||||
uint32_t reg_index_2 = (packet >> 11) & 0x7FF;
|
||||
uint32_t reg_data_1 = xe::load_and_swap<uint32_t>(ptr);
|
||||
uint32_t reg_data_2 = xe::load_and_swap<uint32_t>(ptr + 4);
|
||||
out_info->actions.emplace_back(
|
||||
PacketAction::RegisterWrite(reg_index_1, reg_data_1));
|
||||
out_info->actions.emplace_back(
|
||||
PacketAction::RegisterWrite(reg_index_2, reg_data_2));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PacketDisassembler::DisasmPacketType2(const uint8_t* base_ptr,
|
||||
uint32_t packet,
|
||||
PacketInfo* out_info) {
|
||||
static const PacketTypeInfo type_2_info = {PacketCategory::kGeneric,
|
||||
"PM4_TYPE2"};
|
||||
out_info->type_info = &type_2_info;
|
||||
|
||||
out_info->count = 1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PacketDisassembler::DisasmPacketType3(const uint8_t* base_ptr,
|
||||
uint32_t packet,
|
||||
PacketInfo* out_info) {
|
||||
static const PacketTypeInfo type_3_unknown_info = {PacketCategory::kGeneric,
|
||||
"PM4_TYPE3_UNKNOWN"};
|
||||
out_info->type_info = &type_3_unknown_info;
|
||||
|
||||
uint32_t opcode = (packet >> 8) & 0x7F;
|
||||
uint32_t count = ((packet >> 16) & 0x3FFF) + 1;
|
||||
out_info->count = 1 + count;
|
||||
auto ptr = base_ptr + 4;
|
||||
|
||||
if (packet & 1) {
|
||||
out_info->predicated = true;
|
||||
}
|
||||
|
||||
bool result = true;
|
||||
switch (opcode) {
|
||||
case PM4_ME_INIT: {
|
||||
// initialize CP's micro-engine
|
||||
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
|
||||
"PM4_ME_INIT"};
|
||||
out_info->type_info = &op_info;
|
||||
break;
|
||||
}
|
||||
case PM4_NOP: {
|
||||
// skip N 32-bit words to get to the next packet
|
||||
// No-op, ignore some data.
|
||||
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
|
||||
"PM4_NOP"};
|
||||
out_info->type_info = &op_info;
|
||||
break;
|
||||
}
|
||||
case PM4_INTERRUPT: {
|
||||
// generate interrupt from the command stream
|
||||
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
|
||||
"PM4_INTERRUPT"};
|
||||
out_info->type_info = &op_info;
|
||||
uint32_t cpu_mask = xe::load_and_swap<uint32_t>(ptr + 0);
|
||||
for (int n = 0; n < 6; n++) {
|
||||
if (cpu_mask & (1 << n)) {
|
||||
// graphics_system_->DispatchInterruptCallback(1, n);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PM4_XE_SWAP: {
|
||||
// Xenia-specific VdSwap hook.
|
||||
// VdSwap will post this to tell us we need to swap the screen/fire an
|
||||
// interrupt.
|
||||
// 63 words here, but only the first has any data.
|
||||
static const PacketTypeInfo op_info = {PacketCategory::kSwap,
|
||||
"PM4_XE_SWAP"};
|
||||
out_info->type_info = &op_info;
|
||||
uint32_t frontbuffer_ptr = xe::load_and_swap<uint32_t>(ptr + 0);
|
||||
break;
|
||||
}
|
||||
case PM4_INDIRECT_BUFFER: {
|
||||
// indirect buffer dispatch
|
||||
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
|
||||
"PM4_INDIRECT_BUFFER"};
|
||||
out_info->type_info = &op_info;
|
||||
uint32_t list_ptr = xe::load_and_swap<uint32_t>(ptr + 0);
|
||||
uint32_t list_length = xe::load_and_swap<uint32_t>(ptr + 4);
|
||||
break;
|
||||
}
|
||||
case PM4_WAIT_REG_MEM: {
|
||||
// wait until a register or memory location is a specific value
|
||||
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
|
||||
"PM4_WAIT_REG_MEM"};
|
||||
out_info->type_info = &op_info;
|
||||
uint32_t wait_info = xe::load_and_swap<uint32_t>(ptr + 0);
|
||||
uint32_t poll_reg_addr = xe::load_and_swap<uint32_t>(ptr + 4);
|
||||
uint32_t ref = xe::load_and_swap<uint32_t>(ptr + 8);
|
||||
uint32_t mask = xe::load_and_swap<uint32_t>(ptr + 12);
|
||||
uint32_t wait = xe::load_and_swap<uint32_t>(ptr + 16);
|
||||
break;
|
||||
}
|
||||
case PM4_REG_RMW: {
|
||||
// register read/modify/write
|
||||
// ? (used during shader upload and edram setup)
|
||||
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
|
||||
"PM4_REG_RMW"};
|
||||
out_info->type_info = &op_info;
|
||||
uint32_t rmw_info = xe::load_and_swap<uint32_t>(ptr + 0);
|
||||
uint32_t and_mask = xe::load_and_swap<uint32_t>(ptr + 4);
|
||||
uint32_t or_mask = xe::load_and_swap<uint32_t>(ptr + 8);
|
||||
break;
|
||||
}
|
||||
case PM4_COND_WRITE: {
|
||||
// conditional write to memory or register
|
||||
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
|
||||
"PM4_COND_WRITE"};
|
||||
out_info->type_info = &op_info;
|
||||
uint32_t wait_info = xe::load_and_swap<uint32_t>(ptr + 0);
|
||||
uint32_t poll_reg_addr = xe::load_and_swap<uint32_t>(ptr + 4);
|
||||
uint32_t ref = xe::load_and_swap<uint32_t>(ptr + 8);
|
||||
uint32_t mask = xe::load_and_swap<uint32_t>(ptr + 12);
|
||||
uint32_t write_reg_addr = xe::load_and_swap<uint32_t>(ptr + 16);
|
||||
uint32_t write_data = xe::load_and_swap<uint32_t>(ptr + 20);
|
||||
break;
|
||||
}
|
||||
case PM4_EVENT_WRITE: {
|
||||
// generate an event that creates a write to memory when completed
|
||||
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
|
||||
"PM4_EVENT_WRITE"};
|
||||
out_info->type_info = &op_info;
|
||||
uint32_t initiator = xe::load_and_swap<uint32_t>(ptr + 0);
|
||||
break;
|
||||
}
|
||||
case PM4_EVENT_WRITE_SHD: {
|
||||
// generate a VS|PS_done event
|
||||
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
|
||||
"PM4_EVENT_WRITE_SHD"};
|
||||
out_info->type_info = &op_info;
|
||||
uint32_t initiator = xe::load_and_swap<uint32_t>(ptr + 0);
|
||||
uint32_t address = xe::load_and_swap<uint32_t>(ptr + 4);
|
||||
uint32_t value = xe::load_and_swap<uint32_t>(ptr + 8);
|
||||
break;
|
||||
}
|
||||
case PM4_EVENT_WRITE_EXT: {
|
||||
// generate a screen extent event
|
||||
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
|
||||
"PM4_EVENT_WRITE_EXT"};
|
||||
out_info->type_info = &op_info;
|
||||
uint32_t unk0 = xe::load_and_swap<uint32_t>(ptr + 0);
|
||||
uint32_t unk1 = xe::load_and_swap<uint32_t>(ptr + 4);
|
||||
break;
|
||||
}
|
||||
case PM4_DRAW_INDX: {
|
||||
// initiate fetch of index buffer and draw
|
||||
// dword0 = viz query info
|
||||
static const PacketTypeInfo op_info = {PacketCategory::kDraw,
|
||||
"PM4_DRAW_INDX"};
|
||||
out_info->type_info = &op_info;
|
||||
uint32_t dword0 = xe::load_and_swap<uint32_t>(ptr + 0);
|
||||
uint32_t dword1 = xe::load_and_swap<uint32_t>(ptr + 4);
|
||||
uint32_t index_count = dword1 >> 16;
|
||||
auto prim_type = static_cast<PrimitiveType>(dword1 & 0x3F);
|
||||
uint32_t src_sel = (dword1 >> 6) & 0x3;
|
||||
if (src_sel == 0x0) {
|
||||
// Indexed draw.
|
||||
uint32_t guest_base = xe::load_and_swap<uint32_t>(ptr + 8);
|
||||
uint32_t index_size = xe::load_and_swap<uint32_t>(ptr + 12);
|
||||
auto endianness = static_cast<Endian>(index_size >> 30);
|
||||
index_size &= 0x00FFFFFF;
|
||||
bool index_32bit = (dword1 >> 11) & 0x1;
|
||||
index_size *= index_32bit ? 4 : 2;
|
||||
} else if (src_sel == 0x2) {
|
||||
// Auto draw.
|
||||
} else {
|
||||
// Unknown source select.
|
||||
assert_always();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PM4_DRAW_INDX_2: {
|
||||
// draw using supplied indices in packet
|
||||
static const PacketTypeInfo op_info = {PacketCategory::kDraw,
|
||||
"PM4_DRAW_INDX_2"};
|
||||
out_info->type_info = &op_info;
|
||||
uint32_t dword0 = xe::load_and_swap<uint32_t>(ptr + 0);
|
||||
uint32_t index_count = dword0 >> 16;
|
||||
auto prim_type = static_cast<PrimitiveType>(dword0 & 0x3F);
|
||||
uint32_t src_sel = (dword0 >> 6) & 0x3;
|
||||
assert_true(src_sel == 0x2); // 'SrcSel=AutoIndex'
|
||||
bool index_32bit = (dword0 >> 11) & 0x1;
|
||||
uint32_t indices_size = index_count * (index_32bit ? 4 : 2);
|
||||
auto index_ptr = ptr + 4;
|
||||
break;
|
||||
}
|
||||
case PM4_SET_CONSTANT: {
|
||||
// load constant into chip and to memory
|
||||
// PM4_REG(reg) ((0x4 << 16) | (GSL_HAL_SUBBLOCK_OFFSET(reg)))
|
||||
// reg - 0x2000
|
||||
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
|
||||
"PM4_SET_CONSTANT"};
|
||||
out_info->type_info = &op_info;
|
||||
uint32_t offset_type = xe::load_and_swap<uint32_t>(ptr + 0);
|
||||
uint32_t index = offset_type & 0x7FF;
|
||||
uint32_t type = (offset_type >> 16) & 0xFF;
|
||||
switch (type) {
|
||||
case 0: // ALU
|
||||
index += 0x4000;
|
||||
break;
|
||||
case 1: // FETCH
|
||||
index += 0x4800;
|
||||
break;
|
||||
case 2: // BOOL
|
||||
index += 0x4900;
|
||||
break;
|
||||
case 3: // LOOP
|
||||
index += 0x4908;
|
||||
break;
|
||||
case 4: // REGISTERS
|
||||
index += 0x2000;
|
||||
break;
|
||||
default:
|
||||
assert_always();
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
for (uint32_t n = 0; n < count - 1; n++, index++) {
|
||||
uint32_t data = xe::load_and_swap<uint32_t>(ptr + 4 + n * 4);
|
||||
out_info->actions.emplace_back(
|
||||
PacketAction::RegisterWrite(index, data));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PM4_SET_CONSTANT2: {
|
||||
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
|
||||
"PM4_SET_CONSTANT2"};
|
||||
out_info->type_info = &op_info;
|
||||
uint32_t offset_type = xe::load_and_swap<uint32_t>(ptr + 0);
|
||||
uint32_t index = offset_type & 0xFFFF;
|
||||
for (uint32_t n = 0; n < count - 1; n++, index++) {
|
||||
uint32_t data = xe::load_and_swap<uint32_t>(ptr + 4 + n * 4);
|
||||
out_info->actions.emplace_back(
|
||||
PacketAction::RegisterWrite(index, data));
|
||||
}
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
case PM4_LOAD_ALU_CONSTANT: {
|
||||
// load constants from memory
|
||||
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
|
||||
"PM4_LOAD_ALU_CONSTANT"};
|
||||
out_info->type_info = &op_info;
|
||||
uint32_t address = xe::load_and_swap<uint32_t>(ptr + 0);
|
||||
address &= 0x3FFFFFFF;
|
||||
uint32_t offset_type = xe::load_and_swap<uint32_t>(ptr + 4);
|
||||
uint32_t index = offset_type & 0x7FF;
|
||||
uint32_t size_dwords = xe::load_and_swap<uint32_t>(ptr + 8);
|
||||
size_dwords &= 0xFFF;
|
||||
uint32_t type = (offset_type >> 16) & 0xFF;
|
||||
switch (type) {
|
||||
case 0: // ALU
|
||||
index += 0x4000;
|
||||
break;
|
||||
case 1: // FETCH
|
||||
index += 0x4800;
|
||||
break;
|
||||
case 2: // BOOL
|
||||
index += 0x4900;
|
||||
break;
|
||||
case 3: // LOOP
|
||||
index += 0x4908;
|
||||
break;
|
||||
case 4: // REGISTERS
|
||||
index += 0x2000;
|
||||
break;
|
||||
default:
|
||||
assert_always();
|
||||
return true;
|
||||
}
|
||||
for (uint32_t n = 0; n < size_dwords; n++, index++) {
|
||||
// Hrm, ?
|
||||
// xe::load_and_swap<uint32_t>(membase_ + GpuToCpu(address + n * 4));
|
||||
uint32_t data = 0xDEADBEEF;
|
||||
out_info->actions.emplace_back(
|
||||
PacketAction::RegisterWrite(index, data));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PM4_SET_SHADER_CONSTANTS: {
|
||||
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
|
||||
"PM4_SET_SHADER_CONSTANTS"};
|
||||
out_info->type_info = &op_info;
|
||||
uint32_t offset_type = xe::load_and_swap<uint32_t>(ptr + 0);
|
||||
uint32_t index = offset_type & 0xFFFF;
|
||||
for (uint32_t n = 0; n < count - 1; n++, index++) {
|
||||
uint32_t data = xe::load_and_swap<uint32_t>(ptr + 4 + n * 4);
|
||||
out_info->actions.emplace_back(
|
||||
PacketAction::RegisterWrite(index, data));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case PM4_IM_LOAD: {
|
||||
// load sequencer instruction memory (pointer-based)
|
||||
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
|
||||
"PM4_IM_LOAD"};
|
||||
out_info->type_info = &op_info;
|
||||
uint32_t addr_type = xe::load_and_swap<uint32_t>(ptr + 0);
|
||||
auto shader_type = static_cast<ShaderType>(addr_type & 0x3);
|
||||
uint32_t addr = addr_type & ~0x3;
|
||||
uint32_t start_size = xe::load_and_swap<uint32_t>(ptr + 4);
|
||||
uint32_t start = start_size >> 16;
|
||||
uint32_t size_dwords = start_size & 0xFFFF; // dwords
|
||||
assert_true(start == 0);
|
||||
break;
|
||||
}
|
||||
case PM4_IM_LOAD_IMMEDIATE: {
|
||||
// load sequencer instruction memory (code embedded in packet)
|
||||
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
|
||||
"PM4_IM_LOAD_IMMEDIATE"};
|
||||
out_info->type_info = &op_info;
|
||||
uint32_t dword0 = xe::load_and_swap<uint32_t>(ptr + 0);
|
||||
uint32_t dword1 = xe::load_and_swap<uint32_t>(ptr + 4);
|
||||
auto shader_type = static_cast<ShaderType>(dword0);
|
||||
uint32_t start_size = dword1;
|
||||
uint32_t start = start_size >> 16;
|
||||
uint32_t size_dwords = start_size & 0xFFFF; // dwords
|
||||
assert_true(start == 0);
|
||||
break;
|
||||
}
|
||||
case PM4_INVALIDATE_STATE: {
|
||||
// selective invalidation of state pointers
|
||||
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
|
||||
"PM4_INVALIDATE_STATE"};
|
||||
out_info->type_info = &op_info;
|
||||
uint32_t mask = xe::load_and_swap<uint32_t>(ptr + 0);
|
||||
break;
|
||||
}
|
||||
case PM4_SET_BIN_MASK_LO: {
|
||||
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
|
||||
"PM4_SET_BIN_MASK_LO"};
|
||||
out_info->type_info = &op_info;
|
||||
uint32_t value = xe::load_and_swap<uint32_t>(ptr);
|
||||
// bin_mask_ = (bin_mask_ & 0xFFFFFFFF00000000ull) | value;
|
||||
out_info->actions.emplace_back(PacketAction::SetBinMask(value));
|
||||
break;
|
||||
}
|
||||
case PM4_SET_BIN_MASK_HI: {
|
||||
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
|
||||
"PM4_SET_BIN_MASK_HI"};
|
||||
out_info->type_info = &op_info;
|
||||
uint32_t value = xe::load_and_swap<uint32_t>(ptr);
|
||||
// bin_mask_ =
|
||||
// (bin_mask_ & 0xFFFFFFFFull) | (static_cast<uint64_t>(value) << 32);
|
||||
break;
|
||||
}
|
||||
case PM4_SET_BIN_SELECT_LO: {
|
||||
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
|
||||
"PM4_SET_BIN_SELECT_LO"};
|
||||
out_info->type_info = &op_info;
|
||||
uint32_t value = xe::load_and_swap<uint32_t>(ptr);
|
||||
// bin_select_ = (bin_select_ & 0xFFFFFFFF00000000ull) | value;
|
||||
out_info->actions.emplace_back(PacketAction::SetBinSelect(value));
|
||||
break;
|
||||
}
|
||||
case PM4_SET_BIN_SELECT_HI: {
|
||||
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
|
||||
"PM4_SET_BIN_SELECT_HI"};
|
||||
out_info->type_info = &op_info;
|
||||
uint32_t value = xe::load_and_swap<uint32_t>(ptr);
|
||||
// bin_select_ =
|
||||
// (bin_select_ & 0xFFFFFFFFull) | (static_cast<uint64_t>(value) <<
|
||||
// 32);
|
||||
break;
|
||||
}
|
||||
|
||||
// Ignored packets - useful if breaking on the default handler below.
|
||||
case 0x50: { // 0xC0015000 usually 2 words, 0xFFFFFFFF / 0x00000000
|
||||
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
|
||||
"PM4_TYPE3_0x50"};
|
||||
out_info->type_info = &op_info;
|
||||
break;
|
||||
}
|
||||
case 0x51: { // 0xC0015100 usually 2 words, 0xFFFFFFFF / 0xFFFFFFFF
|
||||
static const PacketTypeInfo op_info = {PacketCategory::kGeneric,
|
||||
"PM4_TYPE3_0x51"};
|
||||
out_info->type_info = &op_info;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool PacketDisassembler::DisasmPacket(const uint8_t* base_ptr,
|
||||
PacketInfo* out_info) {
|
||||
const uint32_t packet = xe::load_and_swap<uint32_t>(base_ptr);
|
||||
const uint32_t packet_type = packet >> 30;
|
||||
switch (packet_type) {
|
||||
case 0x00:
|
||||
return DisasmPacketType0(base_ptr, packet, out_info);
|
||||
case 0x01:
|
||||
return DisasmPacketType1(base_ptr, packet, out_info);
|
||||
case 0x02:
|
||||
return DisasmPacketType2(base_ptr, packet, out_info);
|
||||
case 0x03:
|
||||
return DisasmPacketType3(base_ptr, packet, out_info);
|
||||
default:
|
||||
assert_unhandled_case(packet_type);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
|
@ -0,0 +1,103 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_GPU_PACKET_DISASSEMBLER_H_
|
||||
#define XENIA_GPU_PACKET_DISASSEMBLER_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/gpu/register_file.h"
|
||||
#include "xenia/gpu/trace_protocol.h"
|
||||
#include "xenia/gpu/trace_reader.h"
|
||||
#include "xenia/memory.h"
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
|
||||
enum class PacketCategory {
|
||||
kGeneric,
|
||||
kDraw,
|
||||
kSwap,
|
||||
};
|
||||
|
||||
struct PacketTypeInfo {
|
||||
PacketCategory category;
|
||||
const char* name;
|
||||
};
|
||||
|
||||
struct PacketAction {
|
||||
enum class Type {
|
||||
kRegisterWrite,
|
||||
kSetBinMask,
|
||||
kSetBinSelect,
|
||||
};
|
||||
Type type;
|
||||
|
||||
union {
|
||||
struct {
|
||||
uint32_t index;
|
||||
RegisterFile::RegisterValue value;
|
||||
} register_write;
|
||||
struct {
|
||||
uint64_t value;
|
||||
} set_bin_mask;
|
||||
struct {
|
||||
uint64_t value;
|
||||
} set_bin_select;
|
||||
};
|
||||
|
||||
static PacketAction RegisterWrite(uint32_t index, uint32_t value) {
|
||||
PacketAction action;
|
||||
action.type = Type::kRegisterWrite;
|
||||
action.register_write.index = index;
|
||||
action.register_write.value.u32 = value;
|
||||
return action;
|
||||
}
|
||||
|
||||
static PacketAction SetBinMask(uint64_t value) {
|
||||
PacketAction action;
|
||||
action.type = Type::kSetBinMask;
|
||||
action.set_bin_mask.value = value;
|
||||
return action;
|
||||
}
|
||||
|
||||
static PacketAction SetBinSelect(uint64_t value) {
|
||||
PacketAction action;
|
||||
action.type = Type::kSetBinSelect;
|
||||
action.set_bin_select.value = value;
|
||||
return action;
|
||||
}
|
||||
};
|
||||
|
||||
struct PacketInfo {
|
||||
const PacketTypeInfo* type_info;
|
||||
bool predicated;
|
||||
uint32_t count;
|
||||
std::vector<PacketAction> actions;
|
||||
};
|
||||
|
||||
class PacketDisassembler {
|
||||
public:
|
||||
static PacketCategory GetPacketCategory(const uint8_t* base_ptr);
|
||||
|
||||
static bool DisasmPacketType0(const uint8_t* base_ptr, uint32_t packet,
|
||||
PacketInfo* out_info);
|
||||
static bool DisasmPacketType1(const uint8_t* base_ptr, uint32_t packet,
|
||||
PacketInfo* out_info);
|
||||
static bool DisasmPacketType2(const uint8_t* base_ptr, uint32_t packet,
|
||||
PacketInfo* out_info);
|
||||
static bool DisasmPacketType3(const uint8_t* base_ptr, uint32_t packet,
|
||||
PacketInfo* out_info);
|
||||
static bool DisasmPacket(const uint8_t* base_ptr, PacketInfo* out_info);
|
||||
};
|
||||
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_GPU_PACKET_DISASSEMBLER_H_
|
|
@ -0,0 +1,186 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/gpu/trace_player.h"
|
||||
|
||||
#include "xenia/gpu/command_processor.h"
|
||||
#include "xenia/gpu/graphics_system.h"
|
||||
#include "xenia/memory.h"
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
|
||||
TracePlayer::TracePlayer(xe::ui::Loop* loop, GraphicsSystem* graphics_system)
|
||||
: loop_(loop),
|
||||
graphics_system_(graphics_system),
|
||||
current_frame_index_(0),
|
||||
current_command_index_(-1) {
|
||||
// Need to allocate all of physical memory so that we can write to it
|
||||
// during playback.
|
||||
graphics_system_->memory()
|
||||
->LookupHeapByType(true, 4096)
|
||||
->AllocFixed(0, 0x1FFFFFFF, 4096,
|
||||
kMemoryAllocationReserve | kMemoryAllocationCommit,
|
||||
kMemoryProtectRead | kMemoryProtectWrite);
|
||||
}
|
||||
|
||||
TracePlayer::~TracePlayer() = default;
|
||||
|
||||
const TraceReader::Frame* TracePlayer::current_frame() const {
|
||||
if (current_frame_index_ > frame_count()) {
|
||||
return nullptr;
|
||||
}
|
||||
return frame(current_frame_index_);
|
||||
}
|
||||
|
||||
void TracePlayer::SeekFrame(int target_frame) {
|
||||
if (current_frame_index_ == target_frame) {
|
||||
return;
|
||||
}
|
||||
current_frame_index_ = target_frame;
|
||||
auto frame = current_frame();
|
||||
current_command_index_ = int(frame->commands.size()) - 1;
|
||||
|
||||
assert_true(frame->start_ptr <= frame->end_ptr);
|
||||
PlayTrace(frame->start_ptr, frame->end_ptr - frame->start_ptr,
|
||||
TracePlaybackMode::kBreakOnSwap);
|
||||
}
|
||||
|
||||
void TracePlayer::SeekCommand(int target_command) {
|
||||
if (current_command_index_ == target_command) {
|
||||
return;
|
||||
}
|
||||
int previous_command_index = current_command_index_;
|
||||
current_command_index_ = target_command;
|
||||
if (current_command_index_ == -1) {
|
||||
return;
|
||||
}
|
||||
auto frame = current_frame();
|
||||
const auto& command = frame->commands[target_command];
|
||||
assert_true(frame->start_ptr <= command.end_ptr);
|
||||
if (target_command && previous_command_index == target_command - 1) {
|
||||
// Seek forward.
|
||||
const auto& previous_command = frame->commands[target_command - 1];
|
||||
PlayTrace(previous_command.end_ptr,
|
||||
command.end_ptr - previous_command.end_ptr,
|
||||
TracePlaybackMode::kBreakOnSwap);
|
||||
} else {
|
||||
// Full playback from frame start.
|
||||
PlayTrace(frame->start_ptr, command.end_ptr - frame->start_ptr,
|
||||
TracePlaybackMode::kBreakOnSwap);
|
||||
}
|
||||
}
|
||||
|
||||
void TracePlayer::PlayTrace(const uint8_t* trace_data, size_t trace_size,
|
||||
TracePlaybackMode playback_mode) {
|
||||
graphics_system_->command_processor()->CallInThread(
|
||||
[this, trace_data, trace_size, playback_mode]() {
|
||||
PlayTraceOnThread(trace_data, trace_size, playback_mode);
|
||||
});
|
||||
}
|
||||
|
||||
void TracePlayer::PlayTraceOnThread(const uint8_t* trace_data,
|
||||
size_t trace_size,
|
||||
TracePlaybackMode playback_mode) {
|
||||
auto memory = graphics_system_->memory();
|
||||
auto command_processor = graphics_system_->command_processor();
|
||||
|
||||
command_processor->set_swap_mode(SwapMode::kIgnored);
|
||||
|
||||
auto trace_ptr = trace_data;
|
||||
bool pending_break = false;
|
||||
const PacketStartCommand* pending_packet = nullptr;
|
||||
while (trace_ptr < trace_data + trace_size) {
|
||||
auto type = static_cast<TraceCommandType>(xe::load<uint32_t>(trace_ptr));
|
||||
switch (type) {
|
||||
case TraceCommandType::kPrimaryBufferStart: {
|
||||
auto cmd =
|
||||
reinterpret_cast<const PrimaryBufferStartCommand*>(trace_ptr);
|
||||
//
|
||||
trace_ptr += sizeof(*cmd) + cmd->count * 4;
|
||||
break;
|
||||
}
|
||||
case TraceCommandType::kPrimaryBufferEnd: {
|
||||
auto cmd = reinterpret_cast<const PrimaryBufferEndCommand*>(trace_ptr);
|
||||
//
|
||||
trace_ptr += sizeof(*cmd);
|
||||
break;
|
||||
}
|
||||
case TraceCommandType::kIndirectBufferStart: {
|
||||
auto cmd =
|
||||
reinterpret_cast<const IndirectBufferStartCommand*>(trace_ptr);
|
||||
//
|
||||
trace_ptr += sizeof(*cmd) + cmd->count * 4;
|
||||
break;
|
||||
}
|
||||
case TraceCommandType::kIndirectBufferEnd: {
|
||||
auto cmd = reinterpret_cast<const IndirectBufferEndCommand*>(trace_ptr);
|
||||
//
|
||||
trace_ptr += sizeof(*cmd);
|
||||
break;
|
||||
}
|
||||
case TraceCommandType::kPacketStart: {
|
||||
auto cmd = reinterpret_cast<const PacketStartCommand*>(trace_ptr);
|
||||
trace_ptr += sizeof(*cmd);
|
||||
std::memcpy(memory->TranslatePhysical(cmd->base_ptr), trace_ptr,
|
||||
cmd->count * 4);
|
||||
trace_ptr += cmd->count * 4;
|
||||
pending_packet = cmd;
|
||||
break;
|
||||
}
|
||||
case TraceCommandType::kPacketEnd: {
|
||||
auto cmd = reinterpret_cast<const PacketEndCommand*>(trace_ptr);
|
||||
trace_ptr += sizeof(*cmd);
|
||||
if (pending_packet) {
|
||||
command_processor->ExecutePacket(pending_packet->base_ptr,
|
||||
pending_packet->count);
|
||||
pending_packet = nullptr;
|
||||
}
|
||||
if (pending_break) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TraceCommandType::kMemoryRead: {
|
||||
auto cmd = reinterpret_cast<const MemoryReadCommand*>(trace_ptr);
|
||||
trace_ptr += sizeof(*cmd);
|
||||
std::memcpy(memory->TranslatePhysical(cmd->base_ptr), trace_ptr,
|
||||
cmd->length);
|
||||
trace_ptr += cmd->length;
|
||||
break;
|
||||
}
|
||||
case TraceCommandType::kMemoryWrite: {
|
||||
auto cmd = reinterpret_cast<const MemoryWriteCommand*>(trace_ptr);
|
||||
trace_ptr += sizeof(*cmd);
|
||||
// ?
|
||||
trace_ptr += cmd->length;
|
||||
break;
|
||||
}
|
||||
case TraceCommandType::kEvent: {
|
||||
auto cmd = reinterpret_cast<const EventCommand*>(trace_ptr);
|
||||
trace_ptr += sizeof(*cmd);
|
||||
switch (cmd->event_type) {
|
||||
case EventType::kSwap: {
|
||||
if (playback_mode == TracePlaybackMode::kBreakOnSwap) {
|
||||
pending_break = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
command_processor->set_swap_mode(SwapMode::kNormal);
|
||||
command_processor->IssueSwap(0, 1280, 720);
|
||||
}
|
||||
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_GPU_TRACE_PLAYER_H_
|
||||
#define XENIA_GPU_TRACE_PLAYER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "xenia/gpu/trace_protocol.h"
|
||||
#include "xenia/gpu/trace_reader.h"
|
||||
#include "xenia/ui/loop.h"
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
|
||||
class GraphicsSystem;
|
||||
|
||||
enum class TracePlaybackMode {
|
||||
kUntilEnd,
|
||||
kBreakOnSwap,
|
||||
};
|
||||
|
||||
class TracePlayer : public TraceReader {
|
||||
public:
|
||||
TracePlayer(xe::ui::Loop* loop, GraphicsSystem* graphics_system);
|
||||
~TracePlayer() override;
|
||||
|
||||
GraphicsSystem* graphics_system() const { return graphics_system_; }
|
||||
int current_frame_index() const { return current_frame_index_; }
|
||||
int current_command_index() const { return current_command_index_; }
|
||||
const Frame* current_frame() const;
|
||||
|
||||
void SeekFrame(int target_frame);
|
||||
void SeekCommand(int target_command);
|
||||
|
||||
private:
|
||||
void PlayTrace(const uint8_t* trace_data, size_t trace_size,
|
||||
TracePlaybackMode playback_mode);
|
||||
void PlayTraceOnThread(const uint8_t* trace_data, size_t trace_size,
|
||||
TracePlaybackMode playback_mode);
|
||||
|
||||
xe::ui::Loop* loop_;
|
||||
GraphicsSystem* graphics_system_;
|
||||
int current_frame_index_;
|
||||
int current_command_index_;
|
||||
};
|
||||
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_GPU_TRACE_PLAYER_H_
|
|
@ -0,0 +1,84 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_GPU_TRACE_PROTOCOL_H_
|
||||
#define XENIA_GPU_TRACE_PROTOCOL_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
|
||||
enum class TraceCommandType : uint32_t {
|
||||
kPrimaryBufferStart,
|
||||
kPrimaryBufferEnd,
|
||||
kIndirectBufferStart,
|
||||
kIndirectBufferEnd,
|
||||
kPacketStart,
|
||||
kPacketEnd,
|
||||
kMemoryRead,
|
||||
kMemoryWrite,
|
||||
kEvent,
|
||||
};
|
||||
|
||||
struct PrimaryBufferStartCommand {
|
||||
TraceCommandType type;
|
||||
uint32_t base_ptr;
|
||||
uint32_t count;
|
||||
};
|
||||
|
||||
struct PrimaryBufferEndCommand {
|
||||
TraceCommandType type;
|
||||
};
|
||||
|
||||
struct IndirectBufferStartCommand {
|
||||
TraceCommandType type;
|
||||
uint32_t base_ptr;
|
||||
uint32_t count;
|
||||
};
|
||||
|
||||
struct IndirectBufferEndCommand {
|
||||
TraceCommandType type;
|
||||
};
|
||||
|
||||
struct PacketStartCommand {
|
||||
TraceCommandType type;
|
||||
uint32_t base_ptr;
|
||||
uint32_t count;
|
||||
};
|
||||
|
||||
struct PacketEndCommand {
|
||||
TraceCommandType type;
|
||||
};
|
||||
|
||||
struct MemoryReadCommand {
|
||||
TraceCommandType type;
|
||||
uint32_t base_ptr;
|
||||
uint32_t length;
|
||||
};
|
||||
|
||||
struct MemoryWriteCommand {
|
||||
TraceCommandType type;
|
||||
uint32_t base_ptr;
|
||||
uint32_t length;
|
||||
};
|
||||
|
||||
enum class EventType {
|
||||
kSwap,
|
||||
};
|
||||
|
||||
struct EventCommand {
|
||||
TraceCommandType type;
|
||||
EventType event_type;
|
||||
};
|
||||
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_GPU_TRACE_PROTOCOL_H_
|
|
@ -0,0 +1,152 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/gpu/trace_reader.h"
|
||||
|
||||
#include "xenia/base/mapped_memory.h"
|
||||
#include "xenia/gpu/packet_disassembler.h"
|
||||
#include "xenia/gpu/trace_protocol.h"
|
||||
#include "xenia/memory.h"
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
|
||||
bool TraceReader::Open(const std::wstring& path) {
|
||||
Close();
|
||||
|
||||
mmap_ = MappedMemory::Open(path, MappedMemory::Mode::kRead);
|
||||
if (!mmap_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
trace_data_ = reinterpret_cast<const uint8_t*>(mmap_->data());
|
||||
trace_size_ = mmap_->size();
|
||||
|
||||
ParseTrace();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TraceReader::Close() {
|
||||
mmap_.reset();
|
||||
trace_data_ = nullptr;
|
||||
trace_size_ = 0;
|
||||
}
|
||||
|
||||
void TraceReader::ParseTrace() {
|
||||
auto trace_ptr = trace_data_;
|
||||
Frame current_frame = {
|
||||
trace_ptr, nullptr, 0,
|
||||
};
|
||||
const PacketStartCommand* packet_start = nullptr;
|
||||
const uint8_t* packet_start_ptr = nullptr;
|
||||
const uint8_t* last_ptr = trace_ptr;
|
||||
bool pending_break = false;
|
||||
while (trace_ptr < trace_data_ + trace_size_) {
|
||||
++current_frame.command_count;
|
||||
auto type = static_cast<TraceCommandType>(xe::load<uint32_t>(trace_ptr));
|
||||
switch (type) {
|
||||
case TraceCommandType::kPrimaryBufferStart: {
|
||||
auto cmd =
|
||||
reinterpret_cast<const PrimaryBufferStartCommand*>(trace_ptr);
|
||||
trace_ptr += sizeof(*cmd) + cmd->count * 4;
|
||||
break;
|
||||
}
|
||||
case TraceCommandType::kPrimaryBufferEnd: {
|
||||
auto cmd = reinterpret_cast<const PrimaryBufferEndCommand*>(trace_ptr);
|
||||
trace_ptr += sizeof(*cmd);
|
||||
break;
|
||||
}
|
||||
case TraceCommandType::kIndirectBufferStart: {
|
||||
auto cmd =
|
||||
reinterpret_cast<const IndirectBufferStartCommand*>(trace_ptr);
|
||||
trace_ptr += sizeof(*cmd) + cmd->count * 4;
|
||||
break;
|
||||
}
|
||||
case TraceCommandType::kIndirectBufferEnd: {
|
||||
auto cmd = reinterpret_cast<const IndirectBufferEndCommand*>(trace_ptr);
|
||||
trace_ptr += sizeof(*cmd);
|
||||
break;
|
||||
}
|
||||
case TraceCommandType::kPacketStart: {
|
||||
auto cmd = reinterpret_cast<const PacketStartCommand*>(trace_ptr);
|
||||
packet_start_ptr = trace_ptr;
|
||||
packet_start = cmd;
|
||||
trace_ptr += sizeof(*cmd) + cmd->count * 4;
|
||||
break;
|
||||
}
|
||||
case TraceCommandType::kPacketEnd: {
|
||||
auto cmd = reinterpret_cast<const PacketEndCommand*>(trace_ptr);
|
||||
trace_ptr += sizeof(*cmd);
|
||||
if (!packet_start_ptr) {
|
||||
continue;
|
||||
}
|
||||
auto packet_category = PacketDisassembler::GetPacketCategory(
|
||||
packet_start_ptr + sizeof(*packet_start));
|
||||
switch (packet_category) {
|
||||
case PacketCategory::kDraw: {
|
||||
Frame::Command command;
|
||||
command.type = Frame::Command::Type::kDraw;
|
||||
command.head_ptr = packet_start_ptr;
|
||||
command.start_ptr = last_ptr;
|
||||
command.end_ptr = trace_ptr;
|
||||
current_frame.commands.push_back(std::move(command));
|
||||
last_ptr = trace_ptr;
|
||||
break;
|
||||
}
|
||||
case PacketCategory::kSwap: {
|
||||
//
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pending_break) {
|
||||
current_frame.end_ptr = trace_ptr;
|
||||
frames_.push_back(std::move(current_frame));
|
||||
current_frame.start_ptr = trace_ptr;
|
||||
current_frame.end_ptr = nullptr;
|
||||
current_frame.command_count = 0;
|
||||
pending_break = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TraceCommandType::kMemoryRead: {
|
||||
auto cmd = reinterpret_cast<const MemoryReadCommand*>(trace_ptr);
|
||||
trace_ptr += sizeof(*cmd) + cmd->length;
|
||||
break;
|
||||
}
|
||||
case TraceCommandType::kMemoryWrite: {
|
||||
auto cmd = reinterpret_cast<const MemoryWriteCommand*>(trace_ptr);
|
||||
trace_ptr += sizeof(*cmd) + cmd->length;
|
||||
break;
|
||||
}
|
||||
case TraceCommandType::kEvent: {
|
||||
auto cmd = reinterpret_cast<const EventCommand*>(trace_ptr);
|
||||
trace_ptr += sizeof(*cmd);
|
||||
switch (cmd->event_type) {
|
||||
case EventType::kSwap: {
|
||||
pending_break = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// Broken trace file?
|
||||
assert_unhandled_case(type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pending_break || current_frame.command_count) {
|
||||
current_frame.end_ptr = trace_ptr;
|
||||
frames_.push_back(std::move(current_frame));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
|
@ -0,0 +1,102 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_GPU_TRACE_READER_H_
|
||||
#define XENIA_GPU_TRACE_READER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "xenia/base/mapped_memory.h"
|
||||
#include "xenia/gpu/trace_protocol.h"
|
||||
#include "xenia/memory.h"
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
|
||||
// void Foo() {
|
||||
// auto trace_ptr = trace_data;
|
||||
// while (trace_ptr < trace_data + trace_size) {
|
||||
// auto cmd_type = *reinterpret_cast<const TraceCommandType*>(trace_ptr);
|
||||
// switch (cmd_type) {
|
||||
// case TraceCommandType::kPrimaryBufferStart:
|
||||
// break;
|
||||
// case TraceCommandType::kPrimaryBufferEnd:
|
||||
// break;
|
||||
// case TraceCommandType::kIndirectBufferStart:
|
||||
// break;
|
||||
// case TraceCommandType::kIndirectBufferEnd:
|
||||
// break;
|
||||
// case TraceCommandType::kPacketStart:
|
||||
// break;
|
||||
// case TraceCommandType::kPacketEnd:
|
||||
// break;
|
||||
// case TraceCommandType::kMemoryRead:
|
||||
// break;
|
||||
// case TraceCommandType::kMemoryWrite:
|
||||
// break;
|
||||
// case TraceCommandType::kEvent:
|
||||
// break;
|
||||
// }
|
||||
// /*trace_ptr = graphics_system->PlayTrace(
|
||||
// trace_ptr, trace_size - (trace_ptr - trace_data),
|
||||
// GraphicsSystem::TracePlaybackMode::kBreakOnSwap);*/
|
||||
// }
|
||||
//}
|
||||
|
||||
class TraceReader {
|
||||
public:
|
||||
struct Frame {
|
||||
struct Command {
|
||||
enum class Type {
|
||||
kDraw,
|
||||
kSwap,
|
||||
};
|
||||
const uint8_t* head_ptr;
|
||||
const uint8_t* start_ptr;
|
||||
const uint8_t* end_ptr;
|
||||
Type type;
|
||||
union {
|
||||
struct {
|
||||
//
|
||||
} draw;
|
||||
struct {
|
||||
//
|
||||
} swap;
|
||||
};
|
||||
};
|
||||
|
||||
const uint8_t* start_ptr;
|
||||
const uint8_t* end_ptr;
|
||||
int command_count;
|
||||
std::vector<Command> commands;
|
||||
};
|
||||
|
||||
TraceReader() = default;
|
||||
virtual ~TraceReader() = default;
|
||||
|
||||
const Frame* frame(int n) const { return &frames_[n]; }
|
||||
int frame_count() const { return int(frames_.size()); }
|
||||
|
||||
bool Open(const std::wstring& path);
|
||||
|
||||
void Close();
|
||||
|
||||
protected:
|
||||
void ParseTrace();
|
||||
|
||||
std::unique_ptr<MappedMemory> mmap_;
|
||||
const uint8_t* trace_data_ = nullptr;
|
||||
size_t trace_size_ = 0;
|
||||
std::vector<Frame> frames_;
|
||||
};
|
||||
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_GPU_TRACE_READER_H_
|
|
@ -0,0 +1,141 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/gpu/trace_writer.h"
|
||||
|
||||
#include "xenia/base/string.h"
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
|
||||
TraceWriter::TraceWriter(uint8_t* membase)
|
||||
: membase_(membase), file_(nullptr) {}
|
||||
|
||||
TraceWriter::~TraceWriter() = default;
|
||||
|
||||
bool TraceWriter::Open(const std::wstring& path) {
|
||||
Close();
|
||||
|
||||
auto canonical_path = xe::to_absolute_path(path);
|
||||
auto base_path = xe::find_base_path(canonical_path);
|
||||
xe::filesystem::CreateFolder(base_path);
|
||||
|
||||
file_ = xe::filesystem::OpenFile(canonical_path, "wb");
|
||||
return file_ != nullptr;
|
||||
}
|
||||
|
||||
void TraceWriter::Flush() {
|
||||
if (file_) {
|
||||
fflush(file_);
|
||||
}
|
||||
}
|
||||
|
||||
void TraceWriter::Close() {
|
||||
if (file_) {
|
||||
fflush(file_);
|
||||
fclose(file_);
|
||||
file_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void TraceWriter::WritePrimaryBufferStart(uint32_t base_ptr, uint32_t count) {
|
||||
if (!file_) {
|
||||
return;
|
||||
}
|
||||
auto cmd = PrimaryBufferStartCommand({
|
||||
TraceCommandType::kPrimaryBufferStart, base_ptr, 0,
|
||||
});
|
||||
fwrite(&cmd, 1, sizeof(cmd), file_);
|
||||
}
|
||||
|
||||
void TraceWriter::WritePrimaryBufferEnd() {
|
||||
if (!file_) {
|
||||
return;
|
||||
}
|
||||
auto cmd = PrimaryBufferEndCommand({
|
||||
TraceCommandType::kPrimaryBufferEnd,
|
||||
});
|
||||
fwrite(&cmd, 1, sizeof(cmd), file_);
|
||||
}
|
||||
|
||||
void TraceWriter::WriteIndirectBufferStart(uint32_t base_ptr, uint32_t count) {
|
||||
if (!file_) {
|
||||
return;
|
||||
}
|
||||
auto cmd = IndirectBufferStartCommand({
|
||||
TraceCommandType::kIndirectBufferStart, base_ptr, 0,
|
||||
});
|
||||
fwrite(&cmd, 1, sizeof(cmd), file_);
|
||||
}
|
||||
|
||||
void TraceWriter::WriteIndirectBufferEnd() {
|
||||
if (!file_) {
|
||||
return;
|
||||
}
|
||||
auto cmd = IndirectBufferEndCommand({
|
||||
TraceCommandType::kIndirectBufferEnd,
|
||||
});
|
||||
fwrite(&cmd, 1, sizeof(cmd), file_);
|
||||
}
|
||||
|
||||
void TraceWriter::WritePacketStart(uint32_t base_ptr, uint32_t count) {
|
||||
if (!file_) {
|
||||
return;
|
||||
}
|
||||
auto cmd = PacketStartCommand({
|
||||
TraceCommandType::kPacketStart, base_ptr, count,
|
||||
});
|
||||
fwrite(&cmd, 1, sizeof(cmd), file_);
|
||||
fwrite(membase_ + base_ptr, 4, count, file_);
|
||||
}
|
||||
|
||||
void TraceWriter::WritePacketEnd() {
|
||||
if (!file_) {
|
||||
return;
|
||||
}
|
||||
auto cmd = PacketEndCommand({
|
||||
TraceCommandType::kPacketEnd,
|
||||
});
|
||||
fwrite(&cmd, 1, sizeof(cmd), file_);
|
||||
}
|
||||
|
||||
void TraceWriter::WriteMemoryRead(uint32_t base_ptr, size_t length) {
|
||||
if (!file_) {
|
||||
return;
|
||||
}
|
||||
auto cmd = MemoryReadCommand({
|
||||
TraceCommandType::kMemoryRead, base_ptr, uint32_t(length),
|
||||
});
|
||||
fwrite(&cmd, 1, sizeof(cmd), file_);
|
||||
fwrite(membase_ + base_ptr, 1, length, file_);
|
||||
}
|
||||
|
||||
void TraceWriter::WriteMemoryWrite(uint32_t base_ptr, size_t length) {
|
||||
if (!file_) {
|
||||
return;
|
||||
}
|
||||
auto cmd = MemoryWriteCommand({
|
||||
TraceCommandType::kMemoryWrite, base_ptr, uint32_t(length),
|
||||
});
|
||||
fwrite(&cmd, 1, sizeof(cmd), file_);
|
||||
fwrite(membase_ + base_ptr, 1, length, file_);
|
||||
}
|
||||
|
||||
void TraceWriter::WriteEvent(EventType event_type) {
|
||||
if (!file_) {
|
||||
return;
|
||||
}
|
||||
auto cmd = EventCommand({
|
||||
TraceCommandType::kEvent, event_type,
|
||||
});
|
||||
fwrite(&cmd, 1, sizeof(cmd), file_);
|
||||
}
|
||||
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_GPU_TRACE_WRITER_H_
|
||||
#define XENIA_GPU_TRACE_WRITER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "xenia/base/filesystem.h"
|
||||
#include "xenia/gpu/trace_protocol.h"
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
|
||||
class TraceWriter {
|
||||
public:
|
||||
explicit TraceWriter(uint8_t* membase);
|
||||
~TraceWriter();
|
||||
|
||||
bool is_open() const { return file_ != nullptr; }
|
||||
|
||||
bool Open(const std::wstring& path);
|
||||
void Flush();
|
||||
void Close();
|
||||
|
||||
void WritePrimaryBufferStart(uint32_t base_ptr, uint32_t count);
|
||||
void WritePrimaryBufferEnd();
|
||||
void WriteIndirectBufferStart(uint32_t base_ptr, uint32_t count);
|
||||
void WriteIndirectBufferEnd();
|
||||
void WritePacketStart(uint32_t base_ptr, uint32_t count);
|
||||
void WritePacketEnd();
|
||||
void WriteMemoryRead(uint32_t base_ptr, size_t length);
|
||||
void WriteMemoryWrite(uint32_t base_ptr, size_t length);
|
||||
void WriteEvent(EventType event_type);
|
||||
|
||||
private:
|
||||
uint8_t* membase_;
|
||||
FILE* file_;
|
||||
};
|
||||
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_GPU_TRACE_WRITER_H_
|
|
@ -1,49 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/gpu/tracing.h"
|
||||
|
||||
#include "xenia/base/filesystem.h"
|
||||
#include "xenia/base/string.h"
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
|
||||
TraceWriter::TraceWriter(uint8_t* membase)
|
||||
: membase_(membase), file_(nullptr) {}
|
||||
|
||||
TraceWriter::~TraceWriter() = default;
|
||||
|
||||
bool TraceWriter::Open(const std::wstring& path) {
|
||||
Close();
|
||||
|
||||
auto canonical_path = xe::to_absolute_path(path);
|
||||
auto base_path = xe::find_base_path(canonical_path);
|
||||
xe::filesystem::CreateFolder(base_path);
|
||||
|
||||
file_ = xe::filesystem::OpenFile(canonical_path, "wb");
|
||||
return file_ != nullptr;
|
||||
}
|
||||
|
||||
void TraceWriter::Flush() {
|
||||
if (file_) {
|
||||
fflush(file_);
|
||||
}
|
||||
}
|
||||
|
||||
void TraceWriter::Close() {
|
||||
if (file_) {
|
||||
fflush(file_);
|
||||
fclose(file_);
|
||||
file_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
|
@ -1,195 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_GPU_TRACING_H_
|
||||
#define XENIA_GPU_TRACING_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "xenia/memory.h"
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
|
||||
enum class TraceCommandType : uint32_t {
|
||||
kPrimaryBufferStart,
|
||||
kPrimaryBufferEnd,
|
||||
kIndirectBufferStart,
|
||||
kIndirectBufferEnd,
|
||||
kPacketStart,
|
||||
kPacketEnd,
|
||||
kMemoryRead,
|
||||
kMemoryWrite,
|
||||
kEvent,
|
||||
};
|
||||
|
||||
struct PrimaryBufferStartCommand {
|
||||
TraceCommandType type;
|
||||
uint32_t base_ptr;
|
||||
uint32_t count;
|
||||
};
|
||||
|
||||
struct PrimaryBufferEndCommand {
|
||||
TraceCommandType type;
|
||||
};
|
||||
|
||||
struct IndirectBufferStartCommand {
|
||||
TraceCommandType type;
|
||||
uint32_t base_ptr;
|
||||
uint32_t count;
|
||||
};
|
||||
|
||||
struct IndirectBufferEndCommand {
|
||||
TraceCommandType type;
|
||||
};
|
||||
|
||||
struct PacketStartCommand {
|
||||
TraceCommandType type;
|
||||
uint32_t base_ptr;
|
||||
uint32_t count;
|
||||
};
|
||||
|
||||
struct PacketEndCommand {
|
||||
TraceCommandType type;
|
||||
};
|
||||
|
||||
struct MemoryReadCommand {
|
||||
TraceCommandType type;
|
||||
uint32_t base_ptr;
|
||||
uint32_t length;
|
||||
};
|
||||
|
||||
struct MemoryWriteCommand {
|
||||
TraceCommandType type;
|
||||
uint32_t base_ptr;
|
||||
uint32_t length;
|
||||
};
|
||||
|
||||
enum class EventType {
|
||||
kSwap,
|
||||
};
|
||||
|
||||
struct EventCommand {
|
||||
TraceCommandType type;
|
||||
EventType event_type;
|
||||
};
|
||||
|
||||
class TraceWriter {
|
||||
public:
|
||||
explicit TraceWriter(uint8_t* membase);
|
||||
~TraceWriter();
|
||||
|
||||
bool is_open() const { return file_ != nullptr; }
|
||||
|
||||
bool Open(const std::wstring& path);
|
||||
void Flush();
|
||||
void Close();
|
||||
|
||||
void WritePrimaryBufferStart(uint32_t base_ptr, uint32_t count) {
|
||||
if (!file_) {
|
||||
return;
|
||||
}
|
||||
auto cmd = PrimaryBufferStartCommand({
|
||||
TraceCommandType::kPrimaryBufferStart, base_ptr, 0,
|
||||
});
|
||||
fwrite(&cmd, 1, sizeof(cmd), file_);
|
||||
}
|
||||
|
||||
void WritePrimaryBufferEnd() {
|
||||
if (!file_) {
|
||||
return;
|
||||
}
|
||||
auto cmd = PrimaryBufferEndCommand({
|
||||
TraceCommandType::kPrimaryBufferEnd,
|
||||
});
|
||||
fwrite(&cmd, 1, sizeof(cmd), file_);
|
||||
}
|
||||
|
||||
void WriteIndirectBufferStart(uint32_t base_ptr, uint32_t count) {
|
||||
if (!file_) {
|
||||
return;
|
||||
}
|
||||
auto cmd = IndirectBufferStartCommand({
|
||||
TraceCommandType::kIndirectBufferStart, base_ptr, 0,
|
||||
});
|
||||
fwrite(&cmd, 1, sizeof(cmd), file_);
|
||||
}
|
||||
|
||||
void WriteIndirectBufferEnd() {
|
||||
if (!file_) {
|
||||
return;
|
||||
}
|
||||
auto cmd = IndirectBufferEndCommand({
|
||||
TraceCommandType::kIndirectBufferEnd,
|
||||
});
|
||||
fwrite(&cmd, 1, sizeof(cmd), file_);
|
||||
}
|
||||
|
||||
void WritePacketStart(uint32_t base_ptr, uint32_t count) {
|
||||
if (!file_) {
|
||||
return;
|
||||
}
|
||||
auto cmd = PacketStartCommand({
|
||||
TraceCommandType::kPacketStart, base_ptr, count,
|
||||
});
|
||||
fwrite(&cmd, 1, sizeof(cmd), file_);
|
||||
fwrite(membase_ + base_ptr, 4, count, file_);
|
||||
}
|
||||
|
||||
void WritePacketEnd() {
|
||||
if (!file_) {
|
||||
return;
|
||||
}
|
||||
auto cmd = PacketEndCommand({
|
||||
TraceCommandType::kPacketEnd,
|
||||
});
|
||||
fwrite(&cmd, 1, sizeof(cmd), file_);
|
||||
}
|
||||
|
||||
void WriteMemoryRead(uint32_t base_ptr, size_t length) {
|
||||
if (!file_) {
|
||||
return;
|
||||
}
|
||||
auto cmd = MemoryReadCommand({
|
||||
TraceCommandType::kMemoryRead, base_ptr, uint32_t(length),
|
||||
});
|
||||
fwrite(&cmd, 1, sizeof(cmd), file_);
|
||||
fwrite(membase_ + base_ptr, 1, length, file_);
|
||||
}
|
||||
|
||||
void WriteMemoryWrite(uint32_t base_ptr, size_t length) {
|
||||
if (!file_) {
|
||||
return;
|
||||
}
|
||||
auto cmd = MemoryWriteCommand({
|
||||
TraceCommandType::kMemoryWrite, base_ptr, uint32_t(length),
|
||||
});
|
||||
fwrite(&cmd, 1, sizeof(cmd), file_);
|
||||
fwrite(membase_ + base_ptr, 1, length, file_);
|
||||
}
|
||||
|
||||
void WriteEvent(EventType event_type) {
|
||||
if (!file_) {
|
||||
return;
|
||||
}
|
||||
auto cmd = EventCommand({
|
||||
TraceCommandType::kEvent, event_type,
|
||||
});
|
||||
fwrite(&cmd, 1, sizeof(cmd), file_);
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t* membase_;
|
||||
FILE* file_;
|
||||
};
|
||||
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_GPU_TRACING_H_
|
|
@ -161,8 +161,6 @@ std::unique_ptr<ImmediateTexture> GLImmediateDrawer::CreateTexture(
|
|||
GraphicsContextLock lock(graphics_context_);
|
||||
auto texture =
|
||||
std::make_unique<GLImmediateTexture>(width, height, filter, repeat);
|
||||
glTextureStorage2D(static_cast<GLuint>(texture->handle), 1, GL_RGBA8, width,
|
||||
height);
|
||||
if (data) {
|
||||
UpdateTexture(texture.get(), data);
|
||||
}
|
||||
|
@ -188,8 +186,8 @@ void GLImmediateDrawer::Begin(int render_target_width,
|
|||
glEnablei(GL_BLEND, 0);
|
||||
glBlendEquationi(0, GL_FUNC_ADD);
|
||||
glBlendFunci(0, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glDisablei(GL_DEPTH_TEST, 0);
|
||||
glDisablei(GL_SCISSOR_TEST, 0);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
|
||||
// Prepare drawing resources.
|
||||
glUseProgram(program_);
|
||||
|
@ -223,11 +221,11 @@ void GLImmediateDrawer::BeginDrawBatch(const ImmediateDrawBatch& batch) {
|
|||
|
||||
void GLImmediateDrawer::Draw(const ImmediateDraw& draw) {
|
||||
if (draw.scissor) {
|
||||
glEnablei(GL_SCISSOR_TEST, 0);
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
glScissorIndexed(0, draw.scissor_rect[0], draw.scissor_rect[1],
|
||||
draw.scissor_rect[2], draw.scissor_rect[3]);
|
||||
} else {
|
||||
glDisablei(GL_SCISSOR_TEST, 0);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
}
|
||||
|
||||
if (draw.texture_handle) {
|
||||
|
@ -261,7 +259,7 @@ void GLImmediateDrawer::EndDrawBatch() { glFlush(); }
|
|||
|
||||
void GLImmediateDrawer::End() {
|
||||
// Restore modified state.
|
||||
glDisablei(GL_SCISSOR_TEST, 0);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glBindTextureUnit(0, 0);
|
||||
glUseProgram(0);
|
||||
glBindVertexArray(0);
|
||||
|
|
Loading…
Reference in New Issue