/** ****************************************************************************** * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace rex::stream { class ByteStream; } // namespace rex::stream namespace rex::graphics { class GraphicsSystem; class Shader; enum class ReadbackResolveMode { kDisabled, kFast, kSome, kFull, }; 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 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 worker_running_; system::object_ref worker_thread_; std::queue> pending_fns_; // MicroEngine binary from PM4_ME_INIT std::vector 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 write_ptr_index_event_; std::atomic 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 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