MLAA: Эффективно переносим антиалиасинг с GPU на CPU

Скачать статью и посетить домашнюю страницу MLAA

Скачать статью MLAA: Efficiently Moving Antialiasing from the GPU to the CPU [Eng., PDF 1.2MB]
Домашняя страница Морфологическое сглаживание (MLAA) (исходный код, видео)

 

Введение

Эффективные методы антиалиасинга - важная составляющая высококачественного рендеринга в реальном времени. MSAA (Multisample antialiasing) – стандартный метод, широко распространённый в наши дни, который, однако, имеет ряд серьёзных недостатков:

  • Несовместимость с методом отложенного освещения , который всё больше применяется при реализации рендеринга в реальном времени;
  • Высокие требования к памяти и скорости обработки, что препятствует его применению на некоторых широко распространённых платформах (таких как Sony Playstation* PS3* [Perthuis 2010]);
  • Невозможность сглаживания не геометрических границ. Для этого требуется применение алгоритмов альфа-смешивания.

Новый метод, разработанный в Intel Labs и называемый «морфологическим антиалиасингом» (MLAA) [Reshetov 2009], позволяет преодолеть все эти ограничения. MLAA – это фильтр, основанный на постобработке изображений. Он находит разрывы в изображениях и смешивает цвета в районе этих разрывов, что обеспечивает эффективный антиалиасинг. Приведённый здесь пример основан на стандартной базовой реализации MLAA, представленной А. Решетовым. Для улучшения производительности были внесены следующие изменения:

  • Интегрирована новая эффективная и более удобная система задач, реализованная с использованием Intel® Threading Building Blocks (Intel® TBB);
  • Интегрирована новая конвейерная система для подгрузки в CPU графических задач;
  • Улучшена схема доступа к данным;
  • Широкое использование инструкций Intel® SSE для оптимизации поиска разрывов и смешивания цветов.

 

Алгоритм MLAA

 

Концептуально, алгоритм MLAA обрабатывает изображение за три шага:

  1. Находит разрывы между пикселями данного изображения;
  2. Определяет U, Z, L-образные профили;
  3. Смешивает цвета в районе этих профилей.

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

К концу первого шага каждый пиксель маркируется флагом горизонтальногои/иливертикального разрыва, в случае обнаружения таковых. В следующем шаге мы «обходим» отмеченные флагами пиксели в поисках линий разрывов (последовательностей соседних пикселей, отмеченных одним флагом разрыва), и определяем, как они могут формировать L-образные профили:

Figure 1
Рис. 1: Обработка изображения в MLAA. На изображении слева показаны профили Z, U и L.

Третьим и последним шагом выполняется смешивание цветов на каждом из обнаруженных L-образных профилей.

Основная идея заключается в соединение средней точки первого сегмента L-профиля (горизонтальная зелёная линия на рисунке внизу) со средней точкой второго сегмента (вертикальная зелёная линия; соединяющая линия красная). Соединяющая линия разделяет каждый пиксель на две трапеции. Для каждого пикселя площадь соответствующей трапеции определяет его цветовой вес при смешивании. Например, на рисунке, приведённом внизу, видно, что площадь трапеции пикселя с5 составляет 1/3. Поэтому новый цвет с5 будет рассчитан как 2/3 * (цвет с5) + 1/3 * (цвет нижнего соседнего пикселя с5).


Рис. 5:Расчёт весов пикселей при смешивании

На практике для того, чтобы силуэт выглядел гладко, необходимо минимизировать разницу цветов в районе соединения последовательных L-образных профилей. Для этого мы слегка регулируем цвет точек соприкосновения на L- образных сегментах на основе цветов пикселей вокруг них.

 

Управление программой

Камеру можно двигать, используя мышь; колесо мыши используется для зумирования. Также в правом верхнем углу имеются три блока управления:


Рис. 3: Экран программы в действии

Первый блок контролирует рендеринг сцены:

  • Pause Scene включает/выключает анимацию в сцене;
  • Show zoom boxвключает/выключает окно зумирования, позволяет пользователю поближе рассмотреть участки изображения для эффективной оценки методов антиалиасинга. Область увеличения можно менятьс помощью правой кнопки мыши;
  • Scene complexity симулирует эффект увеличения сложности сцены с помощью перерисовки. Значение (от 1 до 100, меняется бегунком) показывает, сколько раз сцена перерисовывается за фрейм.

