硬件gpu加速计划效果_硬件gpu加速计划效果「建议收藏」

硬件gpu加速计划效果_硬件gpu加速计划效果「建议收藏」刚才怎么发到it168的blog去了,cu的跳转有我呢?it168不能超过一万字,发不了,分了两篇还是觉得这边好,发这边一篇

硬件gpu加速计划效果_硬件gpu加速计划效果「建议收藏」刚才怎么发到it168的blog去了,cu的跳转有我呢? it168不能超过一万字,发不了,分了两篇

还是觉得这边好,发这边一篇。

kdrive的xvide的加速的实现。

首先xvideo本来也是在驱动里面实现的,实际上我们先要做一个驱动。

kdrive硬件加速驱动的实现,其实就是kaa的实现,kaa分xserver这边和driver这边,xserver这边会提供一种机制

这个机制就是当gc操作的时候会判断一下回调函数是否注册,如果注册就使用回调函数,如果没有注册就使用软件的实现,

而注册这个回调函数就是在kdrive的驱动里面来实现的。因为kdrive不能动态装载驱动,所以驱动都是直接编译到Xserver

里面的,也就是说编译出来的Xfbdev是直接带了驱动的。

我们先看看代码的布局,之前是在xserver 1.5.x的基础上来实现的,大概就以1.5.3来讲吧

关于kdrive和xserver的关系之前说过,kdrive是xserver的一种,xserver在xorg的代码树里面有好几种实现,但是大体的代码都是共用的

比如基本的事件处理,clip等等代码都是共用的。hw目录下面就是不同的xserver的实现。xorg只是其中一种,如前所说,kdrive和xorg有很多

代码是共用的,所以xorg的代码更新过快,kdrive更新过慢,导致kdrive的有很多事件处理有问题。

本来应该先说kaa的实现的,但是因为xivdeo的实现比较简单,所以先说。

我们先看看xvideo的工作流程,就是说xvideo到底是怎么发挥作用的。

xvideo的目的是为了把video或者frame输出直接输出到屏幕上面,尽量少的转存,尽量多的利用硬件加速,而xorg是管理最后输出的(目前是这样)

所以需要在xserver里面有一个扩展这个就是Xv的扩展了,

这里需要一个图片说明

我们看到最原始的数据无非是一个视频文件,我们先假定这个文件是一个mpeg4的文件。然后通过demux之后就有不同的数据流,比如音频,视频,还有其他的,

但是我们这里先只关注视频,视频流出来之后就经过解码,解码完成之后就成了一帧一帧的图片,和放电影的原理是一样的。然后我们就要把这个图片显示

到屏幕上面,最终的输出函数一般都是XPutImage,XvPutImage,XvShmPutImage类似这样的函数。

当然使用shm的版本会比不使用shm的版本要快很多了,Xshm本来也是Xserver的一个扩展,这个主要是为了减少数据的copy

一般来说写的好的播放器都会判断一些xserver是否支持shm扩展,如果支持,就会使用shm的函数。比如mplayer,gstreamer(xvimagesink)

因为Xserver这个框架是C/S模式的,说这个很好理解,但是很多人还是知其然不知其所以然,我们从原始的架构来理解。

原始的X是可以通过网络来传输的,但是因为这种模式我们现在用的不是很多了,除了ssh -X abc@192.168.1.100这样的用法之外,可能我们

很少会用到这样的远程传输,我们本机一般用的是unix socket。也就是说所有的请求,实际上是这样的,

我们编写应用程序里面会有XPutImage的函数,实际上这个函数是由libX11来提供的(如果我没有记错的话),XvSHmPutImage这样的函数是由libXext来实现的。

这个里面的代码实际上就是封装一个X的请求,然后通过Xsend发送出去。然后Xserver那边会dispatch这些消息,由对应的扩展或者函数来处理,因为这些Xext初始化

的时候会告诉Xserver这类消息由自己的扩展来处理。

所以想想如果是一个很大帧的数据,都走socket的话是很慢的,所以有了Xshm就好很多了,shm,和我们普通的shm是一样的,就是共享内存了,就是用户程序和

