Битва титанов ATI и NVIDIA: куда подевались честные поединки?

или «Вертим 3DMark-ом как хотим»


NVIDIA

Казалось бы, ещё не успели утихнуть страсти вокруг 3DMark2003 и драйверов Detonator FX, как в сети появилась информация об обнаружении новых «заточек» под этот бенчмарк в том же самом печально известном Detonator FX, а именно об агрессивной оптимизации анизотропной фильтрации, критерием активизации которой являлось имя запускаемого приложения. Более того, в свете этих же событий обновился и ShaderMark от ToMMTi-Systems, незначительная модификация кода шейдеров которого также вызвала ощутимое падение производительности, фактически выявив заточку Detonator FX и под него. Увидев, что «охота на ведьм» начинает приобретать массовый характер, мы также заинтересовались данным вопросом и решили провести своё независимое внутреннее расследование. Однако мы прекрасно понимаем, что для выявления фактов подтасовки драйвером результатов того или иного бенчмарка со стороны приложения в девяноста девяти процентах случаев просто необходимо непосредственное участие его разработчиков. Более того, даже сам разработчик очень часто не в состоянии отследить и блокировать «заточку», поскольку, как мы убедимся позже, в драйверах зачастую используются слишком изощрённые методы распознавания того или иного приложения. Именно поэтому мы решили пойти другим путём и взглянуть на данную проблему с противоположной стороны, а именно проанализировать драйвер изнутри и попытаться локализовать и блокировать все распознавания того или иного Direct3D-приложения, лишив драйвер возможности подстройки под него и, соответственно, сведя риск подтасовки результатов бенчмарка к минимуму.

Итак, за начальную точку отсчёта для наших исследований мы взяли информацию о том, что драйвер реагирует на переименование исполняемого файла как минимум одного бенчмарка, что может говорить только о том, что в его коде обязан присутствовать алгоритм анализа имени запущенного Direct3D-приложения. Его поиском мы и занялись. Исследовав код, мы выяснили, что Detonator FX действительно содержит целую базу распознаваемых по командной строке приложений, включающую в себя порядка 70 элементов. При создании контекста Direct3D драйвер выполняет расчёт двух 64-битных контрольных сумм, используя в качестве входных данных имя исполняемого файла и список заголовков всех окон процесса, получая, таким образом, уникальный 128-битный идентификатор запущенного Direct3D-приложения и выполняя определённые внутренние настройки под него, если оно присутствует в его базе знаний. К сожалению, процесс обратного преобразования базы (то есть процесс получения имён распознаваемых приложений по содержащимся в ней 128-битным идентификаторам) нереален. Максимум, что можно будет сделать в будущем для того, чтобы попытаться расшифровать имена идентифицируемых приложений, — это написать внешнюю утилиту, способную рассчитать контрольные суммы для произвольно введённой строки по такому же точно алгоритму, как это делает драйвер. Это поможет, по крайней мере, проверить, есть ли интересующее нас приложение в базе драйвера или нет. Пока же мы можем только предположить, имена каких приложений помимо 3DMark2003 присутствуют в базе. Её общий размер позволяет нам с достаточной степенью уверенности сделать вывод о том, что её значительная часть служит для обхода тех или иных проблем и, возможно, для оптимизации под те или иные игровые приложения, поскольку общее количество существующих на данный момент синтетических и игровых Direct3D-бенчмарков явно меньше семидесяти, причём чуть ли не в разы. Рискнём предположить, что в эту же базу перекочевали и настройки совместимости для форсирования полноэкранного сглаживания методом суперсемплинга в определённых DirectX6 и DirectX7 приложениях (база настроек совместимости для суперсемплинга довольно внушительного размера присутствовала в ранних версиях драйверов в незашифрованном виде). Однако, само собой, нельзя исключать и того, что некоторые из приложений распознаются драйвером для не совсем честных c точки зрения многих пользователей действий, а именно для активизации агрессивных оптимизаций под тот или иной бенчмарк. Так это или нет, нам поможет понять тестирование, которое мы проведём немного позже. Пока же вернёмся к найденной нами процедуре расчёта контрольной суммы, которая, как мы уже отметили выше, используется драйвером для идентификации запускаемых приложений.

Дальнейшее исследование кода выявило, что эта процедура является унифицированной, то есть драйвер использует её не только для идентификации запущенного приложения путём расчёта 128-битной контрольной суммы при создании контекста, но и вызывает её в других местах кода для формирования дополнительных контрольных сумм. Изучив эти места подробнее, мы обнаружили ссылки на эту процедуру и очень подозрительные проверки контрольных сумм в функциях-обработчиках многих D3DDP2OP_... токенов. Здесь, пожалуй, надо сделать маленькое отступление и пояснить, о чём вообще идёт речь, дабы словосочетание «D3DDP2OP_... токен» не ставило Вас в тупик. Читатели, знакомые с программированием в API Direct3D, знают, что общение приложения с Direct3D-ускорителем производится посредством COM-интерфейса IDirect3DDevice..., однако мало кто представляет себе, в какой момент и в каком виде данные, передаваемые интерфейсу приложением, попадают непосредственно к Direct3D-драйверу. Вызовы многих методов данного интерфейса трансформируются API в специальные управляющие слова (токены), попадающие во внутренний буфер команд, содержимое которого, собственно, и является входными данными для Direct3- драйвера. Так, например, при создании пиксельного шейдера с помощью метода CreatePixelShader API помещает в буфер команд токен D3DDP2OP_CREATEPIXELSHADER и уникальную для этого токена структуру, содержащую токенизированный код шейдера, который должен быть создан. Другими словами, внутренний буфер команд содержит токены D3DDP2OP_..., уникально идентифицирующие ту или иную операцию, которую выполнило Direct3D-приложение, а также некоторые поставляемые приложением данные. Обработкой буфера команд и занимается Direct3D-драйвер, а точнее — реализованная в нём функция D3dDrawPrimitives2. Именно этап обработки драйвером токенов буфера как нельзя лучше подходит для реализации распознавания какого-либо приложения путём анализа поставляемых им данных. Именно в этот момент драйвер может уникально идентифицировать создаваемые приложением вершинные и пиксельные шейдеры, проанализировать формат и содержимое текстуры при её подкачке в видеопамять, да и вообще распознать уйму других специфичных для приложения вещей. Поэтому именно обработчик буфера команд, реализованный в теле функции D3dDrawPrimitives2, должен быть объектом нашего пристального внимания в том случае, если мы пытаемся найти места кода, предназначенные для распознавания драйвером тех или иных приложений. Именно на его изучение мы и потратили некоторое количество времени и, как оказалось, не зря.

