/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2014 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include "xenia/gpu/gl4/gl4_graphics_system.h" #include #include #include "xenia/base/clock.h" #include "xenia/base/logging.h" #include "xenia/base/threading.h" #include "xenia/cpu/processor.h" #include "xenia/emulator.h" #include "xenia/gpu/gl4/gl4_gpu_flags.h" #include "xenia/gpu/gpu_flags.h" #include "xenia/gpu/tracing.h" #include "xenia/profiling.h" #include "xenia/ui/window.h" namespace xe { namespace gpu { namespace gl4 { void InitializeIfNeeded(); void CleanupOnShutdown(); void InitializeIfNeeded() { static bool has_initialized = false; if (has_initialized) { return; } has_initialized = true; // atexit(CleanupOnShutdown); } void CleanupOnShutdown() {} std::unique_ptr Create(Emulator* emulator) { InitializeIfNeeded(); return std::make_unique(emulator); } std::unique_ptr GL4GraphicsSystem::CreateContext( ui::Window* target_window) { // Setup the GL control that actually does the drawing. // We run here in the loop and only touch it (and its context) on this // thread. That means some sync-fu when we want to swap. return xe::ui::gl::GLContext::Create(target_window); } GL4GraphicsSystem::GL4GraphicsSystem(Emulator* emulator) : GraphicsSystem(emulator), worker_running_(false) {} GL4GraphicsSystem::~GL4GraphicsSystem() = default; X_STATUS GL4GraphicsSystem::Setup(cpu::Processor* processor, ui::Loop* target_loop, ui::Window* target_window) { auto result = GraphicsSystem::Setup(processor, target_loop, target_window); if (result) { return result; } display_context_ = reinterpret_cast(target_window->context()); // Watch for paint requests to do our swap. target_window->on_painting.AddListener( [this](xe::ui::UIEvent* e) { Swap(e); }); // Create rendering control. // This must happen on the UI thread. std::unique_ptr processor_context; target_loop_->PostSynchronous([&]() { // Setup the GL 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 = display_context_->CreateShared(); processor_context->ClearCurrent(); }); if (!processor_context) { xe::FatalError( "Unable to initialize GL context. Xenia requires OpenGL 4.5. Ensure " "you have the latest drivers for your GPU and that it supports OpenGL " "4.5. See http://xenia.jp/faq/ for more information."); return X_STATUS_UNSUCCESSFUL; } // Create command processor. This will spin up a thread to process all // incoming ringbuffer packets. command_processor_ = std::make_unique(this); if (!command_processor_->Initialize(std::move(processor_context))) { XELOGE("Unable to initialize command processor"); return X_STATUS_UNSUCCESSFUL; } command_processor_->set_swap_request_handler( [this]() { target_window_->Invalidate(); }); // Let the processor know we want register access callbacks. memory_->AddVirtualMappedRange( 0x7FC80000, 0xFFFF0000, 0x0000FFFF, this, reinterpret_cast(MMIOReadRegisterThunk), reinterpret_cast(MMIOWriteRegisterThunk)); // 60hz vsync timer. worker_running_ = true; worker_thread_ = kernel::object_ref(new kernel::XHostThread( emulator()->kernel_state(), 128 * 1024, 0, [this]() { uint64_t vsync_duration = FLAGS_vsync ? 16 : 1; uint64_t last_frame_time = Clock::QueryGuestTickCount(); while (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. worker_thread_->set_can_debugger_suspend(true); worker_thread_->set_name("GL4 Vsync"); worker_thread_->Create(); if (FLAGS_trace_gpu_stream) { BeginTracing(); } return X_STATUS_SUCCESS; } void GL4GraphicsSystem::Shutdown() { EndTracing(); worker_running_ = false; worker_thread_->Wait(0, 0, 0, nullptr); worker_thread_.reset(); command_processor_->Shutdown(); // TODO(benvanik): remove mapped range. command_processor_.reset(); GraphicsSystem::Shutdown(); } void GL4GraphicsSystem::InitializeRingBuffer(uint32_t ptr, uint32_t page_count) { command_processor_->InitializeRingBuffer(ptr, page_count); } void GL4GraphicsSystem::EnableReadPointerWriteBack(uint32_t ptr, uint32_t block_size) { command_processor_->EnableReadPointerWriteBack(ptr, block_size); } void GL4GraphicsSystem::RequestFrameTrace() { command_processor_->RequestFrameTrace(xe::to_wstring(FLAGS_trace_gpu_prefix)); } void GL4GraphicsSystem::BeginTracing() { command_processor_->BeginTracing(xe::to_wstring(FLAGS_trace_gpu_prefix)); } void GL4GraphicsSystem::EndTracing() { command_processor_->EndTracing(); } void GL4GraphicsSystem::PlayTrace(const uint8_t* trace_data, size_t trace_size, TracePlaybackMode playback_mode) { command_processor_->CallInThread([this, trace_data, trace_size, playback_mode]() { command_processor_->set_swap_mode(SwapMode::kIgnored); auto trace_ptr = trace_data; bool pending_break = false; const PacketStartCommand* pending_packet = nullptr; while (trace_ptr < trace_data + trace_size) { auto type = static_cast(xe::load(trace_ptr)); switch (type) { case TraceCommandType::kPrimaryBufferStart: { auto cmd = reinterpret_cast(trace_ptr); // trace_ptr += sizeof(*cmd) + cmd->count * 4; break; } case TraceCommandType::kPrimaryBufferEnd: { auto cmd = reinterpret_cast(trace_ptr); // trace_ptr += sizeof(*cmd); break; } case TraceCommandType::kIndirectBufferStart: { auto cmd = reinterpret_cast(trace_ptr); // trace_ptr += sizeof(*cmd) + cmd->count * 4; break; } case TraceCommandType::kIndirectBufferEnd: { auto cmd = reinterpret_cast(trace_ptr); // trace_ptr += sizeof(*cmd); break; } case TraceCommandType::kPacketStart: { auto cmd = reinterpret_cast(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(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) { return; } break; } case TraceCommandType::kMemoryRead: { auto cmd = reinterpret_cast(trace_ptr); trace_ptr += sizeof(*cmd); std::memcpy(memory()->TranslatePhysical(cmd->base_ptr), trace_ptr, cmd->length); trace_ptr += cmd->length; break; } case TraceCommandType::kMemoryWrite: { auto cmd = reinterpret_cast(trace_ptr); trace_ptr += sizeof(*cmd); // ? trace_ptr += cmd->length; break; } case TraceCommandType::kEvent: { auto cmd = reinterpret_cast(trace_ptr); trace_ptr += sizeof(*cmd); switch (cmd->event_type) { case EventType::kSwap: { if (playback_mode == TracePlaybackMode::kBreakOnSwap) { pending_break = true; } break; } } break; } } } command_processor_->set_swap_mode(SwapMode::kNormal); command_processor_->IssueSwap(0, 1280, 720); }); } void GL4GraphicsSystem::ClearCaches() { command_processor_->CallInThread( [&]() { command_processor_->ClearCaches(); }); } void GL4GraphicsSystem::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 GL4GraphicsSystem::Swap(xe::ui::UIEvent* e) { if (!command_processor_) { return; } // Check for pending swap. auto& swap_state = command_processor_->swap_state(); { std::lock_guard lock(swap_state.mutex); if (swap_state.pending) { swap_state.pending = false; std::swap(swap_state.front_buffer_texture, swap_state.back_buffer_texture); } } if (!swap_state.front_buffer_texture) { // Not yet ready. return; } // Blit the frontbuffer. display_context_->blitter()->BlitTexture2D( swap_state.front_buffer_texture, Rect2D(0, 0, swap_state.width, swap_state.height), Rect2D(0, 0, target_window_->width(), target_window_->height()), GL_LINEAR); } uint32_t GL4GraphicsSystem::ReadRegister(uint32_t addr) { uint32_t r = addr & 0xFFFF; switch (r) { case 0x3C00: // ? return 0x08100748; case 0x3C04: // ? return 0x0000200E; case 0x6530: // Scanline? return 0x000002D0; case 0x6544: // ? vblank pending? return 1; case 0x6584: // Screen res - 1280x720 return 0x050002D0; } assert_true(r < RegisterFile::kRegisterCount); return register_file_.values[r].u32; } void GL4GraphicsSystem::WriteRegister(uint32_t addr, uint32_t value) { uint32_t r = addr & 0xFFFF; switch (r) { case 0x0714: // CP_RB_WPTR command_processor_->UpdateWritePointer(value); break; case 0x6110: // ? swap related? XELOGW("Unimplemented GPU register %.4X write: %.8X", r, value); return; default: XELOGW("Unknown GPU register %.4X write: %.8X", r, value); break; } assert_true(r < RegisterFile::kRegisterCount); register_file_.values[r].u32 = value; } } // namespace gl4 } // namespace gpu } // namespace xe