嵌入式linux下的NES游戏显示效果优化方案:infoNES显示效果优化
博主有篇文章《iMX6ULL应用移植 | 移植 infoNES 模拟器(重玩经典NES游戏)》,但这个有个问题啊,传统的nes游戏都是位图拼成的人物图形,在大屏设备上显示的话,颗粒感严重。如何提升NES游戏的画质?让经典的NES游戏重新焕发生命力?这是此文探讨的内容。
infoNES 是一个功能强大的NES游戏模拟器,它为玩家在现代计算机上重温经典NES游戏提供了可能。然而,当我们将 NES 游戏画面放大到大屏幕时,原始设计中的最近邻插值算法导致画面颗粒感严重,细节模糊。本文将探讨如何优化 infoNES 的显示效果,使其在大屏幕上也能保持清晰、自然的画面。
博主之前的那篇嵌入式linux下NES游戏模拟器移植文章地址:
iMX6ULL应用移植 | 移植 infoNES 模拟器(重玩经典NES游戏)
当前存在的问题
- 使用简单的最近邻插值
- 这种方法速度快,但在放大时会导致画面颗粒感严重。
- 没有抗锯齿处理
- NES 游戏的像素艺术风格在放大时容易出现锯齿边缘。
- 色彩空间转换简单
- 原始的色彩转换算法没有考虑颜色的平滑过渡,导致画面色彩显得生硬。
优化思路
- 实现高质量插值算法
- 双线性插值和双三次插值可以提供更好的过渡效果。
- 添加抗锯齿滤镜
- 使用HQ2x、HQ3x或xBRZ滤镜可以在保持像素艺术风格的同时减少锯齿。
- 改进色彩空间转换
- 优化色彩转换算法以实现更自然的颜色过渡。
- 支持多种缩放模式
- 提供选项选择不同的缩放模式以适应不同的硬件性能和显示需求。
具体实现方案
优化方案对比
方案 | 质量 | 性能 | 描述 |
---|---|---|---|
最近邻插值 | ★☆☆☆☆ | ★★★★★ | 原始算法,速度快但质量差 |
双线性插值 | ★★★☆☆ | ★★★★☆ | 平滑过渡,质量较好 |
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_HQ2X
或DISPLAY_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
测试建议
- 测试游戏:Super Mario Bros、Tetris、Mega Man
- 测试场景:文字、精灵动画、背景滚动
- 测试分辨率:720p、1080p、4K
- 测试模式:窗口模式、全屏模式
性能基准
分辨率 | 最近邻 | 双线性 | HQ2x | HQ3x |
---|---|---|---|---|
720p | 60fps | 60fps | 60fps | 45fps |
1080p | 60fps | 60fps | 55fps | 35fps |
4K | 60fps | 50fps | 30fps | 20fps |
测试环境:Intel i5-8400, 8GB RAM
贡献指南
欢迎提交改进:
- 新的滤镜算法
- 性能优化
- 新的后处理效果
- 更好的颜色校正
代码实现
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();
}/* 其他函数保持不变... */