Второй блок позволяет выбрать, какой метод антиалиасинга использовать: MLAA, MSAA (4x) или без антиалиасинга. Это нужно для сравнения эффективности и качества работы разных методов (по умолчанию стоит «MLAA»).

Последний блок доступен только если выбран метод антиалиасинга MLAA. Здесь можно выбрать, как алгоритм будет работать:

  • Copy/Map/Unmap only копирует цветовой буфер из GPU в CPU и обратно. Между двумя операциями копирования никаких действий MLAA не производится. Это позволяет измерить влияние операции копирования на общую производительность алгоритма;
  • CPU tasks pipelining включает/выключает конвейер, подгружающий в CPU графические задачи (по умолчанию включён). Помогает оценить плюсы применения конвейерного режима;
  • Show found edges запускает первую часть алгоритма (нахождение разрывов между пикселями), а шаг смешивания заменён на шаг отладки, в котором цвет пикселей:
    • Меняется на зелёный, если были найдены горизонтальные разрывы;
    • Меняется на синий, если были найдены вертикальные разрывы;
    • Меняется на красный, если были найдены и горизонтальные, и вертикальные разрывы;
    • Не меняется, если разрывов не обнаружено.


Рис. 4: Сцена механизма башни с MLAA и включенной опцией ”show found edges”

 

Архитектура программы

Без использования режима конвейера последовательность событий в каждом фрейме выглядит так:

  1. АНИМИРОВАНИЕ И РЕНДЕРИНГ ТЕСТОВОЙ СЦЕНЫ
  2. РАБОТА MLAA (ЕСЛИ ВКЛЮЧЁН)
    • Копирование цветового буфера, где осуществлялся процесс рендеринга, во вспомогательный буфер;
    • Подключение вспомогательного буфера в память для доступа со стороны CPU;
    • Постобработка MLAA (вспомогательный буфер работает одновременно на ввод и на вывод);
    • Отключение вспомогательного буфера;
    • Копирование данных из вспомогательного буфера обратно в цветовой буфер GPU;
    • Рендеринг окна зумирования (если применим).
  3. РЕНДЕРИНГ ПОЛЬЗОВАТЕЛЬСКОГО ИНТЕРФЕЙСА, ПОКАЗ ФРЕЙМА

За исключением шага постобработки MLAA (и снова, пока не принимая во внимание конвейерный режим), все эти шаги выполняются с помощью стандартных методов Microsoft DirectX*.

 

API управления задачами

Алгоритм MLAA легко параллелизуется, так как и нахождение разрывов, и операции смешивания могут обрабатывать цветовой буфер по частям (блоками по несколько смежных строк или столбцов). MLAA можно реализовать с использованием метода параллелизма задач, что автоматически загружает все доступные ядра CPU, при этом код количество ядер никак не отслеживает.

В нашей программе используется простой API управления задачами на С, реализованный с помощью Intel® Threading Building Blocks (Intel® TBB). ЭтотAPI был создан для облегчения интеграции нашего метода в уже существующие программы, в которых уже есть подобные API управления задачами (например, кросс-платформные игровые движки). Вдобавок, легче читать исходный код основного файла MLAA.cpp.

Вот две важные функции нашего API:

    BOOL CreateTaskSet(
        TASKSETFUNC                 pFunc,      //  Function pointer to the 
                                                //  Taskset callback function
        VOID*                       pArg,       //  App data pointer (can be NULL)
        UINT                        uTaskCount, //  Number of tasks to create 
        TASKSETHANDLE*              pDepends,   //  Array of TASKSETHANDLEs that 
                                                //  this taskset depends on.  The 
                                                //  taskset will not be scheduled
                                                //  until all tasksets in this list
                                                //  complete.
        UINT                        uDepends,   //  Count of the depends list
        OPTIONAL LPCSTR             szSetName,  //  [Optional] name of the taskset
                                                //  the name is used for profiling
        OUT TASKSETHANDLE*          pOutHandle  //  [Out] Handle to the new taskset
        );

И:

    //  WaitForSet will yield the main thread to the tasking system and return
    //  only when the taskset specified has completed execution.
    VOID WaitForSet(
        TASKSETHANDLE               hSet        // Taskset to wait for completion
        );

Функция обратного вызова имеет следующий вид:

typedef VOID (*TASKSETFUNC)(VOID* TaskInfo, INT iContext, UINT uTaskId, UINT uTaskCount);

 

MLAA требует соблюдения следующей зависимости между тремя последовательными наборами задач:

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

