|
Графический фильтр на Cortex-M7, Увеличение ровно в 2 раза |
|
|
|
Jul 16 2018, 14:54
|

Местный
  
Группа: Участник
Сообщений: 257
Регистрация: 5-09-17
Пользователь №: 99 126

|
Цитата(jcxz @ Jul 16 2018, 09:50)  Я так понимаю - Вы сами пытались оптимизировать функцию Diff() asm-вставками? И в её начале - закомментаренное си-тело? Если так, то действие: Код int c1v = (c1 & Vmask) - (c2 & Vmask); if (Absolute(c1v) > trV) совсем не аналогично варианту на асме ниже. Подумайте, что будет если к примеру c1==0x81, c2==0 в первом и во втором случае. Если конечно в реализации изначально не заложено какое-то ограничение по количеству цветов (диапазону значений байтов). Но в любом случае: взятие модуля от числа - не аналогично операции x=-x. Да и вычитать SSUB8 c1,c2 чтобы потом находить NEG от каждого байта - это как-то бессмысленно. Почему бы тогда сразу не сделать SSUB8 c2,c1 ? PS: Так что похоже у Вас ещё и реализация кривая....  Это не реализация!!! Это драфт, не претендующий на конечный вариант. За ошибку спасибо! Нашёл с того форума(про фильтр на NEON) реализацию Диффа. Но я не силён в GCC-asm и его синтаксис ставит меня немного в ступор: Код static inline int Diff(u32 yuv1, u32 yuv2) { register u32 tmp1, tmp2;
asm( "usub8 %[tmp1], %[value1], %[value2] \n\t" // tmp1 = value1 - value2 // tmp1 = yuv1 - yuv2 "usub8 %[value2], %[value2], %[value1] \n\t" // value2 = value2 - value1 // value2 = yuv2 - yuv1 "mov %[tmp2], #0 \n\t" // tmp2 = 0 "movw %[value1], #0x0706 \n\t" "movt %[value1], #0x30 \n\t" // value1 = 0x300706 "sel %[tmp1], %[value2], %[tmp1] \n\t" // tmp1 = (value2 >= 0)?value2:tmp1 // tmp1 = abs(yuv1 - yuv2) "usub8 %[value2], %[value1], %[tmp1] \n\t" // value2 = value1 - tmp1 // value2 = 0x300706 - abs(yuv1 - yuv2) "sel %[value1], %[tmp2], %[value1] " // value1 = (value2 >= 0)?tmp2:value1 // value1 = (0x300706 >= abs(yuv1 - yuv2))?0:0x300706
: [value1] "+r" (yuv1), [value2] "+r" (yuv2), [tmp1] "=r" (tmp1), [tmp2] "=r" (tmp2) : : "cc" );
return yuv1; Начал переделывать под Keil Asm (или ARM Asm): Код static inline int Diff(u32 yuv1, u32 yuv2) { register u32 tmp1,tmp2; register u32 value1=yuv1,value2=yuv2; __asm { usub8 tmp1,value1,value2 // tmp1 = value1 - value2 // tmp1 = yuv1 - yuv2 usub8 value2,value2,value1 // value2 = value2 - value1 // value2 = yuv2 - yuv1 mov tmp2,#0 // tmp2 = 0 movw value1,#0x0706 movt value1,#0x30 // value1 = 0x300706 sel tmp1,value2,tmp1 // tmp1 = (value2 >= 0)?value2:tmp1 // tmp1 = abs(yuv1 - yuv2) usub8 value2,value1,tmp1 // value2 = value1 - tmp1 // value2 = 0x300706 - abs(yuv1 - yuv2) sel value1,tmp2,value1 // value1 = (value2 >= 0)?tmp2:value1 // value1 = (0x300706 >= abs(yuv1 - yuv2))?0:0x300706 } return yuv1; // ??? } Знающие GCC ASM, подскажите пожалуйста, по переделке на ARM ASM, что загоняется в value1, value 2? И куда возвращаемый результат кладётся? И что означает в конце: Код : [value1] "+r" (yuv1), [value2] "+r" (yuv2), [tmp1] "=r" (tmp1), [tmp2] "=r" (tmp2) : : "cc" ?
|
|
|
|
|
Jul 16 2018, 16:21
|

Местный
  
Группа: Участник
Сообщений: 257
Регистрация: 5-09-17
Пользователь №: 99 126

|
Цитата(Obam @ Jul 16 2018, 16:52)  Т.к. кортекс-М умеет только команды Tumb-2, то загрузка 32битной константы выполняется за 2 команды: movw (младшее полуслово) и movt (старшее). А смысл 0x00300706 вам знать. LDR value1, =_00300706 одной командой не будет быстрее? Обращение к ячейке памяти. Предположим, что это STM32H743 и переменная в DTCM и адрес выровнен на 4 байта. Что будет быстрее: один LDR или 2 MOV ? К тому же LDR загрузит адрес, а не значение, а это уже 2 LDR: LDR r0, =DATA00300706 LDR r1,[r0] или у меня недопонятки?
|
|
|
|
|
Jul 16 2018, 18:30
|
Гуру
     
Группа: Свой
Сообщений: 5 228
Регистрация: 3-07-08
Из: Омск
Пользователь №: 38 713

|
Цитата(__inline__ @ Jul 16 2018, 19:21)  Что будет быстрее: один LDR или 2 MOV ? К тому же LDR загрузит адрес, а не значение, а это уже 2 LDR: LDR r0, =DATA00300706 LDR r1,[r0] Это не так. LDR Rx, =0x300706 говорит транслятору расположить вблизи этого места в кодовом сегменте (после LTORG) константу 0x300706 и считать её в Rx через косвенную адресацию по [PC, #...]. Типа сделать как: Код LDR Rx, ptrVal ... DATA ptrVal DC32 0x300706 Команда будет одна. Но вот быстрее она будет двух MOVW/MOVT - это ещё вопрос. По идее можно попробовать написать просто: MOV Rx, #0x300706. Такой команды конечно нет, но некоторые трансляторы умные и заменяют такое на требуемую последовательность команд, возможно - наиболее оптимальную по их мнению. Цитата(__inline__ @ Jul 16 2018, 17:54)  Знающие GCC ASM, подскажите пожалуйста, по переделке на ARM ASM, что загоняется в value1, value 2? И куда возвращаемый результат кладётся? Я не знаток GCC ASM, но вроде там всё интуитивно понятно: register u32 value1=yuv1,value2=yuv2; - эта запись явно назначает алиасные имена регистрам, которые содержат данные аргументы. Просто замените эти value1, value2, ... на регистры какие больше нравятся, и с которыми команды покороче будут. И аргументы поступают в функцию и результат должен класться согласно соглашениям вызова используемым вашим компилятором. Прочитайте их в доке на компилятор.
|
|
|
|
|
Jul 17 2018, 09:01
|

Местный
  
Группа: Участник
Сообщений: 257
Регистрация: 5-09-17
Пользователь №: 99 126

|
Проделал несколько экспериментов. 1) Переписал Diff на ARM ASM: Код static inline int Diff(u32 yuv1,u32 yuv2) { register u32 t,z; __asm { USUB8 t,yuv1,yuv2 // t=yuv1-yuv2 USUB8 yuv2,yuv2,yuv1 // yuv2=yuv2-yuv1 SEL t,yuv2,t // t=abs(yuv1-yuv2) MOV yuv1,#YUVMASK // yuv1=YUVMASK MOV z,#0 // z=0 USUB8 yuv2,yuv1,t // yuv2=YUVMASK-abs(yuv1-yuv2) SEL yuv1,z,yuv1 // yuv1=(YUVMASK>=abs(yuv1-yuv2))?0:YUVMASK } return yuv1; } Фильтр работает, причем быстрее, чем с Си-шной реализацией Diff(). 2) Сравнил быстродействие фильтра с вариантом JumpTable и Case/Switch. Как это ни странно, вариант с Case/Switch быстрее! Оптимизация Keil CC : -O3 -Otime. Приемлемость работы фильтра оцениваю по косвенному признаку: задержки обновления кольцевого буфера DMA для воспроизведения звука - если звук хрипит, значит данные не успевают обновляться, значит фильтр нужно ещё более оптимизировать! С фильтрами SaI, LQ2x, Scale2x звук не хрипит. 3) Возникла идея откомпилировать код фильтра на GCC и полученный объектник подстыковать к Keil. Думал, что невозможно, а оказалось - возможно! Откомпилировал фильтр GCC тулчейн arm-eabi-gcc6.2.0-r4.exe : Код C:\arm-eabi\bin\arm-eabi-gcc.exe -fshort-wchar -mcpu=cortex-m7 -mthumb -mfloat-abi=hard -O3 -Ofast -c -I ../Port HQ2x.cpp -o HQ2x.obj Построил либу для Кейла: Код C:\arm-eabi\bin\arm-eabi-ar.exe cru HQ2x.lib HQ2x.obj и подстыковал её в проект Keil. Результаты приятно удивили - звук идёт ровно без лагов, фильтр работает! Выходит, что GCC лучше заоптимизировал? Ещё заметил, что ASM-листинг, даваемый компилятором Keil какой-то избыточный. А в GCC листинг достаточно прозрачен : применение инструкций вполне очевиден. Связано ли это с тем, что Keil таблеточный? И производитель делает флуд АСМ-инструкций в генерируемый код в случае таблетки? 4) Вернул JumpTable вместо Case/Switch и скомпилировал в GCC. Результаты лучше, чем с Keil, но и тут Case/Switch также победил. 5) Заменил своппинг байтов на аппаратный: Код //#define rev16(x) (((x)>>8)|((x)<<8))
static u32 inline rev16(u32 x) { asm( "rev16 %[value],%[value]"
: [value] "+r" (x) : : "cc" );
return x; } Стало ещё лучше - в GCC. А вот в Keil - наоборот было хуже. Это странно. Асм-листинг в GCC смотрю: Код C:\arm-eabi\bin\arm-eabi-gcc.exe -fshort-wchar -mcpu=cortex-m7 -mthumb -mfloat-abi=hard -O3 -Ofast -S -I ../Port HQ2x.cpp -o HQ2x.S А теперь вопросы, попрошу ответить на них: 1) Можно ли в GCC выставить ещё более сильные флаги оптимизации по скорости, кроме тех что есть -O3 - Ofast ? 2) Оправдан ли переход на более новую версию тулчейна GCC с целью получить более производительный код? 3) Может ли ядро Cortex-M7 посчитать интерполяцию быстрее. Такого типа: Код pixel =( (pixel1 * 3) + pixel2) / 4 где pixel - это пиксели 0:8:8:8 bit 0:R:G:B 4) Спуфит ли Keil лишними ASM-инструкциями для намерянного снижения производительности в вылеченных версиях? 5) Хочется избавиться от байт-своппинга REV16. Необходимость продиктована использованием DMA - дисплей требует занос старшего байта вначале, что приводит к некорректному отображения цвета (так как DMA передаёт вначале младший байт по дефолту). 6) В STM32H743 есть Master DMA и битовое поле Little/Big endianess для байтов/полуслов/слов. Но он у меня не заработал (трансфер Memory to Peripheral). А должен ли? Рабочий код фильтра прикрепляю (и батник для сборки либы для Keil-ы под GCC ):
HQ2x_GCC_Opt_SRC.rar ( 33.59 килобайт )
Кол-во скачиваний: 11Цитата(jcxz @ Jul 17 2018, 07:17)  Кстати - по этой найденной Вами ассемблерной Diff() уже видно как значительно можно выиграть, написав весь блок вызовов Diff() в виде асм-функции и заинлайнив туда саму Diff(). Из неё сразу выпадают: Код mov tmp2,#0 // tmp2 = 0 movw value1,#0x0706 movt value1,#0x30 // value1 = 0x300706 Которые запросто выносятся за рамки цикла. И вызовов/возвратов не нужно. Попытаюсь осмыслить Вами сказанное и испытать. Цитата(jcxz @ Jul 16 2018, 19:30)  По идее можно попробовать написать просто: MOV Rx, #0x300706. Такой команды конечно нет, но некоторые трансляторы умные и заменяют такое на требуемую последовательность команд, возможно - наиболее оптимальную по их мнению. Проверил: GCC делает 2 MOV, а Keil - LDR память
Сообщение отредактировал __inline__ - Jul 17 2018, 09:09
|
|
|
|
|
Jul 17 2018, 11:11
|

