2

I want to get a fast access to the pixels of a game which uses directX9 or 10. Therefore I used the D3D9 library. Do I have to use the D3D10 library when it is a directX10 game? If this is true, the next part is irrelevant.

The following code works except I'm only get zeros in qDebug("%.2f, %.2f, %.2f, %.2f", pixel[0].r, pixel[0].g, pixel[0].b, pixel[0].a); although it should have an other color and I have no clue why. Is there an error in swapping the data?

Initialization:

IDirect3D9 *m_d3d;
IDirect3DDevice9 *m_d3ddev;
D3DDISPLAYMODE *m_d3ddm;
HRESULT hr;
m_d3d = Direct3DCreate9(D3D_SDK_VERSION);
if (m_d3d == NULL)
{
    qFatal("Cannot create Direct3D!");
}

m_d3ddm = (D3DDISPLAYMODE *)calloc(1, sizeof(D3DDISPLAYMODE));
hr = m_d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, m_d3ddm);
if (hr != D3D_OK)
{
    m_d3d->Release();
    qFatal("Cannot get DisplayMode!");
}

D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = m_target;
d3dpp.BackBufferWidth = m_d3ddm->Width;
d3dpp.BackBufferHeight = m_d3ddm->Height;
d3dpp.BackBufferFormat = m_d3ddm->Format;

hr = m_d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, m_target, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &m_d3ddev);
if (FAILED(hr))
{
    m_d3d->Release();
    qFatal("Failed  CreateDevice!");
}
else 
{
    qDebug("Created Device with %i * %i", m_d3ddm->Width, m_d3ddm->Height);
}

Capture:

IDirect3DSurface9 *renderTarget= NULL;
IDirect3DSurface9 *destTarget = NULL;
hr = m_d3ddev->GetRenderTarget(0, &renderTarget);
if (FAILED(hr))
{
    qFatal("Failed  GetRenderTarget!");
}
hr = m_d3ddev->CreateOffscreenPlainSurface(m_d3ddm->Width, m_d3ddm->Height, m_d3ddm->Format, D3DPOOL_SYSTEMMEM, &destTarget, NULL);
if (FAILED(hr))
{
    qFatal("Failed  CreateOffscreenPlainSurface!");
}
hr = m_d3ddev->GetRenderTargetData(renderTarget, destTarget);
if (FAILED(hr))
{
    qFatal("Failed  GetRenderTargetData!");
}

D3DLOCKED_RECT lr;
ZeroMemory(&lr, sizeof(D3DLOCKED_RECT));
hr = destTarget->LockRect(&lr, 0, D3DLOCK_READONLY);
if (FAILED(hr))
{
    qFatal("Cannot lock rect!");
}
ARGB *pixel = (ARGB *)lr.pBits;
if (!pixel)
{
    qFatal("No data!");
}
qDebug("%.2f, %.2f, %.2f, %.2f", pixel[0].r, pixel[0].g, pixel[0].b, pixel[0].a);

hr = destTarget->UnlockRect();
if (FAILED(hr))
{
    qFatal("Cannot unlock rect!");
}
renderTarget->Release();
destTarget->Release(); 

Cleaning

m_d3ddev->Release();
m_d3d->Release();

delete m_d3ddm;
delete m_d3d;
delete m_d3ddev;
Jens Metzner
  • 109
  • 1
  • 13
  • You're only printing the first pixel. Are you absolutely sure that ONE pixel is not black? Try printing more of them in a for-loop.. – Brandon Jan 18 '15 at 17:05
  • @Brandon Yes, I checked it and it is an absolutely black screen:(. Do I use the wrong buffer? – Jens Metzner Jan 18 '15 at 18:01
  • What function are you calling your above capture code in? `Present`? `EndScene`? Your capture code looks fine to me. I actually use the exact same code in my Direct-X hook. – Brandon Jan 18 '15 at 21:34
  • This is my full code. So do I understand it right that I have to call `Present()` before I can swap buffers? – Jens Metzner Jan 18 '15 at 21:47

1 Answers1

3

This code is a simulation of what you should have.. It shows that the backbuffer is actually being captured. I created the device the exact same way you did. I capture the back-buffer the same way..

To test if the back-buffer is actually captured, I have set the window background to a navy-blue colour. Then when I call the capture function, I compare all the colours in the buffer to the original background colour.. This lets me know if a single pixel is different than the background..

If it is different, we "possibly" failed to capture the background. Otherwise, we are good..

I've tested the below and it indeed captures the backbuffer perfectly fine.. I even saved the backbuffer to a bitmap to see if it captures it and it does..

All in all, there's nothing wrong with your code, but I cannot guarantee that you are using it correctly.. In other words, you haven't shown me where you call your back-buffer capture code.. There are many places it can be called, but the best places are either in a hook to EndScene or a hook to Present. If you are using this on your own application, then call it in your onDraw/onUpdate function..

#include <tchar.h>
#include <windows.h>
#include <d3d9.h>
#include <d3dx9.h>
#include <iostream>

HRESULT capture(IDirect3DDevice9* m_d3ddev, void* buffer, int& width, int& height, D3DFORMAT format);


void onUpdate(HWND hwnd, IDirect3D9* d3d9, IDirect3DDevice9* d3ddev9, D3DFORMAT format)
{
    d3ddev9->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 40, 100), 1.0f, 0);
    d3ddev9->BeginScene();
    //All drawing goes here..
    d3ddev9->EndScene();
    d3ddev9->Present(NULL, NULL, NULL, NULL);  //Swaps the back and front buffers. Displays all drawing on the screen..


    RECT rect;
    GetClientRect(hwnd, &rect);

    int width = rect.right - rect.left, height = rect.bottom - rect.top;
    unsigned char* buffer = new unsigned char[width * height * 4];
    capture(d3ddev9, buffer, width, height, format);

    unsigned char* px = buffer;

    for(int i = 0; i < height; ++i)
    {
        for(int j = 0; j < width; ++j)
        {
            unsigned char B = *px++;
            unsigned char G = *px++;
            unsigned char R = *px++;
            unsigned char A = *px++;

            if(D3DCOLOR_XRGB(R, G, B) != D3DCOLOR_XRGB(0, 40, 100))
            {
                MessageBox(NULL, "FAILED.. Colour differs from original background..", NULL, 0);
            }
        }
    }

    delete[] buffer;
}