Эти зависимости отражены в MLAA.cpp в виде последовательности из трёх вызовов функции CreateTaskSet; функциями обратного вызова (реализованными в MLAAPostProcess.cpp) соответственно являются MLAAFindDiscontinuitiesTask, MLAABlendHTask, MLAABlendVTask.

Если конвейерный режим выключён, то мы задаём ожидание завершения работы последнего набора задач с помощью функции WaitForSet, вызываемой с идентификатором последнего набора задач. Когда эта функция завершается, работа MLAA в текущем фрейме считается законченной. При использовании конвейера всё немного усложняется.

 

Конвейер CPU/GPU

Чтобы получить максимальную производительность, мы стараемся по максимуму загружать как CPU, так и GPU. Из-за наличия зависимостей между наборами задач, полной загрузки можно достичь с помощью попеременной обработки сразу нескольких фреймов, как это показано на приведённой ниже диаграмме:


Рис. 5: Продвижение фреймов по конвейеру. Красные блоки на линии main thread показывают, что главный поток берёт на себя часть работы MLAA в случаях повышенной нагрузки на CPU.

 

Другими словами, мы должны построить такуюсистему, в которой конвейер представлял бы собой последовательность стадий обработки на CPU и GPU, и которая могла бы запускать по несколько подобных конвейеров одновременно.

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

Шаг 1 (GPU):

  • Анимирование и рендеринг тестовой сцены;
  • Копирование цветового буфера во вспомогательный буфер (используя функцию копирования GPU CopyResource).

Шаг 2 (CPU):

  • Подключение вспомогательного буфера;
  • Выполнение MLAA-постобработки (вспомогательный буфер служит как для ввода, так и для вывода).

Шаг 3 (GPU):

  • Отключение вспомогательного буфера и копирование его обратно в цветовой буфер;
  • Завершение рендеринга фрейма (окно зума, графический интерфейс) и вывод на экран.

Для реализации данной концепции мы разработали простой класс Pipeline. Каждая стадия на нём представлена структурой PipelineFunction, которая описывает вызываемую функцию и тип стадии. Функция обратного вызова должна иметь следующий вид:

typedef void (*PPIPELINEFUNC)( UINT uInstance, ID3D11DeviceContext* pContext )

Где uInstance указывает, из какого экземпляра конвейера была вызвана функция. На стадии GPU для сообщения о завершении используется запрос DirectX* (имеющий тип D3D11_QUERY_EVENT), но на стадии CPU, чтобы сообщить о завершении, приходится явно вызывать метод класса Pipeline CompleteCPUWait:

    //  CompleteCPUWait signals an instance that expects an CPU wait in order
    //  to complete. When the Pipeline reaches that instance, it will be complete
    //  for that stage in the pipeline.
    VOID
    CompleteCPUWait(
        UINT                    uInstance );

Создание экземпляров конвейера требует вызова метода Init. В нашем случае код выглядит так:

	PipelineFunction Func[ 2 ];
	Func[0].Type = PIPELINE_EVENT_COMPLETE;
	Func[0].pFunc = AnimateAndRenderScene;
	Func[1].Type = PIPELINE_CPU_COMPLETE;
	Func[1].pFunc = MLAAPostProcessing;

	g_Pipeline.Init(g_NumPipelineInstances, ARRAYSIZE( Func ), Func, g_pPipelineQueries );

Где g_NumPipelineInstances это количество создаваемых экземпляров конвейера (в данном случае 3).

Для запуска конвейеров в каждом фрейме вызывается метод Start:

    //  Start is used to execute the Pipeline.  Processing will continue until
    //  an instance in the pipeline completes.  The index of that instance is
    //  return.  PIPELINE_INVALID is returned if the Pipeline was set to flush
    //  and there are no more instances left in the Pipeline.
    UINT
    Start(
        ID3D11DeviceContext*    pContext,       //  Context to issue Event query on.
        
        BOOL                    bFlush = FALSE  //  If bFlush is TRUE, the Pipeline
                                                //  will not create new instances.
                                                //  Specify TRUE if you want to flush
                                                //  the Pipeline.
        );

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

Конвейерная система не использует функцию API WaitForSet, поскольку эта функция блокирующая и, поэтому не позволяет работать конвейеру. Вместо неё мы использовали завершающую задачу, то есть такую задачу, которая ожидает завершения всех остальных задач и единственной функцией которой является вызов метода CompleteCPUWait.

 

Оптимизация с помощью Intel® SSE

