Search code examples
c++directxpalette

DirectX7 SetPalette always failed


I'm learning DirectX, I want to Bind a palette to the PrimarySurface, but the process always failed. I give my code below:

#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
#define SCREEN_BPP 32
#define MAX_COLORS_PALETTE 256

#define DDRAW_INIT_STRUCT(ddstruct) { memset(&ddstruct, 0, sizeof(ddstruct)); ddstruct.dwSize = sizeof(ddstruct); }

LPDIRECTDRAW7 lpdd = NULL;
LPDIRECTDRAWSURFACE7 lpddPrimarySurface = NULL;
LPDIRECTDRAWPALETTE lpddPalette = NULL;
PALETTEENTRY palette[256];

// Omit the unneccessary content

int GameInit()
{
    if (FAILED(DirectDrawCreateEx(NULL, (void**)&lpdd, IID_IDirectDraw7, NULL)))
        return 0;
    if (FAILED(lpdd->SetCooperativeLevel(g_GameHwnd, DDSCL_FULLSCREEN | DDSCL_ALLOWMODEX | DDSCL_EXCLUSIVE | DDSCL_ALLOWREBOOT)))
        return 0;
    if (FAILED(lpdd->SetDisplayMode(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, 0, 0)))
        return 0;

    DDRAW_INIT_STRUCT(ddsd);
    ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
    ddsd.dwBackBufferCount = 1;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_COMPLEX | DDSCAPS_FLIP;

    if (FAILED(lpdd->CreateSurface(&ddsd, &lpddPrimarySurface, NULL)))
        return 0;

    ddsd.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER;
    if (FAILED(lpddPrimarySurface->GetAttachedSurface(&ddsd.ddsCaps, &lpddBackSurface)))
        return 0;

    memset(palette, 0, MAX_COLORS_PALETTE * sizeof(PALETTEENTRY));

    for (int index = 0; index < MAX_COLORS_PALETTE; index++)
    {
        if (index < 64)
            palette[index].peRed = index * 4;
        else if (index >= 64 && index < 128)
            palette[index].peGreen = (index - 64) * 4;
        else if (index >= 128 && index < 192)
            palette[index].peBlue = (index - 128) * 4;
        else if (index >= 192 && index < 256)
            palette[index].peRed = palette[index].peGreen = palette[index].peBlue = (index - 192) * 4;

        palette[index].peFlags = PC_NOCOLLAPSE;
    }

    if (FAILED(lpdd->CreatePalette(DDPCAPS_8BIT | DDPCAPS_ALLOW256 | DDPCAPS_INITIALIZE, palette, &lpddPalette, NULL)))
        return 0;

    **// I always failed to set palette to primary surface here....**
    if (FAILED(lpddPrimarySurface->SetPalette(lpddPalette)))
    {
        MessageBox(NULL, "Failed", NULL, MB_OK);
        return 0;
    }

    DDRAW_INIT_STRUCT(ddsd);

    if (FAILED(lpddBackSurface->Lock(NULL, &ddsd, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT, NULL)))
        return 0;

    UINT *videoBuffer = (UINT*)ddsd.lpSurface;

    for (int y = 0; y < SCREEN_HEIGHT; y++)
    {
        memset((void*)videoBuffer, y % 256, SCREEN_WIDTH * sizeof(UINT));
        videoBuffer += ddsd.lPitch >> 2;
    }

    if (FAILED(lpddBackSurface->Unlock(NULL)))
        return 0;


   return 1;
}

I don't know why I always failed to SetPalette to the primary surface. I set the DisplayMode to 640*480*32, my palette is only 256-color, is this the reason? But I consult the MSDN, CreatePalette can only create 2Bit, 4Bit, 8Bit palettes. Can a 32-bit display mode be compatible with 8Bit palette? Where is the problem?

I will be grateful if someone could give me some advice. Thanks.


Solution

  • Anything greater than 8-bit mode (16, 24, 32) are direct color modes and the data in the frame buffer is the actual color, so there is no palette like there would be in an 8-bit indexed color mode.

    To make your example work as it is, change SCREEN_BPP to 8. The problem with that is in modern versions of Windows you may find your palette doesn't stick and it gets set back to the system palette. There are some workarounds you might try listed here: "Exclusive" DirectDraw palette isn't actually exclusive

    If you want to use 32-bit mode then there is no palette and you must set the color directly rather than using the index into a palette. You could do something like this to copy the palette color into the frame buffer in your example:

    if (FAILED(lpddBackSurface->Lock(NULL, &ddsd, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT, NULL)))
        return 0;
    
    UINT *videoBuffer = (UINT*)ddsd.lpSurface;
    
    for (int y = 0; y < SCREEN_HEIGHT; y++)
    {
        const PALETTEENTRY& p = palette[y%256];
        UINT color = (p.peRed << 16) | (p.peGreen << 8) | p.peBlue;
        for(int x = 0; x < SCREEN_WIDTH; ++x)
        {
            videoBuffer[x] = color;
        }
        videoBuffer += ddsd.lPitch / sizeof(UINT);
    }
    
    if (FAILED(lpddBackSurface->Unlock(NULL)))
        return 0;