Welcome!

Join our community of MMO enthusiasts and game developers! By registering, you'll gain access to discussions on the latest developments in MMO server files and collaborate with like-minded individuals. Join us today and unlock the potential of MMO server development!

Join Today!

[C++][DirectX 9] Displaying .GIF animated images

[emoji848]
Legend
Joined
Dec 3, 2011
Messages
2,232
Reaction score
1,518
I've been stuck with this topic for a while now. .GIF is like the most commonly used file format for short image animations. Why not use it for games too, instead of converting it to single frames or videos or rarely used formats, I thought. I would like to use it for a large scope of animations. Starting from little loading / button animations to showing a bigger loading screen.

Unfortunately there is no .gif file support in DirectX and searching the net hardly gives any good advise. So I came up with different approaches to do this, but nothing convinced me fully yet. I feel like there is something more straight forward and efficient to use.

Important to know

I'm developing a DirectX9 3D Application that uses swapchaining (having a front & backbuffer). Each time the 3D Environment gets rendered, the Tick() methods of all components are called to update the game logic. Afterwards the Render() methods are called to draw onto the back buffer before flipping.

To init / restored / invalidate / release components, respective methods exist but are simplified / left out for the sake of simplicity.

Here is what I tried so far:

1. Approach: GDI+
As I was using GDI+ several times when developing MFC applications already, I knew it could render pretty much any common image file format, including .gifs. However GDI+ is hardly used together with DirectX I noticed and it's not really made to work with it, but with some "hacks" it can be implemented using the back buffers surface (IDirect3DSurface9*).

This is the simplified code I came up with:

Code:
// Member and global vars used for this (dumb, I know but it's just for testing atm)
LPDIRECT3DDEVICE9 g_pD3dDev // Global directX device pointer
Gdiplus::Image* m_pImage; // GDI+ image handle
Gdiplus::GUID* m_pDimensionIDs; // GDI+ Image dimensions
UINT m_FrameCount; // Amount of Frames stored in the GIF
Gdiplus::PropertyItem* m_pItem; // Additional GDI+ properties for the GIF
UINT m_iCurrentFrame; // The index of the current frame to render
IDirect3DSurface9* surface; // Pointer to the back buffers surface

// Used for time calculations for frame display velocity
float ftime;
#define FRAME_SPEED			0.033f

// Basic loading of the GIF, it's animations and other important properties
HRESULT CGIFCtrl::Init()
{
	// GDI+ Image initialization
	m_pImage = Image::FromFile(L"C:\\opening.gif");

	UINT count = m_pImage->GetFrameDimensionsCount();
	m_pDimensionIDs = new GUID[count];
	m_pImage->GetFrameDimensionsList(m_pDimensionIDs, count);

	WCHAR strGuid[39];
	StringFromGUID2(m_pDimensionIDs[0], strGuid, 39);
	m_FrameCount = m_pImage->GetFrameCount(&m_pDimensionIDs[0]);

	UINT TotalBuffer = m_pImage->GetPropertyItemSize(PropertyTagFrameDelay);
	m_pImage->GetPropertyItem(PropertyTagFrameDelay, TotalBuffer, m_pItem);

	// Logic initialization
	ftime = 0;
	m_iCurrentFrame = 0;
	GUID Guid = FrameDimensionTime; // Predefined multi-frame dimension ID in GdiPlusImaging.h
	m_pImage->SelectActiveFrame(&Guid, 0);

	// Here comes the little "hack" by getting the back buffer surface for gdi+ graphics rendering later on
	g_pD3dDev->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &surface);

	return S_OK;
}

// Update / Ticking logic for the GIF, setting the frames to render with certain velocity
// Called in front of each render call
void CGIFCtrl::Tick(float fElapsedTime)
{
	ftime += fElapsedTime;
	if (ftime >= FRAME_SPEED)
	{
		ftime -= FRAME_SPEED;

		GUID Guid = FrameDimensionTime;
		if (m_FrameCount == m_iCurrentFrame++)
		{
			// Logic to stop the playback here
			return;
		}
		m_pImage->SelectActiveFrame(&Guid, m_iCurrentFrame);
	}
}

