Reverse-engineering aid for decoding game mesh formats against GPU ground
truth. When the `log_draws` cvar is set, each distinct draw's primitive
type, index buffer (guest base / count / format / endianness), and full
per-stream vertex declaration (fetch-constant base + stride, and every
element's format + offset) is written to xenia_re_draws.log.
- command_processor.{h,cc}: CommandProcessor::LogDrawForRE(), no-op unless
the cvar is set. De-dups by the vertex-declaration fingerprint (shader +
primitive + element formats/offsets), so animated UI that redraws the
same format into fresh buffers every frame collapses to one record --
keeping it near-free (an earlier address-keyed de-dup flooded the log and
stalled the GPU thread). Capped at 4096 distinct formats.
- pm4_command_processor_implement.h: call it from ExecutePacketType3Draw
after IssueDraw (so the vertex shader has been analyzed). Backend-agnostic
base path -- works for the Vulkan build.
Used to confirm Project Sylpheed's XBG7 mesh layout (triangle list, pos
f32x3 / normal f16x4 / uv f16x2, variable stride).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
556 lines
20 KiB
C++
556 lines
20 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 <unordered_map>
|
|
#include <vector>
|
|
|
|
#include "xenia/base/math.h"
|
|
#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 {
|
|
|
|
enum class GPUSetting { ClearMemoryPageState, ReadbackMemexport };
|
|
|
|
enum class ReadbackResolveMode {
|
|
kDisabled, // No readback (none)
|
|
kFast, // Delayed sync, 1 frame behind (fast)
|
|
kFull // Immediate sync with GPU stall (full)
|
|
};
|
|
|
|
// Occlusion queries - ZPD report mode.
|
|
enum class ZPDMode {
|
|
kFake, // Fake sample counts, no real GPU queries (fake)
|
|
kFast, // Real queries with speculative cached writes (fast)
|
|
kFastAlt, // Fast queries, but preserves cached zeroes (fast-alt)
|
|
kStrict, // Real queries, waits before writeback (strict)
|
|
};
|
|
|
|
void SaveGPUSetting(GPUSetting setting, uint64_t value);
|
|
bool GetGPUSetting(GPUSetting setting);
|
|
ReadbackResolveMode GetReadbackResolveMode();
|
|
void SetReadbackResolveMode(const std::string& mode);
|
|
ZPDMode GetZPDMode();
|
|
void SetZPDMode(const std::string& mode);
|
|
|
|
// Shared pool capacity for D3D12 and Vulkan.
|
|
constexpr uint32_t kZPDQueryPoolCapacity = 8192;
|
|
|
|
// Contiguous range of query indices for batched resolve/copy operations.
|
|
struct ResolveRange {
|
|
uint32_t start;
|
|
uint32_t count;
|
|
};
|
|
|
|
// Backstop for strict mode. Abandon any pending retires after this many polls
|
|
// so EVENT_WRITE_ZPD doesn't keep spinning on an unresolved report.
|
|
constexpr uint32_t kStrictZPDRetireMaxStalls = 16;
|
|
// Clock backstop used for strict retire if guest polling is sparse.
|
|
constexpr uint64_t kStrictZPDRetireDeadlineMs = 2;
|
|
|
|
// Cap for the fast-mode cached delta map. Games reuse a small set of report
|
|
// addresses so this should never be hit, but prevents unbounded growth if a
|
|
// title cycles through unique addresses. Clearing the cache has no
|
|
// correctness impact - it only removes speculative writeback hints.
|
|
constexpr size_t kFastZPDCacheMaxEntries = 1024;
|
|
|
|
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<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) {}
|
|
|
|
// 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,
|
|
std::function<void()> completion_callback = nullptr);
|
|
|
|
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 LogRegisterSet(uint32_t register_index, uint32_t value);
|
|
void LogRegisterSets(uint32_t base_register_index, const uint32_t* values,
|
|
uint32_t n_values);
|
|
|
|
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;
|
|
};
|
|
|
|
static constexpr uint32_t kReadbackBufferSizeIncrement = 16 * 1024 * 1024;
|
|
|
|
// Eviction policy constants for readback buffer cache
|
|
static constexpr size_t kMaxReadbackBuffers = 64;
|
|
static constexpr uint64_t kReadbackBufferEvictionAgeFrames = 60;
|
|
|
|
// Progressive alignment for readback buffers to avoid wasting memory
|
|
static inline uint32_t AlignReadbackBufferSize(uint32_t size) {
|
|
if (size < 1 * 1024 * 1024) {
|
|
return xe::align(size, 256u * 1024u); // 256KB for < 1MB
|
|
} else if (size < 4 * 1024 * 1024) {
|
|
return xe::align(size, 1u * 1024u * 1024u); // 1MB for < 4MB
|
|
} else {
|
|
return xe::align(size, kReadbackBufferSizeIncrement); // 16MB for >= 4MB
|
|
}
|
|
}
|
|
|
|
// Generate a cache key for a specific resolve operation
|
|
static inline uint64_t MakeReadbackResolveKey(uint32_t address,
|
|
uint32_t length) {
|
|
return (uint64_t(address) << 32) | uint64_t(length);
|
|
}
|
|
|
|
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 PollCompletedSubmission() {}
|
|
|
|
// Used by strict ZPD to distinguish normal in flight latency from a
|
|
// genuinely stuck report.
|
|
virtual uint64_t GetCompletedSubmission() const { return 0; }
|
|
|
|
virtual void OnPrimaryBufferEnd() {}
|
|
|
|
// TODO(boma): Add tracking for EVENT_WRITE_EXT reports.
|
|
using ReportHandle = uint64_t;
|
|
static constexpr ReportHandle kInvalidReportHandle = 0;
|
|
|
|
enum class QueryOpenResult {
|
|
kOpened,
|
|
kDeferred,
|
|
kPoolExhausted,
|
|
kFailed,
|
|
};
|
|
|
|
// One active guest report slot. May span multiple host query segments split
|
|
// across submissions or render passes, final value is the normalized sum.
|
|
struct ZPDReport {
|
|
// Raw host count across all segments, normalized at retirement.
|
|
uint64_t accumulated_samples = 0;
|
|
// Submission of the first closed segment.
|
|
uint64_t first_segment_end_submission = 0;
|
|
// Submission containing the most recently closed segment's resolve.
|
|
uint64_t last_segment_end_submission = 0;
|
|
uint64_t slot_sequence_id = 0;
|
|
uint32_t slot_base = 0;
|
|
uint32_t begin_record = 0;
|
|
uint32_t end_record = 0;
|
|
// Snapshotted at BEGIN from zpd_slot_values_.
|
|
uint32_t begin_value = 0;
|
|
uint32_t pending_segments = 0;
|
|
// Last known delta. Carried forward on forced close so slot doesn't
|
|
// briefly look fully occluded. 0 is a valid delta for alternate fast path.
|
|
uint32_t cached_delta = 0;
|
|
bool has_cached_delta = false;
|
|
bool ended = false;
|
|
};
|
|
|
|
// Currently open guest lifetime. Retired reports are tracked separately
|
|
// by handle until their query segments resolve. This intentionally models
|
|
// only one logical report at a time. That's enough for conventional ZPD
|
|
// reports, but QueryBatch can have multiple slots in flight, so it doesn't
|
|
// fit this layout. Eventually this probably wants to become something more
|
|
// like a map of active reports keyed by slot and sequence instead.
|
|
struct ActiveZPDSegment {
|
|
ReportHandle report_handle = kInvalidReportHandle;
|
|
uint32_t slot_base = 0;
|
|
uint32_t begin_record = 0;
|
|
uint32_t end_record = 0;
|
|
bool segment_active = false;
|
|
bool segment_pending_begin = false;
|
|
bool logical_active = false;
|
|
};
|
|
|
|
struct PendingZPDSlot {
|
|
ReportHandle report_handle = kInvalidReportHandle;
|
|
uint32_t cached_delta = 0;
|
|
bool has_cached_delta = false;
|
|
};
|
|
|
|
virtual void EnsureZPDQueryResources() {}
|
|
virtual void ShutdownZPDQueryResources() {}
|
|
|
|
virtual bool IsZPDQueryPoolReady() const { return false; }
|
|
virtual bool CanOpenZPDQuery() const { return true; }
|
|
|
|
// Backend acquires a pool slot, records BeginQuery, tracks it internally.
|
|
virtual QueryOpenResult OpenZPDQuery(ReportHandle report_handle,
|
|
bool can_close_submission) {
|
|
return QueryOpenResult::kFailed;
|
|
}
|
|
// Backend records EndQuery, queues a resolve for the active slot.
|
|
virtual bool CloseZPDQuery(ReportHandle report_handle,
|
|
uint64_t& out_submission) {
|
|
return false;
|
|
}
|
|
// Backend discards the active query without resolving.
|
|
virtual bool DiscardZPDQuery() { return false; }
|
|
|
|
// Backend drains completed resolves and calls OnZPDQueryResolved for each.
|
|
virtual void PumpQueryResolves() {}
|
|
// Backend waits for all pending segments of report_handle to resolve.
|
|
virtual bool AwaitQueryResolve(ReportHandle report_handle,
|
|
uint64_t wait_for_submission) {
|
|
return false;
|
|
}
|
|
|
|
bool BeginZPDReport(uint32_t report_address);
|
|
bool EndZPDReport(uint32_t report_address, bool guest_forced_end);
|
|
// Opens a new host query segment when CanOpenZPDQuery is true.
|
|
void OpenQuerySegment(bool can_close_submission);
|
|
// Closes the current segment at a submission or render pass boundary.
|
|
// The logical report stays open and a new segment will open at the next
|
|
// opportunity.
|
|
void CloseQuerySegment();
|
|
|
|
// Called by backends when a host query resolve completes. Accumulates
|
|
// the raw sample count, and if all segments are done, commits the report
|
|
// to guest memory.
|
|
void OnZPDQueryResolved(ReportHandle report_handle, uint64_t raw_samples);
|
|
|
|
// Writes guest report with begin_value read from guest memory.
|
|
// Orphan END path only when no controller snapshot is available.
|
|
void WriteZPDReport(uint32_t begin_record, uint32_t end_record,
|
|
uint32_t begin_value, uint32_t delta_value,
|
|
bool write_begin_record);
|
|
|
|
// Called from PrepareForWait so strict mode can retire before guest loops
|
|
// again. Gives up after kStrictZPDRetireMaxStalls.
|
|
void PumpPendingRetire();
|
|
|
|
// Divides host count by draw resolution scale.
|
|
uint32_t NormalizeSampleCount(uint64_t samples) const;
|
|
|
|
// Writes the final report to guest memory and advances the slot running
|
|
// total. Called when a report fully resolves or is abandoned.
|
|
void CommitZPDReport(ZPDReport& report, uint32_t delta_value);
|
|
// Checks that the report's slot sequence is still current (not reused).
|
|
bool IsZPDReportCurrent(const ZPDReport& report) const;
|
|
PendingZPDSlot GetPendingZPDSlot(uint32_t slot_base,
|
|
uint32_t end_record) const;
|
|
|
|
void ResetZPDState() {
|
|
zpd_active_segment_ = {};
|
|
zpd_next_report_handle_ = 1;
|
|
zpd_slot_sequences_.clear();
|
|
zpd_slot_values_.clear();
|
|
logical_zpd_reports_.clear();
|
|
fast_zpd_report_cached_values_.clear();
|
|
fake_zpd_sample_count_ = 0;
|
|
querybatch_zpd_sample_count_ = UINT32_MAX;
|
|
zpd_pending_retire_handle_ = kInvalidReportHandle;
|
|
zpd_pending_retire_stalls_ = 0;
|
|
zpd_pending_retire_start_ms_ = 0;
|
|
zpd_force_fake_fallback_ = false;
|
|
}
|
|
|
|
#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; }
|
|
|
|
// Reverse-engineering aid (cvar `log_draws`): dump the guest's exact
|
|
// primitive type + index buffer + per-stream vertex declaration for each
|
|
// distinct draw to a dedicated file, for decoding game mesh formats.
|
|
// No-op unless the cvar is enabled. Defined in command_processor.cc.
|
|
void LogDrawForRE(uint32_t vgt_draw_initiator_value,
|
|
const IndexBufferInfo* index_buffer_info);
|
|
|
|
// "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;
|
|
|
|
ReportHandle zpd_next_report_handle_ = 1;
|
|
std::unordered_map<uint32_t, uint64_t> zpd_slot_sequences_;
|
|
std::unordered_map<uint32_t, uint32_t> zpd_slot_values_;
|
|
std::unordered_map<ReportHandle, ZPDReport> logical_zpd_reports_;
|
|
ActiveZPDSegment zpd_active_segment_{};
|
|
|
|
// Cached delta per END.
|
|
// Fast mode uses this for speculative writeback and orphaned END replay.
|
|
std::unordered_map<uint32_t, uint32_t> fast_zpd_report_cached_values_;
|
|
|
|
uint32_t querybatch_zpd_sample_count_ = UINT32_MAX;
|
|
bool zpd_force_fake_fallback_ = false;
|
|
|
|
// Strict mode defers guest completion until the queued END has retired.
|
|
ReportHandle zpd_pending_retire_handle_ = kInvalidReportHandle;
|
|
uint32_t zpd_pending_retire_stalls_ = 0;
|
|
// Uptime in ms when zpd_pending_retire_handle_ was first set.
|
|
uint64_t zpd_pending_retire_start_ms_ = 0;
|
|
|
|
// Set by the backend when resolution scale changes.
|
|
uint32_t zpd_draw_resolution_scale_x_ = 1;
|
|
uint32_t zpd_draw_resolution_scale_y_ = 1;
|
|
|
|
uint32_t zpd_draw_resolution_scale_x() const {
|
|
return zpd_draw_resolution_scale_x_;
|
|
}
|
|
uint32_t zpd_draw_resolution_scale_y() const {
|
|
return zpd_draw_resolution_scale_y_;
|
|
}
|
|
|
|
uint32_t fake_zpd_sample_count_ = 0;
|
|
|
|
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;
|
|
|
|
XE_NOINLINE XE_COLD void LogKickoffInitator(uint32_t value);
|
|
};
|
|
|
|
} // namespace gpu
|
|
} // namespace xe
|
|
|
|
#endif // XENIA_GPU_COMMAND_PROCESSOR_H_
|