/** ****************************************************************************** * 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. * ****************************************************************************** */ #ifndef XENIA_GPU_COMMAND_PROCESSOR_H_ #define XENIA_GPU_COMMAND_PROCESSOR_H_ #include #include #include #include #include #include #include #include #include "xenia/base/ring_buffer.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/presenter.h" namespace xe { class ByteStream; namespace gpu { class GraphicsSystem; class Shader; 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 { protected: RingBuffer reader_; // chrispy: instead of having ringbuffer on stack, have it near // the start of the class so we can access it via rel8. This // also reduces the number of params we need to pass public: enum class SwapPostEffect { kNone, kFxaa, kFxaaExtreme, }; CommandProcessor(GraphicsSystem* graphics_system, kernel::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(); // "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) {} // 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); bool is_paused() const { return paused_; } void Pause(); void Resume(); bool Save(ByteStream* stream); bool Restore(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; // rarely needed, most register writes have no special logic here XE_NOINLINE void HandleSpecialRegisterWrite(uint32_t index, uint32_t value); virtual void WriteRegister(uint32_t index, uint32_t value); // mem has big-endian register values XE_FORCEINLINE virtual void WriteRegistersFromMem(uint32_t start_index, uint32_t* base, uint32_t num_registers); XE_FORCEINLINE virtual void WriteRegisterRangeFromRing(xe::RingBuffer* ring, uint32_t base, uint32_t num_registers); XE_NOINLINE void WriteOneRegisterFromRing( uint32_t base, uint32_t num_times); // repeatedly write a value to one register, presumably a // register with special handling for writes void WriteALURangeFromRing(xe::RingBuffer* ring, uint32_t base, uint32_t num_times); void WriteFetchRangeFromRing(xe::RingBuffer* ring, uint32_t base, uint32_t num_times); void WriteBoolRangeFromRing(xe::RingBuffer* ring, uint32_t base, uint32_t num_times); void WriteLoopRangeFromRing(xe::RingBuffer* ring, uint32_t base, uint32_t num_times); void WriteREGISTERSRangeFromRing(xe::RingBuffer* ring, uint32_t base, uint32_t num_times); 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(); virtual void OnPrimaryBufferEnd() {} #include "pm4_command_processor_declare.h" virtual Shader* LoadShader(xenos::ShaderType shader_type, uint32_t guest_address, const uint32_t* host_address, uint32_t dword_count) { return nullptr; } virtual bool IssueDraw(xenos::PrimitiveType prim_type, uint32_t index_count, IndexBufferInfo* index_buffer_info, bool major_mode_explicit) { return false; } virtual bool IssueCopy() { return false; } // "Actual" is for the command processor thread, to be read by the // implementations. SwapPostEffect GetActualSwapPostEffect() const { return swap_post_effect_actual_; } virtual void InitializeTrace(); Memory* memory_ = nullptr; kernel::KernelState* kernel_state_ = nullptr; GraphicsSystem* graphics_system_ = nullptr; RegisterFile* XE_RESTRICT 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_; kernel::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_; 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; 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 gpu } // namespace xe #endif // XENIA_GPU_COMMAND_PROCESSOR_H_