现在的位置: 首页 > 综合 > 正文

D3D9中设备提交的实现细节 The Implementation Level Detail of IDirect3DDevice9::Present

2013年02月25日 ⁄ 综合 ⁄ 共 6368字 ⁄ 字号 评论关闭

The Implementation Level Detail of IDirect3DDevice9::Present

首先我们来看d3d documentation里对此函数的描述

IDirect3DDevice9::Present

Presents the contents of the next buffer in the sequence of back buffers owned by the device.

HRESULT Present(
  CONST RECT * pSourceRect,
  CONST RECT * pDestRect,
  HWND hDestWindowOverride,
  CONST RGNDATA * pDirtyRegion
);

Parameters

pSourceRect

[in] Pointer to a value that must be NULL unless the swap chain was created with D3DSWAPEFFECT_COPY. pSourceRect is a pointer to a RECT structure containing the source rectangle. If NULL, the entire source surface is presented. If the rectangle exceeds the source surface, the rectangle is clipped to the source surface.

pDestRect

[in] Pointer to a value that must be NULL unless the swap chain was created with D3DSWAPEFFECT_COPY. pDestRect is a pointer to a RECT structure containing the destination rectangle, in window client coordinates. If NULL, the entire client area is filled. If the rectangle exceeds the destination client area, the rectangle is clipped to the destination client area.

 

Present最常见的使用方式是g_pd3dDevice->Present( NULL, NULL, NULL, NULL );他会自动把整个back buffer填充到front buffer(如果是full screen则填充整个front buffer,窗口模式只填充窗口client area对应部分的front buffer)

附录1是一个简单的d3d9程序(SimpleLighting.cpp,单文件),当我们运行程序的时候,back buffer的大小刚好==client area的大小,此时如果我们通过鼠标更改窗口大小,那么client area随之改变,显示的画面也随之变化。一开始我怀疑back buffer自动重建成跟client area同样大小,我们可以通过下面的代码来查看back buffer的大小。事实上back buffer并没有发生任何改变,

IDirect3DSurface9 *pBackBuffer;

HRESULT hr = g_pd3dDevice->GetBackBuffer( 0, 0, D3DBACKBUFFER_TYPE_MONO, &pBackBuffer );

D3DSURFACE_DESC desc;

pBackBuffer->GetDesc( &desc );

desc.Width; desc.Height;

if ( SUCCEEDED(hr) )

pBackBuffer->Release();

也就是说,IDirect3DDevice9::Present函数支持source rectdest rect不匹配的提交操作(副作用亦很明显)。稍后我们会进行更多的讨论,下面先介绍几个核心概念——swap chainfront buffer, back buffer, swapeffect

swap chain- IDirect3DSwapChain9

交换链,对应的接口是IDirect3DSwapChain9,是整个Directx 3D中最核心的接口。D3d程序至少包含一个swap chain,在调用CreateDevice时自动创建,其中的D3DPRESENT_PARAMETERS * pPresentationParameters参数设置将要创建的IDirect3DSwapChain9的属性。首先我们看看这个接口提供的函数,我们可以找到Present函数,其实不难猜到IDirect3DDevice9::Present函数内部就是调用了默认IDirect3DSwapChain9Present函数。进一步讲,IDirect3DDevice9GetBackBufferGetFrontBufferData等函数都调用了IDirect3DSwapChain9的接口所以一般有一个UINT  iSwapChain参数来指示调用哪个swap chain,0代表默认CreateDevice时创建的IDirect3DSwapChain9
IDirect3DSwapChain9::GetBackBuffer

Retrieves a back buffer from the swap chain of the device.

IDirect3DSwapChain9::GetDevice
Retrieves the device associated with the swap chain.
IDirect3DSwapChain9::GetDisplayMode
Retrieves the display mode's spatial resolution, color resolution, and refresh frequency.
IDirect3DSwapChain9::GetFrontBufferData
Generates a copy of the swapchain's front buffer and places that copy in a system memory buffer provided by the application.
IDirect3DSwapChain9::GetPresentParameters
Retrieves the presentation parameters associated with a swap chain.
IDirect3DSwapChain9::GetRasterStatus
Returns information describing the raster of the monitor on which the swap chain is presented.
IDirect3DSwapChain9::Present
Presents the contents of the next buffer in the sequence of back buffers owned by the swap chain.

Front buffer

A graphics adapter holds a pointer to a surface that represents the image being displayed on the monitor, called a front buffer.其中的内容会直接发送给显示器显示,其大小等于screen size,可以通过下面代码来获取。首先我们必须创建一个CpuIDirect3DSurface9,然后通过GetFrontBufferData填充其内容。如果直接绘制到Front buffer上,会使画面不连续,撕裂(tearing)等等。当显示器(monitor)刷新时,显卡把front buffer的内容发送给显示器。一般的显示器刷新频率在60Hz100Hz之间,这跟计算机其他部件的工作频率相差甚远。如果程序在显示器刷新的过程中更新了front buffer,那么显示出来的上半部分画面是旧的,下半部分画面是新的,这就是撕裂(tearing)

