数码相框-主界面MainPage显存管理
打开驱动程序得到显存。
写显示操作结构体
#ifndef _DISP_MANAGER_H
#define _DISP_MANAGER_H
// 包含显示操作相关的定义和结构体
#include <pic_operation.h>
// 显示操作结构体,定义了显示设备的操作接口
typedef struct DispOpr {
char *name; // 设备名称
int iXres; // 水平分辨率
int iYres; // 垂直分辨率
int iBpp; // 每像素位数
unsigned char *pucDispMem; // 显示内存指针
int (*DeviceInit)(void); // 设备初始化函数
int (*ShowPixel)(int iPenX, int iPenY, unsigned int dwColor); // 显示单个像素点
int (*CleanScreen)(unsigned int dwBackColor); // 清屏函数
int (*ShowPage)(PT_VideoMem ptVideoMem); // 显示一页图像
struct DispOpr *ptNext; // 指向下一个显示设备操作的指针
}T_DispOpr, *PT_DispOpr;
// 视频内存状态枚举,标识视频内存的不同状态
typedef enum {
VMS_FREE = 0, // 未使用
VMS_USED_FOR_PREPARE, // 给子线程使用
VMS_USED_FOR_CUR, // 当前主线程使用
}E_VideoMemState;
// 图片状态枚举,标识图片的不同生成状态
typedef enum {
PS_BLANK = 0, // 为空
PS_GENERATING, // 正在生成中
PS_GENERATED, // 已生成
}E_PicState;
// 视频内存结构体,定义了视频内存的相关信息和操作
typedef struct VideoMem {
int iID; // 视频内存ID
int bDevFrameBuffer; // 是否为设备帧缓冲
E_VideoMemState eVideoMemState; // 视频内存状态
E_PicState ePicState; // 图片状态
T_PixelDatas tPixelDatas; // 像素数据
struct VideoMem *ptNext; // 指向下一个视频内存的指针
}T_VideoMem, *PT_VideoMem;
// 注册显示操作
int RegisterDispOpr(PT_DispOpr ptDispOpr);
// 展示所有已注册的显示操作
void ShowDispOpr(void);
// 初始化显示系统
int DisplayInit(void);
// 选择默认的显示设备
void SelectDefaultDispDev(char *name);
// 获取当前显示设备的分辨率和位深度
int GetDispResolution(int *piXres, int *piYres, int *piBpp);
// 初始化帧缓冲
int FBInit(void);
#endif /* _DISP_MANAGER_H */
分配显存
/*
* 分配视频内存
* 参数:
* iNum: 需要分配的视频内存块的数量(不包括设备本身的framebuffer)
* 返回值:
* 成功返回0,失败返回-1
*/
int AllocVideoMem(int iNum)
{
int i;
int iXres; // 显示分辨率的宽度
int iYres; // 显示分辨率的高度
int iBpp; // 每像素位数
int iVMSize; // 视频内存大小
int iLineBytes; // 每行字节
PT_VideoMem ptNew; // 新的视频内存结构体指针
// 获取显示分辨率和每像素位数
GetDispResolution(&iXres, &iYres, &iBpp);
// 计算视频内存大小和每行字节
iVMSize = iXres * iYres * iBpp / 8;
iLineBytes = iXres * iBpp / 8;
/* 分配内存给新的视频内存结构体,并设置为设备本身的framebuffer */
ptNew = malloc(sizeof(T_VideoMem));
if (ptNew == NULL)
{
return -1;
}
ptNew->tPixelDatas.aucPixelDatas = g_ptDefaultDispOpr->pucDispMem;
// 初始化新分配的视频内存块信息
ptNew->iID = 0;
ptNew->bDevFrameBuffer = 1;//是帧缓冲区
ptNew->eVideoMemState = VMS_FREE;
ptNew->ePicState = PS_BLANK;
ptNew->tPixelDatas.iWidth = iXres;
ptNew->tPixelDatas.iHeight = iYres;
ptNew->tPixelDatas.iBpp = iBpp;
ptNew->tPixelDatas.iLineBytes = iLineBytes;
ptNew->tPixelDatas.iTotalBytes = iVMSize;
// 如果需要,更改视频内存状态为当前使用
if (iNum != 0)
{
ptNew->eVideoMemState = VMS_USED_FOR_CUR;//确保获取getVideoMem显存时,不会获得fb。
}
/* 将新的视频内存块链接到链表头部 */
ptNew->ptNext = g_ptVideoMemHead;
g_ptVideoMemHead = ptNew;
// 为额外请求的视频内存块重复分配和初始化
for (i = 0; i < iNum; i++)
{
ptNew = malloc(sizeof(T_VideoMem) + iVMSize);
if (ptNew == NULL)
{
return -1;
}
ptNew->tPixelDatas.aucPixelDatas = (unsigned char *)(ptNew + 1);
// 初始化新分配的视频内存块信息
ptNew->iID = 0;
ptNew->bDevFrameBuffer = 0;
ptNew->eVideoMemState = VMS_FREE;
ptNew->ePicState = PS_BLANK;
ptNew->tPixelDatas.iWidth = iXres;
ptNew->tPixelDatas.iHeight = iYres;
ptNew->tPixelDatas.iBpp = iBpp;
ptNew->tPixelDatas.iLineBytes = iLineBytes;
ptNew->tPixelDatas.iTotalBytes = iVMSize;
/* 将新的视频内存块链接到链表头部 */
ptNew->ptNext = g_ptVideoMemHead;
g_ptVideoMemHead = ptNew;
}
return 0;
}
虽然设备本身有固定的 framebuffer,但应用程序可能需要动态管理额外的视频内存资源以满足特定需求。数码相框可能需要同时处理多张图片的加载、预览、编辑或动画效果,这些操作可能超出设备原生 framebuffer 的容量。通过 AllocVideoMem
函数,应用程序可以按需分配额外的视频内存块,用于存放这些额外的图像数据,而不必局限于设备固有的 framebuffer 大小。
AllocVideoMem
允许为每个任务分配专属的视频内存块,并通过链表结构进行组织管理,便于任务间的切换和同步。
AllocVideoMem
函数不仅分配内存,还初始化了与视频内存块关联的各种状态信息(如 eVideoMemState
、ePicState
等)。这些状态信息有助于应用程序跟踪内存块的使用情况(如是否正在被当前画面使用、是否为空白等),便于进行高效的内存调度和更新策略。
即使设备初始设计时提供了足够的 framebuffer 空间,未来可能因为软件升级、功能增强或用户需求变化而需要更多视频内存。使用这样的函数可以灵活应对未来可能的变化,无需修改大量现有代码,只需调整内存分配策略或参数即可。
头插法的链表操作:
在函数中,每次新分配的视频内存块(如 ptNew
)被插入到链表头部的操作如下:
ptNew->ptNext = g_ptVideoMemHead;
g_ptVideoMemHead = ptNew;
这里,首先将新节点的 ptNext
指针指向当前链表头节点(g_ptVideoMemHead
),然后将全局链表头指针 g_ptVideoMemHead
更新为新节点 ptNew
。这种操作方式确保新节点始终成为链表新的第一个元素,即头插法。
获得显存
/**
* 获取视频内存块
*
* 本函数用于从视频内存链表中获取一个合适的视频内存块。首先尝试找到一个空闲的且ID匹配的内存块,
* 如果没有找到,则尝试找到任何一个空闲的内存块。如果找到,会将其状态标记为使用中。ptTmp->ePicState = PS_BLANK;为空白,是为了后续更新照片。
*
* @param iID 要求的视频内存块的ID。如果为匹配ID,则优先使用该ID的内存块。
* @param bCur 标识当前视频帧还是下一帧视频帧需要使用的内存,true表示当前帧,false表示下一帧。
* @return 返回获取到的视频内存块的指针,如果没有找到可用的内存块则返回NULL。
*/
PT_VideoMem GetVideoMem(int iID, int bCur)
{
PT_VideoMem ptTmp = g_ptVideoMemHead;
/* 1. 优先: 取出空闲的、ID相同的videomem */
while (ptTmp)
{
if ((ptTmp->eVideoMemState == VMS_FREE) && (ptTmp>iID == iID))
{
ptTmp->eVideoMemState = bCur ? VMS_USED_FOR_CUR : VMS_USED_FOR_PREPARE;//分配一个空闲且ID相同的视频内存块时,可以认为其图片状态默认为PS_BLANK,无需额外显式设置。
return ptTmp;
}
ptTmp = ptTmp->ptNext;
}
/* 2. 优先: 取出任意一个空闲videomem */
ptTmp = g_ptVideoMemHead;
while (ptTmp)
{
if (ptTmp->eVideoMemState == VMS_FREE)
{
ptTmp->iID = iID;
ptTmp->ePicState = PS_BLANK;
ptTmp->eVideoMemState = bCur ? VMS_USED_FOR_CUR : VMS_USED_FOR_PREPARE;
return ptTmp;
}
ptTmp = ptTmp->ptNext;
}
return NULL;
}
释放显存
/**
* 将视频内存状态设置为可用
*
* @param ptVideoMem 指向视频内存结构体的指针,该结构体描述了视频内存的状态。
*
* 本函数用于将指定的视频内存状态设置为可用(VMS_FREE),通常在释放视频内存资源时调用。
*/
void PutVideoMem(PT_VideoMem ptVideoMem)
{
ptVideoMem->eVideoMemState = VMS_FREE; // 设置视频内存状态为可用
}
显示主页面
写一个生成 ID 的宏:
#define ID(name) (int(name[0]) + int(name[1]) + int(name[2]) + int(name[3]))
将 ptVideoMem 中的数据刷新到设备显示缓冲区:
/**
* 将视频内存中的数据刷新到设备显示缓冲区。
*
* @param ptVideoMem 指向视频内存结构体的指针,该结构体包含要显示的视频帧数据。
*
* 此函数首先检查视频内存中的数据是否已经映射到设备帧缓冲区。如果没有,则通过调用
* 默认显示设备的ShowPage函数将视频数据显示到屏幕上。
*/
void FlushVideoMemToDev(PT_VideoMem ptVideoMem)
{
// 如果视频内存没有映射到设备帧缓冲区,则调用显示设备的显示函数
if (!ptVideoMem->bDevFrameBuffer)
{
GetDefaultDispDev()->ShowPage(ptVideoMem);
}
}
VideoMem 内存与设备显示缓冲区之间存在映射关系(即 ptVideoMem->bDevFrameBuffer
为真),则可能无需通过函数调用复制数据,而是可以直接访问同一块物理内存,实现零拷贝渲染。然而,对于未映射的情况,必须通过特定接口(如 GetDefaultDispDev()->ShowPage
)将视频内存数据推送到显示设备,以实现画面更新。FlushVideoMemToDev
函数根据实际情况选择合适的方法,保证了数据传输的高效性。
显示页面到帧缓冲区:
/**
* @brief 显示页面到帧缓冲区
*
* 该函数用于将指定的视频内存内容复制到显示内存中,以更新显示内容。
*
* @param ptVideoMem 指向包含视频内存数据结构的指针,该数据结构包含了要显示的像素数据。
* @return int 函数没有返回值。
*/
static int FBShowPage(PT_VideoMem ptVideoMem)
{
// 将视频内存中的像素数据复制到显示内存
memcpy(g_tFBOpr->pucDispMem, ptVideoMem->tPixelDatas.aucPixelDatas, ptVideoMem->tPixelDatas.iTotalBytes);
}