xserver共享一个内存空间,当用户程序填充帧的时候实际上就直接写到xserver的内存空间了(需要图片说明),然后xserver可以直接显示出去,这样会节省很多空间

Ximage这个数据接口是有data指针的,但是如果是shm的版本就没有,里面会带一个地址。XshmImage.这些内容都是凭记忆写的,可能会有漏误

话说回来,Xvideo在xserver这边对应的处理流程。

xserver无非也会接收到XshmPutimage的请求,这个在Xserver里面xorg-server-1.5.3/Xext/shm.c里面实现的

static int

SProcShmPutImage(client)

    ClientPtr client;

{

    register int n;

    REQUEST(xShmPutImageReq);

    swaps(&stuff->length, n);

    REQUEST_SIZE_MATCH(xShmPutImageReq);

    swapl(&stuff->drawable, n);

    swapl(&stuff->gc, n);

    swaps(&stuff->totalWidth, n);

    swaps(&stuff->totalHeight, n);

    swaps(&stuff->srcX, n);

    swaps(&stuff->srcY, n);

    swaps(&stuff->srcWidth, n);

    swaps(&stuff->srcHeight, n);

    swaps(&stuff->dstX, n);

    swaps(&stuff->dstY, n);

    swapl(&stuff->shmseg, n);

    swapl(&stuff->offset, n);

    return ProcShmPutImage(client);

}

ProcShmPutImage这个函数会调用下面的函数下面调用的是XPutImage的注册回调函数了,

    (*pGC->ops->PutImage) (pDraw, pGC, stuff->depth,

                   stuff->dstX, stuff->dstY,

                   stuff->totalWidth, stuff->srcHeight,

                   stuff->srcX, stuff->format,

                   shmdesc->addr + stuff->offset +

                   (stuff->srcY * length));

从dispatch开始看

Xext/xvdisp.c

#define XVCALL(name) Xv##name

ProcXvPutImage

最后调用的是这个,

 return XVCALL(diPutImage)(client, pDraw, pPort, pGC,

                stuff->src_x, stuff->src_y,

                stuff->src_w, stuff->src_h,

                stuff->drw_x, stuff->drw_y,

                stuff->drw_w, stuff->drw_h,

                pImage, (unsigned char*)(&stuff[1]), FALSE,

                stuff->width, stuff->height);

实际上是下面的函数XvdiPutImage

Xext/xvmain.c这个函数是xvideo的具体实现,当然xorg和kdrive实际上都用这里的代码。

XvdiPutImage

调用下面的回调函数了

 return (* pPort->pAdaptor->ddPutImage)(client, pDraw, pPort, pGC,

                       src_x, src_y, src_w, src_h,

                       drw_x, drw_y, drw_w, drw_h,

                       image, data, sync, width, height);

我们要实现的自然就是ddPutImage了。

这个函数就是gc注册的了,这里我们使用kdrive的框架,自然就是kaa去注册的了。

然后kaa的具体实现,也就是我们的驱动,会注册kaa的函数。

所有kdrive的函数都在

xorg-server-1.5.3/hw/kdrive

ailantian@vax:/mnt/sdb1/ubd/soft/xorg/temp/xorg-server-1.5.3/hw/kdrive$ ls

ati    ephyr  fake   i810   mach64       Makefile.in  neomagic  pm2   sdl     smi  vesa

chips  epson  fbdev  linux  Makefile.am  mga          nvidia    r128  sis300  src  via

ailantian@vax:/mnt/sdb1/ubd/soft/xorg/temp/xorg-server-1.5.3/hw/kdrive$ pwd

这下面基本每个目录都是一个driver,比如有intel 810, ati,等等,很多驱动了。

kaa这个框架是在hw/kdrive/kaa.c里面实现的,至于xvideo的框架的实现就是在kxv里面来实现的了。

我们的驱动里面通过一定的机制实际上会调用到下面这个函数,