Местный
  
Группа: Участник
Сообщений: 257
Регистрация: 5-09-17
Пользователь №: 99 126

|
Цитата(jcxz @ Jul 17 2018, 07:17)  Кстати - по этой найденной Вами ассемблерной Diff() уже видно как значительно можно выиграть, написав весь блок вызовов Diff() в виде асм-функции и заинлайнив туда саму Diff(). Из неё сразу выпадают: Код mov tmp2,#0 // tmp2 = 0 movw value1,#0x0706 movt value1,#0x30 // value1 = 0x300706 Которые запросто выносятся за рамки цикла. И вызовов/возвратов не нужно. Пытался обдумать, не выходит. Там слева более простое выражение, которое если "ложь", то Diff() не вычисляется. Может вычислять все Diff не надо? В цикле строки: Код if ((w1 != w5) && (Diff(y, RGBtoYUV[w1]))) pattern |= (1 << 0); if ((w2 != w5) && (Diff(y, RGBtoYUV[w2]))) pattern |= (1 << 1); if ((w3 != w5) && (Diff(y, RGBtoYUV[w3]))) pattern |= (1 << 2); if ((w4 != w5) && (Diff(y, RGBtoYUV[w4]))) pattern |= (1 << 3); if ((w6 != w5) && (Diff(y, RGBtoYUV[w6]))) pattern |= (1 << 4); if ((w7 != w5) && (Diff(y, RGBtoYUV[w7]))) pattern |= (1 << 5); if ((w8 != w5) && (Diff(y, RGBtoYUV[w8]))) pattern |= (1 << 6); if ((w9 != w5) && (Diff(y, RGBtoYUV[w9]))) pattern |= (1 << 7); Если я Вас не понял, то можете более подробнее изложить суть предлагаемых изменений?
|
|
|
|
|
Jul 17 2018, 15:26
|
Частый гость
 
