1
votes

I'm using an engine which utilizes DirectX9.

It creates it's render targets using:

D3DXCreateTexture(
    pD3DDevice, width, height, 1, 
    D3DUSAGE_RENDERTARGET, D3DFMT_X8R8G8B8, 
    D3DPOOL_DEFAULT, &pRT
)


And all other textures are created like so:

D3DXCreateTexture(
    pD3DDevice, width, height,
    1, 0, D3DFMT_A8R8G8B8,
    D3DPOOL_MANAGED, &pTex
)

It is worth noting that the backbuffer is also D3DFMT_X8R8G8B8 so I am unsure why textures are created differently... but I suppose "if it ain't broke, don't fix it," right? All textures appear to load/render just fine. For more details on how this engine operates, here is it's main graphics module's code.
(mostly note the Target_Create and Texture_Create methods)...

HOWEVER, I've had a ton of trouble trying to stick with these rules while trying to convert a render target texture into a regular texture. The engine has no built-in functionality for this, and render targets are treated as separate objects from textures... And in turn, this limits my ability to actually USE anything which requires a render target (ie, pixel shaders, etc). The end result is that I need to find a way to copy this render target data into a texture created as above, which the engine could use without any conflict.

I have tried something like the following:

inline PVOID LockedBits(LPDIRECT3DSURFACE9 surface, UINT w, UINT h, INT* size)
{
    D3DLOCKED_RECT lr;
    RECT rc = {0, 0, w, h};
    surface->LockRect(&lr, &rc, 0);
    if(size) *size = (w*h) * 4;
    return lr.pBits;
}
inline PVOID LockedBits(LPDIRECT3DTEXTURE9 texture, UINT w, UINT h, INT* size)
{
    D3DLOCKED_RECT lr;
    RECT rc = {0, 0, w, h};
    texture->LockRect(0, &lr, &rc, 0);
    if(size) *size = (w*h) * 4;
    return lr.pBits;
}

LPDIRECT3DTEXTURE9 CloneTextureFromTarget(LPDIRECT3DDEVICE9 device, LPDIRECT3DTEXTURE9 target, UINT w, UINT h)
{
    D3DDISPLAYMODE dm;
    device->GetDisplayMode(0, &dm);

    // Create source and destination surfaces and copy rendertarget
    LPDIRECT3DSURFACE9 dstSurf = NULL, srcSurf = NULL;
    device->CreateOffscreenPlainSurface(w, h, dm.Format, D3DPOOL_SYSTEMMEM, &dstSurf, NULL);
    target->GetSurfaceLevel(0, &srcSurf);
    device->GetRenderTargetData(srcSurf, dstSurf);
    SafeRelease(&srcSurf);

    // Create destination texture
    LPDIRECT3DTEXTURE9 dstTexture = NULL;
    D3DXCreateTexture(device, w, h, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &dstTexture);

    // Get bits for destination surface and texture
    INT dwSrc, dwDst;
    PVOID pBitsSrc = LockedBits(dstSurf, w, h, &dwSrc);
    PVOID pBitsDst = LockedBits(dstTexture, w, h, &dwDst);

    // Copy bits from surface to texture
    RtlCopyMemory(pBitsSrc, pBitsDst, dwSrc);
    dstTexture->UnlockRect(0);
    dstSurf->UnlockRect();
    SafeRelease(&dstSurf);

    /* Just to double-check if it worked... */
    D3DXSaveTextureToFileA("C:\\outSrc.png", D3DXIFF_PNG, target, NULL);
    D3DXSaveTextureToFileA("C:\\outDst.png", D3DXIFF_PNG, dstTexture, NULL);

    // Return the result
    return dstTexture;
}

The result of this code is that both textures are properly saved to disk and come out as expected, but refuse to properly render...

Am I doing something wrong? What's the best way to achieve a perfect copy of this data into a new texture created with D3DFMT_A8R8G8B8 and D3DPOOL_MANAGED?

1
You can create your rendertarget with D3DFMT_A8R8G8B8, only the backbuffer of the device should be created with D3DFMT_X8R8G8B8. Moreover this rendertarget normally can be used for rendering, but not for locking-operations. Do you need a lock or do you only want to render the rendertarget onto the backbuffer?Gnietschow
If I create it using D3DFMT_A8R8G8B8 then I would still require a means of stripping it away into a new D3DPOOL_MANAGED texture, since I will no longer have any use for the rendertarget object and would like to free up any resources it uses. The engine treats textures and rendertargets as separate objects, where rendertargets store/consume more resources... And for rendering, my application utilizes an array of texture objects, so the rendertarget is merely used during initialization to create new textures (in a pixel shader) to add to this array of D3DPOOL_MANAGED textures.RectangleEquals
So as far as locking the rendertarget goes... As long as I can still (somehow) clone it's data into a newly created D3DPOOL_MANAGED texture, then no it does not require locking support... Otherwise, yes it does. Any idea how I could safely clone this data into a new texture? Also, does the code above work for you? For me, it appears to correctly save the image to disk, but somehow fails to render in my application.RectangleEquals
I have no idea, why your code doesn't work. Have you tried to work alternatively with StretchRect to copy your rendertarget? In one of my applications I used it to get the data of a rendertarget and it worked well.Gnietschow
According to the MSDN documentation (driver support section), you cannot use StretchRect to go from a RT texture to a normal texture. Only off-screen plain textures can go into a regular texture, and it doesn't even allow copying from RT to off-screen plain.RectangleEquals

1 Answers

0
votes

TBH change your copy method as follows:

char* pSrc = (char*)pBitsSrc;
char* pDst = (char*)pBitsDst;
int p = 0;
int pMax = w * h;
while( p < pMax )
{
    // Copy B
    *pDst = *pSrc;
    pDst++;
    pSrc++;
    // Copy G
    *pDst = *pSrc;
    pDst++;
    pSrc++;
    // Copy R
    *pDst = *pSrc;
    pDst++;
    pSrc++;
    // Set A
    *pDst = 0xff;
    pDst++;
    pSrc++;
    p++;
}

Or failing that simply create the destination texture as a D3DFMT_X8R8G8B8.