WinAPI:HBITMAP屏幕显示异常求助——无法实现预期闪烁效果
问题描述
编写了一个以60FPS频率调用的渲染函数,目标是切换显示两张HBITMAP图片,实现每秒各渲染30次的交替闪烁效果。但实际运行时,一张图片会长时间显示,另一张无法正常渲染,一段时间后才切换到另一张图片长时间显示,达不到预期的闪烁效果。注:已创建两个HDC,为未来支持多图显示做准备。
相关代码
渲染函数
void Game::render() { static HBITMAP hBitMap1 = (HBITMAP)LoadImage(NULL, L"..\\images\\player01.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); static HBITMAP hBitMap2 = (HBITMAP)LoadImage(NULL, L"..\\images\\player02.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); HDC memDC = CreateCompatibleDC(dc); HDC memDC1 = CreateCompatibleDC(dc); HBITMAP memBM = CreateCompatibleBitmap(dc, windowRect.right, windowRect.bottom); SelectObject(memDC, memBM); static int image_changing = 0; int x, y; HBITMAP hBitMap; if (image_changing < 1) { hBitMap = hBitMap1; x = 200; y = 200; image_changing++; } else { hBitMap = hBitMap2; x = 200; y = 50; image_changing = 0; } HBITMAP hbit = (HBITMAP)SelectObject(memDC1, hBitMap); BitBlt(memDC, x, y, IMAGE_SIZE_X, IMAGE_SIZE_Y, memDC1, 0, 0, SRCCOPY); SelectObject(memDC1, hbit); BitBlt(dc, 0, 0, windowRect.right, windowRect.bottom, memDC, 0, 0, SRCCOPY); DeleteDC(memDC1); DeleteDC(memDC); DeleteObject(memBM); }
渲染循环
void Game::run() { // Initialize previous time to current time DWORD prevTime = timeGetTime(); // Initialize frame counter and FPS variables int frameCount = 0; // Initialize timer variables DWORD timer = timeGetTime(); int elapsedTime = 0; MSG msg = { }; while (true) { if (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { // Exit if (msg.message == WM_QUIT) break; // Add Bullets if (msg.message == WM_LBUTTONDOWN) if (player->getPlayerCanFire()) { addBullet(LOWORD(msg.lParam), HIWORD(msg.lParam)); player->setPlayerCanFire(FALSE); player->setPlayerFireElapsedTime(0); } TranslateMessage(&msg); DispatchMessage(&msg); } else { // Get current time DWORD currentTime = timeGetTime(); // Calculate difference between current time and previous time DWORD deltaTime = currentTime - prevTime; // If difference is less than desired time interval, sleep for the difference if (deltaTime < FRAME_INTERVAL) { Sleep(FRAME_INTERVAL - deltaTime); } prevTime = currentTime; // convert to seconds update(deltaTime / 1000.0); render(); // Increment frame counter frameCount++; // Check if timer interval has elapsed elapsedTime = currentTime - timer; if (elapsedTime >= 1000) { // Calculate FPS fps = frameCount * 1000 / elapsedTime; // Reset timer and frame counter timer = currentTime; frameCount = 0; } } } }
问题原因分析
- 视觉暂留效应:当前逻辑每帧切换一次图片(60FPS下每秒切换60次),每张图仅显示约16ms,远低于人类视觉暂留时间(约50ms),导致眼睛无法分辨单独的帧,看起来像是两张图叠加而非闪烁。
- GDI资源频繁创建销毁:每次
render都创建/销毁memDC、memDC1和memBM,会导致性能开销和资源波动,可能引发帧率不稳定,进一步影响视觉效果。 - 渲染无清屏操作:渲染前未清空内存DC,前一帧的图像会残留,导致两张图叠加,模糊切换效果。
- 时间计算逻辑错误:
prevTime在Sleep前更新,未考虑Sleep实际占用的时间,导致deltaTime计算不准,帧率控制失效,可能出现连续多帧显示同一张图的情况。
解决方案
1. 调整图片切换频率
根据视觉感知需求,修改切换逻辑,设置固定时间间隔让眼睛能分辨切换动作,比如每100ms切换一次:
static DWORD lastSwitchTime = timeGetTime(); const DWORD SWITCH_INTERVAL = 100; // 每100ms切换一次,每秒各5次 DWORD currentTime = timeGetTime(); static bool image_changing = false; HBITMAP hBitMap; int x, y; if (currentTime - lastSwitchTime >= SWITCH_INTERVAL) { image_changing = !image_changing; lastSwitchTime = currentTime; } if (image_changing) { hBitMap = hBitMap1; x = 200; y = 200; } else { hBitMap = hBitMap2; x = 200; y = 50; }
2. 复用GDI资源
将memDC、memDC1、memBM改为成员变量,初始化一次避免频繁创建:
// 在Game类中声明成员变量 HDC m_memDC = nullptr; HDC m_memDC1 = nullptr; HBITMAP m_memBM = nullptr; // 初始化(比如在构造函数或init方法中) void Game::init() { m_memDC = CreateCompatibleDC(dc); m_memDC1 = CreateCompatibleDC(dc); m_memBM = CreateCompatibleBitmap(dc, windowRect.right, windowRect.bottom); SelectObject(m_memDC, m_memBM); } // 渲染函数中复用资源 void Game::render() { // ... 省略资源创建代码 HBITMAP hbit = (HBITMAP)SelectObject(m_memDC1, hBitMap); BitBlt(m_memDC, x, y, IMAGE_SIZE_X, IMAGE_SIZE_Y, m_memDC1, 0, 0, SRCCOPY); SelectObject(m_memDC1, hbit); BitBlt(dc, 0, 0, windowRect.right, windowRect.bottom, m_memDC, 0, 0, SRCCOPY); } // 析构函数中释放资源 Game::~Game() { DeleteDC(m_memDC1); DeleteDC(m_memDC); DeleteObject(m_memBM); }
3. 添加清屏操作
在渲染前清空内存DC,避免图像残留:
SelectObject(memDC, memBM); // 用白色清空内存DC(可替换为窗口背景色) PatBlt(memDC, 0, 0, windowRect.right, windowRect.bottom, WHITENESS);
4. 修正帧率控制逻辑
调整prevTime的更新时机,确保deltaTime计算准确:
// 替换原时间计算部分 DWORD currentTime = timeGetTime(); DWORD deltaTime = currentTime - prevTime; if (deltaTime < FRAME_INTERVAL) { Sleep(FRAME_INTERVAL - deltaTime); // Sleep后重新获取当前时间 currentTime = timeGetTime(); } deltaTime = currentTime - prevTime; prevTime = currentTime; update(deltaTime / 1000.0); render();
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