Группа: Участник
Сообщений: 182
Регистрация: 16-10-15
Пользователь №: 88 894

|
Цитата(__inline__ @ Jul 17 2018, 15:01)  3) Может ли ядро Cortex-M7 посчитать интерполяцию быстрее. Такого типа: Код pixel =( (pixel1 * 3) + pixel2) / 4 где pixel - это пиксели 0:8:8:8 bit 0:R:G:B Это-ж dma2d, смешивание двух слоёв. Эту операцию можно полностью аппаратно выполнять. Прогресс будет даже на небольших блоках. По самому алгоритму. Честно говорю - пытался понять и нишмог. Блочную схему, ну или хотя-бы точку входа в алгоритм... А то там столько напечатано, что ногу свернуть можно.
|
|
|
|
|
Jul 17 2018, 15:59
|
Гуру
     
Группа: Свой
Сообщений: 5 228
Регистрация: 3-07-08
Из: Омск
Пользователь №: 38 713

|
Цитата(__inline__ @ Jul 17 2018, 14:11)  Если я Вас не понял, то можете более подробнее изложить суть предлагаемых изменений? Ну что непонятного? Код int w[8], w5; int *p = &w[0], i; u32 pattern = 0, c = 1 << 23; do if ((i = *p++) != w5) if (Diff(y, RGBtoYUV[i])) pattern |= c; while ((s32)(c <<= 1) >= 0); pattern &= 255; Типа так. Вместо w1-w9 - w[8], а w5 - отдельно. Изменив в остальных местах соответственно. Подставьте вместо вызова Diff() само тело функции в цикл (она больше нигде не вызывается у Вас) если сам компилятор не заинлайнит. Скомпилите, а затем возьмите полученный asm-листинг и сделайте из него функцию и оптимизируйте её (раз сами не можете сразу асм-функцию написать). Вот как раз одна из оптимизаций будет - вынести те три команды наружу цикла, отдав под них пару регистров. Операцию pattern &= 255 можно выкинуть, если сделать c = 1 << 24 и условие завершения цикла - перенос из старшего бита во флаг переноса по команде LSLS (не знаю как в си такое сделать). Когда будет асм-функция там думаю можно будет и другие возможности оптимизации увидеть. PS: Глядя на SEL yuv1,z,yuv1 // yuv1=(YUVMASK>=abs(yuv1-yuv2))?0:YUVMASK сдаётся мне что там вообще можно Код MOV yuv1,#YUVMASK // yuv1=YUVMASK MOV z,#0 выкинуть, если Diff() будет встроена в тело цикла. Как я понимаю - в зависимости от результата предыдущей команды SEL выбирает один из своих регистров-аргументов и копирует его в целевой регистр. Но потом это используется только как флаг. Так что можно заменить z и yuv1 в ней на другие подходящие регистры, выкинув вообще пару команд указанную выше. Надо читать описание команды SEL - не использовал её никогда, не уверен.
|
|
|
|
|
Jul 18 2018, 03:03
|

