鸿蒙NEXT开发【马良GPU最佳实践】GPU加速实践

简介

概述

本文档主要指导开发者如何在马良(Maleoon)GPU上达到最佳的性能表现,只针对Maleoon GPU的优化实践,帮助开发者高效完成渲染任务,如果想要达成该目标,首先需要通过[Graphics Profiler]等GPU分析工具,找到当前的能效瓶颈点,并遵循以下两个基本优化原则进行性能调优。本文所有优化建议,都将围绕这两个基本优化原则展开。

  1. 结合Maleoon GPU的软硬件架构,让驱动和硬件各模块并发执行,使芯片能力充分释放出来。
  2. 利用硬件能力高效执行任务,从而降低芯片负载。

图形渲染的基本流程如下图所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

读者对象

本文档主要适用于以下开发者:

熟悉图形标准API([Vulkan]或[OpenGL ES])、了解shader编码,有一定GPU性能优化基础。

CPU优化

Memory

1. Device Memory分配与释放

vkAllocateMemory为了避免分配的内存没有真正被渲染线程使用,造成内存浪费,采用了延迟分配(protected memory除外)的处理。在调用vkBindImageMemory/vkBindBufferMemory/vkMapMemory(如果内存支持VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT属性)时会真正分配内存,但是需要在此之前调用vkAllocateMemory分配device memory对象。

【推荐】

根据资源需求,选择最为匹配的内存类型进行内存分配,相同类型的memory按照用户实际需求一次分配大块size用于不同类型的资源(比如index buffer、vertex buffer及uniform buffer),可以提升内存申请的效率。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • vkFreeMemory一定要与vkAllocateMemory成对使用,避免内存泄露。
  • 绑定的memory资源尽量分时复用。
  • 如果存在CPU访问内存,建议使用VK_MEMORY_PROPERTY_HOST_CACHED_BIT进行申请。

【不推荐】

频繁使用vkAllocateMemory。设备内存申请次数支持的最大数量可以通过maxMemoryAllocationCount limits属性获取。

2. D evice Memory访问

按照spec描述的内存类型范围支持4种类型:

  • VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
  • VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT
  • VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT
  • VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_PROTECTED_BIT

对于不同类型的memory资源,有不同的使用方法:

  • DEVICE_LOCAL | HOST_VISIBLE | HOST_COHERENT类型:属于CPU non-cache的内存,最好是用于CPU只写的资源。

  • DEVICE_LOCAL | HOST_VISIBLE | HOST_CACHED类型:HOST_CACHED内存的访问要考虑一致性问题,该类型内存写操作后要使用如下接口进行cache同步操作:

    • vkFlushMappedMemoryRanges:CPU修改对GPU可见,CPU到GPU的同步。
    • vkInvalidateMappedMemoryRanges:GPU对memory的更新对CPU可见,GPU到CPU的同步。

    以上这两个操作对性能都有一定的消耗。

  • DEVICE_LOCAL | LAZILY_ALLOCATED类型:此种类型的内存允许只分配虚拟地址空间而不分配物理页,如果存在内存访问,则按需分配更多的物理页,支持内存增长。

  • DEVICE_LOCAL | PROTECTED类型:Protected memory将内存分成了Protected device memory和Unprotected device memory两种。

    • Protected device memory只对device(即GPU)可见,对host(即CPU)不可见。
    • Unprotected device memory对device可见,也可以对host可见,取决于host visiable属性。

【推荐】

  • DEVICE_LOCAL | HOST_VISIBLE | HOST_COHERENT类型的内存用于CPU只写的资源。
  • DEVICE_LOCAL | HOST_VISIBLE | HOST_CACHED类型的内存用于CPU回读的资源。
  • DEVICE_LOCAL | LAZILY_ALLOCATED类型的内存只能用于TRANSIENT_ATTACHMENT用途的image。

【不推荐】

