- 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:
The huge problem with this attempt is that Gdiplus::Graphics:rawImage 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:rawImage 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:
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:
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
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:rawImage 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:rawImage 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
Last edited: