Знакомство с Radeon Rays SDK

31 марта 2018, суббота 16:58
для раздела Блоги

Введение

Radeon Rays - это библиотека, которая позволяет ускорять вычисление пересечений при расчете трассировки лучей и имеет базовую поддержку разнородных систем. AMD разработала Radeon Rays  для того, чтобы помочь разработчикам получить максимальную отдачу от GPU и APU AMD, а также избавить их от поддержки аппаратно-зависимого кода. Radeon Rays предоставляет четко определенный C++ API для создания сцены и выполнения запросов пересечения асинхронных лучей. Текущая реализация основана на OpenCL, это означает, что Radeon Rays работает на всех платформах, которые соответствуют стандарту OpenCL 1.2. При этом не обязательно использовать именно оборудование от AMD или определенную операционную систему. Radeon Rays выпускается под лицензией GPUOpen, то есть при необходимости вы можете модифицировать библиотеку в соответствии с вашими запросами. Однако, использование Radeon Rays с помощью API гарантирует совместимость и наилучшую производительность при использовании настоящих и будущих продуктов AMD.


Окклюзия окружения (затухание рассеянного света в закрытых областях)


Глобальное освещение и тени



FirePro W9100





Сцена "Дворец Спонца"
(273 тыс. треугольников)

Основной просчет
(мс / млн лучей/с)
Просчет теней
(2 x основной, мс / млн лучей/с)
Дополнительный просчет (мс / млн лучей/с)

Одно пересечение

6 / 384
20 / 230
27 / 85

Два пересечения

7,8 / 295
26 / 177
32,8 / 70

Три пересечения

10 / 230
29,8 / 154
39,7 / 58



Сцена "Город Рунгхольт"
(6,7 млн треугольников)






Одно пересечение

5 / 460
6 / 768
17.9 / 95

Одно пересечение

11,6 / 199
22,8 / 202
33,4 / 70

Три пересечения

6,6 / 351
20,4 / 225
73 / 31,5



MacPro (FirePro D700 x2)





Сцена "Дворец Спонца"
(273 тыс. треугольников)

Основной просчет
(мс / млн лучей/с)
Просчет теней
(2 x основной, мс / млн лучей/с)
Дополнительный просчет (мс / млн лучей/с)

Одно пересечение

8 / 288
17 / 271
26 / 88

Два пересечения

9 / 256
24 / 200
30 / 77

Три пересечения

13 / 177
22 / 208
34 / 67







Сцена "Город Рунгхольт"
(6,7 млн треугольников)





Одно пересечение

6 / 384
5,5 / 837
21 / 83

Два пересечения

7 / 329
18 / 242
56 / 40

Три пересечения

6 / 384
22,5 / 204
70 / 31


Системные требования

  • ПК с 64-битной версией Windows 7, Windows 8, Windows 8.1, Windows 10, Linux или MacOS X
  • Любое устройство, совместимое с OpenCL 1.2
  • Установленная IDE Microsoft Visual Studio 2013 для компилирования образцового визуализатора
  • Для запуска предкомпилированных образцовых библиотек требуется распростаняемый пакет Visual Studio 2013


Начало работы с API

Концепция данного API и процесс работы с ним относительно просты. Для задействования API необходимо включить следующий заголовок:

#include 

Все классы и функции определены в соответствующем пространстве имен, поэтому для доступа к ним требуется явно задать пространство имен или вставить следующий оператор "using":

using namespace RadeonRays;


Перечисление устройств

Далее идет конфигурирование устройств пересечения средствами Radeon Rays device emuneration API. Для этого в классе IntersectionApi содержатся статические методы, представляющие все устройства, которые способны выполнять запросы на пересечение.

//Получить количество доступных в системе устройств
static int GetIntersectionDeviceCount();
//Получить информацию о конкретном устройстве
static IntersectionDeviceInfo const& GetIntersectionDeviceInfo(int devidx);





Поскольку идентификация устройств осуществляется по индексам, то вам потребуется сохранить необходимые для последующего доступа. Следующая конструкция демонстрирует осуществление выбора устройства GPU OpenCL с использованием enumeration API:

int gpuidx = -1;
for (int idx=0; idx < IntersectionApi::GetIntersectionDeviceCount(); ++idx)
{
    IntersectionDeviceInfo const&
    devinfo(IntersectionApi::GetIntersectionDeviceInfo(idx));

    if (devinfo.type == IntersectionDeviceInfo::kGpu &&
    devinfo.apis & IntersectionDeviceInfo::kOpenCl)
    {
        gpuidx = idx;
    }
}


Инициализация API

Затем создается экземпляр API с указанием выбранного устройства, что показано в следующем примере:

int apitype = IntersectionDeviceInfo::kOpenCl;
api_ = IntersectionApi::Create(0, &gpuidx, &apitype, 1);

Обратите внимание, что функция Create() принимает массив устройств. Однако, в текущей версии библиотеки пока поддерживается только одно устройство. Первый параметр также зарезервирован для использования в будущем и должен быть равен 0.

Во время инициализации библиотеки может возникнуть ошибка (проблема выполнения OpenCL кода, проблемы с правами доступа и т. д.). Чтобы сообщить причину ошибки, библиотека использует производную от интерфейса Exception, с помощью которой можно перехватить ошибку и получить ее текстовое описание.


Создание геометрии

После получения экземпляра API можно перейти к этапу создания геометрии. Текущая версия API поддерживает сетки из треугольников, квадратов, смешанные сетки, а также их копирование. Следующий фрагмент кода показывает, как создать простую сетку, состоящую из одного треугольника:

// Вершины сетки
float vertices[] = {
    0.f,0.f,0.f,
    0.f,1.f,0.f,
    1.f,0.f,0.f
};

int indices[] = {0, 1, 2};

// Количество вершин плоскости
int numfaceverts[] = { 3 };
Shape* shape = api_->CreateMesh(vertices, 3, 3*sizeof(float), indices, 0, numfaceverts, 1));

В данном случае 0 используется для обозначения шага индексов, то есть здесь индексы плотно упакованы. Хотя метод является блокирующим, тем не менее его можно безопасно вызывать из нескольких потоков до тех пор, эти вызовы не чередуются с вызовами методов бросания лучей.





Следующий шаг заключается в построении сцены из нескольких сеток. Для добавления сеток в текущую сцену или удаления их из сцены (которая неявно определена экземпляром API), могут использоваться следующие методы:

api_->AttachShape(shape);
api_->DetachShape(shape);

Это достаточно быстрые методы, поскольку они не используют какие-либо ресурсозатратные операции. Фактическая передача данных и ускорение построения структуры откладываются до вызова метода Commit(). Данный метод должен вызываться каждый раз, когда в сцене что-либо было изменено.

api_->Commit()

Это блокирующий метод и он не может быть вызван одновременно с другими методами API.


Копирование геометрии

Геометрия может быть скопирована, то есть одна и та же геометрия может быть использована для разных объектов, имеющих разные преобразования мировых координат. Для создания копии сетки используется следующий код:

Shape* instance = api_->CreateInstance(shape);

Получившаяся копия может быть прикреплена к сцене так же, как и любая обычная фигура. Копирование позволяет создавать чрезвычайно сложные сцены, задействуя умеренный объем памяти, с помощью повторного использования геометрии.


Простые запросы на пересечение

Как только геометрия будет зафиксирована методом Commit(), можно выполнять запросы на пересечение. Поскольку библиотека разработана специально для разнородных архитектур, то в качестве входных данных для методов она охотнее принимает группу лучей, а не отдельные лучи. Как правило, чем больше эта группа, тем лучше, поскольку устройства, обладающие высоким параллелизмом, могут загружаться более полно и лучше скрывать задержки памяти.

Вообще существует два типа запросов на пересечение:

  • Запрос ближайшего столкновения (пересечение)
  • Запрос любого столкновения (окклюзия)

Методы для запроса ближайшего столкновения именуются IntersectBatch(), методы для окклюзии также называются IntersectBatch() (с предикатом окклюзии).





Простейшая версия запроса на пересечение выглядит следующим образом:

// Лучи
ray rays[3];

// Подготовка лучей
rays[0].o = float4(0.f,0.f,-10.f, 1000.f);
rays[0].d = float3(0.f,0.f,1.f);
rays[1].o = float4(0.f,0.5f,-10.f, 1000.f);
rays[1].d = float3(0.f,0.f,1.f);
rays[2].o = float4(0.5f,0.f,-10.f, 1000.f);
rays[2].d = float3(0.f,0.f,1.f);

// Пересечение и данные столкновения
Intersection isect[3];

// Пересечение
ASSERT_NO_THROW(api_->IntersectBatch(rays, 3, isect));


Данный метод принимает массив лучей. Схема лучевой структуры такая:

o.xyz
Начало луча
d.xyz
Направление луча
o.w
Максимальная дальность луча
d.w
Временная метка для размытия движения


В ответ за запрос ближайшего пересечения возвращается массив структур пересечений в соответствии со следующей схемой:

uvwt.xyz
Параметрические координаты столкновения (xy для треугольников и квадратов)
uvwt.w
Расстояние удара вдоль луча
shapeid
Идентификатор фигуры
primid
Идентификатор примитива фигуры


