Исследование вопроса о точности Pixel Shader 2.0



В этой статье я хочу сделать небольшое обозрение текущей ситуации с пиксельными шейдерами второй версии, представленными в DirectX9. Сначала немного истории.

Джон Кармак

Еще в 2000 году Джон Кармак (John Carmaсk) упомянул о необходимости использования чисел с плавающей точкой в пиксельном конвейере, в дополнение к числам с плавающей точкой в геометрическом конвейере видеокарт. Ниже приведен перевод некоторых выдержек из его «плана»:

4/29/00

-------

Нам необходимо больше бит на цвет в наших 3Д-ускорителях.

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

………………

Есть еще несколько тонких моментов [из-за ограниченной точности — прим. ред.], таких как потеря возможного результата из-за повторяющегося возведения в квадрат исходных значений и последствий ограничений суммирования несколько источников света до модуляции их «вниз» цветом материала. Необходимость расширенного диапазона еще более очевидна. Некоторые значения имеют присущий им диапазон значений от 0 до 1, например, коэффициенты отражений и прохождений. Нормализованные вектора имеют диапазон от -1 до 1. Однако, основное числовое значение в рендеринге — свет — ничем не ограничено. Нам необходимо НАМНОГО больший диапазон, чем от 0 до 1. Q3 подстраивает таблицы гамма коррекции, жертвуя битом точности ради получения диапазона от 0 до 2, но мне необходимо больше чем это для даже примитивных техник рендеринга. Для точного моделирования полного светового диапазона воспринимаемого человеком нам необходимо больше пяти бит экспоненты.

……………………

64 бит пиксели. Это «The Right Thing to do». Поставщики оборудования: не будьте последней компанией, которая сделает этот переход.

Полный оригинальный текст можно найти по адресу.

Главная идея вышеприведенного текста — необходимость применения чисел с плавающей точкой при пиксельных операциях (вместо словосочетания «плавающая точка» местами будет использоваться аббревиатура FP). Позже в статье я еще раз обращусь к высказыванию Кармака, а пока следуем дальше.

Microsoft

В феврале 2001 Microsoft уже представляла свое видение архитектуры DirectX9 (очень кстати похожей на то, что мы получили в итоге в лице ATi R300). На той презентации, в том числе было объявлено, что следующая версия пиксельных шейдеров в DirectX9, известная под аббревиатурой «PS 2.0» будет оперировать с FP числами одинарной точности (single precision), и будет близка по функциональности к вершинным шейдерам.

Что было с успехом реализовано в финальной версии DirectX9, вышедшей в декабре 2002 года.

Что-же такое числа с плавающей точкой?

Существуют несколько способов представить действительные числа на компьютерах.

1) Представление с фиксированной точкой — устанавливает точку-разделитель в произвольном месте целого числа. Такое представление сопоставляет целые числа части чего-либо. Например, считая для 1/100 метра эквивалентно единице и предположив, что мы можем хранить в памяти 5 десятичных чисел, мы сможем представлять как 999.00 м. так и 000.99 м.

2) Представление в виде дробных чисел — представляет число как отношение двух целых чисел.

3) Представление с плавающей точкой — наиболее используемое решение. Это представление хранит числа в «научном» написании — 1,45*1019. Далее мы будем рассматривать именно это представление.

Итак: представление с плавающей точкой

В научном написании действительные числа пишутся в виде двух чисел: мантиссы и экспоненты. Например: 123.456 может быть написано как 1.23456 x 102. В шестнадцатиричной системе счисления число 123.abc будет эквивалентно 1.23abc x 162.

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

Числа с плавающей точкой с другой стороны используют что-то подобное «плавающему окну» точности в зависимости от масштаба числа. Это позволяет с легкостью представлять числа от 1,000,000,000,000 до 0.0000000000000001.

В статье я не буду рассматривать тонкости стандарта в отношении операций с плавающей точкой, а коснусь только главного — иного представления FP чисел по сравнению с целыми числами — и сравню диапазоны и точность FP чисел, поддерживаемых центральными процессорами и GPU видео-чипами.

Но сначала еще немного истории.

Intel и стандарт чисел с плавающей точкой

Сегодня стандарт IEEE-754, описывающий числа с плавающей точкой является наиболее распространенным для представления действительных чисел на компьютерах, включая x86, IA64, PowerPC и большинство Unix совместимых платформ. Как же он образовался?

