Files
Xenia-Canary/src/xenia/gpu/graphics_system.cc
Sandy Carter 382dd8860f [threading] Change thread names to suit pthread
Shorten names to 16.
Rename Win32 to Windowing.
Shorten GraphicsSystem thread names due to 16 length limit of pthread.
Without this change, both show up as GraphicsSystem.
Remove redundant "Worker" and "Thread" from names.
Remove redundant thread handle from thread name.
2020-11-15 11:12:05 -06:00

345 lines
11 KiB
C++

/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/gpu/graphics_system.h"
#include "xenia/base/byte_stream.h"
#include "xenia/base/clock.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/base/profiling.h"
#include "xenia/base/threading.h"
#include "xenia/gpu/command_processor.h"
#include "xenia/gpu/gpu_flags.h"
#include "xenia/ui/graphics_provider.h"
#include "xenia/ui/loop.h"
DEFINE_bool(
store_shaders, true,
"Store shaders persistently and load them when loading games to avoid "
"runtime spikes and freezes when playing the game not for the first time.",
"GPU");
namespace xe {
namespace gpu {
// Nvidia Optimus/AMD PowerXpress support.
// These exports force the process to trigger the discrete GPU in multi-GPU
// systems.
// https://developer.download.nvidia.com/devzone/devcenter/gamegraphics/files/OptimusRenderingPolicies.pdf
// https://stackoverflow.com/questions/17458803/amd-equivalent-to-nvoptimusenablement
#if XE_PLATFORM_WIN32
extern "C" {
__declspec(dllexport) uint32_t NvOptimusEnablement = 0x00000001;
__declspec(dllexport) uint32_t AmdPowerXpressRequestHighPerformance = 1;
} // extern "C"
#endif // XE_PLATFORM_WIN32
GraphicsSystem::GraphicsSystem() : vsync_worker_running_(false) {}
GraphicsSystem::~GraphicsSystem() = default;
X_STATUS GraphicsSystem::Setup(cpu::Processor* processor,
kernel::KernelState* kernel_state,
ui::Window* target_window) {
memory_ = processor->memory();
processor_ = processor;
kernel_state_ = kernel_state;
target_window_ = target_window;
// Initialize display and rendering context.
// This must happen on the UI thread.
std::unique_ptr<xe::ui::GraphicsContext> processor_context = nullptr;
if (provider_) {
if (target_window_) {
target_window_->loop()->PostSynchronous([&]() {
// Create the context used for presentation.
assert_null(target_window->context());
target_window_->set_context(provider_->CreateContext(target_window_));
// Setup the context the command processor will do all its drawing in.
// It's shared with the display context so that we can resolve
// framebuffers from it.
processor_context = provider()->CreateOffscreenContext();
});
} else {
processor_context = provider()->CreateOffscreenContext();
}
if (!processor_context) {
xe::FatalError(
"Unable to initialize graphics context. Xenia requires Vulkan "
"support.\n"
"\n"
"Ensure you have the latest drivers for your GPU and "
"that it supports Vulkan.\n"
"\n"
"See https://xenia.jp/faq/ for more information and a list of "
"supported GPUs.");
return X_STATUS_UNSUCCESSFUL;
}
}
// Create command processor. This will spin up a thread to process all
// incoming ringbuffer packets.
command_processor_ = CreateCommandProcessor();
if (!command_processor_->Initialize(std::move(processor_context))) {
XELOGE("Unable to initialize command processor");
return X_STATUS_UNSUCCESSFUL;
}
if (target_window) {
command_processor_->set_swap_request_handler(
[this]() { target_window_->Invalidate(); });
// Watch for paint requests to do our swap.
target_window->on_painting.AddListener(
[this](xe::ui::UIEvent* e) { Swap(e); });
// Watch for context lost events.
target_window->on_context_lost.AddListener(
[this](xe::ui::UIEvent* e) { Reset(); });
} else {
command_processor_->set_swap_request_handler([]() {});
}
// Let the processor know we want register access callbacks.
memory_->AddVirtualMappedRange(
0x7FC80000, 0xFFFF0000, 0x0000FFFF, this,
reinterpret_cast<cpu::MMIOReadCallback>(ReadRegisterThunk),
reinterpret_cast<cpu::MMIOWriteCallback>(WriteRegisterThunk));
// 60hz vsync timer.
vsync_worker_running_ = true;
vsync_worker_thread_ = kernel::object_ref<kernel::XHostThread>(
new kernel::XHostThread(kernel_state_, 128 * 1024, 0, [this]() {
uint64_t vsync_duration = cvars::vsync ? 16 : 1;
uint64_t last_frame_time = Clock::QueryGuestTickCount();
while (vsync_worker_running_) {
uint64_t current_time = Clock::QueryGuestTickCount();
uint64_t elapsed = (current_time - last_frame_time) /
(Clock::guest_tick_frequency() / 1000);
if (elapsed >= vsync_duration) {
MarkVblank();
last_frame_time = current_time;
}
xe::threading::Sleep(std::chrono::milliseconds(1));
}
return 0;
}));
// As we run vblank interrupts the debugger must be able to suspend us.
vsync_worker_thread_->set_can_debugger_suspend(true);
vsync_worker_thread_->set_name("GPU VSync");
vsync_worker_thread_->Create();
if (cvars::trace_gpu_stream) {
BeginTracing();
}
return X_STATUS_SUCCESS;
}
void GraphicsSystem::Shutdown() {
if (command_processor_) {
EndTracing();
command_processor_->Shutdown();
}
if (vsync_worker_thread_) {
vsync_worker_running_ = false;
vsync_worker_thread_->Wait(0, 0, 0, nullptr);
vsync_worker_thread_.reset();
}
}
void GraphicsSystem::Reset() {
// TODO(DrChat): Reset the system.
XELOGI("Context lost; Reset invoked");
Shutdown();
xe::FatalError("Graphics device lost (probably due to an internal error)");
}
uint32_t GraphicsSystem::ReadRegisterThunk(void* ppc_context,
GraphicsSystem* gs, uint32_t addr) {
return gs->ReadRegister(addr);
}
void GraphicsSystem::WriteRegisterThunk(void* ppc_context, GraphicsSystem* gs,
uint32_t addr, uint32_t value) {
gs->WriteRegister(addr, value);
}
uint32_t GraphicsSystem::ReadRegister(uint32_t addr) {
uint32_t r = (addr & 0xFFFF) / 4;
switch (r) {
case 0x0F00: // RB_EDRAM_TIMING
return 0x08100748;
case 0x0F01: // RB_BC_CONTROL
return 0x0000200E;
case 0x194C: // R500_D1MODE_V_COUNTER
return 0x000002D0;
case 0x1951: // interrupt status
return 1; // vblank
case 0x1961: // AVIVO_D1MODE_VIEWPORT_SIZE
// Screen res - 1280x720
// maximum [width(0x0FFF), height(0x0FFF)]
return 0x050002D0;
default:
if (!register_file_.GetRegisterInfo(r)) {
XELOGE("GPU: Read from unknown register ({:04X})", r);
}
}
assert_true(r < RegisterFile::kRegisterCount);
return register_file_.values[r].u32;
}
void GraphicsSystem::WriteRegister(uint32_t addr, uint32_t value) {
uint32_t r = (addr & 0xFFFF) / 4;
switch (r) {
case 0x01C5: // CP_RB_WPTR
command_processor_->UpdateWritePointer(value);
break;
case 0x1844: // AVIVO_D1GRPH_PRIMARY_SURFACE_ADDRESS
break;
default:
XELOGW("Unknown GPU register {:04X} write: {:08X}", r, value);
break;
}
assert_true(r < RegisterFile::kRegisterCount);
register_file_.values[r].u32 = value;
}
void GraphicsSystem::InitializeRingBuffer(uint32_t ptr, uint32_t log2_size) {
command_processor_->InitializeRingBuffer(ptr, log2_size + 0x3);
}
void GraphicsSystem::EnableReadPointerWriteBack(uint32_t ptr,
uint32_t block_size) {
command_processor_->EnableReadPointerWriteBack(ptr, block_size);
}
void GraphicsSystem::SetInterruptCallback(uint32_t callback,
uint32_t user_data) {
interrupt_callback_ = callback;
interrupt_callback_data_ = user_data;
XELOGGPU("SetInterruptCallback({:08X}, {:08X})", callback, user_data);
}
void GraphicsSystem::DispatchInterruptCallback(uint32_t source, uint32_t cpu) {
if (!interrupt_callback_) {
return;
}
auto thread = kernel::XThread::GetCurrentThread();
assert_not_null(thread);
// Pick a CPU, if needed. We're going to guess 2. Because.
if (cpu == 0xFFFFFFFF) {
cpu = 2;
}
thread->SetActiveCpu(cpu);
// XELOGGPU("Dispatching GPU interrupt at {:08X} w/ mode {} on cpu {}",
// interrupt_callback_, source, cpu);
uint64_t args[] = {source, interrupt_callback_data_};
processor_->ExecuteInterrupt(thread->thread_state(), interrupt_callback_,
args, xe::countof(args));
}
void GraphicsSystem::MarkVblank() {
SCOPE_profile_cpu_f("gpu");
// Increment vblank counter (so the game sees us making progress).
command_processor_->increment_counter();
// TODO(benvanik): we shouldn't need to do the dispatch here, but there's
// something wrong and the CP will block waiting for code that
// needs to be run in the interrupt.
DispatchInterruptCallback(0, 2);
}
void GraphicsSystem::ClearCaches() {
command_processor_->CallInThread(
[&]() { command_processor_->ClearCaches(); });
}
void GraphicsSystem::InitializeShaderStorage(
const std::filesystem::path& storage_root, uint32_t title_id,
bool blocking) {
if (!cvars::store_shaders) {
return;
}
if (blocking) {
if (command_processor_->is_paused()) {
// Safe to run on any thread while the command processor is paused, no
// race condition.
command_processor_->InitializeShaderStorage(storage_root, title_id, true);
} else {
xe::threading::Fence fence;
command_processor_->CallInThread(
[this, storage_root, title_id, &fence]() {
command_processor_->InitializeShaderStorage(storage_root, title_id,
true);
fence.Signal();
});
fence.Wait();
}
} else {
command_processor_->CallInThread([this, storage_root, title_id]() {
command_processor_->InitializeShaderStorage(storage_root, title_id,
false);
});
}
}
void GraphicsSystem::RequestFrameTrace() {
command_processor_->RequestFrameTrace(cvars::trace_gpu_prefix);
}
void GraphicsSystem::BeginTracing() {
command_processor_->BeginTracing(cvars::trace_gpu_prefix);
}
void GraphicsSystem::EndTracing() { command_processor_->EndTracing(); }
void GraphicsSystem::Pause() {
paused_ = true;
command_processor_->Pause();
}
void GraphicsSystem::Resume() {
paused_ = false;
command_processor_->Resume();
}
bool GraphicsSystem::Save(ByteStream* stream) {
stream->Write<uint32_t>(interrupt_callback_);
stream->Write<uint32_t>(interrupt_callback_data_);
return command_processor_->Save(stream);
}
bool GraphicsSystem::Restore(ByteStream* stream) {
interrupt_callback_ = stream->Read<uint32_t>();
interrupt_callback_data_ = stream->Read<uint32_t>();
return command_processor_->Restore(stream);
}
} // namespace gpu
} // namespace xe