且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何直接访问显卡的输出?

更新时间:2023-12-01 15:22:22

我不在 Linux 下编码,但在 Windows 下(无论如何你在模拟器中运行它)你可以使用WinAPI第三方应用程序直接访问任何窗口甚至桌面的画布.某些 GPU 叠加层可能难以捕捉(尤其是基于 DirectX 的),但我的 GL/GLSL 暂时没有问题.

I do not code under Linux but in Windows (you are running it in emulator anyway) you can use WinAPI to directly access canvas of any window or even desktop from 3th party App. Some GPU overlays could be problematic to catch (especially DirectX based) but I had no problems with mine GL/GLSL for now.

如果您可以访问应用程序源,则可以使用 glReadPixels 直接从 GL 提取图像(但这仅适用于当前基于 GL渲染).

If you got access to App source you can use glReadPixels for image extraction from GL directly (but that works only for current GL based rendering).

  1. 使用glReadPixels

如前所述,这必须直接在目标应用程序中实现,因此您需要拥有它的源代码或在正确的位置/时间注入您的代码.我用来截取这段代码:

As mentioned this must be implemented directly in the targeted app so you need to have it source code or inject your code in the right place/time. I use for screenshoting this code:

void OpenGLscreen::screenshot(Graphics::TBitmap *bmp)
    {
    if (bmp==NULL) return;
    int *dat=new int[xs*ys],x,y,a,*p;
    if (dat==NULL) return;
    bmp->HandleType=bmDIB;
    bmp->PixelFormat=pf32bit;
    if ((bmp->Width!=xs)||(bmp->Height!=ys)) bmp->SetSize(xs,ys);
    if ((bmp->Width==xs)&&(bmp->Height==ys))
        {
        glReadPixels(0,0,xs,ys,GL_BGRA,GL_UNSIGNED_BYTE,dat);
        glFinish();
        for (a=0,y=ys-1;y>=0;y--)
         for (p=(int*)bmp->ScanLine[y],x=0;x<xs;x++,a++)
          p[x]=dat[a];
        }
    delete[] dat;
    }

其中 xs,ys 是 OpenGL 窗口分辨率,你可以忽略整个 bmp 的东西(它是我用来存储截图的 VCL 位图),也可以忽略 for 它只是将图像从缓冲区复制到位图.所以重要的事情是这样的:

where xs,ys is OpenGL window resolution, you can ignore the whole bmp stuff (it is VCL bitmap I use to store screenshot) and also can ignore the for it just copy the image from buffer to bitmap. So the important stuff is just this:

int *dat=new int[xs*ys]; // each pixel is 32bit int
glReadPixels(0,0,xs,ys,GL_BGRA,GL_UNSIGNED_BYTE,dat);
glFinish();

您需要在渲染完成后执行此代码,否则您将获得未完成或空的缓冲区.我在重绘/重绘事件后使用它.如前所述,这将仅获取 GL 渲染的内容,因此如果您的应用结合 GDI+OpenGL,***使用下一种方法.

You need to execute this code after the rendering is done otherwise you will obtain unfinished or empty buffer. I use it after redraw/repaint events. As mentioned before this will obtain only the GL rendered stuff so if your App combines GDI+OpenGL it is better to use the next approach.

WinAPI 方法

为了获取我编写的这个类的任何窗口的 Canvas 图像:

To obtain Canvas image of any window I wrote this class:

//---------------------------------------------------------------------------
//--- screen capture ver: 1.00 ----------------------------------------------
//---------------------------------------------------------------------------
class scrcap
    {
public:
    HWND hnd,hnda;
    TCanvas *scr;
    Graphics::TBitmap *bmp;
    int x0,y0,xs,ys;
    scrcap()
        {
        hnd=NULL;
        hnda=NULL;
        scr=new TCanvas();
        bmp=new Graphics::TBitmap;
        #ifdef _mmap_h
        mmap_new('scrc',scr,sizeof(TCanvas()        ));
        mmap_new('scrc',bmp,sizeof(Graphics::TBitmap));
        #endif
        if (bmp)
            {
            bmp->HandleType=bmDIB;
            bmp->PixelFormat=pf32bit;
            }
        x0=0; y0=0; xs=1; ys=1;
        hnd=GetDesktopWindow();
        }
    ~scrcap()
        {
        #ifdef _mmap_h
        mmap_del('scrc',scr);
        mmap_del('scrc',bmp);
        #endif
        if (scr) delete scr; scr=NULL;
        if (bmp) delete bmp; bmp=NULL;
        }
    void init(HWND _hnd=NULL)
        {
        RECT r;
        if (scr==NULL) return;
        if (bmp==NULL) return;
        bmp->SetSize(1,1);
        if (!IsWindow(_hnd)) _hnd=hnd;
        scr->Handle=GetDC(_hnd);
        hnda=_hnd;
        resize();
        }
    void resize()
        {
        if (!IsWindow(hnda)) return;
        RECT r;
//      GetWindowRect(hnda,&r);
        GetClientRect(hnda,&r);
        x0=r.left; xs=r.right-x0;
        y0=r.top; ys=r.bottom-y0;
        bmp->SetSize(xs,ys);
        xs=bmp->Width;
        ys=bmp->Height;
        }
    void capture()
        {
        if (scr==NULL) return;
        if (bmp==NULL) return;
        bmp->Canvas->CopyRect(Rect(0,0,xs,ys),scr,TRect(x0,y0,x0+xs,y0+ys));
        }
    };
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------

它再次使用 VCL,因此将位图 bmp 和画布 scr 重写为您的编程环境风格.还要忽略 _mmap_h 代码块,它们只是用于调试/跟踪与我当时面临的一些令人讨厌的编译器错误相关的内存指针.

Again it uses VCL so rewrite bitmap bmp and canvas scr to your programing environment style. Also igneore the _mmap_h chunks of code they are just for debugging/tracing of memory pointers related to some nasty compiler bug I was facing at that time I wrote this.

用法很简单:

// globals
scrcap cap;
// init
cap.init();

// on screenshot
cap.capture();
// here use cap.bmp

如果你调用 cap.init() 它将锁定整个 Windows 桌面.如果你调用 cap.init(window_handle) 它将锁定特定的可视化窗口/组件.要从第 3 个应用程序端获取窗口句柄,请参阅:

If you call cap.init() it will lock on the whole windows desktop. If you call cap.init(window_handle) it will lock on specific visual window/component instead. To obtain window handle from 3th app side see:

抱歉,它在 SE/RE 上而不是在 SE/SO 上,但我在此处涉及此主题的回答已被删除.我用它来捕捉视频……我的答案中的所有动画 GIF 都是由这段代码创建的.另一个例子可以在我的这个答案的底部看到:

Sorry it is on SE/RE instead of here on SE/SO but My answer here covering this topic was deleted. I use this for video capture ... all of the animated GIFs in my answers where created by this code. Another example could be seen on the bottom of this answer of mine:

如您所见,它也适用于带有经典媒体播放器的 DirectX 覆盖(即使是 Windows PrintScreen 功能也无法正确执行).正如我写的那样,我还没有遇到任何问题.

As you can see it works also for DirectX overlay with media player classic (even Windows PrintScreen function cant do it right). As I wrote I got no problems with this yet.

注意视觉内容 WinAPI 调用必须从应用程序主线程 (WNDPROC) 调用,否则可能会发生严重问题,导致应用程序中任何位置的随机无关 WinAPI 调用异常.

Beware visual stuff WinAPI calls MUST BE CALLED FROM APP MAIN THREAD (WNDPROC) otherwise serious problems could occur leading to random unrelated WinAPI calls exceptions anywhere in the App.