【问题标题】:How can I access a graphics card's output directly?如何直接访问显卡的输出?
【发布时间】:2016-11-26 21:50:34
【问题描述】:

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

我想知道是否可以在 Linux 上“捕获”虚拟机的输出,该虚拟机可以直接访问 GPU,并且运行 Windows。理想情况下,我可以直接从内存访问输出,而无需接触 GPU,因为此代码将能够在 Linux 主机上运行。

另一种选择是编写一个 Windows 驱动程序,该驱动程序读取 GPU 的输出并将其写入内存中的某个位置。然后,在 Linux 端,程序可以读取此内存。这似乎有些不可能,因为我不确定如何让主机上的进程与来宾上的进程共享内存。

是否可以执行选项 1 并简单地从内存中读取输出?

【问题讨论】:

  • 你认为显卡的“输出”是什么?你认为的可能是错误的。
  • 一个框架。一堆像素。基本上,我希望 Windows 及其驱动程序处理所有渲染,并且我希望访问输出。
  • 输出通过电缆连接到显示器。谁说它存在于记忆中的任何地方?
  • 这就是我问的原因。
  • @Spektre 听起来你有答案要分享:)

标签: c graphics gpu


【解决方案1】:

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

如果您可以访问 App 源,则可以使用 glReadPixels 直接从 GL 中提取图像(但这仅适用于当前基于 GL 的渲染)。 p>

  1. 使用glReadPixels

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

    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,它只是将图像从缓冲区复制到位图。所以重要的事情就是这样:

    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,则最好使用下一种方法。

  2. WinAPI 方法

    要获取我写的这个类的任何窗口的画布图像:

    //---------------------------------------------------------------------------
    //--- 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 代码块,它们只是用于调试/跟踪与我在写这篇文章时遇到的一些讨厌的编译器错误相关的内存指针。

    用法很简单:

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

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

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

    如您所见,它也适用于带有经典媒体播放器的 DirectX 覆盖(即使是 Windows PrintScreen 功能也无法做到这一点)。正如我所写的那样,我对此还没有任何问题。

    当心视觉内容 WinAPI 调用必须从应用程序主线程 (WNDPROC) 调用,否则可能会出现严重问题,导致应用程序中任何地方出现随机无关的 WinAPI 调用异常。

【讨论】:

  • 谢谢!那时我可能会使用 WinAPI。我可以看到任何地方,我需要弄清楚如何从 Linux 端以某种方式访问​​它。这是一个不同的话题。感谢您的帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-19
  • 2016-07-26
  • 2013-07-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多