Directx windows form c

Современная C++-библиотека для программирования DirectX

Я много писал как DirectX-кода, так и о самом DirectX. Я даже подготовил онлайновые учебные курсы по DirectX. На самом деле он не настолько сложен, как кажется некоторым разработчикам. Для его изучения определенно потребуется время, но, как только вы вникнете в него, вам будет нетрудно понять, как и почему DirectX работает так, как работает. Тем не менее, соглашусь с тем, что семейство DirectX API могло бы быть и полегче в использовании.

Несколько вечеров назад я решил исправить эту ситуацию. Все ночь напролет я писал небольшой заголовочный файл. Спустя несколько дней (точнее вечеров) в нем содержалось уже примерно 5000 строк кода. Моей целью было создать нечто такое, что упростило бы вам создание приложений с применением Direct2D, и бросить вызов всем этим аргументам наподобие «C++ очень сложен» или «DirectX слишком труден», так популярным в наши дни. Я не хотел создавать еще одну увесистую оболочку вокруг DirectX. Вместо этого я решил задействовать C++11, чтобы сконструировать более простые API для DirectX, с которыми можно было бы работать без таких усилий, как с базовым DirectX API. СОзданную мной библиотеку вы найдете на dx.codeplex.com.

Сама библиотека состоит только из единственного заголовочного файла dx.h — остальной исходный код на CodePlex представляет собой примеры ее использования.

В этой статье я покажу, как использовать данную библиотеку для упрощения выполнения распространенных операций, связанных с DirectX. Кроме того, я опишу архитектуру этой библиотеки, чтобы вы получили представление о том, как C++11 помогает сделать классические COM API более удобными в работе, не прибегая к тяжелым оболочкам наподобие Microsoft .NET Framework.

Очевидно, что в центре внимания будет Direct2D. Он остается самым простым и эффективным способом применения DirectX в широком классе приложений и игр. Немалая часть разработчиков, похоже, разделилась на два противоположных лагеря. Есть «хардкорные» DirectX-разработчики, которые выросли на различных версиях DirectX API. Их мастерство укреплялось с годами по мере эволюции DirectX, и они счастливы тому, что пребывают в закрытом клубе с высокой планкой входа, к которому могут присоединиться лишь очень немногие разработчики. В другом лагере находятся разработчики, услышавшие посыл о том, что DirectX сложен, и они не хотят иметь с ним дела. Естественно, они склонны и к неприятию C++.

Я не отношу себя ни к одному из этих лагерей. Я считаю, что C++ и DirectX не обязательно должны быть столь сложны. В своей прошлой статье из этой рубрики (msdn.microsoft.com/magazine/dn198239) я познакомил вас с Direct2D 1.1 и обязательным кодом для Direct3D и DirectX Graphics Infrastructure (DXGI), который необходим для создания устройства и управления цепочкой замен (swap chain). Код для создания Direct3D-устройства с помощью функции D3D11CreateDevice, подходящий для рендеринга на GPU или CPU, выливается примерно в 35 строк. Однако с помощью моего небольшого заголовочного файла он сокращается до:

Функция CreateDevice возвращает объект Device1. Все Direct3D-определения находятся в пространстве имен Direct3D, поэтому я мог бы выразить то же самое более явным образом и написать следующее:

Объект Device1 — это просто оболочка указателя на COM-интерфейс ID3D11Device1 (интерфейс Direct3D-устройства, введенный в выпуске DirectX 11.1). Класс Device1 наследует от класса Device, который в свою очередь является оболочкой исходного интерфейса ID3D11Device. Он представляет одну ссылку и не добавляет никаких дополнительных издержек по сравнению с простым удержанием самого указателя на интерфейс. Заметьте, что Device1 и его родительский класс Device являются обычными C++-классами, а не интерфейсами. Вы могли бы рассматривать их как смарт-указатели, но это было бы чрезмерным упрощением. Конечно, они обеспечивают учет ссылок и предоставляют оператор «->» для прямого вызова выбранного вами метода, но по-настоящему полезными они становятся, когда вы начинаете использовать множество не виртуальных методов, предлагаемых библиотекой dx.h.