KdXVInitAdaptors

      pa->pScreen = pScreen;

      pa->ddAllocatePort = KdXVAllocatePort;

      pa->ddFreePort = KdXVFreePort;

      pa->ddPutVideo = KdXVPutVideo;

      pa->ddPutStill = KdXVPutStill;

      pa->ddGetVideo = KdXVGetVideo;

      pa->ddGetStill = KdXVGetStill;

      pa->ddStopVideo = KdXVStopVideo;

      pa->ddPutImage = KdXVPutImage;

      pa->ddSetPortAttribute = KdXVSetPortAttribute;

      pa->ddGetPortAttribute = KdXVGetPortAttribute;

      pa->ddQueryBestSize = KdXVQueryBestSize;

      pa->ddQueryImageAttributes = KdXVQueryImageAttributes;

实际上我们只关注

      pa->ddPutImage = KdXVPutImage;

这个函数是在xserver的xvideo扩展里面被调用的。

也就是说kxv实际上是一个框架,然后会有人来填充KdXVPutImage这样的回调函数的,这个就是我们kaa驱动里面要做的事情了。

其实i810等等这些驱动可以直接参考,我们可以直接copy一下一个目录,然后作为自己驱动开发的基础,然后改改makefile就行了。

这里还是以ati作为例子来讲解吧,我自己的代码写的比较乱,可能还有其他一些问题,所以不便贴出来。

一般来说xvideo的实现都是在xxx_video.c里面实现的,比如我们看ati的,就是在ati_video.c里面了。

其实代码量比较小。

其实输出出去,最终还是这个函数,

ATIPutImage,然后这个函数一般会调用DisplayVideo这样的函数,不过这个只是开发者的习惯而已。

那么先看看框架。

以ati为例,注册回调函数从哪里开始,

grep一下代码就知道了,实际上是这个。

ATISetupImageVideo

ATIInitVideo调用ATISetupImageVideo

看个人的喜好了,反正是要注册这些回调函数了,

下面是关键代码,所谓的框架都在这里了。

static KdVideoAdaptorPtr

ATISetupImageVideo(ScreenPtr pScreen)

{

    KdScreenPriv(pScreen);

    ATIScreenInfo(pScreenPriv);

    KdVideoAdaptorPtr adapt;

    ATIPortPrivPtr pPortPriv;

    int i;

    atis->num_texture_ports = 16;

    adapt = xcalloc(1, sizeof(KdVideoAdaptorRec) + atis->num_texture_ports *

        (sizeof(ATIPortPrivRec) + sizeof(DevUnion)));

    if (adapt == NULL)

        return NULL;

    adapt->type = XvWindowMask | XvInputMask | XvImageMask;

    adapt->flags = VIDEO_CLIP_TO_VIEWPORT;

    adapt->name = “ATI Texture Video”;

    adapt->nEncodings = 1;

    adapt->pEncodings = DummyEncoding;

    adapt->nFormats = NUM_FORMATS;

    adapt->pFormats = Formats;

    adapt->nPorts = atis->num_texture_ports;

    adapt->pPortPrivates = (DevUnion*)(&adapt[1]);

    pPortPriv =

        (ATIPortPrivPtr)(&adapt->pPortPrivates[atis->num_texture_ports]);

    for (i = 0; i < atis->num_texture_ports; i++)

        adapt->pPortPrivates[i].ptr = &pPortPriv[i];

    adapt->nAttributes = NUM_ATTRIBUTES;

    adapt->pAttributes = Attributes;

    adapt->pImages = Images;

    adapt->nImages = NUM_IMAGES;

    adapt->PutVideo = NULL;

    adapt->PutStill = NULL;

    adapt->GetVideo = NULL;

    adapt->GetStill = NULL;

    adapt->StopVideo = ATIStopVideo;

    adapt->SetPortAttribute = ATISetPortAttribute;

    adapt->GetPortAttribute = ATIGetPortAttribute;

    adapt->QueryBestSize = ATIQueryBestSize;

    adapt->PutImage = ATIPutImage;

    adapt->ReputImage = ATIReputImage;

    adapt->QueryImageAttributes = ATIQueryImageAttributes;

    /* gotta uninit this someplace */

    REGION_INIT(pScreen, &pPortPriv->clip, NullBox, 0);

    atis->pAdaptor = adapt;

    xvBrightness = MAKE_ATOM(“XV_BRIGHTNESS”);

    xvSaturation = MAKE_ATOM(“XV_SATURATION”);

    return adapt;

}

