Ошибка при использовании Metal Indirect Command Buffer: Фрагментный шейдер нельзя использовать с косвенными командными буферами

Я работаю над приложением на основе Metal MTKView, которое использует преимущества архитектуры A11 TBDR для выполнения отложенного затенения за один проход рендеринга. В качестве справки я использовал пример кода Deferred Lighting от Apple, и он отлично работает.

Я хотел бы попробовать изменить проход буфера геометрии, чтобы он управлялся графическим процессором, используя функцию Indirect Command Buffer в Metal 2 на оборудовании A11.

Я использовал кодирование косвенных командных буферов в образце кода графического процессора от Apple как моя основная точка отсчета для этого. Я могу запустить этот образец на своем iPhone XR (хотя, вероятно, не по теме, прокрутка не плавная, она дрожит).

Однако я сталкиваюсь с трудностями со своим собственным кодом, когда пытаюсь переместить проход буфера геометрии в буфер косвенных команд. Когда я устанавливаю supportIndirectCommandBuffers на true на MTLRenderPipelineDescriptor конвейера буфера геометрии, device.makeRenderPipelineState выходит из строя с ошибкой

AGXMetalA12 Code = 3 «Фрагментный шейдер нельзя использовать с косвенными командными буферами»

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

SharedTypes.h

Заголовок используется Metal и Swift

#ifndef SharedTypes_h
#define SharedTypes_h

#ifdef __METAL_VERSION__

#define NS_CLOSED_ENUM(_type, _name) enum _name : _type _name; enum _name : _type
#define NSInteger metal::int32_t

#else

#import <Foundation/Foundation.h>

#endif

#include <simd/simd.h>

typedef struct {
    uint32_t meshId;
    matrix_float3x3 normalViewMatrix;
    matrix_float4x4 modelMatrix;
    matrix_float4x4 shadowMVPTransformMatrix;
} InstanceData;

typedef struct {
    vector_float3 cameraPosition;
    float voxelScale;
    float blockScale;
    vector_float3 lightDirection;
    matrix_float4x4 viewMatrix;
    matrix_float4x4 projectionMatrix;
    matrix_float4x4 projectionMatrixInverse;
    matrix_float4x4 shadowViewProjectionMatrix;
} VoxelUniforms;

typedef NS_CLOSED_ENUM(NSInteger, BufferIndex)
{
    BufferIndexInstances  = 0,
    BufferIndexVertices = 1,
    BufferIndexIndices = 2,
    BufferIndexVoxelUniforms = 3,
};

typedef NS_CLOSED_ENUM(NSInteger, RenderTarget)
{
    RenderTargetLighting = 0,
    RenderTargetNormal_shadow = 1,
    RenderTargetVoxelIndex = 2,
    RenderTargetDepth = 3,
};

#endif /* SharedTypes_h */

Шейдер GBuffer

#include <metal_stdlib>
using namespace metal;
#include "../SharedTypes.h"

struct VertexIn {
    packed_half3 position;
    packed_half3 texCoord3D;
    half ambientOcclusion;
    uchar normalIndex;
};

struct VertexInOut {
    float4 position [[ position ]];
    half3 worldPos;
    half3 eyeNormal;
    half3 localPosition;
    half3 localNormal;
    float eyeDepth;
    float3 shadowCoord;
    half3 texCoord3D;
};

vertex VertexInOut gBufferVertex(device InstanceData* instances [[ buffer( BufferIndexInstances ) ]],
                                 device VertexIn* vertices [[ buffer( BufferIndexVertices ) ]],
                                 constant VoxelUniforms &uniforms [[ buffer( BufferIndexVoxelUniforms ) ]],
                                 uint vid [[ vertex_id ]],
                                 ushort iid [[ instance_id ]])
{
    InstanceData instance = instances[iid];
    VertexIn vert = vertices[vid];
    VertexInOut out;
    float4 position = float4(float3(vert.position), 1);
    float4 worldPos = instance.modelMatrix * position;
    float4 eyePosition = uniforms.viewMatrix * worldPos;
    out.position = uniforms.projectionMatrix * eyePosition;
    out.worldPos = half3(worldPos.xyz);
    out.eyeDepth = eyePosition.z;

    half3 normal = normals[vert.normalIndex];
    out.eyeNormal = half3(instance.normalViewMatrix * float3(normal));
    out.shadowCoord = (instance.shadowMVPTransformMatrix * position).xyz;

    out.localPosition = half3(vert.position);
    out.localNormal = normal;
    out.texCoord3D = half3(vert.texCoord3D);
    return out;
}

