/* ______ ___ ___ * /\ _ \ /\_ \ /\_ \ * \ \ \L\ \\//\ \ \//\ \ __ __ _ __ ___ * \ \ __ \ \ \ \ \ \ \ /'__`\ /'_ `\/\`'__\/ __`\ * \ \ \/\ \ \_\ \_ \_\ \_/\ __//\ \L\ \ \ \//\ \L\ \ * \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/ * \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/ * /\____/ * \_/__/ * * DirectDraw bitmap management routines. * * By Stefan Schimanski. * * Improved page flipping mechanism by Robin Burrows. * * Improved video bitmap allocation scheme by Eric Botcazou. * * See readme.txt for copyright information. */ #include "wddraw.h" #define PREFIX_I "al-wddbmp INFO: " #define PREFIX_W "al-wddbmp WARNING: " #define PREFIX_E "al-wddbmp ERROR: " /* The video bitmap allocation scheme works as follows: * - the screen is allocated as a single DirectDraw surface (primary or overlay, * depending on the driver) at startup, * - the first video bitmap reuses the DirectDraw surface of the screen which is * then assigned to flipping_page[0], * - the second video bitmap allocates flipping_page[1] and uses it as its * DirectDraw surface; it also destroys the single surface pointed to by * flipping_page[0] and creates a DirectDraw flipping chain whose frontbuffer * is connected back to flipping_page[0] and backbuffer to flipping_page[1], * - the third video bitmap allocates flipping_page[2] and uses it as its * DirectDraw surface; it also destroys the flipping chain pointed to by * flipping_page[0] and flipping_page[1] and creates a new flipping chain * whose frontbuffer is connected back to flipping_page[0], first backbuffer * back to flipping_page[1] and second backbuffer to flipping_page[2]. * * When a video bitmap is to be destroyed, the flipping chain (if it exists) is * destroyed and recreated with one less backbuffer, and its surfaces are assigned * back to the flipping_page[] array in order. If the video bitmap is not attached * to the surface that was just destroyed, its surface is assigned to the video * bitmap parent of the just destroyed surface. * * After triple buffering setup: * * screen video_bmp[0] video_bmp[1] video_bmp[2] * \ | | | * \ | | | * page_flipping[0] page_flipping[1] page_flipping[2] * (frontbuffer)-----(backbuffer1)-----(backbuffer2) * * After request_video_bitmap(video_bmp[1]): * * screen video_bmp[0] video_bmp[1] video_bmp[2] * \ \/ | * \ /\ | * page_flipping[0] page_flipping[1] page_flipping[2] * (frontbuffer)-----(backbuffer1)-----(backbuffer2) * * This ensures that every video bitmap keeps track of the actual part of the * video memory it represents (see the documentation of DirectDrawSurface::Flip). */ static DDRAW_SURFACE *flipping_page[3] = {NULL, NULL, NULL}; static int n_flipping_pages = 0; /* dd_err: * Returns a DirectDraw error string. */ #ifdef DEBUGMODE static char *dd_err(long err) { static char err_str[64]; switch (err) { case DD_OK: _al_sane_strncpy(err_str, "DD_OK", sizeof(err_str)); break; case DDERR_GENERIC: _al_sane_strncpy(err_str, "DDERR_GENERIC", sizeof(err_str)); break; case DDERR_INCOMPATIBLEPRIMARY: _al_sane_strncpy(err_str, "DDERR_INCOMPATIBLEPRIMARY", sizeof(err_str)); break; case DDERR_INVALIDCAPS: _al_sane_strncpy(err_str, "DDERR_INVALIDCAPS", sizeof(err_str)); break; case DDERR_INVALIDOBJECT: _al_sane_strncpy(err_str, "DDERR_INVALIDOBJECT", sizeof(err_str)); break; case DDERR_INVALIDPARAMS: _al_sane_strncpy(err_str, "DDERR_INVALIDPARAMS", sizeof(err_str)); break; case DDERR_INVALIDPIXELFORMAT: _al_sane_strncpy(err_str, "DDERR_INVALIDPIXELFORMAT", sizeof(err_str)); break; case DDERR_NOFLIPHW: _al_sane_strncpy(err_str, "DDERR_NOFLIPHW", sizeof(err_str)); break; case DDERR_NOTFLIPPABLE: _al_sane_strncpy(err_str, "DDERR_NOTFLIPPABLE", sizeof(err_str)); break; case DDERR_OUTOFMEMORY: _al_sane_strncpy(err_str, "DDERR_OUTOFMEMORY", sizeof(err_str)); break; case DDERR_OUTOFVIDEOMEMORY: _al_sane_strncpy(err_str, "DDERR_OUTOFVIDEOMEMORY", sizeof(err_str)); break; case DDERR_PRIMARYSURFACEALREADYEXISTS: _al_sane_strncpy(err_str, "DDERR_PRIMARYSURFACEALREADYEXISTS", sizeof(err_str)); break; case DDERR_SURFACEBUSY: _al_sane_strncpy(err_str, "DDERR_SURFACEBUSY", sizeof(err_str)); break; case DDERR_SURFACELOST: _al_sane_strncpy(err_str, "DDERR_SURFACELOST", sizeof(err_str)); break; case DDERR_UNSUPPORTED: _al_sane_strncpy(err_str, "DDERR_UNSUPPORTED", sizeof(err_str)); break; case DDERR_UNSUPPORTEDMODE: _al_sane_strncpy(err_str, "DDERR_UNSUPPORTEDMODE", sizeof(err_str)); break; case DDERR_WASSTILLDRAWING: _al_sane_strncpy(err_str, "DDERR_WASSTILLDRAWING", sizeof(err_str)); break; default: _al_sane_strncpy(err_str, "DDERR_UNKNOWN", sizeof(err_str)); break; } return err_str; } #else #define dd_err(hr) "\0" #endif /* create_directdraw2_surface: * Wrapper around DirectDraw2::CreateSurface taking the surface characteristics * as parameters. Returns a DirectDrawSurface2 interface if successful. */ static LPDIRECTDRAWSURFACE2 create_directdraw2_surface(int w, int h, LPDDPIXELFORMAT pixel_format, int type, int n_backbuffers) { DDSURFACEDESC ddsurf_desc; LPDIRECTDRAWSURFACE ddsurf1; LPVOID ddsurf2; HRESULT hr; /* describe surface characteristics */ memset(&ddsurf_desc, 0, sizeof(DDSURFACEDESC)); ddsurf_desc.dwSize = sizeof(DDSURFACEDESC); ddsurf_desc.dwFlags = DDSD_CAPS; switch (type) { case DDRAW_SURFACE_PRIMARY: ddsurf_desc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; _TRACE(PREFIX_I "Creating primary surface..."); break; case DDRAW_SURFACE_OVERLAY: ddsurf_desc.ddsCaps.dwCaps = DDSCAPS_VIDEOMEMORY | DDSCAPS_OVERLAY; ddsurf_desc.dwFlags |= DDSD_HEIGHT | DDSD_WIDTH; ddsurf_desc.dwHeight = h; ddsurf_desc.dwWidth = w; if (pixel_format) { /* use pixel format */ ddsurf_desc.dwFlags |= DDSD_PIXELFORMAT; ddsurf_desc.ddpfPixelFormat = *pixel_format; } _TRACE(PREFIX_I "Creating overlay surface..."); break; case DDRAW_SURFACE_VIDEO: ddsurf_desc.ddsCaps.dwCaps = DDSCAPS_VIDEOMEMORY | DDSCAPS_OFFSCREENPLAIN; ddsurf_desc.dwFlags |= DDSD_HEIGHT | DDSD_WIDTH; ddsurf_desc.dwHeight = h; ddsurf_desc.dwWidth = w; _TRACE(PREFIX_I "Creating video surface..."); break; case DDRAW_SURFACE_SYSTEM: ddsurf_desc.ddsCaps.dwCaps = DDSCAPS_SYSTEMMEMORY | DDSCAPS_OFFSCREENPLAIN; ddsurf_desc.dwFlags |= DDSD_HEIGHT | DDSD_WIDTH; ddsurf_desc.dwHeight = h; ddsurf_desc.dwWidth = w; if (pixel_format) { /* use pixel format */ ddsurf_desc.dwFlags |= DDSD_PIXELFORMAT; ddsurf_desc.ddpfPixelFormat = *pixel_format; } _TRACE(PREFIX_I "Creating system surface..."); break; default: _TRACE(PREFIX_E "Unknown surface type\n"); return NULL; } /* set backbuffer requirements */ if (n_backbuffers > 0) { ddsurf_desc.ddsCaps.dwCaps |= DDSCAPS_COMPLEX | DDSCAPS_FLIP; ddsurf_desc.dwFlags |= DDSD_BACKBUFFERCOUNT; ddsurf_desc.dwBackBufferCount = n_backbuffers; _TRACE("...with %d backbuffer(s)\n", n_backbuffers); } /* create the surface */ hr = IDirectDraw2_CreateSurface(directdraw, &ddsurf_desc, &ddsurf1, NULL); if (FAILED(hr)) { _TRACE(PREFIX_E "Unable to create the surface (%s)\n", dd_err(hr)); return NULL; } /* retrieve the DirectDrawSurface2 interface */ hr = IDirectDrawSurface_QueryInterface(ddsurf1, &IID_IDirectDrawSurface2, &ddsurf2); /* there is a bug in the COM part of DirectX 3: * If we release the DirectSurface interface, the actual * object is also released. It is fixed in DirectX 5. */ if (_dx_ver >= 0x500) IDirectDrawSurface_Release(ddsurf1); if (FAILED(hr)) { _TRACE(PREFIX_E "Unable to retrieve the DirectDrawSurface2 interface (%s)\n", dd_err(hr)); return NULL; } return (LPDIRECTDRAWSURFACE2)ddsurf2; } /* gfx_directx_create_surface: * Creates a DirectDraw surface. */ DDRAW_SURFACE *gfx_directx_create_surface(int w, int h, LPDDPIXELFORMAT pixel_format, int type) { DDRAW_SURFACE *surf; surf = malloc(sizeof(DDRAW_SURFACE)); if (!surf) return NULL; /* create the surface with the specified characteristics */ surf->id = create_directdraw2_surface(w, h, pixel_format,type, 0); if (!surf->id) { free(surf); return NULL; } surf->flags = type; surf->lock_nesting = 0; register_ddraw_surface(surf); return surf; } /* gfx_directx_destroy_surface: * Destroys a DirectDraw surface. */ void gfx_directx_destroy_surface(DDRAW_SURFACE *surf) { IDirectDrawSurface2_Release(surf->id); unregister_ddraw_surface(surf); free(surf); } /* gfx_directx_make_bitmap_from_surface: * Connects a DirectDraw surface with an Allegro bitmap. */ BITMAP *gfx_directx_make_bitmap_from_surface(DDRAW_SURFACE *surf, int w, int h, int id) { BITMAP *bmp; int i; bmp = (BITMAP *) malloc(sizeof(BITMAP) + sizeof(char *) * h); if (!bmp) return NULL; bmp->w =w; bmp->cr = w; bmp->h = h; bmp->cb = h; bmp->clip = TRUE; bmp->cl = 0; bmp->ct = 0; bmp->vtable = &_screen_vtable; bmp->write_bank = gfx_directx_write_bank; bmp->read_bank = gfx_directx_write_bank; bmp->dat = NULL; bmp->id = id; bmp->extra = NULL; bmp->x_ofs = 0; bmp->y_ofs = 0; bmp->seg = _video_ds(); for (i = 0; i < h; i++) bmp->line[i] = pseudo_surf_mem; bmp->extra = surf; return bmp; } /* gfx_directx_created_sub_bitmap: */ void gfx_directx_created_sub_bitmap(BITMAP *bmp, BITMAP *parent) { bmp->extra = parent; } /* recreate_flipping_chain: * Destroys the previous flipping chain and creates a new one. */ static int recreate_flipping_chain(int n_pages) { int w, h, type, n_backbuffers; DDSCAPS ddscaps; HRESULT hr; ASSERT(n_pages > 0); /* set flipping chain characteristics */ w = gfx_directx_forefront_bitmap->w; h = gfx_directx_forefront_bitmap->h; type = flipping_page[0]->flags & DDRAW_SURFACE_TYPE_MASK; n_backbuffers = n_pages - 1; /* release existing flipping chain */ if (flipping_page[0]->id) { hr = IDirectDrawSurface2_Release(flipping_page[0]->id); if (FAILED(hr)) { _TRACE(PREFIX_E "Unable to release the primary surface (%s)", dd_err(hr)); return -1; } } /* create the new flipping chain with the specified characteristics */ flipping_page[0]->id = create_directdraw2_surface(w, h, ddpixel_format, type, n_backbuffers); if (!flipping_page[0]->id) return -1; /* retrieve the backbuffers */ if (n_backbuffers > 0) { memset(&ddscaps, 0, sizeof(DDSCAPS)); /* first backbuffer */ ddscaps.dwCaps = DDSCAPS_BACKBUFFER; hr = IDirectDrawSurface2_GetAttachedSurface(flipping_page[0]->id, &ddscaps, &flipping_page[1]->id); if (FAILED(hr)) { _TRACE(PREFIX_E "Unable to retrieve the first backbuffer (%s)", dd_err(hr)); return -1; } flipping_page[1]->flags = flipping_page[0]->flags; flipping_page[1]->lock_nesting = 0; if (n_backbuffers > 1) { /* second backbuffer */ ddscaps.dwCaps = DDSCAPS_FLIP; hr = IDirectDrawSurface2_GetAttachedSurface(flipping_page[1]->id, &ddscaps, &flipping_page[2]->id); if (FAILED(hr)) { _TRACE(PREFIX_E "Unable to retrieve the second backbuffer (%s)", dd_err(hr)); return -1; } flipping_page[2]->flags = flipping_page[0]->flags; flipping_page[2]->lock_nesting = 0; } } /* attach the global palette if needed */ if (flipping_page[0]->flags & DDRAW_SURFACE_INDEXED) { hr = IDirectDrawSurface2_SetPalette(flipping_page[0]->id, ddpalette); if (FAILED(hr)) { _TRACE(PREFIX_E "Unable to attach the global palette (%s)", dd_err(hr)); return -1; } } return 0; } /* gfx_directx_create_video_bitmap: */ BITMAP *gfx_directx_create_video_bitmap(int width, int height) { DDRAW_SURFACE *surf; BITMAP *bmp; /* try to detect page flipping and triple buffering patterns */ if ((width == gfx_directx_forefront_bitmap->w) && (height == gfx_directx_forefront_bitmap->h)) { switch (n_flipping_pages) { case 0: /* recycle the forefront surface as the first flipping page */ flipping_page[0] = DDRAW_SURFACE_OF(gfx_directx_forefront_bitmap); bmp = gfx_directx_make_bitmap_from_surface(flipping_page[0], width, height, BMP_ID_VIDEO); if (bmp) { flipping_page[0]->parent_bmp = bmp; n_flipping_pages++; return bmp; } else { flipping_page[0] = NULL; return NULL; } case 1: case 2: /* try to attach an additional page to the flipping chain */ flipping_page[n_flipping_pages] = malloc(sizeof(DDRAW_SURFACE)); if (recreate_flipping_chain(n_flipping_pages+1) == 0) { bmp = gfx_directx_make_bitmap_from_surface(flipping_page[n_flipping_pages], width, height, BMP_ID_VIDEO); if (bmp) { flipping_page[n_flipping_pages]->parent_bmp = bmp; n_flipping_pages++; return bmp; } } recreate_flipping_chain(n_flipping_pages); free(flipping_page[n_flipping_pages]); flipping_page[n_flipping_pages] = NULL; return NULL; } } /* create the DirectDraw surface */ if (ddpixel_format) surf = gfx_directx_create_surface(width, height, ddpixel_format, DDRAW_SURFACE_SYSTEM); else surf = gfx_directx_create_surface(width, height, NULL, DDRAW_SURFACE_VIDEO); if (!surf) return NULL; /* create the bitmap that wraps up the surface */ bmp = gfx_directx_make_bitmap_from_surface(surf, width, height, BMP_ID_VIDEO); if (!bmp) { gfx_directx_destroy_surface(surf); return NULL; } return bmp; } /* gfx_directx_destroy_video_bitmap: */ void gfx_directx_destroy_video_bitmap(BITMAP *bmp) { DDRAW_SURFACE *surf, *tail_page; surf = DDRAW_SURFACE_OF(bmp); if ((surf == flipping_page[0]) || (surf == flipping_page[1]) || (surf == flipping_page[2])) { /* handle surfaces belonging to the flipping chain */ if (--n_flipping_pages > 0) { tail_page = flipping_page[n_flipping_pages]; /* If the surface attached to the bitmap is not the tail page * that is to be destroyed, attach it to the bitmap whose * attached surface is the tail page. */ if (surf != tail_page) { surf->parent_bmp = tail_page->parent_bmp; surf->parent_bmp->extra = surf; } /* remove the tail page from the flipping chain */ recreate_flipping_chain(n_flipping_pages); free(tail_page); } flipping_page[n_flipping_pages] = NULL; } else { /* destroy the surface */ gfx_directx_destroy_surface(surf); } free(bmp); } /* flip_with_forefront_bitmap: * Worker function for DirectDraw page flipping. */ static int flip_with_forefront_bitmap(BITMAP *bmp, int wait) { DDRAW_SURFACE *surf; HRESULT hr; /* flip only in the foreground */ if (!_win_app_foreground) { _win_thread_switch_out(); return 0; } /* retrieve the underlying surface */ surf = DDRAW_SURFACE_OF(bmp); if (surf == flipping_page[0]) return 0; ASSERT((surf == flipping_page[1]) || (surf == flipping_page[2])); /* flip the contents of the surfaces */ hr = IDirectDrawSurface2_Flip(flipping_page[0]->id, surf->id, wait ? DDFLIP_WAIT : 0); /* if the surface has been lost, try to restore all surfaces */ if (hr == DDERR_SURFACELOST) { if (restore_all_ddraw_surfaces() == 0) hr = IDirectDrawSurface2_Flip(flipping_page[0]->id, surf->id, wait ? DDFLIP_WAIT : 0); } if (FAILED(hr)) { _TRACE(PREFIX_E "Can't flip (%s)\n", dd_err(hr)); return -1; } /* attach the surface to the former forefront bitmap */ surf->parent_bmp = flipping_page[0]->parent_bmp; surf->parent_bmp->extra = surf; /* make the bitmap point to the forefront surface */ flipping_page[0]->parent_bmp = bmp; bmp->extra = flipping_page[0]; return 0; } /* gfx_directx_show_video_bitmap: */ int gfx_directx_show_video_bitmap(BITMAP *bmp) { /* guard against show_video_bitmap(screen); */ if (bmp == gfx_directx_forefront_bitmap) return 0; return flip_with_forefront_bitmap(bmp, _wait_for_vsync); } /* gfx_directx_request_video_bitmap: */ int gfx_directx_request_video_bitmap(BITMAP *bmp) { /* guard against request_video_bitmap(screen); */ if (bmp == gfx_directx_forefront_bitmap) return 0; return flip_with_forefront_bitmap(bmp, FALSE); } /* gfx_directx_poll_scroll: */ int gfx_directx_poll_scroll(void) { HRESULT hr; ASSERT(n_flipping_pages == 3); hr = IDirectDrawSurface2_GetFlipStatus(flipping_page[0]->id, DDGFS_ISFLIPDONE); /* if the surface has been lost, try to restore all surfaces */ if (hr == DDERR_SURFACELOST) { if (restore_all_ddraw_surfaces() == 0) hr = IDirectDrawSurface2_GetFlipStatus(flipping_page[0]->id, DDGFS_ISFLIPDONE); } if (FAILED(hr)) return -1; return 0; } /* gfx_directx_create_system_bitmap: */ BITMAP *gfx_directx_create_system_bitmap(int width, int height) { DDRAW_SURFACE *surf; BITMAP *bmp; /* create the DirectDraw surface */ surf = gfx_directx_create_surface(width, height, ddpixel_format, DDRAW_SURFACE_SYSTEM); if (!surf) return NULL; /* create the bitmap that wraps up the surface */ bmp = gfx_directx_make_bitmap_from_surface(surf, width, height, BMP_ID_SYSTEM); if (!bmp) { gfx_directx_destroy_surface(surf); return NULL; } return bmp; } /* gfx_directx_destroy_system_bitmap: */ void gfx_directx_destroy_system_bitmap(BITMAP *bmp) { /* destroy the surface */ gfx_directx_destroy_surface(DDRAW_SURFACE_OF(bmp)); free(bmp); } /* win_get_dc: (WinAPI) * Returns device context of a video or system bitmap. */ HDC win_get_dc(BITMAP *bmp) { LPDIRECTDRAWSURFACE2 ddsurf; HDC dc; HRESULT hr; if (bmp) { if (bmp->id & (BMP_ID_SYSTEM | BMP_ID_VIDEO)) { ddsurf = DDRAW_SURFACE_OF(bmp)->id; hr = IDirectDrawSurface2_GetDC(ddsurf, &dc); /* If the surface has been lost, try to restore all surfaces * and, on success, try again to get the DC. */ if (hr == DDERR_SURFACELOST) { if (restore_all_ddraw_surfaces() == 0) hr = IDirectDrawSurface2_GetDC(ddsurf, &dc); } if (hr == DD_OK) return dc; } } return NULL; } /* win_release_dc: (WinAPI) * Releases device context of a video or system bitmap. */ void win_release_dc(BITMAP *bmp, HDC dc) { LPDIRECTDRAWSURFACE2 ddsurf; HRESULT hr; if (bmp) { if (bmp->id & (BMP_ID_SYSTEM | BMP_ID_VIDEO)) { ddsurf = DDRAW_SURFACE_OF(bmp)->id; hr = IDirectDrawSurface2_ReleaseDC(ddsurf, dc); /* If the surface has been lost, try to restore all surfaces * and, on success, try again to release the DC. */ if (hr == DDERR_SURFACELOST) { if (restore_all_ddraw_surfaces() == 0) hr = IDirectDrawSurface2_ReleaseDC(ddsurf, dc); } } } }