从CPU non-cache的内存中频繁回读数据,会影响性能。

Pipeline

Pipeline是整个渲染过程的状态集合,包括可编程的shader和不可编程的fixed function state,其参数配置影响整个渲染过程的执行效率。

1. Shader Stage

【推荐】

  • 在shader中如果需要用同一个sampler对同一个纹理多次采样的结果进行加权处理(比如模糊处理)并且权值和坐标是都mediump,建议可以使用base采样坐标加多组偏移的方式进行采样,并且用一层for循环进行多次采样加权操作。base采样坐标、偏移值和加权值需要是uniform,例如编译时常量或者来自uniform buffer(循环次数为编译时常量,最大为64)。
vec2 baseCoord = ve2(0.0);
vec2 offset[4] = {...};
float weight[4] = {...};
vec4 color = vec4(0.0);
for (int i = 0; i < 4; i++) {
    color += weight[i] * texture(texSampler, baseCoord + offset[i]);
}
  • shader中如果访问小批量的常量数据,推荐使用push constant。

  • shader中访问uniform buffer array时,索引下标使用编译时常量。

  • shader中访问uniform buffer时,访问的位置使用编译时常量。

  • compute shader的一个workgroup size的三个维度的乘积为32的整数倍。

  • fragment shader中,使用input attribute作为采样坐标去采样的次数小于等于8次,且所有采样用的texture和sampler都放到同一个descriptor set中。

【不推荐】

  • 使用tessellation shader和geometry shader。
  • create VkDevice时使能VkPhysicalDeviceProtectedMemoryFeatures::protectedMemory(或者VkPhysicalDeviceVulkan11Features::protectedMemory)。
  • 需要使能depth test时,fragment shader中使用discard指令。
  • 需要使能depth test时,fragment shader中写gl_FragDepth。
  • 需要使能depth test时,fragment shader中写了storage image或者storage buffer。
  • 需要使能depth test时,fragment shader中写了gl_SampleMask。
  • fragment shader中使用highp。
  • 需要使能blend时,fragment shader的输出color值与color attachment format的精度不匹配。例如color format是VK_FORMAT_B10G11R11_UFLOAT_PACK32,针对R通道,预期颜色在0.0~1.0f精度范围内时11bit的float可以表示较为精准,但是数值越大能够表示的精度越差。两个相邻像素本来预期的颜色假如是1024.0f和1024.1f,但是11bit可以精确表示1024.0f,但是无法精确表示1024.1f,所以1024.1f被存储成1040.0f,导致视觉效果上产生突变。

2. Fixed State

【推荐】

  • 不同的vertex attribute在vertex buffer内连续排布。
  • 不同的vertex attribute其所属的vertex buffer的binding号从0开始连续编排。比如有2个vertex buffer,其binding号配置为0和1性能较友好,配置为1和3性能不友好。
  • App如果需要提前使用vkCreateGraphicsPipelines进行场景预热,建议create info中(主要是renderpass、pipeline layout、blend state、multisample count、alpha to coverage)要填充真实场景所使用的create info,因为这些info会影响shader的编译,如果预热时填充的create info与真实场景不一致,pipeline cache起不到作用,达不到预热的目的。

【不推荐】

使能depth test时,开启alpha to coverage。

【影响】

使能multisample,消耗多倍GPU运行资源,降低执行效率。

Shader编译

Shader的首次编译(glCompileShader,glLinkProgram)耗时长,若在应用运行时进行实时编译,容易引起卡顿和丢帧,影响用户体验。

【推荐】

建议将Shader编译提前至应用启动时进行。

【不推荐】

在应用运行时实时编译Shader。

Command Buffer

Command buffer usage flags会影响command buffer的执行性能。当使用了SIMULTANEOUS_USE_BIT时,会降低command buffer的执行性能。如果在renderpass内使用secondary command buffer,flags不会有影响。

【推荐】

使用ONE_TIME_SUBMIT_BIT flag创建Command Buffer。

