示例#1
0
u32 VideoBackendHardware::Video_AccessEFB(EFBAccessType type, u32 x, u32 y, u32 InputData)
{
	if (s_BackendInitialized && g_ActiveConfig.bEFBAccessEnable)
	{
		s_accessEFBArgs.type = type;
		s_accessEFBArgs.x = x;
		s_accessEFBArgs.y = y;
		s_accessEFBArgs.Data = InputData;

		s_efbAccessRequested.Set();

		if (SConfig::GetInstance().m_LocalCoreStartupParameter.bCPUThread)
		{
			s_efbAccessReadyEvent.Reset();
			if (s_FifoShuttingDown.IsSet())
				return 0;
			s_efbAccessRequested.Set();
			s_efbAccessReadyEvent.Wait();
		}
		else
			VideoFifo_CheckEFBAccess();

		return s_AccessEFBResult;
	}

	return 0;
}
示例#2
0
void VideoBackendHardware::Video_ExitLoop()
{
	ExitGpuLoop();
	s_FifoShuttingDown.Set();
	s_efbAccessReadyEvent.Set();
	s_perfQueryReadyEvent.Set();
}
示例#3
0
// Run from the CPU thread (from VideoInterface.cpp)
void VideoBackendHardware::Video_EndField()
{
	if (s_BackendInitialized)
	{
		s_swapRequested.Set();
	}
}
示例#4
0
static void signal_handler(int)
{
  const char message[] = "A signal was received. A second signal will force Dolphin to stop.\n";
  if (write(STDERR_FILENO, message, sizeof(message)) < 0)
  {
  }
  s_shutdown_requested.Set();
}
示例#5
0
文件: Fifo.cpp 项目: stenzek/dolphin
void EmulatorState(bool running)
{
  s_emu_running_state.Set(running);
  if (running)
    s_gpu_mainloop.Wakeup();
  else
    s_gpu_mainloop.AllowSleep();
}
示例#6
0
文件: Fifo.cpp 项目: stenzek/dolphin
// May be executed from any thread, even the graphics thread.
// Created to allow for self shutdown.
void ExitGpuLoop()
{
  // This should break the wait loop in CPU thread
  CommandProcessor::fifo.bFF_GPReadEnable = false;
  FlushGpu();

  // Terminate GPU thread loop
  s_emu_running_state.Set();
  s_gpu_mainloop.Stop(false);
}
示例#7
0
void HiresTexture::Shutdown()
{
  if (s_prefetcher.joinable())
  {
    s_textureCacheAbortLoading.Set();
    s_prefetcher.join();
  }

  s_textureMap.clear();
  s_textureCache.clear();
}
示例#8
0
static void StopDVDThread()
{
  ASSERT(s_dvd_thread.joinable());

  // By setting s_DVD_thread_exiting, we ask the DVD thread to cleanly exit.
  // In case the request queue is empty, we need to set s_request_queue_expanded
  // so that the DVD thread will wake up and check s_DVD_thread_exiting.
  s_dvd_thread_exiting.Set();
  s_request_queue_expanded.Set();

  s_dvd_thread.join();
}
示例#9
0
u32 VideoBackendHardware::Video_GetQueryResult(PerfQueryType type)
{
	if (!g_perf_query->ShouldEmulate())
	{
		return 0;
	}

	// TODO: Is this check sane?
	if (!g_perf_query->IsFlushed())
	{
		if (SConfig::GetInstance().m_LocalCoreStartupParameter.bCPUThread)
		{
			s_perfQueryReadyEvent.Reset();
			if (s_FifoShuttingDown.IsSet())
				return 0;
			s_perfQueryRequested.Set();
			s_perfQueryReadyEvent.Wait();
		}
		else
			g_perf_query->FlushResults();
	}

	return g_perf_query->GetQueryResult(type);
}
示例#10
0
void HiresTexture::Update()
{
  bool BuildMaterialMaps = g_ActiveConfig.bHiresMaterialMapsBuild;
  if (s_prefetcher.joinable())
  {
    s_textureCacheAbortLoading.Set();
    s_prefetcher.join();
  }

  if (!g_ActiveConfig.bHiresTextures)
  {
    s_textureMap.clear();
    s_textureCache.clear();
    size_sum.store(0);
    return;
  }

  if (!g_ActiveConfig.bCacheHiresTextures)
  {
    s_textureCache.clear();
    size_sum.store(0);
  }

  s_textureMap.clear();
  const std::string& game_id = SConfig::GetInstance().GetGameID();
  const std::string texture_directory = GetTextureDirectory(game_id);

  std::string ddscode(".dds");
  std::string cddscode(".DDS");
  std::vector<std::string> Extensions;
  Extensions.push_back(".png");
  if (!BuildMaterialMaps)
  {
    Extensions.push_back(".dds");
  }

  std::vector<std::string> filenames =
      Common::DoFileSearch({texture_directory}, Extensions, /*recursive*/ true);

  const std::string miptag = "mip";

  for (const std::string& fileitem : filenames)
  {
    std::string filename;
    std::string Extension;
    SplitPath(fileitem, nullptr, &filename, &Extension);
    if (filename.substr(0, s_format_prefix.length()) != s_format_prefix)
    {
      // Discard wrong files
      continue;
    }
    size_t map_index = 0;
    size_t max_type = BuildMaterialMaps ? MapType::specular : MapType::normal;
    bool arbitrary_mips = false;
    for (size_t tag = 1; tag <= MapType::specular; tag++)
    {
      if (StringEndsWith(filename, s_maps_tags[tag]))
      {
        map_index = tag;
        filename = filename.substr(0, filename.size() - s_maps_tags[tag].size());
        break;
      }
    }
    if (map_index > max_type)
    {
      continue;
    }
    if (BuildMaterialMaps && map_index == MapType::material)
    {
      continue;
    }
    else if (!BuildMaterialMaps && map_index == MapType::color)
    {
      const size_t arb_index = filename.rfind("_arb");
      arbitrary_mips = arb_index != std::string::npos;
      if (arbitrary_mips)
        filename.erase(arb_index, 4);
    }
    const bool is_compressed = Extension.compare(ddscode) == 0 || Extension.compare(cddscode) == 0;
    hires_mip_level mip_level_detail(fileitem, Extension, is_compressed);
    u32 level = 0;
    size_t idx = filename.find_last_of('_');
    std::string miplevel = filename.substr(idx + 1, std::string::npos);
    if (miplevel.substr(0, miptag.length()) == miptag)
    {
      sscanf(miplevel.substr(3, std::string::npos).c_str(), "%i", &level);
      filename = filename.substr(0, idx);
    }
    HiresTextureCache::iterator iter = s_textureMap.find(filename);
    u32 min_item_size = level + 1;
    if (iter == s_textureMap.end())
    {
      HiresTextureCacheItem item(min_item_size);
      if (arbitrary_mips)
      {
        item.has_arbitrary_mips = true;
      }
      item.maps[map_index].resize(min_item_size);
      std::vector<hires_mip_level>& dst = item.maps[map_index];
      dst[level] = mip_level_detail;
      s_textureMap.emplace(filename, item);
    }
    else
    {
      std::vector<hires_mip_level>& dst = iter->second.maps[map_index];
      if (arbitrary_mips)
      {
        iter->second.has_arbitrary_mips = true;
      }
      if (dst.size() < min_item_size)
      {
        dst.resize(min_item_size);
      }
      dst[level] = mip_level_detail;
    }
  }

  if (g_ActiveConfig.bCacheHiresTextures && s_textureMap.size() > 0)
  {
    // remove cached but deleted textures
    auto iter = s_textureCache.begin();
    while (iter != s_textureCache.end())
    {
      if (s_textureMap.find(iter->first) == s_textureMap.end())
      {
        size_sum.fetch_sub(iter->second->m_cached_data_size);
        iter = s_textureCache.erase(iter);
      }
      else
      {
        iter++;
      }
    }
    s_textureCacheAbortLoading.Clear();
    s_prefetcher = std::thread(Prefetch);
    if (g_ActiveConfig.bWaitForCacheHiresTextures && s_prefetcher.joinable())
    {
      s_prefetcher.join();
    }
  }
}
示例#11
0
  void MainLoop() override
  {
    bool fullscreen = SConfig::GetInstance().bFullscreen;
    int last_window_width = SConfig::GetInstance().iRenderWindowWidth;
    int last_window_height = SConfig::GetInstance().iRenderWindowHeight;

    if (fullscreen)
    {
      rendererIsFullscreen = X11Utils::ToggleFullscreen(dpy, win);
#if defined(HAVE_XRANDR) && HAVE_XRANDR
      XRRConfig->ToggleDisplayMode(True);
#endif
    }

    // The actual loop
    while (s_running.IsSet())
    {
      if (s_shutdown_requested.TestAndClear())
      {
        const auto ios = IOS::HLE::GetIOS();
        const auto stm = ios ? ios->GetDeviceByName("/dev/stm/eventhook") : nullptr;
        if (!s_tried_graceful_shutdown.IsSet() && stm &&
            std::static_pointer_cast<IOS::HLE::Device::STMEventHook>(stm)->HasHookInstalled())
        {
          ProcessorInterface::PowerButton_Tap();
          s_tried_graceful_shutdown.Set();
        }
        else
        {
          s_running.Clear();
        }
      }

      XEvent event;
      KeySym key;
      for (int num_events = XPending(dpy); num_events > 0; num_events--)
      {
        XNextEvent(dpy, &event);
        switch (event.type)
        {
        case KeyPress:
          key = XLookupKeysym((XKeyEvent*)&event, 0);
          if (key == XK_Escape)
          {
            if (Core::GetState() == Core::State::Running)
            {
              if (SConfig::GetInstance().bHideCursor)
                XUndefineCursor(dpy, win);
              Core::SetState(Core::State::Paused);
            }
            else
            {
              if (SConfig::GetInstance().bHideCursor)
                XDefineCursor(dpy, win, blankCursor);
              Core::SetState(Core::State::Running);
            }
          }
          else if ((key == XK_Return) && (event.xkey.state & Mod1Mask))
          {
            fullscreen = !fullscreen;
            X11Utils::ToggleFullscreen(dpy, win);
#if defined(HAVE_XRANDR) && HAVE_XRANDR
            XRRConfig->ToggleDisplayMode(fullscreen);
#endif
          }
          else if (key >= XK_F1 && key <= XK_F8)
          {
            int slot_number = key - XK_F1 + 1;
            if (event.xkey.state & ShiftMask)
              State::Save(slot_number);
            else
              State::Load(slot_number);
          }
          else if (key == XK_F9)
            Core::SaveScreenShot();
          else if (key == XK_F11)
            State::LoadLastSaved();
          else if (key == XK_F12)
          {
            if (event.xkey.state & ShiftMask)
              State::UndoLoadState();
            else
              State::UndoSaveState();
          }
          break;
        case FocusIn:
          rendererHasFocus = true;
          if (SConfig::GetInstance().bHideCursor && Core::GetState() != Core::State::Paused)
            XDefineCursor(dpy, win, blankCursor);
          break;
        case FocusOut:
          rendererHasFocus = false;
          if (SConfig::GetInstance().bHideCursor)
            XUndefineCursor(dpy, win);
          break;
        case ClientMessage:
          if ((unsigned long)event.xclient.data.l[0] == XInternAtom(dpy, "WM_DELETE_WINDOW", False))
            s_shutdown_requested.Set();
          break;
        case ConfigureNotify:
        {
          if (last_window_width != event.xconfigure.width ||
              last_window_height != event.xconfigure.height)
          {
            last_window_width = event.xconfigure.width;
            last_window_height = event.xconfigure.height;

            // We call Renderer::ChangeSurface here to indicate the size has changed,
            // but pass the same window handle. This is needed for the Vulkan backend,
            // otherwise it cannot tell that the window has been resized on some drivers.
            if (g_renderer)
              g_renderer->ChangeSurface(s_window_handle);
          }
        }
        break;
        }
      }
      if (!fullscreen)
      {
        Window winDummy;
        unsigned int borderDummy, depthDummy;
        XGetGeometry(dpy, win, &winDummy, &SConfig::GetInstance().iRenderWindowXPos,
                     &SConfig::GetInstance().iRenderWindowYPos,
                     (unsigned int*)&SConfig::GetInstance().iRenderWindowWidth,
                     (unsigned int*)&SConfig::GetInstance().iRenderWindowHeight, &borderDummy,
                     &depthDummy);
        rendererIsFullscreen = false;
      }
      Core::HostDispatchJobs();
      usleep(100000);
    }
  }