看看上面最重要的函数就是注册了    adapt->PutImage = ATIPutImage;

这个函数在最后就会被调用到。

大家可能会问这个adapt怎么会和上面的KdXVPutImage打上关系,这个自然是要注册的。

KdXVScreenInit->KdXVInitAdaptors

      adaptorPriv->flags = adaptorPtr->flags;

      adaptorPriv->PutVideo = adaptorPtr->PutVideo;

      adaptorPriv->PutStill = adaptorPtr->PutStill;

      adaptorPriv->GetVideo = adaptorPtr->GetVideo;

      adaptorPriv->GetStill = adaptorPtr->GetStill;

      adaptorPriv->StopVideo = adaptorPtr->StopVideo;

      adaptorPriv->SetPortAttribute = adaptorPtr->SetPortAttribute;

      adaptorPriv->GetPortAttribute = adaptorPtr->GetPortAttribute;

      adaptorPriv->QueryBestSize = adaptorPtr->QueryBestSize;

      adaptorPriv->QueryImageAttributes = adaptorPtr->QueryImageAttributes;

      adaptorPriv->PutImage = adaptorPtr->PutImage;

      adaptorPriv->ReputImage = adaptorPtr->ReputImage;

      portPriv->AdaptorRec = adaptorPriv;

这个函数指针都放在了AdaptorRec这个里面,而这个是在KdXVXXXX函数里面被调用的。

比如

KdXVPutImage

到输出的时候就会尝试调用这个注册的回调函数了。

  ret = (*portPriv->AdaptorRec->PutImage)(portPriv->screen, pDraw,

        src_x, src_y, WinBox.x1, WinBox.y1,

        src_w, src_h, drw_w, drw_h, format->id, data, width, height,

        sync, &ClipRegion, portPriv->DevPriv.ptr);

而这个回调函数就是我们之前ATISetupImageVideo里面注册的函数,通过一系列周转到这里来的。

回到正题,只看最重要的部分,如何硬件加速输出的。其实看代码grep一下,肯定是和寄存器相关,或者mmio相关的,因为老的框架都是使用mmio的。

ATIPutImage里面加入了Xrandr的处理,这个我们可以先不管,主要是屏幕旋转会造成问题,所以需要处理一下,不过我们如果不旋转可以不关注这个代码了。

最关键的代码如下我们解码出来的图片是在内存里面,但是实际上我们要显示出去,必须放到显存里面,这里我不想讲kaa的内存管理了,偏离标题了,

    if (pPortPriv->off_screen == NULL) {

        pPortPriv->off_screen = KdOffscreenAlloc(screen->pScreen,

            size * 2, 64, TRUE, ATIVideoSave, pPortPriv);

        if (pPortPriv->off_screen == NULL)

            return BadAlloc;

    }

反正就是先我们要在显存里面划拨一个区域来存放这个图片才行。

下面有一段pixmap的判断,这个先不说,

总之,我们必须保证这个东西是在显存里面,无论是不是offscreen的。

然后就是下面的代码了,

    switch(id) {

    case FOURCC_YV12:

    case FOURCC_I420:

        top &= ~1;

        nlines = ((((rot_y2 + 0xffff) >> 16) + 1) & ~1) – top;

        KdXVCopyPlanarData(screen, buf, pPortPriv->src_addr, randr,

            srcPitch, srcPitch2, dstPitch, rot_src_w, rot_src_h,

            height, top, left, nlines, npixels, id);

        break;

    case FOURCC_UYVY:

    case FOURCC_YUY2:

    default:

        nlines = ((rot_y2 + 0xffff) >> 16) – top;

        KdXVCopyPackedData(screen, buf, pPortPriv->src_addr, randr,

            srcPitch, dstPitch, rot_src_w, rot_src_h, top, left,

            nlines, npixels);

        break;

    }

这个代码实际上是用来进行数据格式的转换的,也就是说,其实显卡所支持的硬件加速的格式是有限的,如果这个格式不被硬件所支持的话,我们就需要进行软件的