Итак, выше отмечено, что мы наткнулись на достаточно подозрительные проверки контрольных сумм в функциях-обработчиках некоторых D3DDP2OP_… токенов. К сожалению, среди них были и токены D3DDP2OP_CREATEPIXELSHADER и D3DDP2OP_CREATEVERTEXSHADER, причём расчёт контрольной суммы производился по токенизированному входному коду шейдера, что, как это ни печально, можно однозначно трактовать только одним образом — всё это также не что иное, как попытки драйвера идентифицировать тот или иной пиксельный и вершинный шейдер, либо то или иное Direct3D приложение. Недавний скандал вокруг 3DMark2003 и DetonatorFX уже доказал нам, что драйвер действительно распознаёт и подменяет некоторые пиксельные и вершинные шейдеры в данном бенчмарке, однако нас несколько смутило, что только количество проверяемых контрольных сумм вершинных шейдеров близко к полусотне, что явно несоизмеримо с количеством найденных и обнародованных Futuremark «заточек» для 3DMark2003. Именно поэтому мы решили попробовать блокировать найденные нами проверки, дабы оценить чистую производительность Direct3D-драйвера. Отлично структурированный код Detonator явно помог нам это сделать: поскольку во всех механизмах распознавания Direct3D-приложений драйвер использует одну и ту же процедуру расчёта контрольной суммы, мы создали небольшой патч-скрипт NVAntiDetector, немного модифицирующий алгоритм её расчёта путём искажения начального значения ключа её генерации. Это привело к тому, что процедура просто стала генерировать другие контрольные суммы, соответственно, блокировав все распознавания шейдеров, анализ командной строки и так далее. Конечно, мы не можем стопроцентно гарантировать, что данный патч-скрипт отключает абсолютно все «заточки», равно как и не исключаем возможности блокирования вполне допустимых оптимизаций и обхода проблем в тех или иных приложениях. Мы полностью принимаем это допущение, однако оно не мешает нам, поскольку мы ставим перед собой цель исследования производительности в бенчмарках, а любое распознавание того или иного популярного синтетического тестового приложения, предназначенного только для оценки производительности системы, с нашей точки зрения, не должно иметь места в теле драйвера.

Результат тестирования Detonator FX в совокупности с NVAntiDetector, к сожалению, превзошел все ожидания. Лишённое «костылей», семейство графических процессоров во главе с флагманом GeForceFX 5900Ultra значительно сдало в производительности практически во всех синтетических и игровых Direct3D-бенчмарках, причём падение производительности в том же самом 3DMark2003 было явно несоизмеримым даже с результатами патча 330, блокировавшего все обнаруженные Futuremark заточки Detonator FX под данный бенчмарк. Фактически, из всех проверенных нами тестов не изменились лишь результаты бета-версии нашего RightMark3D, да и то только потому, что данный бенчмарк пока малопопулярен и его шейдеры ещё незнакомы Direct3D-драйверу. В данный момент мы готовим полномасштабный обзор серийных плат GeForceFX 5900Ultra, который будет включать в себя и исследование поведения данной платы в совокупности со скриптом NVAntiDetector. А пока попробуем заглянуть в прошлое и посмотрим на результаты бенчмарка, который сегодня уже становится вчерашним днём. Как вы уже, наверное, догадались, мы поговорим о предшественнике 3DMark2003, 3DMark2001 SE. Многие могут возразить нам, что дни этого бенчмарка уже сочтены, поэтому тестирование в нём не имеет никакого практического смысла. Вспомним, однако, что Futuremark всегда пытались и до сих пор пытаются создавать бенчмарки с намёком на будущее, поэтому его комплексная сложность вполне соответствует тому, что мы видим в сегодняшних играх. Более того, поиск заточек в этом бенчмарке поможет нам выяснить, насколько далеки от истины заявления, сделанные NVIDIA сразу же после выпуска Futuremark патча 330, нивелирующего «оптимизации» Detonator FX под 3DMark2003. Как Вы, наверное, помните, представители корпорации попытались обосновать факты заточки драйвера под данный бенчмарк тем, что игровые тесты 3DMark2003, по их мнению, не соответствуют тому, что мы увидим в играх будущего, да и вообще 3DMark2003 преднамеренно разработан таким образом, чтобы дискредитировать их продукты. Давайте посмотрим на предыдущее творение Futuremark, которое не вызвало такой бурной реакции у NVIDIA и отнюдь не было необъективным с их точки зрения.

