unity中framebufferfetch在arm mali GPU设备上崩溃
type
Post
status
Published
date
Apr 10, 2023
slug
unity_framebufferfetch_arm_crash
summary
tags
category
icon
password
最近在优化游戏带宽,发现URP流程下的copy depth/copy color导致打断渲染产生片上带宽的消耗非常大,[数据]
 
我们深度图只用于水的深度渐变、decal、软粒子,在这些场景中没有随机读的需求,只需要知道当前像素的深度值。
 
可以用移动芯片framebufferfetch的技术做优化,这个功能在unity中是内置的。但unity只支持GL_EXT_shader_framebuffer_fetch 。这个官方扩展只支持读颜色,那简单的,我们可以把深度数据encode到alpha通道,然后在需要读取深度的fragment decode出深度值。
#pragma only_renderers framebufferfetch void frag(v2f i, inout float4 ocol : SV_Target) { ocol.rgb = col.rgb*col.a + ocol.rgb*(1.0-col.a); }
但考虑带宽,现在一般android的默认RT是RGBA8888(32b),8位的深度值在使用中是不够的,如果再上一层就只能用RGBA16(64b), 带宽消耗又会多一倍。我们考虑在encode的时候主要关心主角周围的,效果还能接受。
使用这个技术,带宽可以降大概1000MB/s, 耗电量可以减少100ma,效果拔群。
 
但有个很坑的点是,unity这套方案在arm mali GPU上不支持
 
原以为所谓不支持,我只需要判断下arm机型,做下shader的lod就可以,没有想到会在shader的编译阶段就crash,非常难用。
 
这样我们就只能在unity内手写glsl,非常难写,unity本身基本上不会给出shader写法正确性。不像原生的cg语法,unity会给出错误提示。
手动转换hlsl代码取glsl,大致的框架:
Shader "GLSLFetch" { // defines the name of the shader Properties{ _MainTex("MainTex", 2D) = "white" {} } GLSLINCLUDE #include "UnityCG.glslinc" ENDGLSL SubShader{ // Unity chooses the subshader that fits the GPU best Pass { // some shaders require multiple passes GLSLPROGRAM // here begins the part in Unity's GLSL #ifdef VERTEX // here begins the vertex shader void main() // all vertex shaders define a main() function { gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; textureCoordinates = gl_MultiTexCoord0.xy; } #endif // here ends the definition of the vertex shader #ifdef FRAGMENT // here begins the fragment shader void main() // all fragment shaders define a main() function { gl_FragColor = vec4(gl_LastFragColorARM, 1, 0, 1); } #endif // here ends the definition of the fragment shader ENDGLSL // here ends the part in GLSL } }
原生的glsl语法,非常优美。这样我们就可以用glsl的扩展
#extension GL_ARM_shader_framebuffer_fetch : enable
然后就可以用 gl_LastFragColorARM 读当前像素的值,其余做法就和之前一致了。
 
但是,如果我们都可以直接用GL_ARM_shader_framebuffer_fetch了,那同样的,我们可以直接用GL_EXT_shader_framebuffer_fetch代替unity自带的framebufferfetch,不过这样我们就还得自己处理ios下的metal,简直灾难。
 
进一步的,我们可以用GL_ARM_shader_framebuffer_fetch_depth_stencil扩展直接读深度信息,这样精度就足够高了,可以在不损失效果的情况下,完全代替copy depth的方案。而且mali和高通都可以用,ios的没测试。
 
所以最终的方案就是:
  1. 高通opengles3.0,ios metal用 #pragma only_renderers framebufferfetch
  1. mali 用GL_ARM_shader_framebuffer_fetch
 
希望下一个项目可以直接从glsl构建,可以用上GL_ARM_shader_framebuffer_fetch_depth_stencil的优化。还是缺少一个转换,不理解为什么unity不做这个事情,shader的编译项目也接管不了,ue就做了这个事情也可以方便的改。
万恶之源就是unity自己做了个cg的语法,然后urp之后推荐用hlsl语法,但还是不够开放。
 
hlsl和opengl或者metal在规范上还是没法1对1的映射上。
 
龚敏敏给hlsl提了个issue新增framebufferfetch的规范,结果也没同意。
 
规范:
 

© Luxuia 2021-2025