Местный
  
Группа: Участник
Сообщений: 257
Регистрация: 5-09-17
Пользователь №: 99 126

|
Цитата(jcxz @ Jul 17 2018, 16:59)  Код int w[8], w5; int *p = &w[0], i; u32 pattern = 0, c = 1 << 23; do if ((i = *p++) != w5) if (Diff(y, RGBtoYUV[i])) pattern |= c; while ((s32)(c <<= 1) >= 0); pattern &= 255; Типа так. Вместо w1-w9 - w[8], а w5 - отдельно. Изменив в остальных местах соответственно. Подставьте вместо вызова Diff() само тело функции в цикл (она больше нигде не вызывается у Вас) если сам компилятор не заинлайнит. Скомпилите, а затем возьмите полученный asm-листинг и сделайте из него функцию и оптимизируйте её (раз сами не можете сразу асм-функцию написать). Вот как раз одна из оптимизаций будет - вынести те три команды наружу цикла, отдав под них пару регистров. Операцию pattern &= 255 можно выкинуть, если сделать c = 1 << 24 и условие завершения цикла - перенос из старшего бита во флаг переноса по команде LSLS (не знаю как в си такое сделать). Когда будет асм-функция там думаю можно будет и другие возможности оптимизации увидеть. PS: Глядя на SEL yuv1,z,yuv1 // yuv1=(YUVMASK>=abs(yuv1-yuv2))?0:YUVMASK сдаётся мне что там вообще можно Код MOV yuv1,#YUVMASK // yuv1=YUVMASK MOV z,#0 выкинуть, если Diff() будет встроена в тело цикла. Как я понимаю - в зависимости от результата предыдущей команды SEL выбирает один из своих регистров-аргументов и копирует его в целевой регистр. Но потом это используется только как флаг. Так что можно заменить z и yuv1 в ней на другие подходящие регистры, выкинув вообще пару команд указанную выше. Надо читать описание команды SEL - не использовал её никогда, не уверен. Попробовал. Стало хуже. Но это по вине компилятора: 1) int w[8] - это обращение к памяти, в то время как w1..w9 - очевидно регистры 2) Цикл разворачивается 8 раз. Никакие #pragma GCC optimize ("no-unroll-loops") и #pragma GCC optimize ("no-peel-loops") не помогают. Только -Ofast глобально убирать. 3) Одна итерация цикла строится от 11 до 13 инструкций. Зависит от настроения компилятора. 4) Не получается гарантировать сохранность регистров(для 0 и YUVMASK) внутри цикла -компилятор их внаглую затирает. Если объявить пару регистров глобально или -ffixed-reg, то работает, но эти регистры более нигде не применяются за пределами цикла(что тоже плохо). 5) И ещё вызов Diff() идёт в Case/Switch, а не только где сжали циклом: Код case 255: { if (Diff(RGBtoYUV[w[3]], RGBtoYUV[w[1]])) { X2PIXEL00_0 } else ................ Делал так: Код //Diff:
static inline int Diff(u32 yuv1,u32 yuv2) { register u32 tmp1;
asm( "usub8 %[tmp1], %[value1], %[value2] \n\t" "usub8 %[value2], %[value2], %[value1] \n\t" "sel %[tmp1], %[value2], %[tmp1] \n\t" "usub8 %[value2], r6, %[tmp1] \n\t" "sel %[value1], r7, %[value1] "
: [value1] "+r" (yuv1), [value2] "+r" (yuv2), [tmp1] "=r" (tmp1) : : "cc" ); return yuv1; }
//В цикле строки:
u32 *p=&w[0],i; u32 c=1;
register u32 r6 asm("r6")=YUVMASK; register u32 r7 asm("r7")=0;
do if((i=*p++)!=w5)if(Diff(y,RGBtoYUV[i]))pattern|=c; while((c<<=1)<256); Тут только на ассемблере писать, иначе компилятор фигню будет строить.
Сообщение отредактировал __inline__ - Jul 18 2018, 03:07
|
|
|
|
|
Jul 18 2018, 08:57
|
Гуру
     
