Cách chụp màn hình trong DirectX 9 thành bitmap thô trong bộ nhớ mà không cần sử dụng D3DXSaveSurfaceToFile


8

Tôi biết rằng trong OpenGL tôi có thể làm một cái gì đó như thế này

glReadBuffer( GL_FRONT );
glReadPixels( 0, 0, _width, _height, GL_RGB, GL_UNSIGNED_BYTE, _buffer ); 

Và nó khá nhanh, tôi nhận được bitmap thô trong _buffer. Khi tôi cố gắng làm điều này trong DirectX. Giả sử rằng tôi có một đối tượng D3DDevice, tôi có thể làm một cái gì đó như thế này

if (SUCCEEDED(D3DDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &pBackbuffer))) {
   HResult hr = D3DXSaveSurfaceToFileA(filename, D3DXIFF_BMP, pBackbuffer, NULL, NULL); 

Nhưng D3DXSaveSurfaceToFile khá chậm và dù sao tôi cũng không cần phải ghi lại vào đĩa, vì vậy tôi đã tự hỏi liệu có cách nào nhanh hơn để làm điều này không.

EDIT: Tôi chỉ nhắm mục tiêu các ứng dụng DirectX. Thực tế ứng dụng DX9. Tôi đang làm điều này thông qua một cái móc để trình bày. (Tôi đang sử dụng Detours nếu điều đó có liên quan) và tôi đang làm điều này cho mọi khung hình. Tôi không cần (hoặc muốn) một định dạng bitmap cụ thể như BMP PNG JPEG, v.v. và không gian màu của RGB24 là OK. Lấy nó làm YUV480p sẽ tốt hơn nhưng chắc chắn không cần thiết. Cảm ơn mọi người vì những câu trả lời rất hữu ích


2
"Bitmap" nghĩa là gì? Một tập tin trong một định dạng cụ thể hoặc một mảng các màu pixel? Nếu đó là một tệp, hãy thử điều này: msdn.microsoft.com/en-us/l Library / windows / desktop / Lỗi Nếu mảng, tôi cũng muốn biết câu trả lời.
rắn5

vâng, ý tôi là một mảng byte, 3 byte cho mỗi pixel thông tin RGB. Cũng giống như điều mà glReadPixels thực hiện trong openGL. Tôi có nghĩa là một bitmap RAW. Tôi sẽ cập nhật câu hỏi để làm cho nó rõ ràng hơn
cloudraven

Trên thực tế, chức năng mà bạn đề cập sẽ lưu nó vào bộ nhớ, tôi chỉ có thể đọc d3xbuffer mà nó nhận được. Tôi nên thử nó, nếu nó đủ nhanh tôi có thể chấp nhận nó là giải pháp. (Tuy nhiên, nếu ai đó biết cách nhanh hơn để chụp màn hình, điều đó rất đáng hoan nghênh)
cloudraven

Câu trả lời:


6

Cách tôi làm điều này là như sau.

IDirect3DDevice9 :: GetBackBuffer : Truy cập vào IDirect3DSurface9 đại diện cho bộ đệm phía sau, giống như bạn hiện có. Đừng quên Giải phóng bề mặt này khi thực hiện vì cuộc gọi này sẽ tăng số tham chiếu!

IDirect3DSurface :: GetDesc : Nhận mô tả về bề mặt bộ đệm phía sau, sẽ cung cấp cho bạn chiều rộng, chiều cao và định dạng.

IDirect3DDevice9 :: Tạo OfferscreenPlainSurface : Tạo một đối tượng bề mặt mới trong D3DPOOL_SCRATCH; bạn thường muốn sử dụng cùng chiều rộng, chiều cao và định dạng (nhưng thực tế bạn không phải làm theo phương pháp này). Một lần nữa, Phát hành khi hoàn thành. Nếu bạn đang thực hiện thao tác này mọi khung hình (trong trường hợp đó, bạn nên tìm kiếm các giải pháp thay thế như cách tiếp cận dựa trên shader cho những gì bạn đang cố gắng thực hiện), bạn có thể tạo bề mặt phẳng ngoài màn hình một lần khi khởi động và sử dụng lại nó, thay vì tạo ra nó mỗi khung hình.

D3DXLoadSurfaceFromSurface : Sao chép từ bề mặt bộ đệm phía sau sang bề mặt trơn. Điều này sẽ tự động thay đổi kích thước và định dạng cho bạn. Ngoài ra, nếu bạn không muốn hoặc cần thay đổi kích thước hoặc thay đổi định dạng, bạn có thể sử dụng IDirect3DDevice9 :: GetRenderTargetData , nhưng nếu vậy thì hãy tạo bề mặt đơn giản ngoài màn hình trong D3DPOOL_SYIUSMEM thay thế.

IDirect3DSurface9 :: LockRect : Truy cập vào dữ liệu trên bề mặt đơn giản ngoài màn hình và có cách độc ác của riêng bạn với nó; UnlockRect khi hoàn tất.

Mã này trông giống mã hơn rất nhiều nhưng bạn sẽ thấy rằng nó cũng nhanh như glReadPixels, và thậm chí có thể nhanh hơn nếu bạn không cần thực hiện chuyển đổi định dạng (mà glReadPixels sử dụng GL_RGB gần như chắc chắn).

Chỉnh sửa để thêm: một số chức năng của trình trợ giúp (đã sẵn sàng) tôi cũng có thể hữu ích khi sử dụng phương pháp này cho ảnh chụp màn hình:

// assumes pitch is measured in 32-bit texels, not bytes; use locked_rect.Pitch >> 2
void CollapseRowPitch (unsigned *data, int width, int height, int pitch)
{
    if (width != pitch)
    {
        unsigned *out = data;

        // as a minor optimization we can skip the first row
        // since out and data point to the same this is OK
        out += width;
        data += pitch;

        for (int h = 1; h < height; h++)
        {
            for (int w = 0; w < width; w++)
                out[w] = data[w];

            out += width;
            data += pitch;
        }
    }
}


void Compress32To24 (byte *data, int width, int height)
{
    byte *out = data;

    for (int h = 0; h < height; h++)
    {
        for (int w = 0; w < width; w++, data += 4, out += 3)
        {
            out[0] = data[0];
            out[1] = data[1];
            out[2] = data[2];
        }
    }
}

// bpp is bits, not bytes
void WriteDataToTGA (char *name, void *data, int width, int height, int bpp)
{
    if ((bpp == 24 || bpp == 8) && name && data && width > 0 && height > 0)
    {
        FILE *f = fopen (name, "wb");

        if (f)
        {
            byte header[18];

            memset (header, 0, 18);

            header[2] = 2;
            header[12] = width & 255;
            header[13] = width >> 8;
            header[14] = height & 255;
            header[15] = height >> 8;
            header[16] = bpp;
            header[17] = 0x20;

            fwrite (header, 18, 1, f);
            fwrite (data, (width * height * bpp) >> 3, 1, f);

            fclose (f);
        }
    }
}

Tôi thực sự đang làm nó mọi khung hình. Tôi đang nối chức năng Present và sau đó lấy khung hình trong hook của tôi. Bạn có thể vui lòng giải thích về việc sử dụng một shader để làm điều này, đó có thể là những gì tôi đang tìm kiếm. Cảm ơn rất nhiều cho câu trả lời thấu đáo của bạn.
cloudraven

1
Một cách tiếp cận dựa trên shader chỉ thích hợp nếu bạn thực hiện kết xuất tiếp theo với hình ảnh được chụp. Từ mô tả của bạn, có vẻ như bạn đang làm một cái gì đó như quay video, đúng không? Bạn có thể xác nhận điều này?
Maximus Minimus

Bạn đoán đúng, nhưng tôi không thực sự quay video, nó rất gần. Tôi không mã hóa tất cả các khung (nhưng đủ để yêu cầu hiệu suất tốt) và tôi đang áp dụng một vài bộ lọc 2D cho hình ảnh được chụp trước khi mã hóa chúng.
cloudraven

Tôi đã thay đổi mã của mình để sử dụng BGRA. Nó đã trở nên tốt hơn. Cảm ơn vì sự sáng suốt
cloudraven

Nhân tiện, tôi đã đăng một câu hỏi tiếp theo về phương pháp đổ bóng. Tôi sẽ biết ơn nếu bạn xem nó. gamedev.stackexchange.com/questions/44587/ từ
cloudraven

1

Tôi tin rằng bạn cần LockR chữ nhật trên pBackBuffer và sau đó đọc các pixel từ D3DLOCKED_RECT.pBits . Điều này sẽ khá nhanh. Nếu bạn chỉ cần đọc một vài pixel, hãy xem xét sao chép toàn bộ backbuffer sang bộ đệm nhỏ hơn thông qua một cái gì đó như DX11 CopySubresourceRegion (Tôi chắc chắn cũng có giải pháp thay thế trong DX9), được thực hiện trên GPU và sau đó truyền bộ đệm nhỏ này từ GPU sang CPU thông qua LockRonymous - bạn lưu lưu chuyển bộ đệm lớn trên xe buýt.


1
Câu trả lời là thiếu phần quan trọng nhất: chuyển đổi. pBits sẽ trỏ đến dữ liệu ở định dạng bề mặt. Nó hầu như sẽ không bằng một mảng các bộ ba byte (RGB24).
rắn5

1
Ngoài việc chuyển đổi định dạng, điều này đòi hỏi CreatDevice với cờ backbuffer có thể khóa (mà tài liệu cảnh báo là một cú đánh hiệu năng) và việc khóa backbuffer sẽ luôn phải chịu một đường ống dẫn (sẽ là vấn đề lớn hơn nhiều so với truyền dữ liệu).
Maximus Minimus

1
đó là lý do tại sao tôi khuyên bạn nên sao chép một phần của backbuffer sang bộ đệm khác. Nếu bạn có thể đọc dữ liệu thô từ bề mặt, chuyển đổi sang định dạng "bitmap" tùy thuộc vào bạn ...
GPUquant

Tôi thực sự sẽ cần phải chuyển đổi nó thành Yuv420p tại một số phần trong quá trình này. Vì vậy, DirectX sử dụng một số định dạng nội bộ không phải là RGB?
cloudraven

2
Không có thứ gọi là định dạng bên trong RGB - định dạng bên trong là 32bpp - ngay cả trong OpenGL GL_RGB chỉ có nghĩa là 8 bit dự phòng không được sử dụng. Xem opengl.org/wiki/Common_Mistakes#Texture_upload_and_pixel_reads - vì vậy cả OpenGL và D3D thực sự sẽ là BGRA trong nội bộ, đó là lý do tại sao việc sử dụng GL_RGB lại chậm - nó cần phải nén và làm mờ dữ liệu sang RGB.
Maximus Minimus

1

Bạn không thể chụp màn hình bằng GetBackbuffer, chức năng này chỉ nhận được một con trỏ của bộ đệm phía sau, không phải dữ liệu.

Cách chính xác tôi có thể nghĩ là như dưới đây

  1. Tạo một bề mặt ngoài màn hình bằng cách gọi CreatPackscreenPlainSurface .
  2. Gọi GetFrontBufferData để đưa hình ảnh màn hình ra bề mặt ngoài màn hình
  3. Gọi LockRect của bề mặt để lấy dữ liệu trên bề mặt, một bề mặt ngoài màn hình luôn có thể khóa bất kể loại hồ bơi của chúng.

1
IDirect3DDevice9 :: GetFrontBufferData - " Chức năng này là rất chậm, do thiết kế, và không nên được sử dụng trong bất kỳ con đường hoạt động quan trọng " - msdn.microsoft.com/en-us/library/windows/desktop/...
Maximus Minimus

1
Đúng, nó chậm, nhưng AFAIK, đó là cách duy nhất để có được hình ảnh màn hình bằng DirectX hoặc bạn có thể thực hiện một số thao tác trước khi gọi hàm EndScene.
zdd

Cảm ơn! Tôi thực sự đang làm nó như một cái móc, nhưng không phải cho EndScene mà là Present, và tôi đang nối các ứng dụng directX cụ thể. Cho rằng, có một cái gì đó thông minh hơn mà tôi có thể làm về nó.
cloudraven

@cloudraven, tôi không quen với hooking, nhưng bạn có thể xem trang này để tham khảo. stackoverflow.com/questions/1994676/
hy

1

Một chút muộn màng. Nhưng hy vọng điều này sẽ giúp cho một ai đó.

tạo ra

        if (SUCCEEDED(hr = _device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backBuf)))
        {
            D3DSURFACE_DESC sd;
            backBuf->GetDesc(&sd);

            if (SUCCEEDED(hr = _device->CreateOffscreenPlainSurface(sd.Width, sd.Height, sd.Format, D3DPOOL_SYSTEMMEM, &_surface, NULL)))

bắt giữ

    if (SUCCEEDED(hr = _device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backBuf)))
    {
        if (SUCCEEDED(hr = _device->GetRenderTargetData(backBuf, _surface)))
        {
            if (SUCCEEDED(hr = D3DXSaveSurfaceToFileInMemory(&buf, D3DXIFF_DIB, _surface, NULL, NULL)))
            {
                LPVOID imgBuf = buf->GetBufferPointer();
                DWORD length = buf->GetBufferSize();

Trong buf bạn sẽ có dữ liệu hình ảnh thô. Đừng quên phát hành mọi thứ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.