Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Графический фильтр на Cortex-M7
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > ARM
__inline__
Бьюсь над реализацией графического фильтра HQ2X на STM32H743 (Cortex-M7). Фильтр работает, но притормаживает, когда в кадре много мелких деталей.

Была предпринята оптимизация: Switch/Case из 256 значений был заменён на JumpTable. Не помогло. Исходный код фильтра (Keil ARM MDK):

Нажмите для просмотра прикрепленного файла

Требуется растянуть кадр в 2 раза по обеим осям.

Есть другие фильтры Scale2x, SaI2x , LQ2x - с ними проблем нет, на STM32H743 они идут довольно шустро(написанные на C, без Asm-а).

Вот тут чувак заточил под NEON и DSP фильтр HQnX (что не годится для Cortex-M7): https://pyra-handheld.com/boards/threads/ru...sp.69047/page-5

Существуют ли аналогичные графические фильтры (в частности HQ 2x), оптимизированные на ассемблере для ядер ARM Cortex-M7?

Работа фильтра пояснена на рисунке:

Нажмите для просмотра прикрепленного файла
Genadi Zawidowski
А попорбовать вместо ассемблерных вставок (которые обычно сбивают оптимизатор) использовть встроенные инлайны компилятора? В случае GCC, к примеру, могут оптимизатором варьироваться регистры, содержащие исходные значения/результаты...
Кстати, сравнение для четырех восьмибитных чисел должно быть тоже в SIMD... скорее всего. Вы его делаете "в ручную".
Код
Searching for 'SSUB8'...
C:\USER\SVN\CMSIS_5-5.3.0\CMSIS\Core\Include\cmsis_armcc.h(803):#define __SSUB8                           __ssub8
C:\USER\SVN\CMSIS_5-5.3.0\CMSIS\Core\Include\cmsis_armclang.h(1381):__STATIC_FORCEINLINE uint32_t __SSUB8(uint32_t op1, uint32_t op2)
C:\USER\SVN\CMSIS_5-5.3.0\CMSIS\Core\Include\cmsis_armclang.h(1385):  __ASM volatile ("ssub8 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) );
C:\USER\SVN\CMSIS_5-5.3.0\CMSIS\Core\Include\cmsis_gcc.h(1590):__STATIC_FORCEINLINE uint32_t __SSUB8(uint32_t op1, uint32_t op2)
C:\USER\SVN\CMSIS_5-5.3.0\CMSIS\Core\Include\cmsis_gcc.h(1594):  __ASM volatile ("ssub8 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) );
C:\USER\SVN\CMSIS_5-5.3.0\CMSIS\Core\Include\cmsis_iccarm.h(397):    #define __SSUB8   __iar_builtin_SSUB8
C:\USER\SVN\CMSIS_5-5.3.0\CMSIS\Core_A\Include\cmsis_iccarm.h(310):  #define __SSUB8   __iar_builtin_SSUB8
7 occurrence(s) have been found.


И самый главный вопрос - DATA CACHE включен в процессоре?
ViKo
Рисунок врет. Например, глаза лягушонка он размыл (округлил), а такой же черный квадрат недалеко оставил, как есть. И диагональные линии выглядят слишком красиво.
Растянуть в 2 раза - половина пикселей (по одной оси) уже есть, а половину всунуть кубической интерполяцией. Еще есть пиксели по диагонали, те нужно интерполировать побеим осям.
aaarrr
Цитата(ViKo @ Jul 16 2018, 10:26) *
Рисунок врет. Например, глаза лягушонка он размыл (округлил), а такой же черный квадрат недалеко оставил, как есть. И диагональные линии выглядят слишком красиво.

Просто это не совсем фильтр в привычном понимании. Алгоритм.
__inline__
Цитата(Genadi Zawidowski @ Jul 16 2018, 07:12) *
А попорбовать вместо ассемблерных вставок (которые обычно сбивают оптимизатор) использовть встроенные инлайны компилятора? В случае GCC, к примеру, могут оптимизатором варьироваться регистры, содержащие исходные значения/результаты...
Кстати, сравнение для четырех восьмибитных чисел должно быть тоже в SIMD... скорее всего. Вы его делаете "в ручную".


Наскоряк не нашёл CMSIS-овский хедер, поэтому на ассемблере написал. Попробуем использовать инлайны.

Цитата(Genadi Zawidowski @ Jul 16 2018, 07:12) *
И самый главный вопрос - DATA CACHE включен в процессоре?


Да. Без него всё очень сильно медленно.
Genadi Zawidowski
CMSIS обычно подключен через соответствующий процессорный header - например, в моем случае:
через stm32h7xx.h включен stm32h743xx.h - а в нем в районе 240 строки core_cm7.h:

Код
#define __CM7_REV               0x0100U   /*!< Cortex-M7 revision r1p0                       */
#define __MPU_PRESENT             1       /*!< CM7 provides an MPU                           */
#define __NVIC_PRIO_BITS          4       /*!< CM7 uses 4 Bits for the Priority Levels       */
#define __Vendor_SysTickConfig    0       /*!< Set to 1 if different SysTick Config is used  */
#define __FPU_PRESENT             1       /*!< FPU present                                   */
#define __ICACHE_PRESENT          1       /*!< CM7 instruction cache present                 */
#define __DCACHE_PRESENT          1       /*!< CM7 data cache present                        */
#include "core_cm7.h"                     /*!< Cortex-M7 processor and core peripherals      */
ViKo
Цитата(aaarrr @ Jul 16 2018, 10:49) *
Просто это не совсем фильтр в привычном понимании. Алгоритм.

Неужели глаза размером в пиксель отличает от просто кубика?
__inline__
Цитата(ViKo @ Jul 16 2018, 08:26) *
Рисунок врет. Например, глаза лягушонка он размыл (округлил), а такой же черный квадрат недалеко оставил, как есть. И диагональные линии выглядят слишком красиво.
Растянуть в 2 раза - половина пикселей (по одной оси) уже есть, а половину всунуть кубической интерполяцией. Еще есть пиксели по диагонали, те нужно интерполировать побеим осям.


Не врёт. Это волшебный фильтр. Не просто билинейная-бикубическая фильтрация , а смарт-фильтр, заточенный под пиксель-арт.

В доказательство прикрепляю программу (под винду) вместе с исходником и мейкфайлом. Фильтр LQ2x (практически результат схож с HQ2x, но быстрее и легче для STM32):

Нажмите для просмотра прикрепленного файла

Входные данные: файл test.raw, 160x102 пикселя RGB 8:8:8
Выходные данные после отработки программы : LQ2x.raw 204x320 пикселей RGB 8:8:8
В программе фильтр делает ещё поворот на 90 градусов и работает в цветовом пространстве RGB 5:6:5 (для моих целей).

RAW смотреть к примеру IrfanView, выставив длину, ширину и пиксель-формат.

Ну и ниже картинка с результатами фильтров (с википедии):

Нажмите для просмотра прикрепленного файла
jcxz
Цитата(__inline__ @ Jul 16 2018, 08:35) *
Была предпринята оптимизация: Switch/Case из 256 значений был заменён на JumpTable. Не помогло. Исходный код фильтра (Keil ARM MDK):

Уже писали много раз и тут в том числе: все эти рукопашные inline - мёртвому припарки. При включённой оптимизации компилятор сам заинлайнит что нужно.
И тем более - какие-то таблицы переходов: компилятор сам прекрасно умеет их делать если switch/case удовлетворяет некоторым условиям: достаточно большое число case и последовательное расположение case-значений. Прежде чем пытаться что-то делать в подобном духе, надо хотя-бы листинг при полной оптимизации посмотреть и подумать.
А ускорить тут можно только ассемблером.
__inline__
Цитата(jcxz @ Jul 16 2018, 09:00) *
Уже писали много раз и тут в том числе: все эти рукопашные inline - мёртвому припарки. При включённой оптимизации компилятор сам заинлайнит что нужно.
И тем более - какие-то таблицы переходов: компилятор сам прекрасно умеет их делать если switch/case удовлетворяет некоторым условиям: достаточно большое число case и последовательное расположение case-значений. Прежде чем пытаться что-то делать в подобном духе, надо хотя-бы листинг при полной оптимизации посмотреть и подумать.
А ускорить тут можно только ассемблером.


При -O3 -Otime switch/case заменяется на Jumptable при условии, что есть некая упорядоченность в проверяемых значениях. А в фильтре она идёт в разнобой. Максимум на что можно надеяться при таком подходе - это на поиск с бинарным разделением
jcxz
Цитата(__inline__ @ Jul 16 2018, 11:07) *
При -O3 -Otime switch/case заменяется на Jumptable при условии, что есть некая упорядоченность в проверяемых значениях. А в фильтре она идёт в разнобой. Максимум на что можно надеяться при таком подходе - это на поиск с бинарным разделением

Ещё и не факт что подобная раскрутка циклов:
Код
   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);

даст ускорение. Тело цикла достаточно большое, чтобы одна команда перехода в конце незначительно влияла на скорость. Зато кеша надо намного меньше.
AVI-crak
Цитата(__inline__ @ Jul 16 2018, 11:35) *
Вот тут чувак заточил под NEON и DSP фильтр HQnX (что не годится для Cortex-M7)

NEON имеет множество пробелов по сравнению с возможностями Cortex-M7. И уж точно Cortex-M7 может выполнять всё что есть в NEON.
Нужно просто перелопатить код, удаляя и заменяя вставки NEON функций.
Genadi Zawidowski
Там тоже ассемблер с "неоном". Неон умеет по 16 байт за один раз обсчитывать, Cortex-M7 только по четыре.
лично я делал FIR фильтры (многоканальные, для float) с импользованием NEON. Получилось.


ps: "там" это тут
Цитата
jcxz
Я так понимаю - Вы сами пытались оптимизировать функцию 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: Так что похоже у Вас ещё и реализация кривая.... laughing.gif
KnightIgor
Цитата(__inline__ @ Jul 16 2018, 08:54) *
Не врёт. Это волшебный фильтр. Не просто билинейная-бикубическая фильтрация , а смарт-фильтр, заточенный под пиксель-арт.
Фильтр LQ2x (практически результат схож с HQ2x, но быстрее и легче для STM32):

ОТ: Back to 80's? Не в смысле фильтров, а моды на 8-битные игры? Интересно узнать, каков back ground изысканий...
__inline__
Цитата(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: Так что похоже у Вас ещё и реализация кривая.... laughing.gif



Это не реализация!!! Это драфт, не претендующий на конечный вариант.

За ошибку спасибо!

Нашёл с того форума(про фильтр на 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"


?
Obam
Т.к. кортекс-М умеет только команды Tumb-2, то загрузка 32битной константы выполняется за 2 команды: movw (младшее полуслово) и movt (старшее). А смысл 0x00300706 вам знать.
LDR value1, =_00300706 одной командой не будет быстрее?
__inline__
Цитата(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]

или у меня недопонятки?
jcxz
Цитата(__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, ... на регистры какие больше нравятся, и с которыми команды покороче будут.
И аргументы поступают в функцию и результат должен класться согласно соглашениям вызова используемым вашим компилятором. Прочитайте их в доке на компилятор.
jcxz
Кстати - по этой найденной Вами ассемблерной Diff() уже видно как значительно можно выиграть, написав весь блок вызовов Diff() в виде асм-функции и заинлайнив туда саму Diff(). Из неё сразу выпадают:
Код
  mov tmp2,#0                // tmp2 = 0
  movw value1,#0x0706
  movt value1,#0x30          // value1 = 0x300706
Которые запросто выносятся за рамки цикла. И вызовов/возвратов не нужно.
__inline__
Проделал несколько экспериментов.

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 ):

Нажмите для просмотра прикрепленного файла

Цитата(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__
Цитата(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);


Если я Вас не понял, то можете более подробнее изложить суть предлагаемых изменений?
AVI-crak
Цитата(__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, смешивание двух слоёв. Эту операцию можно полностью аппаратно выполнять. Прогресс будет даже на небольших блоках.

По самому алгоритму.
Честно говорю - пытался понять и нишмог.
Блочную схему, ну или хотя-бы точку входа в алгоритм... А то там столько напечатано, что ногу свернуть можно.
jcxz
Цитата(__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 - не использовал её никогда, не уверен.
__inline__
Цитата(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);


Тут только на ассемблере писать, иначе компилятор фигню будет строить.
jcxz
Цитата(__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 и - вперёд! rolleyes.gif
__inline__
Цитата(jcxz @ Jul 18 2018, 09:57) *
Я вообще-то Вам и говорил, что это надо на асме писать. А код привёл для иллюстрации примерного алгоритма (на асме было писать дольше - лень).
Вы уже дошли до пределов возможностей оптимизатора си, дальше только брать всё в свои руки - писать на чистом асме.
Как бы ни были хороши современные оптимизаторы, а если взять листинг какой-либо функции скомпилённой IAR-ом на самой максимальной оптимизации, то там как правило сразу видно много путей оптимизации. Да и во многих местах даже последний IAR очень сильно лажает, добавляя кучу бессмысленных команд даже на макс. оптимизации.
Так что: компилите функцию, берёте листинг, берёте даташит на систему команд Cortex-M и - вперёд! rolleyes.gif


Попробовал вручную оптимизировать 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:

Нажмите для просмотра прикрепленного файла

Входные данные: 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 : нет переходов и считываний из лукапов sm.gif
__inline__
Цитата(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 в стадии отладки и оптимизации) rolleyes.gif
Arlleex
Цитата(__inline__ @ Jul 21 2018, 12:26) *
Все фильтры, которые тут обсуждались успешно применил (HQ2x в стадии отладки и оптимизации) rolleyes.gif

А зачем?
У меня вот, в детстве была Sega. А у друзей были Sega Mega Drive 16bit, Dendi... И там особо не было никаких фильтров - резкие картинки и четкие силуэты. Вот как на оригинале у Вас. И ИМХО именно это сейчас бы вселило в меня куда бОльшую настольгию, чем красочные и сглаженные картинки rolleyes.gif
__inline__
Цитата(Arlleex @ Jul 21 2018, 11:13) *
А зачем?
У меня вот, в детстве была Sega. А у друзей были Sega Mega Drive 16bit, Dendi... И там особо не было никаких фильтров - резкие картинки и четкие силуэты. Вот как на оригинале у Вас. И ИМХО именно это сейчас бы вселило в меня куда бОльшую настольгию, чем красочные и сглаженные картинки rolleyes.gif

Дело в том что у SEGA MegDrive, о которй Вы тут написали видеоконтроллер поддерживает разрешение до 320x240 пикселей. И в QVGA LCD, который я применяю, оно очень вписывается. Тут фильтры НЕ нужны и не используются. То же касается NES и SNES (256x240 , 256x256).

Но в Atari Lynx экран всего 160x102, в ГеймБой: 160x144. На QVGA-шном дисплее это всего 1/4 площади. Картинка маленькая, неудобно смотреть (особенно когда нужно в формактор кредитки уложить все устройство). И если с бордюрами в NES,SNES ещё как-то можно смириться, то тут стретч 2x как бы и напрашивается автоматически.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.