Группа: Свой
Сообщений: 5 228
Регистрация: 3-07-08
Из: Омск
Пользователь №: 38 713

|
Цитата(__inline__ @ Jul 18 2018, 06:03)  Попробовал. Стало хуже. Но это по вине компилятора: 1) int w[8] - это обращение к памяти, в то время как w1..w9 - очевидно регистры 2) Цикл разворачивается 8 раз. Никакие #pragma GCC optimize ("no-unroll-loops") и #pragma GCC optimize ("no-peel-loops") не помогают. Только -Ofast глобально убирать. ... Тут только на ассемблере писать, иначе компилятор фигню будет строить. Я вообще-то Вам и говорил, что это надо на асме писать. А код привёл для иллюстрации примерного алгоритма (на асме было писать дольше - лень). Вы уже дошли до пределов возможностей оптимизатора си, дальше только брать всё в свои руки - писать на чистом асме. Как бы ни были хороши современные оптимизаторы, а если взять листинг какой-либо функции скомпилённой IAR-ом на самой максимальной оптимизации, то там как правило сразу видно много путей оптимизации. Да и во многих местах даже последний IAR очень сильно лажает, добавляя кучу бессмысленных команд даже на макс. оптимизации. Так что: компилите функцию, берёте листинг, берёте даташит на систему команд Cortex-M и - вперёд!
|
|
|
|
|
Jul 21 2018, 02:38
|

