# HarmonyOS Libretro 前端问题解决方案

本文档针对 `问题.md` 中识别的问题提供详细的企业级解决方案，整合了 Libretro/RetroArch 最佳实践与鸿蒙平台特性。

---

## 高优先级问题

---

### 1. GLES 渲染路径缺失 PBO 优化

**问题**：`GLESRenderer::Render` 直接使用 `glTexSubImage2D(data)` 同步上传纹理，未利用已定义的 `upload_scratch_ring_` PBO 机制。

**解决方案**：启用 PBO 双缓冲异步上传

#### 核心实现

```cpp
// gles_renderer.cpp - Render 函数修改

void GLESRenderer::Render(const void *data, unsigned width, unsigned height,
                          size_t pitch, retro_pixel_format format,
                          float content_aspect_ratio) {
  // ... 前置检查保持不变 ...

  // === 启用 PBO 异步上传 ===
  const size_t bpp = (format == RETRO_PIXEL_FORMAT_XRGB8888) ? 4 : 2;
  const size_t dataSize = pitch * height;

  // 1. 获取可用 PBO 槽位（等待 GPU 完成旧上传）
  unsigned slot = BeginUploadScratch(dataSize);
  
  // 2. 确保 scratch buffer 足够大
  if (upload_scratch_ring_[slot].size() < dataSize) {
    upload_scratch_ring_[slot].resize(dataSize);
  }

  // 3. 生成或复用 PBO
  if (pbo_ring_[slot] == 0) {
    glGenBuffers(1, &pbo_ring_[slot]);
  }
  
  // 4. 绑定 PBO 并分配/孤儿化
  glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_ring_[slot]);
  glBufferData(GL_PIXEL_UNPACK_BUFFER, dataSize, nullptr, GL_STREAM_DRAW);
  
  // 5. Map + memcpy（异步 DMA 启动点）
  void* mappedPtr = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, dataSize,
                                     GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
  if (mappedPtr) {
    memcpy(mappedPtr, data, dataSize);
    glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
  }

  // 6. 异步纹理上传（从 PBO 偏移 0 读取）
  if (width != tex_width_ || height != tex_height_ || format != current_format_) {
    glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0,
                 pixelFormat, pixelType, nullptr);  // offset = 0
    tex_width_ = width;
    tex_height_ = height;
    current_format_ = format;
  } else {
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height,
                    pixelFormat, pixelType, nullptr);  // offset = 0
  }

  // 7. 解绑 PBO
  glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);

  // 8. 插入 Fence 以追踪上传完成
  EndUploadScratch(slot);

  // ... 后续绘制和 SwapBuffers 保持不变 ...
}
```

#### 变更清单

| 文件 | 修改项 |
|------|--------|
| `gles_renderer.h` | 添加 `std::array<GLuint, 3> pbo_ring_{0, 0, 0}` 成员 |
| `gles_renderer.cpp` | 修改 `Render` 函数使用 PBO 机制 |
| `gles_renderer.cpp` | 在 `Deinit` 中添加 `glDeleteBuffers` 清理 PBO |

#### 预期收益
- **CPU 不再阻塞**：`glTexSubImage2D` 立即返回
- **帧率提升**：1080p 场景预期提升 15-30%
- **GPU 利用率更高**：DMA 并行传输

---

### 2. HwRenderPresenter 每帧 glGet 导致流水线停顿

**问题**：`Present` 函数调用 `CaptureState` 执行大量 `glGetIntegerv/glGetFloatv`，导致 GPU 流水线停顿。

**解决方案**：移除状态捕获,采用"设置后不恢复"策略

#### 核心原理
根据 Libretro 规范，Frontend 完全控制 FBO 0。Core 渲染到 FBO（由 Frontend 提供），Frontend 将其 Blit 到屏幕。无需保存/恢复 Core 的 GL 状态。

#### 实现方案

