// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0

#include "gpu_backend.h"
#include "gpu.h"
#include "gpu_sw_rasterizer.h"
#include "host.h"
#include "performance_counters.h"
#include "save_state_version.h"
#include "settings.h"
#include "system.h"
#include "system_private.h"
#include "video_presenter.h"
#include "video_thread.h"

#include "util/gpu_device.h"
#include "util/imgui_manager.h"
#include "util/state_wrapper.h"
#include "util/translation.h"

#include "common/error.h"
#include "common/file_system.h"
#include "common/log.h"
#include "common/path.h"
#include "common/threading.h"

#include "IconsEmoji.h"
#include "IconsFontAwesome.h"
#include "fmt/format.h"

LOG_CHANNEL(GPU);

namespace {

struct CoreThreadState
{
  static constexpr u32 WAIT_NONE = 0;
  static constexpr u32 WAIT_CORE_THREAD_WAITING = 1;
  static constexpr u32 WAIT_VIDEO_THREAD_SIGNALING = 2;
  static constexpr u32 WAIT_VIDEO_THREAD_POSTED = 3;

  std::atomic<u32> queued_frames;
  std::atomic<u32> wait_state;
  Threading::KernelSemaphore video_thread_wait;
};

} // namespace

GPUBackend::Counters GPUBackend::s_counters = {};
GPUBackend::Stats GPUBackend::s_stats = {};

ALIGN_TO_CACHE_LINE static CoreThreadState s_core_thread_state = {};

GPUBackend::GPUBackend()
{
  GPU_SW_Rasterizer::SelectImplementation();
  ResetStatistics();
}

GPUBackend::~GPUBackend()
{
  VideoPresenter::ClearDisplayTexture();
}

void GPUBackend::SetScreenQuadInputLayout(GPUPipeline::GraphicsConfig& config)
{
  static constexpr GPUPipeline::VertexAttribute screen_vertex_attributes[] = {
    GPUPipeline::VertexAttribute::Make(0, GPUPipeline::VertexAttribute::Semantic::Position, 0,
                                       GPUPipeline::VertexAttribute::Type::Float, 2, OFFSETOF(ScreenVertex, x)),
    GPUPipeline::VertexAttribute::Make(1, GPUPipeline::VertexAttribute::Semantic::TexCoord, 0,
                                       GPUPipeline::VertexAttribute::Type::Float, 2, OFFSETOF(ScreenVertex, u)),
  };

  // common state
  config.input_layout.vertex_attributes = screen_vertex_attributes;
  config.input_layout.vertex_stride = sizeof(ScreenVertex);
  config.primitive = GPUPipeline::Primitive::TriangleStrips;
}

GSVector4 GPUBackend::GetScreenQuadClipSpaceCoordinates(const GSVector4i bounds, const GSVector2i rt_size)
{
  const GSVector4 fboundsxxyy = GSVector4(bounds.xzyw());
  const GSVector2 fsize = GSVector2(rt_size);
  const GSVector2 x = ((fboundsxxyy.xy() * GSVector2::cxpr(2.0f)) / fsize.xx()) - GSVector2::cxpr(1.0f);
  const GSVector2 y = GSVector2::cxpr(1.0f) - (GSVector2::cxpr(2.0f) * (fboundsxxyy.zw() / fsize.yy()));
  return GSVector4::xyxy(x, y).xzyw();
}

void GPUBackend::DrawScreenQuad(const GSVector4i bounds, const GSVector2i rt_size, const GSVector4 uv_bounds,
                                const void* push_constants, u32 push_constants_size)
{
  const GSVector4 xy = GetScreenQuadClipSpaceCoordinates(bounds, rt_size);

  ScreenVertex* vertices;
  u32 space;
  u32 base_vertex;
  g_gpu_device->MapVertexBuffer(sizeof(ScreenVertex), 4, reinterpret_cast<void**>(&vertices), &space, &base_vertex);

  vertices[0].Set(xy.xy(), uv_bounds.xy());
  vertices[1].Set(xy.zyzw().xy(), uv_bounds.zyzw().xy());
  vertices[2].Set(xy.xwzw().xy(), uv_bounds.xwzw().xy());
  vertices[3].Set(xy.zw(), uv_bounds.zw());

  g_gpu_device->UnmapVertexBuffer(sizeof(ScreenVertex), 4);

  if (push_constants_size > 0)
    g_gpu_device->DrawWithPushConstants(4, base_vertex, push_constants, push_constants_size);
  else
    g_gpu_device->Draw(4, base_vertex);
}

bool GPUBackend::Initialize(bool clear_vram, Error* error)
{
  m_clamped_drawing_area = GPU::GetClampedDrawingArea(GPU_SW_Rasterizer::g_drawing_area);
  return true;
}

bool GPUBackend::UpdateSettings(const GPUSettings& old_settings, Error* error)
{
  if (g_gpu_settings.display_show_gpu_stats != old_settings.display_show_gpu_stats)
    GPUBackend::ResetStatistics();

  return true;
}

void GPUBackend::UpdatePostProcessingSettings(bool force_reload)
{
}

