rexglue-skate3/include/rex/graphics/command_processor.h

313 lines
13 KiB
C++

/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
*/
#pragma once
#include <atomic>
#include <cstring>
#include <functional>
#include <memory>
#include <mutex>
#include <queue>
#include <string>
#include <unordered_map>
#include <vector>
#include <rex/graphics/register_file.h>
#include <rex/graphics/registers.h>
#include <rex/graphics/trace_writer.h>
#include <rex/graphics/xenos.h>
#include <rex/memory.h>
#include <rex/memory/ring_buffer.h>
#include <rex/system/xthread.h>
#include <rex/thread.h>
#include <rex/ui/presenter.h>
namespace rex::stream {
class ByteStream;
} // namespace rex::stream
namespace rex::graphics {
class GraphicsSystem;
class Shader;
enum class ReadbackResolveMode {
kDisabled,
kFast,
kSome,
kFull,
kAuto,
};
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;
// Backend data
void* backend_data = nullptr;
// Whether the back buffer is dirty and a swap is pending.
bool pending = false;
};
enum class SwapMode {
kNormal,
kIgnored,
};
enum class GammaRampType {
kUnknown = 0,
kTable,
kPWL,
};
class CommandProcessor {
public:
enum class SwapPostEffect {
kNone,
kFxaa,
kFxaaExtreme,
};
CommandProcessor(GraphicsSystem* graphics_system, system::KernelState* kernel_state);
virtual ~CommandProcessor();
uint32_t counter() const { return counter_; }
void increment_counter() { counter_++; }
Shader* active_vertex_shader() const { return active_vertex_shader_; }
Shader* active_pixel_shader() const { return active_pixel_shader_; }
virtual bool Initialize();
virtual void Shutdown();
void CallInThread(std::function<void()> fn);
virtual void ClearCaches();
virtual void InvalidateGpuMemory();
// "Desired" is for the external thread managing the post-processing effect.
SwapPostEffect GetDesiredSwapPostEffect() const { return swap_post_effect_desired_; }
void SetDesiredSwapPostEffect(SwapPostEffect swap_post_effect);
// Implementations must not make assumptions that the front buffer will
// necessarily be a resolve destination - it may be a texture generated by any
// means like written to by the CPU or loaded from a file (the disclaimer
// screen right in the beginning of 4D530AA4 is not a resolved render target,
// for instance).
virtual void IssueSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
uint32_t frontbuffer_height) = 0;
// May be called not only from the command processor thread when the command
// processor is paused, and the termination of this function may be explicitly
// awaited.
virtual void InitializeShaderStorage(const std::filesystem::path& cache_root, uint32_t title_id,
bool blocking);
virtual void RequestFrameTrace(const std::filesystem::path& root_path);
virtual void BeginTracing(const std::filesystem::path& root_path);
virtual void EndTracing();
virtual void TracePlaybackWroteMemory(uint32_t base_ptr, uint32_t length) = 0;
void RestoreRegisters(uint32_t first_register, const uint32_t* register_values,
uint32_t register_count, bool execute_callbacks);
void RestoreGammaRamp(const reg::DC_LUT_30_COLOR* new_gamma_ramp_256_entry_table,
const reg::DC_LUT_PWL_DATA* new_gamma_ramp_pwl_rgb,
uint32_t new_gamma_ramp_rw_component);
virtual void RestoreEdramSnapshot(const void* snapshot) = 0;
void InitializeRingBuffer(uint32_t ptr, uint32_t size_log2);
void EnableReadPointerWriteBack(uint32_t ptr, uint32_t block_size_log2);
void UpdateWritePointer(uint32_t value);
void ExecutePacket(uint32_t ptr, uint32_t count);
bool is_paused() const { return paused_; }
void Pause();
void Resume();
bool Save(::rex::stream::ByteStream* stream);
bool Restore(::rex::stream::ByteStream* stream);
protected:
struct IndexBufferInfo {
xenos::IndexFormat format = xenos::IndexFormat::kInt16;
xenos::Endian endianness = xenos::Endian::kNone;
uint32_t count = 0;
uint32_t guest_base = 0;
size_t length = 0;
};
void WorkerThreadMain();
virtual bool SetupContext() = 0;
virtual void ShutdownContext() = 0;
virtual void WriteRegister(uint32_t index, uint32_t value);
uint32_t ReadRegisterValue(uint32_t index) const;
virtual void WriteRegistersFromMem(uint32_t start_index, uint32_t* base, uint32_t num_registers);
virtual void WriteRegisterRangeFromRing(memory::RingBuffer* ring, uint32_t base,
uint32_t num_registers);
void WriteALURangeFromRing(memory::RingBuffer* ring, uint32_t base, uint32_t num_registers);
void WriteFetchRangeFromRing(memory::RingBuffer* ring, uint32_t base, uint32_t num_registers);
void WriteBoolRangeFromRing(memory::RingBuffer* ring, uint32_t base, uint32_t num_registers);
void WriteLoopRangeFromRing(memory::RingBuffer* ring, uint32_t base, uint32_t num_registers);
void WriteREGISTERSRangeFromRing(memory::RingBuffer* ring, uint32_t base, uint32_t num_registers);
void WriteALURangeFromMem(uint32_t start_index, uint32_t* base, uint32_t num_registers);
void WriteFetchRangeFromMem(uint32_t start_index, uint32_t* base, uint32_t num_registers);
void WriteBoolRangeFromMem(uint32_t start_index, uint32_t* base, uint32_t num_registers);
void WriteLoopRangeFromMem(uint32_t start_index, uint32_t* base, uint32_t num_registers);
void WriteREGISTERSRangeFromMem(uint32_t start_index, uint32_t* base, uint32_t num_registers);
const reg::DC_LUT_30_COLOR* gamma_ramp_256_entry_table() const {
return gamma_ramp_256_entry_table_;
}
const reg::DC_LUT_PWL_DATA* gamma_ramp_pwl_rgb() const { return gamma_ramp_pwl_rgb_[0]; }
virtual void OnGammaRamp256EntryTableValueWritten() {}
virtual void OnGammaRampPWLValueWritten() {}
virtual void MakeCoherent();
virtual void PrepareForWait();
virtual void ReturnFromWait();
uint32_t ExecutePrimaryBuffer(uint32_t start_index, uint32_t end_index);
virtual void OnPrimaryBufferEnd() {}
void ExecuteIndirectBuffer(uint32_t ptr, uint32_t length);
bool ExecutePacket(memory::RingBuffer* reader);
bool ExecutePacketType0(memory::RingBuffer* reader, uint32_t packet);
bool ExecutePacketType1(memory::RingBuffer* reader, uint32_t packet);
bool ExecutePacketType2(memory::RingBuffer* reader, uint32_t packet);
bool ExecutePacketType3(memory::RingBuffer* reader, uint32_t packet);
bool ExecutePacketType3_ME_INIT(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
bool ExecutePacketType3_NOP(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
bool ExecutePacketType3_INTERRUPT(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
bool ExecutePacketType3_XE_SWAP(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
bool ExecutePacketType3_INDIRECT_BUFFER(memory::RingBuffer* reader, uint32_t packet,
uint32_t count);
bool ExecutePacketType3_WAIT_REG_MEM(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
bool ExecutePacketType3_REG_RMW(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
bool ExecutePacketType3_REG_TO_MEM(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
bool ExecutePacketType3_MEM_WRITE(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
bool ExecutePacketType3_COND_WRITE(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
bool ExecutePacketType3_EVENT_WRITE(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
bool ExecutePacketType3_EVENT_WRITE_SHD(memory::RingBuffer* reader, uint32_t packet,
uint32_t count);
bool ExecutePacketType3_EVENT_WRITE_EXT(memory::RingBuffer* reader, uint32_t packet,
uint32_t count);
virtual bool ExecutePacketType3_EVENT_WRITE_ZPD(memory::RingBuffer* reader, uint32_t packet,
uint32_t count);
bool ExecutePacketType3Draw(memory::RingBuffer* reader, uint32_t packet, const char* opcode_name,
uint32_t viz_query_condition, uint32_t count_remaining);
bool ExecutePacketType3_DRAW_INDX(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
bool ExecutePacketType3_DRAW_INDX_2(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
bool ExecutePacketType3_SET_CONSTANT(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
bool ExecutePacketType3_SET_CONSTANT2(memory::RingBuffer* reader, uint32_t packet,
uint32_t count);
bool ExecutePacketType3_LOAD_ALU_CONSTANT(memory::RingBuffer* reader, uint32_t packet,
uint32_t count);
bool ExecutePacketType3_SET_SHADER_CONSTANTS(memory::RingBuffer* reader, uint32_t packet,
uint32_t count);
bool ExecutePacketType3_IM_LOAD(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
bool ExecutePacketType3_IM_LOAD_IMMEDIATE(memory::RingBuffer* reader,
uint32_t packet, uint32_t count);
bool ExecutePacketType3_INVALIDATE_STATE(memory::RingBuffer* reader, uint32_t packet,
uint32_t count);
bool ExecutePacketType3_VIZ_QUERY(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
virtual Shader* LoadShader(xenos::ShaderType shader_type, uint32_t guest_address,
const uint32_t* host_address, uint32_t dword_count) = 0;
virtual bool IssueDraw(xenos::PrimitiveType prim_type, uint32_t index_count,
IndexBufferInfo* index_buffer_info, bool major_mode_explicit) = 0;
virtual bool IssueCopy() = 0;
// "Actual" is for the command processor thread, to be read by the
// implementations.
SwapPostEffect GetActualSwapPostEffect() const { return swap_post_effect_actual_; }
virtual void InitializeTrace();
// Shared readback resolve mode with backend legacy-flag alias support.
ReadbackResolveMode GetReadbackResolveMode(bool legacy_readback_resolve_enabled) const;
// Shared memexport readback enable state with backend legacy-flag override support.
bool IsReadbackMemexportEnabled(bool legacy_backend_flag) const;
memory::Memory* memory_ = nullptr;
system::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::filesystem::path trace_stream_path_;
std::filesystem::path trace_frame_path_;
std::atomic<bool> worker_running_;
system::object_ref<system::XHostThread> worker_thread_;
std::queue<std::function<void()>> pending_fns_;
// MicroEngine binary from PM4_ME_INIT
std::vector<uint32_t> me_bin_;
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<rex::thread::Event> write_ptr_index_event_;
std::atomic<uint32_t> write_ptr_index_;
// Some titles submit writes beyond the emulated register file range in PM4
// packets. Preserve these values so dependent packet logic can still observe
// them instead of dropping the write entirely.
std::unordered_map<uint32_t, uint32_t> extended_register_values_;
uint64_t bin_select_ = 0xFFFFFFFFull;
uint64_t bin_mask_ = 0xFFFFFFFFull;
Shader* active_vertex_shader_ = nullptr;
Shader* active_pixel_shader_ = nullptr;
bool paused_ = false;
// By default (such as for tools), post-processing is disabled.
// "Desired" is for the external thread managing the post-processing effect.
SwapPostEffect swap_post_effect_desired_ = SwapPostEffect::kNone;
SwapPostEffect swap_post_effect_actual_ = SwapPostEffect::kNone;
// Set by backend command processors to their legacy memexport readback cvar
// name (for explicit-override compatibility).
const char* legacy_readback_memexport_cvar_name_ = nullptr;
private:
reg::DC_LUT_30_COLOR gamma_ramp_256_entry_table_[256] = {};
reg::DC_LUT_PWL_DATA gamma_ramp_pwl_rgb_[128][3] = {};
uint32_t gamma_ramp_rw_component_ = 0;
};
} // namespace rex::graphics