// Render called each time before the swapchain (60 Hz in my case)
void CGIFCtrl::Render()
{
	// Getting the Device Context Handle from the backbuffer surface for GDI+ drawing
	HDC hdc = NULL;
	HRESULT hr = surface->GetDC(&hdc);
	Gdiplus::Graphics g(hdc);

	// Drawing of the image onto the surface
	// HUGE PERFORMANCE PROBLEM HERE
	g.DrawImage(m_pImage, 0, 0,
		g_pD3dApp->GetBackBufferDesc().Width, g_pD3dApp->GetBackBufferDesc().Height);

	// HDC cleanup
	hr = surface->ReleaseDC(hdc);
}

The huge problem with this attempt is that Gdiplus::Graphics::DrawImage takes a huuuuge amount of time to draw larger images to the surface. Tests with a resolution of 1920 x 1080 resulted in 800-1500ms per Gdiplus::Graphics::DrawImage call!
Obviously that cannot be tolerated in any game with a framerate higher than 1, lol.

Even with smaller images the method is too exhausting for acceptable performance. So either there is a trick here or it's simply not efficient enough to use GDI+.

2. Approach: Extracting and rendering each frame as DirectX Texture

On my second approach I extracted each frame of the .GIF and re-packed it to a custom file with additional information. On the code side I load each frame as a LPDIRECT3DTEXTURE9 using D3DXCreateTextureFromFileInMemoryEx (so it's optimally directly on the GPU memory) and draw it using a sprite later on:

Code:
// Member and global vars used for this (dumb, I know but it's just for testing atm)
LPDIRECT3DDEVICE9	g_pD3dDev // Global directX device pointer
LPDIRECT3DTEXTURE9* m_pImages; // Array of directX textures which were loaded for each frame
LPD3DXSPRITE m_pSprite; // DirectX sprite to draw the respective texture
RECT m_Rect; // The target rectangle to draw the image in
DWORD m_Color; // The default color used by the sprite to draw

UINT m_FrameCount; // Total count of loaded frames
UINT m_iCurrentFrame; // The index of the current frame to render

// Used for time calculations for frame display velocity
float ftime;
#define FRAME_SPEED			0.033f

// Basic loading of the GIF, it's animations and other important properties
HRESULT CGIFCtrl::Init()
{
	// Loading of my simple packed file (simpliyfied as it's another method and boring file parsing)
	struct GIF_DATA data = ParseFile("myCustomFile.ani");
	m_FrameCount = data.FrameCount;
	void* fileData = data.FileData;
	UINT fileDataLength = data.FileDataLength;
	m_Rect = data.Area;

	// Creating DirectX containers for the stuff to render later on
	D3DXCreateSprite(g_pD3dDev, &m_pSprite);

	D3DXIMAGE_INFO SrcInfo;
	m_pImages = new LPDIRECT3DTEXTURE9[m_FrameCount];
	for (UINT i = 0; i < m_FrameCount; i++)
	{
		D3DXCreateTextureFromFileInMemoryEx( g_pD3dDev, fileData,fileDataLength,D3DX_DEFAULT,D3DX_DEFAULT,D3DX_DEFAULT,
			0, D3DFMT_UNKNOWN, D3DPOOL_MANAGED, D3DX_FILTER_NONE, D3DX_DEFAULT, 
			0, &SrcInfo, NULL, &m_pImages[i]);
	}

	// Logic initialization
	ftime = 0;
	m_iCurrentFrame = 0;

	return S_OK;
}

// Update / Ticking logic for the GIF, setting the frames to render with certain velocity
// Called in front of each render call
void CGIFCtrl::Tick(float fElapsedTime)
{
	ftime += fElapsedTime;
	if (ftime >= FRAME_SPEED)
	{
		ftime -= FRAME_SPEED;

		if (m_FrameCount == m_iCurrentFrame++)
		{
			// Logic to stop the playback here
			return;
		}
	}
}

// Render called each time before the swapchain (60 Hz in my case)
void CGIFCtrl::Render()
{
	// Drawing the current frame using the D3D sprite and images
	m_pSprite->Begin(D3DXSPRITE_ALPHABLEND);
	m_pSprite->Draw(m_pImages[m_iCurrentFrame], m_Rect, NULL, NULL, m_Color);
	m_pSprite->End();
}

While this works nicely and very performant for short animations, the problem with this approach is that it's very exhausting for memory (both System and GPU). Specially with larger scales or longer animations my process memory went up 1 GB only by loading the images....

What I have not tried yet:

  • What I didn't try yet was creating the DirectX images per render call and releasing them right after to spare memory. I believe this is a very bad idea too but can be proven wrong ;)
  • I didn't try making a video file out of the GIF yet. It comes with great maintainability deficits because of the conversions and I'm honestly a big noob when it comes to the Direct Show stuff using Graphs and that kind of stuff.
  • Using external libraries for the .GIF handling and drawing. I'm generally a strong opponent of too much external libraries for simple stuff. So if possible I'd not like to consider it at all.

Maybe you guys can help me a bit? Maybe some of you have already made some experiences in this area? It's hilarious how little you can find on the net regarding this topic. So I'm really really happy about any advice... or pretty much anything at all :love:
 
Last edited:
Joined
Sep 27, 2006
Messages
557
Reaction score
88
Just pulling straws as directx and gifs are hard to do.

1.write your own texture importer
2."extract" the individual frames from gif
3.animate the texture during rendering
 
[emoji848]
Legend
Joined
Dec 3, 2011
Messages
2,232
Reaction score
1,518
Just pulling straws as directx and gifs are hard to do.

1.write your own texture importer
2."extract" the individual frames from gif
3.animate the texture during rendering

Well you gave a very generic answer to what is a very delicate problem.

1. Writing an own texture importer is kind of what I did on my second approach. Problem with this is the massive memory resource use with the way I did it. Have you got some better idea on how to do it? An example of what you mean with "my own texture importer"? Otherwise I can't make much use out of that.

2. I did this as I described. Unpacked each frame of the .gif and repacked to a binary file that has little to no overhead but just the pure frames. Then load each frame and render it.

3. I don't really understand what's meant with that. Animate the texture? Other than moving it from A to B I can't think of what you mean by "animate the texture". The .gif file structure defines the animation and uses several frames. I don't see much way to get around rendering it frame by frame instead of animating a single texture, if that is what you meant.

Thanks for the answer tho...
 
Joined
Jun 23, 2010
Messages
2,323
Reaction score
2,195
To the question why gifs are never used in games is just because the small amount of colors.

I'm not sure if you tried this but have you tried repacking the image, like a spritesheet. So you can just dump that single image in the memory. Then you only change the the position of what area to draw on your rectangle.
 
[emoji848]
Legend
Joined
Dec 3, 2011
Messages
2,232
Reaction score
1,518
To the question why gifs are never used in games is just because the small amount of colors.

I'm not sure if you tried this but have you tried repacking the image, like a spritesheet. So you can just dump that single image in the memory. Then you only change the the position of what area to draw on your rectangle.

Uhm... you can go 256 bit with a .gif, I believe? I don't think it's not used because of the colors but DX developers rather decided to not support it as it's half image / half animation. And for both of that they added respective methods already.

The spritesheet is actually not a bad idea at all! Comes with effort ofc when converting each .gif back and forth. Though with high-res .gifs that wont work out very well I think :/

Maybe I just have to accept that there is no decent supported way to use .gifs with DirectX :/ Maybe I need to start looking into Direct Show and .avi / .wmv formats for what I want to achieve.
 
Back
Top