VideoThreadCommand* GPUBackend::NewClearVRAMCommand()
{
  return static_cast<VideoThreadCommand*>(
    VideoThread::AllocateCommand(VideoThreadCommandType::ClearVRAM, sizeof(VideoThreadCommand)));
}

VideoThreadCommand* GPUBackend::NewClearDisplayCommand()
{
  return static_cast<VideoThreadCommand*>(
    VideoThread::AllocateCommand(VideoThreadCommandType::ClearDisplay, sizeof(VideoThreadCommand)));
}

GPUBackendUpdateDisplayCommand* GPUBackend::NewUpdateDisplayCommand()
{
  return static_cast<GPUBackendUpdateDisplayCommand*>(
    VideoThread::AllocateCommand(VideoThreadCommandType::UpdateDisplay, sizeof(GPUBackendUpdateDisplayCommand)));
}

GPUBackendSubmitFrameCommand* GPUBackend::NewSubmitFrameCommand()
{
  return static_cast<GPUBackendSubmitFrameCommand*>(
    VideoThread::AllocateCommand(VideoThreadCommandType::SubmitFrame, sizeof(GPUBackendUpdateDisplayCommand)));
}

VideoThreadCommand* GPUBackend::NewClearCacheCommand()
{
  return static_cast<VideoThreadCommand*>(
    VideoThread::AllocateCommand(VideoThreadCommandType::ClearCache, sizeof(VideoThreadCommand)));
}

VideoThreadCommand* GPUBackend::NewBufferSwappedCommand()
{
  return static_cast<VideoThreadCommand*>(
    VideoThread::AllocateCommand(VideoThreadCommandType::BufferSwapped, sizeof(VideoThreadCommand)));
}

GPUBackendReadVRAMCommand* GPUBackend::NewReadVRAMCommand()
{
  return static_cast<GPUBackendReadVRAMCommand*>(
    VideoThread::AllocateCommand(VideoThreadCommandType::ReadVRAM, sizeof(GPUBackendReadVRAMCommand)));
}

GPUBackendFillVRAMCommand* GPUBackend::NewFillVRAMCommand()
{
  return static_cast<GPUBackendFillVRAMCommand*>(
    VideoThread::AllocateCommand(VideoThreadCommandType::FillVRAM, sizeof(GPUBackendFillVRAMCommand)));
}

GPUBackendUpdateVRAMCommand* GPUBackend::NewUpdateVRAMCommand(u32 num_words)
{
  const u32 size = sizeof(GPUBackendUpdateVRAMCommand) + (num_words * sizeof(u16));
  GPUBackendUpdateVRAMCommand* cmd =
    static_cast<GPUBackendUpdateVRAMCommand*>(VideoThread::AllocateCommand(VideoThreadCommandType::UpdateVRAM, size));
  return cmd;
}

GPUBackendCopyVRAMCommand* GPUBackend::NewCopyVRAMCommand()
{
  return static_cast<GPUBackendCopyVRAMCommand*>(
    VideoThread::AllocateCommand(VideoThreadCommandType::CopyVRAM, sizeof(GPUBackendCopyVRAMCommand)));
}

GPUBackendSetDrawingAreaCommand* GPUBackend::NewSetDrawingAreaCommand()
{
  return static_cast<GPUBackendSetDrawingAreaCommand*>(
    VideoThread::AllocateCommand(VideoThreadCommandType::SetDrawingArea, sizeof(GPUBackendSetDrawingAreaCommand)));
}

GPUBackendUpdateCLUTCommand* GPUBackend::NewUpdateCLUTCommand()
{
  return static_cast<GPUBackendUpdateCLUTCommand*>(
    VideoThread::AllocateCommand(VideoThreadCommandType::UpdateCLUT, sizeof(GPUBackendUpdateCLUTCommand)));
}

GPUBackendDrawPolygonCommand* GPUBackend::NewDrawPolygonCommand(u32 num_vertices)
{
  const u32 size = sizeof(GPUBackendDrawPolygonCommand) + (num_vertices * sizeof(GPUBackendDrawPolygonCommand::Vertex));
  GPUBackendDrawPolygonCommand* cmd =
    static_cast<GPUBackendDrawPolygonCommand*>(VideoThread::AllocateCommand(VideoThreadCommandType::DrawPolygon, size));
  cmd->num_vertices = Truncate16(num_vertices);
  return cmd;
}

GPUBackendDrawPrecisePolygonCommand* GPUBackend::NewDrawPrecisePolygonCommand(u32 num_vertices)
{
  const u32 size =
    sizeof(GPUBackendDrawPrecisePolygonCommand) + (num_vertices * sizeof(GPUBackendDrawPrecisePolygonCommand::Vertex));
  GPUBackendDrawPrecisePolygonCommand* cmd = static_cast<GPUBackendDrawPrecisePolygonCommand*>(
    VideoThread::AllocateCommand(VideoThreadCommandType::DrawPrecisePolygon, size));
  cmd->num_vertices = Truncate16(num_vertices);
  return cmd;
}