Читайте также:  Как настроить raid массив windows 10

Вот пример. Вам часто требуется передавать DXGI-интерфейс Direct3D-устройства какому-то другому методу или функции. Вы могли бы пойти по трудному пути:

Это, разумеется, работает, но теперь вы должны напрямую взаимодействовать с DXGI-интерфейсом устройства. Кроме того, вам понадобится помнить, что интерфейс IDXGIDevice2 является версией интерфейса DXGI устройства в DirectX 11.1. Вместо этого вы можете просто вызвать метод AsDxgi:

Полученный объект Device2, на этот раз определенный в пространстве имен Dxgi, обертывает указатель на COM-интерфейс IDXGIDevice2, предоставляя собственный набор не виртуальных методов. В продолжение этого примера вы можете использовать «объектную модель» DirectX, чтобы добраться до фабрики DXGI:

Это настолько распространенный шаблон, что Direct3D-класс Device предоставляет метод GetDxgiFactory в качестве удобного сокращения:

Таким образом, помимо нескольких методов и функций, созданных исключительно для удобства, например GetDxgiFactory, не виртуальные методы один в один соответствуют нижележащим методам и функциям интерфейса DirectX. Может показаться не слишком важным, но за счет комбинации ряда приемов библиотека образует гораздо более удобную и продуктивную модель программирования для DirectX. Первый прием — использование перечислений, видимость которых ограничена определенной областью (scoped enumerations). В DirectX-семействе API определен ошеломляющий массив констант, многие из которых являются традиционными перечислимыми или флагами. Они не имеют строгой типизации, их трудно найти, и они плохо ладят с Visual Studio IntelliSense. Если на минуту забыть о параметрах фабрики, то для создания Direct2D-фабрики вам понадобится следующее:

Первый параметр функции D2D1CreateFactory — перечислимое, но поскольку оно не относится к перечислению с ограниченной областью видимости, оно плохо поддается распознаванию с помощью Visual Studio IntelliSense. Эти традиционные перечисления обеспечивают некоторую безопасность типов, но не полную. При ошибке в период выполнения вы в лучшем случае получите код E_INVALIDARG. Не знаю, как вы, но я предпочитаю отлавливать такие ошибки на этапе компиляции, а еще лучше вообще избегать их:

И вновь мне не нужно тратить время на то, чтобы выяснить, какая новейшая версия интерфейса Direct2D-фабрики вызывается. Здесь явно самое крупное преимущество — продуктивность труда. Конечно, DirectX API — нечто гораздо большее простого создания и вызова методов COM-интерфейсов. Для увязки различных свойств и параметров используются многие старые структуры данных. Хороший пример — описание цепочки замен. Учитывая, сколько в ней членов, некоторые из которых весьма туманны, я мог бы никогда не запомнить, как требуется подготавливать эту структуру, не говоря уже о специфике конкретной платформы. И здесь библиотека снова оказывается весьма полезной, предоставляя замену для запутанной структуры DXGI_SWAP_CHAIN_DESC1:

В этом случае осуществляются замены, совместимые на уровне двоичного кода (binary-compatible replacements), которые гарантируют, что DirectX увидит тот же тип, но вы получите нечто чуть более практичное. Это подобно тому, что Microsoft .NET Framework предоставляет с помощью своих P/Invoke-оболочек. Конструктор по умолчанию предлагает исходные значения, подходящие для большинства настольных приложений и приложений Windows Store. Например, вам может понадобиться переопределить это для настольных приложений, чтобы добиться более плавной визуализации при изменении размеров окна:

Данный эффект замены (swap effect), кстати, также требуется при ориентации на Windows Phone 8, но не разрешен в приложениях Windows Store. Поди разбери.

Многие из лучших библиотек позволяют быстро и легко создавать рабочее решение. Рассмотрим конкретный пример. Direct2D предоставляет кисть с линейным градиентом. Формирование такой кисти включает три логических этапа: определение ограничителей градиента (gradient stops), создание набора ограничителей градиента и создание кисти с линейным градиентом на основе этого набора. Как это могло бы выглядеть при прямом использовании Direct2D API, показано на рис. 1.

