📜 ⬆️ ⬇️

Generating shaders GLSL, HLSL, Metal

Good day Habr. This is my first article on Habré, do not judge strictly.

In this article, I would like to consider the topic of shader code generation for different platforms, as well as their optimization.


Every year, mobile devices are becoming more powerful and more accessible. Now no one will be surprised by the 3d game on mobile, the market is growing, and with it the desire of platforms to see exclusives only in its market grows. 3-5 years ago, 2 api, OpenGLES and DirectX were provided for game development, today their number is growing. Apple is actively promoting Metal, and this week has already released a development version of android firmware with Vulkan support. With the growing number of rendering systems supported in the project, the number of platform-dependent code is growing.
')
Asking the question "How to minimize the number of platform-specific code?" I came to the conclusion that it is necessary to start with shaders, despite the fact that the syntax of modern languages ​​is different (HLSL, GLSL, Metal) they perform the same tasks. The first thing that comes to mind is to write on one syntax and generate the rest from it. The idea is not new, this approach is actively used in Unity, Unreal Engine. Unity uses NVidia CG syntax for shaders, CG Toolkit allows you to generate code for different platforms . But unfortunately NVidia has already enough stopped its support. And it's not hard to see in the profiles there are no modern api for example Metal, but at the same time Unity supports its generation. After not a long search, I found a framework optimizing glsl es shaders with support for generating Metal, according to the assurances of the author of this library, it is used in Unity.

So it turned out that you can write shaders on GLSL and generate Metal from them. But one more syntax remains - HLSL, I did not succeed in finding a generator from GLSL in HLSL, and in HLSL there is a notion of accuracy missing in GLSL 1.0. But there was a reverse generator, all from the same author. As a result, the HLSL syntax was taken as a result. It remains to determine the format of the input data, for example, all of the same CG Toolkit have the ability to load fx files containing the necessary functions and metadata with the necessary information (pipeline settings, names of functions for shaders). For parsing fx files, it was decided to use the hlslparser project, the ability to understand metadata was added to this project. As a result, having gathered all this into one, a small library was written, allowing to generate GLSL, HLSL, Metal shaders from input fx files with HLSL syntax.

The following is an example of the input fx file, and what was generated from it

fx.fx
float4x4 u_MVPMatrix; struct VS_DEFAULT_OUTPUT { float4 position: POSITION; float2 texture_coord: TEXCOORD0; float4 color: COLOR0; }; VS_DEFAULT_OUTPUT vs_default_texture(float4 u_position: POSITION, float2 u_texture_coord: TEXCOORD0, float4 u_color: COLOR0) { VS_DEFAULT_OUTPUT Out; Out.position = mul(u_MVPMatrix, u_position); Out.texture_coord = u_texture_coord; Out.color = u_color; return Out; } float4 ps_default_texture(VS_DEFAULT_OUTPUT Out, uniform sampler2D u_texture) : COLOR { float4 clr = tex2D(u_texture, Out.texture_coord) * Out.color; return clr; } technique default_texture { pass P0 { vertexShader = vs_default_texture(); pixelShader = ps_default_texture(); depthtest = false; depthwrite = false; } } 


The generated vertex shader for GLES looks like this.
 uniform highp mat4 u_MVPMatrix; attribute highp vec4 u_position; attribute highp vec2 u_texture_coord; attribute highp vec4 u_color; varying highp vec2 xlv_TEXCOORD0; varying highp vec4 xlv_COLOR0; void main () { gl_Position = (u_MVPMatrix * u_position); xlv_TEXCOORD0 = u_texture_coord; xlv_COLOR0 = u_color; } 

And accordingly pixel shader
 uniform sampler2D xlu_u_texture; varying highp vec2 xlv_TEXCOORD0; varying highp vec4 xlv_COLOR0; void main () { lowp vec4 tmpvar_1; tmpvar_1 = texture2D (xlu_u_texture, xlv_TEXCOORD0); highp vec4 tmpvar_2; tmpvar_2 = (tmpvar_1 * xlv_COLOR0); gl_FragColor = tmpvar_2; } 


The following are generated shaders for Metal
 #include <metal_stdlib> #pragma clang diagnostic ignored "-Wparentheses-equality" using namespace metal; struct xlatMtlShaderInput { float4 u_position [[attribute(0)]]; float2 u_texture_coord [[attribute(1)]]; float4 u_color [[attribute(2)]]; }; struct xlatMtlShaderOutput { float4 gl_Position [[position]]; float2 xlv_TEXCOORD0; float4 xlv_COLOR0; }; struct xlatMtlShaderUniform { float4x4 u_MVPMatrix; }; vertex xlatMtlShaderOutput xlatMtlMain (xlatMtlShaderInput _mtl_i [[stage_in]], constant xlatMtlShaderUniform& _mtl_u [[buffer(0)]]) { xlatMtlShaderOutput _mtl_o; _mtl_o.gl_Position = (_mtl_u.u_MVPMatrix * _mtl_i.u_position); _mtl_o.xlv_TEXCOORD0 = _mtl_i.u_texture_coord; _mtl_o.xlv_COLOR0 = _mtl_i.u_color; return _mtl_o; } 

 #include <metal_stdlib> #pragma clang diagnostic ignored "-Wparentheses-equality" using namespace metal; struct xlatMtlShaderInput { float2 xlv_TEXCOORD0; float4 xlv_COLOR0; }; struct xlatMtlShaderOutput { half4 gl_FragColor; }; struct xlatMtlShaderUniform { }; fragment xlatMtlShaderOutput xlatMtlMain (xlatMtlShaderInput _mtl_i [[stage_in]], constant xlatMtlShaderUniform& _mtl_u [[buffer(0)]] , texture2d<float> xlu_u_texture [[texture(0)]], sampler _mtlsmp_xlu_u_texture [[sampler(0)]]) { xlatMtlShaderOutput _mtl_o; half4 tmpvar_1; tmpvar_1 = half4(xlu_u_texture.sample(_mtlsmp_xlu_u_texture, (float2)(_mtl_i.xlv_TEXCOORD0))); _mtl_o.gl_FragColor = ((half4)((float4)tmpvar_1 * _mtl_i.xlv_COLOR0)); return _mtl_o; } 


Among the shortcomings of this approach, the following should be noted.
- Metal shaders have the same function names, which will not allow them to be carried in the same library, what can this lead to except for creating a separate library for each type of shader, I find it difficult to answer
- The generated shaders do not work with the latest platform features (geometry shader, instancing)

Of the benefits can be noted
- Optimized GLES shaders, due to the fact that compilation on this platform fell on the shoulders of vendors of driver manufacturers, some of them do not perform optimization, because of which performance may suffer

Source: https://habr.com/ru/post/281767/


All Articles