格式转换,这里xserver处理的有点死板,xserver到最后的输出的时候只支持两种格式,所以所有的格式都会转换成那两种格式,如果没有记错的话

应该是YVYU,VYUY,也就是说xserver目前的框架,是将数据封装成这两个格式,然后给硬件的,当然,这个驱动是自己实现的,一般来说,硬件支持很多种格式,

还有我们看到上面的代码,从user application传递过来的之后四种格式,这个是因为我们的驱动只注册了四种格式,也就是说我们只支持这四种格式,

类似mplayer会通过函数来查询当前的驱动到底支持哪几种格式,xvinfo这个x自带的工具也可以看。

注册格式是在这个地方,如果硬件支持更多的比如NV12,NV21等可以加在这里,好像一般的嵌入式的处理器如果有2d处理的话,都会支持。

static KdImageRec Images[NUM_IMAGES] =

{

    XVIMAGE_YUY2,

    XVIMAGE_YV12,

    XVIMAGE_I420,

    XVIMAGE_UYVY

};

xserver默认只支持这几个fourcc,如果要更多,可以从别的驱动里面抄这些定义,或者抄mplayer或者pixman里面都有这些fourcc的定义。因为很多格式还没有被

标准化。

数据pack完成之后自然就是发送给硬件显示了。

这个就比较简单了,因为我们有一个缓冲区,这个缓冲区已经在显存里面了,我们要显示到屏幕的某个地方。

注意offscreen和online screen的区别,比如我们有一个显卡,显存500M,实际上只有很小一部分空间是对应在屏幕上面的。

一般来说,显卡操作显存,是比较快的,有各种不同的技术实现,各家也不一样。但是比内存是要快的,或者至少也要和内存的速度差不多,大块数据的搬运会比较快一些。

比如要从offscreen搬运到online screen,其实嵌入式来说,显存也是内存,划拨一块而已,但是这个操作一般不需要cpu参与了。

好,我们看看显示的过程,

一般驱动程序会单独写一个函数来处理这个过程。

这里其实只有两个需要注意的地方,一个就是clip的处理,一个就是composite的处理。

虽然说xvideo本身的设计是和composite背离的,不过xvideo也可以直接输出到一个texure或者是一个pixmap里面。因为无论是什么方式,无非是一块显存。

R128DisplayVideo

我们看看这个实现。

    BoxPtr pBox = REGION_RECTS(&pPortPriv->clip);

    int nBox = REGION_NUM_RECTS(&pPortPriv->clip);

这里是计算clip的,比如我们播放一个视频的时候,这个时候有菜单盖住屏幕,这样我们的输出区域就被剪切成多个区域了,2个或者说个等等。

我们就只能按小块输出了,每块单独输出,但是还是硬件加速的,我们看看循环就知道了。

    while (nBox–) {

        int srcX, srcY, dstX, dstY, srcw, srch, dstw, dsth;

        dstX = pBox->x1 + dstxoff;

        dstY = pBox->y1 + dstyoff;

        dstw = pBox->x2 – pBox->x1;

        dsth = pBox->y2 – pBox->y1;

        srcX = (pBox->x1 – pPortPriv->dst_x1) *

            pPortPriv->src_w / pPortPriv->dst_w;

        srcY = (pBox->y1 – pPortPriv->dst_y1) *

            pPortPriv->src_h / pPortPriv->dst_h;

        srcw = pPortPriv->src_w – srcX;

        srch = pPortPriv->src_h – srcY;

        BEGIN_DMA(6);

        OUT_RING(DMA_PACKET0(R128_REG_SCALE_SRC_HEIGHT_WIDTH, 2));

        OUT_RING_REG(R128_REG_SCALE_SRC_HEIGHT_WIDTH,

            (srch << 16) | srcw);

        OUT_RING_REG(R128_REG_SCALE_OFFSET_0, pPortPriv->src_offset +

            srcY * pPortPriv->src_pitch + srcX * 2);

        OUT_RING(DMA_PACKET0(R128_REG_SCALE_DST_X_Y, 2));

        OUT_RING_REG(R128_REG_SCALE_DST_X_Y, (dstX << 16) | dstY);

        OUT_RING_REG(R128_REG_SCALE_DST_HEIGHT_WIDTH,

            (dsth << 16) | dstw);

        END_DMA();

        pBox++;

    }