Читайте также:  Linux dr web workstations

Рис. 1. Создание кисти с линейным градиентом трудным способом

С помощью dx.h все это становится гораздо понятнее на интуитивном уровне:

Хотя этот код не намного короче представленного на рис. 1, он определенно проще в написании, и вероятность того, что вы допустите ошибку, гораздо меньше — особенно за счет полноценной поддержки IntelliSense. В этой библиотеке применяются различные методы для создания более дружелюбной к разработчику модели программирования. В данном случае метод CreateGradientStopCollection перегружается шаблоном функции для логического определения размера массива GradientStop на этапе компиляции, поэтому необходимость в использовании макроса _countof отпадает.

Как насчет обработки ошибок? Что ж, одно из требований при создании такой лаконичной модели программирования — использование исключений для распространения информации об ошибках. Рассмотрим упомянутое ранее определение метода AsDxgi Direct3D-объекта Device:

Это очень распространенный шаблон в библиотеке. Первым делом обратите внимание на то, что этот метод помечен ключевым словом const. Практически все методы в библиотеке являются const, так как единственный член данных — это нижележащий ComPtr и модифицировать его не требуется. В теле метода на свет появляется конечный объект Device. Все библиотечные классы поддерживают семантику перемещения (move semantics), поэтому, хотя может показаться, что осуществляется выполнение нескольких экземпляров, а значит, и нескольких пар AddRef/Release, на самом деле ничего такого в период выполнения не происходит. HR, обертывающая выражение в середине кода, является подставляемой функцией, которая генерирует исключение, если result не соответствует S_OK. Наконец, библиотека всегда будет пытаться вернуть наиболее специфичный класс, чтобы избавить вызвавший код от необходимости дополнительных вызовов QueryInterface.

В предыдущем примере использовался ComPtr-метод CopyTo, который в конечном счете просто вызывает QueryInterface. Вот другой пример из Direct2D:

Этот пример слегка отличается в том смысле, что он напрямую вызывает метод нижележащего COM-интерфейса. Такой шаблон фактически является основой большей части кода библиотеки. Здесь я возвращаю битовую карту, используемую кистью при рисовании. Многие Direct2D-методы возвращают void, как в данном случае, поэтому функция HR для проверки результата не требуется. Однако косвенная адресация к методу GetBitmap может быть не столь очевидной.

Создавая прототипы ранних версий библиотеки, я был вынужден выбирать между трюками с компилятором и трюками с COM. Поначалу я пытался изощряться с C++, используя шаблоны, в частности признаков, характеризующих типы (type traits), и признаков, характеризующих типы компилятора (compiler type traits) (также известных как признаки встроенных типов [intrinsic type traits]). Поначалу это было забавно, но очень быстро мне стало ясно, что я создаю сам себе лишнюю работу.

Как видите, библиотека моделирует отношение «is-a» между COM-интерфейсами как конкретными классами. COM-интерфейсы могут напрямую наследовать только от одного интерфейса. За исключением самого IUnknown каждый COM-интерфейс должен прямо наследовать от другого интерфейса. В конечном счете это ведет вас по всей иерархии типов к IUnknown. Я начал с определения класса для каждого COM-интерфейса. Класс RenderTarget содержит указатель на интерфейс ID2D1RenderTarget, класс DeviceContext — указатель на интерфейс ID2D1DeviceContext. Это кажется вполне разумным, пока вам не понадобится интерпретировать DeviceContext как RenderTarget. В конце концов, интерфейс ID2D1DeviceContext наследует от интерфейса ID2D1RenderTarget. Поэтому было бы логично передать DeviceContext методу, ожидающему RenderTarget как параметр, передаваемый по ссылке.

Увы, система типов в C++ смотрит на это иначе. При таком подходе DeviceContext на самом деле не может наследовать от RenderTarget, а иначе он хранил бы две ссылки. Я опробовал комбинацию семантики перемещения и признаков встроенных типов для корректной передачи ссылок. Это почти сработало, но бывали случаи, где вводилась дополнительная пара AddRef/Release. В итоге это оказалось слишком сложным, и требовалось какое-то более простое решение.