```cpp
// hw_render_presenter.cpp - Present 函数修改

void HwRenderPresenter::Present(int viewport_width, int viewport_height,
                                float content_aspect_ratio,
                                unsigned content_width,
                                unsigned content_height) {
  if (!ready_ || viewport_width <= 0 || viewport_height <= 0) {
    return;
  }

  // === 移除 CaptureState 调用 ===
  // GLStateBackup state = CaptureState(attrib_pos_, attrib_tex_);  // 删除

  // 直接设置所需状态（Forward-Only）
  glBindFramebuffer(GL_FRAMEBUFFER, 0);
  glViewport(0, 0, viewport_width, viewport_height);
  glDisable(GL_DEPTH_TEST);
  glDisable(GL_SCISSOR_TEST);
  glDisable(GL_CULL_FACE);
  glDisable(GL_BLEND);
  glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
  glClear(GL_COLOR_BUFFER_BIT);

  glUseProgram(program_);
  glBindBuffer(GL_ARRAY_BUFFER, vbo_);
  UpdateQuad(scale_x, scale_y, u_max, v_top, v_bottom);

  // ... 绑定属性和纹理 ...

  glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

  // === 移除 RestoreState 调用 ===
  // RestoreState(state, attrib_pos_, attrib_tex_);  // 删除

  // 仅恢复 FBO 绑定（如果 Core 需要继续渲染到 FBO）
  glBindFramebuffer(GL_FRAMEBUFFER, framebuffer_);
}
```

#### 变更清单

| 文件 | 修改项 |
|------|--------|
| `hw_render_presenter.cpp` | 删除 `CaptureState` 和 `RestoreState` 调用 |
| `hw_render_presenter.cpp` | 保留 `GLStateBackup` 结构体以备将来需要 |

#### 预期收益
- **每帧节省 10+ 次 glGet 调用**
- **消除流水线停顿**：预期帧时间减少 1-3ms

---

### 3. AudioBridge 非阻塞写入导致模拟器飞速运行

**问题**：`ProcessAudio` 使用非阻塞 `RingBuffer::Write`，缓冲区满时丢弃旧数据而非阻塞，导致核心无法通过音频实现速度同步。

**解决方案**：实现可选的阻塞写入模式

#### 设计思路
Libretro 核心依赖音频回调阻塞来限制速度（Audio Sync）。当缓冲区满时，`WriteWait` 会阻塞直到有空间，从而反压限制核心帧率。

#### 实现方案

```cpp
// audio_bridge.h - 添加同步模式控制
class AudioBridge {
public:
  enum class SyncMode {
    NON_BLOCKING,    // 现有行为：丢弃溢出数据（Fast Forward）
    AUDIO_BLOCKING   // 新行为：阻塞等待空间（精确同步）
  };
  
  void SetSyncMode(SyncMode mode) { sync_mode_.store(mode); }
  SyncMode GetSyncMode() const { return sync_mode_.load(); }

private:
  std::atomic<SyncMode> sync_mode_{SyncMode::AUDIO_BLOCKING};  // 默认阻塞
};

// audio_bridge.cpp - ProcessAudio 修改
size_t AudioBridge::ProcessAudio(const int16_t *data, size_t frames) {
  // ... 重采样逻辑保持不变 ...

  bool success = false;
  if (sync_mode_.load() == SyncMode::AUDIO_BLOCKING) {
    // 阻塞写入：核心会被反压
    success = ring_buffer_->WriteWait(resample_out_buf_.data(), out_frames, running_);
  } else {
    // 非阻塞写入：Fast Forward 模式
    success = ring_buffer_->Write(resample_out_buf_.data(), out_frames);
  }

  // ... 后续逻辑保持不变 ...
}
```

#### NAPI 层暴露接口

```cpp
// libretro_engine_napi.cpp
napi_value SetAudioSyncMode(napi_env env, napi_callback_info info) {
  // 参数: 0 = NON_BLOCKING, 1 = AUDIO_BLOCKING
  // 调用 AudioBridge::GetInstance()->SetSyncMode(...)
}
```

#### 变更清单

| 文件 | 修改项 |
|------|--------|
| `audio_bridge.h` | 添加 `SyncMode` 枚举和 `sync_mode_` 成员 |
| `audio_bridge.cpp` | 修改 `ProcessAudio` 根据模式选择写入方式 |
| `libretro_engine_napi.cpp` | 暴露 `setAudioSyncMode` NAPI 接口 |
| `index.d.ts` | 添加 TypeScript 类型声明 |

#### 预期收益
- **精确帧节奏**：核心以正确速度运行
- **兼容性**：保留 Fast Forward 可选项

---

### 4. 磁盘控制接口未实现

**问题**：`EnvDispatcher` 响应 `SET_DISK_CONTROL_INTERFACE` 返回 `true`，但 `LibretroEngine` 未实现实际换盘逻辑。

**解决方案**：完整实现磁盘控制 API

