没有设备窗口的 DirectX9

Posted

技术标签:

【中文标题】没有设备窗口的 DirectX9【英文标题】:DirectX9 without device window 【发布时间】:2017-01-13 10:33:41 【问题描述】:

我一直认为我们需要先创建一个窗口才能创建 DirectX9 设备。实际上,这就是我对 CreateDevice 方法上的official documentation 的理解:

hFocusWindow [in] 类型:HWND (...) 对于窗口模式,仅当 pPresentationParameters 的 hDeviceWindow 成员设置为有效的非 NULL 值时,此参数才可能为 NULL。

现在,我试了一下,似乎下面的代码对我有用(例如,我得到了一个有效的设备指针,我可以用它来做 soem 渲染目标渲染,即使我没有随时提供任何窗口句柄):

#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;

那么,任何人对此都有解释的开始,是文档不正确还是我遗漏了一点? :-)

【问题讨论】:

【参考方案1】:

文档已经告诉你发生了什么:

每MSDN

对于窗口模式应用程序,此句柄将是 Present 的默认目标窗口。如果此句柄为 NULL,则将获取焦点窗口。

所以它会使用您调用Present 时恰好处于焦点的任何窗口。

请注意,此歧义已通过 DXGI 为 Direct3D 10 或更高版本修复。只有在创建交换链时才需要窗口句柄,并且可以独立于交换链创建 Direct3D 设备(除非您使用有点笨重的帮助函数 D3D11CreateDeviceAndSwapChain,它同时执行这两个操作)。见Anatomy of Direct3D 11 Create Device。

【讨论】:

这是一个更合适的答案,实际上我现在可以理解正在发生的事情:DirectX API 必须选择您报告的当前焦点窗口。可能需要做一些测试来检查,但这对我来说已经足够了。谢谢你的帮助 ! :-) 为了完整起见:无论如何,我仍在尝试使用 NULL 窗口初始化我的 DirectX 设备,在这种情况下我注意到我生成的表面当时不知何故“已损坏”我的桌面内容!...所以在我的情况下,这个设备似乎没有被正确“隔离”,而且这个设置对我来说并不是真的可用。 @Chuck Walbourn 在您调用Present 时,它真的是焦点窗口,还是您在CreateDevice 中指定为hFocusWindow 的窗口?打电话? 我刚刚测试了一下,似乎hFocusWindow形成了CreateDevice调用。【参考方案2】:

文档告诉您必须做什么,但没有具体说明如果您违反规则会发生什么。

它似乎对您有用,但您是否为在多显示器设置上测试它而烦恼?或者当另一个 DirectX 应用程序正在运行时?这只是两个随机变化。

【讨论】:

好吧,我同意这是一个极限情况,它的行为没有明确定义,但同时,我也相信 DirectX API 在错误通知方面非常清楚:你调用 DirectX函数,你得到一个 HRESULT,如果这是成功的,那么显然,你可以假设你的操作成功了。如果您违反任何规则,那么您根本不应该获得成功...... @EmmanuelRoche:不,抱歉,这不是严格保证的。微软不是邪恶的,它会在简单快速的时候验证参数,但是你这边的一些错误不容易被检测到并且不会导致错误返回值。

以上是关于没有设备窗口的 DirectX9的主要内容,如果未能解决你的问题,请参考以下文章

SimController(设备模拟器窗口)在我调试 SWF 时消失

按下窗口上部时防止Ios设备滚动到顶部

使用移动设备大小的菜单按钮后,导航栏消失(在全尺寸窗口中)

Windows无法访问指定设备,路径或文件怎么办

在移动设备中更改方向时需要关闭弹出窗口

解决Vitrual Box Linux无法自适应缩放窗口