Читайте также:  Max fan control mac os

В COM — в отличие от C++ — имеется четко определенный бинарный контракт (binary contract). В нем вся суть COM. Пока вы придерживаетесь правил, определенных соглашениями, COM вас не подведет. Вы можете вытворять всяческие трюки с COM и использовать C++ к своей выгоде вместо того, чтобы бороться с ним. То есть каждый C++-класс должен хранить не строго типизированный указатель на COM-интерфейс, а просто универсальную ссылку на IUnknown. В этом случае C++ возвращает обратно безопасность типов и свои правила наследования классов (а в последнее время и семантику перемещения). Тем самым я вновь получаю возможность интерпретировать эти COM-«объекты» как C++-классы. С концептуальной точки зрения, я начал с этого:

а закончил вот этим:

Поскольку логическая иерархия, образуемая COM-интерфейсами и их связями, теперь заключена в объектную модель C++, модель программирования в целом становится гораздо более естественной и удобной в использовании. В библиотеке заложено и много другого, так что я советую вам внимательно изучить ее исходный код. На момент написания этой статьи библиотека охватывала почти все аспекты Direct2D и Windows Animation Manager, а также полезные части DirectWrite, Windows Imaging Component (WIC), Direct3D и DXGI. Кроме того, я регулярно расширяю функциональность, поэтому почаще проверяйте появление новых версий этой библиотеки. Наслаждайтесь!

Кенни Керр (Kenny Kerr) — высококвалифицированный программист. Живет в Канаде. Автор учебных курсов для Pluralsight, обладатель звания Microsoft MVP. Ведет блог kennykerr.ca. Кроме того, читайте его заметки в twitter.com/kennykerr.

Выражаю благодарность за рецензирование статьи эксперту Microsoft Ворачаи Чаовеерапраситу (Worachai Chaoweeraprasit).

Direct3D11 C# — Часть 1 — Создание окна и настройка проекта

В этой статье рассказ пойдет о создании окна и подключение SharpDX-библиотек. Мы будем использовать одну из библиотек SharpDX, а именно SharpDX.Window.

SharpDX – это управляемая .NET-оболочка с открытым исходным кодом для создания приложений с помощью DirectX.

И так, создадим наш проект.

Выбираем Console App (.NET Framework).

Теперь нам необходимо скачать SharpDX.Windows библиотеку, в NuGet она называется SharpDX.Desktop. Сделать это можно используя NuGet или скачав библиотеку на свой компьютер и затем подключив через Solution Explorer. Этот метод мы будем использовать в других частях этого цикла статей.

Для этого, нажимаем Tools [ Сервис ] → NuGet Package Manage [ Диспетчер пакетов NuGet ] → Manage NuGet Packages for Solution… [ Управление пакетами NuGet для решения… ].

Нам не понадобятся другие библиотеки на этой уроке. Ищем только SharpDX.Desktop.

Ставим галочку напротив своего проекта и нажимаем кнопку Install.

Закрываем вкладку NuGet – Solution.

Теперь мы можем подключить библиотеку SharpDX.Windows. Другие библиотеки нам не понадобятся, поэтому спокойно их удаляем.

Ну что же теперь можно приступить к написанию кода нашего приложения.

Поехали!
Самое первое, что нам понадобится – это класс, в котором будем происходить работа с окном. Я его назвал Core, так как по сути он является ядром. В нем у нас будет три функции: Run (запускающая функция), Core (конструктор класса), RenderCallback (обработчик событий).

Добавим само окно.

В public Core() добавим описание RenderForm window;

Последнее, что нам нужно сделать, чтобы программа была готова – это в функцию static void Main(string[] args) добавим метод Run() создав экземпляр класса.

Game можно изменить на любое другое слово.

Вот и все. Теперь при запуске мы увидим консоль и окно с названием «SharpDX Tutorial 0».
Спасибо за внимание!

Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.

Оцените статью