GPUBackendDrawRectangleCommand* GPUBackend::NewDrawRectangleCommand()
{
  return static_cast<GPUBackendDrawRectangleCommand*>(
    VideoThread::AllocateCommand(VideoThreadCommandType::DrawRectangle, sizeof(GPUBackendDrawRectangleCommand)));
}

GPUBackendDrawLineCommand* GPUBackend::NewDrawLineCommand(u32 num_vertices)
{
  const u32 size = sizeof(GPUBackendDrawLineCommand) + (num_vertices * sizeof(GPUBackendDrawLineCommand::Vertex));
  GPUBackendDrawLineCommand* cmd =
    static_cast<GPUBackendDrawLineCommand*>(VideoThread::AllocateCommand(VideoThreadCommandType::DrawLine, size));
  cmd->num_vertices = Truncate16(num_vertices);
  return cmd;
}

GPUBackendDrawPreciseLineCommand* GPUBackend::NewDrawPreciseLineCommand(u32 num_vertices)
{
  const u32 size =
    sizeof(GPUBackendDrawPreciseLineCommand) + (num_vertices * sizeof(GPUBackendDrawPreciseLineCommand::Vertex));
  GPUBackendDrawPreciseLineCommand* cmd = static_cast<GPUBackendDrawPreciseLineCommand*>(
    VideoThread::AllocateCommand(VideoThreadCommandType::DrawPreciseLine, size));
  cmd->num_vertices = Truncate16(num_vertices);
  return cmd;
}

void GPUBackend::PushCommand(VideoThreadCommand* cmd)
{
  VideoThread::PushCommand(cmd);
}

void GPUBackend::PushCommandAndWakeThread(VideoThreadCommand* cmd)
{
  VideoThread::PushCommandAndWakeThread(cmd);
}

void GPUBackend::PushCommandAndSync(VideoThreadCommand* cmd, bool spin)
{
  VideoThread::PushCommandAndSync(cmd, spin);
}

bool GPUBackend::IsUsingHardwareBackend()
{
  return (VideoThread::GetRequestedRenderer().value_or(GPURenderer::Software) != GPURenderer::Software);
}

bool GPUBackend::BeginQueueFrame()
{
  const u32 queued_frames = s_core_thread_state.queued_frames.fetch_add(1, std::memory_order_acq_rel) + 1;
  if (queued_frames <= g_settings.gpu_max_queued_frames)
    return false;

  if (g_settings.gpu_max_queued_frames > 0)
    DEV_LOG("<-- {} queued frames, {} max, blocking core thread", queued_frames, g_settings.gpu_max_queued_frames);

  s_core_thread_state.wait_state.store(CoreThreadState::WAIT_CORE_THREAD_WAITING, std::memory_order_release);
  return true;
}

void GPUBackend::WaitForOneQueuedFrame()
{
  // Inbetween this and the post call, we may have finished the frame. Check.
  if (s_core_thread_state.queued_frames.load(std::memory_order_acquire) <= g_settings.gpu_max_queued_frames)
  {
    // It's possible that the GPU thread has already signaled the semaphore.
    // If so, then we still need to drain it, otherwise waits in the future will return prematurely.
    u32 expected = CoreThreadState::WAIT_CORE_THREAD_WAITING;
    if (s_core_thread_state.wait_state.compare_exchange_strong(expected, CoreThreadState::WAIT_NONE,
                                                               std::memory_order_acq_rel, std::memory_order_acquire))
    {
      return;
    }
  }

  s_core_thread_state.video_thread_wait.Wait();

  // Depending on where the GPU thread is, now we can either be in WAIT_GPU_THREAD_SIGNALING or WAIT_GPU_THREAD_POSTED
  // state. We want to clear the flag here regardless, so a store-release is fine. Because the GPU thread has a
  // compare-exchange on WAIT_GPU_THREAD_SIGNALING, it can't "overwrite" the value we store here.
  s_core_thread_state.wait_state.store(CoreThreadState::WAIT_NONE, std::memory_order_release);

  // Sanity check: queued frames should be in range now. If they're not, we fucked up the semaphore.
  if (const u32 queued_frames = s_core_thread_state.queued_frames.load(std::memory_order_acquire);
      queued_frames > g_settings.gpu_max_queued_frames) [[unlikely]]
  {
    ERROR_LOG("queued_frames {} above max queued frames {} after core thread wait", queued_frames,
              g_settings.gpu_max_queued_frames);
  }
}

u32 GPUBackend::GetQueuedFrameCount()
{
  return s_core_thread_state.queued_frames.load(std::memory_order_acquire);
}

