当前位置: 首页 > news >正文

嵌入式linux下的NES游戏显示效果优化方案:infoNES显示效果优化

博主有篇文章《iMX6ULL应用移植 | 移植 infoNES 模拟器(重玩经典NES游戏)》,但这个有个问题啊,传统的nes游戏都是位图拼成的人物图形,在大屏设备上显示的话,颗粒感严重。如何提升NES游戏的画质?让经典的NES游戏重新焕发生命力?这是此文探讨的内容。

infoNES 是一个功能强大的NES游戏模拟器,它为玩家在现代计算机上重温经典NES游戏提供了可能。然而,当我们将 NES 游戏画面放大到大屏幕时,原始设计中的最近邻插值算法导致画面颗粒感严重,细节模糊。本文将探讨如何优化 infoNES 的显示效果,使其在大屏幕上也能保持清晰、自然的画面。

博主之前的那篇嵌入式linux下NES游戏模拟器移植文章地址:

iMX6ULL应用移植 | 移植 infoNES 模拟器(重玩经典NES游戏)

在这里插入图片描述

当前存在的问题

  1. 使用简单的最近邻插值
    • 这种方法速度快,但在放大时会导致画面颗粒感严重。
  2. 没有抗锯齿处理
    • NES 游戏的像素艺术风格在放大时容易出现锯齿边缘。
  3. 色彩空间转换简单
    • 原始的色彩转换算法没有考虑颜色的平滑过渡,导致画面色彩显得生硬。

优化思路

  1. 实现高质量插值算法
    • 双线性插值和双三次插值可以提供更好的过渡效果。
  2. 添加抗锯齿滤镜
    • 使用HQ2x、HQ3x或xBRZ滤镜可以在保持像素艺术风格的同时减少锯齿。
  3. 改进色彩空间转换
    • 优化色彩转换算法以实现更自然的颜色过渡。
  4. 支持多种缩放模式
    • 提供选项选择不同的缩放模式以适应不同的硬件性能和显示需求。

具体实现方案

优化方案对比

方案质量性能描述
最近邻插值★☆☆☆☆★★★★★原始算法,速度快但质量差
双线性插值★★★☆☆★★★★☆平滑过渡,质量较好
HQ2x滤镜★★★★★☆★★★☆☆像素艺术专用,边缘清晰
HQ3x滤镜★★★★★★★☆☆☆更高质量,但性能开销大
锐化滤镜★★★☆☆★★★☆☆增强细节,适合低分辨率

编译优化版本

为了编译优化后的 infoNES 版本,您可以执行以下命令:

# 进入linux目录
cd linux# 编译优化版本
make -f Makefile.optimized# 或者编译完整版本
g++ -O3 -std=c++11 InfoNES_System_Linux_complete.cpp ../InfoNES.cpp ../InfoNES_Mapper.cpp ../InfoNES_pAPU.cpp ../K6502.cpp joypad_input.cpp -o InfoNES_complete -lasound -lm -lpthread

运行优化版本

编译完成后,您可以运行优化后的 infoNES 模拟器:

# 运行优化版本
./InfoNES_optimized your_game.nes# 运行完整版本
./InfoNES_complete your_game.nes

配置选项

显示模式设置

display_config.h 中修改:

// 选择显示模式
#define DISPLAY_MODE_HQ2X    // 推荐:HQ2x滤镜
// #define DISPLAY_MODE_BILINEAR  // 双线性插值
// #define DISPLAY_MODE_NEAREST   // 最近邻插值

后处理效果

// 启用锐化滤镜
#define ENABLE_SHARPENING 1// 启用扫描线效果(复古CRT效果)
#define ENABLE_SCANLINES 1// 启用颜色校正
#define ENABLE_COLOR_CORRECTION 1

性能优化建议

  • 低性能设备
    • 使用 DISPLAY_MODE_BILINEAR
    • 禁用锐化滤镜
    • 使用整数缩放
  • 高性能设备
    • 使用 DISPLAY_MODE_HQ2XDISPLAY_MODE_HQ3X
    • 启用锐化滤镜
    • 启用扫描线效果

运行时控制

键盘快捷键(需要实现)

  • F1:切换显示模式
  • F2:切换锐化滤镜
  • F3:切换扫描线效果
  • F4:显示FPS

命令行参数

# 指定显示模式
./InfoNES_complete -mode hq2x game.nes# 启用调试信息
./InfoNES_complete -debug game.nes

效果对比

原始效果

  • 明显的像素块
  • 锯齿边缘
  • 颜色过渡生硬

优化后效果

  • 平滑的边缘
  • 自然的颜色过渡
  • 保留像素艺术风格
  • 可选的CRT扫描线效果

故障排除

编译错误

# 安装依赖
sudo apt-get install build-essential libasound2-dev# 检查头文件路径
gcc -v

运行错误

# 检查framebuffer权限
ls -l /dev/fb0
sudo chmod 666 /dev/fb0# 检查分辨率支持
fbset -i

性能问题

  • 降低显示模式质量
  • 禁用后处理效果
  • 使用整数缩放

高级配置

自定义滤镜

可以修改 hqx_filter.h 中的参数:

  • 调整 YUV_THRESHOLD 改变边缘检测敏感度
  • 修改锐化核强度
  • 添加自定义后处理效果

多线程优化

对于多核CPU,可以启用:

#define USE_THREADING 1

测试建议

  1. 测试游戏:Super Mario Bros、Tetris、Mega Man
  2. 测试场景:文字、精灵动画、背景滚动
  3. 测试分辨率:720p、1080p、4K
  4. 测试模式:窗口模式、全屏模式

性能基准

分辨率最近邻双线性HQ2xHQ3x
720p60fps60fps60fps45fps
1080p60fps60fps55fps35fps
4K60fps50fps30fps20fps