При первом проходе алгоритма MLAA происходит поиск разрывов между пикселями. Для каждого пикселя цвет сравнивается с цветом соседнего нижнего пикселя (при поиске горизонтальных разрывов) или правого соседнего пикселя (при поиске вертикальных разрывов).

В нашей программе мы сохранили тот простой метод обнаружения разрывов, который был в базовой реализации алгоритма. Считается, что разрыв между двумя пикселями присутствует в случае, если один из RGB компонентов цветов пикселей отличается на 16 или более (при шкале 0-255 формата RGBA8).

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

Так как данный шаг не зависит от остальной части алгоритма и работает на CPU, при его работе можно использовать любые данные программы. Единственными ограничивающими факторами являются:

  • Алгоритм должен выдавать один бит на пиксель, с информацией о возможном разрыве в данной точке;
  • Соблюдается компромисс между производительностью и качеством/сложностью;
  • Воображение программиста.

Как и в базовой версии [Reshetov, 2009], мы используем формат рендер-таргета RGBA8, а флаги «вертикальный разрыв» и «горизонтальный разрыв», рассчитываемые MLAA, хранятся в двух старших битах пиксельных данных (т.е. в двух старших битах альфа-компонента, который не участвует в операциях смешивания MLAA). Это позволяет упростить программу и оптимизировать использование памяти, а также оптимизировать следующие шаги алгоритма (см. главу «Оптимизация транспонированием»).