void GPUBackend::ReleaseQueuedFrame()
{
  s_core_thread_state.queued_frames.fetch_sub(1, std::memory_order_acq_rel);

  // We need two states here in case we get preempted in between the compare_exchange_strong() and Post().
  // This means that we will only release the semaphore once the CPU is guaranteed to be in a waiting state,
  // and ensure that we don't post twice if the CPU thread lags and we process 2 frames before it wakes up.
  u32 expected = CoreThreadState::WAIT_CORE_THREAD_WAITING;
  if (s_core_thread_state.wait_state.compare_exchange_strong(expected, CoreThreadState::WAIT_VIDEO_THREAD_SIGNALING,
                                                             std::memory_order_acq_rel, std::memory_order_acquire))
  {
    if (g_gpu_settings.gpu_max_queued_frames > 0)
      DEV_LOG("--> Unblocking core thread");

    s_core_thread_state.video_thread_wait.Post();

    // This needs to be a compare_exchange, because the core thread can clear the flag before we execute this line.
    expected = CoreThreadState::WAIT_VIDEO_THREAD_SIGNALING;
    s_core_thread_state.wait_state.compare_exchange_strong(expected, CoreThreadState::WAIT_VIDEO_THREAD_POSTED,
                                                           std::memory_order_acq_rel, std::memory_order_acquire);
  }
}

bool GPUBackend::AllocateMemorySaveStates(std::span<System::MemorySaveState> states, Error* error)
{
  bool result;
  VideoThread::RunOnThreadAndSync([states, error, &result]() {
    GPUBackend* const backend = VideoThread::GetGPUBackend();
    if (!backend) [[unlikely]]
      return;

    // Free old textures first.
    for (size_t i = 0; i < states.size(); i++)
      g_gpu_device->RecycleTexture(std::move(states[i].vram_texture));

    // Maximize potential for texture reuse by flushing the current command buffer.
    g_gpu_device->WaitForGPUIdle();

    for (size_t i = 0; i < states.size(); i++)
    {
      if (!backend->AllocateMemorySaveState(states[i], error))
      {
        // Try flushing the pool.
        WARNING_LOG("Failed to allocate memory save state texture, trying flushing pool.");
        g_gpu_device->PurgeTexturePool();
        g_gpu_device->WaitForGPUIdle();
        if (!backend->AllocateMemorySaveState(states[i], error))
        {
          // Free anything that was allocated.
          for (size_t j = 0; j <= i; j++)
          {
            states[j].state_data.deallocate();
            states[j].vram_texture.reset();
            result = false;
            return;
          }
        }
      }
    }

    backend->RestoreDeviceContext();
    result = true;
  });
  return result;
}

