Всем привет!
В данной статье рассматриваются вопросы доступа к свойствам графических адаптеров на языке ассемблера. Мы заправим свои мозговые баки топливом DirectX смешав его в должной мере с OpenGL, заглянем в современную модель драйверов видеоадаптеров Vista+, ознакомимся с базовыми терминами графической инфраструктуры, и смешаем весь этот коктейль с исполняемым кодом. Основной акцент делается на способ вычисления объёма локальной видеопамяти VRAM свыше 4 Gb, что является проблематичным на 32-битных системах Windows.
Оглавление:
1. Интерфейсы DirectX и OpenGL;
2. Модель драйверов дисплея WDDM;
3. Доступ к инфраструктуре DXGI;
4. Методы и функции D3DKMT_xx;
5. Практика – чтение данных видеоадаптера;
6. Итоги.
-----------------------------------------
1. Интерфейсы DirectX и OpenGL
Словосочетание DirectX знакомо большинству из нас, но осмелюсь предположить, что не все смогут дать ему точное определение. Чтобы далее не плавать в терминах, сразу определимся с основными понятиями графической подсистемы. Это огромный клубок, размотать который под силу не каждому, и по большому счёту в представленном на ваш суд материале я не преследовал эти цели. Мы пробежимся лишь по макушкам, чтобы перед глазами выстроилась хоть какая-то картина.
Microsoft DirectX – это набор функций прикладного уровня Win32API, для обработки связанных с графикой и мультимедиа задач. Впервые был представлен миру в 1995 году, в штатной поставке Win95. Тогда названия этих API начинались с префикса Direct типа: Direct3D, DirectDraw, Sound, Play, Music и т.д. Бытует мнение, что на презентации один из журналистов высмеял такой зоопарк имён, и сходу предложил термин DirectX, как обобщённый класс всех этих API. В средствах массовой (дез)информации, API трёхмерной графики Direct3D наиболее разрекламированный компонент, а потому названия DirectX и Direct3D стали синонимами.
Наряду с проприетарным DirectX, ещё в то время активно продвигалась и альтернативная технология OpenGL (Open Graphics Library) от компании "Khronos Group" 1994 г. Так разработчикам был представлен выбор – OpenGL или DirectХ, и началась борьба между сторонниками кроссплатформенного GL (Win, Linux, Mac), и DХ3D исключительно для Win. На практике-же два этих интерфейса часто пересекались, поскольку OpenGL не включает в себя все функции DirectX, типа поддержка звука или джойстика. Таким образом DХ3D и GL – это конкурирующие интерфейсы пользовательских API, хотя всегда ходят парой:
Первая версия(5) DХ3D вызвала широкий резонанс и разочаровала программистов. Даже простые операции в ней требовали ручного создания и отправки соответствующих примитивов в буферы исполнения. В OpenGL-же, для смены состояния графического объекта достаточно было вызвать одну\единственную функцию. Другими словами, то-что представлялось в GL одной строкой, требовало чуть-ли не сотни строк кода DX3D. Так пузырь DirectХ на старте оглушительно лопнул, оставив за собой кратер, который можно было наблюдать с луны.
Но отбиваясь от града тухлых яиц M$ продолжала гнуть свою линию и развивать DX3D-API, в результате чего начиная с DX9 бывшие критики признали, что теперь он ничуть не хуже чем OpenGL по простоте использования. DirectХ в некоторых случаях преследует политику модели компонентных объектов COM, а поскольку директивы ассемблера не поддерживают её в полной мере, это выводит нас из зоны комфорта и приходится реализовать всё вручную. Как мы убедимся позже в практической части, СОМ в асме не так-уж страшен, как его малюют – главное иметь документацию, чем мелкософт наградил нас в своём репозитории.
1.1. Графический процессор GPU – общие сведения
GPU в режиме 3D работают с т.н. "полигональной графикой", когда любой объект представляется как набор миниатюрных 2D-треугольников. Графический объект задается "вершинами" и "полигонами" (боковые линии треугольников). Цвет на полигоны накладывается по спец.алгоритмам закраски, с использованием заранее подготовленных плоских изображений "текстур". Шейдеры (shader) – это программные процедуры видеоадаптера для наложения текстур, преломления света, затенения участков объектов и множества других параметров. Задача GPU сводится к тому, чтобы закрасить как можно больше полигонов за единицу времени. Ускорители в графических 3D-адаптерах (Accelerator) в первую очередь были созданы для аппаратного ускорения работы с полигонами.
В архитектуре видеоадаптеров имеется отдельно вершинный, и отдельно пиксельный процессор. Первый – вычислив вершины входящих в состав объекта треугольников и уцепившись за них, озадачен передвижением графических объектов вверх\вниз, влево\вправо. Манипулируя полигонами, вершинный процессор, например, может менять мимику графического персонажа (вертикальная ось), поворачивать объекты вокруг своей оси (горизонт) и т.д.
Второй-же, пиксельный процессор растеризует графику, подготавливая её к "рендерингу" – процессу преобразования 3D модели в плоское 2D-изображение, пригодное для вывода на дисплей (заключительный этап). После наложения текстуры на объект, в пиксельном процессоре происходит запись значений в "буфер кадра" – отображаемую на дисплее область графической памяти. На данном этапе выполняются и доп.операции, типа гамма-коррекция, сжатие Z-координат etc. Полная длина конвейера пиксельного процессора впечатляет – это порядка 200 ступеней, большая часть из которых отведена под операции обработки текстур.
1.2. Память процессоров GPU
Процессоры дискретных (внешних) графических адаптеров снабжены индивидуальной оперативной памятью VRAM (Video Random Access Memory), и при её ограниченном объёме могут запрашивать доп.память у системы. Подобного рода окна в системном ОЗУ назвали "апертурой", для отображения которой в память видеокарты используются таблицы GART (Graphics Address Remapping Table, перераспределение адресов). Адрес апертуры BIOS задаёт через специальные регистры чипсета, а её размер определяется параметром "AGP aperture size":
Апертуры часто практиковали карты с отжившим свой век интерфейсом AGP (Accelerated Graphics Port). Порт поддерживал работу в двух режимах: [1] DiME (Direct Memory Execute) когда код графики исполнялся прямо внутри системной памяти без копирования её в память адаптера, и [2] режим DMA (Direct Memory Access, прямой доступ к памяти без участия процессора), когда акселератор 3D при вычислениях рассматривал свою\локальную память как первичную, а если её недостаточно, то подкачивал в неё данные из апертуры системной памяти.
В наше время порт AGP уже давно не используется, и его место занял более высокоскоростной PCI-Express, который работает только в режиме DMA. Однако чипсеты Intel для встроенных карт IGD (Integrated Graphics Device) "воруют" память у системы и по сей день, только технология называется теперь
2. Модель драйверов дисплея WDDM
Теперь, когда у нас есть базовые сведения, переместимся по туннелям времени в современный мир.
Начиная с Windows Vista, графическая подсистема претерпела огромные изменения. В частности это коснулось задействованной модели драйверов, которую назвали WDDM или "Windows Display Driver Model". Входящая в состав WDDM инфраструктура DXGI (DirectX Graphics Infrastructure) – это компонент пользовательского режима для Vista+, который связывает конкретные API DirectX и OpenGL c библиотеками WDDM.
DXGI предоставляет API-функции и методы СОМ для решения таких задач как: перечисление графических адаптеров и мониторов, запроса их свойств и режимов отображения, выбора форматов выходных буферов, и визуализации кадров на дисплее. Для перехода на модель драйвера WDDM производители адаптеров должны написать совершенно иные драйверы мини-порта дисплея. В отличии от устаревшей модели ХР.хDDM, усовершенствованный драйвер WDDM находится в пользовательском пространстве, и большая его часть сосредоточена в библиотеке dxgi.dll.
Модель не использует сервисы движка Gdi32.dll (Graphics Device Interface), а проходит через среду выполнения runtime d3d10.dll и подсистему графического ядра Dxgkrnl.sys. Версия[1] DXGI требует DirectХ v10 и выше – цель его заключается во взаимодействии с драйвером режима ядра и аппаратным обеспечением, как показано на следующей схеме:
Рассмотрим ключевые компоненты этого рисунка:
3. Доступ к интерфейсу DXGI
Поговорим теперь о программном доступе к инфраструктуре DXGI..
Выше упоминалось, что одноимённая библиотека dxgi.dll содержит в себе как методы СОМ, так и привычные нам функции Win32API. Причём методы компонентных объектов предлагают более широкие возможности по управлению и сбору информации об оборудовании – с них и начнём. Не смотря на то, что процесс этот скучный, и чем-то напоминает супружеский долг после 30 лет совместного проживания, результат вполне оправдывает вложенный депозит, открывая нам доступ в нёдра модели драйверов WDDM.
Что такое СОМ и как с ним работать на асме подробно обсуждалось в статье про дизассемблер, а потому не буду повторяться – мы сделаем ставку на СОМ конкретно взятого DXGI. Структура входящих в его состав интерфейсов с привязанными к ним глобальными идентификаторами GUID выглядит так, где основными являются пара "IID_IDXGI_Factory", а остальные наследуются от них. Более подробную информацию о фабриках DXGI можно найти на
Первое, что нужно сделать, это функцией CoInitialize() из ole32.dll активизировать системный СОМ-сервер, после чего при помощи CreateDXGIFactory1() из dxgi.dll получить указатель на основной интерфейс "IID_IDXGI_Factory1". Так мы получим доступ к виртуальной таблице методов интерфейса "IDXGI_Factory", среди которых нас будет интересовать метод перечисления видеоадаптеров EnumAdapters().
Как видим, структура интерфейса "IDXGI_Factory" (представляющая собой вирт.таблицу vTable) включает в себя две предыдущие структуры "IUnknown+IDXGI_Object", которые я обозначил здесь как поле "Header". Более того, любой интерфейс DXGI начинается с такого заголовка, а первым методом в них всегда будет метод перечисления содержимого QueryInterface(). Таким образом, чтобы найти указатель на метод EnumAdapters(), нужно проделать следующие шаги:
Поскольку меня интересует информация о видеоадаптере, далее нужно вызвать метод GetDesc() из интерфейса "IDXGI_Adapter", который осуществляется так:
Посмотрим, что мы тут имеем..
Если не считать имя активного видеоадаптера, Vid\Did его производителя и ревизию, метод GetDesc() вернул нам и распределение памяти адаптера. Когда на борту дискретная карта с локальной VRAM, в поле "DedicatedVideoMemory" мы получим её объём. Если-же имеем дело со-встроенной картой, то как-правило в этом поле лежит нуль, а следующее "SystemMemory" будет хранить значение апертуры, нагло захваченной адаптером у системы.
Нюансы подобного рода не знакомы утилитам типа WMI (см.запрос в консоли:
В качестве примера, тут я привёл лишь запрос метода GetDesc() из интерфейса "IDXGI_Adapter", хотя имеются ещё как-минимум 10 родственных интерфейсов DXGI (см.список выше), и в каждом из них свои методы. Основная проблема здесь в том, что метод GetDesc() возвращает объём VRAM лишь в диапазоне до 4 Gb, поскольку соответствующие поля в структуре размером DWORD. Но сейчас есть карты с памятью 8 и более гигов, и чтобы получить их размер можно воспользоваться функцией D3DKMTQueryAdapterInfo() из той-же либы dxgi.dll. Она возвращает уже по максимуму 64-битные значения, чего вполне хватает на все случаи жизни.
4. Функции D3DKMT_xx
Поверхностно ознакомившись с довольно некомфортной для ассемблера моделью компонентных объектов СОМ, плавно переместимся в привычное нам русло Win32-API. Примечательным является то, что большинство API-функций из dxgi.dll имеют точные свои ксерокопии и в библиотеке gdi32.dll, причём это не перенаправляющие указатели, а полные дубликаты, в чём можно убедиться открыв эти либы в любом парсере экспорта типа Hiew32.
В данном случае нас интересует функция D3DKMTQueryAdapterInfo() для перечисления свойств адаптера, которая весьма своенравна и требует нехилых подготовительных телодвижений. В единственном своём аргументе она ожидает указатель на предварительно заполненную нами структуру "D3DKMT_QUERYADAPTERINFO" с таким прототипом:
Обратите внимание на поле "Type" структуры. Здесь я перечислил всего 15 типов базовой информации, которые поддерживают все системы начиная с Vista. Если-же посмотреть в инклуде на общее их кол-во, то получается аж 47 констант, просто одни требуют систему не ниже Win8, а другие исключительно Win10. Поэтому (чтобы не было осечки) я выбрал универсальный вариант, дабы код можно было портировать на всю линейку Win.
Основную проблему при вызове D3DKMTQueryAdapterInfo() представляет способ получения дескриптора графического адаптера (см.поле hAdapter), получить который можно цепочкой из трёх функций ниже по такому алго:
Так во-втором поле мы получим дескриптор, который сразу-же в виде аргумента "hAdapter" отфутболим в функцию D3DKMTQueryAdapterInfo(). В практической части я запрашиваю всего 4 типа информации о видеокарте, среди которых есть и
Помимо функции D3DKMTQueryAdapterInfo() в библиотеке gdi32.dll есть ещё одна интересная, но никак недокументированная на MSDN функция D3DKMTQueryStatistics(). У себя на сайте мелкософт
5. Практика – чтение данных видеоадаптера
Под занавес приведу код приложения, который соберёт основную информацию об активном графическом адаптере. Кстати начиная с Win8 инженеры ввели понятие "виртуальный адаптер" и дали ему кличку "BasicRander". Он вступает в игру, когда система не находит драйверов от производителя, что даёт возможность запускать систему с абсолютно незнакомым железом. По этой причине в программе нужно организовать цикл, чтобы перечислить все имеющиеся карты, включая виртуальную.
Другой момент – это способ получения версии DirectX. Поиск в Google выдал мне несколько вариантов, и все как-минимум с сотней строк сишного кода. Это меня никак не вдохновило и я пошёл иным путём. Если учесть, что каждая версия DirectX несёт с собой свои Runtime-библиотеки, то достаточно будет в цикле загружать их по имени через LoadLibrary() и если ошибка, то переходить к следующей. Список этих библиотек выглядит так: d3d9.dll и дальше d3d10,11,12.dll для DirectX v9..12 соответственно. Вариант топорный, но что показательно – в нём нет ничего плохого, поскольку он решает проблему и исправно воркает на всех системах Win от 7 до 10. Вот исходник программы с реализацией задуманного:
На следующем этапе я снял внешнее своё видео "GeForce 9500 GT", и запустил систему только на встроенной карте. Вот лог где видно, что адаптер не имеет своей локальной памяти и запрашивает у системы апертуру размером всего 64 Mb. Зато у дискретной карты GeForce на предыдущем скрине имеется локальная VRAM в объёме 246 Mb, поэтому апертура ему не нужна.
Кроме того код правильно определил тип процессора GPU под ником "GMA-3100", цифро-аналоговый преобразователя DAC (Digital-to-Analog Converter), и версию DirectX v11. Обратите внимание на версию WDDM – дискретная карта знакома с расширенными функциями v1.1, в то время-как для встроенной карты требуется дефолтная не выше v1.0:
Ну и последний тест я провёл на своём буке с Win10 х64. Как оказалось у него карта встроенная размером 128 метров, хотя под графические нужды система выделяет макс 2 Gb. Выше упоминалась, что современные карты работают в режиме динамического выделения системной памяти DVMT: когда память адаптеру действительно нужна – она выделяется, а когда в ней нет необходимости, то на автомате освобождается. Код в цикле обнаружил и виртуальную карту "BasicRender", у которой даже нет адреса "Bus\Dev\Func" на системной шине PCI, а только динамическая память, а так-же сменилась версия DirectX на 12:
6. Заключение
Прежде чем поставить жирную точку и убрать перо с бумагой в стол, хотелось-бы сказать пару слов о работе с графическими объектами на ассемблере. Здесь я вообще не затрагивал данную тему, чтобы не смешивать два параллельных направления. Всех, кого интересует работа с графикой отправляю на сайт wasm.in, где модератор достопочтенного ресурса (и по совместительству участник нашего форума) @Mikl___ приводит массу таких примеров, например основной
Что касается рассмотренного выше кода, то просьба проверить и откликнуться тех, у кого на борту имеется видеоадаптер с памятью более 4 Gb. Интересует, правильно-ли код определит размер VRAM. В скрепке лежит исполняемый файл для тестов, и инклуд с описанием структур DXGI. Всем удачи, пока!
В данной статье рассматриваются вопросы доступа к свойствам графических адаптеров на языке ассемблера. Мы заправим свои мозговые баки топливом DirectX смешав его в должной мере с OpenGL, заглянем в современную модель драйверов видеоадаптеров Vista+, ознакомимся с базовыми терминами графической инфраструктуры, и смешаем весь этот коктейль с исполняемым кодом. Основной акцент делается на способ вычисления объёма локальной видеопамяти VRAM свыше 4 Gb, что является проблематичным на 32-битных системах Windows.
Оглавление:
1. Интерфейсы DirectX и OpenGL;
2. Модель драйверов дисплея WDDM;
3. Доступ к инфраструктуре DXGI;
4. Методы и функции D3DKMT_xx;
5. Практика – чтение данных видеоадаптера;
6. Итоги.
-----------------------------------------
1. Интерфейсы DirectX и OpenGL
Словосочетание DirectX знакомо большинству из нас, но осмелюсь предположить, что не все смогут дать ему точное определение. Чтобы далее не плавать в терминах, сразу определимся с основными понятиями графической подсистемы. Это огромный клубок, размотать который под силу не каждому, и по большому счёту в представленном на ваш суд материале я не преследовал эти цели. Мы пробежимся лишь по макушкам, чтобы перед глазами выстроилась хоть какая-то картина.
Microsoft DirectX – это набор функций прикладного уровня Win32API, для обработки связанных с графикой и мультимедиа задач. Впервые был представлен миру в 1995 году, в штатной поставке Win95. Тогда названия этих API начинались с префикса Direct типа: Direct3D, DirectDraw, Sound, Play, Music и т.д. Бытует мнение, что на презентации один из журналистов высмеял такой зоопарк имён, и сходу предложил термин DirectX, как обобщённый класс всех этих API. В средствах массовой (дез)информации, API трёхмерной графики Direct3D наиболее разрекламированный компонент, а потому названия DirectX и Direct3D стали синонимами.
Наряду с проприетарным DirectX, ещё в то время активно продвигалась и альтернативная технология OpenGL (Open Graphics Library) от компании "Khronos Group" 1994 г. Так разработчикам был представлен выбор – OpenGL или DirectХ, и началась борьба между сторонниками кроссплатформенного GL (Win, Linux, Mac), и DХ3D исключительно для Win. На практике-же два этих интерфейса часто пересекались, поскольку OpenGL не включает в себя все функции DirectX, типа поддержка звука или джойстика. Таким образом DХ3D и GL – это конкурирующие интерфейсы пользовательских API, хотя всегда ходят парой:
Код:
• Win2k/XP (2004г) - DirectX_v9 + OpenGL_v2;
• WinVista (2008г) - DirectX_v10 + OpenGL_v3;
• Win7/8.1 (2011г) - DirectX_v11 + OpenGL_v4;
• Win10 (2015г) - DirectX_v12 + OpenGL_v4.6.
Первая версия(5) DХ3D вызвала широкий резонанс и разочаровала программистов. Даже простые операции в ней требовали ручного создания и отправки соответствующих примитивов в буферы исполнения. В OpenGL-же, для смены состояния графического объекта достаточно было вызвать одну\единственную функцию. Другими словами, то-что представлялось в GL одной строкой, требовало чуть-ли не сотни строк кода DX3D. Так пузырь DirectХ на старте оглушительно лопнул, оставив за собой кратер, который можно было наблюдать с луны.
Но отбиваясь от града тухлых яиц M$ продолжала гнуть свою линию и развивать DX3D-API, в результате чего начиная с DX9 бывшие критики признали, что теперь он ничуть не хуже чем OpenGL по простоте использования. DirectХ в некоторых случаях преследует политику модели компонентных объектов COM, а поскольку директивы ассемблера не поддерживают её в полной мере, это выводит нас из зоны комфорта и приходится реализовать всё вручную. Как мы убедимся позже в практической части, СОМ в асме не так-уж страшен, как его малюют – главное иметь документацию, чем мелкософт наградил нас в своём репозитории.
1.1. Графический процессор GPU – общие сведения
GPU в режиме 3D работают с т.н. "полигональной графикой", когда любой объект представляется как набор миниатюрных 2D-треугольников. Графический объект задается "вершинами" и "полигонами" (боковые линии треугольников). Цвет на полигоны накладывается по спец.алгоритмам закраски, с использованием заранее подготовленных плоских изображений "текстур". Шейдеры (shader) – это программные процедуры видеоадаптера для наложения текстур, преломления света, затенения участков объектов и множества других параметров. Задача GPU сводится к тому, чтобы закрасить как можно больше полигонов за единицу времени. Ускорители в графических 3D-адаптерах (Accelerator) в первую очередь были созданы для аппаратного ускорения работы с полигонами.
В архитектуре видеоадаптеров имеется отдельно вершинный, и отдельно пиксельный процессор. Первый – вычислив вершины входящих в состав объекта треугольников и уцепившись за них, озадачен передвижением графических объектов вверх\вниз, влево\вправо. Манипулируя полигонами, вершинный процессор, например, может менять мимику графического персонажа (вертикальная ось), поворачивать объекты вокруг своей оси (горизонт) и т.д.
Второй-же, пиксельный процессор растеризует графику, подготавливая её к "рендерингу" – процессу преобразования 3D модели в плоское 2D-изображение, пригодное для вывода на дисплей (заключительный этап). После наложения текстуры на объект, в пиксельном процессоре происходит запись значений в "буфер кадра" – отображаемую на дисплее область графической памяти. На данном этапе выполняются и доп.операции, типа гамма-коррекция, сжатие Z-координат etc. Полная длина конвейера пиксельного процессора впечатляет – это порядка 200 ступеней, большая часть из которых отведена под операции обработки текстур.
1.2. Память процессоров GPU
Процессоры дискретных (внешних) графических адаптеров снабжены индивидуальной оперативной памятью VRAM (Video Random Access Memory), и при её ограниченном объёме могут запрашивать доп.память у системы. Подобного рода окна в системном ОЗУ назвали "апертурой", для отображения которой в память видеокарты используются таблицы GART (Graphics Address Remapping Table, перераспределение адресов). Адрес апертуры BIOS задаёт через специальные регистры чипсета, а её размер определяется параметром "AGP aperture size":
Апертуры часто практиковали карты с отжившим свой век интерфейсом AGP (Accelerated Graphics Port). Порт поддерживал работу в двух режимах: [1] DiME (Direct Memory Execute) когда код графики исполнялся прямо внутри системной памяти без копирования её в память адаптера, и [2] режим DMA (Direct Memory Access, прямой доступ к памяти без участия процессора), когда акселератор 3D при вычислениях рассматривал свою\локальную память как первичную, а если её недостаточно, то подкачивал в неё данные из апертуры системной памяти.
В наше время порт AGP уже давно не используется, и его место занял более высокоскоростной PCI-Express, который работает только в режиме DMA. Однако чипсеты Intel для встроенных карт IGD (Integrated Graphics Device) "воруют" память у системы и по сей день, только технология называется теперь
Ссылка скрыта от гостей
, или "Dynamic Video Memory Technology" (динамическое распределение ОЗУ для видео).2. Модель драйверов дисплея WDDM
Теперь, когда у нас есть базовые сведения, переместимся по туннелям времени в современный мир.
Начиная с Windows Vista, графическая подсистема претерпела огромные изменения. В частности это коснулось задействованной модели драйверов, которую назвали WDDM или "Windows Display Driver Model". Входящая в состав WDDM инфраструктура DXGI (DirectX Graphics Infrastructure) – это компонент пользовательского режима для Vista+, который связывает конкретные API DirectX и OpenGL c библиотеками WDDM.
DXGI предоставляет API-функции и методы СОМ для решения таких задач как: перечисление графических адаптеров и мониторов, запроса их свойств и режимов отображения, выбора форматов выходных буферов, и визуализации кадров на дисплее. Для перехода на модель драйвера WDDM производители адаптеров должны написать совершенно иные драйверы мини-порта дисплея. В отличии от устаревшей модели ХР.хDDM, усовершенствованный драйвер WDDM находится в пользовательском пространстве, и большая его часть сосредоточена в библиотеке dxgi.dll.
Модель не использует сервисы движка Gdi32.dll (Graphics Device Interface), а проходит через среду выполнения runtime d3d10.dll и подсистему графического ядра Dxgkrnl.sys. Версия[1] DXGI требует DirectХ v10 и выше – цель его заключается во взаимодействии с драйвером режима ядра и аппаратным обеспечением, как показано на следующей схеме:
Рассмотрим ключевые компоненты этого рисунка:
• Direct3D API: это пользовательский интерфейс Direct3D, библиотеки которого можно найти в папке system32. Идут в штатной поставке OC Vista+.
• Юзер-драйвер (UMD): компонент драйвера, который предоставляет функциональные возможности Direct3D. Это библиотеки dll, которые прилагаются к драйверам конкретной графической карты и поставляются производителем оборудования. Для встроенных карт Intel дров олицетворяется файлом igdumd32.dll (Integrated Graphics Device UM-Driver), а для карт Nvidia это nvd3dum.dll и прочие. Обратите внимание, что UMD имеет отношение только к Direct3D.
• Интерфейсы сторонних поставщиков: Win32API, не основанные на Microsoft DirectX, такие как OpenGL, Vulkan, CUDA и т.д. Компонент известен как ICD (устанавливаемый клиентский драйвер). DXGI фактически является связующим звеном между DХ3D и GL. Клиентские ICD общаются с низкоуровневым интерфейсом WDDM через библиотеку мастдая gdi32.dll.
• Интерфейс WDDM: это набор низкоуровневых API, которые предоставляют функции ядра, коду пользователя. Имеют префикс "D3DKMTхх" и живут в dxgi.dll. Нашему приложению предоставлен выбор – обращаться к DXGI напрямую, или вызывать API среды выполнения d3d10,11,12.dll. Прямой вызов функций DXGI полезен, когда софт планирует создать список устройств или настроить способ представления данных. С введением инженерами данного слоя, в системе перестали появляться связанные с графикой голубые экраны BSOD, чем страдала Win-XP. Теперь ошибки возникают не в ядре, а исключительно на пользовательском уровне, и для их устранения достаточно просто перезагрузить либу.
• Ядро графики DirectX: это компонент, который реализует базовую функциональность. Графическое ядро отвечает за обеспечение совместного использования ресурсов GPU несколькими программными процессами, и управление памятью видеоадаптера. Код компонента находится в файле Dxgkrnl.sys, с прицепом диспетчера памяти видеоадаптера dxgmms1.sys.
• Драйвер режима ядра (KMD): напрямую взаимодействует с аппаратным устройством, от имени ядра графики DirectX.
3. Доступ к интерфейсу DXGI
Поговорим теперь о программном доступе к инфраструктуре DXGI..
Выше упоминалось, что одноимённая библиотека dxgi.dll содержит в себе как методы СОМ, так и привычные нам функции Win32API. Причём методы компонентных объектов предлагают более широкие возможности по управлению и сбору информации об оборудовании – с них и начнём. Не смотря на то, что процесс этот скучный, и чем-то напоминает супружеский долг после 30 лет совместного проживания, результат вполне оправдывает вложенный депозит, открывая нам доступ в нёдра модели драйверов WDDM.
Что такое СОМ и как с ним работать на асме подробно обсуждалось в статье про дизассемблер, а потому не буду повторяться – мы сделаем ставку на СОМ конкретно взятого DXGI. Структура входящих в его состав интерфейсов с привязанными к ним глобальными идентификаторами GUID выглядит так, где основными являются пара "IID_IDXGI_Factory", а остальные наследуются от них. Более подробную информацию о фабриках DXGI можно найти на
Ссылка скрыта от гостей
или
Ссылка скрыта от гостей
:
C-подобный:
;// Основные интерфейсы DXGI
;//--------------------------------------
IID_IDXGI_Factory dd 0x7b7166ec, 0x44ae21c7, 0xaec91ab2, 0x69e31a32
IID_IDXGI_Factory1 dd 0x770aae78, 0x4dbaf26f, 0x3c2529a8, 0x87b3d183
;// Наследуется от "Factory"
;//--------------------------------------
IID_IDXGI_Object dd 0xaec22fb8, 0x463976f3, 0xeb28e09b, 0x2e7aa643
;// Наследуются от "Object"
;//--------------------------------------
IID_IDXGI_Adapter dd 0x2411e7e1, 0x4ccf12ac, 0x989714bd, 0xc04d53e8
IID_IDXGI_Adapter1 dd 0x29038f61, 0x46263839, 0x6808fd91, 0x051a0179
IID_IDXGI_Device dd 0x54ec77fa, 0x44e61377, 0xfd88328c, 0x4cc8445f
IID_IDXGI_Device1 dd 0x77db970f, 0x48ba6276, 0x010728ba, 0x2c39b443
IID_IDXGI_DeviceSubObject dd 0x3d3e0379, 0x4d58f9de, 0xd6186cbb, 0xa6f19229
IID_IDXGI_KeyedMutex dd 0x9d8e1289, 0x465fd7b3, 0x0e252681, 0x5df89a34
IID_IDXGI_Output dd 0xae02eedb, 0x4690c735, 0x8d5a528d, 0xaa1302c2
IID_IDXGI_Resource dd 0x035f3ab4, 0x4e50482e, 0x7f8a1fb4, 0x0b96d88b
IID_IDXGI_Surface dd 0xcafcb56c, 0x48896ac3, 0x239e47bf, 0xec60d2bb
IID_IDXGI_Surface1 dd 0x4AE63092, 0x4c1b6327, 0xe1bfae80, 0x862ba32e
IID_IDXGI_SwapChain dd 0x310d36a0, 0x4c0ad2e7, 0x9d6a04aa, 0x6a88b823
Первое, что нужно сделать, это функцией CoInitialize() из ole32.dll активизировать системный СОМ-сервер, после чего при помощи CreateDXGIFactory1() из dxgi.dll получить указатель на основной интерфейс "IID_IDXGI_Factory1". Так мы получим доступ к виртуальной таблице методов интерфейса "IDXGI_Factory", среди которых нас будет интересовать метод перечисления видеоадаптеров EnumAdapters().
C-подобный:
struct IUnknown
QueryInterface dd 0 ;// This, InterfaceId, pInterface
AddRef dd 0 ;// This
Release dd 0 ;// This
ends
struct IDXGI_Object
Header IUnknown
SetPrivateData dd 0 ;// This, Name, DataSize, DataSize
SetPrivateDataInterface dd 0 ;// This, Name, *pUnknown
GetPrivateData dd 0 ;// This, Name, *pDataSize, *pDataSize
GetParent dd 0 ;// This, riid, **ppParent
ends
struct IDXGI_Factory
Header IDXGI_Object
EnumAdapters dd 0 ;// This,Index,ppAdapter <------- Наш клиент!
MakeWindowAssociation dd 0 ;// This,WindowHandle,Flags
GetWindowAssociation dd 0 ;// This,pWindowHandle
CreateSwapChain dd 0 ;// This,pDevice,pDesc,ppSwapChain
CreateSoftwareAdapter dd 0 ;// This,Module,ppAdapter
ends
struct IDXGI_Adapter
Header IDXGI_Object
EnumOutputs dd 0 ;// This,Output,ppOutput
GetDesc dd 0 ;// This,pDesc
CheckInterfaceSupport dd 0 ;// This,InterfaceName,pUMDVersion
ends
Как видим, структура интерфейса "IDXGI_Factory" (представляющая собой вирт.таблицу vTable) включает в себя две предыдущие структуры "IUnknown+IDXGI_Object", которые я обозначил здесь как поле "Header". Более того, любой интерфейс DXGI начинается с такого заголовка, а первым методом в них всегда будет метод перечисления содержимого QueryInterface(). Таким образом, чтобы найти указатель на метод EnumAdapters(), нужно проделать следующие шаги:
C-подобный:
;// Получить в переменную 'hFactory' линк на интерфейс 'IDXGI_Factory'
invoke CoInitialize,0
invoke CreateDXGIFactory1,IID_IDXGI_Factory1,hFactory
;// Найти и вызвать 'IDXGI_Factory::EnumAdapter'
mov esi,[hFactory] ;// указатель на интерфейс
mov esi,[esi] ;// взять из него адрес метода QueryInterface()
mov eax,[esi+IDXGI_Factory.EnumAdapters] ;// получить адрес метода EnumAdapters()
push hAdapter ;// сюда получим указатель
push [Index] ;// индекс адаптера начиная с нуля
push [hFactory] ;// This = в каком интерфейсе искать
call eax ;// EnumAdapter()!
Поскольку меня интересует информация о видеоадаптере, далее нужно вызвать метод GetDesc() из интерфейса "IDXGI_Adapter", который осуществляется так:
C-подобный:
;// Найти и вызвать 'IDXGI_Adapter::GetDesc'
mov esi,[hAdapter]
mov esi,[esi]
mov eax,[esi+IDXGI_Adapter.GetDesc]
push buff ;// сюда получим структуру "DXGI_ADAPTER_DESC"
push [hAdapter] ;// This = в каком интерфейсе искать
call eax
;//----------------------
struct DXGI_ADAPTER_DESC
Description dw 128 dup(0) ;// Unicode имя адаптера
VendorId dd 0
DeviceId dd 0
SubSysId dd 0
Revision dd 0
DedicatedVideoMemory dd 0 ;// локальная память видеоадаптера
DedicatedSystemMemory dd 0 ;// апертура из системной памяти
SharedSystemMemory dd 0 ;// оставшаяся память из общего пула
AdapterLuid dq 0 ;// идентификатор LUID адаптера в системе
ends
Посмотрим, что мы тут имеем..
Если не считать имя активного видеоадаптера, Vid\Did его производителя и ревизию, метод GetDesc() вернул нам и распределение памяти адаптера. Когда на борту дискретная карта с локальной VRAM, в поле "DedicatedVideoMemory" мы получим её объём. Если-же имеем дело со-встроенной картой, то как-правило в этом поле лежит нуль, а следующее "SystemMemory" будет хранить значение апертуры, нагло захваченной адаптером у системы.
Нюансы подобного рода не знакомы утилитам типа WMI (см.запрос в консоли:
wmic.exe PATH Win32_videocontroller GET AdapterRAM
) – она возвращает лишь сумму трёх\представленных полей. В свою очередь LUID является 64-битным одноразовым значением (Local Unique ID), и при следующей перезагрузке системы будет уже иным.В качестве примера, тут я привёл лишь запрос метода GetDesc() из интерфейса "IDXGI_Adapter", хотя имеются ещё как-минимум 10 родственных интерфейсов DXGI (см.список выше), и в каждом из них свои методы. Основная проблема здесь в том, что метод GetDesc() возвращает объём VRAM лишь в диапазоне до 4 Gb, поскольку соответствующие поля в структуре размером DWORD. Но сейчас есть карты с памятью 8 и более гигов, и чтобы получить их размер можно воспользоваться функцией D3DKMTQueryAdapterInfo() из той-же либы dxgi.dll. Она возвращает уже по максимуму 64-битные значения, чего вполне хватает на все случаи жизни.
4. Функции D3DKMT_xx
Поверхностно ознакомившись с довольно некомфортной для ассемблера моделью компонентных объектов СОМ, плавно переместимся в привычное нам русло Win32-API. Примечательным является то, что большинство API-функций из dxgi.dll имеют точные свои ксерокопии и в библиотеке gdi32.dll, причём это не перенаправляющие указатели, а полные дубликаты, в чём можно убедиться открыв эти либы в любом парсере экспорта типа Hiew32.
В данном случае нас интересует функция D3DKMTQueryAdapterInfo() для перечисления свойств адаптера, которая весьма своенравна и требует нехилых подготовительных телодвижений. В единственном своём аргументе она ожидает указатель на предварительно заполненную нами структуру "D3DKMT_QUERYADAPTERINFO" с таким прототипом:
C-подобный:
struct D3DKMT_QUERYADAPTERINFO
hAdapter dd 0 ;// In. дескриптор адаптера, о котором извлекается информация
Type dd 0 ;// In. KMTQUERYADAPTERINFOTYPE - тип информации, которую нужно извлечь
pPrivateDriverData dd 0 ;// In. указатель на приёмный буфер
PrivateDriverDataSize dd 0 ;// In. размер буфера в байтах
ends
;//--------------------------------------
;// Константы типа извлекаемой инфы
;//--------------------------------------
;// typedef enum _KMTQUERYADAPTERINFOTYPE
;//
KMTQAITYPE_UMDRIVERPRIVATE = 0
KMTQAITYPE_UMDRIVERNAME = 1
KMTQAITYPE_UMOPENGLINFO = 2
KMTQAITYPE_GETSEGMENTSIZE = 3
KMTQAITYPE_ADAPTERGUID = 4
KMTQAITYPE_FLIPQUEUEINFO = 5
KMTQAITYPE_ADAPTERADDRESS = 6
KMTQAITYPE_SETWORKINGSETINFO = 7
KMTQAITYPE_ADAPTERREGISTRYINFO = 8
KMTQAITYPE_CURRENTDISPLAYMODE = 9
KMTQAITYPE_MODELIST = 10
KMTQAITYPE_CHECKDRIVERUPDATESTATUS = 11
KMTQAITYPE_VIRTUALADDRESSINFO = 12
KMTQAITYPE_DRIVERVERSION = 13
KMTQAITYPE_ADAPTERTYPE = 15
Обратите внимание на поле "Type" структуры. Здесь я перечислил всего 15 типов базовой информации, которые поддерживают все системы начиная с Vista. Если-же посмотреть в инклуде на общее их кол-во, то получается аж 47 констант, просто одни требуют систему не ниже Win8, а другие исключительно Win10. Поэтому (чтобы не было осечки) я выбрал универсальный вариант, дабы код можно было портировать на всю линейку Win.
Основную проблему при вызове D3DKMTQueryAdapterInfo() представляет способ получения дескриптора графического адаптера (см.поле hAdapter), получить который можно цепочкой из трёх функций ниже по такому алго:
1. CM_Get_Device_Interface_List() из библиотеки setupapi.dll, передав ей в первом аргументе константу "GUID_DISPLAY_DEVICE_ARRIVAL". Константа действительна для всех Win и прописана в прикреплённом инклуде. На выходе из этой функции получим строку с идентификатором адаптера вида:
\\?\PCI#VEN_8086&DEV_29C2&SUBSYS_75291462&REV_10#3&10#{1ca05180-a699-450a-9a0c-de4fbe3ddd89}
, где GUID в хвосте и есть глобальная константа дисплея.
2. MultiByteToWideChar(), чтобы перевести данный идентификатор из ANSI в Unicode, т.к. следующая функция ожидает на входе именно Юникод.
3. D3DKMTOpenAdapterFromDeviceName() из либы gdi32.dll возвращает уже дескриптор указанного видеоадаптера, а на входе ждёт указатель на структуру "D3DKMT_OPENADAPTERFROMDEVICENAME" с таким прототипом:
C-подобный:
struct D3DKMT_OPENADAPTERFROMDEVICENAME
pDeviceName dd 0 ;// In. линк на Unicode-имя адаптера
hAdapter dd 0 ;// Out. "D3DKMT_HANDLE"
AdapterLuid dq 0 ;// Out. LUID
ends
Так во-втором поле мы получим дескриптор, который сразу-же в виде аргумента "hAdapter" отфутболим в функцию D3DKMTQueryAdapterInfo(). В практической части я запрашиваю всего 4 типа информации о видеокарте, среди которых есть и
"KMTQAITYPE_GETSEGMENTSIZE=3"
. Именно этот запрос возвращает объём VRAM выше 4 Gb, а вот как выглядит формат структуры на выходе (все поля размером QWORD=8 байт):
C-подобный:
struct D3DKMT_SEGMENTSIZEINFO
DedicatedVideoMemorySize dq 0 ;// Out. размер памяти, выделенной из видеоадаптера
DedicatedSystemMemorySize dq 0 ;// Out. размер апертуры ОЗУ для графики
SharedSystemMemorySize dq 0 ;// Out. расшаренная видео-память
ends
Помимо функции D3DKMTQueryAdapterInfo() в библиотеке gdi32.dll есть ещё одна интересная, но никак недокументированная на MSDN функция D3DKMTQueryStatistics(). У себя на сайте мелкософт
Ссылка скрыта от гостей
нас не использовать данную функцию, мол она только для системных нужд. Но беда не в том, что наверху лгут, а в том, что внизу верят. Как и следует из её названия, она призвана собирать различную статистику о видеоадаптере, например время работы GPU в текущем приложении и многое другое. Вот какие запросы статистики ей можно передавать:
C-подобный:
;// enum _D3DKMT_QUERYSTATISTICS_TYPE
;//
D3DKMT_QUERYSTATISTICS_ADAPTER = 0
D3DKMT_QUERYSTATISTICS_PROCESS = 1
D3DKMT_QUERYSTATISTICS_PROCESS_ADAPTER = 2
D3DKMT_QUERYSTATISTICS_SEGMENT = 3
D3DKMT_QUERYSTATISTICS_PROCESS_SEGMENT = 4
D3DKMT_QUERYSTATISTICS_NODE = 5
D3DKMT_QUERYSTATISTICS_PROCESS_NODE = 6
D3DKMT_QUERYSTATISTICS_VIDPNSOURCE = 7
D3DKMT_QUERYSTATISTICS_PROCESS_VIDPNSOURCE = 8
;//<------- Win8+
D3DKMT_QUERYSTATISTICS_PROCESS_SEGMENT_GROUP = 9
D3DKMT_QUERYSTATISTICS_PHYSICAL_ADAPTER = 10
5. Практика – чтение данных видеоадаптера
Под занавес приведу код приложения, который соберёт основную информацию об активном графическом адаптере. Кстати начиная с Win8 инженеры ввели понятие "виртуальный адаптер" и дали ему кличку "BasicRander". Он вступает в игру, когда система не находит драйверов от производителя, что даёт возможность запускать систему с абсолютно незнакомым железом. По этой причине в программе нужно организовать цикл, чтобы перечислить все имеющиеся карты, включая виртуальную.
Другой момент – это способ получения версии DirectX. Поиск в Google выдал мне несколько вариантов, и все как-минимум с сотней строк сишного кода. Это меня никак не вдохновило и я пошёл иным путём. Если учесть, что каждая версия DirectX несёт с собой свои Runtime-библиотеки, то достаточно будет в цикле загружать их по имени через LoadLibrary() и если ошибка, то переходить к следующей. Список этих библиотек выглядит так: d3d9.dll и дальше d3d10,11,12.dll для DirectX v9..12 соответственно. Вариант топорный, но что показательно – в нём нет ничего плохого, поскольку он решает проблему и исправно воркает на всех системах Win от 7 до 10. Вот исходник программы с реализацией задуманного:
C-подобный:
format pe console
entry start
;//-------
section '.inc' data readable
include 'win32ax.inc'
include 'equates\dxgi.inc'
;//-------
.data
d3Fname db 'd3d1' ;//<---- Имя библиотеки DirectX,
verIndex db '2.dll',0 ;//<---- ..и его продолжение для цикла.
dxVersion dd 12
devName rb 512
hFactory dd 0
hAdapter dd 0
Index dd 0
listOffset dd buff
fpuResult dq 0
fpuMb dd 1024*1024 ;// Byte to Mbyte
align 16
disp DISPLAY_DEVICEA
oa D3DKMT_OPENADAPTERFROMDEVICENAME
qai D3DKMT_QUERYADAPTERINFO
stat D3DKMT_QUERYSTATISTICS
buff db 0
;//---------
.code
start: invoke SetConsoleTitle,<'*** Graphic adapter info v1.0 ***',0>
;// Получить версию DirectX
@getDxVersion:
invoke LoadLibrary,d3Fname
or eax,eax
jnz @ok
dec byte[verIndex] ;// если ошибка..
dec [dxVersion]
jmp @getDxVersion
@ok: invoke FreeLibrary,eax
cinvoke printf,<10,' DirectX ver...: %d',10,0>,[dxVersion]
invoke EnumDisplayDevicesA,0,0,disp,0
cinvoke printf,<10,' Object name...: %s',\
10,' Adapter.......: %s',0>,\
disp.DeviceName,disp.DeviceString
;//************************************************************
;//***** Работа с СОМ-интерфейсом, и методами DXGI ************
;//************************************************************
invoke CoInitialize,0
invoke CreateDXGIFactory1,IID_IDXGI_Factory1,hFactory
;// Найти и вызвать 'IDXGI_Factory::EnumAdapter'
mov esi,[hFactory]
mov esi,[esi]
mov eax,[esi+IDXGI_Factory.EnumAdapters]
push hAdapter ;// сюда получим указатель
push [Index] ;// индекс адаптера начиная с нуля
push [hFactory] ;// This = в каком интерфейсе искать
call eax ;// EnumAdapter!!!
;// Найти и вызвать 'IDXGI_Adapter::GetDesc'
mov esi,[hAdapter]
mov esi,[esi]
mov eax,[esi+IDXGI_Adapter.GetDesc]
push buff ;// сюда получим структуру "DXGI_ADAPTER_DESC"
push [hAdapter] ;// This = в каком интерфейсе искать
call eax
mov esi,buff
mov eax,[esi+DXGI_ADAPTER_DESC.DedicatedVideoMemory]
mov ebx,[esi+DXGI_ADAPTER_DESC.DedicatedSystemMemory]
mov ecx,[esi+DXGI_ADAPTER_DESC.SharedSystemMemory]
shr eax,20
shr ebx,20 ;//<------ байты в мегабайты
shr ecx,20
cinvoke printf,< 10,' Vid/Pid.......: %04x-%04x rev.%x',\
10,' LUID..........: %08x%08x',\
10,10,' Mem from Video....: %03d Mb',\
10,' Mem from System...: %03d Mb <--- BIOS Aperture Size',\
10,' Shared memory.....: %03d Mb',0>,\
[esi+DXGI_ADAPTER_DESC.VendorId],\
[esi+DXGI_ADAPTER_DESC.DeviceId],\
[esi+DXGI_ADAPTER_DESC.Revision],\
dword[esi+DXGI_ADAPTER_DESC.AdapterLuid+4],\
dword[esi+DXGI_ADAPTER_DESC.AdapterLuid],\
eax,ebx,ecx
mov esi,buff
mov eax,[esi+DXGI_ADAPTER_DESC.DedicatedVideoMemory]
add eax,[esi+DXGI_ADAPTER_DESC.DedicatedSystemMemory]
add eax,[esi+DXGI_ADAPTER_DESC.SharedSystemMemory]
shr eax,20
cinvoke printf,<10,' Total Video RAM...: %d Mb',0>,eax
;//***************************************************************
;//***** Функции класса D3DKMTхх *********************************
;//***************************************************************
invoke CM_Get_Device_Interface_List, GUID_DISPLAY_DEVICE_ARRIVAL,0,buff,512,0
mov edi,[listOffset]
@GetVideoadapter:
invoke MultiByteToWideChar,CP_UTF8,0,edi,-1,devName,512
cinvoke printf,<10,10,' Adapter ID........: %.65ls',0>,devName
mov [oa.pDeviceName],devName
invoke D3DKMTOpenAdapterFromDeviceName,oa
cinvoke printf,< 10,' Adapter handle....: %08x',\
10,' Adapter LUID......: %08x%08x',0>,\
[oa.hAdapter],dword[oa.AdapterLuid+4],dword[oa.AdapterLuid]
;// Различная инфа от D3DKMTQueryAdapterInfo() ----------
;//----- Адрес видео-адаптера на шине PCI ---------------
mov eax,[oa.hAdapter]
mov [qai.hAdapter],eax
mov [qai.Type],KMTQAITYPE_ADAPTERADDRESS
mov [qai.pPrivateDriverData],buff
mov [qai.PrivateDriverDataSize],sizeof.D3DKMT_ADAPTERADDRESS
invoke D3DKMTQueryAdapterInfo,qai
mov esi,buff
cinvoke printf,< 10,' PCI Bus\Dev\Func..: %02x:%02x:%02x',0>,\
[esi+D3DKMT_ADAPTERADDRESS.BusNumber],\
[esi+D3DKMT_ADAPTERADDRESS.DeviceNumber],\
[esi+D3DKMT_ADAPTERADDRESS.FunctionNumber]
;//----- Информация из реестра --------------------------
mov eax,[oa.hAdapter]
mov [qai.hAdapter],eax
mov [qai.Type],KMTQAITYPE_ADAPTERREGISTRYINFO
mov [qai.pPrivateDriverData],buff
mov [qai.PrivateDriverDataSize],sizeof.D3DKMT_ADAPTERREGISTRYINFO
invoke D3DKMTQueryAdapterInfo,qai
mov esi,buff
mov eax,esi
add eax,D3DKMT_ADAPTERREGISTRYINFO.AdapterString
mov ebx,esi
add ebx,D3DKMT_ADAPTERREGISTRYINFO.BiosString
mov ecx,esi
add ecx,D3DKMT_ADAPTERREGISTRYINFO.DacType
mov edx,esi
add edx,D3DKMT_ADAPTERREGISTRYINFO.ChipType
cinvoke printf,< 10,' Adapter string....: %ls',\
10,' Bios string.......: %ls',\
10,' Dac type..........: %ls',\
10,' GPU chip name.....: %ls',0>,eax,ebx,ecx,edx
;//----- Размер памяти видео-адаптера более 4 Gb -------
mov eax,[oa.hAdapter]
mov [qai.hAdapter],eax
mov [qai.Type],KMTQAITYPE_GETSEGMENTSIZE
mov [qai.pPrivateDriverData],buff
mov [qai.PrivateDriverDataSize],sizeof.D3DKMT_SEGMENTSIZEINFO
invoke D3DKMTQueryAdapterInfo,qai
mov esi,buff
push esi esi esi
push dword[esi+D3DKMT_SEGMENTSIZEINFO.DedicatedVideoMemorySize+4]
push dword[esi+D3DKMT_SEGMENTSIZEINFO.DedicatedVideoMemorySize]
fild qword[esp]
fidiv [fpuMb]
fstp [fpuResult]
add esp,8
cinvoke printf,< 10,' Mem from Video....: %04.0f Mb',0>,\
dword[fpuResult],dword[fpuResult+4]
pop esi
push dword[esi+D3DKMT_SEGMENTSIZEINFO.DedicatedSystemMemorySize+4]
push dword[esi+D3DKMT_SEGMENTSIZEINFO.DedicatedSystemMemorySize]
fild qword[esp]
fidiv [fpuMb]
fstp [fpuResult]
add esp,8
cinvoke printf,< 10,' Mem from System...: %04.0f Mb',0>,\
dword[fpuResult],dword[fpuResult+4]
pop esi
push dword[esi+D3DKMT_SEGMENTSIZEINFO.SharedSystemMemorySize+4]
push dword[esi+D3DKMT_SEGMENTSIZEINFO.SharedSystemMemorySize]
fild qword[esp]
fidiv [fpuMb]
fstp [fpuResult]
add esp,8
cinvoke printf,< 10,' Shared VGA mem....: %04.0f Mb',0>,\
dword[fpuResult],dword[fpuResult+4]
pop esi
fild [esi+D3DKMT_SEGMENTSIZEINFO.DedicatedVideoMemorySize]
fild [esi+D3DKMT_SEGMENTSIZEINFO.DedicatedSystemMemorySize]
fild [esi+D3DKMT_SEGMENTSIZEINFO.SharedSystemMemorySize]
fadd st0,st1
fadd st0,st2
fidiv [fpuMb]
fstp [fpuResult]
cinvoke printf,< 10,' Total Video RAM...: %04.0f Mb',0>,\
dword[fpuResult],dword[fpuResult+4]
;//***** Цикл обхода всех адаптеров для Win8+ ******************
invoke CM_Get_Device_Interface_List, GUID_DISPLAY_DEVICE_ARRIVAL,0,buff,512,0
mov edi,[listOffset]
xor eax,eax
repne scasb
cmp byte[edi],'\'
jne @f
mov [listOffset],edi
jmp @GetVideoadapter
;//*******************************************************************
;//***** Недокументированная статистика ******************************
;//*******************************************************************
@@: cinvoke printf,<10,10,' ****** D3DKMTQueryStatistics() ******',10,0>
mov [stat.Type],D3DKMT_QUERYSTATISTICS_PROCESS
mov eax,dword[oa.AdapterLuid]
mov ebx,dword[oa.AdapterLuid+4]
mov dword[stat.AdapterLuid],eax
mov dword[stat.AdapterLuid+4],ebx
invoke D3DKMTQueryStatistics,stat
cinvoke printf,< 10,' Stat return code..: %x (0 = OK)',0>,eax
@exit: cinvoke _getch
cinvoke exit,0
;//-------
section '.idata' import data readable
library msvcrt,'msvcrt.dll',kernel32,'kernel32.dll',user32,'user32.dll',\
ole32,'ole32.dll',dxgi,'dxgi.dll',gdi32,'gdi32.dll',setupapi,'setupapi.dll'
import dxgi,CreateDXGIFactory1,'CreateDXGIFactory1'
import ole32,CoInitialize,'CoInitialize'
include 'api\msvcrt.inc'
include 'api\kernel32.inc'
include 'api\user32.inc'
include 'api\gdi32.inc'
include 'api\setupapi.inc'
На следующем этапе я снял внешнее своё видео "GeForce 9500 GT", и запустил систему только на встроенной карте. Вот лог где видно, что адаптер не имеет своей локальной памяти и запрашивает у системы апертуру размером всего 64 Mb. Зато у дискретной карты GeForce на предыдущем скрине имеется локальная VRAM в объёме 246 Mb, поэтому апертура ему не нужна.
Кроме того код правильно определил тип процессора GPU под ником "GMA-3100", цифро-аналоговый преобразователя DAC (Digital-to-Analog Converter), и версию DirectX v11. Обратите внимание на версию WDDM – дискретная карта знакома с расширенными функциями v1.1, в то время-как для встроенной карты требуется дефолтная не выше v1.0:
Ну и последний тест я провёл на своём буке с Win10 х64. Как оказалось у него карта встроенная размером 128 метров, хотя под графические нужды система выделяет макс 2 Gb. Выше упоминалась, что современные карты работают в режиме динамического выделения системной памяти DVMT: когда память адаптеру действительно нужна – она выделяется, а когда в ней нет необходимости, то на автомате освобождается. Код в цикле обнаружил и виртуальную карту "BasicRender", у которой даже нет адреса "Bus\Dev\Func" на системной шине PCI, а только динамическая память, а так-же сменилась версия DirectX на 12:
6. Заключение
Прежде чем поставить жирную точку и убрать перо с бумагой в стол, хотелось-бы сказать пару слов о работе с графическими объектами на ассемблере. Здесь я вообще не затрагивал данную тему, чтобы не смешивать два параллельных направления. Всех, кого интересует работа с графикой отправляю на сайт wasm.in, где модератор достопочтенного ресурса (и по совместительству участник нашего форума) @Mikl___ приводит массу таких примеров, например основной
Ссылка скрыта от гостей
, и уроки от
Ссылка скрыта от гостей
.Что касается рассмотренного выше кода, то просьба проверить и откликнуться тех, у кого на борту имеется видеоадаптер с памятью более 4 Gb. Интересует, правильно-ли код определит размер VRAM. В скрепке лежит исполняемый файл для тестов, и инклуд с описанием структур DXGI. Всем удачи, пока!