Итак, для того, чтобы повторить результаты наших тестов, Вам понадобятся:

  • Набор скриптов AntiDetector (работает со всеми доступными на данный момент версиями Detonator FX (44.03, 44.61 и 44.65) под Windows 2000/XP).
  • Поскольку все патч-скрипты представляют собой RTS (RivaTuner Script) файлы, для их запуска Вам также понадобится последняя версия нашей утилиты RivaTuner.

Для инсталляции скриптов Вам потребуется установить и запустить RivaTuner хотя бы один раз для того, чтобы зарегистрировать это приложение в качестве обработчика RTS-файлов. Затем Вы можете просто запустить нужный Вам скрипт из проводника операционной системы и, изменив с его помощью дистрибутив Direct3D-драйвера, просто переустановить пропатченную версию. Этого достаточно, чтобы блокировать в Direct3D-драйвере все найденные нами механизмы распознавания Direct3D-приложений. Итак, давайте посмотрим, изменятся ли результаты 3DMark2001 после инсталляции NVAntiDetector.

Конфигурация тестового стенда:

  • Компьютер на базе AthlonXP:
    • AMD AthlonXP 2100+ Throughbred @ 2400+ (2000MHz, 133MHz FSB);
    • ASUS A7N8X на чипсете NVIDIA nForce2;
    • 512 MB PC2700 DDRAM, CAS2;
    • GeForce4 Ti4600;
    • операционная система Windows XP SP1; DirectX 9.0a;

Результаты, к сожалению, показывают, что ситуация с «оптимизацией» под синтетические бенчмарки явно не нова для NVIDIA, и заточка под 3DMark2003 не является исключением из правил:

Тривиальное блокирование распознавания бенчмарка, как это ни печально, приводит почти к десятипроцентному падению суммарного результата 3DMark2001. Напомним, что программа формирует его по результатам приведённых выше игровых тестов, используя при этом следующую формулу:

3DMark score = (Game1LowDetail + Game2LowDetail + Game3LowDetail) * 10 + (Game1HighDetail + Game2HighDetail + Game3HighDetail + Game4) * 20

Из формулы ясно видно, что добрая половина разницы между оригинальным и модифицированным драйвером, превышающая 600 (20 * (74,6 — 42,8)) баллов, обусловлена почти двукратным падением результатов теста Game4. Давайте попробуем с Вами разобраться, какие же магические действия выполняет драйвер, и что позволяет ему так сильно поднять производительность в случае распознавания данной сцены.

Итак, в первую очередь попробуем понять, что представляет собой тест 4 и в каких местах драйвер мог сэкономить на вычислениях в данном случае. Вероятнее всего, скорость рендеринга сцены Nature должна сильно зависеть от анимированных травы и листьев деревьев, в изобилии присутствующих в кадре и составляющих значительную часть всей геометрии сцены. Именно это и есть главные кандидаты на «оптимизацию», и именно на них, скорее всего, драйвер и экономит ресурсы графического процессора. Чтобы выяснить, насколько наши предположения близки к истине, давайте посмотрим на скриншоты и изучим разницу в качестве изображения между сценой, построенной оригинальным драйвером, и сценой, построенной модифицированным драйвером (ВСЕ ПОЛНОРАЗМЕРНЫЕ СКРИНШОТЫ — в BMP-формате, ПОЭТОМУ ИМЕЮТ РАЗМЕРЫ ПРИМЕРНО 2.5МБАЙТ КАЖДЫЙ, запакованы в RAR-архивы, размер которых от 1.3 до 3 МБайт):

Detonator 44.03 Detonator 44.03 + NVAntiDetector

Казалось бы, сцены абсолютно идентичны. Тем не менее, более внимательное сравнение и последовательное переключение между двумя скриншотами ясно показывает, что и листья, и трава, то есть наши главные кандидаты на «оптимизацию», до и после инсталляции NVAntiDetector расположены на скриншотах просто немного по-разному, точнее просто повёрнуты под другим углом. Для того, чтобы продемонстрировать отличия, мы использовали Adobe Photoshop, попиксельно вычислив разницу между ними (Difference), а затем усилили отличия с помощью функции автоматической регулировки контрастности (Auto Contrast):

Бесспорно, мы не можем сказать, что изображение стало лучше или хуже, — это просто немного другая сцена, с другой геометрией. Ситуация начинает потихоньку проясняться, если вспомнить, что имитирующая эффект ветра анимация листьев и травы в данном тесте реализована с помощью вершинных шейдеров. Собственно, уже даже по этой информации и внешним различиям между скриншотами можно с достаточной степенью вероятности сказать, что, скорее всего, имеют место какие-то махинации с вершинным шейдером. Давайте внимательно посмотрим на код шейдера, использующегося для анимации листьев деревьев в сцене Nature. Сделать это достаточно просто, поскольку 3DMark2001 хранит код шейдеров в текстовом виде в файле data.ras. Итак, посмотрим на tree.vsh:

