"Оптимизация" 3DMark'а ведущими разработчиками видеочипов - |
30.06.2003 |
Казалось бы, ещё не успели утихнуть страсти вокруг 3DMark2003 и драйверов Detonator FX, как в сети с подачи www.tech-report.com появилась информация об обнаружении новых 'заточек' под этот бенчмарк в том же самом печально известном Detonator FX, а именно об агрессивной оптимизации анизотропной фильтрации, критерием активизации которой являлось имя запускаемого приложения. Более того, в свете этих же событий обновился и ShaderMark от ToMMTi-Systems, незначительная модификация кода шейдеров которого также вызвала ощутимое падение производительности, фактически выявив заточку Detonator FX и под него. Увидев, что 'охота на ведьм' начинает приобретать массовый характер, мы также заинтересовались данным вопросом и решили провести своё независимое внутреннее расследование. Однако мы прекрасно понимаем, что для выявления фактов подтасовки драйвером результатов того или иного бенчмарка со стороны приложения в девяноста девяти процентах случаев просто необходимо непосредственное участие его разработчиков. Более того, даже сам разработчик очень часто не в состоянии отследить и блокировать 'заточку', поскольку, как мы убедимся позже, в драйверах зачастую используются слишком изощрённые методы распознавания того или иного приложения. Именно поэтому мы решили пойти другим путём и взглянуть на данную проблему с противоположной стороны, а именно проанализировать драйвер изнутри и попытаться локализовать и блокировать все распознавания того или иного Direct3D приложения, лишив драйвер возможности подстройки под него и, соответственно, сведя риск подтасовки результатов бенчмарка к минимуму.
Для инсталляции скриптов Вам потребуется установить и запустить RivaTuner хотя бы один раз для того, чтобы зарегистрировать это приложение в качестве обработчика RTS файлов. Затем Вы можете просто запустить нужный Вам скрипт из проводника операционной системы и, изменив с его помощью дистрибутив Direct3D драйвера, просто переустановить пропатченную версию. Этого достаточно, чтобы блокировать в Direct3D драйвере все найденные нами механизмы распознавания Direct3D приложений. Итак, давайте посмотрим, изменятся ли результаты 3DMark2001 после инсталляции NVAntiDetector. Конфигурация нашего тестового стенда показана ниже:
Результаты, к сожалению, показывают, что ситуация с 'оптимизацией' под синтетические бенчмарки явно не нова для NVIDIA, и заточка под 3DMark2003 не является исключением из правил: |
|
Тривиальное блокирование распознавания бенчмарка, как это ни печально, приводит почти к десятипроцентному падению суммарного результата 3DMark2001. Напомним, что программа формирует его по результатам приведённых выше игровых тестов, используя при этом следующую формулу:
Казалось бы, сцены абсолютно идентичны. Тем не менее, более внимательное сравнение и последовательное переключение между двумя скриншотами ясно показывает, что и листья и трава, а именно наши главные кандидаты на 'оптимизацию', до и после инсталляции 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, тот же самый Game test 4… От отключения кода распознавания приложения, как это ни печально, также пострадал тест Nature. Давайте попробуем разобраться, на чём экономит ATI, и посмотрим на качество изображения:
Как и в случае с NVIDIA, мы видим практически идентичные с первого взгляда сцены. Однако давайте посмотрим на разницу между скриншотами повнимательнее. Как и ранее, для того, чтобы продемонстрировать отличия, мы использовали Adobe Photoshop, попиксельно вычислив разницу между ними (Difference), а затем усилили отличия с помощью функции автоматической регулировки контрастности (Auto Contrast):
Дежа-вю? Области различий до боли напоминают нам то, что мы уже видели ранее при анализе 'оптимизаций' драйверов NVIDIA. Совершенно очевидно, что ATI также 'подтягивает' результаты за счет альтернативного рендеринга травы и листьев деревьев - то есть объектов, которыми изобилует сцена и именно рендеринг которых, судя по всему, является бутылочным горлышком данного теста. Однако в отличие от того, что мы видели на картах NVIDIA, характер различий между скриншотами тут несколько иной. Судя по всему, в данном случае упрощения имеют место не на стадии трансформации и освещения, а непосредственно в процессе текстурирования полигонов. Учитывая, что разница наиболее заметна на границах листьев, можно с достаточно высокой степенью уверенности предположить, что драйвер, распознав сцену Nature, подменяет формат текстур травы и листьев на более выгодный, сэкономив на разрядности альфа-канала либо принудительно компрессируя несжатую прозрачную текстуру, либо переупаковывая DXT3 текстуры в формат DXT1. Чтобы проверить, насколько наша гипотеза далека от реальности, мы попробовали поэкспериментировать с настройками формата текстур в самом 3DMark2001, анализируя внешний вид листьев и травы при выборе разных форматов. К сожалению, как и следовало ожидать, в конце концов, при выборе 16-битных текстур, аномалии не замедлили проявить себя в виде резкого падения производительности и серьёзного отставания даже от вдвое более требовательного к ресурсам 32-битного формата. Чтобы довести вопрос до логического завершения, мы попробовали методом перебора идентифицировать то место в коде, которое используется для распознавания сцены Nature в 3DMark2001. Результат полностью подтвердил наши опасения: Catalyst производил распознавание данной сцены именно по последовательному созданию Direct3D приложением трёх текстур определённого формата и размера. Николайчук Алексей a.k.a. Unwinder (AlexUnwinder@mail.ru) |
|||||||||