【不推荐】

除了用在renderpass内的secondary command buffer,其他类型的command buffer使用SIMULTANEOUS_USE_BIT。

Draw Call Batching

游戏下发的draw call数量越多,带来的CPU开销越多,整体的性能越差,建议合并draw call来降低CPU侧的开销。

【推荐】

一些典型的Draw call Batching场景:

  • 使用shader相同,资源不同

    所使用的vertex buffers或者texture等资源变化,这些draw可以通过合并vertex buffer或使用texture arrays等方式来合并。

  • 使用shader和Mesh相同

    通常绘制石头、树、灌木丛等情况,游戏会使用多个相同几何形状的instance去绘制,这种情况建议使用instance draw来完成。

  • 使用Multi-Draw indirect功能

【不推荐】

  • 使用大量顶点数少的draw call。
  • 频繁切换管线状态,例如program、uniform等,对应相同管线状态的draw call尽量集中处理。

GPU优化

Vertex shading

1. 精度

为了规避Vertex shader计算位置信息的偏差导致后续shader stage误差放大,Maleoon GPU上vertex shader精度统一按照highp实现。

【推荐】

建议使用highp设置。

2. InstanceID

InstanceID经常会参与uniform buffer索引值的计算,此种情况下,Maleoon GPU会根据是否能有效减小load mem的次数从而开启Single InstanceID优化。此优化可以保证每组任务运行时InstanceID一致,从而每组任务load mem只用执行first thread一次load即可拿到整组对应数据。进一步,如果此shader所有uniform不超过1024 bytes大小,此uniform buffer可以完全放在constant register里面,即可通过Maleoon GPU特有的relative constant register代替此load mem操作,性能最优。

【推荐】

  • InstanceID参与uniform buffer索引值计算时,uniform buffer中尽量精简只保留有效数据。
  • 条件允许下,InstanceID每个实例可以多画一些点,性能收益更大。
  • InstanceID参与uniform buffer索引值计算时,尽量不要在复杂的嵌套之内。

【示例】

原始shader:

struct unity_ Type {
    vec4 vecArrA[4];
    vec4 vecArrB[4];
};
layout(std140) uniform UnityInstancing_PerDraw0 {
    unity_Type unity_Array[32];
};
void main() {
    u_xlati0 = gl_InstanceID + unity_BaseInstanceID;
    u_xlati0 = u_xlati0 << 2;
    u_xlat1.yw = (-_mhyWorldOffset.zx) + unity_Array[u_xlati0 / 4].vecArrA[3].zx;
}

推荐shader:

struct unity_ Type {
    vec4 vecArrA[4];
};
layout(std140) uniform UnityInstancing_PerDraw0 {
    unity_Type unity_Array[32];
};
void main() {
    u_xlati0 = gl_InstanceID + unity_BaseInstanceID;
    u_xlati0 = u_xlati0 << 2;
    u_xlat1.yw = (-_mhyWorldOffset.zx) + unity_Array[u_xlati0 / 4].vecArrA[3].zx;
}

3. 顶点排布

Maleoon GPU是Tile-Based架构的GPU,对于Tile-Based架构的GPU,有一个单独的pass(binning pass)仅用于计算顶点着色器的顶点位置。因此,如果输入的顶点位置相关属性与所有其他属性存储在同一个buffer中,则binning pass获取vertex shader输入时,由于memory的连续读取粒度较大,将会导致实际读取到较多对该shader无效的输入数据,内存带宽将会增加很多。

【推荐】

将顶点相关的属性存储在独立buffer中。

Per-Fragment Test

Overdraw

Overdraw作为影响GPU性能的核心问题之一,开发者可以对于不透明的primitive进行排序,让后面的primitive可以被剔除,GPU也在硬件层次上支持不依赖primitive排序的剔除方案。

Maleoon GPU内部有一套针对Depth Tes

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值