void GPUBackend::HandleCommand(const VideoThreadCommand* cmd)
{
  switch (cmd->type)
  {
    case VideoThreadCommandType::ClearVRAM:
    {
      ClearVRAM();
    }
    break;

    case VideoThreadCommandType::LoadState:
    {
      LoadState(static_cast<const GPUBackendLoadStateCommand*>(cmd));
    }
    break;

    case VideoThreadCommandType::LoadMemoryState:
    {
      System::MemorySaveState& mss = *static_cast<const GPUBackendDoMemoryStateCommand*>(cmd)->memory_save_state;
      StateWrapper sw(mss.gpu_state_data.span(0, mss.gpu_state_size), StateWrapper::Mode::Read, SAVE_STATE_VERSION);
      DoMemoryState(sw, mss);
    }
    break;

    case VideoThreadCommandType::SaveMemoryState:
    {
      System::MemorySaveState& mss = *static_cast<const GPUBackendDoMemoryStateCommand*>(cmd)->memory_save_state;
      StateWrapper sw(mss.gpu_state_data.span(), StateWrapper::Mode::Write, SAVE_STATE_VERSION);
      DoMemoryState(sw, mss);
      mss.gpu_state_size = static_cast<u32>(sw.GetPosition());
    }
    break;

    case VideoThreadCommandType::ClearDisplay:
    {
      VideoPresenter::ClearDisplay();
    }
    break;

    case VideoThreadCommandType::UpdateDisplay:
    {
      HandleUpdateDisplayCommand(static_cast<const GPUBackendUpdateDisplayCommand*>(cmd));
    }
    break;

    case VideoThreadCommandType::SubmitFrame:
    {
      HandleSubmitFrameCommand(&static_cast<const GPUBackendSubmitFrameCommand*>(cmd)->frame);
    }
    break;

    case VideoThreadCommandType::ClearCache:
    {
      ClearCache();
    }
    break;

    case VideoThreadCommandType::BufferSwapped:
    {
      OnBufferSwapped();
    }
    break;

    case VideoThreadCommandType::ReadVRAM:
    {
      const GPUBackendReadVRAMCommand* ccmd = static_cast<const GPUBackendReadVRAMCommand*>(cmd);
      s_counters.num_reads++;
      ReadVRAM(ZeroExtend32(ccmd->x), ZeroExtend32(ccmd->y), ZeroExtend32(ccmd->width), ZeroExtend32(ccmd->height));
    }
    break;

    case VideoThreadCommandType::FillVRAM:
    {
      const GPUBackendFillVRAMCommand* ccmd = static_cast<const GPUBackendFillVRAMCommand*>(cmd);
      FillVRAM(ZeroExtend32(ccmd->x), ZeroExtend32(ccmd->y), ZeroExtend32(ccmd->width), ZeroExtend32(ccmd->height),
               ccmd->color, ccmd->interlaced_rendering, ccmd->active_line_lsb);
    }
    break;

    case VideoThreadCommandType::UpdateVRAM:
    {
      const GPUBackendUpdateVRAMCommand* ccmd = static_cast<const GPUBackendUpdateVRAMCommand*>(cmd);
      s_counters.num_writes++;
      UpdateVRAM(ZeroExtend32(ccmd->x), ZeroExtend32(ccmd->y), ZeroExtend32(ccmd->width), ZeroExtend32(ccmd->height),
                 ccmd->data, ccmd->set_mask_while_drawing, ccmd->check_mask_before_draw);
    }
    break;

    case VideoThreadCommandType::CopyVRAM:
    {
      const GPUBackendCopyVRAMCommand* ccmd = static_cast<const GPUBackendCopyVRAMCommand*>(cmd);
      s_counters.num_copies++;
      CopyVRAM(ZeroExtend32(ccmd->src_x), ZeroExtend32(ccmd->src_y), ZeroExtend32(ccmd->dst_x),
               ZeroExtend32(ccmd->dst_y), ZeroExtend32(ccmd->width), ZeroExtend32(ccmd->height),
               ccmd->set_mask_while_drawing, ccmd->check_mask_before_draw);
    }
    break;

    case VideoThreadCommandType::SetDrawingArea:
    {
      const GPUBackendSetDrawingAreaCommand* ccmd = static_cast<const GPUBackendSetDrawingAreaCommand*>(cmd);
      GPU_SW_Rasterizer::g_drawing_area = ccmd->new_area;
      m_clamped_drawing_area = GPU::GetClampedDrawingArea(ccmd->new_area);
      DrawingAreaChanged();
    }
    break;

    case VideoThreadCommandType::UpdateCLUT:
    {
      const GPUBackendUpdateCLUTCommand* ccmd = static_cast<const GPUBackendUpdateCLUTCommand*>(cmd);
      GPU_SW_Rasterizer::UpdateCLUT(ccmd->reg, ccmd->clut_is_8bit);
    }
    break;

    case VideoThreadCommandType::DrawPolygon:
    {
      const GPUBackendDrawPolygonCommand* ccmd = static_cast<const GPUBackendDrawPolygonCommand*>(cmd);
      s_counters.num_vertices += ccmd->num_vertices;
      s_counters.num_primitives++;
      DrawPolygon(ccmd);
    }
    break;

    case VideoThreadCommandType::DrawPrecisePolygon:
    {
      const GPUBackendDrawPolygonCommand* ccmd = static_cast<const GPUBackendDrawPolygonCommand*>(cmd);
      s_counters.num_vertices += ccmd->num_vertices;
      s_counters.num_primitives++;
      DrawPrecisePolygon(static_cast<const GPUBackendDrawPrecisePolygonCommand*>(cmd));
    }
    break;

    case VideoThreadCommandType::DrawRectangle:
    {
      const GPUBackendDrawRectangleCommand* ccmd = static_cast<const GPUBackendDrawRectangleCommand*>(cmd);
      s_counters.num_vertices++;
      s_counters.num_primitives++;
      DrawSprite(ccmd);
    }
    break;

    case VideoThreadCommandType::DrawLine:
    {
      const GPUBackendDrawLineCommand* ccmd = static_cast<const GPUBackendDrawLineCommand*>(cmd);
      s_counters.num_vertices += ccmd->num_vertices;
      s_counters.num_primitives += ccmd->num_vertices / 2;
      DrawLine(ccmd);
    }
    break;

    case VideoThreadCommandType::DrawPreciseLine:
    {
      const GPUBackendDrawPreciseLineCommand* ccmd = static_cast<const GPUBackendDrawPreciseLineCommand*>(cmd);
      s_counters.num_vertices += ccmd->num_vertices;
      s_counters.num_primitives += ccmd->num_vertices / 2;
      DrawPreciseLine(ccmd);
    }
    break;

      DefaultCaseIsUnreachable();
  }
}

void GPUBackend::HandleUpdateDisplayCommand(const GPUBackendUpdateDisplayCommand* cmd)
{
  s_stats.gpu_busy_pct = cmd->gpu_busy_pct;

  // Assumptions about struct layout.
  static_assert(offsetof(GPUBackendUpdateDisplayCommand, display_width) + sizeof(u16) ==
                offsetof(GPUBackendUpdateDisplayCommand, display_height));
  static_assert(offsetof(GPUBackendUpdateDisplayCommand, display_origin_left) + sizeof(u16) ==
                offsetof(GPUBackendUpdateDisplayCommand, display_origin_top));

  // Height has to be doubled because we halved it on the GPU side.
  const GSVector2i display_size = GSVector2i::load32(&cmd->display_width).u16to32();
  const GSVector2i active_origin = GSVector2i::load32(&cmd->display_origin_left).u16to32();
  const GSVector2i active_size =
    GSVector2i(cmd->display_vram_width, cmd->display_vram_height << BoolToUInt32(cmd->interlaced_display_enabled));
  const GSVector4i active_rect = GSVector4i::xyxy(active_origin, active_origin.add32(active_size));
  VideoPresenter::SetDisplayParameters(display_size, active_rect, cmd->display_pixel_aspect_ratio, cmd->display_24bit);

  UpdateDisplay(cmd);
  if (cmd->submit_frame)
    HandleSubmitFrameCommand(&cmd->frame);
}