#### 架构设计

```
┌─────────────────┐     ┌──────────────────┐     ┌─────────────────┐
│   ArkTS UI      │ ──▶ │  NAPI Bridge     │ ──▶ │ LibretroEngine  │
│ (换盘对话框)    │     │ (diskEject, etc) │     │ (DiskController)│
└─────────────────┘     └──────────────────┘     └─────────────────┘
                                                          │
                                                          ▼
                                                 ┌─────────────────┐
                                                 │ retro_disk_*()  │
                                                 │ (Libretro API)  │
                                                 └─────────────────┘
```

#### 核心实现

```cpp
// disk_controller.h - 新文件
class DiskController {
public:
  void SetCallbacks(const retro_disk_control_ext_callback* cbs);
  
  bool Eject();
  bool Insert();
  bool SetImageIndex(unsigned index);
  unsigned GetImageIndex() const;
  unsigned GetNumImages() const;
  bool AddImagePath(const std::string& path);
  
private:
  retro_disk_control_ext_callback callbacks_{};
  bool ejected_ = false;
};

// libretro_engine.h - 添加成员
class LibretroEngine {
public:
  // 磁盘控制 API
  bool DiskEject();
  bool DiskInsert();
  bool DiskSetIndex(unsigned index);
  unsigned DiskGetIndex() const;
  unsigned DiskGetNumImages() const;
  bool DiskAddImage(const std::string& path);

private:
  std::unique_ptr<DiskController> disk_controller_;
};

// env_dispatcher.cpp - 修改回调注册
case RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE: {
  auto* cbs = static_cast<const retro_disk_control_ext_callback*>(data);
  if (cbs && engine_->disk_controller_) {
    engine_->disk_controller_->SetCallbacks(cbs);
    return true;
  }
  return false;
}
```

#### NAPI 接口

```typescript
// index.d.ts
export function diskEject(): boolean;
export function diskInsert(): boolean;
export function diskSetIndex(index: number): boolean;
export function diskGetIndex(): number;
export function diskGetNumImages(): number;
export function diskAddImage(path: string): boolean;
```

#### 变更清单

| 文件 | 修改项 |
|------|--------|
| `disk_controller.h/cpp` | 新建磁盘控制器类 |
| `libretro_engine.h/cpp` | 添加磁盘控制成员和方法 |
| `env_dispatcher.cpp` | 连接回调到 DiskController |
| `libretro_engine_napi.cpp` | 暴露 6 个磁盘控制 NAPI |
| `index.d.ts` | 添加类型声明 |

---

### 5. 丢帧导致逻辑加速（已修复）

**问题**：`VideoPipeline` 在丢帧（Fence 超时、Surface 丢失）时直接返回，无时序补偿，导致游戏加速。

**解决方案**：在丢帧路径补齐 FramePacer 的 EndFrame 补偿

> ⚠️ **评审反馈**：简单的 `std::this_thread::sleep_for(16.67ms)` 在非实时操作系统上精度差，且无法对齐 VSync 信号，会导致微卡顿。推荐使用**相对计时 + 自旋等待**方案。

#### 核心实现 (FramePacer)

```cpp
// frame_pacer.h - 新文件
#pragma once
#include <atomic>
#include <chrono>
#include <thread>

namespace libretro {

class FramePacer {
public:
  void BeginFrame() {
    frame_start_ = std::chrono::steady_clock::now();
  }

  // 在丢帧或正常帧结束时调用，确保帧时间稳定
  void EndFrame() {
    auto target_end = frame_start_ + 
        std::chrono::microseconds(target_frame_time_us_);
    auto now = std::chrono::steady_clock::now();
    
    if (now >= target_end) {
      // 已超时，无需补偿
      return;
    }
    
    auto remaining = std::chrono::duration_cast<std::chrono::microseconds>(
        target_end - now).count();
    
    // 阶段1：Sleep 粗调（预留 ~1ms 调度误差）
    if (remaining > 2000) {
      std::this_thread::sleep_for(
          std::chrono::microseconds(remaining - 1500));
    }
    
    // 阶段2：Spin wait 精调（最后 ~1.5ms）
    while (std::chrono::steady_clock::now() < target_end) {
      std::this_thread::yield();  // 让出 CPU 时间片
    }
  }

  void SetTargetFps(double fps) {
    if (fps > 0.0) {
      target_frame_time_us_ = static_cast<int64_t>(1000000.0 / fps);
    }
  }

  int64_t GetTargetFrameTimeUs() const {
    return target_frame_time_us_;
  }

private:
  std::chrono::steady_clock::time_point frame_start_;
  std::atomic<int64_t> target_frame_time_us_{16667};  // 60 FPS default
};

} // namespace libretro
```

