Итак, понеслась!
Не уверен, что оно кому-то нужно, но как минимум будет упражнение на дизайн и реализацию API.
Вслух и с выражением.
Видение:
Кодится и оформляется софтварный блиттер.
В виде stb-style библиотеки. Одним файлом (заголовок; реализация инстанцируется #define'ом). Минимум зависимостей. Public domain.
Пишется на общем подмножестве C и C++ (его иногда называют "clean C").
Видимо только 32-битный RGBA.
Библиотека подразумевается низкоуровневой, соответственно предполагается, что верхний уровень если захочет - сам обернёт как ему удобнее. Следствие: нет торчащих наружу вспомогательных структур, API принимает встроенные типы C.
Минимально видится интерфейс, состоящий из ровно одной функции:
// Blits 32-bit RGBA image from scr to dst. // (w,h,stride) describe image surface. // NOTE: if src and dst overlap, the resulting image is unspecified. // color if present modulates src (as in GL_MODULATE). // color==NULL is equivalent to {255,255,255,255}, i. e. non-modulated blit. // mode: // 0 - copy // 1 - alpha-blend // 2 - gamma-corrected alpha-blend void blit(int32_t src_w,int32_t src_h,int32_t src_stride, const uint8_t *src_pixels, int32_t dst_w,int32_t dst_h,int32_t dst_stride, uint8_t *dst_pixels, int32_t x,int32_t y, const uint8_t *color, uint32_t mode);
Подкрашивать (modulate) всё-таки хочется, как минимум ради текста. Ну и чтобы плавно уводить спрайт в полупрозрачность.
Препроцессинг спрайтов - видимо нет, т. к. это специфично игре/движку, и библиотека не знает деталей. Соответственно - блитаем как есть.
Вопросы:
1. Какая ещё функциональность нужна? Поворот, масштаб, аффинное преобразование - относятся к компетенции блиттера?
2. Детектирование CPU. Так чтобы работало на MSVC, clang, GCC. Интринсики vs. асм. Пример проблем с интринсиками и GCC: http://www.virtualdub.org/blog/pivot/entry.php?id=363 , https://gist.github.com/rygorous/f26f5f60284d9d9246f6 , https://github.com/nothings/stb/issues/280. Какие SIMD использовать (SSE2, SSE4.1, AVX2)? Где ещё кроме x86 делать специальные версии?
3. Точность vs. скорость. В соседней теме есть максимально точные версии функций. Замерить, сравнить, если менее точные функции не получится сделать существенно быстрее - так можно и забить. Иначе - видимо добавить соотв. mode.
4. Обычный и модулированный блит - разнести или пусть будет одной функцией (верхний уровень если захочет - разнесёт)?
5. color - может const float*? И разрешить значения больше 1.
6. RGBA vs. BGRA. Влияет только на интерпретацию color. #define, параметр функции, вообще забить (пусть верхний уровень сам интерпретирует)?
7. Что-то кроме 32-битного RGBA - надо?
8. Инициализация (детектирование CPU, заполнение таблиц) - явная/on-demand?
9. Один вызов на спрайт - это ok, или батчинг имеет смысл с т. з. производительности?
10. В гамма-корректном режиме альфа всё равно линейна? Обычно это вроде бы так.
11. В гамма-корректном режиме color интерпретировать как линейный или sRGB?
FordPerfect
> Минимально видится интерфейс, состоящий из ровно одной функции:
Предлагаю запилить функцию, где полагать, что src_stride == std_width и dst_dstride == dst_width. Оно часто таким бывает.
> Какая ещё функциональность нужна? Поворот, масштаб, аффинное преобразование -
> относятся к компетенции блиттера?
Целочисленное масштабирование не помешало бы.
> RGBA vs. BGRA. Влияет только на интерпретацию color. #define, параметр функции,
> вообще забить (пусть верхний уровень сам интерпретирует)?
Предлагаю забить на порядок. И чтобы можно было дефайнами задавать какой по номеру компонент является альфой.
> Что-то кроме 32-битного RGBA - надо?
8/16-битный grayscale неплохо было бы иметь.
8-битный палитровый можно (ТарасБ спасибо скажет).
> 10. В гамма-корректном режиме альфа всё равно линейна? Обычно это вроде бы так.
> 11. В гамма-корректном режиме color интерпретировать как линейный или sRGB?
Предлагаю забить на гамму вообще и интерпретировать всё как линейное. Гамма-коррекция ведь по факту нужна только при непосредственном выводе изображения на экран.
FordPerfect
> Препроцессинг спрайтов - видимо нет
Добавь интерфейс для препроцессинга, чтоб его можно было добавлять, не меняя твой блиттер. У Тараса даже что-то такое есть
Panzerschrek[CN]
> Предлагаю забить на гамму вообще и интерпретировать всё как линейное. Гамма-коррекция ведь по факту нужна только при непосредственном выводе изображения на экран.
А как толсто то. Автор ведь только ради этого блиттер и пишет (:
FordPerfect
> Поворот, масштаб, аффинное преобразование - относятся к компетенции блиттера?
Это уже не блиттер. Рисоватор с такими наворотами, по-моему, даже как-то по-особому называется.
> 9. Один вызов на спрайт - это ok, или батчинг имеет смысл с т. з.
> производительности?
Смотря какова реализация низкого уровня. Но, если закладываться на его возможность, то вот это вот всё:
int32_t src_w,int32_t src_h,int32_t src_stride,
const uint8_t *src_pixels,
int32_t dst_w,int32_t dst_h,int32_t dst_stride,
uint8_t *dst_pixels,
int32_t x,int32_t y,
const uint8_t *color,
uint32_t mode
есть смысл запихнуть в одну-единственную структуру и в функцию её передавать по указателю. Заодно будет гибкость с точки зрения кодонаписания (элементы в структуру можно добавлять-удалять-переставлять местами, и тем участкам кода, которые не задействуют изменившиеся параметры, почти ничего не будет, а если поменять состав параметров - придётся переписывать вообще все вызовы).
GEGL, не?
Panzerschrek[CN]
>Предлагаю запилить функцию, где полагать, что src_stride == std_width и dst_dstride == dst_width. Оно часто таким бывает.
Вот уж это верхнему уровню совсем тривиально обернуть.
>Целочисленное масштабирование не помешало бы.
Подразумевается GL_NEAREST (иначе ограничение на целочисленный масштаб не очень понятно)?
Можно сделать.
>И чтобы можно было дефайнами задавать какой по номеру компонент является альфой.
Вроде бы и внятная идея. Но оно хоть где-то востребовано? Бывает альфа в жизни не в [3]?
>8/16-битный grayscale неплохо было бы иметь.
>8-битный палитровый можно (ТарасБ спасибо скажет).
Очевидный вопрос - а как у них с альфой.
5551? GL_LUMINANCE8_ALPHA8? Colorkey? Забить, и делать copy-only?
>Предлагаю забить на гамму вообще и интерпретировать всё как линейное. Гамма-коррекция ведь по факту нужна только при непосредственном выводе изображения на экран.
Идея считать буфер линейным, а конвертировать только при выводе на экран кажется здравой, но потом мы вспоминаем, что:
1. Если (промежеточный) буфер по 8 бит на компоненту, то при конверсии и дискретизации будет меньше возможных цветов (183 значения, вместо 256).
2. Конверсии - дополнительная погрешность, особенно если у нас исходные спрайты художник нарисовал на линейные, а sRGB.
3. Цветовосприятие (зрение) тоже нелинейно, так что sRGB в каком-то смысле эффективнее использует пространство значений, чем линейное пространство.
Если промежуточный буфер - флоат, тогда ок, но здесь не тот случай.
Т. е. как минимум предоставить возможность работать в sRGB и гамма-блитать - считаю внятным.
Ну и вообще - зачем забивать на гамму, если она уже реализована?
laMer007
>Добавь интерфейс для препроцессинга, чтоб его можно было добавлять, не меняя твой блиттер. У Тараса даже что-то такое есть
Ты видимо чуть о другом препроцессинге.
Я о вещах вида "зарание составить список непустых пикселей спрайта и блитать их".
Или "начало линии/конец линии", как пропагандирует 122.
Как такое интегрировать в агностический движку блиттер?
Sbtrn. Devil
>есть смысл запихнуть в одну-единственную структуру и в функцию её передавать по указателю. Заодно будет гибкость с точки зрения кодонаписания (элементы в структуру можно добавлять-удалять-переставлять местами, и тем участкам кода, которые не задействуют изменившиеся параметры, почти ничего не будет, а если поменять состав параметров - придётся переписывать вообще все вызовы).
Тогда уж можно пойти дальше и передавать void* а функция его сама проинтерпретирует, как glDrawArrays.
eMan.Lived
>GEGL
Может и клёвый, но толстый (>1 файла), и LGPL.
Здесь чуть другой прицел.
И ещё:
нужна premultiplied alpha?
FordPerfect
> Тогда уж можно пойти дальше и передавать void* а функция его сама
> проинтерпретирует, как glDrawArrays.
Это уже полиморфизьм, а у нас тут вроде как более низкий уровень обсуждается.
FordPerfect
> нужна premultiplied alpha?
Я бы сказал, что не нужно ничего, кроме нее.
Еще бы я хранил гамма-скорректированную (1 – alpha) вместо просто alpha, но это уже для ценителей.
}:+()___ [Smile]
Я так понимаю - только или оба src и dst обычные (выкидывать этот режим всё-таки стрёмно) или оба premultiplied; смешанные нафиг не нужны?
>гамма-скорректированную (1 – alpha)
Как его дружить с гаммой - ещё думать.
FordPerfect
> смешанные нафиг не нужны?
dst — это фреймбуффер, а src — спрайт, так что на мой взгляд, возможны все комбинации прозрачности.
Или ты про не premultiplied?
> Как его дружить с гаммой - ещё думать.
Я исхожу из того, что гамма нужна для увеличения разрешения в темных областях.
Для блендинга наиболее плохим случаем будет почти непрозрачный черный спрайт поверх белого.
В этом случае результат — это коэффициент пропускания, т. е. 1 – aplha.
Соответственно, чтобы не терять точность, необходимо этот коэффициент хранить с гаммой.
}:+()___ [Smile]
>Или ты про не premultiplied?
Я про вот такой случай: есть например спрайт RGBA, который premultiplied, и dst который тоже RGBA, но не premultiplied. Такое - нафиг? Конкретно в случае dst=framebuffer возможно разница не заметна, т. к. dstA=255, но если мы, например, генерируем промежуточный спрайт (GUI пререндерим, мало ли чего), это влияет. Или обратный случай, совсем стрёмный: обычный RGBA спрайт на premultiplied RGBA dst.
>Соответственно, чтобы не терять точность, необходимо этот коэффициент хранить с гаммой.
Ну это же всё-равно хак?
}:+()___ [Smile]
А послать нафиг "обычный", а делать только premultiplied, как ты предлагаешь - мысль могущая вызвать непонимание в народных массах.
FordPerfect
> Я про вот такой случай: есть например спрайт RGBA, который premultiplied, и dst который тоже RGBA, но не premultiplied.
В не premultiplied фреймбуффер ты нифига не сблендишь, там формулы ужасные с делением.
> Ну это же всё-равно хак?
В смысле, хак? Вся гамма — это один большой хак, можно ведь работать с линейным uint16 или флоатами.
Просто, если быть последовательными, то гамму, помимо цветовых компонент, надо применять и к коэффициенту пропускания.
Альфа без гаммы — это для меня аналогично, например, зеленому с гаммой, а красному и синему — без.
> А послать нафиг "обычный", а делать только premultiplied, как ты предлагаешь - мысль могущая вызвать непонимание в народных массах.
Я бы сделал функцию подготовки спрайтов, которая переводит одно в другое, и на этом успокоился.
Тема в архиве.