void GPUBackend::HandleSubmitFrameCommand(const GPUBackendFramePresentationParameters* cmd)
{
  // For regtest.
  Host::FrameDoneOnVideoThread(this, cmd->frame_number);

  bool needs_restore = false;
  if (cmd->media_capture)
  {
    VideoPresenter::SendDisplayToMediaCapture(cmd->media_capture);
    needs_restore = true;
  }

  // If this returns false, our backend object is deleted and replaced with null, so bail out.
  if (cmd->present_frame)
  {
    bool result;
    if (VideoPresenter::ShouldPresentFrame(cmd->present_time))
    {
      result = VideoPresenter::PresentFrame(this, cmd->present_time);
      needs_restore = true;
    }
    else
    {
      result = true;
    }

    ReleaseQueuedFrame();
    if (!result)
      return;
  }

  // Update perf counters *after* throttling, we want to measure from start-of-frame
  // to start-of-frame, not end-of-frame to end-of-frame (will be noisy due to different
  // amounts of computation happening in each frame).
  if (cmd->update_performance_counters)
    PerformanceCounters::Update(this, cmd->frame_number, cmd->internal_frame_number);

  if (needs_restore)
    RestoreDeviceContext();
}

#define BOLD(text) "\x02" text "\x01"

void GPUBackend::GetStatsString(SmallStringBase& str) const
{
  if (IsUsingHardwareBackend())
  {
    if (g_gpu_settings.gpu_pgxp_depth_buffer)
    {
      str.format(BOLD("{}{} HW") " | {} " BOLD("P") " | {} " BOLD("DC") " | {} " BOLD("RB") " | {} " BOLD(
                   "C") " | {} " BOLD("W") " | {}% " BOLD("U") " | {} " BOLD("DBC"),
                 GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI()), g_gpu_settings.gpu_use_thread ? "-MT" : "",
                 s_stats.num_primitives, s_stats.host_num_draws, s_stats.host_num_downloads, s_stats.num_copies,
                 s_stats.num_writes, s_stats.gpu_busy_pct, s_stats.num_depth_buffer_clears);
    }
    else
    {
      str.format(BOLD("{}{} HW") " | {} " BOLD("P") " | {} " BOLD("DC") " | {} " BOLD("RB") " | {} " BOLD(
                   "C") " | {} " BOLD("W") " | {}% " BOLD("U"),
                 GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI()), g_gpu_settings.gpu_use_thread ? "-MT" : "",
                 s_stats.num_primitives, s_stats.host_num_draws, s_stats.host_num_downloads, s_stats.num_copies,
                 s_stats.num_writes, s_stats.gpu_busy_pct);
    }
  }
  else
  {
    str.format(
      BOLD("{}{} SW") " | {} " BOLD("P") " | {} " BOLD("R") " | {} " BOLD("C") " | {} " BOLD("W") " | {}% " BOLD("U"),
      GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI()), g_gpu_settings.gpu_use_thread ? "-MT" : "",
      s_stats.num_primitives, s_stats.num_reads, s_stats.num_copies, s_stats.num_writes, s_stats.gpu_busy_pct);
  }
}

void GPUBackend::GetMemoryStatsString(SmallStringBase& str) const
{
  const u32 vram_usage_mb = static_cast<u32>((g_gpu_device->GetVRAMUsage() + (1048576 - 1)) / 1048576);
  const u32 stream_kb = static_cast<u32>((s_stats.host_buffer_streamed + (1024 - 1)) / 1024);

  str.format("{}MB " BOLD("VRAM") " | {}KB " BOLD("STR") " | {} " BOLD("B") " | {} " BOLD("RP") " | {} " BOLD(
               "TC") " | {} " BOLD("TU"),
             vram_usage_mb, stream_kb, s_stats.host_num_barriers, s_stats.host_num_render_passes,
             s_stats.host_num_copies, s_stats.host_num_uploads);
}

#undef BOLD

void GPUBackend::ResetStatistics()
{
  s_counters = {};
  g_gpu_device->ResetStatistics();
}

void GPUBackend::UpdateStatistics(u32 frame_count)
{
  const GPUDevice::Statistics& stats = g_gpu_device->GetStatistics();
  const u32 round = (frame_count - 1);

#define UPDATE_COUNTER(x) s_stats.x = (s_counters.x + round) / frame_count
#define UPDATE_GPU_STAT(x) s_stats.host_##x = (stats.x + round) / frame_count

  UPDATE_COUNTER(num_reads);
  UPDATE_COUNTER(num_writes);
  UPDATE_COUNTER(num_copies);
  UPDATE_COUNTER(num_vertices);
  UPDATE_COUNTER(num_primitives);
  UPDATE_COUNTER(num_depth_buffer_clears);

  // UPDATE_COUNTER(num_read_texture_updates);
  // UPDATE_COUNTER(num_ubo_updates);

  UPDATE_GPU_STAT(buffer_streamed);
  UPDATE_GPU_STAT(num_draws);
  UPDATE_GPU_STAT(num_barriers);
  UPDATE_GPU_STAT(num_render_passes);
  UPDATE_GPU_STAT(num_copies);
  UPDATE_GPU_STAT(num_downloads);
  UPDATE_GPU_STAT(num_uploads);

#undef UPDATE_GPU_STAT
#undef UPDATE_COUNTER

  ResetStatistics();
}