int onDisplay(HWND hwnd)
{
    IDirect3D9* d3d9 = NULL;
    IDirect3DDevice9* d3ddev9 = NULL;
    D3DPRESENT_PARAMETERS Parameters = {0};
    d3d9 = Direct3DCreate9(D3D_SDK_VERSION);

    D3DDISPLAYMODE* dispMode = new D3DDISPLAYMODE();
    d3d9->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, dispMode);

    Parameters.Windowed = true;
    Parameters.SwapEffect = D3DSWAPEFFECT_DISCARD;
    Parameters.hDeviceWindow = hwnd;
    Parameters.BackBufferFormat = dispMode->Format;
    d3d9->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &Parameters, &d3ddev9);
    delete dispMode;

    MSG messages;
    ShowWindow(hwnd, SW_SHOW);

    while(true)
    {
        if(PeekMessage(&messages, NULL, 0, 0, PM_REMOVE) > 0)
        {
            if(messages.message != WM_QUIT)
            {
                TranslateMessage(&messages);
                DispatchMessageW(&messages);
                continue;
            }
            break;
        }
        else
        {
            onUpdate(hwnd, d3d9, d3ddev9, Parameters.BackBufferFormat);
        }
    }

    if(d3ddev9) d3ddev9->Release();
    if(d3d9) d3d9->Release();
    return messages.wParam;
}


LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hwnd, message, wParam, lParam);
    }

    return 0;
}

int WINAPI WinMain(HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nCmdShow)
{
    WNDCLASSEX wincl;
    wincl.hInstance = hThisInstance;
    wincl.lpszClassName = "D3DWindow";
    wincl.lpfnWndProc = WindowProcedure;
    wincl.style = CS_DBLCLKS;
    wincl.cbSize = sizeof(WNDCLASSEX);
    wincl.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wincl.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
    wincl.hCursor = LoadCursor(NULL, IDC_ARROW);
    wincl.lpszMenuName = NULL;
    wincl.cbClsExtra = 0;
    wincl.cbWndExtra = 0;
    wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

    if(!RegisterClassEx(&wincl)) return 0;
    HWND hwnd = CreateWindowEx(0, "D3DWindow", "D3D9: Window", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 544, 375, HWND_DESKTOP, NULL, hThisInstance, NULL);
    return onDisplay(hwnd);
}


HRESULT capture(IDirect3DDevice9* m_d3ddev, void* buffer, int& width, int& height, D3DFORMAT format)
{
    IDirect3DSurface9 *renderTarget= NULL;
    IDirect3DSurface9 *destTarget = NULL;
    HRESULT hr = m_d3ddev->GetRenderTarget(0, &renderTarget);
    hr = m_d3ddev->CreateOffscreenPlainSurface(width, height, format, D3DPOOL_SYSTEMMEM, &destTarget, NULL);
    if(FAILED(hr))
    {
        printf("Failed  CreateOffscreenPlainSurface!");
    }
    hr = m_d3ddev->GetRenderTargetData(renderTarget, destTarget);
    if(FAILED(hr))
    {
        printf("Failed  GetRenderTargetData!");
    }

    D3DLOCKED_RECT lr;
    ZeroMemory(&lr, sizeof(D3DLOCKED_RECT));
    hr = destTarget->LockRect(&lr, 0, D3DLOCK_READONLY);
    if(FAILED(hr))
    {
        printf("Cannot lock rect!");
    }

    if(lr.pBits)
    {
        memcpy(buffer, lr.pBits, width * height * 4);
    }

    hr = destTarget->UnlockRect();
    if(FAILED(hr))
    {
        printf("Cannot unlock rect!");
    }
    renderTarget->Release();
    destTarget->Release();
    return hr;
}
Brandon
  • 22,723
  • 11
  • 93
  • 186
  • Updated to keep exactly the same original code you have in your OP.. This shows that there is absolutely nothing wrong with the code you are showing in the OP.. – Brandon Jan 18 '15 at 23:00
  • Ok, when I directly use `Clear()`, `BeginScene()` etc. in my code, then I can read the right color. Now my question is: How can I get this information from an other application/game? What do I need to call to get access to the buffer? – Jens Metzner Jan 19 '15 at 16:35
  • You need to hook the other game's EndScene function OR Present function. In the hook, you will call capture to capture the back-buffer. – Brandon Jan 19 '15 at 16:41