示例#12
0
void HiresTexture::Update()
{
	s_check_native_format = false;
	s_check_new_format = false;

	if (s_prefetcher.joinable())
	{
		s_textureCacheAbortLoading.Set();
		s_prefetcher.join();
	}

	if (!g_ActiveConfig.bHiresTextures)
	{
		s_textureMap.clear();
		s_textureCache.clear();
		size_sum.store(0);
		return;
	}

	if (!g_ActiveConfig.bCacheHiresTextures)
	{
		s_textureCache.clear();
		size_sum.store(0);
	}
	
	s_textureMap.clear();
	const std::string& gameCode = SConfig::GetInstance().m_strUniqueID;
	std::string szDir = StringFromFormat("%s%s", File::GetUserPath(D_HIRESTEXTURES_IDX).c_str(), gameCode.c_str());	
	std::string ddscode(".dds");
	std::string cddscode(".DDS");
	std::vector<std::string> Extensions = {
		".png",
		".dds"
	};

	auto rFilenames = DoFileSearch(Extensions, { szDir }, /*recursive*/ true);

	const std::string code = StringFromFormat("%s_", gameCode.c_str());
	const std::string miptag = "mip";
	const std::string normaltag = ".nrm";
	for (u32 i = 0; i < rFilenames.size(); i++)
	{
		std::string FileName;
		std::string Extension;
		SplitPath(rFilenames[i], nullptr, &FileName, &Extension);
		if (FileName.substr(0, code.length()) == code)
		{
			s_check_native_format = true;
		}
		else if (FileName.substr(0, s_format_prefix.length()) == s_format_prefix)
		{
			s_check_new_format = true;
		}
		else
		{
			// Discard wrong files
			continue;
		}
		const bool is_compressed = Extension.compare(ddscode) == 0 || Extension.compare(cddscode) == 0;
		const bool is_normal_map = hasEnding(FileName, normaltag);
		if (is_normal_map)
		{
			FileName = FileName.substr(0, FileName.size() - normaltag.size());
		}
		hires_mip_level mip_level_detail(rFilenames[i], Extension, is_compressed);
		u32 level = 0;
		size_t idx = FileName.find_last_of('_');
		std::string miplevel = FileName.substr(idx + 1, std::string::npos);
		if (miplevel.substr(0, miptag.length()) == miptag)
		{
			sscanf(miplevel.substr(3, std::string::npos).c_str(), "%i", &level);
			FileName = FileName.substr(0, idx);
		}
		HiresTextureCache::iterator iter = s_textureMap.find(FileName);
		u32 min_item_size = level + 1;
		if (iter == s_textureMap.end())
		{
			HiresTextureCacheItem item(min_item_size);
			if (is_normal_map)
			{
				item.normal_map.resize(min_item_size);
			}
			std::vector<hires_mip_level> &dst = is_normal_map ? item.normal_map : item.color_map;
			dst[level] = mip_level_detail;
			s_textureMap.emplace(FileName, item);
		}
		else
		{
			std::vector<hires_mip_level> &dst = is_normal_map ? iter->second.normal_map : iter->second.color_map;
			if (dst.size() < min_item_size)
			{
				dst.resize(min_item_size);
			}
			dst[level] = mip_level_detail;
		}
	}

	if (g_ActiveConfig.bCacheHiresTextures && s_textureMap.size() > 0)
	{
		// remove cached but deleted textures
		auto iter = s_textureCache.begin();
		while (iter != s_textureCache.end())
		{
			if (s_textureMap.find(iter->first) == s_textureMap.end())
			{
				size_sum.fetch_sub(iter->second->m_cached_data_size);
				iter = s_textureCache.erase(iter);
			}
			else
			{
				iter++;
			}
		}
		s_textureCacheAbortLoading.Clear();
		s_prefetcher = std::thread(Prefetch);
	}
}
示例#13
0
void VideoBackendHardware::Video_ExitLoop()
{
	ExitGpuLoop();
	s_FifoShuttingDown.Set();
}
示例#14
0
void VideoBackendBase::Video_ExitLoop()
{
  Fifo::ExitGpuLoop();
  s_FifoShuttingDown.Set();
}
示例#15
0
void HiresTexture::Update()
{
	if (s_prefetcher.joinable())
	{
		s_textureCacheAbortLoading.Set();
		s_prefetcher.join();
	}

	if (!g_ActiveConfig.bHiresTextures)
	{
		s_textureMap.clear();
		s_textureCache.clear();
		return;
	}

	if (!g_ActiveConfig.bCacheHiresTextures)
	{
		s_textureCache.clear();
	}

	const std::string& game_id = SConfig::GetInstance().m_strUniqueID;
	const std::string texture_directory = GetTextureDirectory(game_id);
	std::vector<std::string> extensions {
		".png",
		".bmp",
		".tga",
		".dds",
		".jpg" // Why not? Could be useful for large photo-like textures
	};

	std::vector<std::string> filenames = DoFileSearch(extensions, {texture_directory}, /*recursive*/ true);

	const std::string code = game_id + "_";

	for (auto& rFilename : filenames)
	{
		std::string FileName;
		SplitPath(rFilename, nullptr, &FileName, nullptr);

		if (FileName.substr(0, code.length()) == code)
		{
			s_textureMap[FileName] = rFilename;
			s_check_native_format = true;
		}

		if (FileName.substr(0, s_format_prefix.length()) == s_format_prefix)
		{
			s_textureMap[FileName] = rFilename;
			s_check_new_format = true;
		}
	}

	if (g_ActiveConfig.bCacheHiresTextures)
	{
		// remove cached but deleted textures
		auto iter = s_textureCache.begin();
		while (iter != s_textureCache.end())
		{
			if (s_textureMap.find(iter->first) == s_textureMap.end())
			{
				iter = s_textureCache.erase(iter);
			}
			else
			{
				iter++;
			}
		}

		s_textureCacheAbortLoading.Clear();
		s_prefetcher = std::thread(Prefetch);
	}
}
示例#16
0
// Initialize and create emulation thread
// Call browser: Init():s_emu_thread().
// See the BootManager.cpp file description for a complete call schedule.
void EmuThread()
{
  const SConfig& core_parameter = SConfig::GetInstance();
  s_is_booting.Set();

  Common::SetCurrentThreadName("Emuthread - Starting");

  if (SConfig::GetInstance().m_OCEnable)
    DisplayMessage("WARNING: running at non-native CPU clock! Game may not be stable.", 8000);
  DisplayMessage(cpu_info.brand_string, 8000);
  DisplayMessage(cpu_info.Summarize(), 8000);
  DisplayMessage(core_parameter.m_strFilename, 3000);

  // For a time this acts as the CPU thread...
  DeclareAsCPUThread();

  Movie::Init();

  HW::Init();

  if (!g_video_backend->Initialize(s_window_handle))
  {
    s_is_booting.Clear();
    PanicAlert("Failed to initialize video backend!");
    Host_Message(WM_USER_STOP);
    return;
  }

  OSD::AddMessage("Dolphin " + g_video_backend->GetName() + " Video Backend.", 5000);

  if (cpu_info.HTT)
    SConfig::GetInstance().bDSPThread = cpu_info.num_cores > 4;
  else
    SConfig::GetInstance().bDSPThread = cpu_info.num_cores > 2;

  if (!DSP::GetDSPEmulator()->Initialize(core_parameter.bWii, core_parameter.bDSPThread))
  {
    s_is_booting.Clear();
    HW::Shutdown();
    g_video_backend->Shutdown();
    PanicAlert("Failed to initialize DSP emulation!");
    Host_Message(WM_USER_STOP);
    return;
  }

  bool init_controllers = false;
  if (!g_controller_interface.IsInit())
  {
    Pad::Initialize(s_window_handle);
    Keyboard::Initialize(s_window_handle);
    init_controllers = true;
  }
  else
  {
    // Update references in case controllers were refreshed
    Pad::LoadConfig();
    Keyboard::LoadConfig();
  }

  // Load and Init Wiimotes - only if we are booting in Wii mode
  if (core_parameter.bWii && !SConfig::GetInstance().m_bt_passthrough_enabled)
  {
    if (init_controllers)
      Wiimote::Initialize(s_window_handle, !s_state_filename.empty() ?
                                               Wiimote::InitializeMode::DO_WAIT_FOR_WIIMOTES :
                                               Wiimote::InitializeMode::DO_NOT_WAIT_FOR_WIIMOTES);
    else
      Wiimote::LoadConfig();

    // Activate Wiimotes which don't have source set to "None"
    for (unsigned int i = 0; i != MAX_BBMOTES; ++i)
      if (g_wiimote_sources[i])
        GetUsbPointer()->AccessWiiMote(i | 0x100)->Activate(true);
  }

  AudioCommon::InitSoundStream();

  // The hardware is initialized.
  s_hardware_initialized = true;
  s_is_booting.Clear();

  // Set execution state to known values (CPU/FIFO/Audio Paused)
  CPU::Break();

  // Load GCM/DOL/ELF whatever ... we boot with the interpreter core
  PowerPC::SetMode(PowerPC::MODE_INTERPRETER);

  CBoot::BootUp();

  // This adds the SyncGPU handler to CoreTiming, so now CoreTiming::Advance might block.
  Fifo::Prepare();

  // Thread is no longer acting as CPU Thread
  UndeclareAsCPUThread();

  // Setup our core, but can't use dynarec if we are compare server
  if (core_parameter.iCPUCore != PowerPC::CORE_INTERPRETER &&
      (!core_parameter.bRunCompareServer || core_parameter.bRunCompareClient))
  {
    PowerPC::SetMode(PowerPC::MODE_JIT);
  }
  else
  {
    PowerPC::SetMode(PowerPC::MODE_INTERPRETER);
  }

  // Update the window again because all stuff is initialized
  Host_UpdateDisasmDialog();
  Host_UpdateMainFrame();

  // Determine the CPU thread function
  void (*cpuThreadFunc)(void);
  if (core_parameter.m_BootType == SConfig::BOOT_DFF)
    cpuThreadFunc = FifoPlayerThread;
  else
    cpuThreadFunc = CpuThread;

  // ENTER THE VIDEO THREAD LOOP
  if (core_parameter.bCPUThread)
  {
    // This thread, after creating the EmuWindow, spawns a CPU
    // thread, and then takes over and becomes the video thread
    Common::SetCurrentThreadName("Video thread");

    g_video_backend->Video_Prepare();

    // Spawn the CPU thread
    s_cpu_thread = std::thread(cpuThreadFunc);

    // become the GPU thread
    Fifo::RunGpuLoop();

    // We have now exited the Video Loop
    INFO_LOG(CONSOLE, "%s", StopMessage(false, "Video Loop Ended").c_str());
  }
  else  // SingleCore mode
  {
    // The spawned CPU Thread also does the graphics.
    // The EmuThread is thus an idle thread, which sleeps while
    // waiting for the program to terminate. Without this extra
    // thread, the video backend window hangs in single core mode
    // because no one is pumping messages.
    Common::SetCurrentThreadName("Emuthread - Idle");

    // Spawn the CPU+GPU thread
    s_cpu_thread = std::thread(cpuThreadFunc);

    while (CPU::GetState() != CPU::CPU_POWERDOWN)
    {
      g_video_backend->PeekMessages();
      Common::SleepCurrentThread(20);
    }
  }

  INFO_LOG(CONSOLE, "%s", StopMessage(true, "Stopping Emu thread ...").c_str());

  // Wait for s_cpu_thread to exit
  INFO_LOG(CONSOLE, "%s", StopMessage(true, "Stopping CPU-GPU thread ...").c_str());

#ifdef USE_GDBSTUB
  INFO_LOG(CONSOLE, "%s", StopMessage(true, "Stopping GDB ...").c_str());
  gdb_deinit();
  INFO_LOG(CONSOLE, "%s", StopMessage(true, "GDB stopped.").c_str());
#endif

  s_cpu_thread.join();

  INFO_LOG(CONSOLE, "%s", StopMessage(true, "CPU thread stopped.").c_str());

  if (core_parameter.bCPUThread)
    g_video_backend->Video_Cleanup();

  FileMon::Close();

  // Stop audio thread - Actually this does nothing when using HLE
  // emulation, but stops the DSP Interpreter when using LLE emulation.
  DSP::GetDSPEmulator()->DSP_StopSoundStream();

  // We must set up this flag before executing HW::Shutdown()
  s_hardware_initialized = false;
  INFO_LOG(CONSOLE, "%s", StopMessage(false, "Shutting down HW").c_str());
  HW::Shutdown();
  INFO_LOG(CONSOLE, "%s", StopMessage(false, "HW shutdown").c_str());

  if (init_controllers)
  {
    Wiimote::Shutdown();
    Keyboard::Shutdown();
    Pad::Shutdown();
    init_controllers = false;
  }

  g_video_backend->Shutdown();
  AudioCommon::ShutdownSoundStream();

  INFO_LOG(CONSOLE, "%s", StopMessage(true, "Main Emu thread stopped").c_str());

  // Clear on screen messages that haven't expired
  OSD::ClearMessages();

  BootManager::RestoreConfig();

  INFO_LOG(CONSOLE, "Stop [Video Thread]\t\t---- Shutdown complete ----");
  Movie::Shutdown();
  PatchEngine::Shutdown();
  HLE::Clear();

  s_is_stopping = false;

  if (s_on_stopped_callback)
    s_on_stopped_callback();
}