【java 垂直爬虫源码】【junit源码包】【rdd intersection 源码】mask源码

时间:2024-11-28 19:45:02 来源:源码集市互站网 分类:探索

1.【Unity源码学习】遮罩:Mask与Mask2D
2.字符设备中的源码几个函数分析
3.UGUI源码阅读之Mask
4.MaskFormer源码解析

mask源码

【Unity源码学习】遮罩:Mask与Mask2D

       Unity源码学习遮罩详解:Mask与Mask2D

       UGUI裁切功能主要有两种方式:Mask和Mask2D。它们各自有独特的源码原理和适用场景。

       1. Mask原理与实现

       Mask利用IMaskable和IMaterialModifier功能,源码通过指定一张裁切图,源码如圆形,源码限定子元素的源码java 垂直爬虫源码显示区域。GPU通过StencilBuffer(一个用于保存像素标记的源码缓存)来控制渲染,当子元素像素位于Mask指定区域时,源码才会被渲染。源码

       StencilBuffer像一个画板,源码每个像素有一个1字节的源码内存区域,记录是源码否被遮盖。当多个UI元素叠加时,源码通过stencil buffer传递信息,源码实现精确裁切。源码

       2. Mask2D原理

       RectMask2D则基于IClippable接口,其裁剪基于RectTransform的大小。在C#层,它找出所有RectMask2D的交集并设置剪裁区域,然后Shader层依据这些区域判断像素是否在内,不满足则透明度设为0。junit源码包

       RectMask2D的性能优化在于无需依赖Image组件,直接使用RectTransform的大小作为裁剪区域。

       3. 性能区别

       Mask需要Image组件,裁剪区域受限于Image,而RectMask2D独立于Image,裁剪灵活。因此,Mask2D在不需要复杂裁剪时更高效。

       总结:虽然Mask和Mask2D各有优势,选择哪种遮罩取决于具体需求,合理使用能提高性能和用户体验。

字符设备中的几个函数分析

       1.在内核中, dev_t 类型(在 <linux/types.h>中定义)用来持有设备编号 — 主次部分都包括.其中dev_t 是 位的量, 位用作主编号, 位用作次编号

       1 #ifndef _LINUX_TYPES_H

       2 #define _LINUX_TYPES_H

       3

       4 #include <asm/types.h>

       5

       6 #ifndef __ASSEMBLY__

       7 #ifdef __KERNEL__

       8

       9 #define DECLARE_BITMAP(name,bits) /

        unsigned long name[BITS_TO_LONGS(bits)]

       

        #endif

       

        #include <linux/posix_types.h>

       

        #ifdef __KERNEL__

       

        typedef __u __kernel_dev_t;

       

        typedef __kernel_fd_set fd_set;

        typedef __kernel_dev_t dev_t; //用来持有设备编号的主次部分

        typedef __kernel_ino_t ino_t;

        typedef __kernel_mode_t mode_t;

       ...

       2.在 <linux/kdev_t.h>中的一套宏定义. 为获得一个 dev_t 的主或者次编号, 使用:

       2.1设备编号的内部表示

       MAJOR(dev_t dev);

       MINOR(dev_t dev);

       2.在有主次编号时, 需要将其转换为一个 dev_t, 可使用:

       MKDEV(int major, int minor);

       在linux/kdev_t.h中有下了内容

       ...

       4 #define MINORBITS

       5 #define MINORMASK ((1U << MINORBITS) - 1)

       6

       7 #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))

       8 #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))

       9 #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))//高为表示主设备号,低位表示次设备号

       ...

       3.分配和释放设备编号register_chrdev_region函数

       下面摘自文件fs/char_dev.c内核源代码

        /

**

        * register_chrdev_region() - register a range of device numbers

        * @from: the first in the desired range of device numbers; must include

        * the major number.

        * @count: the number of consecutive device numbers required

        * @name: the name of the device or driver.

       