#### VideoPipeline 集成

```cpp
// video_pipeline.h - 添加成员
#include "frame_pacer.h"

class VideoPipeline {
  // ...
private:
  FramePacer frame_pacer_;
};

// video_pipeline.cpp - Render 函数收尾
auto Finish = [&](RenderResult r) {
  if (r == RenderResult::DROPPED) {
    framePacer_.EndFrame();
  }
  // 统计与返回
  return r;
};
```

#### 进阶方案：接入 HarmonyOS VSync

```cpp
// 未来优化：使用 OH_DisplayManager 获取 VSync 信号
// 参考 API: OH_DisplayManager_RegisterDisplayChangeCallback
// 此方案需要额外评估 API 可用性
```

#### 变更清单

| 文件 | 修改项 |
|------|--------|
| `frame_pacer.h` | 新建帧节奏控制器类 |
| `video_pipeline.h` | 添加 `FramePacer frame_pacer_` 成员 |
| `video_pipeline.cpp` | 添加 `BeginFrame`/`EndFrame` 调用 |

---

### 6. VideoPipeline 配置冷却逻辑过于粗糙（已修复）

**问题**：`retry_count_ > 10` 后强制 2 秒冷却，无法区分"错误重试"和"合法的新配置请求"。

**解决方案**：仅对“同一配置连续失败”执行冷却，遇到新配置立即放行

```cpp
// video_pipeline.cpp - EnsureWindowConfiguredIfNeeded 修改

void VideoPipeline::EnsureWindowConfiguredIfNeeded(...) {
  const bool needConfigUpdate = (...);
  if (!needConfigUpdate) {
    return;
  }

  // 检测是否为新的配置请求（而非失败重试）
  const bool isNewConfig = (width != last_request_width_ ||
                            height != last_request_height_ ||
                            mode != last_request_mode_);

  auto now = std::chrono::steady_clock::now();

  // 仅对连续失败的相同配置请求应用冷却
  if (!isNewConfig && retry_count_ > 10) {
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
                        now - last_retry_time_).count();
    if (duration < 2000) {
      geometry_changed_.store(true);
      return;  // 冷却中
    }
    retry_count_ = 0;  // 冷却结束
  }

  // 新配置请求：重置重试计数
  if (isNewConfig) {
    retry_count_ = 0;
  }

  // ... 后续配置逻辑 ...
}
```

---

### 7. RingBuffer 锁竞争过高

**问题**：`RingBuffer` 使用 `std::mutex` 保护所有操作，在高频音频回调场景下性能瓶颈明显。

**解决方案**：采用成熟的 SPSC 无锁队列库

> ⚠️ **评审反馈**：手写无锁结构是 C++ 中最大的坑之一。除非有极高的自信和严格的 Stress Test，否则建议直接使用成熟的库。

#### 推荐方案：使用成熟库

```cpp
// CMakeLists.txt - 添加依赖
include(FetchContent)

# 方案 A：moodycamel::ReaderWriterQueue (推荐)
FetchContent_Declare(readerwriterqueue
  GIT_REPOSITORY https://github.com/cameron314/readerwriterqueue.git
  GIT_TAG v1.0.6)
FetchContent_MakeAvailable(readerwriterqueue)

# 方案 B：boost::lockfree::spsc_queue
# find_package(Boost REQUIRED)

// audio_bridge.cpp - 使用示例
#include <readerwriterqueue.h>

class AudioBridge {
  // 替换 std::mutex 保护的 RingBuffer
  moodycamel::ReaderWriterQueue<int16_t> audio_queue_{96000 * 2};  // 1秒容量
};
```

#### 备选方案：优化现有锁实现

如果不引入外部依赖，可优化现有 `RingBuffer`：

```cpp
// ring_buffer.h - 减少锁持有时间

bool Write(const int16_t *data, size_t frames) {
  const size_t samples = frames * 2;
  
  // 1. 快速检查空间（无锁）
  size_t avail = capacity_ - available_.load(std::memory_order_acquire);
  if (samples > avail) {
    return false;
  }
  
  // 2. 加锁写入
  std::lock_guard<std::mutex> lock(mutex_);
  // ... 实际写入 ...
}
```

