Search code examples
c++handledirectx-9

DirectX9 without device window


I have always thought that we need to create a window before we can create a DirectX9 Device. And actually, that's how I understand the official documentation on the CreateDevice method:

hFocusWindow [in] Type: HWND (...) For windowed mode, this parameter may be NULL only if the hDeviceWindow member of pPresentationParameters is set to a valid, non-NULL value.

Now, I just gave it a try and it seems the following piece of code is working for me (eg. I get a valid device pointer and I can use it to do soem render target rendering, even if I didn't provide any window handle at any time):

#include <windows.h>
#include <stdio.h>
#include <iostream>
#include <sstream>
#include <chrono>
#include <thread>

#if defined(_MSC_VER)
    #pragma warning(disable : 4005)
#endif

#include <d3d9.h>
#include <d3dx9.h>
#include <DxErr.h>

int (WINAPIV * __vsnprintf)(char *, size_t, const char*, va_list) = _vsnprintf;

#define DEBUG_MSG(msg) std::cout << msg << std::endl;
#define ERROR_MSG(msg) std::cout << "[ERROR] " << msg << std::endl;

#define THROW_MSG(msg) { \
    std::ostringstream os; \
    os.precision(9); \
    os << std::fixed << "[FATAL] " << msg << " (at " << __FILE__ <<":"<<__LINE__<<")"; \
    DEBUG_MSG(os.str()); \
    throw std::runtime_error(os.str()); \
}

#define CHECK(cond,msg) if(!(cond)) { THROW_MSG(msg); return; }
#define CHECK_RET(cond,ret,msg) if(!(cond)) { THROW_MSG(msg); return ret; }
#define CHECK_RESULT(val,msg) { HRESULT hr = (val); if(FAILED(hr)) { THROW_MSG(msg << ", err=" << DXGetErrorString(hr) << ", desc=" << DXGetErrorDescription(hr)); return; } }
#define CHECK_RESULT_RET(val,ret,msg) { HRESULT hr = (val); if(FAILED(hr)) { THROW_MSG(msg << ", err=" << DXGetErrorString(hr) << ", desc=" << DXGetErrorDescription(hr)); return ret; } }

#define SAFERELEASE(x) if(x) { x->Release(); x = NULL; }

IDirect3D9Ex* d3dEx = nullptr;
IDirect3DDevice9* deviceEx = nullptr;

IDirect3DSurface9* renderSurface1 = nullptr;
HANDLE                     renderSurfaceHandle1 = nullptr;
IDirect3DSurface9* renderSurface2 = nullptr;
HANDLE                     renderSurfaceHandle2 = nullptr;

bool testCycle() {
  CHECK_RET(d3dEx==nullptr, false,"Invalid D3D context.");
  CHECK_RET(deviceEx==nullptr, false,"Invalid D3D device.");

  // Create dedicated device:
  DEBUG_MSG("Creating Direct3D9Ex context");
  CHECK_RESULT_RET(Direct3DCreate9Ex(D3D_SDK_VERSION, (IDirect3D9Ex **)&d3dEx), 
    false, "Cannot create Direct3D9Ex context" );

  D3DPRESENT_PARAMETERS d3dpp;
  ZeroMemory(&d3dpp, sizeof(d3dpp));
  d3dpp.Windowed = TRUE;
  d3dpp.BackBufferCount = 1;
  d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
  d3dpp.BackBufferWidth = 200; 
  d3dpp.BackBufferHeight = 200; 
  d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;
  d3dpp.hDeviceWindow = NULL;
  d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;

  DEBUG_MSG("Creating Device9Ex.");
  CHECK_RESULT_RET(d3dEx->CreateDevice(0, D3DDEVTYPE_HAL, NULL, 
    D3DCREATE_MULTITHREADED | D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_PUREDEVICE, 
    &d3dpp, &deviceEx), false, "Cannot create Device9Ex.");

  DEBUG_MSG("Setting lighting render state");
  CHECK_RESULT_RET(deviceEx->SetRenderState( D3DRS_LIGHTING, FALSE ), 
    false, "Cannot set lighting render state.");

  int width = 512;
  int height = 256;

  DEBUG_MSG("Creating SDI render surfaces of size "<<width<<"x"<<height);

  CHECK_RESULT_RET(deviceEx->CreateRenderTarget(width, height,
                                          D3DFMT_A8R8G8B8, D3DMULTISAMPLE_NONE, 0,
                                          TRUE, &renderSurface1, &renderSurfaceHandle1),
               false, "Cannot create Render surface 1 for SDIOutput");

  CHECK_RESULT_RET(deviceEx->CreateRenderTarget(width, height,
                                          D3DFMT_A8R8G8B8, D3DMULTISAMPLE_NONE, 0,
                                          TRUE, &renderSurface2, &renderSurfaceHandle2),
               false, "Cannot create Render surface 2 for SDIOutput");

  CHECK_RET(renderSurfaceHandle1 != nullptr, false, "Invalid shared handle for surface 1");
  CHECK_RET(renderSurfaceHandle2 != nullptr, false, "Invalid shared handle for surface 2");

  DEBUG_MSG("Initial render of SDI surface 1")
  CHECK_RESULT_RET(deviceEx->SetRenderTarget(0, renderSurface1), false, "Cannot set render target 1");
  CHECK_RESULT_RET(deviceEx->BeginScene(),false, "Cannot begin scene 1");
  CHECK_RESULT_RET(deviceEx->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(255,0,0), 1.0f, 0), false, "Cannot clear scene 1");
  CHECK_RESULT_RET(deviceEx->EndScene(), false, "Cannot end scene 1");

  DEBUG_MSG("Initial render of SDI surface 2")
  CHECK_RESULT_RET(deviceEx->SetRenderTarget(0, renderSurface2), false, "Cannot set render target 2");
  CHECK_RESULT_RET(deviceEx->BeginScene(), false, "Cannot begin scene 2");
  CHECK_RESULT_RET(deviceEx->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,255,0), 1.0f, 0), false, "Cannot clear scene 2");
  CHECK_RESULT_RET(deviceEx->EndScene(), false, "Cannot end scene 2");

  DEBUG_MSG("Test cycle performed successfully.")
  return true;
}

void releaseResources()
{
  DEBUG_MSG("Releasing resources.");

  SAFERELEASE(renderSurface1);
  SAFERELEASE(renderSurface2);
  renderSurfaceHandle1 = nullptr;
  renderSurfaceHandle2 = nullptr;

  SAFERELEASE(deviceEx);
  SAFERELEASE(d3dEx);
}

#ifdef WINDOWS_APP
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {
#else
int main(int argc, char *argv[]) {
#endif

  testCycle();
  releaseResources();

  DEBUG_MSG("Exiting.");
  return 0;
}

So, anyone has the beginning of an explanation on this, is the documentation incorrect or am I missing a point ? :-)


Solution

  • The docs already tell you what is happening:

    Per MSDN

    For a windowed-mode application, this handle will be the default target window for Present. If this handle is NULL, the focus window will be taken.

    So it's using whatever window happens to be in focus at the time you call Present.

    Note this ambiguity was fixed for Direct3D 10 or later via DXGI. The window handle is only needed for creating the swap chain, and Direct3D device can be created independently of the swap chain (unless you are using the somewhat clunky helper function D3D11CreateDeviceAndSwapChain which does both operations at the same time). See Anatomy of Direct3D 11 Create Device.