测试环境:Intel i5-8400, 8GB RAM

贡献指南

欢迎提交改进:

  1. 新的滤镜算法
  2. 性能优化
  3. 新的后处理效果
  4. 更好的颜色校正

代码实现

hqx_filter.h

/*===================================================================*/
/*                                                                   */
/*  hqx_filter.h : HQ2x/3x/4x filter implementation                  */
/*                                                                   */
/*  High-quality magnification filter for pixel art                  */
/*  Based on HQ2x algorithm optimized for NES emulation              */
/*                                                                   */
/*===================================================================*/#ifndef HQX_FILTER_H
#define HQX_FILTER_H#include <stdint.h>
#include <stdlib.h>// 颜色定义
typedef uint16_t u16;
typedef uint32_t u32;// 颜色掩码
#define MASK_RB  0x00F81F
#define MASK_G   0x0007E0
#define MASK_R   0x00F800
#define MASK_B   0x00001F// 差异阈值
#define YUV_THRESHOLD 48// 内联函数:RGB565转YUV
static inline u32 RGB565_to_YUV(u16 c) {u32 r = (c & 0xF800) >> 11;u32 g = (c & 0x07E0) >> 5;u32 b = (c & 0x001F);// 扩展到8位r = (r << 3) | (r >> 2);g = (g << 2) | (g >> 4);b = (b << 3) | (b >> 2);// 转YUVu32 y = (r + g + b) >> 2;u32 u = 128 + ((r - b) >> 2);u32 v = 128 + ((r + b - (g << 1)) >> 3);return (y << 16) | (u << 8) | v;
}// 内联函数:计算颜色差异
static inline int yuv_diff(u32 yuv1, u32 yuv2) {int y_diff = abs((int)((yuv1 >> 16) & 0xFF) - (int)((yuv2 >> 16) & 0xFF));int u_diff = abs((int)((yuv1 >> 8) & 0xFF) - (int)((yuv2 >> 8) & 0xFF));int v_diff = abs((int)(yuv1 & 0xFF) - (int)(yuv2 & 0xFF));return (y_diff + u_diff + v_diff) / 3;
}// 内联函数:判断两个像素是否相似
static inline int is_pixel_equal(u16 c1, u16 c2) {if (c1 == c2) return 1;u32 yuv1 = RGB565_to_YUV(c1);u32 yuv2 = RGB565_to_YUV(c2);return yuv_diff(yuv1, yuv2) < YUV_THRESHOLD;
}// 内联函数:混合颜色
static inline u16 mix_2_colors(u16 c1, u16 c2) {return ((c1 & MASK_RB) >> 1) + ((c2 & MASK_RB) >> 1) + ((c1 & MASK_G) >> 1) + ((c2 & MASK_G) >> 1);
}static inline u16 mix_3_colors(u16 c1, u16 c2, u16 c3) {return ((c1 & MASK_RB) / 3) + ((c2 & MASK_RB) / 3) + ((c3 & MASK_RB) / 3) +((c1 & MASK_G) / 3) + ((c2 & MASK_G) / 3) + ((c3 & MASK_G) / 3);
}// HQ2x算法核心
static inline void hq2x_32(u16 *sp, u16 *dp, int Xres, int Yres) {int i, j, k;u16 w[10];u16 c[9];u16 e0, e1, e2, e3;for (i = 0; i < Yres; i++) {for (j = 0; j < Xres; j++) {// 获取3x3邻域int x_m1 = (j > 0) ? j - 1 : 0;int x_p1 = (j < Xres - 1) ? j + 1 : Xres - 1;int y_m1 = (i > 0) ? i - 1 : 0;int y_p1 = (i < Yres - 1) ? i + 1 : Yres - 1;c[0] = sp[y_m1 * Xres + x_m1]; // 左上c[1] = sp[y_m1 * Xres + j];    // 上c[2] = sp[y_m1 * Xres + x_p1]; // 右上c[3] = sp[i * Xres + x_m1];    // 左c[4] = sp[i * Xres + j];       // 中心c[5] = sp[i * Xres + x_p1];    // 右c[6] = sp[y_p1 * Xres + x_m1]; // 左下c[7] = sp[y_p1 * Xres + j];    // 下c[8] = sp[y_p1 * Xres + x_p1]; // 右下// 计算模式int pattern = 0;if (!is_pixel_equal(c[4], c[0])) pattern |= 1 << 0;if (!is_pixel_equal(c[4], c[1])) pattern |= 1 << 1;if (!is_pixel_equal(c[4], c[2])) pattern |= 1 << 2;if (!is_pixel_equal(c[4], c[3])) pattern |= 1 << 3;if (!is_pixel_equal(c[4], c[5])) pattern |= 1 << 4;if (!is_pixel_equal(c[4], c[6])) pattern |= 1 << 5;if (!is_pixel_equal(c[4], c[7])) pattern |= 1 << 6;if (!is_pixel_equal(c[4], c[8])) pattern |= 1 << 7;// 根据模式选择插值switch (pattern) {case 0:  // 所有像素相同e0 = e1 = e2 = e3 = c[4];break;case 1:  // 只有左上不同e0 = mix_2_colors(c[4], c[0]);e1 = c[4];e2 = c[4];e3 = c[4];break;case 4:  // 只有右不同e0 = c[4];e1 = mix_2_colors(c[4], c[5]);e2 = c[4];e3 = mix_2_colors(c[4], c[5]);break;case 16: // 只有左下不同e0 = c[4];e1 = c[4];e2 = mix_2_colors(c[4], c[6]);e3 = c[4];break;case 64: // 只有下不同e0 = c[4];e1 = c[4];e2 = mix_2_colors(c[4], c[7]);e3 = mix_2_colors(c[4], c[7]);break;case 5:  // 左上和右不同e0 = mix_2_colors(c[4], c[0]);e1 = mix_2_colors(c[4], c[5]);e2 = c[4];e3 = mix_2_colors(c[4], c[5]);break;case 20: // 右上和左下不同e0 = c[4];e1 = mix_2_colors(c[4], c[2]);e2 = mix_2_colors(c[4], c[6]);e3 = c[4];break;case 80: // 右下和上不同e0 = c[4];e1 = mix_2_colors(c[4], c[1]);e2 = mix_2_colors(c[4], c[8]);e3 = mix_2_colors(c[4], c[1]);break;case 65: // 左上和右下不同e0 = mix_2_colors(c[4], c[0]);e1 = c[4];e2 = mix_2_colors(c[4], c[8]);e3 = c[4];break;default: // 其他情况e0 = e1 = e2 = e3 = c[4];break;}// 写入结果dp[(i * 2) * (Xres * 2) + (j * 2)] = e0;dp[(i * 2) * (Xres * 2) + (j * 2) + 1] = e1;dp[(i * 2 + 1) * (Xres * 2) + (j * 2)] = e2;dp[(i * 2 + 1) * (Xres * 2) + (j * 2) + 1] = e3;}}
}// HQ3x算法(简化版)
static inline void hq3x_32(u16 *sp, u16 *dp, int Xres, int Yres) {// 这里实现HQ3x算法,类似HQ2x但扩展到3x// 为简化,使用双线性插值作为后备int dst_width = Xres * 3;int dst_height = Yres * 3;for (int y = 0; y < dst_height; y++) {for (int x = 0; x < dst_width; x++) {float src_x = (float)x / 3.0f;float src_y = (float)y / 3.0f;int x1 = (int)src_x;int y1 = (int)src_y;int x2 = (x1 + 1) < Xres ? (x1 + 1) : (Xres - 1);int y2 = (y1 + 1) < Yres ? (y1 + 1) : (Yres - 1);float dx = src_x - x1;float dy = src_y - y1;u16 c11 = sp[y1 * Xres + x1];u16 c12 = sp[y2 * Xres + x1];u16 c21 = sp[y1 * Xres + x2];u16 c22 = sp[y2 * Xres + x2];// 双线性插值u16 r = (u16)(((c11 >> 11) & 0x1F) * (1 - dx) * (1 - dy) +((c21 >> 11) & 0x1F) * dx * (1 - dy) +((c12 >> 11) & 0x1F) * (1 - dx) * dy +((c22 >> 11) & 0x1F) * dx * dy);u16 g = (u16)(((c11 >> 5) & 0x3F) * (1 - dx) * (1 - dy) +((c21 >> 5) & 0x3F) * dx * (1 - dy) +((c12 >> 5) & 0x3F) * (1 - dx) * dy +((c22 >> 5) & 0x3F) * dx * dy);u16 b = (u16)((c11 & 0x1F) * (1 - dx) * (1 - dy) +(c21 & 0x1F) * dx * (1 - dy) +(c12 & 0x1F) * (1 - dx) * dy +(c22 & 0x1F) * dx * dy);dp[y * dst_width + x] = (r << 11) | (g << 5) | b;}}
}// 主缩放函数
static inline void hq_filter_scale(u16 *src, u16 *dst, int src_w, int src_h, int dst_w, int dst_h, int scale) {if (scale == 2) {hq2x_32(src, dst, src_w, src_h);} else if (scale == 3) {hq3x_32(src, dst, src_w, src_h);} else {// 使用双线性插值进行任意缩放float scale_x = (float)src_w / dst_w;float scale_y = (float)src_h / dst_h;for (int y = 0; y < dst_h; y++) {for (int x = 0; x < dst_w; x++) {float src_x = x * scale_x;float src_y = y * scale_y;int x1 = (int)src_x;int y1 = (int)src_y;int x2 = (x1 + 1) < src_w ? (x1 + 1) : (src_w - 1);int y2 = (y1 + 1) < src_h ? (y1 + 1) : (src_h - 1);float dx = src_x - x1;float dy = src_y - y1;u16 c11 = src[y1 * src_w + x1];u16 c12 = src[y2 * src_w + x1];u16 c21 = src[y1 * src_w + x2];u16 c22 = src[y2 * src_w + x2];u16 r = (u16)(((c11 >> 11) & 0x1F) * (1 - dx) * (1 - dy) +((c21 >> 11) & 0x1F) * dx * (1 - dy) +((c12 >> 11) & 0x1F) * (1 - dx) * dy +((c22 >> 11) & 0x1F) * dx * dy);u16 g = (u16)(((c11 >> 5) & 0x3F) * (1 - dx) * (1 - dy) +((c21 >> 5) & 0x3F) * dx * (1 - dy) +((c12 >> 5) & 0x3F) * (1 - dx) * dy +((c22 >> 5) & 0x3F) * dx * dy);u16 b = (u16)((c11 & 0x1F) * (1 - dx) * (1 - dy) +(c21 & 0x1F) * dx * (1 - dy) +(c12 & 0x1F) * (1 - dx) * dy +(c22 & 0x1F) * dx * dy);dst[y * dst_w + x] = (r << 11) | (g << 5) | b;}}}
}#endif /* HQX_FILTER_H */

display_config.h

/*===================================================================*/
/*                                                                   */
/*  display_config.h : Display optimization configuration            */
/*                                                                   */
/*  Configuration options for different display enhancement modes    */
/*                                                                   */
/*===================================================================*/#ifndef DISPLAY_CONFIG_H
#define DISPLAY_CONFIG_H// 显示优化模式选择
#define DISPLAY_MODE_NEAREST    0   // 最近邻插值(原始)
#define DISPLAY_MODE_BILINEAR   1   // 双线性插值
#define DISPLAY_MODE_HQ2X       2   // HQ2x滤镜
#define DISPLAY_MODE_HQ3X       3   // HQ3x滤镜
#define DISPLAY_MODE_XBRZ       4   // xBRZ滤镜
#define DISPLAY_MODE_SHARPEN    5   // 锐化滤镜// 默认显示模式
#ifndef DISPLAY_MODE
#define DISPLAY_MODE DISPLAY_MODE_HQ2X
#endif// 缩放选项
#define SCALING_INTEGER_ONLY    0   // 仅整数缩放
#define SCALING_FRACTIONAL      1   // 允许分数缩放
#define SCALING_ASPECT_RATIO    1   // 保持宽高比// 性能选项
#define USE_SSE2_OPTIMIZATION   1   // 使用SSE2优化
#define USE_NEON_OPTIMIZATION   0   // 使用NEON优化(ARM)
#define USE_THREADING           0   // 多线程渲染// 后处理选项
#define ENABLE_SHARPENING       1   // 锐化滤镜
#define ENABLE_GAMMA_CORRECTION 0   // 伽马校正
#define ENABLE_SCANLINES        0   // 扫描线效果
#define ENABLE_CURVATURE        0   // 屏幕曲率效果// 颜色增强
#define ENABLE_COLOR_CORRECTION 1   // 颜色校正
#define ENABLE_SATURATION_BOOST 0   // 饱和度增强// 边界处理
#define BORDER_MODE_STRETCH     0   // 拉伸填充
#define BORDER_MODE_BLACK       1   // 黑边
#define BORDER_MODE_PATTERN     2   // 图案填充// 默认边界模式
#define BORDER_MODE BORDER_MODE_BLACK// 屏幕尺寸限制
#define MIN_SCALE_FACTOR        1.0f
#define MAX_SCALE_FACTOR        4.0f// 性能阈值(低于此分辨率使用高质量算法)
#define HQ_THRESHOLD_WIDTH      400
#define HQ_THRESHOLD_HEIGHT     300// 调试选项
#define DEBUG_PERFORMANCE       0   // 性能统计
#define DEBUG_PIXEL_GRID        0   // 显示像素网格#endif /* DISPLAY_CONFIG_H */

InfoNES_System_Linux_complete.cpp

/*===================================================================*/
/*                                                                   */
/*  InfoNES_System_Linux_complete.cpp : Complete optimized display   */
/*                                                                   */
/*  Full-featured display optimization with multiple scaling modes   */
/*                                                                   */
/*===================================================================*/#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <math.h>
#include <time.h>#include "../InfoNES.h"
#include "../InfoNES_System.h"
#include "../InfoNES_pAPU.h"
#include "hqx_filter.h"
#include "display_config.h"// 编译器优化
#define likely(x)       __builtin_expect(!!(x), 1)
#define unlikely(x)     __builtin_expect(!!(x), 0)// 显示配置
static int display_mode = DISPLAY_MODE_HQ2X;
static int enable_sharpen = ENABLE_SHARPENING;
static int enable_scanlines = ENABLE_SCANLINES;
static int border_mode = BORDER_MODE_BLACK;// 帧缓冲相关
static int fb_fd = -1;
static unsigned char *fb_mem = NULL;
static int px_width, line_width, screen_width;
static int lcd_width, lcd_height;
static struct fb_var_screeninfo var;// 渲染缓冲区
static WORD *render_buffer = NULL;
static WORD *temp_buffer = NULL;
static WORD *hq_buffer = NULL;// 缩放因子
static float scale_x, scale_y;
static int offset_x, offset_y;
static int scaled_width, scaled_height;// 性能统计
static struct timespec last_frame_time;
static float fps_counter = 0.0f;
static int frame_count = 0;extern int InitJoypadInput(void);
extern int GetJoypadInput(void);// 颜色转换优化
static inline unsigned short RGB565_to_RGB888(unsigned short color) {unsigned char r = (color >> 11) & 0x1F;unsigned char g = (color >> 5) & 0x3F;unsigned char b = color & 0x1F;r = (r << 3) | (r >> 2);g = (g << 2) | (g >> 4);b = (b << 3) | (b >> 2);return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}// 高性能像素绘制
static inline void draw_pixel(int x, int y, unsigned short color) {if (x < 0 || x >= lcd_width || y < 0 || y >= lcd_height) return;unsigned short *pixel = (unsigned short *)(fb_mem + y * line_width + x * px_width);*pixel = color;
}// 边界填充
static void fill_borders(unsigned short color) {// 上边界for (int y = 0; y < offset_y; y++) {memset(fb_mem + y * line_width, 0, lcd_width * px_width);}// 下边界for (int y = offset_y + scaled_height; y < lcd_height; y++) {memset(fb_mem + y * line_width, 0, lcd_width * px_width);}// 左边界for (int y = offset_y; y < offset_y + scaled_height; y++) {memset(fb_mem + y * line_width, 0, offset_x * px_width);}// 右边界for (int y = offset_y; y < offset_y + scaled_height; y++) {memset(fb_mem + y * line_width + (offset_x + scaled_width) * px_width, 0, (lcd_width - offset_x - scaled_width) * px_width);}
}// 计算最佳缩放
static void calculate_scaling() {float screen_aspect = (float)lcd_width / lcd_height;float nes_aspect = (float)NES_DISP_WIDTH / NES_DISP_HEIGHT;if (SCALING_ASPECT_RATIO) {if (screen_aspect > nes_aspect) {// 屏幕更宽,以高度为准scaled_height = lcd_height;scaled_width = (int)(lcd_height * nes_aspect);} else {// 屏幕更高,以宽度为准scaled_width = lcd_width;scaled_height = (int)(lcd_width / nes_aspect);}} else {scaled_width = lcd_width;scaled_height = lcd_height;}// 整数缩放限制if (SCALING_INTEGER_ONLY) {int max_scale = (int)fminf(lcd_width / NES_DISP_WIDTH, lcd_height / NES_DISP_HEIGHT);max_scale = max_scale < 1 ? 1 : max_scale;scaled_width = NES_DISP_WIDTH * max_scale;scaled_height = NES_DISP_HEIGHT * max_scale;}offset_x = (lcd_width - scaled_width) / 2;offset_y = (lcd_height - scaled_height) / 2;scale_x = (float)scaled_width / NES_DISP_WIDTH;scale_y = (float)scaled_height / NES_DISP_HEIGHT;
}// 最近邻插值
static void render_nearest() {for (int y = 0; y < scaled_height; y++) {int src_y = (int)(y / scale_y);src_y = src_y < 0 ? 0 : (src_y >= NES_DISP_HEIGHT ? NES_DISP_HEIGHT - 1 : src_y);for (int x = 0; x < scaled_width; x++) {int src_x = (int)(x / scale_x);src_x = src_x < 0 ? 0 : (src_x >= NES_DISP_WIDTH ? NES_DISP_WIDTH - 1 : src_x);WORD color = WorkFrame[src_y * NES_DISP_WIDTH + src_x];draw_pixel(offset_x + x, offset_y + y, color);}}
}// 双线性插值
static void render_bilinear() {for (int y = 0; y < scaled_height; y++) {float src_y = y / scale_y;int y1 = (int)src_y;int y2 = (y1 + 1) < NES_DISP_HEIGHT ? (y1 + 1) : (NES_DISP_HEIGHT - 1);float dy = src_y - y1;for (int x = 0; x < scaled_width; x++) {float src_x = x / scale_x;int x1 = (int)src_x;int x2 = (x1 + 1) < NES_DISP_WIDTH ? (x1 + 1) : (NES_DISP_WIDTH - 1);float dx = src_x - x1;WORD c11 = WorkFrame[y1 * NES_DISP_WIDTH + x1];WORD c12 = WorkFrame[y2 * NES_DISP_WIDTH + x1];WORD c21 = WorkFrame[y1 * NES_DISP_WIDTH + x2];WORD c22 = WorkFrame[y2 * NES_DISP_WIDTH + x2];// 分解颜色分量unsigned char r11 = (c11 >> 11) & 0x1F;unsigned char g11 = (c11 >> 5) & 0x3F;unsigned char b11 = c11 & 0x1F;unsigned char r21 = (c21 >> 11) & 0x1F;unsigned char g21 = (c21 >> 5) & 0x3F;unsigned char b21 = c21 & 0x1F;unsigned char r12 = (c12 >> 11) & 0x1F;unsigned char g12 = (c12 >> 5) & 0x3F;unsigned char b12 = c12 & 0x1F;unsigned char r22 = (c22 >> 11) & 0x1F;unsigned char g22 = (c22 >> 5) & 0x3F;unsigned char b22 = c22 & 0x1F;// 双线性插值unsigned char r = (unsigned char)(r11 * (1 - dx) * (1 - dy) + r21 * dx * (1 - dy) +r12 * (1 - dx) * dy + r22 * dx * dy);unsigned char g = (unsigned char)(g11 * (1 - dx) * (1 - dy) + g21 * dx * (1 - dy) +g12 * (1 - dx) * dy + g22 * dx * dy);unsigned char b = (unsigned char)(b11 * (1 - dx) * (1 - dy) + b21 * dx * (1 - dy) +b12 * (1 - dx) * dy + b22 * dx * dy);WORD color = (r << 11) | (g << 5) | b;draw_pixel(offset_x + x, offset_y + y, color);}}
}// HQ2x滤镜渲染
static void render_hq2x() {if (scaled_width == NES_DISP_WIDTH * 2 && scaled_height == NES_DISP_HEIGHT * 2) {// 使用HQ2x算法hq2x_32(WorkFrame, hq_buffer, NES_DISP_WIDTH, NES_DISP_HEIGHT);// 复制到屏幕for (int y = 0; y < scaled_height; y++) {for (int x = 0; x < scaled_width; x++) {WORD color = hq_buffer[y * scaled_width + x];draw_pixel(offset_x + x, offset_y + y, color);}}} else {// 使用双线性插值作为后备render_bilinear();}
}// 锐化滤镜
static void apply_sharpening() {if (!enable_sharpen) return;// 简单的锐化核int kernel[3][3] = {{ 0, -1,  0},{-1,  5, -1},{ 0, -1,  0}};memcpy(temp_buffer, WorkFrame, NES_DISP_WIDTH * NES_DISP_HEIGHT * sizeof(WORD));for (int y = 1; y < NES_DISP_HEIGHT - 1; y++) {for (int x = 1; x < NES_DISP_WIDTH - 1; x++) {int r = 0, g = 0, b = 0;for (int ky = -1; ky <= 1; ky++) {for (int kx = -1; kx <= 1; kx++) {WORD pixel = temp_buffer[(y + ky) * NES_DISP_WIDTH + (x + kx)];int weight = kernel[ky+1][kx+1];r += ((pixel >> 11) & 0x1F) * weight;g += ((pixel >> 5) & 0x3F) * weight;b += (pixel & 0x1F) * weight;}}r = r < 0 ? 0 : (r > 31 ? 31 : r);g = g < 0 ? 0 : (g > 63 ? 63 : g);b = b < 0 ? 0 : (b > 31 ? 31 : b);WorkFrame[y * NES_DISP_WIDTH + x] = (r << 11) | (g << 5) | b;}}
}// 扫描线效果
static void apply_scanlines() {if (!enable_scanlines) return;for (int y = 0; y < NES_DISP_HEIGHT; y++) {if (y % 2) {for (int x = 0; x < NES_DISP_WIDTH; x++) {WORD pixel = WorkFrame[y * NES_DISP_WIDTH + x];WORD darkened = ((pixel & 0xF81F) >> 1) + ((pixel & 0x07E0) >> 1);WorkFrame[y * NES_DISP_WIDTH + x] = darkened;}}}
}// 主渲染函数
void InfoNES_LoadFrame() {if (fb_fd <= 0) return;// 性能统计开始clock_gettime(CLOCK_MONOTONIC, &last_frame_time);// 应用后处理效果apply_sharpening();apply_scanlines();// 清屏memset(fb_mem, 0, screen_width);// 计算缩放calculate_scaling();// 根据模式渲染switch (display_mode) {case DISPLAY_MODE_NEAREST:render_nearest();break;case DISPLAY_MODE_BILINEAR:render_bilinear();break;case DISPLAY_MODE_HQ2X:render_hq2x();break;default:render_bilinear();break;}// 填充边界fill_borders(0x0000);// 性能统计frame_count++;if (frame_count % 60 == 0) {struct timespec now;clock_gettime(CLOCK_MONOTONIC, &now);float elapsed = (now.tv_sec - last_frame_time.tv_sec) + (now.tv_nsec - last_frame_time.tv_nsec) / 1e9f;fps_counter = 60.0f / elapsed;}
}// 初始化显示系统
static int lcd_fb_init() {fb_fd = open("/dev/fb0", O_RDWR);if (fb_fd == -1) {printf("Error: Cannot open /dev/fb0\n");return -1;}if (ioctl(fb_fd, FBIOGET_VSCREENINFO, &var) == -1) {close(fb_fd);printf("Error: Cannot get framebuffer info\n");return -1;}px_width = var.bits_per_pixel / 8;line_width = var.xres * px_width;screen_width = var.yres * line_width;lcd_width = var.xres;lcd_height = var.yres;printf("Framebuffer: %dx%d, %d bpp\n", lcd_width, lcd_height, var.bits_per_pixel);fb_mem = (unsigned char *)mmap(NULL, screen_width, PROT_READ | PROT_WRITE, MAP_SHARED, fb_fd, 0);if (fb_mem == MAP_FAILED) {close(fb_fd);printf("Error: Cannot mmap framebuffer\n");return -1;}// 分配缓冲区render_buffer = (WORD *)malloc(lcd_width * lcd_height * sizeof(WORD));temp_buffer = (WORD *)malloc(NES_DISP_WIDTH * NES_DISP_HEIGHT * sizeof(WORD));hq_buffer = (WORD *)malloc(NES_DISP_WIDTH * 2 * NES_DISP_HEIGHT * 2 * sizeof(WORD));if (!render_buffer || !temp_buffer || !hq_buffer) {printf("Error: Cannot allocate memory\n");return -1;}memset(fb_mem, 0, screen_width);return 0;
}// 清理资源
static void cleanup_display() {if (render_buffer) free(render_buffer);if (temp_buffer) free(temp_buffer);if (hq_buffer) free(hq_buffer);if (fb_mem && fb_mem != MAP_FAILED) munmap(fb_mem, screen_width);if (fb_fd > 0) close(fb_fd);
}// 设置显示模式
void set_display_mode(int mode) {display_mode = mode;
}// 切换滤镜
void toggle_sharpening() {enable_sharpening = !enable_sharpening;
}// 切换扫描线
void toggle_scanlines() {enable_scanlines = !enable_scanlines;
}// 获取FPS
float get_fps() {return fps_counter;
}// 保持兼容性的函数
int make_zoom_tab() {return lcd_fb_init();
}// 声音系统(保持原有实现)
static snd_pcm_t *playback_handle;int InfoNES_SoundOpen(int samples_per_sync, int sample_rate) {unsigned int rate = sample_rate;snd_pcm_hw_params_t *hw_params;if (snd_pcm_open(&playback_handle, "default", SND_PCM_STREAM_PLAYBACK, 0) < 0) {printf("snd_pcm_open error\n");return -1;}snd_pcm_hw_params_malloc(&hw_params);snd_pcm_hw_params_any(playback_handle, hw_params);snd_pcm_hw_params_set_access(playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);snd_pcm_hw_params_set_format(playback_handle, hw_params, SND_PCM_FORMAT_U8);snd_pcm_hw_params_set_rate_near(playback_handle, hw_params, &rate, 0);snd_pcm_hw_params_set_channels(playback_handle, hw_params, 1);snd_pcm_hw_params(playback_handle, hw_params);snd_pcm_hw_params_free(hw_params);snd_pcm_prepare(playback_handle);return 1;
}void InfoNES_SoundClose() {if (playback_handle) {snd_pcm_close(playback_handle);playback_handle = NULL;}
}void InfoNES_SoundOutput(int samples, BYTE *wave1, BYTE *wave2, BYTE *wave3, BYTE *wave4, BYTE *wave5) {unsigned char *pcmBuf = (unsigned char *)malloc(samples);for (int i = 0; i < samples; i++) {pcmBuf[i] = (wave1[i] + wave2[i] + wave3[i] + wave4[i] + wave5[i]) / 5;}snd_pcm_writei(playback_handle, pcmBuf, samples);free(pcmBuf);
}// 其他系统函数(保持原有实现)
void InfoNES_Wait() { }
void InfoNES_MessageBox(char *pszMsg, ...) { printf("InfoNES: %s\n", pszMsg); }// 手柄输入(保持原有实现)
void InfoNES_PadState(DWORD *pdwPad1, DWORD *pdwPad2, DWORD *pdwSystem) {extern DWORD dwKeyPad1, dwKeyPad2, dwKeySystem;*pdwPad1 = dwKeyPad1;*pdwPad2 = dwKeyPad2;*pdwSystem = dwKeySystem;
}// 初始化函数
int main_init() {return lcd_fb_init();
}// 清理函数
void main_cleanup() {cleanup_display();
}

InfoNES_System_Linux_optimized.cpp

/*===================================================================*/
/*                                                                   */
/*  InfoNES_System_Linux_optimized.cpp : Linux optimized display     */
/*                                                                   */
/*  Optimized display with bilinear interpolation and anti-aliasing  */
/*                                                                   */
/*===================================================================*/#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <math.h>#include "../InfoNES.h"
#include "../InfoNES_System.h"
#include "../InfoNES_pAPU.h"//bool define
#define TRUE 1
#define FALSE 0/* lcd 操作相关 头文件 */
#include <alsa/asoundlib.h>static snd_pcm_t *playback_handle;
static int fb_fd = -1;
static unsigned char *fb_mem;
static int px_width;
static int line_width;
static int screen_width;
static int lcd_width;
static int lcd_height;
static struct fb_var_screeninfo var;/* 优化:使用浮点缩放表 */
static float *zoom_x_tab_float;
static float *zoom_y_tab_float;/* 抗锯齿和插值相关 */
static WORD *temp_buffer;  // 临时缓冲区用于高质量缩放
static int use_bilinear = 1;  // 默认使用双线性插值
static int use_sharpen = 1;   // 锐化滤镜extern int InitJoypadInput(void);
extern int GetJoypadInput(void);/* 颜色转换优化 */
static inline unsigned short RGB565_to_RGB888(unsigned short color) {unsigned char r = (color >> 11) & 0x1F;unsigned char g = (color >> 5) & 0x3F;unsigned char b = color & 0x1F;// 扩展到8位r = (r << 3) | (r >> 2);g = (g << 2) | (g >> 4);b = (b << 3) | (b >> 2);return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}static inline unsigned short RGB888_to_RGB565(unsigned char r, unsigned char g, unsigned char b) {return ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
}/* 双线性插值函数 */
static inline WORD bilinear_interpolate(WORD *src, int src_width, int src_height, float x, float y) {int x1 = (int)x;int y1 = (int)y;int x2 = (x1 + 1) < src_width ? (x1 + 1) : (src_width - 1);int y2 = (y1 + 1) < src_height ? (y1 + 1) : (src_height - 1);float dx = x - x1;float dy = y - y1;WORD c11 = src[y1 * src_width + x1];WORD c12 = src[y2 * src_width + x1];WORD c21 = src[y1 * src_width + x2];WORD c22 = src[y2 * src_width + x2];// 分解颜色分量unsigned char r11 = (c11 >> 11) & 0x1F;unsigned char g11 = (c11 >> 5) & 0x3F;unsigned char b11 = c11 & 0x1F;unsigned char r21 = (c21 >> 11) & 0x1F;unsigned char g21 = (c21 >> 5) & 0x3F;unsigned char b21 = c21 & 0x1F;unsigned char r12 = (c12 >> 11) & 0x1F;unsigned char g12 = (c12 >> 5) & 0x3F;unsigned char b12 = c12 & 0x1F;unsigned char r22 = (c22 >> 11) & 0x1F;unsigned char g22 = (c22 >> 5) & 0x3F;unsigned char b22 = c22 & 0x1F;// 双线性插值float r = r11 * (1 - dx) * (1 - dy) + r21 * dx * (1 - dy) +r12 * (1 - dx) * dy + r22 * dx * dy;float g = g11 * (1 - dx) * (1 - dy) + g21 * dx * (1 - dy) +g12 * (1 - dx) * dy + g22 * dx * dy;float b = b11 * (1 - dx) * (1 - dy) + b21 * dx * (1 - dy) +b12 * (1 - dx) * dy + b22 * dx * dy;return ((unsigned char)r << 11) | ((unsigned char)g << 5) | (unsigned char)b;
}/* 锐化滤镜 */
static inline WORD sharpen_pixel(WORD *src, int x, int y, int width, int height) {if (!use_sharpen || x <= 1 || y <= 1 || x >= width-2 || y >= height-2) {return src[y * width + x];}// 简单的锐化核int kernel[3][3] = {{ 0, -1,  0},{-1,  5, -1},{ 0, -1,  0}};int r = 0, g = 0, b = 0;for (int ky = -1; ky <= 1; ky++) {for (int kx = -1; kx <= 1; kx++) {WORD pixel = src[(y + ky) * width + (x + kx)];int weight = kernel[ky+1][kx+1];r += ((pixel >> 11) & 0x1F) * weight;g += ((pixel >> 5) & 0x3F) * weight;b += (pixel & 0x1F) * weight;}}// 限制范围r = r < 0 ? 0 : (r > 31 ? 31 : r);g = g < 0 ? 0 : (g > 63 ? 63 : g);b = b < 0 ? 0 : (b > 31 ? 31 : b);return (r << 11) | (g << 5) | b;
}static int lcd_fb_display_px(WORD color, int x, int y) {unsigned char *pen8;unsigned short *pen16;pen8 = (unsigned char *)(fb_mem + y * line_width + x * px_width);pen16 = (unsigned short *)pen8;*pen16 = color;return 0;
}static int lcd_fb_init() {fb_fd = open("/dev/fb0", O_RDWR);if (-1 == fb_fd) {printf("can't open /dev/fb0 \n");return -1;}if (-1 == ioctl(fb_fd, FBIOGET_VSCREENINFO, &var)) {close(fb_fd);printf("can't ioctl /dev/fb0 \n");return -1;}px_width = var.bits_per_pixel / 8;line_width = var.xres * px_width;screen_width = var.yres * line_width;lcd_width = var.xres;lcd_height = var.yres;printf("fb width:%d height:%d pixel:%d \n", lcd_width, lcd_height, px_width * 8);fb_mem = (unsigned char *)mmap(NULL, screen_width, PROT_READ | PROT_WRITE, MAP_SHARED, fb_fd, 0);if (fb_mem == (void *)-1) {close(fb_fd);printf("can't mmap /dev/fb0 \n");return -1;}memset(fb_mem, 0, screen_width);// 分配临时缓冲区temp_buffer = (WORD *)malloc(NES_DISP_WIDTH * NES_DISP_HEIGHT * sizeof(WORD));if (!temp_buffer) {printf("Failed to allocate temp buffer\n");return -1;}return 0;
}/* 生成浮点缩放表 */
int make_zoom_tab_float() {zoom_x_tab_float = (float *)malloc(sizeof(float) * lcd_width);if (NULL == zoom_x_tab_float) {printf("make zoom_x_tab_float error\n");return -1;}zoom_y_tab_float = (float *)malloc(sizeof(float) * lcd_height);if (NULL == zoom_y_tab_float) {printf("make zoom_y_tab_float error\n");return -1;}float scale_x = (float)NES_DISP_WIDTH / lcd_width;float scale_y = (float)NES_DISP_HEIGHT / lcd_height;for (int i = 0; i < lcd_width; i++) {zoom_x_tab_float[i] = i * scale_x;}for (int i = 0; i < lcd_height; i++) {zoom_y_tab_float[i] = i * scale_y;}return 1;
}/* 优化的帧渲染函数 */
void InfoNES_LoadFrame_Optimized() {if (fb_fd <= 0) return;// 首先将WorkFrame复制到临时缓冲区memcpy(temp_buffer, WorkFrame, NES_DISP_WIDTH * NES_DISP_HEIGHT * sizeof(WORD));// 应用锐化滤镜(可选)if (use_sharpen) {WORD *sharpened = (WORD *)malloc(NES_DISP_WIDTH * NES_DISP_HEIGHT * sizeof(WORD));for (int y = 0; y < NES_DISP_HEIGHT; y++) {for (int x = 0; x < NES_DISP_WIDTH; x++) {sharpened[y * NES_DISP_WIDTH + x] = sharpen_pixel(temp_buffer, x, y, NES_DISP_WIDTH, NES_DISP_HEIGHT);}}memcpy(temp_buffer, sharpened, NES_DISP_WIDTH * NES_DISP_HEIGHT * sizeof(WORD));free(sharpened);}// 使用高质量插值进行缩放for (int y = 0; y < lcd_height; y++) {float src_y = zoom_y_tab_float[y];for (int x = 0; x < lcd_width; x++) {float src_x = zoom_x_tab_float[x];WORD color;if (use_bilinear) {color = bilinear_interpolate(temp_buffer, NES_DISP_WIDTH, NES_DISP_HEIGHT, src_x, src_y);} else {// 最近邻插值作为后备int ix = (int)(src_x + 0.5f);int iy = (int)(src_y + 0.5f);ix = ix < 0 ? 0 : (ix >= NES_DISP_WIDTH ? NES_DISP_WIDTH - 1 : ix);iy = iy < 0 ? 0 : (iy >= NES_DISP_HEIGHT ? NES_DISP_HEIGHT - 1 : iy);color = temp_buffer[iy * NES_DISP_WIDTH + ix];}lcd_fb_display_px(color, x, y);}}
}/* 清理函数 */
void cleanup_optimized() {if (temp_buffer) {free(temp_buffer);temp_buffer = NULL;}if (zoom_x_tab_float) {free(zoom_x_tab_float);zoom_x_tab_float = NULL;}if (zoom_y_tab_float) {free(zoom_y_tab_float);zoom_y_tab_float = NULL;}
}/* 保持原有函数名,但使用优化版本 */
void InfoNES_LoadFrame() {InfoNES_LoadFrame_Optimized();
}/* 其他函数保持不变... */
http://www.dtcms.com/a/294301.html

相关文章:

  • 我用EV-21569-SOM评估来开发ADSP-21569(十三)-SigmaStudio Plus做开发(4)
  • Web前端开发:JavaScript遍历方法详解与对比
  • 安全防护-FCW
  • [HarmonyOS] HarmonyOS LiteOS-A 设备开发全流程指南
  • Linux第三天Linux基础命令(二)
  • 服务器对kaggle比赛的数据集下载
  • SAP-ABAP:SELECT语句验证字段和验证方法详解
  • OSPF路由协议——上
  • 28. 找出字符串中第一个匹配项的下标
  • vue3中el-table表头筛选
  • Flink 状态管理设计详解:StateBackend、State、RocksDB和Namespace
  • 谷粒商城篇章13--P340-P360--k8s/KubeSphere【高可用集群篇一】
  • 抖音集团基于Flink的亿级RPS实时计算优化实践
  • k8s pvc是否可绑定在多个pod上
  • 飞算JavaAI:从“工具革命”到“认知革命”——开发者如何借力AI重构技术竞争力
  • SpringBoot 内嵌 Tomcat 的相关配置
  • MySQL binlog解析
  • linux c语言进阶 - 线程,通信方式,安全方式(多并发)
  • Linux中常见的中英文单词对照表
  • 低代码中的统计模型是什么?有什么作用?
  • 第一二章知识点
  • 交换机的六种常见连接方式配置(基于华为eNSP)
  • 洛谷刷题7.23
  • 电子公章怎么弄到合同上?2025最新指南
  • Android NDK与JNI深度解析
  • 为什么本地ip记录成0.0.0.1
  • 观影《长安的荔枝》有感:SwiftUI 中像“荔枝转运”的关键技术及启示
  • SpringMVC快速入门之请求与响应
  • TODAY()-WEEKDAY(TODAY(),2)+1
  • BEVDet-4D 代码详细解析