Ошибка при использовании Metal Indirect Command Buffer: «Фрагментный шейдер нельзя использовать с косвенными командными буферами» - PullRequest
1 голос
/ 01 апреля 2019

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

Я хотел бы попробовать изменить проход буфера геометрии на GPU, используя функцию косвенного буфера командметалла 2 на метизе а11.

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

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

AGXMetalA12 Code = 3 "Фрагментный шейдер не может использоваться с косвенной командойbuffers "

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

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 shader

#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 удается, но когда я добавляю выборку текстур обратно, она снова начинает жаловаться.Кажется, я не могу точно сказать, что именно не нравится в фрагментном шейдере.

1 Ответ

0 голосов
/ 02 апреля 2019

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

Если вы просматриваете интерфейс render_command, который присутствует в заголовке metal_command_buffer вМеталл, вы обнаружите, что для передачи параметров в команды косвенного рендеринга у вас есть только эти функции: 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)

...