bool GPUBackend::RenderScreenshotToBuffer(u32 width, u32 height, bool postfx, bool apply_aspect_ratio, Image* out_image,
                                          Error* error)
{
  bool result;
  VideoThread::RunOnThreadAndSync([width, height, postfx, apply_aspect_ratio, out_image, error, &result]() {
    GPUBackend* const backend = VideoThread::GetGPUBackend();
    if (!backend) [[unlikely]]
    {
      Error::SetStringView(error, "No GPU backend.");
      result = false;
      return;
    }

    // Post-processing requires that the size match the window.
    const bool really_postfx = postfx && g_gpu_device->HasMainSwapChain();
    u32 image_width, image_height;
    if (really_postfx)
    {
      image_width = g_gpu_device->GetMainSwapChain()->GetWidth();
      image_height = g_gpu_device->GetMainSwapChain()->GetHeight();
    }
    else
    {
      // Crop it if border overlay isn't enabled.
      GSVector4i source_rect, draw_rect, display_rect;
      VideoPresenter::CalculateDrawRect(GSVector2i(static_cast<s32>(width), static_cast<s32>(height)),
                                        apply_aspect_ratio, false, true, false, &source_rect, &display_rect,
                                        &draw_rect);
      image_width = static_cast<u32>(display_rect.width());
      image_height = static_cast<u32>(display_rect.height());
    }

    result = VideoPresenter::RenderScreenshotToBuffer(image_width, image_height, really_postfx, apply_aspect_ratio,
                                                      out_image, error);
    backend->RestoreDeviceContext();
  });

  return result;
}

void GPUBackend::RenderScreenshotToFile(const std::string_view path, DisplayScreenshotMode mode, u8 quality,
                                        bool show_osd_message)
{
  VideoThread::RunOnThread([path = std::string(path), mode, quality, show_osd_message]() mutable {
    GPUBackend* const backend = VideoThread::GetGPUBackend();
    if (!backend) [[unlikely]]
      return;

    const GSVector2i size = VideoPresenter::CalculateScreenshotSize(mode);
    if (size.x == 0 || size.y == 0)
      return;

    std::string osd_key;
    if (show_osd_message)
      osd_key = fmt::format("ScreenshotSaver_{}", path);

    const bool internal_resolution = (mode != DisplayScreenshotMode::ScreenResolution);
    const bool apply_aspect_ratio = (mode != DisplayScreenshotMode::UncorrectedInternalResolution);
    Error error;
    Image image;
    if (!VideoPresenter::RenderScreenshotToBuffer(size.x, size.y, !internal_resolution, apply_aspect_ratio, &image,
                                                  &error))
    {
      ERROR_LOG("Failed to render {}x{} screenshot: {}", size.x, size.y, error.GetDescription());
      if (show_osd_message)
      {
        Host::AddIconOSDMessage(
          OSDMessageType::Error, std::move(osd_key), ICON_EMOJI_WARNING,
          fmt::format(TRANSLATE_FS("GPU", "Failed to save screenshot:\n{}"), error.GetDescription()));
      }

      backend->RestoreDeviceContext();
      return;
    }

    // no more GPU calls
    backend->RestoreDeviceContext();

    auto fp = FileSystem::OpenManagedCFile(path.c_str(), "wb", &error);
    if (!fp)
    {
      ERROR_LOG("Can't open file '{}': {}", Path::GetFileName(path), error.GetDescription());
      if (show_osd_message)
      {
        Host::AddIconOSDMessage(
          OSDMessageType::Error, std::move(osd_key), ICON_EMOJI_WARNING,
          fmt::format(TRANSLATE_FS("GPU", "Failed to save screenshot:\n{}"), error.GetDescription()));
      }

      return;
    }

    if (show_osd_message)
    {
      Host::AddIconOSDMessage(OSDMessageType::Persistent, osd_key, ICON_EMOJI_CAMERA_WITH_FLASH,
                              fmt::format(TRANSLATE_FS("GPU", "Saving screenshot to '{}'."), Path::GetFileName(path)));
    }

    Host::QueueAsyncTask([path = std::move(path), fp = fp.release(), quality,
                          flip_y = g_gpu_device->UsesLowerLeftOrigin(), image = std::move(image),
                          osd_key = std::move(osd_key)]() mutable {
      Error error;

      if (flip_y)
        image.FlipY();

      if (image.GetFormat() != ImageFormat::RGBA8)
      {
        std::optional<Image> convert_image = image.ConvertToRGBA8(&error);
        if (!convert_image.has_value())
        {
          ERROR_LOG("Failed to convert {} screenshot to RGBA8: {}", Image::GetFormatName(image.GetFormat()),
                    error.GetDescription());
          image.Invalidate();
        }
        else
        {
          image = std::move(convert_image.value());
        }
      }

      bool result = false;
      if (image.IsValid())
      {
        image.SetAllPixelsOpaque();

        result = image.SaveToFile(path.c_str(), fp, quality, &error);
        if (!result)
          ERROR_LOG("Failed to save screenshot to '{}': '{}'", Path::GetFileName(path), error.GetDescription());
      }

      if (!osd_key.empty())
      {
        Host::AddIconOSDMessage(result ? OSDMessageType::Info : OSDMessageType::Error, std::move(osd_key),
                                ICON_EMOJI_CAMERA,
                                fmt::format(result ? TRANSLATE_FS("GPU", "Saved screenshot to '{}'.") :
                                                     TRANSLATE_FS("GPU", "Failed to save screenshot to '{}'."),
                                            Path::GetFileName(path)));
      }

      std::fclose(fp);
      return result;
    });
  });
}