fragment GBufferData gBufferFragment(VertexInOut in [[ stage_in ]],
                                     constant VoxelUniforms &uniforms [[ buffer( BufferIndexVoxelUniforms ) ]],
                                     texture3d<ushort, access::sample> voxelMap [[ texture(0) ]],
                                     depth2d<float> shadowMap [[ texture(1) ]],
                                     texture3d<half, access::sample> fogOfWarMap [[ texture(2) ]]
                                     ) {
    // voxel index
    half3 center = round(in.texCoord3D);
    uchar voxIndex = voxelMap.read(ushort3(center)).r - 1;

    // ambient occlusion
    half3 neighborPos = center + in.localNormal;
    half3 absNormal = abs(in.localNormal);
    half2 texCoord2D = tc2d(in.localPosition / uniforms.voxelScale, absNormal);
    half ao = getAO(voxelMap, neighborPos, absNormal.yzx, absNormal.zxy, texCoord2D);

    // shadow
    constexpr sampler shadowSampler(coord::normalized,
                                    filter::linear,
                                    mip_filter::none,
                                    address::clamp_to_edge,
                                    compare_func::less);

    float shadow_sample = ambientLightingLevel;
    for (short i = 0; i < shadowSampleCount; i++){
        shadow_sample += shadowMap.sample_compare(shadowSampler, in.shadowCoord.xy + poissonDisk[i] * 0.002, in.shadowCoord.z - 0.0018) * shadowContributionPerSample;
    }
    shadow_sample = min(1.0, shadow_sample);

    //fog-of-war
    half fogOfWarSample = fogOfWarMap.sample(fogOfWarSampler, (float3(in.worldPos) / uniforms.blockScale) + float3(0.5, 0.4, 0.5)).r;
    half notVisible = max(fogOfWarSample, 0.5h);

    // output
    GBufferData out;
    out.normal_shadow = half4(in.eyeNormal, ao * half(shadow_sample) * notVisible);
    out.voxelIndex = voxIndex;
    out.depth = in.eyeDepth;
    return out;
};

Настройка трубопровода

extension RenderTarget {

    var pixelFormat: MTLPixelFormat {
        switch self {
        case .lighting: return .bgra8Unorm
        case .normal_shadow: return .rgba8Snorm
        case .voxelIndex: return .r8Uint
        case .depth: return .r32Float
        }
    }

    static var allCases: [RenderTarget] = [.lighting, .normal_shadow, .voxelIndex, .depth]
}

public final class GBufferRenderer {
    private let renderPipelineState: MTLRenderPipelineState
    weak var shadowMap: MTLTexture?

    public init(depthPixelFormat: MTLPixelFormat, colorPixelFormat: MTLPixelFormat, sampleCount: Int = 1) throws {
        let library = try LibraryMonad.getLibrary()
        let device = library.device
        let descriptor = MTLRenderPipelineDescriptor()
        descriptor.vertexFunction = library.makeFunction(name: "gBufferVertex")!
        descriptor.fragmentFunction = library.makeFunction(name: "gBufferFragment")!
        descriptor.depthAttachmentPixelFormat = depthPixelFormat
        descriptor.stencilAttachmentPixelFormat = depthPixelFormat
        descriptor.sampleCount = sampleCount
        for target in RenderTarget.allCases {
            descriptor.colorAttachments[target.rawValue].pixelFormat = target.pixelFormat
        }
        // uncomment below to trigger throw
        // descriptor.supportIndirectCommandBuffers = true
        renderPipelineState = try device.makeRenderPipelineState(descriptor: descriptor) // throws "Fragment shader cannot be used with indirect command buffers"
    }

    public convenience init(mtkView: MTKView) throws {
        try self.init(depthPixelFormat: mtkView.depthStencilPixelFormat, colorPixelFormat: mtkView.colorPixelFormat, sampleCount: mtkView.sampleCount)
    }
}

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

Я попытался урезать фрагментный шейдер, чтобы просто вернуть постоянные значения для GBuffers, и затем makeRenderPipelineState успешно, но когда я снова добавляю выборку текстуры, он снова начинает жаловаться. Я не могу сказать, что именно ему не нравится во фрагментном шейдере.


person OliverD    schedule 01.04.2019    source источник
comment
Не могли бы вы предоставить образец кода?   -  person JustSomeGuy    schedule 01.04.2019
comment
Я обновил вопрос кодом шейдера GBuffer, который я пытаюсь извлечь из графического процессора.   -  person OliverD    schedule 01.04.2019
comment
Вы уже пробовали буферы аргументов? Мне любопытно, помог ли вам мой ответ.   -  person JustSomeGuy    schedule 04.04.2019