; vertex shader file for dx8 scene trees
;
; constants:
; c0-c3 view matrix 4x4
; c4 x = time, y = amplitude, z = bias + PI, w = 1.5f
; c5 x = diffuse amplitude, y = diffuse adder, z = 1.0f / 40320.0f, w = 1.0f / 362880.0f
; c6-c8 cam->world rotation 3x3
; c9-c11 world->mesh 4x3
; c12 vecsin ( 1.0f, -1.0f / 6.0f, 1.0f / 120.0f, -1.0f / 5040.0f )
; c13 veccos ( 1.0f, -1.0f / 2.0f, 1.0f / 24.0f, -1.0f / 720.0f )
; c14 pis( PI, 1.0f / (2.0f * PI), 2.0f * PI, 0 )
;
; in:
; v0 diffuse
; v1 uv0
; v2 x = X, y = Y, z = phase1, w = phase2
; v3 xyz = world position, w = 1
;
; out:
; oPos, oD0, oT0


; float sin2 = sin( vd.fPhase2 + fTime * 1.5f );
;
; float fAngle = sin( vd.fPhase1 + C_fTime ) * sin2 * C_fAmplitude + C_fBias;
; float s = sin( fAngle );
; float c = cos( fAngle );
;
; v.x = c * vd.x — s * vd.y;
; v.y = -( s * vd.x + c * vd.y );
; v.z = 0.0f;
;
; v *= C_matrix;
;
; vWorldPos = vd.vWorldPosition;
; vWorldPos *= C_mWorldToObject;
; v += vWorldPos;
;
; vDiffuse = vd.vDiffuse;
; vDiffuse *= sin2 * C_fDiffuseAmplitude + C_fDiffuseAdder;
;
; pPrim->setPosition( i, v );
; pPrim->setDiffuseRGB( i, vDiffuse );


vs.1.0

;;;;;;;;;; sin2 = sin( vd.fPhase2 + fTime * 1.5f );
;;;;;;;;;; 13 instructions
; v2.w = phase2
; c4.x = time
; c4.w = 1.5

mad r0.x, c4.x, c4.w, v2.w

; wrap theta to -pi..pi
mul r0.x, r0.x, c14.y ; divide by 2*PI
expp r5.y, r0.x ; get fractional part only
;;mul r0.x, r5.y, c14.z ; multiply with 2*PI
;;add r0.x, r0.x,-c14.x ; subtract PI
mad r0.x, r5.y, c14.z, -c14.x ; multiply with 2*PI and subtract PI

; compute first 4 values in sin series
mul r5.x, r0.x, r0.x ; r5.x = x2
; r0.x = x
mul r0.y, r0.x, r5.x ; r0.y = x3
mul r0.z, r0.y, r5.x ; r0.z = x5
mul r0.w, r0.z, r5.x ; r0.w = x7
mul r5.x, r0.w, r5.x ; r5.x = x9

; sin 9th order Taylor approximation:
; x — x3 / 6.0f + x5 / 120.0f — x7 / 5040.0f + x9 / 362880.0f;
mul r0, r0, c12
dp4 r0.x, r0, c12.x
mad r0.x, r5.x, c5.w, r0.x

;; now r0.x = sin2

;;;;;;;;;; float fAngle = sin( vd.fPhase1 + C_fTime ) * sin2 * C_fAmplitude + C_fBias;
;;;;;;;;;; 15 instructions
; v2.z = phase1
; c4.x = time

add r1.x, v2.z, c4.x

; wrap theta to -pi..pi
;;add r1.x, r1.x, c14.x ; add PI
mul r1.x, r1.x, c14.y ; divide by 2*PI
expp r5.y, r1.x ; get fractional part only
;;mul r1.x, r5.y, c14.z ; multiply with 2*PI
;;add r1.x, r1.x,-c14.x ; subtract PI
mad r1.x, r5.y, c14.z, -c14.x ; multiply with 2*PI and subtract PI

; compute first 4 values in sin and cos series
mul r5.x, r1.x, r1.x ; r5.x = x2
; r1.x = x
mul r1.y, r1.x, r5.x ; r1.y = x3
mul r1.z, r1.y, r5.x ; r1.z = x5
mul r1.w, r1.z, r5.x ; r1.w = x7
mul r5.x, r1.w, r5.x ; r5.x = x9

; sin 9th order Taylor approximation:
; x — x3 / 6.0f + x5 / 120.0f — x7 / 5040.0f + x9 / 362880.0f;
mul r1, r1, c12
dp4 r1.x, r1, c12.x
mad r1.x, r5.x, c5.w, r1.x

; r1.x = sin( vd.fPhase1 + C_fTime )
; r0.x = sin2
; c4.y = amplitude
; c4.z = bias

mul r1.x, r1.x, r0.x
mad r1.x, r1.x, c4.y, c4.z

; now r1.x = fAngle

;;;;;;;;;; float s = sin( fAngle );
;;;;;;;;;; float c = cos( fAngle );
;;;;;;;;;; 16 instructions

; wrap theta to -pi..pi
mul r1.x, r1.x, c14.y ; divide by 2*PI
expp r2.y, r1.x ; get fractional part only
;;mul r1.x, r2.y, c14.z ; multiply with 2*PI
;;add r1.x, r1.x,-c14.x ; subtract PI
mad r1.x, r2.y, c14.z, -c14.x ; multiply with 2*PI

; compute first 4 values in sin and cos series
mov r2.x, c12.x ; theta^0 ;r2.x = 1
mul r2.y, r1.x, r1.x ; theta^2
mul r1.y, r1.x, r2.y ; theta^3
mul r2.z, r2.y, r2.y ; theta^4
mul r1.z, r1.x, r2.z ; theta^5
mul r2.w, r2.y, r2.z ; theta^6
mul r1.w, r1.x, r2.w ; theta^7

; sin
mul r1, r1, c12
dp4 r1.x, r1, c12.x

