Мифический текстурный шейдер (мексиканская история)

А, собственно, зачем? Казалось бы, всё итак успешно можно решать прямо на уровне пиксельных шейдеров.

Такая абстракция очевидно толще, чем позволяет формат мексиканской истории, особенно в 5 утра. (Здесь аффтор долго думает грустные мысли о том, кому и зачем он всё это пишет). Как обычно читателям придётся много додумать, но в этот раз, мне кажется, уместно нетривиальное обсуждение.

Начнём, впрочем, с конца. Внятная новая абстракция кроме собственно инноваций очевидно успешно решает уже имеющиеся проблемы.

Сперва абстракция. Итак float4 TexBlah(float4 coordinates). Будем резать. Очевидных линий отреза две: прямо на уровне TexBlah, либо на уровень ниже фильтрации. Нужен-ли шейдер фильтрации я буду думать потом, а пока что отрежем на уровне текстурного сэмпла. float4 SampleTexBlah(int4 coordinates). Уровень TexBlah имеет свои приимущества, но идёт вопреки идее грубой силы – и потому лесом.

Теперь немножко про зачем. И про почему. Шейдеры остаются не менее унифицированные, добавляется дополнительный паралеллизм. На каждый TexBlah сэмплов будет чаще всего значительно больше одного. С очень когерентными coordinates. Появляется дополнительное кеширование. SampleTexBlah определённо функция без сайд-эффектов.

Кеширование, кстати, ставит интересный вопрос. Float4 SampleTexBlah(int4 coordinates) очевидно проигрывает byte4 SampleTexBlah(int4 coordinates) – формат результата зависит от формата текстуры, а для случая DXT1 (которое на современном железе хранится в кеше в нераспакованом виде) придумать внятный формат результата у меня почти не получается. Т-е вопрос исключительно эффективности кеша, и здесь однозначного мнения у меня нет (интруция говорит ограничимся byte4 снизу и float4 сверху). Собственно здесь снова встаёт вопрос шейдера фильтрации, но это, пожалуй, единственный имеющийся у меня use-case.

В итоге очередной кусок специализированного железа (а есть ли он там?) получает равноправное унифициорванное применение. Пусть будет 2x нитей, а не ещё несколько текстурных блоков. Такая система будет значительно лучше масштабироваться.

Как всё это может выглядеть? Ну, например, вот так

byte4 palette[256];
byte [] index_map;
int width, height;

byte4 main ( int2 coordinates )
{
byte index = index_map[coordinates.x+coordinates.y*width];
return palette[index];
}

Теперь, когда как-то понятно, где оно работает, вернёмся снова к предмету обсуждения. А, собственно, зачем?

Пример выше иллюстрирует мой самый первый use-case. А именно поддержка текстур с палитрой на современном железе. Я ничего не буду говорить про компрессию, мегатекстуры, фрагментацию текстурной памяти и прочие самоочевидные вещи. Замечу, только, что работать такое будет значительно лучше, чем сколь угодно продуманный фиксированный API. Меньше всего я хочу удвидеть очередную видеокарту с аппаратной поддержкой мегатекстур. Надеюсь имеющиеся “фенечки” про стенсильный буфер тоже когда-нибудь кто-нибудь выкинет на свалку истории, хотя-бы какими-нибудь бленд-шейдерами. Но это уже тема совсем другой истории.

