Proxy-dll: (2) Usage Examples

HowTo 1: Show an ingame info screen (a simple bitmap)

Okay, you got familiar with the proxy-dll concept so far ? Fine. In this HowTo, we’ll try to add an “ingame info screen” to a DirectX 9 application (I use the Flatspace Demo for testing). Please note that you need the DX9 SDK with the Summer2004 update.

proxy_howto2

Based on the “basic proxy-dll implementation” (see downloads page), we’ll add a partly transparent texture and show it ingame. For the sake of simplicity, I do not cover switching this feature on or off using a keyboard hook. It is always on.

  • To keep things a bit straight, we add a new class CmyOwnStuff to the project, and delete the myIDirect3DDevice9::ShowWeAreHere routine (and the call within ::Present, of course). Ensure the CmyOwnStuff receives a pointer to the dll-internal IDirect3DDevice9 and stores it.
  • Create a fancy 256×128 bitmap (*.bmp), and paint all soon-to-be transparent areas as black (0/0/0). Add this bitmap as a resource to your project.
  • To show this bitmap ingame, we’ll create a ID3DXSprite Object. Using textured triangles would work aswell. I just like the Sprite thingy, though.
  • Add a ID3DXSprite* to your CmyOwnStuff. Create the Sprite in the Constructor, and release it within the Destructor.
  • Add a IDirect3DTexture9* to  CmyOwnStuff. Load your fancy bitmap from the resource, while in the Constructor, and release it within the Destructor.
  • Add a public CmyOwnStuff::Render routine. Here we’ll draw the Sprite using the ID3DXSprite::Draw method.
  • Now we need to ensure that our sprite object is drawn every time the routine myIDirect3DDevice9::Present is called. So, add an CmyOwnStuff pointer to myIDirect3DDevice9. Create the CmyOwnStuff Object once (I do this within ::Present), and delete it withinmyIDirect3DDevice9::Release. Don’t forget to set the OwnStuff pointer to NULL in the myIDirect3DDevice9 Constructor.
  • Hang on, work is nearly finished. As we created the texture for the background as D3DPOOL_MANAGED (believe me, we did), there is no need to take care of it if the DirectX device is in lost state (e.g. when someone tabs out). We need to cover the sprite object, though. So, add two new public routines to CmyOwnStuff: OnLostDevice and OnResetDevice. Call the corresponding routines from ID3DXSprite there. Ensure that CmyOwnStuff::OnLostDevice and CmyOwnStuff::OnResetDevice get called frommyIDirect3DDevice9 (I use the ::Reset for this). Another way (and probably less prone to errors) is to create the ID3DXSprite Object on every render pass (and delete it afterwards). This increases impact on the fps, though.

Well, basically, this is the way I chose to add own content to a DirectX application. You may want to add several security checks. Remember if something fails within your added code, the application will most probably break, leaving a lot of memory garbage on the system.

Code fragments as discussed above (the project source is available at the downloads page):

// myOwnStuff.h
#pragma once
#include <d3dx9core.h>
 
class CmyOwnStuff
{
 public:
   CmyOwnStuff(IDirect3DDevice9 *pD3DDevice);
   ~CmyOwnStuff(void);
   void Render(void);
   void OnLostDevice(void);
   void OnResetDevice(void);
 private:
   IDirect3DDevice9* m_pD3DDevice;
   ID3DXSprite* m_pBackgroundSprite;
   IDirect3DTexture9* m_pBackgroundTexture;
 };
// myOwnStuff.cpp
#include "StdAfx.h"
#include ".\myownstuff.h"

CmyOwnStuff::CmyOwnStuff(IDirect3DDevice9 *pD3DDevice)
{
 extern HINSTANCE gl_hThisInstance;
 m_pD3DDevice = pD3DDevice;
 m_pBackgroundSprite = NULL;
 m_pBackgroundTexture = NULL;

 D3DXCreateSprite(m_pD3DDevice,&m_pBackgroundSprite);
 D3DXCreateTextureFromResourceEx(m_pD3DDevice,
 gl_hThisInstance,
 MAKEINTRESOURCE(IDB_BITMAP1),
 256,
 128,
 0,
 NULL,
 D3DFMT_UNKNOWN,
 D3DPOOL_MANAGED,
 D3DX_FILTER_NONE,
 D3DX_FILTER_NONE,
 0xFF000000,
 NULL,
 NULL,
 &m_pBackgroundTexture);
}