Поскольку каждый пиксель содержит 32 бита данных, и каждый пиксель на данном шаге может обрабатываться независимо от других, то, используя встроенные функции Intel® SSE, мы можем обрабатывать по четыре пикселя за раз. Код обнаружения разрывов очень короткий.

	
	//---------------------------------------------------------------------------------------
	// Given a row of pixels, check for color discontinuities between a pixel and its
	// neighbor. Intel SSE implementation, so we process all 4 pixels in the row at a time.
	// If a discontinuity is detected, the corresponding flag is set in the alpha component of the pixel.
	//---------------------------------------------------------------------------------------
	inline void ComparePixelsSSE(__m128i& vPixels0, __m128i vPixels1, const INT EdgeFlag)
	{
	// Each byte of vDiff is the absolute difference between a pixel's color channel value, and the one from its neighbor.
		__m128i vDiff = _mm_sub_epi8(_mm_max_epu8(vPixels0, vPixels1), 
									 _mm_min_epu8(vPixels0, vPixels1));
	// We are only interested if the diff. is >16, and not interested in alpha differences.
		const INT Threshold = 0x00F0F0F0;
		__m128i vThresholdMask = _mm_set1_epi32(Threshold);
		vDiff = _mm_and_si128(vDiff, vThresholdMask);
	// Each of the four lanes of the selector is 0 if no discontinuity detected RGB, 0xFFFFFFFF else.
		__m128i vSelector = _mm_cmpeq_epi32(vDiff, _mm_setzero_si128());

В базовой реализации вслед за этим идёт обновление альфа-компонентов пикселей, но там для этого используется неэффективный последовательный код. Мы оптимизировали эту часть, используя Intel® SSE:

	
	__m128i vEdgeFlag = _mm_set1_epi32(EdgeFlag);
	vEdgeFlag = _mm_andnot_si128(vSelector, vEdgeFlag);
	// vEdgeFlag now contains EdgeFlag for the pixels where a discontinuity was detected, 0 otherwise.
	vPixels0 = _mm_or_si128(vPixels0, vEdgeFlag);

Мы также заменили функцию MixColors (которая рассчитывает линейную интерполяцию двух цветов) на полноценную функцию, реализованную с помощью Intel® SSE.

 

Оптимизация транспонированием

Следующий шаг алгоритма - нахождение «линий разрыва», т.е. последовательностей следующих друг за другом пикселей, имеющих одинаковый флаг разрыва (горизонтальный при горизонтальном проходе о смешивания, вертикальный при вертикальном проходе смешивания), с помощью обхода строк и столбцов цветового буфера [Reshetov, 2009].

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

Хорошая новость в том, что мы можем проверять по 4 пикселя за раз, используя встроенную Intel® SSE функцию _mm_movemask_ps, при выполнении следующих условий:

  1. Сканируемые пиксели располагаются по соседним адресам;
  2. Адрес первого пикселя выровнен согласно требованиям Intel® SSE (по 16-ти байтам);
  3. Флаг разрыва хранится в старшем бите 32-битных данных пикселя.

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

Если все условия выполняются, то мы подсчитываем флаги:

	__m128 PixelFlags = *(reinterpret_cast<__m128*>(WorkBuffer + OffsetCurrentPixel));
	// Creates a 4-bit mask from the most significant bits
	int HFlags = _mm_movemask_ps(PixelFlags);	

В зависимости от значения HFlags, возможны пять вариантов:

  • 0 (самый частый случай): в данной группе из 4 пикселей нет ни одного флага разрыва, можно переходить к следующей группе;
  • Установлен бит 0: линия разрыва начинается с первого пикселя группы;
  • Установлен бит 1: линия разрыва начинается со второго пикселя группы;
  • Установлен бит 2: линия разрыва начинается с третьего пикселя группы;
  • Установлен бит 3: линия разрыва начинается с четвёртого пикселя группы.

Данная оптимизация является частью базовой реализации и нормально работает при горизонтальном смешивании, но её невозможно применить при вертикальном смешивании. Поскольку код сканирует буфер по вертикали, то (1) не выполняется (соседние пиксели в столбце не хранятся в соседних адресах памяти), (3) тоже не выполняется (вертикальный флаг разрыва хранится в бите 30 пиксельных данных).

Проблему (3) легко решить с помощью дополнительной операции «сдвинуть влево на один бит» при обработке вертикальных флагов:

	int Shift = (EdgeFlag == EdgeFlagV) ? 1 : 0;
	__m128 PixelFlags = *(reinterpret_cast<__m128*>(WorkBuffer + OffsetCurrentPixel));
	PixelFlags = _mm_castsi128_ps( _mm_slli_epi32(_mm_castps_si128(PixelFlags), Shift) );
	int HFlags = _mm_movemask_ps(PixelFlags);

Но первое условие всё ещё остаётся проблемой. К тому же, вертикальное сканирование очень плохо использует кэш. Из-за этих двух проблем вертикальный проход смешивания работает в 3 раза (300%!) медленнее, чем горизонтальный в базовой реализации (это показывают данные профилирования).

Решить данную задачу можно, заставив алгоритм при вертикальном проходе эффективно использовать кэш и команды Intel® SSE. Для этого нужно рассматривать цветовой буфер как матрицу пикселей и производить её транспонирование между проходами:

  • Выполнить горизонтальный проход смешивания;
  • Транспонировать (горизонтально смешанный) цветовой буфер;
  • Выполнить вертикальный проход смешивания;
  • Транспонировать цветовой буфер в исходное состояние;

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

На практике операция транспонирования реализована не как отдельное действие, а как завершающая часть соответствующего прохода смешивания. Это позволяет использовать при работе «горячесть» кэша.

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

 

Итоговая производительность

Производительность работы MLAA измерялась на следующих двух компьютерных конфигурациях:

  • Кодовое имя “Huron River”: процессор Intel® Core™ i7-2820QM (микроархитектура Intel Sandy Bridge, 4 ядра, 8 потоков, 2.3 ГГц) с процессорной графикой GT2, ОЗУ 4ГБ, ОС Windows 7 Ultimate 64-bit SP 1
  • Кодовое имя “Kings Creek”: процессор Intel® Core™ i5-660 (Clarkdale, 2 ядра, 4 потока, 3.33 ГГц), графика GMA HD (Ironlake), ОЗУ 2ГБ, ОС Windows 7 Ultimate 64-bit SP 1

Мы измеряли среднее время рендеринга фреймов как функцию от сложности сцены при разных настройках антиалиасинга. Мы также измеряли время рендеринга в режиме Copy/Map/Umap only, чтобы проследить влияние операций копирования буфера на общую производительность алгоритма.

 

Вывод

Данные показывают, что на Huron River цена MSAA 4x растёт линейно с увеличением сложности сцены (и правда, при всех разрешениях время обработки фрейма при использовании MSAA 4x для рендеринга нашей тестовой сцены приблизительно в два раза превышает время рендеринга фрейма без использования методов антиалиасинга). Затраты же MLAA оказалась более-менее постоянной (около 4 мс на фрейм при разрешении 1280x800). Это совпадает с нашими ожиданиями, так как, в отличие от MSAA 4x, MLAA выполняется только один раз за фрейм, независимо от сложности сцены или количества вызовов отрисовки.

Мы также отметили, что, за исключением очень низких показателей параметра сложности (менее 5), MLAA всегда работает эффективнее, чем MSAA 4x, независимо от разрешения. Также разница в производительности возрастает с ростом сложности (поскольку, как уже отмечалось, цена MSAA 4x растёт линейно вместе со сложностью, в то время как цена работы MLAA не меняется).

На Kings Creek мы не смогли сравнить издержки работы MLAA и MSAA 4x, так как последний метод не имеет реализации для платформы Ironlake. Нашей целью здесь было определить, может ли MLAA выступать программной альтернативой антиалиасинга с приемлемой производительностью. Наши измерения при разрешении 1280x800 показали, что цена работы MLAA в основном не зависит от сложности сцены и составляет в среднем 7.5 мс (мы отбросили показатели при крайних значениях уровня сложности = 100).

Интересный факт, если сравнивать полученный результат с гипотетический реализацией MSAA 4x с примерно такой же производительностью, какую он даёт при работе на Huron River (один фрейм с включённым MSAA 4x по времени примерно как два фрейма без антиалиасинга), то MLAA опять будет опережать MSAA 4x при практически всех значениях сложности (в данном случае ≥4).


Таблица 1. Время рендеринга тестовой сцены на King’s Creek.


Таблица 2. Время рендеринга тестовой сцены на Huron River.


Рис. 6: Время рендеринга тестовой сцены при разных методах антиалиасинга, приведено как функция от сложности сцены. Нижняя кривая показывает разность между графиками «MLAA с включённым конвейером» и «без антиалиасинга» и означает «цену» использования MLAA [Huron River, разрешение 1280x800]


Рис. 7: Время рендеринга тестовой сцены при разных методах антиалиасинга, приведено как функция от сложности сцены. Нижняя кривая показывает разность между графиками «MLAA с включённым конвейером» и «без антиалиасинга» и отмечает цену использования MLAA [Huron River, разрешение 1600x1200]


Рис. 8: Время рендеринга тестовой сцены с включённым и выключеннымMLAA, приведено как функция от сложности сцены. Нижняя кривая показывает разность между графиками «MLAA с включённым конвейером» и «без антиалиасинга» и отмечает цену использования MLAA [Kings Creek, разрешение 1280x800]

Ссылки

[Reshetov 2009] RESHETOV, A. 2009. “Morphological Antialiasing

[Perthuis 2010] PERTHUIS, C. 2010. MLAA in God of War 3. Sony Computer Entertainment America, PS3 Devcon, Santa Clara, July 2010.

[Jimenez et al., 2011] JIMENEZ, J., MASIA, B., ECHEVARRIA, J., NAVARRO, F. and GUTIERREZ, D. 2011. Practical Morphological Anti-Aliasing. In Wolfgang Engel, ed., GPU Pro 2. AK Peters Ltd.

[SIGGRAPH 2011] JIMENEZ, J., GUTIERREZ D., YANG, J., RESHETOV, A., DEMOREUILLE, P., BERGHOFF, T., PERTHUIS, C., YU, H., MCGUIRE, M., LOTTES, T., MALAN, H., PERSSON, E., ANDREEV, D. and SOUSA T. 2011. Filtering Approaches for Real-Time Anti-Aliasing. In ACM SIGGRAPH 2011 Courses.

[Luminance] Definition of luminance for CRT-like devices:

INTERNATIONAL COMMISSION ON ILLUMINATION. 1971. Recommendations on Uniform Color Spaces, Color Difference Equations, Psychometric Color Terms. Supplement No.2 to CIE publication No. 15 (E.-1.3.1), TC-1.3, 1971.

Относительно LCDs:

ITU-R Rec. BT.709-5. 2008. Page 18, items 1.3 and 1.4

Alexandre De Pereyra
07-15-2011
07-15-2011
Tech Articles
Intel® TBB
Performance Analysis
Intel® SSE
 
 
 
no
Efficient antialiasing techniques are an important tool of high-quality, real-time rendering. MSAA (Multisample Antialiasing) is the standard technique in use today, but comes with some serious disadvantages (incompatibility with deferred lighting, high memory/processing cost, inability to smooth non-geometric edges without alpha to coverage). Morphological Antialiasing (MLAA) is a new technique developed by Intel Labs to address these limitations. MLAA is an image-based, post-process filtering technique, that identifies discontinuity patterns and blends colors in the neighborhood of these patterns to perform effective antialiasing. This sample is based on the original CPU-based MLAA implementation, with improvements to greatly increase performance.
Для получения подробной информации о возможностях оптимизации компилятора обратитесь к нашему Уведомлению об оптимизации.
Возможность комментирования русскоязычного контента была отключена. Узнать подробнее.