namespace {

class GPUNullBackend final : public GPUBackend
{
public:
  GPUNullBackend();
  ~GPUNullBackend() override;

  bool Initialize(bool upload_vram, Error* error) override;
  bool UpdateSettings(const GPUSettings& old_settings, Error* error) override;

  u32 GetResolutionScale() const override;

  void RestoreDeviceContext() override;
  void FlushRender() override;

  void ReadVRAM(u32 x, u32 y, u32 width, u32 height) override;
  void FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color, bool interlaced_rendering,
                u8 interlaced_display_field) override;
  void UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, bool set_mask, bool check_mask) override;
  void CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height, bool set_mask,
                bool check_mask) override;

  void DrawPolygon(const GPUBackendDrawPolygonCommand* cmd) override;
  void DrawPrecisePolygon(const GPUBackendDrawPrecisePolygonCommand* cmd) override;
  void DrawSprite(const GPUBackendDrawRectangleCommand* cmd) override;
  void DrawLine(const GPUBackendDrawLineCommand* cmd) override;
  void DrawPreciseLine(const GPUBackendDrawPreciseLineCommand* cmd) override;

  void DrawingAreaChanged() override;
  void ClearCache() override;
  void OnBufferSwapped() override;
  void ClearVRAM() override;

  void UpdateDisplay(const GPUBackendUpdateDisplayCommand* cmd) override;

  void LoadState(const GPUBackendLoadStateCommand* cmd) override;

  bool AllocateMemorySaveState(System::MemorySaveState& mss, Error* error) override;
  void DoMemoryState(StateWrapper& sw, System::MemorySaveState& mss) override;
};

} // namespace

GPUNullBackend::GPUNullBackend() = default;

GPUNullBackend::~GPUNullBackend() = default;

bool GPUNullBackend::Initialize(bool upload_vram, Error* error)
{
  return GPUBackend::Initialize(upload_vram, error);
}

bool GPUNullBackend::UpdateSettings(const GPUSettings& old_settings, Error* error)
{
  return GPUBackend::UpdateSettings(old_settings, error);
}

u32 GPUNullBackend::GetResolutionScale() const
{
  return 1;
}

void GPUNullBackend::RestoreDeviceContext()
{
}

void GPUNullBackend::FlushRender()
{
}

void GPUNullBackend::ReadVRAM(u32 x, u32 y, u32 width, u32 height)
{
}

void GPUNullBackend::FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color, bool interlaced_rendering,
                              u8 interlaced_display_field)
{
}

void GPUNullBackend::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, bool set_mask, bool check_mask)
{
}

void GPUNullBackend::CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height, bool set_mask,
                              bool check_mask)
{
}

void GPUNullBackend::DrawPolygon(const GPUBackendDrawPolygonCommand* cmd)
{
}

void GPUNullBackend::DrawPrecisePolygon(const GPUBackendDrawPrecisePolygonCommand* cmd)
{
}

void GPUNullBackend::DrawSprite(const GPUBackendDrawRectangleCommand* cmd)
{
}

void GPUNullBackend::DrawLine(const GPUBackendDrawLineCommand* cmd)
{
}

void GPUNullBackend::DrawPreciseLine(const GPUBackendDrawPreciseLineCommand* cmd)
{
}

void GPUNullBackend::DrawingAreaChanged()
{
}

void GPUNullBackend::ClearCache()
{
}

void GPUNullBackend::OnBufferSwapped()
{
}

void GPUNullBackend::ClearVRAM()
{
}

void GPUNullBackend::UpdateDisplay(const GPUBackendUpdateDisplayCommand* cmd)
{
}

void GPUNullBackend::LoadState(const GPUBackendLoadStateCommand* cmd)
{
}

bool GPUNullBackend::AllocateMemorySaveState(System::MemorySaveState& mss, Error* error)
{
  return false;
}

void GPUNullBackend::DoMemoryState(StateWrapper& sw, System::MemorySaveState& mss)
{
}

Common::unique_aligned_ptr<GPUBackend> GPUBackend::CreateNullBackend()
{
  return Common::make_unique_aligned<GPUNullBackend>(HOST_CACHE_LINE_SIZE);
}
