Proper handling of nans for VMX max/min on x64 (minps/maxps has special behavior depending on the operand order that vmx does not have for vminfp/vmaxfp) Add extremely unintrusive guest code profiler utilizing KUSER_SHARED systemtime. This profiler is disabled on platforms other than windows, and on windows is disabled by default by a cvar Repurpose GUEST_SCRATCH64 stack offset to instead be for storing guest function profile times, define GUEST_SCRATCH as 0 instead, since thats already meant to be a scratch area Fix xenia silently closing on config errors/other fatal errors by setting has_console_attached_'s default to false Add alternative code path for guest clock that uses kusershared systemtime instead of QueryPerformanceCounter. This is way faster and I have tested it and found it to be working, but i have disabled it because i do not know how well it works on wine or on processors other than mine Significantly reduce log spam by setting XELOGAPU and XELOGGPU to be LogLevel::Debug Changed some LOGI to LOGD in places to reduce log spam Mark VdSwap as kHighFrequency, it was spamming up logs Make logging calls less intrusive for the caller by forcing the test of log level inline and moving the format/AppendLogLine stuff to an outlined cold function Add swcache namespace for software cache operations like prefetches, streaming stores and streaming loads. Add XE_MSVC_REORDER_BARRIER for preventing msvc from propagating a value too close to its store or from its load Add xe_unlikely_mutex for locks we know have very little contention add XE_HOST_CACHE_LINE_SIZE and XE_RESTRICT to platform.h Microoptimization: Changed most uses of size_t to ring_size_t in RingBuffer, this reduces the size of the inlined ringbuffer operations slightly by eliminating rex prefixes, depending on register allocation Add BeginPrefetchedRead to ringbuffer, which prefetches the second range if there is one according to the provided PrefetchTag added inline_loadclock cvar, which will directly use the value of the guest clock from clock.cc in jitted guest code. off by default change uses of GUEST_SCRATCH64 to GUEST_SCRATCH Add fast vectorized xenos_half_to_float/xenos_float_to_half (currently resides in x64_seq_vector, move to gpu code maybe at some point) Add fast x64 codegen for PackFloat16_4/UnpackFloat16_4. Same code can be used for Float16_2 in future commit. This should speed up some games that use these functions heavily Remove cvar for toggling old float16 behavior Add VRSAVE register, support mfspr/mtspr vrsave Add cvar for toggling off codegen for trap instructions and set it to true by default. Add specialized methods to CommandProcessor: WriteRegistersFromMem, WriteRegisterRangeFromRing, and WriteOneRegisterFromRing. These reduce the overall cost of WriteRegister Use a fixed size vmem vector for upload ranges, realloc/memsetting on resize in the inner loop of requestranges was showing up on the profiler (the search in requestranges itself needs work) Rename fixed_vmem_vector to better fit xenia's naming convention Only log unknown register writes in WriteRegister if DEBUG :/. We're stuck on MSVC with c++17 so we have no way of influencing the branch ordering for that function without profile guided optimization Remove binding stride assert in shader_translator.cc, triangle told me its leftover ogl stuff Mark xe::FatalError as noreturn If a controller is not connected, delay by 1.1 seconds before checking if it has been reconnected. Asking Xinput about a controller slot that is unused is extremely slow, and XinputGetState/SetState were taking up an enormous amount of time in profiles. this may have caused a bit of input lag Protect accesses to input_system with a lock Add proper handling for user_index>= 4 in XamInputGetState/SetState, properly return zeroed state in GetState Add missing argument to NtQueryVirtualMemory_entry Fixed RtlCompareMemoryUlong_entry, it actually does not care if the source is misaligned, and for length it aligns down Fixed RtlUpperChar and RtlLowerChar, added a table that has their correct return values precomputed
325 lines
12 KiB
C++
325 lines
12 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. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#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/ring_buffer.h"
|
|
#include "xenia/base/threading.h"
|
|
#include "xenia/gpu/register_file.h"
|
|
#include "xenia/gpu/registers.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 {
|
|
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<void()> 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) = 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(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);
|
|
XE_FORCEINLINE
|
|
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_FORCEINLINE
|
|
virtual void WriteOneRegisterFromRing(
|
|
xe::RingBuffer* ring, uint32_t base,
|
|
uint32_t
|
|
num_times); // repeatedly write a value to one register, presumably a
|
|
// register with special handling for writes
|
|
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(RingBuffer* reader);
|
|
bool ExecutePacketType0(RingBuffer* reader, uint32_t packet);
|
|
bool ExecutePacketType1(RingBuffer* reader, uint32_t packet);
|
|
bool ExecutePacketType2(RingBuffer* reader, uint32_t packet);
|
|
bool ExecutePacketType3(RingBuffer* reader, uint32_t packet);
|
|
bool ExecutePacketType3_ME_INIT(RingBuffer* reader, uint32_t packet,
|
|
uint32_t count);
|
|
bool ExecutePacketType3_NOP(RingBuffer* reader, uint32_t packet,
|
|
uint32_t count);
|
|
bool ExecutePacketType3_INTERRUPT(RingBuffer* reader, uint32_t packet,
|
|
uint32_t count);
|
|
bool ExecutePacketType3_XE_SWAP(RingBuffer* reader, uint32_t packet,
|
|
uint32_t count);
|
|
bool ExecutePacketType3_INDIRECT_BUFFER(RingBuffer* reader, uint32_t packet,
|
|
uint32_t count);
|
|
bool ExecutePacketType3_WAIT_REG_MEM(RingBuffer* reader, uint32_t packet,
|
|
uint32_t count);
|
|
bool ExecutePacketType3_REG_RMW(RingBuffer* reader, uint32_t packet,
|
|
uint32_t count);
|
|
bool ExecutePacketType3_REG_TO_MEM(RingBuffer* reader, uint32_t packet,
|
|
uint32_t count);
|
|
bool ExecutePacketType3_MEM_WRITE(RingBuffer* reader, uint32_t packet,
|
|
uint32_t count);
|
|
bool ExecutePacketType3_COND_WRITE(RingBuffer* reader, uint32_t packet,
|
|
uint32_t count);
|
|
bool ExecutePacketType3_EVENT_WRITE(RingBuffer* reader, uint32_t packet,
|
|
uint32_t count);
|
|
bool ExecutePacketType3_EVENT_WRITE_SHD(RingBuffer* reader, uint32_t packet,
|
|
uint32_t count);
|
|
bool ExecutePacketType3_EVENT_WRITE_EXT(RingBuffer* reader, uint32_t packet,
|
|
uint32_t count);
|
|
bool ExecutePacketType3_EVENT_WRITE_ZPD(RingBuffer* reader, uint32_t packet,
|
|
uint32_t count);
|
|
bool ExecutePacketType3Draw(RingBuffer* reader, uint32_t packet,
|
|
const char* opcode_name,
|
|
uint32_t viz_query_condition,
|
|
uint32_t count_remaining);
|
|
bool ExecutePacketType3_DRAW_INDX(RingBuffer* reader, uint32_t packet,
|
|
uint32_t count);
|
|
bool ExecutePacketType3_DRAW_INDX_2(RingBuffer* reader, uint32_t packet,
|
|
uint32_t count);
|
|
bool ExecutePacketType3_SET_CONSTANT(RingBuffer* reader, uint32_t packet,
|
|
uint32_t count);
|
|
bool ExecutePacketType3_SET_CONSTANT2(RingBuffer* reader, uint32_t packet,
|
|
uint32_t count);
|
|
bool ExecutePacketType3_LOAD_ALU_CONSTANT(RingBuffer* reader, uint32_t packet,
|
|
uint32_t count);
|
|
bool ExecutePacketType3_SET_SHADER_CONSTANTS(RingBuffer* reader,
|
|
uint32_t packet, uint32_t count);
|
|
bool ExecutePacketType3_IM_LOAD(RingBuffer* reader, uint32_t packet,
|
|
uint32_t count);
|
|
bool ExecutePacketType3_IM_LOAD_IMMEDIATE(RingBuffer* reader,
|
|
|
|
uint32_t packet, uint32_t count);
|
|
bool ExecutePacketType3_INVALIDATE_STATE(RingBuffer* reader, uint32_t packet,
|
|
uint32_t count);
|
|
bool ExecutePacketType3_VIZ_QUERY(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();
|
|
|
|
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::filesystem::path trace_stream_path_;
|
|
std::filesystem::path trace_frame_path_;
|
|
|
|
std::atomic<bool> worker_running_;
|
|
kernel::object_ref<kernel::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<xe::threading::Event> write_ptr_index_event_;
|
|
std::atomic<uint32_t> 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_
|