; cos
mul r2, r2, c13
dp4 r2.x, r2, c12.x

; now r1.x = s, r2.x = c

;;;;;;;;;;; v.x = c * vd.x — s * vd.y;
;;;;;;;;;;; v.y = -( s * vd.x + c * vd.y );
;;;;;;;;;;; v.z = 0.0f;
;;;;;;;;;; 5 instructions

; !!!!!!!!! use 2x2 matrix (dp3) for this?

; r1.x = s
; r2.x = c
; v2.x = vd.x
; v2.y = vd.y

mul r3.x, v2.x, r2.x
mad r3.x, v2.y, -r1.x, r3.x

mul r3.y, v2.x, -r1.x
mad r3.y, v2.y, -r2.x, r3.y

mov r3.z, c14.w

; now r3.xyz = v

;;;;;;;;;;;; v *= C_matrix;
;;;;;;;;;; 3 instructions
; r3.xyz = v
; c6-c8 = C_matrix

dp3 r4.x, c6, r3
dp3 r4.y, c7, r3
dp3 r4.z, c8, r3

; now r4.xyz = v

;;;;;;;;;;;;; vWorldPos = vd.vWorldPosition;
;;;;;;;;;;;;; vWorldPos *= C_mWorldToObject;
;;;;;;;;;;;;; v += vWorldPos;
;;;;;;;;;; 5 instructions
; v3 = vd.vWorldPosition
; c9-c11 = C_mWorldToObject

;dp4 r3.x, c9, v3
;dp4 r3.y, c10, v3
;dp4 r3.z, c11, v3
;add r4.xyz, r4.xyz, r3.xyz
add r4.xyz, r4.xyz, v3.xyz
mov r4.w, c12.x

; now r4 = v

;;;;;;;;;;;;; pPrim->setPosition( i, v );
;;;;;;;;;; 6 instructions

m4x4 oPos, r4, c0

;;;;;;;;;;;;; vDiffuse = vd.vDiffuse;
;;;;;;;;;;;;; vDiffuse *= sin2 * C_fDiffuseAmplitude + C_fDiffuseAdder;
; r0.x = sin2
; c5.x = diffuse amplitude
; c5.y = diffuse adder

mad r1.w, r0.x, c5.x, c5.y
mul oD0.xyz, v0.xyz, r1.www

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;; 2 instructions

mov oT0.xy, v1.xy

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

Чтобы убедиться в правомерности своих предположений и доказать тот факт, что каким-то изменениям подвергается именно отвечающий за анимацию листьев вершинный шейдер, мы попробовали незначительно изменить его код, дабы нивелировать его возможное распознавание и подмену драйвером. Сделать это, к счастью, достаточно просто, зная некоторые недокументированные особенности 3DMark2001. Дело в том, что движок данного бенчмарка может читать необходимые данные как из файловой системы, так и из своего собственного файла-хранилища data.ras, причём файловая система имеет более высокий приоритет. Грубо говоря, создав файл tree.vsh в подпапке .\data\vsh в папке 3DMark2001, мы можем заставить бенчмарк использовать наш собственный вершинный шейдер вместо того, который находится в хранилище data.ras. Именно так мы и поступили, создав по указанному выше пути файл tree.vsh с приведённым выше содержимым. Как и следовало ожидать, его самое незначительное изменение (а именно не влияющая на функциональность вставка инструкции nop в произвольном месте либо простая замена версии с 1.0 на 1.1), возвращала листья на скриншоте на свои места, значительно снижая при этом производительность.

Честно говоря, после всего увиденного становится очень грустно. Сразу вспоминается выход Detonator 40.xx и золотые горы, обещаемые NVIDIA вместе с его анонсом. Вспоминается и анонсированная двадцатипятипроцентная прибавка в производительности шейдеров в данной версии драйверов, в качестве доказательства которой нам гордо демонстрировался результат Nature из 3DMark2001. Как это ни прискорбно, но после инсталляции NVAntiDetector результаты в этом тесте снова вернулись обратно на своё место, вплотную приблизившись к результатам Detonator 30.xx и сведя на нет весь прирост от якобы имевших место шейдерных оптимизаций.

ATI

К сожалению, нам сразу вспомнился и ещё один магический прирост в этом же самом тесте, имевший место вскоре после анонса графического процессора R200. Однако на этот раз, прирост был в драйверах конкурента NVIDIA, канадской компании ATI. В поисках истины, мы решили проверить на честность и их драйвера, а именно попробовать отыскать и блокировать заточки под бенчмарки в последней версии драйверов Catalyst. Сразу отметим, что позиция ATI в скандале с 3DMark2003 бесспорно выглядела на порядок честнее по отношению к покупателям, чем позиция NVIDIA, и не могла не вызывать уважения. Именно поэтому исследование драйверов ATI также было бы очень интересным, поскольку оно могло помочь нам понять, насколько далеки от истины публичные заявления представителей отдела программного обеспечения ATI. Хотя нам и очень хотелось верить в правдивость их слов, к сожалению, слишком уж наивно выглядели наигранно эмоциональные заявления о том, что хотя компания никогда не использовала заточек такого рода под бенчмарки ранее, теперь ситуация изменится, поскольку они начнут играть по правилам конкурента.