D3DDISPLAYMODE dm;

    g_pD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &dm );

    IDirect3DSurface9* pSurface;

    g_pd3dDevice->CreateOffscreenPlainSurface( dm.Width, dm.Height,

       D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &pSurface, NULL);

    g_pd3dDevice->GetFrontBufferData(0, pSurface);  

D3DXSaveSurfaceToFile( _T("Desktop.bmp"), D3DXIFF_BMP, pSurface, NULL, NULL );

pSurface->Release();

解决这个问题有两种方案:
1,只在垂直回扫(vertical retrace)时允许更新front buffer,在每一次刷新,显示器从上到下,从左到右的移动光头(light pin),一直到右下角为止。然后光头又移动回显示器的左上角(vertical retrace),为下一次刷新做准备。这样可以避免撕裂,但是垂直回扫(vertical retrace)时间往往不足以绘制复杂场景。
2, 后台缓存(back buffering), Back buffering is the process of drawing a scene to an off-screen surface, called a back buffer. Note that any surface other than the front buffer is called an off-screen surface because it is never directly viewed by the monitor. By using a back buffer, an application has the freedom to render a scene whenever the system is idle (that is, no windows messages are waiting) without having to consider the monitor's refresh rate. Back buffering brings in an additional complication of how and when to move the back buffer to the front buffer.解决方法是在垂直回扫(vertical retrace)时把数据从back buffer移动到front buffer,两种移动方式,直接copy或者Flipping Surfaces,这可以通过D3DPRESENT_PARAMETERSSwapEffect参数来指定,下面我们将进行解释。

D3DSWAPEFFECT

1.  D3DSWAPEFFECT_COPY
顾名思义,就是在垂直回扫(vertical retrace)时把back buffer的数据copyfront buffer,确保back buffer的数据没有任何变化。可以通过Present的参数pSourceRectpDestRect来指定传输的区间,两者大小可以不匹配(stretch blits,会产生aliasing)。一般来说此时swap chain只有一个back buffer。当swap chainwindow swap chain时,d3d自动跟踪窗口客户区起始位置,pDestRect里采用client coordinate
2.  D3DSWAPEFFECT_FLIP
翻转,因为显卡的front buffer只是一个指针,所以我们可以直接交换front bufferback buffer的指针来达到更新front buffer的目的(full screen swap chain起作用,如果是window swap chain则会有极大的副作用)。工作方式如下图所示,front buffer跟所有的back buffer连成一个环状链表。

前后台缓冲交换图 
3.  D3DSWAPEFFECT_DISCARD
不难看出,如果对window swap chain采用D3DSWAPEFFECT_FLIP或者对full screen swap chain采用D3DSWAPEFFECT_COPY都会带来性能上的损失。D3DSWAPEFFECT_DISCARD则让驱动(display driver)选择最优效的presentation technique。另外,只有这个参数下才能设置MultiSampleType
4.   D3DSWAPEFFECT_OVERLAY
不介绍
这样,我们应该知道为什么只有在D3DSWAPEFFECT_COPY中才能设定pSourceRectpDestRect
 

D3DPRESENT_PARAMETERS:PresentationInterval

前面我们讲到了,只有在垂直回扫(vertical retrace)时才能把back buffer的数据传输到front buffer(copy or flip)。这会使得fps的上限等于显示器的刷新频率,有时候我们想知道程序的真实fps,我们可以设定D3DPRESENT_PARAMETERS:PresentationIntervalD3DPRESENT_INTERVAL_IMMEDIATE,立即提交。

 

附录 1,示例代码

//=============================================================================

// Desc: 简单灯光范例程序

//=============================================================================

 

#include <d3dx9.h>

#include <stdio.h>

#include <tchar.h>

 

//-----------------------------------------------------------------------------

// Desc: 全局变量

//-----------------------------------------------------------------------------

LPDIRECT3D9               g_pD3D         = NULL;   //Direct3D对象

LPDIRECT3DDEVICE9         g_pd3dDevice   = NULL;   //Direct3D设备对象

LPDIRECT3DVERTEXBUFFER9   g_pVB          = NULL;   //顶点缓冲区对象

 

//-----------------------------------------------------------------------------

// Desc: 顶点结构

//-----------------------------------------------------------------------------

struct CUSTOMVERTEX

{

    D3DXVECTOR3 position;   //顶点位置

    D3DXVECTOR3 normal;     //顶点法线

};

#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_NORMAL)

 

抱歉!评论已关闭.