P.S. ужасно уродливый шейдер про текстуру с палитрой и билинейной фильтрацией могу отдать в хорошие руки.

  • Xunter

    интересно конечно думать про свои вариации на тему кубмепов, мипов, cssm, dxt и прочих вейвлетах.
    а вместо этого убогие GS и марсианская тесселяция.. спасибо хоть за CS.

  • orange

    > P.S. ужасно уродливый шейдер про текстуру с палитрой и билинейной фильтрацией могу отдать в хорошие руки.
    может просто приведете для примера? хотелось бы просто заценить живучесть такой шняги ;)

    З.Ы. а развитие железок пугает, это да, вон у nVidia в GLSL уже указатели появились :)

  • http://www.microsoft.com CEMEH

    Хорошо бы с кем-то из NV/ATI поговорить.

    Мне кажется, принципиальный момент – насколько влияет tex fetch latency на общую производительность в текущих играх (и насколько ее увеличит подобная схема).

    В случаях, когда появление текстуры на экране предсказуемое, может можно некий scratchpad & проход compute для декомпрессии делать.

  • IronPeter

    У меня другие тесткейсы.

    void *jpegBlocks[];

    [code]
    void jpeg_decompressor( byte4 *jpeg16x16[16 * 16], int cacheid )
    {
    ...
    }

    byte4 main ( int2 coordinates )
    {
    int index = interleave_bits( coordinates.x, coordinates.y );
    byte4 *jpeg16x16 = acquire_cached( /*int cacheid*/ index >> 8 );
    return jpeg16x16[ index & 255 ];
    }
    [/code]

    Выглядит как-то тухло, однако. Совсем другое чем твое. Кеширование внутри, а не поверх.

    C DXT совсем непонятно. В L2 кеше оно понятно, что компресованное. В L1 тоже? Не верю почему-то.

    Еще можно посмотреть на альтернативу DXT. Этот самый дурацкий формат PVRTC, который на iphones. Напишу процедуру сэмплинга из него.

    [code]

    sampler2D bicubicA;
    sampler2D bicubicB;
    sampler2D plainTexture2bits;

    float4 tex2DPVRTC( float2 coordinates )
    {
    float4 texA = tex2D( bicubicA, coordinates * 0.25f, BICUBIC_FILTERING );
    float4 texB = tex2D( bicubicB, coordinates * 0.25f, BICUBIC_FILTERING );
    return lerp( texA, texB, tex2D( plainTexture2bits ) );
    }

    [/code]

    Он забавный тем, что есть два бикубических ( о, йе! ) сэмплера с более низким ( x / 4 ) разрешением. Соответственно в них очень частые хиты паттерном 4×4, а докрутка идет коэффициентами фильтрации. Ну и двухбитная текстура, в которой хранятся lerp факторы.

    Как такое положить на твои шейдеры – совсем не знаю.

  • IronPeter

    сукин движок съедает угловые скобочки. Правильно тут:

    http://www.everfall.com/paste/id.php?agovyre2nbic

  • look4awhile

    итак

    1. вот это твоё – acquire_cached – оно, безусловно, забавное. т-е если решить что элемент кеша может быть хотя-бы 64 байта… т-е отдельная абстракция, сама по себе. т-е если вынести такой кеш отдельной абстракцией, то весь этот самый модный jpeg отлично ложится на мою модель (код надо?). блоками 8×8, разумеется. за исключением пустяка – собственно сжатие, которое из битиков в байтики. потому что “где начинаются нужные битики – тайна сия зело велика есьмь. если где-то хранить начало битиков – то уже можно. но и здесь сомнения меня не оставляют. поясню. этот кеш – это почти muteable state. т-е если эмбедидть код – то будет совсем muteable state. соответственно нужен отдельный кеш-шейдер. как-то так.

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

    2. у нас проблемы с терминами. L2 – это большой общий кеш, на контроллере памяти. L1 – в нашем случае маленький кеш, на каждый текстурный сэмплер свой. опять-же перед контроллером памяти. то что я предлагал ввести – дополнтиельный LT – кеш с результатами ф-ции Sample2D.

    проблемы с PVRTC не вижу вообще. т-е совсем. т-е вот твой код, какой ты привёл – он отлично подходит. прям как есть. а если, вдруг, смущает разница между совсем честным бикубическим, и интерполированным – можно сделать вид что разрешение больше, чем оно есть. думаю, впрочем, что это ненужные глупости.

  • look4awhile

    ещё подумал. говно идея распаковывать поток на GPU. т-е jpeg на GPU, в его хафмановой (или арифметической, или rle) части – говно идея. хорошо когда данные контекстно независимые. сносно когда контекст тонкий. толстый контекст – говно идея. против шерсти идём. т-е явно надо думать про то, что не нужен JPEG, а нужно что-то ещё. альтернативно резать выше либо резать ниже

    ну и ещё чуть потока сознания. очевидно (мне) что кешировать большие блоки on demmand – говно идея. из-за размеров контекста. из-за latency. просто так. вопрос “как собирать статистику” сродни вопросу “как строить гистограммы для HDR”. чем-то.

    про второй пример думал. проблемы не вижу вообще. нисколько. что я не понимаю?

  • IronPeter

    >т-е если вынести такой кеш отдельной абстракцией, то весь этот самый модный jpeg отлично ложится на мою модель (код надо?).

    Не надо. Если отдельная абстракция – то таки да. Подумал, что acquire_cached может помочь в других условиях. Скажем, когда надо шарить lowres вычисления для пикселей. Скажем, тени. Или всякие хитрости в ssao.

    >у нас проблемы с терминами. L2 – это большой общий кеш, на контроллере памяти. L1 – в нашем случае маленький кеш, на каждый текстурный сэмплер свой. опять-же перед контроллером памяти.

    Ага-ага. Но вроде как L1 сейчас – это не чистый кеш. Ты же предлагаешь поделить его на два. В явном виде
    L1P(Pure) и L1T(Texture), последний ближе к пользователю. Не совсем ввести новую абстракцию – а поделить старую на две части.

    >про второй пример думал. проблемы не вижу вообще. нисколько. что я не понимаю?

    Ты все отлично понимаешь. Если у нас набор униформных регистров, поделенный на две половины, две нити, первая ALU, вторая под текстурный шейдер, то tex2D в процедуре текстурного шейдера PVRTC – это TEX2D. Кастомная функция, которая не имеет никакого отношения ни к чему, функцию надо писать.

    Еще, кстати, depth shadows есть. Где сравнения до фильтрации. Хитрые вычисления, которые не укладываются в твою идеологию. По крайней мере не вижу, как укладываются.

  • IronPeter

    Еще раз подумал, как свести все воедино.

    Выглядит так ( форумный движок съедает скобочки, не могу бороться! )

    http://www.everfall.com/paste/id.php?dwge4e6b3ekw

    Пользователь определяет кешик, настроенный в данном случае на длину линейки 64 байта. Кешик индексируется int. Что туда засунет пользователь – его дела. Лично я засунул перепутанные биты текстурных координат, они же индексируют блоки в DXT текстуре. Но это мое личное решение. Функция tex2D умеет принимать в общем-то произвольные тайлики из byte4, short4, float4. От [4][4] до [1][1]. Линеечка кеша от 32 бо 128 байт.

  • IronPeter

    Ехал по дороге в метро, думал. Понял, что написал хуету. Т.е. сама идея acquire_cached() не прокатывает, если ее применить к CUDA. Хотя бы потому, что 64 входа в кещ при сотнях тредов в полете выглядят нелепо.

    Подумал и понял, что Боря дело говорит.

    Вот есть CUDA warp, 32 треда. Допустим, все эти треды решили сделать билинейную ( или не дай Б-г анизотропную ) фильтрации. В случае билинейной набрали 128 максимум запросов на тексели. Это вообще-то 128 потенциальных запросов к памяти. Очевидно, что полезно редуцировать. Действительно, можно завести небольшой кешик. Хоть по int4. Аккуратно хеш посчитать, в железке, чтобы редуцировать к более тонкому ключу. И сообразить батч из текселей, каждый из которых создат реальные обращения к памяти. Кодом, как Боря написал.

    Выглядит здраво.

  • Pingback: highly professional scums » Blog Archive » шейдер интерполятора()