В 1976 году корпорация Intel начала разработку сопроцессоров для вычисления с числами с плавающей точкой для ее i8086/8 and i432 микропроцессоров. Десять лет спустя доктор Джон Палмер (Dr. John Palmer) — менеджер Intel по направлению FP сопроцессоров — в университете Стэнфорд (Stanford) нанял Вильяма Кэхэна (William Kahan) в качестве консультанта по готовящемуся i8087 сопроцессору к процессорам i8086/8. В последствии по Силиконовой долине распространились слухи о i8087, и это настолько озаботило фирм-разработчиков процессоров, что в итого был сформирован комитет по определению стандарта FP арифметики для микропроцессоров. В 1977 году, после нескольких встреч комитета Кэхэна (Kahan), студент Джером Конен (Jerome Coonen) из U.C. Berkeley и профессор Гарольд Стоун (Harold Stone) подготовили черновик спецификации в виде IEEE стандарта и представили его на обсуждение в качестве стандарта IEEE p754 — этот черновик был назван «K-C-S». К 1985 году, когда IEEE-754 стандарт был принял, он уже стал стандартом де-факто.

Современные x86 совместимые микропроцессоры используют 32, 64 и 80 битное представление FP чисел.

Формат хранения FP чисел

Числа с плавающей точкой формата IEEE-754 включают в себя три основные компоненты: знак, экспоненту и мантиссу. Мантисса состоит из дробной части и подразумеваемой «ведущей» цифры. Экспонента всегда считается по основанию 2.

Ниже показана таблица с разбиением чисел с плавающей точкой одинарной (single — 32-bit), двойной (double — 64-bit), четверной (quadruple — 128-bit) и расширенной (extended — 80-bit) точности. В таблице приведено количество бит и диапазон (показан в квадратных скобках) выделенные под каждое поле:

 

Sign

Exponent

Mantissa

Bias

Single Precision

  1 [31]   8 [30-23]   23 [22-00]

127

Double Precision

  1 [63]   11 [62-52]   52 [51-00]

1023

Quadruple Precision

  1 [127]   15 [126-112]   112 [111-00]

16383

Extended Precision

  1 [79]   15 [78-63]   64 [63-00]

16383

Одним из представлений разбиения чисел с плавающей точкой является «sXXeYY» форма, где XX обозначает количество бит в мантиссе, а YY — количество бит в экспоненте. В этом представлении числа различной точности описываются следующим образом: одинарной (single) — s23e8, двойной (double) — s52e11, расширенной (extended) — s64e15 и четверной (quadruple) — s112e15.

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

  sign   exponent   mantissa

Рассмотрим, что же хранится в данных полях.

Знаковый бит

Всего возможно два значения: 0 — обозначает положительное число; 1 — отрицательное.

Экспонента

Поле экспоненты должно представлять как отрицательные, так и положительные значения. Для этого, к искомому значению экспоненты прибавляется некое число — bias (сдвиг), и полученное в итоге число храниться в качестве экспоненты. Например, для чисел одинарной точности значение сдвига равно 127. Таким образом, экспонента равная 0 означает, что в поле экспоненты будет храниться 127. А значение поля, равное 200 означает экспоненту равную (200-127), или 73.

Мантисса

Мантисса представляет точность числа. Она состоит из подразумеваемого лидирующего бита и «дробных» битов.

Чтобы понять значение подразумеваемого лидирующего бита учтем, что любое число может быть представлено в научной нотации по-разному. Например, число пять может быть представлено любым из нижеприведенных способов:

  • 5.00 x 100
  • 0.05 x 102
  • 5000 x 10-3

Для максимизации количества представимых чисел, числа с плавающей точкой хранятся в нормализованной форме. В нормализованной форме «десятичная» (двоичная, шестнадцетиричная) точка всегда должна располагаться после первой ненулевой цифры. В нормализованной форме пять представляется как 5.00 x 100.

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

Диапазоны и точность чисел с плавающей точкой

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

 

Single

Double

Quadruple

Extended

Decimal digits of precision
  p / log2(10)

7.22

15.95

34.01

19.26

Emax

+127

+1023

+16383

+16383

Emin

-126

-1022

-16382

-16382

Range Magnitude Maximum
  2Emax + 1

3.4028E+38

1.7976E+308

1.1897E+4932

1.1897E+4932

Range Magnitude Minimum
  2Emin

1.1754E-38

2.2250E-308

3.3621E-4932

3.3621E-4932

Теперь взглянем подробнее на стандарт PS 2.0 и современные видеочипы

Изначально при представлении PS 2.0 и далее, при выпуске первой бета версии DirectX9 Microsoft представила единые требования по минимально допустимой точности чисел с плавающей точкой, с идеалом, стремящемся к полноценным числам одинарной точности (32 бит — single). Позже, очевидно из-за лоббирования NVIDIA, в стандарт было введено понятие ограниченной точности (partial precision) при выполнении команд шейдера. Заметим, что флаг ограниченной точности для шейдерной команды — это только намек на то, что операция не требует максимальной точности, — и этот флаг может быть спокойно проигнорирован драйверами/видеочипом, соответственно операция будет выполнена в обычном режиме.

Ниже приведен текст текущей спецификации по отношению к точности операций с плавающей точкой:

Internal Precision

— All hardware that support PS2.0 needs to set D3DPTEXTURECAPS_TEXREPEATNOTSCALEDBYSIZE.

— MaxTextureRepeat is required to be at least (-128, +128).

— Implementations vary precision automatically based on precision of inputs to a given op for optimal performance.

— For ps_2_0 compliance, the minimum level of internal precision for temporary registers (r#) is s16e7

— The minimum internal precision level for constants (c#) is s10e5.

— The minimum internal precision level for input texture coordinates (t#) is s16e7.

— Diffuse and specular (v#) are only required to support [0-1] range, and high-precision is not required.

Проанализируем вышеприведенную информацию. Для освещения (рассеянного и отраженного) увеличенная точность не требуется — достаточно и той точности, которую обеспечивали DX8 совместимые видеочипы. Дополнительная точность требуется только для (1) временных регистров — r# — s16e7 aka 24bit, (2) констант — c# — s10e5 aka 16bit и (3) текстурных координат — t# — s16e7 aka 24bit.

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

PixelShader 2.0 precision test. Version 1.3
Copyright (c) 2003 by ReactorCritical / iXBT.com
Questio ns, bug reports send to: clootie@ixbt.com


Device: RADEON 9500 SERIES
Driver: ati2dvag.dll
Driver version: 6.14.1.6292


Registers precision:
Rxx = s16e7 (temporary registers)
Cxx = s16e7 (constant registers)
Txx = s16e7 (texture coordinates)


Registers precision in partial precision mode:
Rxx = s16e7 (temporary registers)
Cxx = s16e7 (constant registers)
Txx = s16e7 (texture coordinates)

Итого, после прогона программы мы получаем шесть значений, отражающих точность реализации чисел с плавающей точкой в видеочипе, по одной для каждого типа регистров в двух различных режимах выполнения операций. Саму программу Вы можете загрузить по адресу, указанному в конце статьи. Вместе с программой желающие могут посмотреть код шейдеров, использованных при определении точности вычислений.

Итак посмотрим на результаты, полученые нами при тестировании видеокарт на базе чипов от ATI и NVIDIA.

Registers

ATI
R3x0/Rv350

NVIDIA
NV30/NV31/NV34

NVIDIA
NV35

rXX s16e7 s10e5 s23e8
cXX s16e7 s10e5 s23e8
tXX s16e7 s10e5 s23e8
rXX partial precision s16e7 s10e5 s10e5
cXX partial precision s16e7 s10e5 s10e5
tXX partial precision s16e7 s10e5 s23e8

Если бы не результаты, полученные недавно на NV35, то значения в таблице были бы не очень разнообразны, не правда ли? Давайте разберемся в полученных цифрах. Широко известно, что чипы ATI используют 24 битные FP числа всегда, вне зависимости от флага ограниченной точности. Гораздо интереснее ситуация с чипами NVIDIA — все чипы, кроме NV35, всегда использует 16 битные FP числа, вне зависимости от указанной точности! И это несмотря на то, что само понятие ограниченное точности было введено именно по настоянию NVIDIA, чипы прекрасно поддерживают 32 битную точность при использовании OpenGL расширения NV_fragment_program, и именно NVIDIA везде рекламировала свои новые чипы, как чипы с истинной 32 битной точностью при выполнении пиксельных операций!

Наиболее разнообразно и корректно [среди чипов NVIDIA] поведение NV35. Видно, что в стандартном режиме в соответствии со спецификациями Microsoft вычисления производятся с 32 битной точностью, при указании возможности вычислений с пониженной точностью временные и константные регистры используют 16 битную точность, а текстурные регистры используют 32 битную точность вычислений. Хотя по спецификации Microsoft текстурные регистры в режиме с частичной точностью также могут быть 16 битной точности.

Обращаю Ваше внимание на то, что все результаты для NV3x получены уже на сертифицированных WHQL драйверах, так можно только сожалеть, что Microsoft не контролирует выполнение собственных спецификаций. Также стоит обратить внимание, что формат 16 битных FP чисел, используемых NVIDIA, в точности совпадает с тем, что предложил Кармак в 2000 году.

Проанализируем полученные результаты. Ниже приведены характеристики 16 и 24 битных FP чисел, и 32 битных FP чисел, как эталона.

 

s10e5

s16e7

s23e8

Size (bits)

16

24

32

Mantissa (bits)

10

16

23

Exponent (bits)

5

7

8

Decimal digits of precision   p / log2(10)

3.31

5.11

7.22

Mantissa distinct values

1024

65536

8388608

Emax

+15

+63

+127

Emin

-14

-62

-126

Range Magnitude Maximum
  2Emax + 1

65536

1.8446E+19

3.4028E+38

Range Magnitude Minimum
  2Emin

0.000061

2.1684E-19

1.1754E-38

Отставание s10e5 формата от остальных форматов хранения чисел с плавающей точкой по большинству параметров бросается в глаза. Как ни парадоксально, но s10e5 числа корректней сравнивать с числами с фиксированной точкой, используемыми в PS 1.x. Точность чисел в PS1.x даже в NV3x равна 12 бит, что эквивалентно точности чисел в формате s10e5 (учитывая 10 бит мантиссы, знаковый и подразумеваемый ведущий биты). И именно в сравнении с числами с фиксированной точкой видны преимущества формата s10e5 — намного большие абсолютные значения: 1 (или 2 или 4 или 8 в зависимости от чипа) в сравнении с 65536 и одновременно намного меньшие абсолютные значения.

Здесь опять можно вспомнить письмо Джона Кармака, где он четко сформулировал области, в которых он хотел бы использовать числа s10e5 — это освещение. Именно расширенный диапазон позволяет использовать overbright lighting, когда нужно имитировать очень яркие источники света, ослепляющие человека, а также не позволит потерять детали в тени.

Но точность s10e5 чисел — именно та область, где нужно очень осторожно оперировать. Очевидно, что они не позволят корректно реализовать raytracer, как это было продемонстрировано ATi, но даже вычисление текстурных координат в пиксельном шейдере может привести к нежелательным последствиям. Ведь точность s10e5 чисел даже не позволит корректно адресовать текстуры размером 1024 пикселей по одной из осей при билинейной фильтрации. В NVIDIA хорошо это понимают и уже начали активно вести работу с разработчиками, обучая как обнаружить те области, где недостаточная точность чисел формата s10e5 приводит к неверным результатам. NVIDIA также усиленно пропагандирует проведение всех высокоточных вычислений в вершинных шейдерах.

Что же дальше?

В статье я рассмотрел что такое числа с плавающей точкой, текущие форматы таких чисел в мире микропроцессоров, а также какую поддержку таких чисел обеспечивают производители видеочипов сегодня. Хочется подчеркнуть, что 16 битные числа с плавающей точкой в формате s10e5 не могут считаться подходящими для выполнения общих математических вычислений. Будем надеяться, что NVIDIA позволит разработчикам игр выбирать самим: когда использовать 32 битные числа с плавающей точкой, а когда — 16 битную версию с ограниченной точностью. Причем такой выбор должен быть не только у флагмана NV35, но и у всех представителей семейства GeForce FX.

Вероятно все видеочипы следующего поколения, которые будут поддерживать пиксельные шейдеры версии 3.0, также будут поддерживать и вычисления с полноценными 32 битными числами с плавающей точкой. Но программисты, которые часто сталкиваются с вычислениями с плавающей точкой знают, что даже при работе с 32 битными числами с плавающей точкой или, как их еще называют — числа с одинарной точностью (single precision), нужно быть очень осторожными и ситуации с переполнением диапазона и потерей точности довольно часты. Так что же: будем ждать следующего шага — чисел с двойной точностью (double precision — 64 бит)? Видимо этого не стоит ожидать в скором будущем. В подтверждение приведу следующую цитату, относящуюся к использованию чисел с плавающей точкой в пакетах рендеринга для RenderMan API:

In article <875uvp$t23$1@nnrp1.deja.com>, <rminsk@my-deja.com> wrote:
>I noticed that the binary RIB file specification does not support double
>precision arrays only double precision values. Is there anywhere in
>PRMan or BMRT where values are stored as double precision? Should I
>ever output double precision values in my binary RIB?


The Ri routines are all single precision (so all input is parsed and
put into floats), and thus both BMRT and PRMan are almost completely
float on the inside. Of course, both use doubles occasionally as
temporaries for intermediate calculations in certain parts of the
renderers where that last little bit of precision is vital. But it's
almost correct to say that both renderers are just single precision
throughout.

--
Larry Gritz Pixar Animation Studioslg@pixar.com Richmond, CA

Что это значит для нас? Не стоит ожидать большой пользы от применения чисел с двойной точностью. Когда они будут введены, то не как базовый тип данных, а как дополнительный, который разработчики должны явно затребовать для использования, а базовым типом чисел с плавающей точкой еще долго будут 32 битовые числа одинарной точности.

Ссылка на утилиту

Список литературы

  • An Interview with the Old Man of Floating-Point
  • IEEE-754 References
  • IEEE Standard 754 Floating Point Numbers




Дополнительно

iXBT BRAND 2016

«iXBT Brand 2016» — Выбор читателей в номинации «Процессоры (CPU)»:
Подробнее с условиями участия в розыгрыше можно ознакомиться здесь. Текущие результаты опроса доступны тут.

Нашли ошибку на сайте? Выделите текст и нажмите Shift+Enter

Код для блога бета

Выделите HTML-код в поле, скопируйте его в буфер и вставьте в свой блог.