#### 手写实现（不推荐）

> ⛔ **风险警告**：
> - 必须仅限 **SPSC**（单生产单消费）场景
> - `alignas(64)` 在某些 ARM 架构上可能不够（ARM Cortex-A 缓存行可能是 128 字节）
> - 需要添加 **严格的 Stress Test 单元测试**

```cpp
// 仅当确实需要手写时参考此实现
template<typename T>
class LockFreeRingBuffer {
public:
  explicit LockFreeRingBuffer(size_t capacity)
      : capacity_(RoundUpPowerOf2(capacity)),
        mask_(capacity_ - 1),
        buffer_(new T[capacity_]) {}

  bool Write(const T* data, size_t count) {
    size_t write = write_pos_.load(std::memory_order_relaxed);
    size_t read = read_pos_.load(std::memory_order_acquire);
    
    size_t available = capacity_ - (write - read);
    if (count > available) {
      return false;
    }

    for (size_t i = 0; i < count; ++i) {
      buffer_[(write + i) & mask_] = data[i];
    }

    write_pos_.store(write + count, std::memory_order_release);
    return true;
  }

  size_t Read(T* data, size_t count) {
    size_t read = read_pos_.load(std::memory_order_relaxed);
    size_t write = write_pos_.load(std::memory_order_acquire);
    
    size_t available = write - read;
    size_t toRead = std::min(count, available);

    for (size_t i = 0; i < toRead; ++i) {
      data[i] = buffer_[(read + i) & mask_];
    }

    read_pos_.store(read + toRead, std::memory_order_release);
    return toRead;
  }

private:
  static size_t RoundUpPowerOf2(size_t v) {
    v--;
    v |= v >> 1; v |= v >> 2; v |= v >> 4;
    v |= v >> 8; v |= v >> 16; v |= v >> 32;
    return v + 1;
  }

  const size_t capacity_;
  const size_t mask_;
  std::unique_ptr<T[]> buffer_;
  
  // 缓存行对齐，避免伪共享
  alignas(64) std::atomic<size_t> write_pos_{0};
  alignas(64) std::atomic<size_t> read_pos_{0};
};
```

#### 迁移策略
1. 创建 `LockFreeRingBuffer<int16_t>` 类
2. 在 `AudioBridge` 中替换 `RingBuffer` 为新实现
3. 保留 `WriteWait`/`ReadWait` 接口（使用条件变量实现阻塞）

---

### 8-10. 架构层面问题

**渲染管线职责分裂** / **核心切换 Window 状态复用** / **接口抽象缺失**

进度：阶段一/二已完成（落地在 VideoPipeline 内部），接口抽象缺失仍未完成。

这些问题需要更大规模的重构，建议分阶段处理：

#### 阶段一：统一渲染抽象（已完成）

```cpp
// i_render_backend.h
class IRenderBackend {
public:
  virtual ~IRenderBackend() = default;
  virtual bool Initialize(OHNativeWindow* window) = 0;
  virtual void Shutdown() = 0;
  virtual RenderResult Render(const FrameData& frame) = 0;
  virtual void OnWindowResized(int width, int height) = 0;
};

class SoftwareBackend : public IRenderBackend { ... };
class GLESBackend : public IRenderBackend { ... };
class HWRenderBackend : public IRenderBackend { ... };
```

#### 阶段二：Window 状态管理器（已完成，保存/恢复受限）

```cpp
// window_state_manager.h
class WindowStateManager {
public:
  void Reset();
  ApplyResult Apply(OHNativeWindow* window, const WindowState& state, ...);
  void ResetToDefaults();

private:
  struct WindowState {
    uint64_t usage;
    int scalingMode;
    // ...
  };
};
```

---

## 中优先级问题

---

### 11. Vulkan WaitSyncIndex 空实现

```cpp
// vulkan_presenter.cpp
void VulkanPresenter::WaitSyncIndex(unsigned index) {
  if (index >= sync_fences_.size()) {
    return;
  }
  
  VkFence fence = sync_fences_[index];
  if (fence != VK_NULL_HANDLE) {
    constexpr uint64_t kTimeout = 100000000ULL;  // 100ms
    VkResult result = vkWaitForFences(device_, 1, &fence, VK_TRUE, kTimeout);
    if (result == VK_TIMEOUT) {
      LOGF(LOG_WARN, "VulkanPresenter: WaitSyncIndex timeout");
    }
  }
}
```

