[GPU] Add FXAA post-processing [UI] Add FidelityFX FSR and CAS post-processing [UI] Add blue noise dithering from 10bpc to 8bpc [GPU] Apply the DC PWL gamma ramp closer to the spec, supporting fully white color [UI] Allow the GPU CP thread to present on the host directly, bypassing the UI thread OS paint event [UI] Allow variable refresh rate (or tearing) [UI] Present the newest frame (restart) on DXGI [UI] Replace GraphicsContext with a far more advanced Presenter with more coherent surface connection and UI overlay state management [UI] Connect presentation to windows via the Surface class, not native window handles [Vulkan] Switch to simpler Vulkan setup with no instance/device separation due to interdependencies and to pass fewer objects around [Vulkan] Lower the minimum required Vulkan version to 1.0 [UI/GPU] Various cleanup, mainly ComPtr usage [UI] Support per-monitor DPI awareness v2 on Windows [UI] DPI-scale Dear ImGui [UI] Replace the remaining non-detachable window delegates with unified window event and input listeners [UI] Allow listeners to safely destroy or close the window, and to register/unregister listeners without use-after-free and the ABA problem [UI] Explicit Z ordering of input listeners and UI overlays, top-down for input, bottom-up for drawing [UI] Add explicit window lifecycle phases [UI] Replace Window virtual functions with explicit desired state, its application, actual state, its feedback [UI] GTK: Apply the initial size to the drawing area [UI] Limit internal UI frame rate to that of the monitor [UI] Hide the cursor using a timer instead of polling due to no repeated UI thread paints with GPU CP thread presentation, and only within the window
236 lines
8.2 KiB
C++
236 lines
8.2 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. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include "xenia/gpu/trace_player.h"
|
|
|
|
#include "xenia/gpu/command_processor.h"
|
|
#include "xenia/gpu/graphics_system.h"
|
|
#include "xenia/gpu/xenos.h"
|
|
#include "xenia/memory.h"
|
|
|
|
namespace xe {
|
|
namespace gpu {
|
|
|
|
TracePlayer::TracePlayer(GraphicsSystem* graphics_system)
|
|
: graphics_system_(graphics_system),
|
|
current_frame_index_(0),
|
|
current_command_index_(-1) {
|
|
// Need to allocate all of physical memory so that we can write to it during
|
|
// playback. The 64 KB page heap is larger, covers the entire physical memory,
|
|
// so it is used instead of the 4 KB page one.
|
|
auto heap = graphics_system_->memory()->LookupHeapByType(true, 64 * 1024);
|
|
heap->AllocFixed(heap->heap_base(), heap->heap_size(), heap->page_size(),
|
|
kMemoryAllocationReserve | kMemoryAllocationCommit,
|
|
kMemoryProtectRead | kMemoryProtectWrite);
|
|
|
|
playback_event_ = xe::threading::Event::CreateAutoResetEvent(false);
|
|
}
|
|
|
|
TracePlayer::~TracePlayer() { delete[] edram_snapshot_; }
|
|
|
|
const TraceReader::Frame* TracePlayer::current_frame() const {
|
|
if (current_frame_index_ >= frame_count()) {
|
|
return nullptr;
|
|
}
|
|
return frame(current_frame_index_);
|
|
}
|
|
|
|
void TracePlayer::SeekFrame(int target_frame) {
|
|
if (current_frame_index_ == target_frame) {
|
|
return;
|
|
}
|
|
current_frame_index_ = target_frame;
|
|
auto frame = current_frame();
|
|
current_command_index_ = int(frame->commands.size()) - 1;
|
|
|
|
assert_true(frame->start_ptr <= frame->end_ptr);
|
|
PlayTrace(frame->start_ptr, frame->end_ptr - frame->start_ptr,
|
|
TracePlaybackMode::kBreakOnSwap, false);
|
|
}
|
|
|
|
void TracePlayer::SeekCommand(int target_command) {
|
|
if (current_command_index_ == target_command) {
|
|
return;
|
|
}
|
|
int previous_command_index = current_command_index_;
|
|
current_command_index_ = target_command;
|
|
if (current_command_index_ == -1) {
|
|
return;
|
|
}
|
|
auto frame = current_frame();
|
|
const auto& command = frame->commands[target_command];
|
|
assert_true(frame->start_ptr <= command.end_ptr);
|
|
if (previous_command_index != -1 && target_command > previous_command_index) {
|
|
// Seek forward.
|
|
const auto& previous_command = frame->commands[previous_command_index];
|
|
PlayTrace(previous_command.end_ptr,
|
|
command.end_ptr - previous_command.end_ptr,
|
|
TracePlaybackMode::kBreakOnSwap, false);
|
|
} else {
|
|
// Full playback from frame start.
|
|
PlayTrace(frame->start_ptr, command.end_ptr - frame->start_ptr,
|
|
TracePlaybackMode::kBreakOnSwap, true);
|
|
}
|
|
}
|
|
|
|
void TracePlayer::WaitOnPlayback() {
|
|
xe::threading::Wait(playback_event_.get(), true);
|
|
}
|
|
|
|
void TracePlayer::PlayTrace(const uint8_t* trace_data, size_t trace_size,
|
|
TracePlaybackMode playback_mode,
|
|
bool clear_caches) {
|
|
playing_trace_ = true;
|
|
// Pass a copy of present_last_copy_ to the thread so it's not accessible by
|
|
// multiple threads at once.
|
|
bool present_last_copy = present_last_copy_;
|
|
graphics_system_->command_processor()->CallInThread([=]() {
|
|
PlayTraceOnThread(trace_data, trace_size, playback_mode, clear_caches,
|
|
present_last_copy);
|
|
});
|
|
}
|
|
|
|
void TracePlayer::PlayTraceOnThread(const uint8_t* trace_data,
|
|
size_t trace_size,
|
|
TracePlaybackMode playback_mode,
|
|
bool clear_caches, bool present_last_copy) {
|
|
auto memory = graphics_system_->memory();
|
|
auto command_processor = graphics_system_->command_processor();
|
|
|
|
if (clear_caches) {
|
|
command_processor->ClearCaches();
|
|
}
|
|
|
|
if (present_last_copy) {
|
|
command_processor->SetIgnoreSwap(true);
|
|
}
|
|
|
|
playback_percent_ = 0;
|
|
auto trace_end = trace_data + trace_size;
|
|
|
|
playing_trace_ = true;
|
|
auto trace_ptr = trace_data;
|
|
bool pending_break = false;
|
|
const PacketStartCommand* pending_packet = nullptr;
|
|
while (trace_ptr < trace_data + trace_size) {
|
|
playback_percent_ = uint32_t(
|
|
(float(trace_ptr - trace_data) / float(trace_end - trace_data)) *
|
|
10000);
|
|
|
|
auto type = static_cast<TraceCommandType>(xe::load<uint32_t>(trace_ptr));
|
|
switch (type) {
|
|
case TraceCommandType::kPrimaryBufferStart: {
|
|
auto cmd =
|
|
reinterpret_cast<const PrimaryBufferStartCommand*>(trace_ptr);
|
|
//
|
|
trace_ptr += sizeof(*cmd) + cmd->count * 4;
|
|
break;
|
|
}
|
|
case TraceCommandType::kPrimaryBufferEnd: {
|
|
auto cmd = reinterpret_cast<const PrimaryBufferEndCommand*>(trace_ptr);
|
|
//
|
|
trace_ptr += sizeof(*cmd);
|
|
break;
|
|
}
|
|
case TraceCommandType::kIndirectBufferStart: {
|
|
auto cmd =
|
|
reinterpret_cast<const IndirectBufferStartCommand*>(trace_ptr);
|
|
//
|
|
trace_ptr += sizeof(*cmd) + cmd->count * 4;
|
|
break;
|
|
}
|
|
case TraceCommandType::kIndirectBufferEnd: {
|
|
auto cmd = reinterpret_cast<const IndirectBufferEndCommand*>(trace_ptr);
|
|
//
|
|
trace_ptr += sizeof(*cmd);
|
|
break;
|
|
}
|
|
case TraceCommandType::kPacketStart: {
|
|
auto cmd = reinterpret_cast<const PacketStartCommand*>(trace_ptr);
|
|
trace_ptr += sizeof(*cmd);
|
|
std::memcpy(memory->TranslatePhysical(cmd->base_ptr), trace_ptr,
|
|
cmd->count * 4);
|
|
trace_ptr += cmd->count * 4;
|
|
pending_packet = cmd;
|
|
break;
|
|
}
|
|
case TraceCommandType::kPacketEnd: {
|
|
auto cmd = reinterpret_cast<const PacketEndCommand*>(trace_ptr);
|
|
trace_ptr += sizeof(*cmd);
|
|
if (pending_packet) {
|
|
command_processor->ExecutePacket(pending_packet->base_ptr,
|
|
pending_packet->count);
|
|
pending_packet = nullptr;
|
|
}
|
|
if (pending_break) {
|
|
playing_trace_ = false;
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case TraceCommandType::kMemoryRead: {
|
|
auto cmd = reinterpret_cast<const MemoryCommand*>(trace_ptr);
|
|
trace_ptr += sizeof(*cmd);
|
|
DecompressMemory(cmd->encoding_format, trace_ptr, cmd->encoded_length,
|
|
memory->TranslatePhysical(cmd->base_ptr),
|
|
cmd->decoded_length);
|
|
trace_ptr += cmd->encoded_length;
|
|
command_processor->TracePlaybackWroteMemory(cmd->base_ptr,
|
|
cmd->decoded_length);
|
|
break;
|
|
}
|
|
case TraceCommandType::kMemoryWrite: {
|
|
auto cmd = reinterpret_cast<const MemoryCommand*>(trace_ptr);
|
|
trace_ptr += sizeof(*cmd);
|
|
// ?
|
|
// Assuming the command processor will do the same write.
|
|
trace_ptr += cmd->encoded_length;
|
|
break;
|
|
}
|
|
case TraceCommandType::kEdramSnapshot: {
|
|
auto cmd = reinterpret_cast<const EdramSnapshotCommand*>(trace_ptr);
|
|
trace_ptr += sizeof(*cmd);
|
|
if (!edram_snapshot_) {
|
|
edram_snapshot_ = new uint8_t[xenos::kEdramSizeBytes];
|
|
}
|
|
DecompressMemory(cmd->encoding_format, trace_ptr, cmd->encoded_length,
|
|
edram_snapshot_, xenos::kEdramSizeBytes);
|
|
trace_ptr += cmd->encoded_length;
|
|
command_processor->RestoreEdramSnapshot(edram_snapshot_);
|
|
break;
|
|
}
|
|
case TraceCommandType::kEvent: {
|
|
auto cmd = reinterpret_cast<const EventCommand*>(trace_ptr);
|
|
trace_ptr += sizeof(*cmd);
|
|
switch (cmd->event_type) {
|
|
case EventCommand::Type::kSwap: {
|
|
if (playback_mode == TracePlaybackMode::kBreakOnSwap) {
|
|
pending_break = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
playing_trace_ = false;
|
|
|
|
if (present_last_copy) {
|
|
command_processor->SetIgnoreSwap(false);
|
|
command_processor->IssueSwap(0, 1280, 720);
|
|
}
|
|
|
|
playback_event_->Set();
|
|
}
|
|
|
|
} // namespace gpu
|
|
} // namespace xe
|