里面的OUT_RING我们就不看了,简单点理解,就是硬件输出。

各家硬件不一样,这里的目的是一样的,就是输出区域。nBox就是我们区域的个数了。

我们要把buffer输出到一个指定的区域,指定的偏移就可以了。

至于composite,我们无非是要指定输出的区域就行了,这个区域如果不在online screen,我们就输出到pixmap了。

然后composite会管理输出,可能是windowmanager做这个或者是xcompmgr类似这样的程序。

    DamageDamageRegion(pPortPriv->pDraw, &pPortPriv->clip);

这个是需要的。xv的框架输出,我们需要汇报一下,我们写了这个区域,如果有人也在使用这块区域,就需要知道这个事情。

所以实际上xv的框架涉及到的扩展比较多

Xv,Xcomposite,Xdamage,Xfixes,

大体上就是这样了,其他的函数都是一些辅助的函数,直接照抄就行了。

另外要说明的一点就是。各种不同的flag的作用。

VIDEO_CLIP_TO_VIEWPORT等等,这个是根据不同的显示模式来设置的。

overlay和texture是不一样的,overlay在现在好像用的很少了,不过因为是硬件的支持,可能比texture会快一些,毕竟一个单独的layer操作

起来加上硬件的alpha blending会快的多,而texture的输出,完全靠显卡,感觉现在的显卡虽然很强了,但是面对大量的数据的alpha blending

无论是从显存大小,还是数据传输还是计算能力,都还是不够的。pc如此,嵌入式更如此。

想想现在都是大分辨率1440×900这个是一般的小屏幕了,24bit depth,然后还要加上composite,需求加倍。内存占用就很多了,还有就是所有的窗口都要先

offscreen render一下,然后再说alpha blending,很慢,我也不知道composite有什么好,为了酷炫,就要付出更多的代价。

唉,用的是fcitx的全拼打字,还是有点慢的,上面的东西写了一个半小时,其他事情也还比较多

kaa不知道什么时候写了。

kdriver+kaa+kxv

xorg+exa+xv

两个框架其实是类似的,xorg扩展性更强一些,可以动态装载。

xorg的input 自动探测也比较简单,还像说说。evdev统一天下还需时日。

晕,输入居然多余的万字了,我太能了:)

看来我写小说还挺快,一个半小时能超过一万字呢

漏掉一点就是为什么要使用xvideo

这个之前的文章说过,就是我们从mpeg4或者h264等等文件解码出来的实际上是yuv的数据,比如可能是yuv422的

但是我们的屏幕实际上是rgb的,不同的系统不一样了,比如我们的pc一般rgb888的,对于嵌入式来说可能会选择rgb565这样的,所以就需要转换,

这个yuv转rgb是需要很多计算量的,另外就是硬件缩放,比如解码出来的图像是vga或者wvga的,我们要全屏显示的时候就需要缩放,这个时候也非常占用cpu。

没记错的话,应该都是浮点运算。虽然coretex-a8等等都有了vfp等等指令,但是实际上,这点性能还是远远不够。还有就是xvideo的设计就是为了快速的输出,

所以从框架上来说是很精简的,为了快速显示而设计的。

不过有一点要注意就是profile,我们一般会profile一下,看看这个xvideo的实现性能在什么地方,因为现在输出都是由硬件来做的,所以cpu占用很少,

后来用oprofile看的时候发现其实cpu占用最多的地方居然在数据封装的地方,也就是我们把用户程序传递过来的数据封装成硬件支持的格式。

这个地方还是可以用汇编优化一下的。改善很明显。毕竟是驱动里面最”热”的地方。

不过后来因为迁移到了xorg,所以kdrive这边的实现,包括kaa的驱动和xvideo的实现都没再继续用了

xorg这边有芯片提供商在开发xorg的dri+exa的driver,所以我就没再做这个工作了。

今天的文章硬件gpu加速计划效果_硬件gpu加速计划效果「建议收藏」分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/69238.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注