*

        * Return value is zero on success, a negative error code on failure.

        */

        int register_chrdev_region(dev_t from, unsigned count, const char *name)

        {

        struct char_device_struct *cd;

        dev_t to = from + count; //计算分配号范围中的最大值+=

        dev_t n, next;

       

        for (n = from; n < to; n = next) { /*每次申请个设备号*/

        next = MKDEV(MAJOR(n)+1, 0);/*主设备号加一得到的设备号,次设备号为0*/

        if (next > to)

        next = to;

        cd = __register_chrdev_region(MAJOR(n), MINOR(n),

        next - n, name);

        if (IS_ERR(cd))

        goto fail;

        }

        return 0;

        fail:/*当一次分配失败的时候,释放所有已经分配到地设备号*/

        to = n;

        for (n = from; n < to; n = next) {

        next = MKDEV(MAJOR(n)+1, 0);

        kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));

        }

        return PTR_ERR(cd);

        }

       这里, from是要分配的起始设备编号. from 的次编号部分常常是 0, 但是没有要求是那个效果. count是你请求的连续设备编号的总数. 注意, 如果count 太大, 要求的范围可能溢出到下一个次编号;但是只要要求的编号范围可用, 一切都仍然会正确工作. 最后, name 是应当连接到这个编号范围的设备的名子; 它会出现在 /proc/devices 和 sysfs 中.如同大部分内核函数, 如果分配成功进行, register_chrdev_region 的返回值是 0. 出错的情况下, 返回一个负的错误码, 不能存取请求的区域.

       4.下面是char_device_struct结构体的信息

       fs/char_dev.c

       static struct char_device_struct {

       struct char_device_struct *next; // 指向散列冲突链表中的下一个元素的指针

       unsigned int major; // 主设备号

       unsigned int baseminor; // 起始次设备号

       int minorct; // 设备编号的范围大小

       const char *name; // 处理该设备编号范围内的设备驱动的名称

       struct file_operations *fops; // 没有使用

       struct cdev *cdev; /* will die指向字符设备驱动程序描述符的指针*/

       } *chrdevs[MAX_PROBE_HASH];

        /

*

        * Register a single major with a specified minor range.

       

*

        * If major == 0 this functions will dynamically allocate a major and return

        * its number.

       

*

        * If major > 0 this function will attempt to reserve the passed range of

        * minors and will return zero on success.

       

*

        * Returns a -ve errno on failure.

        */

       /

**

       * 该函数主要是注册注册注册主设备号和次设备号

       * major == 0此函数动态分配主设备号

       * major > 0 则是申请分配指定的主设备号

       * 返回0表示申请成功,返 回负数说明申请失败

       */

        static struct char_device_struct

*

        __register_chrdev_region(unsigned int major, unsigned int baseminor,

        int minorct, const char *name)

        { /*以下处理char_device_struct变量的初始化和注册*/

        struct char_device_struct *cd, **cp;

        int ret = 0;

        int i;

        //kzalloc()分配内存并且全部初始化为0,

        cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);

        if (cd == NULL)

       //ENOMEM定义在include/asm-generic/error-base.h中,

       // #define ENOMEM /* Out of memory */

        return ERR_PTR(-ENOMEM);

       

        mutex_lock(&chrdevs_lock);

       

        /* temporary */

        if (major == 0) { //下面动态申请主设备号

        for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i—) {

       //ARRAY_SIZE是定义为ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))

       //#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))

       if (chrdevs[i] == NULL)

       //chrdevs是内核中已经注册了的设备好设备的一个数组

        break;

        }

       

        if (i == 0) {

        ret = -EBUSY;

        goto out;

        }

        major = i;

        ret = major;//这里得到一个位使用的设备号

        }

        //下面四句是对已经申请到的设备数据结构进行填充

        cd->major = major;

        cd->baseminor = baseminor;

        cd->minorct = minorct;/*申请设备号的个数*/

        strlcpy(cd->name, name, sizeof(cd->name));

       /*以下部分将char_device_struct变量注册到内核*/

        i = major_to_index(major);

       

        for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)

        if ((*cp)->major > major || //chardevs[i]设备号大于主设备号

        ((*cp)->major == major &&

        (((*cp)->baseminor >= baseminor) || //chardevs[i]主设备号等于主设备号,并且此设备号大于baseminor

        ((*cp)->baseminor + (*cp)->minorct > baseminor))))

        break;

        //在字符设备数组中找到现在注册的设备

        /* Check for overlapping minor ranges. */

        if (*cp && (*cp)->major == major) {

        int old_min = (*cp)->baseminor;

        int old_max = (*cp)->baseminor + (*cp)->minorct - 1;

        int new_min = baseminor;

        int new_max = baseminor + minorct - 1;

       

        /* New driver overlaps from the left. */

        if (new_max >= old_min && new_max <= old_max) {

        ret = -EBUSY;

        goto out;

        }

       

        /* New driver overlaps from the right. */

        if (new_min <= old_max && new_min >= old_min) {

        ret = -EBUSY;

        goto out;

        }

        }

        /*所申请的设备好号能够满足*/

        cd->next = *cp;/*按照主设备号从小到大顺序排列*/

        *cp = cd;

        mutex_unlock(&chrdevs_lock);

        return cd;

        out:

        mutex_unlock(&chrdevs_lock);

        kfree(cd);

        return ERR_PTR(ret);

        }

       以上程序大体上分为两个步骤:

       1.char_device_struct类型变量的分配以及初始化~行

       2.将char_device_struct变量注册到内核,行页到行

       1.char_device_struct类型变量的分配以及初始化

       (1)首先,调用 kmalloc 分配一个 char_device_struct 变量cd。

       检查返回值,进行错误处理。

       (2)将分配的char_device_struct变量的内存区清零memset。

       (3)获取chrdevs_lock读写锁,rdd intersection 源码并且关闭中断,禁止内核抢占,write_lock_irq。

       (4)如果传入的主设备号major不为0,跳转到第(7)步。

       (5)这时,major为0,首先需要分配一个合适的主设备号。

       将 i 赋值成 ARRAY_SIZE(chrdevs)-1,其中的 chrdevs 是包含有个char_device_struct *类型的数组,

       然后递减 i 的值,直到在chrdevs数组中出现 NULL。当chrdevs数组中不存在空值的时候,

       ret = -EBUSY; goto out;

       (6)到达这里,就表明主设备号major已经有合法的值了,接着进行char_device_struct变量的初始化。

       设置major, baseminor, minorct以及name。

       2.将char_device_struct变量注册到内核

       (7)将 i 赋值成 major_to_index(major)

       将major对取余数,得到可以存放char_device_struct在chrdevs中的索引

       (8)进入循环,在chrdevs[i]的toggle源码 仙剑链表中找到一个合适位置。

       退出循环的条件:

       (1)chrdevs[i]为空。

       (2)chrdevs[i]的主设备号大于major。

       (3)chrdevs[i]的主设备号等于major,但是次设备号大于等于baseminor。

       注意:cp = &(*cp)->next,cp是char_device_struct **类型,(*cp)->next是一个char_device_struct