---

### 12. 音频重采样无条件开启（已修复）

```cpp
// audio_bridge.cpp - ProcessAudio 修改
size_t AudioBridge::ProcessAudio(const int16_t *data, size_t frames) {
  // ... 前置检查 ...

  // Bypass 优化：采样率相同时直接写入
  if (core_sample_rate_ == output_sample_rate_) {
    bool success = ring_buffer_->Write(data, frames);
    // ... 后续逻辑 ...
    return success ? frames : 0;
  }

  // 需要重采样
  // ... 现有重采样逻辑 ...
}
```

---

### 13. GetVariable 重入风险（已修复）

```cpp
// env_dispatcher.cpp - 多槽位 TLS 缓存
const char* EnvState::GetVariable(const char* key) {
  static thread_local std::array<std::string, 8> tls_values;
  static thread_local size_t tls_index = 0;
  
  std::string& slot = tls_values[tls_index];
  tls_index = (tls_index + 1) % tls_values.size();
  
  // 填充 slot
  slot = GetVariableInternal(key);
  return slot.c_str();
}
```

---

### 14-20. 其他中优先级修复

| 问题 | 快速修复 |
|------|----------|
| CanSendVirtual 检查不完整 | 添加 `inputPortRouter_->IsPortMapped(port)` 检查 |
| Vulkan Swapchain 频繁重建 | 添加 200ms 去抖动 (`std::chrono` 节流) |
| Gambatte 硬编码 | 抽象 `CoreQuirksManager::GetQuirks(library_name)` |
| 日志节流失效 | 冷却期间仍输出 1 条/10秒 关键日志 |
| GLES 回退过于激进 | **[已修复]** 添加 2秒冷却重试逻辑 |
| CPU 渲染 stride 对齐 | **[已修复]** `PixelConverter` 已处理 stride |
| EnsureDirExists 非递归 | 改用递归创建或 `std::filesystem::create_directories` |
| RTLD_GLOBAL 符号污染 | **[已修复]** 仅使用 `RTLD_LOCAL` |

---

### 28. NAPI 神之函数重构（已修复）

**问题**：`LoadRomDataFromRawfileIfNeeded` 职责过载，耦合了资源加载、临时文件管理、CueSheet 解析等逻辑。

**解决方案**：
1. 提取 `TempFileManager` (platform/resource) 负责临时文件生命周期。
2. 提取 `CueParser` (common) 负责 .cue 文件解析。
3. NAPI 层仅作为胶水代码调用上述组件。

**变更清单**：
- 新增 `entry/src/main/cpp/platform/resource/temp_file_manager.h/cpp`
- 新增 `entry/src/main/cpp/common/cue_parser.h/cpp`
- 重构 `entry/src/main/cpp/app/napi/libretro_engine_napi.cpp`

---

## 低优先级问题

---

### 21-23. 快速修复清单

| 问题 | 修复 |
|------|------|
| 高频输入事件积压 | `ThreadSafeQueue::Push` 时合并连续 `MouseMove` 事件 |
| refactoredSendSensor 类型缺失 | 在 `index.d.ts` 添加 `sendSensor(data: Float32Array): void` |
| CoreOptionsRegistry 手写解析 | 引入 `libretro-common/file/config_file.h` |

---

## HarmonyOS 平台特定优化

> 📋 **补充章节**：根据技术评审反馈，添加鸿蒙平台特有的优化考量。

---

### 24. XComponent 生命周期处理

App 切换到后台或锁屏时，GL Context 会丢失，需要正确处理资源重建：

```cpp
// window_lifecycle_handler.h
class WindowLifecycleHandler {
public:
  void OnSurfaceDestroyed() {
    // 1. 释放 GL 资源（而非仅保存状态）
    InvalidateGLResources();
    
    // 2. 标记需要重建
    context_lost_ = true;
    
    // 3. 通知渲染管线停止
    pipeline_->SetSurfaceLost(true);
  }

  void OnSurfaceRecreated(OHNativeWindow* window) {
    if (context_lost_) {
      // 1. 重建 EGL Context
      graphics_context_->RecreateContext(window);
      
      // 2. 重建着色器和纹理
      RebuildGLResources();
      
      // 3. 恢复渲染
      pipeline_->SetSurfaceLost(false);
      context_lost_ = false;
    }
  }

private:
  bool context_lost_ = false;
};
```

