Vulkan下多线程渲染设计

2023-10-27

1 Vulkan 视角下的多线程渲染

首先我们需要从vulkan api的顶层框架上来看一下,它在哪些地方可以让我们并行。

Vulkan API的基本框架

Vulkan不同于Gles只有一个(不被API暴露出来的)单一链条的cmdbuffer处理,它最大的特点是允许多个、多种类型的cmdbuffer同时在多个设备线程上被处理

上图可以看到vk拥有多个physical device(或gpu,当然也可以是支持的其他处理器),只要是同一个physical device group中的physical device,就可以联合起来一起来创建出一个device(就是你的app 实例),而每个physical device上又有多个queue,这些queue属于三种queuefamily(gfx,cs,translate),每个queuefamily Qn都可以创建一个command pool Pn,每个Pn又可以创建一些cmdbuffer,这些cmdbuffer可以独自被处理,独自被提交给Qn的任意一个queue上。cmdbuffer是vk显示暴露的数据结构,它是cpu同gpu传输信息的桥梁,cpu将渲染指令记录到cmdbuffer上,然后通过提交给queue交由gpu执行。在一个cmdbuffer内部又包含了renderpass,指令被记录在renderpass里面或外面,记录在renderpass里面的指令还可以被封装成次级cmdbuffer,即secondarycmdbuffer的形式被主cmdbuffer执行。。。

总的来说,vulkan里面有并行的GPU设备,并行的queue,并行的cmdbuffer。

那么哪些阶段可以被并行执行?