Местный
  
Группа: Участник
Сообщений: 257
Регистрация: 5-09-17
Пользователь №: 99 126

|
Цитата(jcxz @ Jul 18 2018, 09:57)  Я вообще-то Вам и говорил, что это надо на асме писать. А код привёл для иллюстрации примерного алгоритма (на асме было писать дольше - лень). Вы уже дошли до пределов возможностей оптимизатора си, дальше только брать всё в свои руки - писать на чистом асме. Как бы ни были хороши современные оптимизаторы, а если взять листинг какой-либо функции скомпилённой IAR-ом на самой максимальной оптимизации, то там как правило сразу видно много путей оптимизации. Да и во многих местах даже последний IAR очень сильно лажает, добавляя кучу бессмысленных команд даже на макс. оптимизации. Так что: компилите функцию, берёте листинг, берёте даташит на систему команд Cortex-M и - вперёд!  Попробовал вручную оптимизировать ASM-листинг с GCC и потом его в объектник с помощью ассемблера. Что заметил: 1) Много лишних переприсавиваний (верхние 2 строки закомментировал, ниже свой вариант): Код @ ldr r3, [r8, r2, lsl #2] @ mov ip, r3
ldr ip, [r8, r2, lsl #2]
.syntax unified @ 128 "HQ2x.cpp" 1 usub8 r3, lr, ip usub8 ip, r5, r3 sel ip, ip, r3 usub8 ip, r7, ip sel ip, r5, r7 Причем избавиться от этого не вышло, манипулируя разными комбинациями в объявлении входных/выходных/clobber- переменных 2) Цикл, переходы на метки - всё разворачивает. Объявление переменной границы цикла как volatile не даёт развернуть цикл, и он работает медленее, чем когда развёрнуто. Для моих целей оказался лучше другой фильтр - SaI. Он лучше сглаживает края (антиалиасинг), чем HQ2x и LQ2x. Исходники SaI фильтра + makefile + бинарник под Win32:
SaI2x_Win32.rar ( 39.11 килобайт )
Кол-во скачиваний: 11Входные данные: test.raw 160x144 RGB 8:8:8 Выходные: test2x.raw 288x320 RGB 8:8:8 (разворот на 90 градусов!) Картинка, иллюстрирующая работу фильтров:
BSpline - это "обычный фотошопный" фильтр (изображение размыто) SaI 320x240 - попытка втиснуть 320x288 в дисплей 320x240. Каждая 6-я строка выходного буфера пропускается, получается 240 линий вместо 288. Вроде б неплохо, если сравнивать с 320x240. И что самое главное, алгоритм менее ресурсозатратный , чем HQ2x : нет переходов и считываний из лукапов
|
|
|
|
|
Jul 21 2018, 09:26
|

Местный
  
Группа: Участник
Сообщений: 257
Регистрация: 5-09-17
Пользователь №: 99 126

|
Цитата(KnightIgor @ Jul 16 2018, 12:54)  ОТ: Back to 80's? Не в смысле фильтров, а моды на 8-битные игры? Интересно узнать, каков back ground изысканий... Цель - прощупать новый флагман от STM: Cortex-M7 @ 400MHz H743 в мультимедийном контексте. Just for fun & Proof concept. Если подробнее, то веду тему здесь (там же демонстрация работы, исходники и многое другое): http://vrtp.ru/index.php?showtopic=30174&st=0Эмуляторы игровых приставок/консолей Все фильтры, которые тут обсуждались успешно применил (HQ2x в стадии отладки и оптимизации)
Сообщение отредактировал __inline__ - Jul 21 2018, 09:27
|
|
|
|
|
Jul 21 2018, 10:41
|

Местный
  
Группа: Участник
Сообщений: 257
Регистрация: 5-09-17
Пользователь №: 99 126

|
Цитата(Arlleex @ Jul 21 2018, 11:13)  А зачем? У меня вот, в детстве была Sega. А у друзей были Sega Mega Drive 16bit, Dendi... И там особо не было никаких фильтров - резкие картинки и четкие силуэты. Вот как на оригинале у Вас. И ИМХО именно это сейчас бы вселило в меня куда бОльшую настольгию, чем красочные и сглаженные картинки  Дело в том что у SEGA MegDrive, о которй Вы тут написали видеоконтроллер поддерживает разрешение до 320x240 пикселей. И в QVGA LCD, который я применяю, оно очень вписывается. Тут фильтры НЕ нужны и не используются. То же касается NES и SNES (256x240 , 256x256). Но в Atari Lynx экран всего 160x102, в ГеймБой: 160x144. На QVGA-шном дисплее это всего 1/4 площади. Картинка маленькая, неудобно смотреть (особенно когда нужно в формактор кредитки уложить все устройство). И если с бордюрами в NES,SNES ещё как-то можно смириться, то тут стретч 2x как бы и напрашивается автоматически.
|
|
|
|
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|