Как избежать преобразования int-›float при передаче данных в пиксельный шейдер?

У меня есть пиксельный шейдер:

varying vec2 f_texcoord;
uniform vec4 mycolor_mult;
uniform sampler2D mytexture;
void main(void) {
    gl_FragColor = (texture2D(mytexture, f_texcoord) * mycolor_mult);
};

и соответствующий код С++:

GLint m_attr = glGetUniformLocation(m_program, "mycolor_mult");
// ...
unsigned int myColor = ...; // 0xAARRGGBB format
float a = (myColor >> 24) / 255.f;
float r = ((myColor >> 16) & 0xFF) / 255.f;
float g = ((myColor >> 8) & 0xFF) / 255.f;
float b = (myColor & 0xFF) / 255.f;
glUniform4f(m_attr, r, g, b, a);

Я сохраняю цвет спрайта как unsigned int и должен преобразовать его в 4 числа с плавающей запятой, чтобы передать их шейдеру.

Можно ли его оптимизировать? Я имею в виду, могу ли я передать не числа с плавающей запятой, а беззнаковые символы в качестве компонентов шейдера и избежать операций «деления на 255»? Что мне нужно изменить в шейдере и в коде C++, чтобы это сделать?


person Nick    schedule 29.12.2015    source источник
comment
Какое профилирование вы сделали, чтобы предположить, что это узкое место, которое даже нуждается в оптимизации? Если ответа нет и вы пытаетесь оптимизировать, основываясь на интуиции, забудьте об этом и двигайтесь дальше. Сначала заставьте свой код работать, посмотрите (возможно, в буквальном смысле), есть ли проблемы с производительностью, и только затем используйте соответствующие инструменты, чтобы выяснить, где именно. Не тратьте время на ошибочные догадки, пытаясь оптимизировать там, где проблемы нет, что может привести к немедленным или будущим ошибкам и в результате непрозрачному коду.   -  person Nick    schedule 29.12.2015
comment
Как будто два пользователя с именем Ник недостаточно запутались, Ник редактирует вопрос Ника. Почему StackExchange позволяет это?   -  person Andon M. Coleman    schedule 30.12.2015


Ответы (2)


В современном OpenGL (GLSL >= 4.1) есть unpackUnorm4x8 Функция GLSL, которая делает именно то, что вы хотите: она берет один 32-битный uint и создает из него нормализованный вектор с плавающей запятой. Вам просто нужно изменить результат, чтобы он соответствовал вашему порядку байтов, эта функция будет интерпретировать младший значащий байт как первый канал.

uniform uint mycolor_packed;
//...
vec4 mycolor_mult=unpackUnorm4x8(mycolor_packed).bgra;

Это потенциально наиболее эффективный способ преобразования в самом шейдере. Тем не менее, все еще остается сомнительным, что выполнение этого один раз для каждого фрагмента на графическом процессоре более эффективно, чем только один раз для каждого вызова отрисовки на ЦП.

person derhass    schedule 29.12.2015

У этого вопроса есть несколько аспектов.

Стоит ли оптимизировать?

Я согласен с комментарием @Nick. Существует высокая вероятность того, что вы пытаетесь оптимизировать что-то, что вообще не критично для производительности. Например, если этот код выполняется только один раз за кадр, время выполнения этого кода абсолютно незначительно. Если это выполняется много раз за кадр, все может выглядеть немного иначе. Использование профилировщика может сказать вам, сколько времени потрачено на этот код.

Правильно ли вы оптимизируете?

Убедитесь, что вызов glGetUniformLocation() выполняется только один раз после связывания шейдера, а не каждый раз, когда вы устанавливаете униформу. В противном случае этот вызов, скорее всего, будет намного дороже, чем остальная часть кода. Из кода не совсем ясно, если вы уже это делаете.

Можно ли использовать более эффективные вызовы OpenGL?

Не совсем, если вам нужны значения как плавающие в шейдере. Для униформы нет автоматического преобразования формата, поэтому вы не можете просто использовать другой вызов из семейства glUniform*(). Из спецификации:

Для всех других типов униформы используемая команда Uniform* должна соответствовать размеру и типу униформы, объявленным в шейдере. Преобразования типов не выполняются.

Можно ли оптимизировать код?

Если вы действительно хотите выполнить микрооптимизацию, вы можете заменить деление на умножение. Деление намного дороже, чем умножение на большинстве процессоров. Тогда код выглядит так:

const float COLOR_SCALE = 1.0f / 255.f;
float a = (myColor >> 24) * COLOR_SCALE;
float r = ((myColor >> 16) & 0xFF) * COLOR_SCALE;
float g = ((myColor >> 8) & 0xFF) * COLOR_SCALE;
float b = (myColor & 0xFF) * COLOR_SCALE;

Вы не можете рассчитывать на то, что компилятор выполнит это преобразование за вас, поскольку изменение операций может повлиять на точность/округление операции. Некоторые компиляторы имеют флаги для включения таких оптимизаций. См., например, Оптимизация операций деления и преобразования с плавающей запятой.

person Reto Koradi    schedule 29.12.2015