|
Главная
Статьи
Ссылки
Скачать
Скриншоты
Юмор
Почитать
Tools
Проекты
Обо мне
Гостевая
Форум
|
Из предыдущий статей вы уже наверное поняли, что написание кода под Vulkan требует довольно много кода (хотя вы пока еще даже не представляет6 насколько много :)).
С другой стороны современный OpenGL тоже не слишком прост - если все делать руками без использования различных библиотек и wrapper-ов, то тоже нужно будет писать совсем не мало кода. В своих примерах я не пишу на "сыром" OpenGL, а использую библиотеки и обертки, что позволило заметно сократить размер примеров, сведя большинство примеров к 1-2 страницам кода.
Давайте применим этот же подход для написания кода на Vulkan.
Мы завернем все основные сущности Vulkan в удобные для использования классы.
При этом в отличии от распространенной библиотеки Vulkan.hpp мы будем заворачивать не
базовые структуры Vk*, а гораздо более высокоуровневые сущности, чтобы сделать получающийся код
гораздо компактнее и нагляднее.
Обратите внимание, что для всех сущностей, являющихся обертками объектов Vulkan, удалены
copy-constructor и оператор присваивания.
Но для того, чтобы их можно было складывать в контейнеры, добавлены move-версии данные операций.
Кроме того обычно конструктор не создает сущностей Vulkan, для этого будет использоваться специальный метод create.
При этом, как и ранее, мы будем использовать библиотеку VMA для выделения памяти под буфера и
изображения. Также мы будем использовать библиотеку GLFW для работы с окнами и обработки
событий (она поддерживает не только OpenGL, но и Vulkan).
Мы будем использовать библиотеку GLM для работы с векторами и матрицами, только нужно будет определить
символ GLM_FORCE_DEPTH_ZERO_TO_ONE, чтобы матрица проектирования строилась именно для Vulkan
(в нем zeye изменяется от 0 до 1, а не от -1 до 1 как в OpenGL).
Первым и самым главным нашим классом будет класс Device.
Он будет отвечать сразу на несколько базовых сущностей Vulkan - инстанс (экземпляр). физическое устройство
и логическое устройство. Также в нем будут храниться очереди и различная информация об устройстве (properties, features).
bool create ( VkInstance _instance, VkPhysicalDevice _physicalDebice,
VkSurfaceKHR surface, const std::vector<const char*>& deviceExtensions,
const std::vector<const char*>& validationLayers );
Самыми важными методами этого класса являются create, отвечающий за создание всех содержащихся сущностей, и clean,
обеспечивающий их освобождение (например для последующего пересоздания).
void clean ()
Метод allocCommandBuffers создает заданное количество командных буферов (точнее оберток, их содержащих внутри себя).
Метод freeCommandBuffer освобождает созданный командный буфер и все связанные с ним ресурсы.
void freeCommandBuffer ( CommandBuffer& buffer );
std::vector<CommandBuffer> allocCommandBuffers ( uint32_t count );
Для выделения памяти используется библиотека VMA. Однако при этом поддерживается также вариант не использовать библиотеку VMA,
в этом случае используется класс GpuMemory как абстракция выделенного блока памяти GPU. Главными методами этого класса являются alloc и clean служащие для выделения и освобождения
блока памяти GPU.
void clean ()
bool alloc ( Device& _device, VkMemoryRequirements memRequirements, VkMemoryPropertyFlags properties )
Методы copy, map и unmap служат для копирования памяти GPU и управления отображением отображения памяти GPU в память CPU
(если тип выделенной памяти это поддерживает).
Для инкапсуляции буферов будет использоваться класс Buffer.
Самый главный метод этого класса это create, создающий буфер в памяти GPU с заданным типом памяти.
Метод clean освобождает все ресурсы GPU, связанные с этим буфером.
void clean ()
bool create ( Device& dev, VkDeviceSize size, VkBufferUsageFlags usage, int mappable )
Также есть несколько вариантов перегруженного метода copy, служащего для копирования данных в буфер/из буфера.
bool copy ( const void * ptr, VkDeviceSize size, size_t offs = 0 )
template <typename T>
bool copy ( const std::vector<T>& data )
{
return copy ( data.data (), data.size () * sizeof ( T ) );
}
template <typename T>
bool copy ( const T& data )
{
return copy ( &data, sizeof ( T ) );
}
С помощью метода copyBuffer можно скопировать в текущий буфер содержимое другого буфера.
void copyBuffer ( SingleTimeCommand& cmd, Buffer& fromBuffer, VkDeviceSize size )
От класса Buffer наследуется класс PersistentBuffer, соответствующий случаю, когда для буфера все время поддерживается
отображение в память CPU (без необходимости постоянно создавать и освобождать такое отображение).
Метод getPtr служит для получения указателя на отображенную память CPU.
От класса
Для доступа к отдельным элементам на стороне CPU служат перегруженные операторы
Также мы сделаем обертки для классов синхронизации (semaphore, fence и event).
Класс
Класс
Класс
В этой статье мы рассмотрели самые базовые классы, во следующей мы продолжим рассмотрение используемых классов.
Рассматриваемые классы можно найти в репозитории https://github.com/steps3d/vulkan-with-classes.
PersistentBuffer наследуется шаблонный класс UniformT.
Обратите внимание на метод create - он получает ссылку на устройство, число элементов в массиве и выравнивание
для этих элементов в массиве (в ряде случаев выравнивание отдельных элементов играет очень важную роль).
bool create ( Device& device, VkBufferUsageFlags usage, int n = 1, int al = 1 )
-> и [].
T * operator -> () const
return getPtr ();
}
T& operator [] ( int i )
{
return *(T *)(i*itemSize + (char *)getPtr ());
}
Semaphore является оберткой семафора в Vulkan, как и для остальных классов, его
конструктор не создает семафора в Vulkan (но его деструктор уничтожает уже созданный).
Для создания семафора служит метод create, получающий на вход ссылку на объект Device.
Метод clean уничтожает созданный семафор, освобождая выделенные ресурсы.
Метод signal переводит семафор во signalled-состояние, помещая соответствующий запрос в переданную очередь.
class Semaphore
{
VkSemaphore handle = VK_NULL_HANDLE;
Device * device = nullptr;
public:
Semaphore () = default;
Semaphore ( Semaphore&& s )
{
std::swap ( handle, s.handle );
}
Semaphore ( const Semaphore& ) = delete;
~Semaphore ()
{
clean ();
}
Semaphore& operator = ( const Semaphore& ) = delete;
bool isOk () const
{
return device != nullptr && handle != VK_NULL_HANDLE;
}
VkSemaphore getHandle () const
{
return handle;
}
void clean ()
{
if ( handle != VK_NULL_HANDLE )
vkDestroySemaphore ( device->getDevice (), handle, nullptr );
handle = VK_NULL_HANDLE;
}
void create ( Device& dev )
{
device = &dev;
VkSemaphoreCreateInfo semaphoreCreateInfo = {};
semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
if ( vkCreateSemaphore ( device->getDevice (), &semaphoreCreateInfo, nullptr, &handle ) != VK_SUCCESS )
fatal () << "Semaphore: error creating" << Log::endl;
}
void signal ( VkQueue queue )
{
VkSubmitInfo submitInfo = {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = &handle;
vkQueueSubmit ( queue, 1, &submitInfo, VK_NULL_HANDLE );
vkQueueWaitIdle ( queue );
}
};
Fence служит оберткой для объекта fence.
Метод create как обычно служит для создания соответствующего объекта (параметр signalled позволяет создавать
объект сразу во взведенном состоянии).
Метод clean уничтожает созданный объект.
При помощи метода reset можно сбросить состояние fence, метод status возвращает является ли текущее состояние
взведенным.
При помощи метода wait можно подождать пока объект не будет переведен во взведенное состояние.
Для этого метода задается параметр timeout, задающий максимальное время ожидания в наносекундах.
class Fence
{
VkFence fence = VK_NULL_HANDLE;
Device * device = nullptr;
public:
Fence () = default;
Fence ( Fence&& f )
{
std::swap ( fence, f.fence );
}
Fence ( const Fence& ) = delete;
~Fence ()
{
clean ();
}
Fence& operator = ( const Fence& ) = delete;
bool isOk () const
{
return device != nullptr && fence != VK_NULL_HANDLE;
}
VkFence getHandle () const
{
return fence;
}
void clean ()
{
if ( fence != VK_NULL_HANDLE )
vkDestroyFence ( device->getDevice (), fence, nullptr );
fence = VK_NULL_HANDLE;
}
void create ( Device& dev, bool signaled = false )
{
VkFenceCreateInfo fenceInfo = {};
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fenceInfo.flags = signaled ? VK_FENCE_CREATE_SIGNALED_BIT : 0;
device = &dev;
if ( vkCreateFence ( device->getDevice (), &fenceInfo, nullptr, &fence ) != VK_SUCCESS )
fatal () << "Fence: error creating" << Log::endl;
}
// set state to unsignalled from host
void reset ()
{
vkResetFences ( device->getDevice (), 1, &fence );
}
// VK_SUCCESS - signaled
// VK_NOT_READY unsignaled
VkResult status () const
{
return vkGetFenceStatus ( device->getDevice (), fence );
}
// wait for fence, timeout in nanoseconds
bool wait ( uint64_t timeout )
{
return vkWaitForFences ( device->getDevice (), 1, &fence, VK_TRUE, timeout ) == VK_SUCCESS;
}
};
Event служит оберткой для события (event).
Как и со многими другим объектами для создания мы используем метод create, для уничтожения - метод clean.
Метод status возвращает текущее состояние события - наступили ли оно на данный момент времени или нет.
Метод reset сбрасывает событие (в ненаступившее состояние), метод signal переводит его в наступившее состояние.
class Event
{
VkEvent event = VK_NULL_HANDLE;
Device * device = nullptr;
public:
Event () = default;
Event( Event&& f )
{
std::swap ( event, f.event );
}
Event ( const Event& ) = delete;
~Event ()
{
clean ();
}
Event& operator = ( const Event& ) = delete;
bool isOk () const
{
return device != nullptr && event != VK_NULL_HANDLE;
}
VkEvent getHandle () const
{
return event;
}
void clean ()
{
if ( event != VK_NULL_HANDLE )
vkDestroyEvent ( device->getDevice (), event, nullptr );
event = VK_NULL_HANDLE;
}
void create ( Device& dev )
{
VkEventCreateInfo eventInfo = {};
eventInfo.sType = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO;
device = &dev;
if ( vkCreateEvent ( device->getDevice (), &eventInfo, nullptr, &event ) != VK_SUCCESS )
fatal () << "Event: error creating" << Log::endl;
}
bool status () const
{
VkResult res = vkGetEventStatus ( device->getDevice (), event );
return res == VK_EVENT_SET;
}
// set state to unsignalled from host
void reset ()
{
vkResetEvent ( device->getDevice (), event );
}
void signal () // set
{
vkSetEvent ( device->getDevice (), event );
}
};