---

### 25. 沙箱路径转换

Libretro 核心通常只认标准 POSIX 路径，但鸿蒙文件选择返回 URI：

```cpp
// path_converter.h
#include <sys/stat.h>
#include <fstream>

class PathConverter {
public:
  // 将 URI 或沙箱外路径转换为可用的 POSIX 路径
  static std::string ConvertToAccessiblePath(
      const std::string& uri_or_path,
      const std::string& sandbox_dir) {
    
    // file:// 协议直接转换
    if (uri_or_path.starts_with("file://")) {
      return uri_or_path.substr(7);
    }
    
    // content:// 协议需要复制到沙箱
    if (uri_or_path.starts_with("content://")) {
      return CopyToSandbox(uri_or_path, sandbox_dir);
    }
    
    // 检查路径是否可访问
    struct stat st;
    if (stat(uri_or_path.c_str(), &st) == 0) {
      return uri_or_path;
    }
    
    // 不可访问，尝试复制
    return CopyToSandbox(uri_or_path, sandbox_dir);
  }

private:
  static std::string CopyToSandbox(
      const std::string& source, 
      const std::string& sandbox_dir) {
    // 实际实现需要使用 ohos.file.fs API
    // 这里是简化版本
    auto filename = GetFilename(source);
    auto target = sandbox_dir + "/" + filename;
    
    // 使用 FileAccessHelper 复制文件
    // ...
    
    return target;
  }
};
```

---

### 26. NAPI 边界开销优化

高频跨 NAPI 调用会带来性能损耗：

```cpp
// 不推荐：每帧多次跨 NAPI
void SendSensorX(float x);  // NAPI 调用
void SendSensorY(float y);  // NAPI 调用
void SendSensorZ(float z);  // NAPI 调用

// 推荐：批量传输
void SendSensorData(Float32Array data);  // 单次 NAPI 调用

// 或者：使用 SharedArrayBuffer 避免拷贝
```

#### 变更建议

| 高频接口 | 优化方案 |
|---------|---------|
| `sendSensor` | 改为 `Float32Array` 批量传输 |
| 输入事件 | 合并同帧的多个事件 |
| 音频数据 | 已使用 RingBuffer，无需优化 |

---

### 27. Release 包优化

```cpp
// common/gl_debug.h - Release 包禁用 glGetError

#ifdef NDEBUG
  // Release 模式：禁用 GL 错误检查以提升性能
  #define GL_CHECK_ERROR() ((void)0)
  #define GL_CLEAR_ERRORS() ((void)0)
#else
  // Debug 模式：启用错误检查
  #define GL_CHECK_ERROR() do { \
    GLenum err = glGetError(); \
    if (err != GL_NO_ERROR) { \
      LOGF(LOG_WARN, "GL Error: 0x%x at %s:%d", err, __FILE__, __LINE__); \
    } \
  } while(0)
  
  #define GL_CLEAR_ERRORS() while(glGetError() != GL_NO_ERROR) {}
#endif
```

## 验证计划

### 自动化测试

```bash
# 编译验证
./build.sh --target arm64-v8a --profile Release

# 单元测试（如有）
./test_runner --filter="RingBuffer*"
./test_runner --filter="PixelConverter*"
```

### 手动验证清单

1. **PBO 优化**：运行 1080p 核心，观察 GPU Profiler 纹理上传时间
2. **音频同步**：运行 Gambatte，确认游戏速度正常（不加速）
3. **磁盘控制**：加载多盘 PS1 游戏，测试换盘流程
4. **丢帧补偿**：人为触发丢帧（锁屏/后台），确认恢复后速度正常
5. **无锁缓冲区**：高负载测试，确认无音频撕裂

---

## 实施优先级

```
紧急修复 (P0) - 影响核心功能
├── 音频阻塞写入（问题 3）
├── 丢帧补偿（问题 5）
└── 磁盘控制（问题 4）

高优先级 (P1) - 性能关键
├── PBO 异步上传（问题 1）
├── 移除 glGet（问题 2）
└── 无锁缓冲区（问题 7）

中优先级 (P2) - 质量改进
├── 配置冷却优化（问题 6）
├── 重采样 Bypass（问题 12）
└── 其他中优先级问题

低优先级 (P3) - 技术债务
└── 架构重构（问题 8-10）
```
