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

10

图形卡通常会将其输出写入某个内存位置,我可以访问吗?我是否必须使用驱动程序?如果是,我能否使用OpenGL?

我想知道在Linux上是否有可能“捕获”直接访问GPU并运行Windows的VM的输出。理想情况下,我可以直接从内存中访问输出,而不需要触碰GPU,因为这段代码将能够在Linux主机上运行。

另一个选项是编写一个Windows驱动程序,它读取GPU的输出并将其写入某个内存位置。然后,在Linux侧,一个程序可以读取这个内存。这似乎有些不可能,因为我不知道如何让主机上的进程与客户机上的进程共享内存。

是否可以选择1,并仅从内存中读取输出?


1
一个框架。一堆像素。基本上我想让Windows及其驱动程序处理所有的渲染,而我想要访问输出。 - user377628
输出通过电缆连接到显示器。谁说它存在于内存中? - stark
4
那就是我为什么在问的原因。 - user377628
1
@Spektre 听起来你有答案要分享 :) - nebuch
@nebuch 是的,我以前做过这样的任务,但答案取决于我提到的细节,因此首先需要更多的规格来澄清来自 OP 作者方面的问题... - Spektre
显示剩余4条评论
1个回答

9
我不在Linux下编码,而是在Windows下(请注意,您无论如何都在模拟器中运行),您可以使用WinAPI直接访问任何窗口甚至是桌面的画布,但有些GPU叠加层可能会很难捕捉(特别是基于DirectX的),但目前我对我的GL/GLSL没有问题。
如果您可以获得App源代码,可以直接使用glReadPixelsGL中提取图像(但这仅适用于当前的GL渲染)。
  1. Using 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;
        }
    

    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();
    

    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.

  2. WinAPI approach

    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));
            }
        };
    //---------------------------------------------------------------------------
    //---------------------------------------------------------------------------
    //---------------------------------------------------------------------------
    

    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.

    The usage is simple:

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

    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:

    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:

    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.

    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.


谢谢!我可能会使用WinAPI。我发现,要在这方面取得进展,我需要想办法从Linux端获取访问权限。不过这是另一个话题了。感谢你的帮助。 - user377628

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接