Cpu一侧有两个主要的可被并行处理的阶段:

  1. Cmdbuffer的Record:所有vkcmd***类型的API都可以认为在进行cmdbuffer的record。我们可以拆解出多个独立的cmdbuffer,由不同的RHI线程进行api调用。(这其实是一般游戏的主要瓶颈,即drawcall数量瓶颈)
  2. Cmdbuffer的submit:不同的cmdbuffer可以被提交到各自单独的queue中。可以根据设备提供的queue的数量,创建多个queue,在不同的rhi线程中将不同的cmdbuffer submit到不同的queue。(在实际项目中submit占用的时间非常短,少于1ms,且多数情况一帧只需要提交1次,做这个优化性价比不高)
  3. 将cmdbuffer的record和submit并行,submit和record两个步骤又可以在不同线程执行,cmdbuffer在当前帧record,在下一帧submit。(同样因为submit不是瓶颈,且又需要延迟一帧渲染,性价比不高)
  4. 为Translate,Gfx,CS独立各自单独的queue和cmdbuffer,这样,图形drawcall,cs的dispatch,图形资源的准备这三种不同工作将在不同的线程上处理,减少drawcall被其他工作block的机会。(这个是有意义的,ue新版本有async compute shader,实际上就是使用了同gfx不同的cs queue,至于translate,则可以将很多非dc性质的api调用同dc的分离,提高dc的吞吐效率,这个在gles也可以模拟实现,可以见https://km.woa.com/group/24861/articles/show/489959,只不过vk下可以往一个单独的queue去提交。)

本文主要讨论cmdbuffer的并行record,他用来解决大量drawcallapi瓶颈。

结构如下,我们在多个线程调用api填充cmdbuffer,全部cmdbuffer准备好后从一个线程进行最终的submit。

 

关于并行粒度

讨论这个问题前我们需要了解vulkan API的一些设计限制。Vulkan有如下的API规范:

一个renderpass必须被包含在同一个cmdbuffer (即renderpass不能跨越cmdbuffer

这意味着我们不能将一个renderpass中的drawcall拆分到多个cmdbuffer中,而移动端游戏为了带宽的最小化,都是尽量减少合并renderpass的,既然这样是不是意味着在控制带宽(renderpass数量)和并行drawcall之间难以兼得?

不是的,vulkan考虑了这个问题,并引入secondarycommandbuffer的概念来解决这个问题,下面会详细讲述。

我们的设计支持两种类型的并行。

  1. 整个pass级别的并行(本文称为Async Pass

每个cmdbuffer里面封装1个或几个renderpass,renderpass完整的嵌入在一个cmdbuffer里面。如下图,每个thread上的cmdbuffer上有完整的renderpass,每个renderpass并行的调用API。因为renderpass之间相互隔离,它的实现最为简单,每个thread上就是正常的启动,结束一个pass和drawcall。

 

但是当一个renderpass里面的drawcall太多时,我们就必须实现drawcall级别的并行了。

     2.支持draw call级别的并行(本文称为Async Drawcall

正如前面提到的vulkan API限制renderpass不能跨越cmdbuffer,所以需要依靠secondary command buffer支持。

Secondary command buffer是中特殊的command buffer:

  • 它内部不能执行renderpass相关的操作,只能执行drawcall相关的API。
  • 它不能直接submmit给queue,只能被正常的command buffer(或称为primary command buffer)通过vkexecutecmdbuffer的形式执行
  • Vkexecutecmdbuffer必须在该primary commandbuffer结束记录状态前(vkendcommandbuffer)执行

这种级别的并行实现上就显得比较复杂,如下图,对thread1上的renderpass中的300个drawcall进行等分拆分到3个线程上。1.1-1.3的线程上都使用的secondary cmd buffer,他们填充好后,交给1线程的primary cmdbuffer execute,最后1线程才能执行endcmdbuffer。

但是这是不是说1线程要依赖1.1-1.3三个线程执行完才能继续下去?为了解决这个问题,可以在1线程上创建多个primary cmdbuffer,只有内嵌了secondary cmd buffer的那个primary cmdbuffer才需要等待,每当1线程上的一个primary cmd buffer (n)需要内嵌secondary cmd buffer时,就再重新开辟一个新的primary cmd buffer (n+1)即可,这样1线程上后续的dc在primary cmd buffer (n+1)上记录,primary cmd buffer (n)则负责等待它的所有secondary thread完成再end,我们最终在提交的时候只要保证在提交列表中primary cmd buffer (n)在primary cmd buffer (n+1)之前即可保证渲染顺序。

 

基于secondary cmdbuffer,可以将drawcall的并行拆到最细,理论上所有的drawcall都可以并行,只需要在最终submit之前保证所有thread的工作完成即可。

2 UE4现有的多线程渲染框架与改造目标

现有的UE4多线程渲染框架

逻辑,渲染指令生成和API调用三大任务分开

项目引擎之前在UE4现有版本加入了auxiliary rhi机制

将资源生成类型的任务放到一个单独的rhi线程,允许与小核,减少资源准备类api对drawcall 类api的block

现在新的目标是,在vulkan下将drawcallAPI并行处理:

  • 依然保留主rhi(main rhi thread),用来进行那些不适合拆分到子线程的api以及做最终的submit和present。
  • 对于相对独立,无法前后复用的pass,做并行的async render pass处理
  • 对于其他pass,如果体量较大,将其中的部分分拆出来做async draw call 处理
  • 为了能够异步的填充rhi command,每个rhi thread也要对应一个单独的render thread,即render thread 本身也被拆开并行

移动端对于线程数目的考量:

我们常说对于移动端不要肆意的使用多线程优化,因为最好的android设备也就4个大核,所以前面我们描绘的架构虽然可以让你拥有无限多的线程,任意分拆drawcall,但那是脱离实际的完美情况。

因为考虑到已经存在的game,main render,main rhi3个大线程,所以最终我们手机上实际上只会增多一个异步的rhi线程(本文称task rhi thread)和一个异步的render 线程(本文称task render thread),更多的异步线程数量测试上都会导致效率变差。也就是说我们其实只是给原有单链条的渲染API增加了一条新路,变成两条而已。

最终版本移动端的vulkan多线程渲染框架如下

 

在上图中,实时的csm shadow作为async pass放在了task上(因为他永远是单独的pass,是的因为drawcall变的廉价,甚至可以尝试不用csm shadow cache~),而static mesh部分因为原本就有可能同其他部分共用pass而被作为async drawcall放在task上。

 

3 render thread rhi thread改造的更多细节

vksecondary command buffer相关机制支持:

  • secondary command buffer的创建:和primary 不一样,创建时需要一个特殊的flag,需要为它维护一个单独的cmdbuffer pool

 

 

  • secondary command bufferbegin,这里涉及到vulkan的如下设计规范:

      

secondary cmdbufferbegin的时候就要传入它所被嵌入的renderpass

和primary cmdbuffer不一样的是,secondary cmd buffer在begin的时候需要指定它所在的renderpass,这意味着虽然primary和secondary是在各自独立的线程记录,但是renderpass这个信息是api record的时候唯一必备的外部信息(事实上文档上说如果能提供primary的framebuffer则更有利于优化,不提供也可),而在目前ue的框架下,renderpass是有可能在primary的cmd beginrenderpass时才创建的,所以secondary cmd buffer的执行至少要在primary command buffer的begin pass 执行之后执行,这是一个同步点。但是如果我们有了rendergraph之类的,能够在一帧开始前就先验的知道这帧要用到的所有renderpass,这里面就不需要这个等待了。

此外Secondary command buffer begin的时候还需要设置begin flag为renderpass_continue_bit,(表示它作为一个secondary buffer,完全在另一个cmdbuffer的pass内执行

  • secondary command buffer内部的渲染状态,这里涉及到vulkan的如下设计规范:

secondary cmdbuffer内部不能感知包含它的primarycmdbuffer的渲染状态

vulkan并没有在官方文档很明显的地方提到这个问题,只在一些角落暗示过,实际上它意味着你在primary cmdbuffer级别里面设置的各种渲染状态,如shader,stencil什么的,对于它里面包含的secondary cmdbuffer是无效的,你必须在secondary cmdbuffer里面重新设置过。

  这要求你在record一个secondary cmd buffer的时候要能在程序中获取到它所处的primary 的一些外部渲染环境,设置进去,在程序实现的时候这可能是要特别小心的。

  • 需要灵活支持一个 renderpasssubpass_content_secondary还是subpass_content_inline状态,这是非常麻烦的一个改造。这里又涉及到vulkan的一个设计规范:

Vulkanrenderpass在一个subpass之内只能处于以下两种模式之一:要么里面完全只能执行secondary cmdbuffer(所有非vkexecutesecondaryapi皆为非法),要么里面完全不能执行secondary cmd buffer

看下面的这个图,第一种情况是ok的,subpass之内都是统一的内容状态,但是第二种是不行的,因为它杂糅了普通drawcall和secondaryexecute 

这是非常麻烦的一个点,为什么这样说?

通常我们移动端游戏为了减少render pass的切换,需要尽量复用一个renderpass,而一个大的renderpass它可能只是部分drawcall需要被拆出来到task rhi上异步执行,那就不可避免的出现这种“杂糅”状况。

为了解决这种杂糅,一种是如上图第一种形式插入更多的nextsubpass,用subpass做篱笆将不同种类分开,但是这是有损gpu性能的,我们不能确定硬件在切subpass的时候对于相同的rt情况下是否有优化,有可能将产生rt内容的load/store。另一种是如下图所示,只要一个renderpass中有任何一个drawcall被拆出来放到taskrhi上,那么整个renderpass的所有drawcall都需要被拆成secondary cmd buffer的形式在,只是说有个secondary cmd buffer在task上异步填充,有的依然在当前primary的main rhi上填充。

 

从上面这个图能够看出来,pass内部全都是vkexecutesecondary了,注意这里不意味着secondary4 要等待异步线程吧secondary 2和3处理完,他们依然是并行的,你只需要记住在4前面还有个2和3,只需要在最终submmit的之前end这个pass,在endpass的时候按照1 2 3 4的顺序执行这个execute就可以的。

  •       带有async drawcallrenderpassend

对于内嵌了secondary的renderpass在end的时候有个新的问题,因为executesecondary必须要发生在endpass之前,而mainrhi想endpass的时候task rhi可能还没有执行完,这会导致asyn rhi对main rhi的阻塞,因为main rhi还有后续的renderpass要begin。

一种解决方案是,此时重新begin一个新的primary cmd buffer,用于begin后续的renderpass,而之前的那个primary cmd buffer会等待它的secondary都执行完。这意味着我们的设计思路是:所有的renderpass都会延迟到最终submmit之前才进行帧的endpass(endpass前要按顺序execute secondary),每当一个renderpass被要求endpass的时候,如果他内含secondary,就马上启动一个新的的primary cmd buffer记录后续renderpass。

 

如上面图所示endpass只是加了一个mark,后面会开启新的primary cmd buffer,我们知道一次submit的时候可以submit多个primay cmd buffer,你只需要保证他们在submitinfo中的列表顺序就能保证他们的渲染顺序。

这样原本ue4中submmit的一个单一的primary cmd buffer可能被碎解成多个,因此vkcmdbuffer的结构也需要做些改造,里面增加了previous cmd buffer的数组,此外因为primary cmdbuffer还可能包含一些secondary cmd buffer,所以还增加了一个children 数组。

改造后的结构是如下:

 

  • Primary Cmd bufferSubmmit提交,提交是最终的一步操作,也是整个结构唯一需要同步等待所有线程工作完成的地方,在main rhi上对于每个primary cmd buffer,依次调用vkexecutecmdbuffer执行它的每个secondary cmd buffer(这个执行顺序很重要,它就是gpu的处理顺序),再执行相应的nextsubpass,endpass,endcmdbuffer这操作,当然primary cmdbuffer要按照渲染顺序放入submitinfo中。

Thread-safe

首先考虑到任何renderthread上的逻辑都有可能被在异步执行,所以一些不thread-safe的渲染线程代码都要改造,例如去掉一些单例的使用。

Task rendermain Render 之间的关系

在每帧render结束前,main render会等待task render执行完毕,所以要合适的分配main 和task的任务,尽量让main的工作量稍多一些。

其他问题

负载均衡

我们需要很好的平衡main 和task 两个渲染线程间的任务量,让main的任务稍多与task一点点,最好的方式是基于frame graph预先知道当前帧需要绘制哪些东西,才能做到最佳的负载均衡

GPU负载同HSR失效

我们发现在一些Adreno设备上,当使用secondary command buffer进行多线程提交时,gpu时间会大大增长,很像是硬件的深度筛选(即adreno的LRZ技术)失效,导致了更多的overdraw。

通过翻阅官方文档,确实找到了官方的说法,在文档https://blogs.igalia.com/dpiliaiev/adreno-lrz/和https://developer.qualcomm.com/sites/default/files/docs/adreno-gpu/developer-guide/gpu/overview.html#lrz中都有明确提到:

 

在sdp 855及以下的设备上,因为lrz机制基于的ztest方向跟踪是在cpu一侧做的,并行的api提交会让他紊乱,这样会导致lrz失效,进而gpu不能准确找到每个pixel最靠前的primitive进行渲染,出现更多的overdraw。而865以后因为在gpu上进行跟踪,所以解决了这个问题。但是我们的实际测试数据发现一些865 870的设备依然存在这个问题,保险起见,secondary cmd buffer只能运行在888及以上的设备上,这真的是一个令人悲伤的事情,说明硬件的发展还没有很好的为vulkan做好优化啊。

但是好消息是mali的所有设备不存在这个问题!~~

5 性能分析

 

从android的trace上可以看到有两条并行提交的队列,相比只有一条提交队列,总的渲染的cpu耗时被大大缩减,在我们项目中大部分情况下rhi线程的时间被缩短30%左右,有意思的一点是在大部分设备上,gpu的耗时也略微减少,目前还不能很好的解释。

6.结语

Vulkan作为现代的图形API,有着更加强大复杂的特性,同gles相比,更像是C++对比lua,它可以使我们从更底层的视角去看待图形编程,自己掌握多线程,内存分配,同步等,我们就不能像应用古老的gles一样去搭建渲染框架,那样就只是用着vulkan的皮囊而没有发挥它真正的威力,不过这对编程人员是没有那么友好的,且移动端的前人经验不多,就需要深入分析vulkan的文档,多尝试,另外vulkan作为一个追求性能的api,不太在api层次做校验,这导致对api的调用极易引起gpu的device lost,必须擅用它的validation layer及时发现潜在的问题。

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Vulkan下多线程渲染设计 的相关文章

  • UE4/UE5 动画控制

    工程下载 https mbd pub o bread ZJ2cm5pu 蓝图控制sequence播放 倒播动画 设置开启鼠标指针 开启鼠标事件 在场景中进行过场动画制作 设置控制事件
  • UE4 UI界面

    在UE4中创建UI界面是创建一个widget 进去之后左上角是选择控件 找到直接拖上去 中间那个框代表的就是我们的屏幕 在button中打字也就是给button命名时需要在上面在拖一个text控件 更好的排版可以改变锚点 这四个就类似与边距
  • UE4的视频播放(Media Player)

    1 视频播放Begining 首先将需要播放的视频拖入 创建Media Player和Media Texture 创建Material 将材质改为User Interface 在UI界面 创建Image 将这个材质装入 在人物Pawn界面添
  • UE4 VR WidgetInteraction 局域网设定

    无论用什么 我们要先确定是什么 鲁迅 下面是关于WidgetInteraction的官方定义 控件交互组件执行光线投射 确定它是否命中世界场景中的控件组件 如命中 可设置规则确定与其交互的方式 交互通过模拟定义的按键来执行 例如一个按钮可通
  • ue4_timeline时间轴

    1 给一个cube添加蓝图 需要修改的是z轴方向移动位置 将z轴传入时间轴 时间轴蓝图如下 z轴时间轴修改为 第一个节点 time 0 value 300 物体的z轴初始位置 第二个节点 time 1 value 600 z轴移动300个单
  • Unity震撼首发,最新一代高清数字人短片《Enemies》

    我们屡获殊荣的 Demo 团队又一次在 异教徒 The Heretic 累积了超 400 万观众 的基础上取得了进展 推出了 Enemies 一支全新的电影式预告片 以 4K 分辨率的实时渲染来展示眼睛 头发和皮肤渲染等方面的重大突破 创建
  • UE4文字显示乱码“字字字字字字字字”的解决办法

    键盘win R 搜索fonts 2 滑到最底下右键复制 宋体常规简体字 3 复制到ue4项目的字体文件夹中 如下 注意在外部文件处复制 4 回到项目界面 此时右下角会有个弹窗提示是否确认导入 点击导入 然后会弹一个 字体样式导入选项 弹框
  • UE4 解决景深效果闪烁问题

    原因 1 模型的垂直竖线 造成抗锯齿算法对竖线的渲染计算 处于一种不稳定的状态 因此闪烁 解决办法 使用LOD 用贴图去替代线条模型 2 材质的法线贴图 当法线贴图含有垂直竖线的纹理效果 也会造成闪烁 比如这种幕墙材质 解决办法 关闭或动态
  • 着色器中包围体层次结构的遍历

    我正在使用 vulkan 计算着色器开发路径跟踪器 我实现了一棵树代表包围体层次结构 BVH 的想法是最大限度地减少需要执行光线相交测试的对象数量 1 简单的实施 我的第一个实现非常快 它遍历树到singleBVH 树的叶子 然而 射线可能
  • GtkGLArea 小部件 (GTK+) 的 Vulkan 等效项是什么?

    背景 我想编写一个 CAD 应用程序 我想用Vulkan实现所有渲染 我想使用 GTK 我不想要一个 可见的 不同的窗口来显示对象的渲染图像 所以我研究并发现GtkGLAreawidget 它似乎可以满足我的需求 但是对于 OpenGL 有
  • 线性化深度

    在 OpenGL 中 您可以像这样线性化深度值 float linearize depth float d float zNear float zFar float z n 2 0 d 1 0 return 2 0 zNear zFar z
  • MacOS - 当 VkPhysicalDeviceFeatures WideLines = VK_TURE 且不支持 vkCmdSetLineWidth API 时,Vulkan 在运行时 vkCreateDevice() 失败

    我是 Vulkan 新手 最近开始学习 我在运行时遇到问题vkCreateDevice 失败 当VkPhysicalDeviceFeatures是启用与 VkPhysicalDeviceFeatures 功能 features wideLi
  • Vulkan 中的 VKAPI_ATTR 和 VKAPI_CALL 宏

    我一直在寻找但我仍然不确定what VKAPI ATTR and VKAPI CALL是 我不确定它们是否应该是一个宏或一些我不知道的奇特的 C 函数声明 What is VKAPI ATTR void VKAPI CALL vkComma
  • OSG中几何体的绘制(二)

    5 几何体操作 在本章的前言中就讲到 场景都是由基本的绘图基元构成的 基本的绘图基元构成简单的几何体 简单的几何体构成复杂的几何体 复杂的几何体最终构造成复杂的场景 当多个几何体组合时 可能存在多种降低场景渲染效率的原因 在很多3D引擎中
  • 如何将 Vulkan 与 MinGW 结合使用? (R_X86_64_32 错误)

    我正在尝试设置一个简单的程序来使用 Vulkan 我安装了 LunarG SDK 我有一个小程序 基本上只是调用vkCreateInstance 我用这一行编译 g std c 11 I c VulkanSDK 1 0 3 1 Includ
  • 对 VkDescriptorPoolCreateInfo.pPoolSizes 的这种理解是否正确?

    在Vulkan中 我知道描述符池用于分配某些布局的描述符集以在着色器中使用 但是在VkDescriptorPoolCreateInfo传递给vkCreateDescriptorPool 有一个字段pPoolSizes它需要一堆包含描述符类型
  • 在 Vulkan 中重新绑定图形管道是否保证无操作?

    在简化的场景中 每个要渲染的对象都被转换为辅助命令缓冲区 并且每个命令缓冲区最初都绑定一个图形管道 是否可以保证无操作来绑定之前立即绑定的管道 或者辅助命令缓冲区的执行顺序根本无法保证 是否可以保证无操作来绑定之前立即绑定的管道 不 事实上
  • 如何为已渲染的多个3D模型拥有多个模型矩阵?

    我已经遵循了 vulkan 教程的大部分内容 https vulkan tutorial com https vulkan tutorial com 我目前有一个 vulkan 程序 可以使用 OBJ 文件加载多个 3D 模型 但是我只有一
  • Vulkan:在多个命令缓冲区中排序图像内存屏障

    对于资源转换 您需要了解 之前 和 之后 VkImageLayout资源的 例如 在VkImageMemoryBarrier传递给vkCmdPipelineBarrier Vulkan 不保证命令缓冲区执行的任何顺序 除非 API 文档中明
  • vulkan 扩展:哪些由谁支持?

    有EXT KHR or AMD or NV扩展 也许还有其他一些 我知道NV means NvidiaAMD 不太可能支持 it nv 扩展 但是 khr 或 ext 又如何呢 他们是所有人都强制支持的吗 有一个website https

随机推荐

  • 回顾2021,展望2022

    2021 这一年最大的收获是孕育了一个聪明漂亮机灵的小家伙 这一年我虚岁28岁 和爱的人有了爱的结晶 东哥各方面都挺好的 我们都不是圣人 都是能力有限的普通人 但他在尽其所能的对我好 我不是万能的人 但也独立坚强 之后一个人带娃的日子适应的
  • LaTeX公式中指定某些字母或符号为正体

    m rm G 显示效果为
  • I/O 函数/缓存和字节流、占位符、getchar(),putchar()

    I O 函数 C 语言提供了一些函数 用于与外部设备通信 称为输入输出函数 简称 I O 函数 输入 import 指的是获取外部数据 输出 export 指的是向外部传递数据 缓存和字节流 严格地说 输入输出函数并不是直接与外部设备通信
  • 从零开始学C++之异常(三):异常与继承、异常与指针、异常规格说明

    一 异常与继承 如果异常类型为C 的类 并且该类有其基类 则应该将派生类的错误处理程序放在前面 基类的错误处理程序放在后面 include
  • python3.5源码分析-启动与虚拟机

    Python3源码分析 本文环境python3 5 2 参考书籍 lt
  • 虚幻引擎游戏开发过程中,游戏鼠标如何双击判定?

    UE虚幻引擎对于游戏开发者来说都不陌生 市面上有47 主机游戏使用虚幻引擎开发游戏 作为是一款游戏的核心动力 它的功能十分完善 囊括了场景制作 灯光渲染 动作镜头 粒子特效 材质蓝图等 本文介绍了虚幻引擎游戏开发过程中游戏鼠标双击判定 一起
  • 数据库变更管理:Liquibase or Flyway

    从零打造项目 系列文章 工具 比MyBatis Generator更强大的代码生成器 ORM框架选型 SpringBoot项目基础设施搭建 SpringBoot集成Mybatis项目实操 SpringBoot集成MybatisPlus项目实
  • vsftpd默认值

    原文地址 http vsftpd beasts org vsftpd conf html 国内解释 http hi baidu com 346430756 blog item 5527e9363402652a0b55a9d0 html VS
  • Leetcode链表篇总结(C++)

    文章目录 一 基础知识 二 经典题目 1 203 移除链表元素 简单 2 707 设计链表 中等 3 206 反转链表 简单 4 142 环形链表 中等 5 19 删除链表的倒数第N个结点 中等 6 面试题 02 07 链表相交 简单 三
  • Java实现添加文字水印、图片水印功能实战

    本文介绍java实现在图片上加文字水印的方法 水印可以是图片或者文字 操作方便 java实现给图片添加水印实现步骤 获取原图片对象信息 本地图片或网络图片 添加水印 设置水印颜色 字体 坐标等 处理输出目标图片 一 java实现给图片添加文
  • 目标检测的数据格式

    在目标检测任务中 常见的数据集格式有三种 分别为voc coco yolo 一 VOC voc数据集由五个部分构成 JPEGImages Annotations ImageSets SegmentationClass以及Segmentati
  • lua元表的相关知识

    setmetatable 和getmetatable local a 8 local b s local t 1 2 在Lua代码中 只能设置table的元表 若要设置其它类型的值得元表 则必须通过C代码来完成 对于字符串 标准的字符串程序
  • Linux中apt命令

    apt简介 Advanced Packaging Tool apt 是Linux下的一款安装包管理工具 最初只有 tar gz的打包文件 用户必须编译每个他想在GNU Linux上运行的软件 用户们普遍认为系统很有必要提供一种方法来管理这些
  • Github的创建及使用

    Github创建 注册账号 进入GitHub官网 https github com 步骤1 注册账号 username 不能使用下划线 并且短横线不能打头 中文也是不合法昵称 email 要填写合法邮箱 并且是未在GitHub注册过的邮箱
  • 什么是大小端?如何确定大小端?

    一 什么是大小端 对于一个由2个字节组成的16位整数 在内存中存储这两个字节有两种方法 一种是将低序字节存储在起始地址 这称为小端 little endian 字节序 另一种方法是将高序字节存储在起始地址 这称为大端 big endian
  • 基于微信小程序的游泳馆管理系统

    末尾获取源码 开发语言 Java Java开发工具 JDK1 8 后端框架 SSM 前端 Vue 数据库 MySQL5 7和Navicat管理工具结合 开发软件 IDEA Eclipse 小程序 微信开发者工具 是否Maven项目 是 目录
  • DHCP协议工作原理(分配IP地址的方式)

    DHCP工作在应用层 使用UDP协议工作 负责给局域网内的用户分配IP地址 分配IP地址的方式有三种 手动配置 自动配置 动态配置 手动配置是指管理员手动给客户端配置一个特定的IP地址 自动配置是指服务器为第一次链接的客户端分配一个永久地址
  • 数据挖掘实验第一次作业

    import random from matplotlib import pyplot class MTKL def init self n m self n n self m m def MC self n self n m self m
  • c语言中%s的用法

    转自 https www pinlue com article 2020 03 3100 5310073904413 html C语言是计算机软件领域非常经典的编程语言 unix linux等众多操作系统均是由C语言编写而成 而在硬件控制
  • Vulkan下多线程渲染设计

    1 Vulkan 视角下的多线程渲染 首先我们需要从vulkan api的顶层框架上来看一下 它在哪些地方可以让我们并行 Vulkan API的基本框架 Vulkan不同于Gles只有一个 不被API暴露出来的 单一链条的cmdbuffer