*

       类型,所以&(*cp)->next,就得到一个char_device_struct **,并且这时候由于是指针,所以

       对cp赋值,就相当于对链表中的元素的next字段进行操作。

       (9)进行冲突检查,因为退出循环的情况可能造成设备号冲突(产生交集)。

       如果*cp不空,并且*cp的major与要申请的major相同,此时,如果(*cp)->baseminor < baseminor + minorct,

       就会发生冲突,因为和已经分配了的复杂mfc源码设备号冲突了。出错就跳转到ret = -EBUSY; goto out;

       ()到这里,内核可以满足设备号的申请,将cd链接到链表中。

       ()释放chrdevs_lock读写锁,开中断,开内核抢占。

       ()返回加入链表的char_device_struct变量cd。

       ()out出错退出

       a.释放chrdevs_lock读写锁,开中断,开内核抢占。

       b.释放char_device_struct变量cd,kfree。

       c.返回错误信息

       下面程序出自fs/char_dev.c

       动态申请设备号

       ...

        /

**

        * alloc_chrdev_region() - register a range of char device numbers

        * @dev: output parameter for first assigned number

        * @baseminor: first of the requested range of minor numbers

        * @count: the number of minor numbers required

        * @name: the name of the associated device or driver

       

*

        * Allocates a range of char device numbers. The major number will be

        * chosen dynamically, and returned (along with the first minor number)

        * in @dev. Returns zero or a negative error code.

        */

        int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,

        const char *name)

        {

       /* dev:

       仅仅作为输出参数,成功分配后将保存已分配的第一个设备编号。

       baseminor:

       被请求的第一个次设备号,通常是0。

       count:

       所要分配的设备号的个数。

       name:

       和所分配的设备号范围相对应的设备名称。

       b.返回值:

       成功返回0,失败返回负的错误编码

       */

        struct char_device_struct *cd;

        cd = __register_chrdev_region(0, baseminor, count, name);

        if (IS_ERR(cd))

        return PTR_ERR(cd);

        *dev = MKDEV(cd->major, cd->baseminor);

        return 0;

        }

       

       ...