CmyOwnStuff::~CmyOwnStuff(void)
{
 if (m_pBackgroundSprite) m_pBackgroundSprite->Release();
 if (m_pBackgroundTexture) m_pBackgroundTexture->Release();
}

void CmyOwnStuff::Render(void)
{
 m_pD3DDevice->BeginScene();
 m_pBackgroundSprite->Begin(NULL);
 m_pBackgroundSprite->Draw(m_pBackgroundTexture,
 NULL,
 NULL,
 NULL,
 D3DCOLOR_ARGB(0xaa,0xff,0xff,0xff));
 m_pBackgroundSprite->End();
 m_pD3DDevice->EndScene();
}

void CmyOwnStuff::OnLostDevice(void)
{
 m_pBackgroundSprite->OnLostDevice();
}

void CmyOwnStuff::OnResetDevice(void)
{
 m_pBackgroundSprite->OnResetDevice();
}
// myIDirect3DDevice9.cpp
HRESULT myIDirect3DDevice9::Present(...)
{
 // we may want to draw own things here before flipping surfaces
 // ... draw own stuff ...
 if (!m_pMyOwnStuff) m_pMyOwnStuff = new CmyOwnStuff(m_pIDirect3DDevice9);
 if (m_pMyOwnStuff) m_pMyOwnStuff->Render();

 // call original routine
 HRESULT hres = m_pIDirect3DDevice9->Present(  pSourceRect,
 pDestRect,
 hDestWindowOverride,
 pDirtyRegion);

 return (hres);
}

HRESULT myIDirect3DDevice9::Reset(...)
{
 if (m_pMyOwnStuff) m_pMyOwnStuff->OnLostDevice();
 if (m_pMyOwnStuff) m_pMyOwnStuff->OnResetDevice();

 return(m_pIDirect3DDevice9->Reset(pPresentationParameters));
}

HowTo 2: Record an ingame movie (*.avi file)

Now that we can intercept every IDirect3DDevice9::Present event, how about saving every frame to a file, thus creating an ingame video ? Well, let see how this could work.

The video file creation routines of this HowTo are based on a fine article by James Dougherty, written for gamedev.net (http://www.gamedev.net/reference/articles/article2063.asp). I do use some parts of his code, too.

Again, I utilize the Flatspace Demo for testing, focusing on DX9.

How would video creation work ? First, we’d need a keyboard hook to switch recording ingame on and off. Then, we’d intercept every call to IDirect3DDevice9::Present , take a screen snapshot and process it and save it to an avi-stream. Basically, we should have an ingame video then.

  • Add a global keyboard hook to your project (using SetWindowsHookEx and a callback routine).
  • Add a new class (like “CAviSystem”) to your project (where all the new processing takes place).
  • Call bitmap/avi processing routines from within myIDirect3DDevice9::Present, if needed.

For the demo, I chose the “INSERT” key to toggle ingame recording on and off. The newly created *.avi file will always be saved to C:\myVideoX.avi (where “X” is an increasing number, in case several recordings are done after a game was started). There is a beep when recording toggles.

To see what I got from Flatspace with this setup, see the downloads page. Of course, you might need some more tweaking to enhance this up to a “fully qualifying ingame video tool”. The source to this HowTo is available at the downloads page, too.

Additional info, brought up by Peter Kuhn:

Taken screenshots can sometimes miss some elements of the scene (preferably, HUD or Sprites). This is a result of delayed caching within the video driver. The elements not visible are simply not yet drawn while intercepting Present – although one would expect that at first glance.
Peter proposes to add a “wait loop” inside the proxy’d ::Present call. This will have some impact on fps (minor, though). It could look like so:

HRESULT myIDirect3DDevice9::Present(CONST RECT* pSourceRect,CONST RECT* pDestRect,HWND hDestWindowOverride,CONST RGNDATA* pDirtyRegion)
 {
  // call original routine
  HRESULT hres = m_pIDirect3DDevice9->Present(
  pSourceRect, 
  pDestRect, 
  hDestWindowOverride, 
  pDirtyRegion);
 
  // Flush the engine content
  IDirect3DQuery9* mQuery = NULL;
 
  this->CreateQuery(D3DQUERYTYPE_EVENT, &mQuery);
  mQuery->Issue(D3DISSUE_END);
  while(mQuery->GetData(NULL, 0, D3DGETDATA_FLUSH) == S_FALSE); // wait loop here
  mQuery->Release();
 
  // do anything you want here (e.g. capturing
  // using the GDI), the frame is now rendered
  // completely
 
  return (hres);
 }