Идентификатор фигуры соответствует значению, которое либо автоматически присваивается фигуре во время создания API, либо устанавливается вручную с использованием метода Shape::SetId(). Идентификатор примитива - это начинающийся с нуля индекс примитива фигуры (в том порядке, в котором они были переданы методу CreateMesh). Если пересечение не было обнаружено, то оба параметра устанавливаются в значение kNullId.

Запрос на окклюзию имеет такой же формат, но возвращает уже массив целых чисел, где каждая запись равна либо -1 (без пересечения), либо 1 (пересечение).


Асинхронные запросы

Описанные выше методы являются блокирующими, но существует возможность создания асинхронных запросов для лучей с использованием следующего метода:

// Пересечение
Event* event = nullptr;
api_->IntersectBatch(rays, numrays, intersections, nullptr, &event);

Этот метод создает асинхронный запрос, возвращающий указатель на объект Event (событие). Это событие может использоваться для отслеживания состояния выполнения запроса или построения цепочек зависимостей. Для отслеживания статуса могут использоваться следующие методы:

event->complete();
event->Wait();

Первый вызов возвращает true, если метод завершен, и содержимое буфера результатов доступно. Второй ожидает завершения выполнения.

Кроме того, существует возможность передать объект Event другому запросу, тем самым устанавливая зависимость. В этом случае второй запрос начнет свое выполнение только после завершения первого:

api_->IntersectBatch(rays, numrays, intersections, event, nullptr);


API для памяти

Ранее описанные запросы пересечений работают в памяти CPU. Однако библиотека также предоставляет интерфейс, который можно использовать для создания буферов в памяти устройства (например, памяти GPU). Схема буферов по сути такая же, как и для CPU. Чтобы создать буфер в памяти удаленного устройства, используется следующий метод класса IntersectionApi:

virtual Buffer* CreateBuffer(size_t size, void* initdata) const = 0;

Буфер можно разместить и удалить, используя следующие вызовы:

virtual void MapBuffer(Buffer const* buffer, MapType type, size_t offset, size_t size, void** data, Event** event) const = 0;
virtual void UnmapBuffer(Buffer const* buffer, void* ptr, Event** event) const = 0;

Обратите внимание, что эти операции являются асинхронными, и для обеспечения слаженной работы, необходимо установить правильные зависимости для запросов пересечения.


Совместимость с OpenCL

Существует способ использования уже имеющихся контекстов OpenCL, а также совместного использования существующих буферов OpenCL в коде приложения. Чтобы создать экземпляр API с использованием имеющегося контекста OpenCL, можно вызвать следующие методы:

Cl_int status = clGetDeviceIDs(platform[0], type, 1, &device, nullptr);
cl_context rawcontext = clCreateContext(nullptr, 1, &device, nullptr, nullptr, &status);
cl_command_queue queue = clCreateCommandQueue(rawcontext, device, 0, &status);
api_ = IntersectionApi::CreateFromOpenClContext(0, rawcontext, &device, &queue, 1);

Необходимо убедиться, что в контексте, переданном в API, присутствует только одно устройство.

Следующий код используется для расшаривания буфера:

cl_mem rays_buffer = clCreateBuffer(rawcontext, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, sizeof(ray), &r, &status);

rays = api_->CreateFromOpenClBuffer(rays_buffer);


Глобальные параметры

С помощью параметров можно настраивать различные составляющие Radeon Rays. В настоящее время они в основном используются для управления алгоритмами обхода и ускорением построения структур. Для получения полного набора поддерживаемых опций следует обратиться к заголовочному файлу. Чтобы установить параметр можно использовать такой вызов:

api_->SetOption(“bvh.builder”, “sah”);


Стратегии ускорения вычислений

По умолчанию для ускорения вычислений используется иерархия ограничивающего объема (BHV) с использованием метода пространственного срединного разбиения. С помощью глобальной опции можно активировать SAH-конструктор, что позволит обеспечить высокую скорость построения ограничивающих объемов и показать приличную производительность при расчете пересечений.

Если потребуется быстро перестроить иерархию ограничивающего объема (например, если геометрические объекты часто меняют свое положение), то можно задействовать двухуровневую BHV, которая при геометрических преобразованиях не будет каждый раз воссоздаваться заново.

В сценах, где используются копии или размытие в движении, двухуровневая иерархия ограничивающего объема применяется по умолчанию.


Удаление объектов

Все объекты, созданные с помощью Radeon Rays API, должны быть удалены, когда приложение завершает свою работу. Для удаления фигур, буферов и событий доступны следующие методы:

api_->DeleteShape(shape);
api_->DeleteBuffer(buffer);
api_->DeleteEvent(event);

Также следует уничтожить сам экземпляр API:

IntersectionApi::Delete(api_);


Источник: gpuopen

Оценитe материал

Возможно вас заинтересует

Популярные новости

Сейчас обсуждают