Итак, давайте посмотрим, как обстоят дела с другой стороны, и заглянем внутрь драйверов Catalyst. Не имея первоначальной точки отсчёта для поисков, как это было в случае с Detonator'ом, мы тем не менее решили наудачу проверить ati3duag.dll, — унифицированный Direct3D-драйвер для чипов на ядре R300 и старше. Анализу подверглись обработчики некоторых D3DDP2OP_... токенов, наиболее вероятных для реализации детекта приложений. И, как оказалось, не зря. Даже поверхностный анализ кода в поисках чего-либо подозрительного выявил c десяток мест в коде, которые, к сожалению, могут быть идентифицированы только как попытки распознавания тех или иных Direct3D-приложений. Так же, как и в драйверах NVIDIA, мы обнаружили в драйверах ATI проверки кода шейдеров в момент их создания. Программисты ATI подошли к этому вопросу проще, обойдясь без каких-либо контрольных сумм и прошив токенизированный код распознаваемых шейдеров прямо в тело драйвера. Однако количество распознаваемых пиксельных шейдеров, абсолютно несоизмеримое с тем, что мы видели в драйверах NVIDIA, нас приятно удивило: их в теле драйвера прошито всего три, причём два из них (версии 2.0), скорее всего, относятся к «оптимизациям», обнаруженным Futuremark при выпуске патча 330 для 3DMark2003. Если смириться с тем фактом, что мы могли пропустить какие-либо места в коде и считать, что нам удалось обнаружить все заточки такого рода, то остаётся только порадоваться за ATI — шейдерных заточек в их драйвере явно на порядок меньше, чем у конкурента.

Кроме этого, Catalyst также содержит поиски паттернов текстур в обработчике D3DDP2OP_TEXBLT, попытки распознать приложение по последовательному созданию драйвером текстур определённого формата и размера, а также другие приёмы, которые могут быть однозначно интерпретированы не иначе, как распознавания Direct3D-приложений. Отметим, что практически все обнаруженные нами процедуры распознавания приложений работали сходным образом, а именно возвращали уникальный идентификатор, если запущенное Direct3D-приложение подходило под их критерий распознавания, либо возвращали ноль в противном случае. Вызов каждой из этих процедур приводил к инициализации внутренней переменной драйвера значением, уникально идентифицирующим распознанное драйвером Direct3D приложение. Исходя из этого, мы построили скрипт ATIAntiDetector на принципе блокирования всех найденных нами попыток инициализации данной переменной, также лишив драйвер возможности идентификации запущенного приложения. Кроме того, мы исказили скриптом первые восемь байт каждого из двух распознаваемых драйвером пиксельных шейдеров версии 2.0, поскольку их механизм распознавания, в отличие от остальных обнаруженных, приводил не к инициализации внутренней переменной драйвера, а к реальной подмене их кода.

Итак, давайте посмотрим, как изменится производительность при инсталляции драйвера Catalyst, обработанного скриптом ATIAntiDetector. Для экспериментов мы использовали тот же самый тестовый стенд (его конфигурацию вы можете увидеть выше) и видеоадаптер RADEON 9500 64MB с разблокированными свойствами RADEON 9500PRO:

Тот же самый 3DMark2001, тот же самый Game test 4… От отключения кода распознавания приложения, как это ни печально, также пострадал тест Nature. Давайте попробуем разобраться, на чём экономит ATI, и посмотрим на качество изображения (ВСЕ ПОЛНОРАЗМЕРНЫЕ СКРИНШОТЫ — в BMP-формате, ПОЭТОМУ ИМЕЮТ РАЗМЕРЫ ПРИМЕРНО 2,5 МБ КАЖДЫЙ, запакованы в RAR-архивы, размер которых от 1,3 до 3 МБ):

Catalyst 3.4 Catalyst 3.4 + ATIAntiDetector

Как и в случае с NVIDIA, мы видим практически идентичные с первого взгляда сцены. Однако давайте посмотрим на разницу между скриншотами повнимательнее. Как и ранее, для того, чтобы продемонстрировать отличия, мы использовали Adobe Photoshop, попиксельно вычислив разницу между ними (Difference), а затем усилили отличия с помощью функции автоматической регулировки контрастности (Auto Contrast):

Дежа-вю? Области различий до боли напоминают нам то, что мы уже видели ранее при анализе «оптимизаций» драйверов NVIDIA. Совершенно очевидно, что ATI также «подтягивает» результаты за счет альтернативного рендеринга травы и листьев деревьев — то есть объектов, которыми изобилует сцена и именно рендеринг которых, судя по всему, является бутылочным горлышком данного теста. Однако в отличие от того, что мы видели на картах NVIDIA, характер различий между скриншотами тут несколько иной. Судя по всему, в данном случае упрощения имеют место не на стадии трансформации и освещения, а непосредственно в процессе текстурирования полигонов. Учитывая, что разница наиболее заметна на границах листьев, можно с достаточно высокой степенью уверенности предположить, что драйвер, распознав сцену Nature, подменяет формат текстур травы и листьев на более выгодный, сэкономив на разрядности альфа-канала, либо принудительно компрессируя несжатую прозрачную текстуру, либо переупаковывая DXT3 текстуры в формат DXT1. Чтобы проверить, насколько наша гипотеза близка к истине, мы попробовали поэкспериментировать с настройками формата текстур в самом 3DMark2001, анализируя внешний вид листьев и травы при выборе разных форматов. К сожалению, как и следовало ожидать, в конце концов, при выборе 16-битных текстур, аномалии не замедлили проявить себя в виде резкого падения производительности и серьёзного отставания даже от вдвое более требовательного к ресурсам 32-битного формата. Чтобы довести вопрос до логического завершения, мы попробовали методом перебора идентифицировать то место в коде, которое используется для распознавания сцены Nature в 3DMark2001. Результат полностью подтвердил наши опасения: Catalyst производил распознавание данной сцены именно по последовательному созданию Direct3D-приложением трёх текстур определённого формата и размера.

