Tối ưu hóa hiệu suất đồ họa trên WinCE 5.0 bằng kỹ thuật Direct Screen Access

Trong quá trình phát triển ứng dụng nhúng trên nền tảng iMX31 chạy Windows CE 5.0, vấn đề hiệu suất hiển thị là một thách thức lớn. Cụ thể, trên màn hình có độ phân giải 800x480, việc sử dụng hàm BitBlt() để vẽ lại toàn bộ màn hình tiêu tốn khoảng 120ms, trong khi cùng một thao tác trên phần cứng khác (như Prima) chỉ mất dưới 20ms. Để giải quyết vấn đề này, chúng ta cần tiếp cận trực tiếp vào bộ nhớ màn hình (Direct Screen Access) thay vì phụ thuộc hoàn toàn vào các hàm vẽ GDI chuẩn.

Để thực hiện ghi trực tiếp lên màn hình, hai hàm API chính được sử dụng là CreateDC(TEXT("DISPLAY"),...) để lấy ngữ cảnh thiết bị màn hình và CreateDIBSection() để tạo một vùng nhớ bitmap có thể truy cập trực tiếp dữ liệu điểm ảnh (pixel data).

1. Cơ chế ghi trực tiếp vào bộ nhớ đệm khung hình (Frame Buffer)

Hàm CreateDC tương đối đơn giản, tuy nhiên CreateDIBSection đòi hỏi cài đặt phức tạp hơn để thiết lập kết nối giữa đối tượng bitmap, thiết bị ngữ cảnh (DC) và con trỏ vùng nhớ. Ví dụ dưới đây minh họa cách khởi tạo và sao chép dữ liệu đồ họa trực tiếp vào địa chỉ bộ nhớ vật lý của màn hình (gpbFrameAddress).

void DirectScreenWrite(HDC hdcSource, void* pVideoMemory, int nWidth, int nHeight)
{
    BITMAPINFO bitmapInfo;
    ZeroMemory(&bitmapInfo, sizeof(BITMAPINFO));
    
    // Cấu trúc BITMAPINFOHEADER định dạng ảnh 16bit (565)
    bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bitmapInfo.bmiHeader.biWidth = nWidth;
    bitmapInfo.bmiHeader.biHeight = -nHeight; // Sử dụng chiều âm để tạo DIB top-down
    bitmapInfo.bmiHeader.biPlanes = 1;
    bitmapInfo.bmiHeader.biBitCount = 16;
    bitmapInfo.bmiHeader.biCompression = BI_RGB;

    HDC hdcMem = CreateCompatibleDC(hdcSource);
    BYTE* pPixelBits = NULL;

    // Tạo đối tượng DIB Section cho phép truy xuất trực tiếp mảng byte pPixelBits
    HBITMAP hDibSection = CreateDIBSection(
        hdcMem, 
        &bitmapInfo, 
        DIB_RGB_COLORS, 
        (void**)&pPixelBits, 
        NULL, 
        0
    );

    if (hDibSection != NULL && pPixelBits != NULL) {
        HGDIOBJ hOldObj = SelectObject(hdcMem, hDibSection);

        // Chuyển dữ liệu từ nguồn (dcSource) vào bộ nhớ DIB Section
        BitBlt(hdcMem, 0, 0, nWidth, nHeight, hdcSource, 0, 0, SRCCOPY);

        // Sao chép trực tiếp dữ liệu pixel từ DIB Section vào địa chỉ bộ nhớ màn hình
        // 2 bytes = 16 bits mỗi pixel
        CopyMemory(pVideoMemory, pPixelBits, nWidth * nHeight * 2);

        // Khôi phục và dọn dẹp tài nguyên
        SelectObject(hdcMem, hOldObj);
        DeleteObject(hDibSection);
    }

    DeleteDC(hdcMem);
}

2. Tiện ích chụp và lưu màn hình (Screen Capture)

Dưới đây là đoạn mã đã được tối ưu hóa để thực hiện chức năng chụp màn hình và lưu lại dưới dạng file ảnh. Đoạn mã này bao gồm việc lấy ngữ cảnh màn hình, tạo bitmap tương thích và xử lý việc ghi dữ liệu ra file BMP với các định dạng màu sắc khác nhau (16-bit, 24-bit, 32-bit).

#include <windows.h>