Ответы (1)


Просматривая код, документацию Metal и спецификацию Metal Shading Language, я думаю, что знаю, почему вы получаете эту ошибку.

Если вы просмотрите интерфейс render_command, который присутствует в заголовке metal_command_buffer в Metal, вы обнаружите, что для передачи параметров командам косвенного рендеринга у вас есть только следующие функции: set_vertex_buffer и set_fragment_buffer, нет set_vertex_texture или set_vertex_sampler, как в MTLRenderCommandEncoder.

Но поскольку ваш конвейер использует шейдер, который, в свою очередь, использует текстуры в качестве аргументов, и вы указываете с помощью supportIndirectCommandBuffers, что хотели бы использовать этот конвейер в косвенных командах, у Metal нет другого выбора, кроме как потерпеть неудачу при создании конвейера.

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

Спецификация: Спецификация языка затенения металла (раздел 5.16)

person JustSomeGuy    schedule 02.04.2019
comment
Думаю, ты мог бы быть прав. Когда я переключаюсь с использования текстур на буферы, я могу создать renderPipelineState. Однако следующая проблема заключается в том, что компилятор Metal дает сбой при попытке создать ядро ​​непрямого рисования. MTLCompiler: Ошибка компиляции с XPC_ERROR_CONNECTION_INTERRUPTED. Я думаю, что это может быть темой для другого вопроса о переполнении стека, отметив это как правильный ответ. Я думаю, что довольно сложно перенести существующий конвейер на работу с графическим процессором, возможно, лучше начать с нуля. - person OliverD; 04.04.2019
comment
Кажется, что это функция, которую люди редко используют, так как она настолько глубока, что может быть еще много ошибок, которые нужно найти. Если ничего не работает, попробуйте отправить отчет об ошибке в Apple прямо со страницы bugreport.apple.com. - person JustSomeGuy; 04.04.2019
comment
Я не понимаю, как должен работать индексированный рисунок, если вы упаковываете вершины и индексы для всех геометрий в отдельные буферы. По сравнению с версией CPU, в команде GPU отсутствует свойство indexStart: void draw_indexed_primitives(primitive_type type, uint index_count, device/constant ushort/uint *index_buffer, uint instance_count, uint base_vertex, uint base_instance); - person OliverD; 04.04.2019
comment
Это действительно хороший вопрос. Я не пробовал, но, возможно, вы могли бы просто выполнить арифметику указателя на index_buffer, поскольку это должно быть разрешено. Таким образом, вы просто передадите my_index_buffer + index_start вместо index_buffer аргумента. Трудно сказать, я хочу попробовать прототип конвейера, управляемого графическим процессором, и другие вещи в моем движке, но он еще далек от быстрого прототипирования. - person JustSomeGuy; 04.04.2019
comment
Мне удалось исправить проблему, из-за которой MTLCompiler падал при компиляции ядра. Это произошло потому, что в моем буфере аргументов был массив текстур с использованием синтаксиса массива C, texture3d<ushort> voxelMaps [MaxVoxelMeshCount];, массивы текстур в буферах аргументов, по-видимому, должны использовать синтаксис array<T, N>: array<texture3d<ushort>, MaxVoxelMeshCount> voxelMaps;. Вероятно, это должна быть ошибка времени компиляции, а не сбой компилятора, поэтому я мог бы сообщить об этом. - person OliverD; 04.04.2019
comment
Говорил скоро, забыл, что закомментировал строку, которая устанавливает буфер текстуры cmd.set_fragment_buffer(textureData, BufferIndexTextureData); Если я раскомментирую это, я получу сбой компилятора. Возможно, для этого возникнет новый вопрос SO. Я не могу поверить, что нет способа использовать текстуры в косвенном рисовании, управляемом графическим процессором, что было бы почти бесполезно (но теперь я также подозреваю, что образец кода Apple для косвенного рисования не использует текстуры) - person OliverD; 04.04.2019
comment
Не забудьте сообщить об ошибке непосредственно в Apple с помощью воспроизводимого кода, иначе они могут даже не узнать, что что-то сломано. Обычно они отвечают довольно быстро - person JustSomeGuy; 05.04.2019
comment
Я думал, прежде чем сообщать об ошибке, я выясню, является ли это предполагаемым поведением, задав вопрос в разделе Metal на форумах разработчиков Apple. Иногда инженеры из команды GPU отвечают на вопросы: forum.developer.apple.com/message/355833 - person OliverD; 05.04.2019