В заключение анализа драйвера Catalyst мы также решили проверить внутренности ati3d2ag.dll — Direct3D-драйвера для процессоров R200, поскольку именно благодаря этому чипу мы и пришли к мысли о возможном существовании заточек в драйвере ATI. Итак, заглянув в код, мы так же, как и в драйвере ati3duag.dll, увидели многочисленные попытки распознавания приложений, причём даже в большем количестве, чем в предыдущем. Даже не создавая патч-скрипта, мы наудачу выполнили для него такую же точно проверку, как и для карт NVIDIA, а именно, внесли незначительные изменения в код шейдера tree.vsh. Результат на той же самой системе с видеоадаптером RADEON 8500LE, к сожалению, очень и очень печален: почти тридцатипроцентное (с 42 до 31 FPS) падение производительности в том же самом злополучном game test 4. Снова Nature? Cнова дежа-вю? Снова «оптимизированные» вершинные шейдеры? Комментарии, как говорится, излишни.

Итак, давайте подведём итог. Наверняка, у многих из наших читателей возникает вполне резонный вопрос: «А что же, собственно, плохого в том, что обе компании стараются повысить производительность в 3DMark? Тем более, если мы совсем не видим разницы в качестве, как, например, в случае с Game test 2 — Dragothic и драйверами Detonator FX? К тому же, даже если разница и заметна, как, например, в сцене тесте Game test 4 — Nature, то ведь нельзя сказать, что изображение хотя бы немного ухудшилось. Что плохого в таком подходе?»

Ответ достаточно прост: во-первых, все обнаруженные и блокированные нами "оптимизации" активизируются в одном единственном бенчмарке, что само по себе является знаком того, что программисты провели достаточное количество времени, анализируя его поведение и специфику, выявляя узкие места того или иного теста и анализируя части сцены, на которых можно сэкономить, пусть даже и без ущерба качеству изображения. Однако любые активизируемые только при распознавании бенчмарка оптимизации практически со стопроцентной гарантией основаны на тщательном анализе его специфики и очень и очень жёстко привязаны к конкретной сцене, то есть, грубо говоря, просто искажают смысл существования бенчмарков, не отражая реальной производительности в большинстве других приложений. Вспомним, что синтетические бенчмарки предназначены не для того, чтобы создатели драйверов могли посоревноваться в умении максимально эффективно отрендерить заранее известный им кадр путём отброса того или иного заведомо невидимого объекта или, ещё хуже, путём использования альтернативных процедур рендеринга. Бенчмарки преследуют одну единственную цель: оценить скорость выполнения видеоадаптером строго детерминированного набора вычислений. Замечательно, если создатели драйвера, знающие особенности своих чипов на порядок лучше сторонних разработчиков программного обеспечения, в состоянии заменить неэффективно написанный шейдер, и пусть даже путём незначительных изменений в качестве результирующего изображения могут в несколько раз поднять скорость его исполнения. Хотя мы и не приветствуем такие вещи, мы, пожалуй, согласимся, что "оптимизации" такого рода в некоторых случаях имеют право на жизнь, но только и только в игровых приложениях. Тем не менее, использование таких "оптимизаций", на наш взгляд, абсолютно недопустимо в тестовых приложениях, предназначенных исключительно для оценки производительности. В противном случае использование бенчмарка для сравнения производительности видеоадаптеров разных производителей просто не будет иметь никакого смысла, поскольку мы будем сравнивать скорость выполнения ими абсолютно несоизмеримых объёмов вычислений.

Выводы

Давайте подумаем, какие же выводы мы можем сделать из всего этого? К сожалению, очень неутешительные. Во-первых, сразу хочется спросить Futuremark, где они были до этого и почему принципиальная позиция по борьбе с читингом в их продуктах появилась только сейчас. Не знали раньше, что вендоры делают агрессивные заточки под их бенчмарк? Вряд ли. Не было предпосылок? Были. Вспомним хотя бы ситуацию с отключением заставок в Detonator или печально известные драйверы SiS Xabre. Не хватало квалификации, чтобы локализовать и устранить "заточки"? Не верим. Даже мы, не имея исходного кода 3DMark2001, потратили на поиск и блокирование заточек в драйверах двух крупнейших вендоров не более суток. Очень не хочется думать, что участие в бета-программе автоматически даёт мандат на такого рода "оптимизации", но, к сожалению, это единственная мысль, которая приходит нам в голову. Искренне надеемся, что мы ошибаемся.