// Hàm chụp toàn bộ màn hình trả về handle bitmap
HBITMAP CaptureScreenBitmap(int* pnResultWidth, int* pnResultHeight)
{
    HDC hdcDesktop = CreateDC(_T("DISPLAY"), NULL, NULL, NULL);
    HDC hdcMemory = CreateCompatibleDC(hdcDesktop);

    int nScreenWidth = GetDeviceCaps(hdcDesktop, HORZRES);
    int nScreenHeight = GetDeviceCaps(hdcDesktop, VERTRES);

    if (pnResultWidth) *pnResultWidth = nScreenWidth;
    if (pnResultHeight) *pnResultHeight = nScreenHeight;

    // Tạo bitmap tương thích với màn hình
    HBITMAP hCaptureBitmap = CreateCompatibleBitmap(hdcDesktop, nScreenWidth, nScreenHeight);
    HGDIOBJ hOldBitmap = SelectObject(hdcMemory, hCaptureBitmap);

    // Thực hiện sao chép pixel từ màn hình thật vào memory DC
    BitBlt(hdcMemory, 0, 0, nScreenWidth, nScreenHeight, hdcDesktop, 0, 0, SRCCOPY);

    // Hoàn tất và dọn dẹp
    SelectObject(hdcMemory, hOldBitmap);
    DeleteDC(hdcMemory);
    DeleteDC(hdcDesktop);

    return hCaptureBitmap;
}

// Hàm lưu bitmap ra file với độ sâu màu mong muốn (16, 24, hoặc 32)
BOOL SaveBitmapToFile(HBITMAP hBitmap, LPCTSTR lpszFilePath, WORD wBitsPerPixel)
{
    HDC hdcDisplay = CreateDC(TEXT("DISPLAY"), NULL, NULL, NULL);
    HDC hdcOffscreen = CreateCompatibleDC(hdcDisplay);
    SelectObject(hdcOffscreen, hBitmap);

    BITMAP bmpInfo;
    GetObject(hBitmap, sizeof(BITMAP), &bmpInfo);

    HANDLE hFile = CreateFile(lpszFilePath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);

    if (hFile == INVALID_HANDLE_VALUE) {
        DeleteDC(hdcOffscreen);
        DeleteDC(hdcDisplay);
        return FALSE;
    }

    // Tính toán kích thước file và header
    DWORD dwPixelSize = (wBitsPerPixel / 8) * bmpInfo.bmWidth * bmpInfo.bmHeight;
    
    BITMAPFILEHEADER bmfHeader;
    ZeroMemory(&bmfHeader, sizeof(BITMAPFILEHEADER));
    bmfHeader.bfType = 0x4D42; // 'BM'
    bmfHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
    bmfHeader.bfSize = bmfHeader.bfOffBits + dwPixelSize;

    BITMAPINFOHEADER bmiHeader;
    ZeroMemory(&bmiHeader, sizeof(BITMAPINFOHEADER));
    bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmiHeader.biWidth = bmpInfo.bmWidth;
    bmiHeader.biHeight = bmpInfo.bmHeight;
    bmiHeader.biPlanes = 1;
    bmiHeader.biBitCount = wBitsPerPixel;
    if (wBitsPerPixel == 32) {
        bmiHeader.biCompression = BI_BITFIELDS; 
        // Thiết lập mask màu nếu cần thiết cho 32bit
    }

    DWORD dwBytesWritten = 0;
    WriteFile(hFile, &bmfHeader, sizeof(BITMAPFILEHEADER), &dwBytesWritten, NULL);
    WriteFile(hFile, &bmiHeader, sizeof(BITMAPINFOHEADER), &dwBytesWritten, NULL);

    // Sử dụng CreateDIBSection để lấy raw data từ bitmap nguồn
    BITMAPINFO biDib;
    ZeroMemory(&biDib, sizeof(BITMAPINFO));
    biDib.bmiHeader = bmiHeader;
    
    // Nếu là 32-bit và dùng BI_BITFIELDS, cần cấu hình màu ở đây
    if (wBitsPerPixel == 32) {
        biDib.bmiColors[0].rgbRed = 0xFF;
        biDib.bmiColors[0].rgbGreen = 0xFF;
        biDib.bmiColors[0].rgbBlue = 0xFF;
    }

    BYTE* pImageData = new BYTE[dwPixelSize];
    HDC hdcDib = CreateCompatibleDC(hdcDisplay);
    
    HBITMAP hDibBitmap = CreateDIBSection(hdcDib, &biDib, DIB_RGB_COLORS, (void**)&pImageData, NULL, 0);
    
    if (hDibBitmap != NULL) {
        HGDIOBJ hOld = SelectObject(hdcDib, hDibBitmap);
        // Copy từ bitmap nguồn (hBitmap) vào DIB Section mới tạo
        BitBlt(hdcDib, 0, 0, bmpInfo.bmWidth, bmpInfo.bmHeight, hdcOffscreen, 0, 0, SRCCOPY);
        
        // Ghi dữ liệu pixel ra file
        WriteFile(hFile, pImageData, dwPixelSize, &dwBytesWritten, NULL);
        
        SelectObject(hdcDib, hOld);
        DeleteObject(hDibBitmap);
    }

    delete[] pImageData;
    DeleteDC(hdcDib);
    DeleteDC(hdcOffscreen);
    DeleteDC(hdcDisplay);
    CloseHandle(hFile);

    return TRUE;
}

Đăng vào ngày 26 tháng 6 lúc 19:29