Металл эмулирует геометрические шейдеры, используя вычислительные шейдеры - PullRequest
0 голосов
/ 28 мая 2018

Я пытаюсь реализовать отслеживание воксельных конусов в металле.Одним из шагов в алгоритме является вокселизация геометрии с помощью геометрического шейдера.В металле нет геометрических шейдеров, поэтому я пытался эмулировать их с помощью компьютерного шейдера.Я передаю свой буфер вершин в вычислительный шейдер, делаю то, что обычно делает геометрический шейдер, и записываю результат в выходной буфер.Я также добавляю команду рисования в косвенный буфер.Я использую выходной буфер в качестве буфера вершин для моего вершинного шейдера.Это работает нормально, но мне нужно вдвое больше памяти для моих вершин, один для буфера вершин и один для буфера вывода.Есть ли способ напрямую передать выходные данные вычислительного шейдера в вершинный шейдер, не сохраняя его в промежуточном буфере?Мне не нужно сохранять содержимое буфера вывода вычислительного шейдера.Мне просто нужно передать результаты вершинному шейдеру.

Возможно ли это?Спасибо

РЕДАКТИРОВАТЬ

По сути, я пытаюсь эмулировать следующий шейдер из glsl:

#version 450

layout(triangles) in;
layout(triangle_strip, max_vertices = 3) out;

layout(location = 0) in vec3 in_position[];
layout(location = 1) in vec3 in_normal[];
layout(location = 2) in vec2 in_uv[];

layout(location = 0) out vec3 out_position;
layout(location = 1) out vec3 out_normal;
layout(location = 2) out vec2 out_uv;

void main()
{
    vec3 p = abs(cross(in_position[1] - in_position[0], in_position[2] - in_position[0]));

    for (uint i = 0; i < 3; ++i)
    {
        out_position = in_position[i];
        out_normal = in_normal[i];
        out_uv = in_uv[i];

        if (p.z > p.x && p.z > p.y)
        {
            gl_Position = vec4(out_position.x, out_position.y, 0, 1);
        }
        else if (p.x > p.y && p.x > p.z)
        {
            gl_Position = vec4(out_position.y, out_position.z, 0, 1);
        }
        else
        {
            gl_Position = vec4(out_position.x, out_position.z, 0, 1);
        }

        EmitVertex();
    }

    EndPrimitive();
}

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

1 Ответ

0 голосов
/ 28 мая 2018

Вот очень умозрительная возможность, в зависимости от того, что именно должен делать ваш геометрический шейдер.

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

Итак, в коде приложения вычислите количество выходных примитивов и, следовательно, количество выходных вершин, которые будут созданы для заданного количества входных примитивов.,Сделайте отрисовку типа выходного примитива с таким количеством вершин.

Вы бы не предоставили бы буфер с данными выходных вершин в качестве входных данных для этого отрисовки.

Вы бы сделалипредоставить исходный индексный буфер и исходный буфер вершин в качестве входных данных для вершинного шейдера для этого отрисовки.Шейдер вычисляет по идентификатору вершины, для какого выходного примитива он предназначен, и для какой вершины этого примитива (например, для треугольника vid / 3 и vid % 3 соответственно).Из идентификатора выходного примитива он вычислит, какой входной примитив сгенерировал бы его в исходном геометрическом шейдере.

Шейдер будет искать индексы для этого входного примитива из буфера индексов, а затем данные вершин избуфер вершин.(Это может быть чувствительным к различию между списком треугольников и полосой треугольника, например.) Это применило бы к этим данным любое затенение вершин предгеометрического шейдера.Затем он выполняет ту часть вычисления геометрии, которая вносит вклад в идентифицированную вершину идентифицированного выходного примитива.Как только он вычислил выходные данные вершин, вы можете применить любое затенение вершин пост-геометрического шейдера (?), Которое вы хотите.Результатом будет то, что он вернет.

Если геометрический шейдер может создавать переменное количество выходных примитивов на входной примитив, ну, по крайней мере, у вас есть максимальное число.Таким образом, вы можете нарисовать максимальное потенциальное количество вершин для максимального потенциального числа выходных примитивов.Вершинный шейдер может выполнять вычисления, необходимые для того, чтобы выяснить, действительно ли геометрический шейдер создал этот примитив.Если нет, вершинный шейдер может организовать отсечение всего примитива, либо расположив его вне усеченного конуса, либо используя свойство [[clip_distance]] выходных данных вершины.

Это позволяет избежать сохранения сгенерированногопримитивы в буфере.Однако из-за этого графический процессор выполняет несколько вычислений вершинного шейдера и геометрического шейдера до геометрического шейдера.Конечно, оно будет распараллелено, но все же может быть медленнее, чем то, что вы делаете сейчас.Кроме того, он может не справиться с некоторыми оптимизациями при извлечении индексов и данных о вершинах, что возможно при использовании более обычных вершинных шейдеров.


Вот пример преобразования вашего геометрического шейдера:

#include <metal_stdlib>
using namespace metal;

struct VertexIn {
    // maybe need packed types here depending on your vertex buffer layout
    // can't use [[attribute(n)]] for these because Metal isn't doing the vertex lookup for us
    float3 position;
    float3 normal;
    float2 uv;
};

struct VertexOut {
    float3 position;
    float3 normal;
    float2 uv;
    float4 new_position [[position]];
};


vertex VertexOut foo(uint vid [[vertex_id]],
                     device const uint *indexes [[buffer(0)]],
                     device const VertexIn *vertexes [[buffer(1)]])
{
    VertexOut out;

    const uint triangle_id = vid / 3;
    const uint vertex_of_triangle = vid % 3;

    // indexes is for a triangle strip even though this shader is invoked for a triangle list.
    const uint index[3] = { indexes[triangle_id], index[triangle_id + 1], index[triangle_id + 2] };
    const VertexIn v[3] = { vertexes[index[0]], vertexes[index[1]], vertexes[index[2]] };

    float3 p = abs(cross(v[1].position - v[0].position, v[2].position - v[0].position));

    out.position = v[vertex_of_triangle].position;
    out.normal = v[vertex_of_triangle].normal;
    out.uv = v[vertex_of_triangle].uv;

    if (p.z > p.x && p.z > p.y)
    {
        out.new_position = float4(out.position.x, out.position.y, 0, 1);
    }
    else if (p.x > p.y && p.x > p.z)
    {
        out.new_position = float4(out.position.y, out.position.z, 0, 1);
    }
    else
    {
        out.new_position = float4(out.position.x, out.position.z, 0, 1);
    }

    return out;
}
...