【问题标题】:Error when using Metal Indirect Command Buffer: "Fragment shader cannot be used with indirect command buffers"使用金属间接命令缓冲区时出错:“片段着色器不能与间接命令缓冲区一起使用”
【发布时间】:2019-04-01 07:56:38
【问题描述】:

我正在开发一个基于 Metal、MTKView 的应用程序,该应用程序利用 A11 TBDR 架构在单个渲染通道中执行延迟着色。我使用了 Apple 的 Deferred Lighting sample code 作为参考,效果很好。

我想尝试使用 A11 硬件上 Metal 2 的间接命令缓冲区功能将几何缓冲区通道更改为 GPU 驱动。

我一直使用 Apple 的 Encoding Indirect Command Buffers on the GPU sample code 作为我的主要参考点。我可以在我的 iPhone XR 上运行这个示例(尽管可能跑题了,滚动不流畅,它会颤抖)。

当我尝试将几何缓冲区传递移动到间接命令缓冲区时,我遇到了困难。当我在几何缓冲区管道的MTLRenderPipelineDescriptor 上将supportIndirectCommandBuffers 设置为true 时,device.makeRenderPipelineState 失败并出现错误

AGXMetalA12 Code=3 "片段着色器不能与间接命令缓冲区一起使用"

我无法在文档中找到有关此错误的任何信息。我想知道,是否存在间接管道中不允许的某些类型的片段操作,或者我忽略了对 GPU 驱动的绘图的某种限制(可能是颜色附件的数量)?

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)
    }
}

上述方法在以通常方式从 CPU 触发绘图时效果很好,但在设置 supportIndirectCommandBuffers 以准备 GPU 绘图时会引发错误。

我尝试剥离片段着色器以仅返回 GBuffer 的常量值,然后 makeRenderPipelineState 成功,但是当我重新添加纹理采样时,它又开始抱怨了。我似乎无法确定它到底不喜欢 frag 着色器的哪些方面。

【问题讨论】:

  • 能否提供代码示例?
  • 我已经用我试图从 GPU 绘制的 GBuffer 着色器的代码更新了问题
  • 您是否已经尝试过参数缓冲区?我很好奇我的回答是否对您有所帮助。

标签: ios gpu metal deferred-rendering


【解决方案1】:

查看代码以及 Metal 文档和 Metal Shading Language 规范,我想我知道为什么会出现此错误。

如果您查看 Metal 中 metal_command_buffer 标头中存在的 render_command 接口,您会发现要将参数传递给间接渲染命令,您只有这些函数:set_vertex_bufferset_fragment_buffer,那里不是set_vertex_textureset_vertex_sampler,就像你在MTLRenderCommandEncoder 中那样。

但是,由于您的管道使用着色器,而着色器又使用纹理作为参数,并且您使用 supportIndirectCommandBuffers 表示您希望在间接命令中使用此管道,因此 Metal 没有选择,只能使管道创建失败。

如果您想将纹理或采样器传递给间接渲染命令,您应该使用参数缓冲区,您将传递给发出间接渲染命令的着色器,然后使用set_vertex_bufferset_fragment_buffer 绑定它们对于每个render_command

规范:Metal Shading Language Specification(第 5.16 节)

【讨论】:

  • 我认为你可能是对的。当我从使用纹理切换到缓冲区时,我能够创建 renderPipelineState。下一个问题是 Metal 编译器在尝试创建间接绘图内核“MTLCompiler: Compilation failed with XPC_ERROR_CONNECTION_INTERRUPTED”时崩溃。我认为这可能是另一个 Stack Overflow 问题的主题,将其标记为正确答案。我认为将现有管道移植到 GPU 驱动是相当棘手的,最好从头开始。
  • 似乎是一个很少有人使用的功能,因为它太深入了,所以可能还有很多bug需要寻找。如果没有任何效果,请尝试通过bugreport.apple.com直接向 Apple 提交错误报告
  • 我不明白的是,如果您将所有几何图形的顶点和索引打包到单个缓冲区中,索引绘图是如何工作的。与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);
  • 这是一个非常好的问题。我没有尝试过,但可能你可以只对index_buffer 进行指针运算,因为它应该被允许。因此,您只需将 my_index_buffer + index_start 传递给 index_buffer 参数。很难说,我想在我的引擎中尝试原型 GPU 驱动的管道和其他东西,但它远未准备好进行快速原型设计。
  • 我设法解决了 MTLCompiler 在编译内核时崩溃的问题。这是因为我的参数缓冲区有一个使用 C 数组语法 texture3d&lt;ushort&gt; voxelMaps [MaxVoxelMeshCount]; 的纹理数组,参数缓冲区中的纹理数组显然需要使用 array&lt;T, N&gt; 语法:array&lt;texture3d&lt;ushort&gt;, MaxVoxelMeshCount&gt; voxelMaps;。可能应该是编译时错误而不是编译器崩溃,所以我可能会为此提交雷达。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-21
  • 2016-09-25
  • 2013-08-19
  • 2018-10-15
  • 2023-02-08
  • 1970-01-01
相关资源
最近更新 更多