static image::Image *
getRenderTargetImage(IDirect3DDevice9 *pDevice,
                     IDirect3DSurface9 *pRenderTarget) {
    image::Image *image = NULL;
    D3DSURFACE_DESC Desc;
    IDirect3DSurface9 *pStagingSurface = NULL;
    HRESULT hr;

    if (!pRenderTarget) {
        return NULL;
    }

    hr = pRenderTarget->GetDesc(&Desc);
    assert(SUCCEEDED(hr));

    hr = pDevice->CreateOffscreenPlainSurface(Desc.Width, Desc.Height, Desc.Format, D3DPOOL_SYSTEMMEM, &pStagingSurface, NULL);
    if (FAILED(hr)) {
        goto no_staging;
    }

    hr = pDevice->GetRenderTargetData(pRenderTarget, pStagingSurface);
    if (FAILED(hr)) {
        goto no_rendertargetdata;
    }

    image = getSurfaceImage(pDevice, pStagingSurface);

no_rendertargetdata:
    pStagingSurface->Release();
no_staging:
    return image;
}
static image::Image *
getRenderTargetImage(IDirect3DDevice8 *pDevice,
                     IDirect3DSurface8 *pRenderTarget) {
    HRESULT hr;

    if (!pRenderTarget) {
        return NULL;
    }

    D3DSURFACE_DESC Desc;
    hr = pRenderTarget->GetDesc(&Desc);
    assert(SUCCEEDED(hr));

    com_ptr<IDirect3DSurface8> pStagingSurface;
    hr = pDevice->CreateImageSurface(Desc.Width, Desc.Height, Desc.Format, &pStagingSurface);
    if (FAILED(hr)) {
        return NULL;
    }

    hr = pDevice->CopyRects(pRenderTarget, NULL, 0, pStagingSurface, NULL);
    if (FAILED(hr)) {
        std::cerr << "warning: GetRenderTargetData failed\n";
        return NULL;
    }

    return getSurfaceImage(pDevice, pStagingSurface);
}
void
dumpFramebuffer(JSONWriter &json, IDirect3DDevice7 *pDevice)
{
    HRESULT hr;

    json.beginMember("framebuffer");
    json.beginObject();

    com_ptr<IDirectDrawSurface7> pRenderTarget;
    hr = pDevice->GetRenderTarget(&pRenderTarget);
    if (SUCCEEDED(hr) && pRenderTarget) {
        image::Image *image;
        image = getSurfaceImage(pDevice, pRenderTarget);
        if (image) {
            json.beginMember("RENDER_TARGET_0");
            json.writeImage(image);
            json.endMember(); // RENDER_TARGET_*
        }

        // Search for a depth-stencil attachment
        DDSCAPS2 ddsCaps;
        ZeroMemory(&ddsCaps, sizeof ddsCaps);
        ddsCaps.dwCaps = DDSCAPS_ZBUFFER;
        com_ptr<IDirectDrawSurface7> pDepthStencil;
        hr = pRenderTarget->GetAttachedSurface(&ddsCaps, &pDepthStencil);
        if (SUCCEEDED(hr) && pDepthStencil) {
            std::cerr << "found ZS!!\n";
            image = getSurfaceImage(pDevice, pDepthStencil);
            if (image) {
                json.beginMember("DEPTH_STENCIL");
                json.writeImage(image);
                json.endMember(); // DEPTH_STENCIL
            }
        }
    }

    json.endObject();
    json.endMember(); // framebuffer
}
image::Image *
getRenderTargetImage(IDirect3DDevice7 *pDevice) {
    HRESULT hr;

    com_ptr<IDirectDrawSurface7> pRenderTarget;
    hr = pDevice->GetRenderTarget(&pRenderTarget);
    if (FAILED(hr)) {
        return NULL;
    }
    assert(pRenderTarget);

    return getSurfaceImage(pDevice, pRenderTarget);
}
void
dumpFramebuffer(JSONWriter &json, IDirect3DDevice9 *pDevice)
{
    HRESULT hr;

    json.beginMember("framebuffer");
    json.beginObject();

    D3DCAPS9 Caps;
    pDevice->GetDeviceCaps(&Caps);

    for (UINT i = 0; i < Caps.NumSimultaneousRTs; ++i) {
        IDirect3DSurface9 *pRenderTarget = NULL;
        hr = pDevice->GetRenderTarget(i, &pRenderTarget);
        if (FAILED(hr)) {
            continue;
        }

        if (!pRenderTarget) {
            continue;
        }

        image::Image *image;
        image = getRenderTargetImage(pDevice, pRenderTarget);
        if (image) {
            char label[64];
            _snprintf(label, sizeof label, "RENDER_TARGET_%u", i);
            json.beginMember(label);
            json.writeImage(image, "UNKNOWN");
            json.endMember(); // RENDER_TARGET_*
        }

        pRenderTarget->Release();
    }

    IDirect3DSurface9 *pDepthStencil = NULL;
    hr = pDevice->GetDepthStencilSurface(&pDepthStencil);
    if (SUCCEEDED(hr) && pDepthStencil) {
        image::Image *image;
        image = getSurfaceImage(pDevice, pDepthStencil);
        if (image) {
            json.beginMember("DEPTH_STENCIL");
            json.writeImage(image, "UNKNOWN");
            json.endMember(); // RENDER_TARGET_*
        }
    }


    json.endObject();
    json.endMember(); // framebuffer
}
static image::Image *
getTextureImage(IDirect3DDevice8 *pDevice,
                IDirect3DBaseTexture8 *pTexture,
                D3DCUBEMAP_FACES FaceType,
                UINT Level)
{
    HRESULT hr;

    if (!pTexture) {
        return NULL;
    }

    com_ptr<IDirect3DSurface8> pSurface;

    D3DRESOURCETYPE Type = pTexture->GetType();
    switch (Type) {
    case D3DRTYPE_TEXTURE:
        assert(FaceType == D3DCUBEMAP_FACE_POSITIVE_X);
        hr = reinterpret_cast<IDirect3DTexture8 *>(pTexture)->GetSurfaceLevel(Level, &pSurface);
        break;
    case D3DRTYPE_CUBETEXTURE:
        hr = reinterpret_cast<IDirect3DCubeTexture8 *>(pTexture)->GetCubeMapSurface(FaceType, Level, &pSurface);
        break;
    default:
        /* TODO: support volume textures */
        return NULL;
    }
    if (FAILED(hr) || !pSurface) {
        return NULL;
    }

    D3DSURFACE_DESC Desc;
    hr = pSurface->GetDesc(&Desc);
    assert(SUCCEEDED(hr));

    if (Desc.Pool != D3DPOOL_DEFAULT ||
        Desc.Usage & D3DUSAGE_DYNAMIC) {
        // Lockable texture
        return getSurfaceImage(pDevice, pSurface);
    } else if (Desc.Usage & D3DUSAGE_RENDERTARGET) {
        // Rendertarget texture
        return getRenderTargetImage(pDevice, pSurface);
    } else {
        // D3D constraints are such there is not much else that can be done.
        return NULL;
    }
}
void
dumpFramebuffer(StateWriter &writer, IDirect3DDevice8 *pDevice)
{
    HRESULT hr;

    writer.beginMember("framebuffer");
    writer.beginObject();

    com_ptr<IDirect3DSurface8> pRenderTarget;
    hr = pDevice->GetRenderTarget(&pRenderTarget);
    if (SUCCEEDED(hr) && pRenderTarget) {
        image::Image *image;
        image = getRenderTargetImage(pDevice, pRenderTarget);
        if (image) {
            writer.beginMember("RENDER_TARGET_0");
            writer.writeImage(image);
            writer.endMember(); // RENDER_TARGET_*
            delete image;
        }
    }

    com_ptr<IDirect3DSurface8> pDepthStencil;
    hr = pDevice->GetDepthStencilSurface(&pDepthStencil);
    if (SUCCEEDED(hr) && pDepthStencil) {
        image::Image *image;
        image = getSurfaceImage(pDevice, pDepthStencil);
        if (image) {
            writer.beginMember("DEPTH_STENCIL");
            writer.writeImage(image);
            writer.endMember(); // RENDER_TARGET_*
            delete image;
        }
    }

    writer.endObject();
    writer.endMember(); // framebuffer
}