Во-вторых, очень и очень грустно смотреть на поведение вендоров, c завидным упрямством пытающихся обогнать друг друга не совсем честными путями. Давайте посмотрим на хронологию "оптимизаций" нашего злополучного Nature. Итак: анонс и выход R200 — критика производительности шейдеров R200 на основе сравнения результатов данного теста с результатами NV20 — появление "оптимизаций" Nature в драйверах ATI. Выход R300 и такое же точно искусственное подтягивание "за уши" производительности в Nature — ответный удар NVIDIA в виде Detonator 40.xx, поднявшего скорость в том же самом злополучном тесте якобы за счёт возросшей производительности шейдеров… И, к сожалению, нет и не будет ни конца ни краю этой гонке. Очень горько наблюдать и за направленными на улучшение своего имиджа пафосными заявлениями обоих конкурентов в моменты поимки с поличным их оппонента. Не замедлила покрасоваться в выгодном свете в скандале с Quake/Quack NVIDIA, заявив, что они никогда не используют и не использовали оптимизаций под конкретные приложения, пропагандируя в вопросе оптимизации только и только общие подходы. Не замедлила сыграть на скандале с 3DMark2003 и ATI, заняв позицию обиженного ребёнка и делая диаметрально противоположные заявления, то покаявшись и обещая убрать свои оптимизации, клятвенно заверив, что их программисты никогда не используют каких-либо упрощений в рендеринге для повышения результатов в бенчмарке, то обещая также "оптимизировать" драйвера, дабы не отставать от нечестивого конкурента. Грустно? Нам тоже. Всё ещё верите в то, что на рынке хоть кто-то играет честно? Мы тоже верили, и у нас до сих пор ещё теплится надежда на то, что хоть кто-то из вендоров будет умнее и первым прекратит эту нечестную гонку. К сожалению, это только мечты… Пока же — добро пожаловать в реальность. Финальный результат популярных бенчмарков давно имеет достаточно мало общего с реальной производительностью. Как это ни печально, и для ATI, и для NVIDIA объёмы продаж чуть ли не напрямую зависят от количества этих самых злополучных баллов. И повышать их, к сожалению, будут любой ценой. Есть ли у нас шанс когда-нибудь увидеть честные сравнения в бенчмарках? Пожалуй, только в двух случаях: либо когда на рынке останется только один игрок, либо когда один из конкурентов будет откровенно слабее и подтасовка результатов ни с той, ни с другой стороны просто не будет иметь смысла. Пока же есть два близких по параметрам графических процессора от конкурирующих производителей, и у каждого из них есть и будет соблазн лёгкого выхода на первое место. А мы, как это ни грустно, будем видеть в бенчмарках не реальную производительность видеоадаптеров, а скорее соревнование между разработчиками драйверов противоборствующих сторон, старающихся "оптимизировать" код и выжать из бенчмарка как можно большее количество баллов любой ценой, пусть даже альтернативным рендерингом.

Что будет дальше? Будучи хорошо знакомыми с реакцией обоих конкурентов на не слишком приятные для них патч-скрипты и помня деструктивные методы, использованные и NVIDIA, и ATI для защиты против SoftQuadro и SoftR9x00, мы можем почти со стопроцентной гарантией предсказать, что обе противоборствующие стороны попробуют буквально в следующей версии драйверов доказать Вам, что скрипты работают не совсем правильно и блокируют какой-то жизненно важный кусок кода. Скорее всего, NVIDIA распределит все существующие проверки по всему телу Direct3D драйвера, дабы максимально затруднить их локализацию и блокирование, а процедуру расчёта контрольной суммы, модифицируемую скриптом NVAntiDetector, использует для проверки контрольной суммы по фиксированному зашитому в теле драйвера паттерну и, в случае её несовпадения с оригиналом, будет имитировать какие-то проблемы.

Исчезнут ли механизмы распознавания Direct3D-приложений в драйверах Detonator и Catalyst? Очень маловероятно. Судя по результатам тестирования драйверов, вышедших уже после скандала с 3DMark2003, обе компании не намерены отступать от своих позиций. Так, новые драйвера NVIDIA, Detonator FX 44.6x, уже научились распознавать все изменения кода шейдеров, сделанные Futuremark в патче 330, и даже повысили производительность по сравнению с тем, что было до выхода патча. Сломив позиции Futuremark, NVIDIA, по-видимому, решила придерживаться тактики агрессивных оптимизаций и далее, наплевав на крайне негативное отношение большинства пользователей к вещам такого рода. Тем не менее, скрипт NVAntiDetector по-прежнему вызывает катастрофическое падение производительности в данном бенчмарке на флагмане GeForceFX 5900Ultra. ATI также выпустили на днях новый билд драйверов Catalyst, в котором действительно убраны две обнаруженные Futuremark оптимизации под 3DMark2003, и о чём гордо заявляется в сопровождающем драйвер пресс-релизе, однако заточки под предыдущий бенчмарк, само собой, остались на своём месте. Похоже, компания действует по принципу «не пойман — не вор», а благородный жест по блокированию нежелательных оптимизаций затрагивал и будет затрагивать только те заточки, в которые компанию непосредственно ткнули носом. Браво, очень удобно быть частично честным. И волки сыты, и овцы целы.

Очень хочется верить, что этот обзор хоть каким-то образом повлияет на отношение производителей видеоадаптеров к потребителям. Нам кажется, что в свете всех событий последних месяцев пора понять, что есть вещи, которые на порядок важнее результатов 3DMark: а именно репутация и доверие пользователей, которые очень тяжело зарабатываются годами и очень легко разрушаются в один момент. Мы же, в заключение, скажем, что очень надеемся на то, что хоть в чём-то ошиблись в данном исследовании и ситуация не так плачевна на самом деле, хотя, честно говоря, вероятность этого катастрофически мала. Поэтому мы будем очень рады любым замечаниям и поправкам от всех затронутых в данном исследовании сторон. Отметим только, что во время подготовки данного материала к публикации с нами связывались представители ATI, заверив нас, что хотя обнаруженные фрагменты кода и служат для распознавания тех или иных приложений, предназначено это исключительно для обхода проблем самих приложений. Тем не менее, прямой вопрос о ситуации с 3DMark2001, как и следовало ожидать, остался без ответа…




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

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

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

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