Генерация шейдеров GLSL, HLSL, Metal

от автора

Доброго дня хабр. Это моя первая статья на хабре, не судите строго.

В данной статье я хотел-бы рассмотреть тему генерации кода шейдеров для разных платформ, а также их оптимизацию.

С каждым годом мобильные устройства становятся все более мощнее, и более доступнее. Сейчас ни кого не удивишь 3d игрой на мобильном, рынок растет, а вместе с ним и растет желание платформ видеть эксклюзивы только в своем маркете. Еще 3-5 лет назад для разработки игр предоставлялось 2 апи, OpenGLES и DirectX, то сегодня их кол-во растет. Apple активно продвигает Metal, а на этой неделе уже вышли development версии android прошивок с поддержкой Vulkan. С ростом числа поддерживаемых в проекте систем рендеринга растет и кол-во платформо-зависимого кода.

Задаваясь вопросом «Как минимизировать кол-во платформо-зависимого кода?» я пришел к выводу что необходимо начать с шейдеров, несмотря на то что синтаксис у современных языков разный (HLSL, GLSL, Metal) выполняют они одни и те же задачи. Первое что приходит в голову — писать на одном синтаксисе и генерировать из него остальные. Идея не нова, данный подход активно используется в Unity, Unreal Engine. В Unity для шейдеров используется NVidia CG подобный синтаксис, CG Toolkit позволяет генерировать код для разных платформ. Но к сожалению NVidia уже достаточно прекратила его поддержку. И как не трудно заметить в профилях нет современных api к примеру Metal, но при этом Unity поддерживает его генерацию. После не продолжительных поисков я нашел фреймворк оптимизирующий glsl es шейдеры с поддержкой генерации Metal, по заверениям автора данной библиотеки она используется в Unity.

Итого получилось что можно писать шейдеры на GLSL и генерировать из них Metal. Но остается еще один синтаксис — HLSL, найти генератор из GLSL в HLSL у меня не получилось, к тому же в HLSL существует понятие точности отсутствующее в GLSL 1.0. Но зато нашелся обратный генератор все у того же автора. За основу в итоге был взят синтаксис HLSL. Осталось определится о формате входных данных, к примеру все у того же CG Toolkit есть возможность загружать fx файлы, содержащие необходимые функции и метаданные с необходимой информацией (настройки pipeline, имена функций для шейдеров). Для парсинга fx файлов было решено использовать проект hlslparser, в данный проект была добавленна возможность понимать метаданные. В итоге собрав все это во едино, была написана не большая библиотека, позволяющая генерировать GLSL, HLSL, Metal шейдеры из входных fx файлов с синтаксисом HLSL.

Ниже следует пример входного fx файла, и что из него сгенерировалось

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; 	} } 

Сгенерированный вертексный шейдер для GLES выглядит следующим образом

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; } 

И соответственно пиксельный шейдер

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; } 

Ниже следуют сгенерированные шейдеры для 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; } 

Из недостатков данного подхода стоит отметить следующие
— Metal шейдеры имеют одинаковые имена функций, что не позволит их нести в одной библиотеке, к чему это может привести кроме как созданию для каждого типа шейдера отдельной библиотеки, я затрудняюсь ответить
— В сгенерированных шейдерах отсутствует работа с последними фичами платформ (geometry shader, instancing)

Из плюсов можно отметить
— Оптимизированные GLES шейдеры, из-за того что компиляция на данной платформе легла на плечи вендоров производителей драйверов, некоторые из них не проводят оптимизацию, из-за чего может страдать производительность

ссылка на оригинал статьи https://habrahabr.ru/post/281767/