UGUI源码阅读之Mask

       Mask主要基于模版测试来进行裁剪,因此先来了解一下unity中的模版测试。

       Unity Shader中的模版测试配置代码大致如上

       模版测试的伪代码大概如上

       传统的渲染管线中,模版测试和深度测试一般发生在片元着色器(Fragment Shader)之后,但是现在又出现了Early Fragment Test,可以在片元着色器之前进行。

       Mask直接继承了UIBehaviour类,同时继承了ICanvasRaycastFilter和IMaterialModifier接口。

       Mask主要通过GetModifiedMaterial修改graphic的Material。大致流程:

       1.获取当前Mask的层stencilDepth

       2.StencilMaterial.Add修改baseMaterial的模板测试相关配置,并将其缓存

       3.StencilMaterial.Add设置一个unmaskMaterial,用于最后将模板值还原

       MaskableGraphic通过MaskUtilities.GetStencilDepth计算父节点的Mask层数,然后StencilMaterial.Add修改模板测试的配置。

       通过Frame Debugger看看具体每个batch都做了什么。先看第一个,是Mask1的m_MaskMaterial,关注Stencil相关的数值,白色圆内的stencil buffer的值设置为1

       这个是Mask2的m_MaskMaterial,根据stencil的计算公式,Ref & ReadMask=1,Comp=Equal,只有stencil buffer & ReadMask=1的像素可以通过模板测试,即第一个白色圆内的像素,然后Pass=Replace,会将通过的像素写入模板值(Ref & WriteMask=3),即两圆相交部分模板值为3

       这个是RawImage的Material,只有模板值等于3的像素可以通过模板测试,所以只有两个圆相交的部分可以写入buffer,其他部分舍弃,通过或者失败都不改变模板值

       这是Mask2的unmaskMaterial,将两个圆相交部分的模板值设置为1,也就是还原Mask2之前的stencil buffer

       这是Mask1的unmaskMaterial,将第一个圆内的模板值设置为0,还有成最初的stencil buffer

       可以看到Mask会产生比较严重的overdraw。

       2.drawcall和合批

       每添加一个mask,一般会增加2个drawcall(加上mask会阻断mask外和mask内的合批造成的额外drawcall),一个用于设置遮罩用的stencil buffer,一个用于还原stencil buffer。

       如图,同一个Mask下放置两个使用相同的RawImage,通过Profiler可以看到两个RawImage可以进行合批

       如图,两个RawImage使用相同的,它们处于不同的Mask之下,但是只要m_StencilValue相等,两个RawImage还是可以进行合批。同时可以看到Mask1和Mask1 (1),Mask2和Mask2 (1)也进行了合批,说明stencilDepth相等的Mask符合合批规则也可以进行合批。

       StencilMaterial.Add会将修改后的材质球缓存在m_List中,因此调用StencilMaterial.Add在相同参数情况下将获得同一个材质球。

MaskFormer源码解析

       整个代码结构基于detectron2框架,代码逻辑清晰,从配置文件中读取相关变量,无需过多关注注册指令,核心在于作者如何实现网络结构图中的关键组件。MaskFormer模型由backbone、sem_seg_head和criterion构成,backbone负责特征提取,sem_seg_head整合其他部分,criterion用于计算损失。

       在backbone部分,作者使用了resnet和swin两种网络,关注输出特征的键值,如'res2'、'res3'等。在MaskFormerHead中,核心在于提供Decoder功能,这个部分直接映射到模型的解码过程,通过layers()函数实现。

       pixel_decoder部分由配置文件指定,指向mask_former/heads/pixel_decoder.py文件中的TransformerEncoderPixelDecoder类,这个类负责将backbone提取的特征与Transformer结合,实现解码过程。predictor部分则是基于TransformerPredictor类,负责最终的预测输出。

       模型细节中,TransformerEncoderPixelDecoder将backbone特征与Transformer结合,生成mask_features。TransformerEncoderPixelDecoder返回的参数是FPN结果与Transformer编码结果,后者通过TransformerEncoder实现,关注维度调整以适应Transformer计算需求。predictor提供最终输出,通过Transformer结构实现类别预测与mask生成。

       损失函数计算部分采用匈牙利算法匹配查询和目标,实现类别损失和mask损失的计算,包括dice loss、focal loss等。整个模型结构和输出逻辑清晰,前向运算输出通过特定函数实现。

       总的来说,MaskFormer模型通过backbone提取特征,通过Transformer实现解码和预测,损失函数计算统一了语义分割和实例分割任务,实现了一种有效的方法。理解代码的关键在于关注核心组件的功能实现和参数配置,以及损失函数的设计思路。强烈建议阅读原论文以获取更深入的理解。