Разбираемся в особенностях графической подсистемы микроконтроллеров

    Привет!

    В этой статье я бы хотел рассказать об особенностях реализации графического пользовательского интерфейса с виджетами на микроконтроллере и как при этом иметь и привычный пользовательский интерфейс и приличный FPS. Внимание я хотел бы акцентировать не на какой-то конкретной графической библиотеке, а на общих вещах — память, кэш процессора, dma и так далее. Поскольку я являюсь разработчиком команды Embox, приведенные примеры и эксперименты будут на данной ОС РВ.


    Ранее мы уже рассказывали про запуск библиотеки Qt на микроконтроллере. Получилась достаточно плавная анимация, но при этом затраты по памяти даже на хранение прошивки были существенными — код исполнялся из внешней флэш памяти QSPI. Конечно, когда требуется сложный и многофункциональный интерфейс, который еще и анимацию какую-то умеет делать, то затраты по аппаратным ресурсам могут быть вполне оправданы (особенно если у вас уже есть этот код, разработанный под Qt).

    Но если вам не нужна вся функциональность Qt? Что если у вас четыре кнопки, один регулятор громкости и пара popup меню? При этом хочется, чтобы “выглядело красиво и работало быстро” :) Тогда будет целесообразным использовать более легковесные средства, например библиотеку lvgl или аналогичную.

    У нас в проекте Embox некоторое время назад был портирован Nuklear — проект по созданию очень легковесной библиотека, состоящая из одного хедера и позволяющий легко создавать несложный GUI. Его мы и решили использовать для создания небольшого приложения в котором будет виджет с набором графических элементов и которым можно было бы управлять через touchscreen.

    В качестве платформы выбрали STM32F7-Discovery c Cortex-M7 и сенсорным экраном.

    Первые оптимизации. Экономия памяти


    Итак, графическая библиотека выбрана, платформа тоже. Теперь поймем что по ресурсам. Тут стоит отметить, что основная память SRAM в разы быстрей внешней SDRAM, поэтому если вам позволяют размеры экраны, то конечно лучше положить фреймбуфер в SRAM. Наш экран имеет разрешение 480x272. Если мы хотим цвет в 4 байта на пиксель, то получается порядка 512 Кб. При этом размер внутреннего RAM всего 320 и сразу понятно, что видеопамять будет внешней. Другой вариант — уменьшить битность цвета до 16 (т.е. 2 байта), и таким образом сократить расход памяти до 256 Кб, что уже может влезть в основную RAM.

    Первое что можно попробовать — сэкономить на всем. Сделаем видео буфер на 256 Кб, разместим его в RAM и будем в него же и рисовать. Проблема, с которой сразу же столкнулись — это “мерцание” сцены возникающее если рисовать напрямую в видеопамять. Nuklear перерисовывает всю сцену “с нуля”, поэтому каждый раз сначала выполняется заливка всего экрана, далее рисуется виджет, потом в него кладется кнопка, в которую помещается текст и так далее. Как следствие, невооруженным взглядом заметно, как вся сцена перерисовывается и картинка “мигает”. То есть простое помещение во внутреннюю память не спасает.

    Промежуточный буфер. Компиляторные оптимизации. FPU


    После того как мы немного повозились с предыдущим способом (размещением во внутренней памяти), в голову сразу стали приходить воспоминания об X Server и Wayland. Да, действительно, по сути оконные менеджеры и занимаются тем, что обрабатывают запросы от клиентов (как раз наше пользовательское приложение), и далее собирают элементы в итоговую сцену. К примеру, ядро Линукса посылает серверу события от input устройств через драйвер evdev. Сервер, в свою очередь, определяет кому из клиентов адресовать событие. Клиенты, получив событие (например, нажатие на сенсорном экране) выполняют свою внутреннюю логику — подсвечивают кнопку, отображают новое меню. Далее (немного по-разному для X и Wayland) либо сам клиент, либо сервер производит отрисовку изменений в буфер. И затем компоновщик (compositor) уже соединяет все кусочки воедино для отрисовки на экран. Достаточно просто и схематичное объяснение вот здесь.

    Стало ясно, что нам нужна похожая логика, вот только запихивать X Server в stm32 ради маленького приложения очень уж не хочется. Поэтому попробуем просто рисовать не в видео память, а в обычную память. После отрисовки всей сцены будет копировать буфер в видео память.

    Код виджета
            if (nk_begin(&rawfb->ctx, "Demo", nk_rect(50, 50, 200, 200),
                NK_WINDOW_BORDER|NK_WINDOW_MOVABLE|
                NK_WINDOW_CLOSABLE|NK_WINDOW_MINIMIZABLE|NK_WINDOW_TITLE)) {
                enum {EASY, HARD};
                static int op = EASY;
                static int property = 20;
                static float value = 0.6f;
    
                if (mouse->type == INPUT_DEV_TOUCHSCREEN) {
                    /* Do not show cursor when using touchscreen */
                    nk_style_hide_cursor(&rawfb->ctx);
                }
    
                nk_layout_row_static(&rawfb->ctx, 30, 80, 1);
                if (nk_button_label(&rawfb->ctx, "button"))
                    fprintf(stdout, "button pressed\n");
                nk_layout_row_dynamic(&rawfb->ctx, 30, 2);
                if (nk_option_label(&rawfb->ctx, "easy", op == EASY)) op = EASY;
                if (nk_option_label(&rawfb->ctx, "hard", op == HARD)) op = HARD;
                nk_layout_row_dynamic(&rawfb->ctx, 25, 1);
                nk_property_int(&rawfb->ctx, "Compression:", 0, &property, 100, 10, 1);
    
                nk_layout_row_begin(&rawfb->ctx, NK_STATIC, 30, 2);
                {
                    nk_layout_row_push(&rawfb->ctx, 50);
                    nk_label(&rawfb->ctx, "Volume:", NK_TEXT_LEFT);
                    nk_layout_row_push(&rawfb->ctx, 110);
                    nk_slider_float(&rawfb->ctx, 0, &value, 1.0f, 0.1f);
                }
                nk_layout_row_end(&rawfb->ctx);
            }
            nk_end(&rawfb->ctx);
            if (nk_window_is_closed(&rawfb->ctx, "Demo")) break;
    
            /* Draw framebuffer */
            nk_rawfb_render(rawfb, nk_rgb(30,30,30), 1);
    
            memcpy(fb_info->screen_base, fb_buf, width * height * bpp);
    


    В этом примере создается окно размером 200 x 200 пикселей, в него отрисовываются графические элементы. Сама итоговая сцена рисуется в буффер fb_buf, который мы выделили SDRAM. А далее в последней строчке просто вызывается memcpy. И все повторяется в бесконечном цикле.

    Если просто собрать и запустить этот пример, получим порядка 10-15 FPS. Что конечно не очень хорошо, ведь заметно даже глазом. Причем поскольку в коде рендера Nuklear много вычислений с плавающей точкой, ее поддержку мы включили изначально, без нее FPS был бы еще ниже. Первая и самая простая (бесплатная) оптимизация, конечно, флаг компилятора -O2.

    Соберем и запустим тот же самый пример — получим 20 FPS. Уже лучше, но все равно недостаточно для хорошей работы.

    Включение кэшей процессора. Режим Write-Through


    Перед тем как перейти к дальнейшим оптимизациям, скажу что мы используем плагин rawfb в составе Nuklear, который как раз и рисует напрямую в память. Соответственно, оптимизация работы с памятью выглядит очень перспективно. Первое что приходит на ум — это cache.

    В старших версиях Cortex-M, таких как Cortex-M7 (наш случай), встроен дополнительный кэш процессора (кэш инструкций и кэш данных). Он включается через регистр CCR блока System Control Block. Но с включением кэша приходят новые проблемы — несогласованность данных в кэше и памяти. Есть несколько способов управления кэшем, но в этой статье я не буду на них останавливаться, поэтому перейду к одному из самых простых, на мой взгляд. Чтобы решить проблему несогласованности кэша и памяти можно просто пометить всю доступную нам память как “некэшируемую”. Это означает, что все записи в эту память будут всегда проходить в память, а не в кэш. Но если мы таким способом пометим всю память, то и от кэша смысла не будет. Есть еще один вариант. Это “сквозной” режим, при котором все записи в память помеченную как write through попадают одновременно как в кэш, так и в память. Это создает накладные расходы на запись, но с другой стороны, сильно ускоряет чтение, поэтому результат будет зависеть от конкретного приложения.

    Для Nuklear’а write-through режим оказался очень хорош — производительность поднялась с 20 FPS до 45 FPS, что само по себе уже достаточно хорошо и плавно. Эффект конечно интересный, мы даже пробовали отключать write through режим, не обращая внимания на несогласованность данных, но FPS поднимался лишь до 50 FPS, то есть значительного прироста по сравнению с write through не наблюдалось. Отсюда мы сделали вывод, что для нашего приложения требуются много именно операций чтения, а не записи. Вопрос конечно откуда? Возможно, из-за количества преобразований в коде rawfb, которые часто обращаются в память за чтением очередного коэффициента или что-то в этом роде.

    Двойная буферизация (пока с промежуточным буфером). Включение DMA


    Останавливаться на 45 FPS не хотелось, поэтому решили поэкспериментировать дальше. Следующей идей была двойная буферизация. Идея широко известная, и в общем-то нехитрая. Отрисовываем сцену с помощью одного устройства в один буфер, а другое устройство в это время выводит на экран из другого буфера. Если посмотреть на предыдущий код, то хорошо виден цикл, в котором сначала в буфер рисуется сцена, а затем с помощью memcpy содержимое копируется в видео память. Понятно, что memcpy использует CPU, то есть отрисовка и копирование происходят последовательно. Наша идея была в том, что копирование можно делать параллельно с помощью DMA. Другими словами, пока процессор рисует новую сцену, DMA копирует предыдущую сцену в видеопамять.

    Memcpy заменяется следующим кодом:

                while (dma_in_progress()) {
                }
    
                ret = dma_transfer((uint32_t) fb_info->screen_base,
                        (uint32_t) fb_buf[fb_buf_idx], (width * height * bpp) / 4);
                if (ret < 0) {
                    printf("DMA transfer failed\n");
                }
    
                fb_buf_idx = (fb_buf_idx + 1) % 2;
    

    Здесь вводится fb_buf_idx — индекс буфера. fb_buf_idx = 0 — это front buffer, fb_buf_idx = 1 — это back buffer. Функция dma_transfer() принимает destination, source и кол-во 32 битных слов. Далее DMA заряжается требуемыми данными, а работа продолжается со следующим буфером.

    Попробовав такой механизм производительность выросла примерно до 48 FPS. Чуть лучше чем с memcpy(), но незначительно. Я не хочу сказать, что DMA оказался бесполезен, просто в этом конкретном примере влияние кэша на общую картину показало себя лучше.

    После небольшого удивления, что DMA показал себя хуже чем ожидалось, пришла “отличная”, как нам тогда казалось, мысль использовать несколько DMA каналов. В чем суть? Число данных, которые можно зарядить в DMA за один раз на stm32f7xx составляет 256 Кб. При этом помним, что экран у нас 480x272 и видеопамять порядка 512 Кб, а значит, казалось бы, что можно первую половину данных положить в один канал DMA, а вторую половину — во второй. И все вроде бы хорошо… Вот только производительность падает с 48 FPS до 25-30 FPS. То есть возвращаемся к той ситуации, когда еще не включили кэш. С чем это может быть связано? На самом деле с тем, что доступ к памяти SDRAM синхронизируется, даже память так и называется Synchronous Dynamic Random Access Memory (SDRAM), поэтому такой вариант лишь добавляет дополнительную синхронизацию, не делая при этом запись в память параллельной, как хочется. Немного поразмыслив, мы поняли, что ничего удивительного тут нет, ведь память то одна, и циклы записи и чтения генерируются к одной микросхеме (по одной шине), а поскольку добавляется еще один источник/приемник, то арбитру, который и разруливает обращения по шине, нужно смешивать циклы команд от разных DMA каналов.

    Двойная буферизация. Работа с LTDC


    Копирование из промежуточного буфера конечно хорошо, но как мы выяснили, этого недостаточно. Рассмотрим еще одно очевидное улучшение — двойную буферизацию. В подавляющем большинстве современных контроллеров дисплея можно задавать адрес на используемую видеопамять. Таким образом можно вообще избежать копирования, и просто переставлять адрес видеопамяти на подготовленный буфер, а контроллер экрана заберет данные оптимальным для него способом самостоятельно по DMA. Это и есть настоящая двойная буферизация, без промежуточного буфера как было до этого. Еще есть вариант когда контроллер дисплея может иметь два и более буферов, что по сути дела тоже самое — пишем в один буфер, а другой используется контроллером, при этом копирование не требуется.

    У LTDC (LCD-TFT display controller) в составе stm32f74xx есть два аппаратных уровня наложения — Layer 1 и Layer 2, где Layer 2 накладывается на Layer 1. Каждый из уровней конфигурируется независимо и может быть включен или отключен отдельно. Мы попробовали включить только Layer 1 и у него переставлять адрес видеопамяти на front buffer или back buffer. То есть один отдаем дисплею, а в другой в это время рисуем. Но получили заметное дрожание картинки при переключении наложений.

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

    Причина была не ясна — в документации сказано, что обновление конфигурации слоев можно выполнять “на лету”. Сделали простой тест — отключили кэши, плавающую точку, нарисовали статическую картинку с зеленым квадратом в центре экрана, одинаковую для обоих Layer 1 и Layer 2, и стали переключать уровни в цикле, надеясь получить статическую картину. Но снова получили то же самое дрожание.

    Стало понятно, что дело в чем-то другом. И тут вспомнили про выравнивание адреса фреймбуфера в памяти. Так как буферы выделялись из кучи и их адреса были не выровнены, мы выровняли их адреса на 1 Кб — получили ожидаемую картинку без дрожания. Потом нашли в документации, что LTDC вычитывает данные пачками по 64 байта, и что невыравненность данных дает значительную потерю в производительности. При этом выровнены должны быть как адрес начала фреймбуфера, так и его ширина. Для проверки мы изменили ширину 480x4 на 470x4, которая не делится на 64 байта, и получил то же самое дрожание картинки.

    В итоге, выровняли оба буфера на 64 байта, убедились что ширина выровнена тоже на 64 байта и запустили nuklear — дрожание исчезло. Решение, которое сработало, выглядит так. Вместо переключения между уровнями при помощи полного отключения либо Layer 1 либо Layer используем прозрачность. То есть, чтобы отключить уровень, установим его прозрачность в 0, а чтобы включить — в 255.

            BSP_LCD_SetTransparency_NoReload(fb_buf_idx, 0xff);
    
            fb_buf_idx = (fb_buf_idx + 1) % 2;
    
            BSP_LCD_SetTransparency(fb_buf_idx, 0x00);
    

    Получили 70-75 FPS! Значительно лучше, чем изначальные 15.

    Стоит отметить, что решение работает через управление прозрачностью, а варианты с отключением одного из уровней и вариант с переставлением адреса уровня дают дрожание картинки при FPS больших 40-50, причина нам на данный момент неизвестна. Также забегая вперед скажу, что это решение для данной платы.

    Аппаратная заливка сцены через DMA2D


    Но и это еще не предел, последней на текущий момент нашей оптимизацией для увеличения FPS, стала аппаратная заливка сцены. До этого мы делали заливку программно:
    nk_rawfb_render(rawfb, nk_rgb(30,30,30), 1);
    

    Давайте теперь скажем плагину rawfb, что заливать сцену не нужно, а только рисовать поверх:
    nk_rawfb_render(rawfb, nk_rgb(30,30,30), 0);
    

    Сцену будем заливать тем же цветом 0xff303030, только аппаратно через контроллер DMA2D. Одна из основных функций DMA2D это копирование или заливка цветом прямоугольника в оперативной памяти. Основное удобство здесь в том, что это не непрерывный отрезок памяти, а именно прямоугольная область, которая в памяти располагается с разрывами, а значит обычным DMA сходу не обойтись. В Embox мы еще не работали с этим устройством, поэтому давайте просто воспользовались средствами STM32Cube — функция BSP_LCD_Clear(uint32_t Color). Она программирует в DMA2D цвет заливки и размеры всего экрана.

    Vertical Blanking Period (VBLANK)


    Но даже при достигнутых 80 FPS осталась заметная проблема — части виджета двигались небольшими “разрывами” при перемещении по экрану. То есть, виджет будто бы делился на 3 (или больше) части, которые двигались рядом, но с небольшой задержкой. Оказалось что причина в неправильном обновления видеопамяти. А точнее, обновления в неправильные интервалы времени.

    У контроллера дисплея есть такое свойство как VBLANK, оно же VBI или Vertical Blanking Period. Оно обозначает временной интервал между соседними видео кадрами. Или чуть точнее, время между последней строкой предыдущего видеокадра и первой строкой следующего. В этом промежутке никакие новые данные не передаются на дисплей, картинка статическая. По этой причине обновлять видеопамять безопасно именно внутри VBLANK’а.

    На практике, у контроллера LTDC есть прерывание, которое настраивается на срабатывание после обработки очередной строки фреймбуфера (LTDC line interrupt position configuration register (LTDC_LIPCR)). Таким образом, если настроить это прерывание на номер последней строки, то мы как раз и получим начало интервала VBLANK. В этом месте и производим необходимое переключение буферов.

    В результате таких действий картинка нормализовалась, разрывы ушли. Но при этом FPS упал с 80 до 60. Давайте поймем в чем может быть причина подобного поведения.

    В документации можно найти следующую формулу:

              LCD_CLK (MHz) = total_screen_size * refresh_rate,
    

    где total_screen_size = total_width x total_height. LCD_CLK это частота, на которой контроллер дисплея будет загружать пиксели из видеопамяти в экран (к примеру, через Display Serial Interface (DSI)). А вот refresh_rate это уже частота обновления самого экрана, его физическая характеристика. Выходит, зная refresh rate экрана и его размеры, можно сконфигурировать частоту для контроллера дисплея. Проверив по регистрам ту конфигурацию, которую создает STM32Cube, выяснили, что он настраивает контроллер на экран 60 Hz. Таким образом, все сошлось.

    Немного об input устройствах в нашем примере


    Вернемся к нашему приложению и рассмотрим как происходит работа с touchscreen, ведь как вы понимаете, современный интерфейс подразумевает интерактивность, то есть взаимодействие с пользователем.

    У нас все устроено достаточно просто. События от input устройств обрабатываются в основном цикле программы непосредственно перед отрисовкой сцены:

            /* Input */
            nk_input_begin(&rawfb->ctx);
            {
                switch (mouse->type) {
                case INPUT_DEV_MOUSE:
                    handle_mouse(mouse, fb_info, rawfb);
                    break;
                case INPUT_DEV_TOUCHSCREEN:
                    handle_touchscreen(mouse, fb_info, rawfb);
                    break;
                default:
                    /* Unreachable */
                    break;
                }
            }
            nk_input_end(&rawfb->ctx);
    

    Сама же обработка событий от touchscreen происходит в функции handle_touchscreen():

    handle_touchscreen
    static void handle_touchscreen(struct input_dev *ts, struct fb_info *fb_info,
            struct rawfb_context *rawfb) {
        struct input_event ev;
        int type;
        static int x = 0, y = 0;
    
        while (0 <= input_dev_event(ts, &ev)) {
            type = ev.type & ~TS_EVENT_NEXT;
    
            switch (type) {
            case TS_TOUCH_1:
                x = normalize_coord((ev.value >> 16) & 0xffff, 0, fb_info->var.xres);
                y = normalize_coord(ev.value & 0xffff, 0, fb_info->var.yres);
                nk_input_button(&rawfb->ctx, NK_BUTTON_LEFT, x, y, 1);
                nk_input_motion(&rawfb->ctx, x, y);
                break;
            case TS_TOUCH_1_RELEASED:
                nk_input_button(&rawfb->ctx, NK_BUTTON_LEFT, x, y, 0);
                break;
            default:
                break;
            }
    
        }
    }
    


    По сути, здесь происходит конвертация событий input устройств в формат понятный Nuklear’у. Собственно, наверное и все.

    Запускаем на другой плате


    Получив вполне приличные результаты, мы решили воспроизвести их на другой плате. У нас была другая похожая плата — STM32F769I-DISCO. Там такой же LTDC контроллер, но уже другой экран с разрешением 800x480. После запуска на ней получили 25 FPS. То есть заметное падение производительности. Это легко объясняется размером фреймбуфера — он почти в 3 раза больше. Но основная проблема оказалась в другом — изображение очень сильно искажалось, статической картинки в момент когда виджет должен быть на одном месте не было.

    Причина была не ясна, поэтому мы пошли смотреть стандартные примеры из STM32Cube. Там оказался пример с двойной буферизаций именно для данной платы. В этом примере разработчики в отличие от метода с изменением прозрачности просто переставляют указатель на фреймбуфер по прерыванию VBLANK. Этот способ мы уже пробовали ранее для первой платы, но для нее он не сработал. Но применив этот метод для STM32F769I-DISCO, мы получили достаточно плавное изменение картинки с 25 FPS.

    Обрадовавшись, мы еще раз проверили данный метод (с переставлением указателей) на первой плате, но он все так же не работал при больших FPS. В итоге, на одной плате работает метод с прозрачностями слоев (60 FPS), а на другой метод с переставлением указателей (25 FPS). Обсудив ситуацию, мы решили отложить унификацию до более глубокой проработки графического стека.

    Итоги


    Итак, подведем итоги. Показанный пример представляет простой, но в то же время распространенный паттерн GUI для микроконтроллеров — несколько кнопок, регулятор громкости, может что-то еще. В примере отсутствует какая либо логика привязанная к событиям, так как упор был сделан именно на графику. По производительности получилось вполне приличное значение FPS.

    Накопленные нюансы для оптимизации производительности подводят к выводу, что в современных микроконтроллерах графика усложняется. Теперь нужно, как и на больших платформах, следить за кэшем процессора, что-то размещать во внешней памяти, а что-то в более быстрой, задействовать DMA, использовать DMA2D, следить за VBLANK и так далее. Все это стало похожим на большие платформы, и быть может поэтому я уже несколько раз сослался на X Server и Wayland.

    Пожалуй, одной из самых неоптимизированных частей можно считать сам рендеринг, мы перерисовываем всю сцену с нуля, целиком. Я не могу сказать как сделано в других библиотеках для микроконтроллеров, возможно где-то эта стадия встроена в саму библиотеку. Но по итогам работы с Nuklear кажется что в этом месте нужен аналог X Server или Wayland, конечно, более легковесный, что опять таки уводит нас к мысли, что маленькие системы повторяют путь больших.

    UPD1
    В итоге метод с изменением прозрачности оказался не нужен. На обоих платах заработал общий код — с переставлением адреса буфера по v-sync. При этом метод с прозрачностями тоже корректный, просто в нем нет необходимости.

    UPD2
    Хочу сказать большое спасибо всем людям кто подсказал про тройную буферизацию, мы до нее пока не дошли. Но теперь видно, что это классический способ (тем более для FPS больших частоты загрузки кадра в дисплей), который среди прочего позволит нам избавиться от лагов из-за ожидания v-sync (т.е. когда софт заметно опережает картинку). Мы с этим пока не столкнулись, но это лишь вопрос времени. И отдельное спасибо за обсуждение тройной буферизации хочу сказать besitzeruf и belav!

    Наши контакты:

    Github: https://github.com/embox/embox
    Рассылка: embox-ru[at]googlegroups.com
    Телеграмм чат: t.me/embox_chat
    Embox
    Открытая и свободная ОС для встроенных систем

    Comments 46

      0

      У микроконтроллера FPS больше чем у моего компа!
      Если уж рендинг такой не оптимальный, то ПО для Nextion есть куда оптимизировать.

        +1
        :)
        Тут еще от размера видеопамяти (и, собственно, экрана) зависит. Когда перешли на 800x480 получили 25 FPS. Но в целом да, тоже удивились когда на микроконтроллере хорошего FPS около 60-70 достигли.
        +2

        Интересно, а на таких контроллерах получится сделать плавный drag'n'drop мышкой? Там же будет куча прерываний от мыши — обработка прерываний в usb, потом обработка прерываний в библиотеке.

          +3
          Я думаю может получиться. Если взять какую-нибудь full-speed USB мышь (смотрю сейчас на свою), у нее есть interrupt эндпоинт с интервалом опроса 10 мс. То есть 100 прерываний в секунду, грубо говоря, что не очень-то и много. У нас на той же stm32f7 работает pjsip, а там нагрузка от прерываний по сети и прерываний аудио и DMA точно больше. То есть плавный drag'n'drop мышкой кажется вполне реальным. Но это все в теории, точно ответить можно, конечно, только после экспериментов.
          0
          Так вроде же можно в LTDC включить режим, изменения адреса буфера только по завершению вывода последней строки. То есть использовать два буфера, переключать заранее, а не по прерыванию. Просто уж странно выглядит FPS 25
            0
            Так вроде же можно в LTDC включить режим, изменения адреса буфера только по завершению вывода последней строки.

            Я знаю только про line interrupt. Его можно запрограммировать, к примеру, на последнюю строку, это да. Вы не про него?
            То есть использовать два буфера, переключать заранее, а не по прерыванию.

            Два буфера и используется. Один для отрисовки сцены приложением, второй для LTDC. А заранее это когда?
            Просто уж странно выглядит FPS 25

            Было 60 FPS, увеличили размер видео памяти в 3 раза и получили 25 FPS. Или вы про что-то другое?

              0
              А, да, там есть режим, в котором обновление shadow регистров LTDC (того же адреса, например) происходит во время VBLANK периода. То есть вы правы, это еще один способ. Но тогда нужно за VSYNC битом сделать. Не уверен, что будет выигрыш в сравнении с прерыванием. Но можно проверить.
                0
                чтобы не следить за VSYNC, импользуйте 3 видео буфера.
                  0
                  Хм, а что это даст?
                    0
                    Типа будет задержка в один буфер? И тогда все гарантированно «успеет» обработаться? Но тогда по памяти затраты на один буфер больше.
                      0

                      ответ в начале моего комментария. А так всегда, за одно всегда надо расплачиваться другим. Еще как рариант, сднлайте 2 небольших буффера для отрисовки в sram, и их через DMA пересылайте в будующий видимый буффер (их тоже будет 2)

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

                        Еще как рариант, сднлайте 2 небольших буффера для отрисовки в sram, и их через DMA пересылайте в будующий видимый буффер (их тоже будет 2)

                        Ну это уже дополнительные оптимизации :) Там еще вопрос насколько оно вообще получится в эти небольшие буферы элементы отрисовывать (к примеру, если буфер небольшой, а графический элемент «большой», то отрисовывать в него как-то кусками нужно).
                          +1
                          Хорошо, распишу подробнее о 3 буфере (но опять же, я не призываю так делать, все зависит от условий вашего проекта). Имеем 2 буфера — 1-ый и 2-ой. Пусть изначально 1-ый отображается, во 2-ой мы рендерим кард. Когда кард полностью отрисован, нам нужно сообщить LTDC о том, чтобы он сам переключил указатель адреса на буффер для отображения (используется особенность shadow регистров, т.е. новый адрес буффера мы укажем сразу, но переключение произойдет в момент VBLANK периода.

                          Теперь вопрос… А когда он произойдет, сколько нам ждать? LTDC ведь может только начинать показывать первую строчку из 1-ого буффера. Если мы сразу же начнем рендерить в 1-ый буффер, можно наблюдать артефакты. Ведь дисплей с разрешением в 480x272 пикселей будет рабоать на каких-то 6-10 Мгц-ах а ядро и перефирия на 180Mhz+. Тоесть как вы писали выше, нам надо ждать, пока LTDC дойдет до последней строчки и сменит адрес на буффер…

                          Так вот третий буфер позволит вам избавиться от такой задержки… И только.
                            +1
                            Тоесть как вы писали выше, нам надо ждать, пока LTDC дойдет до последней строчки и сменит адрес на буффер…

                            Так вот третий буфер позволит вам избавиться от такой задержки… И только.

                            Не совсем. Если я правильно вас понял, вы предлагаете после отрисовки во второй буфер, не ждать когда LTDC закончит с первым, а сразу же переходить к третьему. Это хорошо, но в таком режиме, процессор в скором времени «догонит» LTDC (т.е. окажется на том же самом буфере) и рисовать станет некуда, и все опять сведется к ожиданию как и в случае с двумя буферами.
                              +3
                              Верно, но это надо пробовать, обычно сама отрисовка бывает искуственно ограничена макс. количеством кадров в секунду, так как сам дисплей не сможет показывать например 200+ FPS.
                                +1
                                Согласен, надо пробовать.

                                обычно сама отрисовка бывает искуственно ограничена макс. количеством кадров в секунду, так как сам дисплей не сможет показывать например 200+ FPS.

                                Это да. Можно как программно, по идее, ограничить — по таймеру, к примеру, рисовать в отдельном потоке, так и аппаратно — через проверку статуса VSYNC или прерывание. Но тут похоже тоже надо думать, а будет ли тогда выигрыш от дополнительного буфера. Потому как если LTDC в общем случае заканчивает работу с буфером раньше ЦПУ (мы же ЦПУ искусственно ограничили), то тогда у ЦПУ всегда будет по крайней мере один свободный буфер. Короче говоря, вопрос любопытный, надо экспериментировать.
                                +1
                                Вероятно, тройная буферизация может помогать если в среднем, перерисовка экрана меньше времени отображения одного кадра, но иногда занимает больше времени чем один кадр.

                                И ещё вопрос. А в контроллере есть режим с 8-битной палитрой? Могло бы уменьшить требования к памяти, если не нужна фотореалистичная графика.
                                  +1
                                  Да-да, мне тоже кажется что должно быть именно в «среднем». То есть если ЦПУ будет отрисовывать кадр то быстрей, то медленней чем LTDC грузит кадр в дисплей, то они гипотетически могут шаг в шаг идти. Может быть даже буфера может быть не три, а в зависимости от FPS как-то в динамике вычисляться.

                                  И ещё вопрос. А в контроллере есть режим с 8-битной палитрой? Могло бы уменьшить требования к памяти, если не нужна фотореалистичная графика.

                                  Да, палитра есть. Это и правда размер видео памяти раза в 4 уменьшит. Но есть нюанс — плагин rawfb в Nuklear, который мы используем, рисует только в RGBA8888. Соответственно, поэтому настроили и LTDC на этот формат. Но вы правы, если выбрать палитру, и научиться сразу рисовать в нее, а не в RGBA8888, то экономия памяти возрастет. Да и скорость стала бы выше — можно этот буфер уже в быструю память поместить (SRAM).
                                    0
                                    А как Вы рисуете в RGBA8888 если SDRAM в 746-jq Дискавери подключена по 16-ти битной шине (она не 32-х, а 16-ти разрядная)?
                                      0
                                      Ровно как и записываю 32битное число в обычную память. Тут же FMC к, которому подключена SDRAM, будет в нее команды генерировать.
                                        0
                                        В SRAM контроллера, например, Вы можете записать 32-х битное число и все 32 разряда его сохранятся, а тут только нижние 16 бит подключены, а остальные верхние 16 бит в «воздухе висят».
                                          0
                                          Может вы имеете ввиду, что физически у этой SDRAM 32 data линии, но подключена она через первые 16 линий? Если да, то там же FMC настраивается в 16 битный режим, и тогда не имеет значения, что старшие 16 data линии «в воздухе висят», как вы выразились.
                                            0
                                            Да, это имел ввиду. Тогда получается, что сам FMC отправляет «во внутрь контроллера» (а оттуда может пойти на LTDC и внешний RGB LCD например) сначала одно слово, а потом другое? Просто, на днях как раз, наткнулся на цитату в форуме (даже кажется не в одном месте), что дескать такое подключение внешней SDRAM ограничивает возможности LCD (в плане RGB цветов получается). И никто не опрестовал это высказывание. Даже могу поискать и привести тут ссылку на эту цитату.
                                              0
                                              Вот одна из ссылок (человек пишет, что теперь у него работает (т.е. он собрал и проверил все это) в режиме RGB565 и пришлось с этим смириться):
                                              electronix.ru/forum/index.php?app=forums&module=forums&controller=topic&id=148422&do=findComment&comment=1586376
                                              И еще в др. местах видел аналогичное утверждение и могу дальше поискать и выложить тут. Поэтому сомнения возникли.
                                                0
                                                Вот одна из ссылок (человек пишет, что теперь у него работает (т.е. он собрал и проверил все это) в режиме RGB565 и пришлось с этим смириться):
                                                electronix.ru/forum/index.php?app=forums&module=forums&controller=topic&id=148422&page=2&tab=comments#comment-1586376

                                                Сложно сказать, там про разводку плат обсуждение в основном… Да и непонятно по какой причине он RGB565 использует — вполне возможно что просто из соображений производительности.
                                                0
                                                По сути да, с точки зрения программиста это обычный регион памяти (memory-mapped i/o). При записи в этот регион данные попадают не напрямую в SDRAM, а проходят через FMC. Если шина данных 16 бит, то да — 32битное число выставится на нее не за раз, а за два. Это примерно как и с SPI-флэшкой — пишем по адресам в memory-mapped режиме, а данные реально передаются по одной линии данных (или по 4ем, если QSPI-флэшка).
                                                  0
                                                  Хорошо. Спасибо большое за разъяснения. Значит все-таки RGBA888. (насчет RGB565 сейчас вдобавок обнаружил и на ресурсе «narodstream», в его уроках про 746-ую Дискавери, аналогичный подход в предлагаемом драйвере для LCD дисплея — он тоже там почему-то RGB565 применил:
                                                  narodstream.ru/stm-urok-64-hal-ltdc-chast-1
                                                  и видел еще где-то на форуме электроникса недавно аналогичное утверждение — но лучше уже самому почитать/изучать как следует чем других слушать. Спасибо еще раз.)
                                                    0
                                                    насчет RGB565 сейчас вдобавок обнаружил и на ресурсе «narodstream», в его уроках про 746-ую Дискавери, аналогичный подход в предлагаемом драйвере для LCD дисплея — он тоже там почему-то RGB565 применил:

                                                    RGB565 лучше и в плане FPS и в плане расхода памяти. Хуже только в плане цвета, но для такого экрана вполне годится. Единственная причина, по которой мы делали RGBA8888 — это то что Nuklear rawfb только в таком формате уже подготавливает кадр в памяти, а конвертировать не хотелось (об этом писал выше). То есть если можно RGB565 применить, то я бы тоже его посоветовал. А можно и вообще палитру, как выше предложили. Но сути не меняет, наверное зависит от качества картинки.
                    +1
                    border графика на спектруме навеяла )))
                      0

                      Отличная юмористическая статья! 5+! Давно так не смеялся. Жаль только смех был сквозь слезы… Даже хорошо, что автор так ничего и не рассказал про "особенности графической подсистемы мокроконтроллеров", как обещал в названии статьи. От Embox буду держаться как можно дальше.

                        0
                        Неаргументированную критику всерьез не воспринимаю, не старайтесь.
                          +1
                          Ну чтоже, тогда придется аргументировать. Только чур не обижаться — будет не очень приятно.

                          Начну с положительного момента. «Первое что можно попробовать — сэкономить на всем.» — это действительно шорошая шутка, на мой взгляд. Возьму ее на вооружение.

                          Теперь отрицательные моменты:

                          "… доступ к памяти SDRAM синхронизируется, даже память так и называется Synchronous Dynamic Random Access Memory (SDRAM), поэтому такой вариант лишь добавляет дополнительную синхронизацию… Немного поразмыслив, мы поняли, что ничего удивительного тут нет, ведь память то одна, и циклы записи и чтения генерируются к одной микросхеме (по одной шине), ..., то арбитру, который и разруливает обращения по шине, нужно смешивать циклы команд от разных DMA каналов."

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

                          На вашей отладочной плате действительно установлена одна микросхема SDRAM, но транзакции на внутренней шине контроллеры DMA генерируют не для нее
                          (микросхема SDRAM к внутренней шине не может быть подлючена по причине полной несовместимости физических интерфейсов). Транзакции генерируются для
                          встроенного контроллера внешней памяти — FMC (Flexible memory controller). По этой причине установка 2 и более микросхем SDRAM ситуацию не меняет.

                          По этой же причине контроллеры DMA никаких циклов команд для SDRAM не генерируют, а арбитр шины, соответственно, их никаким образом не смешивает.

                          Когда несколько Master-ов хотят обратиться к одному и тому же одноканальному Slave арбитр просто определяет очередность доступа.

                          DMA ничего не знает ни о том чтО за данные он передает, ни о том от кого и кому он передает. Для DMA что внутренняя SRAM, что FMC — все едино.

                          Для DMA есть начальный адрес «откуда», начальный адрес «куда», сколько, как формировать последующие адреса, разрядность данных и (для некоторых
                          DMA) как обрабатывать передаваемые данные (0-ми дополнить, знаком расширить, среднее арифметическое посчитать и т.д.).

                          Ваше пояснение в скобках, что циклы записи и чтения генерируются "...(по одной шине)..." неверное т.к. это в совсем маленьких микроконтроллерах есть одна шина
                          на которой «висят» все Master-ы и все Slave-ы. В таких монстрах как F7 шин много и применяется Bus Matrix.

                          Итак: 1) один Master (CPU) отрисовывает в буфере, находящимся в SDRAM и затем с копирует в «видеопамять», которая тоже находится SDRAM. — 45 FPS
                          2)вы добавляете еще один буфер и заменяете memcopy на DMA (2 Master-а к одному Slave) — 48 FPS
                          3)вы добавляете 2-ой DMA (3 Master-а к одному Slave) — 30 FPS

                          Результаты вас удивляют, а ваши объяснения "… циклы записи и чтения генерируются к одной микросхеме (по одной шине)...", «Я не хочу сказать, что DMA оказался бесполезен...» не выдерживают никакой критики. И это происходит потому, что вы совсем не понимаете что именно и в каких местах происходит.

                          А происходит следующее. Во всех трех вариантах количество транзакций по внутренним шинам одинаковое, из-за того, что slave (FMC) один и для обмена данными у него всего один канал, все транзакции выстраиваются в «очередь» и происходят последовательно. С точки зрения шин и Bus Matrix в этом случае большой разницы между 1-м, 2-мя и 3-мя Master-ами нет. Расходы на арбитраж не очень большие. Поэтому, если бы дело было в «одной микросхеме (по одной шине)», то замена memcopy на DMA дала бы существенное ускорение (DMA существенно быстрее memcopy), добавление второго DMA — незначительное замедление.

                          Проблема в особенностях работы SDRAM — причем не конкретной микросхемы, а SDRAM как таковой. Но об этом вы, явно, мало что знаете. SDRAM очень даже шустрая, если читать или писать большой объем данных, расположенных последовательно. Как только начинается произвольный доступ скорость катастрофически падает. Поэтому, если бы вместо SDRAM к FMC подключили SRAM ситуация была бы иной.

                          При 1) сутуацию во время прорисовки улучшает внутренний буфер FMC, правда memcopy он уже помочь не может.

                          При 2) степень произвольности доступа растет и производительность SDRAM падает. Это происходит потому, что DMA не гонит все ваши 256 кБ одной транзакцией, а выполняет множество burst транзакций по 16 beats и т.к. происходит это одновременно с отрисовкой, транзакции от CPU перемежаются с транзакциями от DMA. Проблема в том, что каждый мастер обращается к своей области памяти в SDRAM (их получается 3 — buf1, buf2, video). Тут на время работы DMA FMC уже помочь ничем не может. Но засчет того, что DMA работает значительно быстрее и эффективнее memcopy, транзакции DMA идут чаще транзакций CPU, в сумме получается даже небольшой выигрыш.

                          При 3) все существенно существенно ухудшается — оба DMA работают одинаково быстро и каждый работает со свими областями (их теперь 5 — buf1, buf2_a, buf2_b, video_a, video_b). Теперь уже речи не идет о том, что степень произвольности доступа растет — теперь на время работы обеих DMA доступ чисто произвольный. Производительность SDRAM падает примерно до плинтуса.

                          Итого:

                          Про организацию шин внутри микроконтоллера и про их работу вы мало что знаете.
                          Про SDRAM вы мало что знаете.
                          Про DMA вы мало что знаете.

                          Про кэш вы тоже мало что знаете, но про это вы пока еще, похоже, не знаете — об этом я вам расскажу в следующем сообщении к концу недели.

                          У вас для разработчика OS недопустимо низкий уровень знаний.
                            +1
                            Во-первых, в любом случае спасибо за информацию.

                            Во-вторых, вот вы написали сейчас полотно, которое просто вышло бы за рамки статьи — соответствнно, объяснять все это было бы нерезонно (да, признаю, часть для меня новое, но 70% того что вы написали я знаю). Про FMC я знаю тоже. Знаете почему? А вы посмотрите на каком адресе расположена память SDRAM — 0x60000000. Мы ее переключаем с 0xc0000000 на 0x60000000 через SWP_FMC, это было нужно для выравнивания на 4 байта. Далее, я не утверждал я что DMA генерирует команды к SDRAM в обход FMC. Но почему я обязан о всех этих деталях с FMC рассказывать в статье?

                            Единственное, я бы может просто заменил мою неточную формулировку в статье на вашу:
                            Проблема в особенностях работы SDRAM — причем не конкретной микросхемы, а SDRAM как таковой. Но об этом вы, явно, мало что знаете. SDRAM очень даже шустрая, если читать или писать большой объем данных, расположенных последовательно. Как только начинается произвольный доступ скорость катастрофически падает. Поэтому, если бы вместо SDRAM к FMC подключили SRAM ситуация была бы иной.

                            Да, тут более четко сказано про особенность SDRAM. И этой особенностью, действительно, много объясняется. И этого было бы достаточно. Но нет же…

                            В остальном, серьезно, вот зачем это все в статье про детали с FMC и Bus Matrix? Вот вы похоже железом занимаетесь (аппаратчик, верилог, FPGA наверное), но к примеру, зачем вам в статье по железу рассказывать как устроены workqueue или timer wheel в Линукс. Могу вам точно так же сказать, если вы этого не знаете, то железячник из вас никудышний.
                              +1
                              А что касается:

                              Про кэш вы тоже мало что знаете, но про это вы пока еще, похоже, не знаете — об этом я вам расскажу в следующем сообщении к концу недели.

                              Конечно пишите, мне будет интересно чего я не знаю.

                              p.s.
                              Кстати, я тоже хотел в относительно недалеком будущем отдельную статью про кэши написать, как раз потому что это вышло за рамки текущей статьи про графику, даже черновик уже есть (но очень-очень сырой). Так что если вдруг не хотите мне «подсказывать», то можете ее дождаться и раскритиковать в пух и прах :)))
                                +2
                                Кстати, я тоже хотел в относительно недалеком будущем отдельную статью про кэши написать

                                Там будет что-то выходящее за рамки этих статей?
                                https://habr.com/ru/post/328542/
                                https://habr.com/ru/post/179647/
                                https://habr.com/ru/post/183834/
                                https://habr.com/ru/post/187654/

                                  +1
                                  Большое спасибо за ссылки! Уже просмотрел (пока по диагонали). На первый взгляд будет. Сейчас там про write-back vs write-through + write-allocate + как можно управлять кэшируемостью памяти через MPU (т.е. помечать как некэшируюмую память под DMA, например, для сетевой карты), а когда стоит применить clean/inval (например, для той же видеопамяти). Да и упор хочется сделать, опять же, с точки зрения программиста — что, зачем и как использовать. Но как я сказал, статья пока очень сырая, там просто лежат те заметки о вещах, с которыми мы лично столкнулись. Еще надо pjsip и другие вещи с кэшем проверить. Но спешить, конечно, не буду. Да и как-то не в наших правилах анонсы статей делать, так как очень много работы, и статьи пишутся только по факту какого-то, пусть и небольшого, осмысленного успеха, а не ради статей. Это я наверное погорячился про анонс, отвечая товарищу выше…
                          0

                          Может, надо сначала внимательно почитать документацию и теорию, а потом экспериментировать? Что Вы в итоге измеряли: пропускную способность памяти? Скорость DMA?

                            +1
                            Может, надо сначала внимательно почитать документацию и теорию, а потом экспериментировать?

                            В процессе разработки все это, конечно же, изучал. Если есть какие-то замечания по делу — высказывайте. Если нет — не тратьте мое время, пожалуйста.

                            Измерялось FPS (frame rate). Эта характеристика зависит не только от аппаратных, но и от программных факторов. Скорость DMA и пропускная способность памятей это вещи сугубо аппаратные, все есть в документации, нечего измерять. Они конечно влияют на итоговый FPS.
                              +2
                              Ну, если изучали, тогда бы не было:
                              Обсудив ситуацию, мы решили отложить унификацию до более глубокой проработки графического стека.

                              Попробуйте сделать тройной буфер, как рекомендовали выше. Это классика. Тогда не будет проблем с маленькими или большими разрешениями. И не надо ловить «обратный ход луча» или как принято сейчас называть — «вертикальный бланк».
                              Ваш первый способ (с переключением слоев) работает только, когда скорость формирования кадра выше скорости вывода, второй (переключение буферов) — наоборот.
                              Возможно, это мое мнение, но fps замерять не очень корректно, более правдиво — время, затраченное на формирование кадра. Тогда можно оценить свободное процессорное время для других задач.
                                +1
                                Спасибо за замечания.

                                Попробуйте сделать тройной буфер, как рекомендовали выше. Это классика. Тогда не будет проблем с маленькими или большими разрешениями. И не надо ловить «обратный ход луча» или как принято сейчас называть — «вертикальный бланк».

                                Разве тройная буферизация всегда исключает необходимость проверки вертикального бланка? Если у вас ЦПУ отрисовывает кадр всегда быстрее, чем контроллер дисплея грузит кадр в экран, то тройная буферизация теряет смысл перед двойной буферизацией. Ну и, собственно, когда контроллер дисплея всегда быстрее грузит кадр нежели система его отрисовывает та же история. При этом, я понимаю как это может помочь «в среднем». Вы же читали обсуждения про тройную буферизацию выше (случаи когда кто-то из ЦПУ или LTDC быстрей)? Было бы интересно вас выслушать.

                                Ваш первый способ (с переключением слоев) работает только, когда скорость формирования кадра выше скорости вывода, второй (переключение буферов) — наоборот.

                                Можете пояснить? Казалось бы, ни один их этих способов не должен зависеть от соотношения скоростей формирования кадра/отображения кадра. При любом соотношении, оба способа должны давать адекватную картинку (без дрожания), ведь переключение буферов в обоих методах происходит исключительно внутри VBLANK.

                                Возможно, это мое мнение, но fps замерять не очень корректно, более правдиво — время, затраченное на формирование кадра. Тогда можно оценить свободное процессорное время для других задач.

                                У нас в этом примере только графическое приложение исполнялось. Помимо этого разве что системный таймер. Поэтому полученные 85 FPS это когда система только графикой и занимается. Если экран 60 Гц, то можно прикинуть какая часть свободного времени остается.
                                  +2
                                  Разве тройная буферизация всегда исключает необходимость проверки вертикального бланка? Если у вас ЦПУ отрисовывает кадр всегда быстрее, чем контроллер дисплея грузит кадр в экран, то тройная буферизация теряет смысл перед двойной буферизацией. Ну и, собственно, когда контроллер дисплея всегда быстрее грузит кадр нежели система его отрисовывает та же история. При этом, я понимаю как это может помочь «в среднем». Вы же читали обсуждения про тройную буферизацию выше (случаи когда кто-то из ЦПУ или LTDC быстрей)? Было бы интересно вас выслушать.

                                  1. один буфер используется, когда поцессор успевает отрисовать кадр во время бланка (вертикального) или перерисовывается малая часть кадра, не заметная для глаза
                                  2. два буфера используется, когда процессор не успевает отрисовать кадр во время бланка. при этом отрисовка нового кадра начинается, когда освободился буфер. т.е. программа (поток) отрисовки должна ожидать.
                                  3. тройная буферизация используется когда идет непрерывный поток формирования кадров (например, камера или Ваш случай, где пытаетесь достичь много fps)
                                  да, я читал выше про тройную буферизацию. там за бланками следить не надо, только контролеру видео сообщить, с какого адреса начинать следующий кадр.
                                  почитайте как делается тройная буферизация, там не сложно. главное, не запутаться какой буфер на вывод, какой в очереди, а какой свободный.

                                  Можете пояснить? Казалось бы, ни один их этих способов не должен зависеть от соотношения скоростей формирования кадра/отображения кадра. При любом соотношении, оба способа должны давать адекватную картинку (без дрожания), ведь переключение буферов в обоих методах происходит исключительно внутри VBLANK.

                                  я код не видел, из контекста понял, что Вы формируете кадры не синхронно с выводом и переключением буферов (см. выше п.2)
                                  У нас в этом примере только графическое приложение исполнялось. Помимо этого разве что системный таймер. Поэтому полученные 85 FPS это когда система только графикой и занимается. Если экран 60 Гц, то можно прикинуть какая часть свободного времени остается.

                                  ну, тут у каждого свое мнение.
                                    0
                                    тройная буферизация используется когда идет непрерывный поток формирования кадров (например, камера или Ваш случай, где пытаетесь достичь много fps)
                                    да, я читал выше про тройную буферизацию. там за бланками следить не надо, только контролеру видео сообщить, с какого адреса начинать следующий кадр.
                                    почитайте как делается тройная буферизация, там не сложно. главное, не запутаться какой буфер на вывод, какой в очереди, а какой свободный.

                                    Спасибо, попробую! Из любопытсва, вы тройную буферизацию на практике реализовывали, или разбирали в теории?
                                      +1
                                      да, реализовывал тройную, когда работал с tms320dm643. делал для камеры и для дисплея.
                                  +1
                                  Я хочу сказать, что главное преимущество тройной буферизации — это повышение FPS. И если даже отрисовка происходит всегда быстрее загрузки кадра в экран, система может просто «перерисовывать» один из двух буферов, и уже, например, по v-sync или еще как-то отдаст в контроллер дисплея наиболее свежий отрисованный кадр. То есть тут важный момент, что вместо ожидания v-sync, всегда можно перерисовать буфер, в котором находится наиболее старый кадр. А вот в двойной же буферизации, перерисовывать самый старый кадр (он же текущий) — нельзя.

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

                                  Но еще раз, та фраза, на которую вы сосласись:
                                  Обсудив ситуацию, мы решили отложить унификацию до более глубокой проработки графического стека.

                                  не относится к оптимизации FPS.
                                    0
                                    Я хочу сказать, что главное преимущество тройной буферизации — это повышение FPS.

                                    нет. это упрощение синхронизации.
                                    не относится к оптимизации FPS.

                                    да, не относится к оптимизации, а относится к невнимательности или непониманию принципов формирования кадров.
                                      0
                                      да, не относится к оптимизации, а относится к невнимательности или непониманию принципов формирования кадров.

                                      Да, так и оказалось :) Была ошибка в коде в том моменте когда nukear отрисовывал очередной кадр и запрашивает смену буфера. Я понимаю, что смотреть вероятно не будете, но на всякий случай оставляю ссылку на правку.

                            Only users with full accounts can post comments. Log in, please.