[英]Error when using Metal Indirect Command Buffer: "Fragment shader cannot be used with indirect command buffers"
I'm working on a Metal, MTKView
based app that takes advantage of the A11 TBDR architecture to do deferred shading in a single render pass.我正在
MTKView
基于MTKView
的 Metal 应用程序,该应用程序利用 A11 TBDR 架构在单个渲染通道中执行延迟着色。 I used Apple's Deferred Lighting sample code as reference, and it works great.我使用 Apple 的Deferred Lighting 示例代码作为参考,效果很好。
I'd like to try changing the geometry buffer pass to be GPU-driven, using the Indirect Command Buffer feature of Metal 2 on A11 hardware.我想尝试在 A11 硬件上使用 Metal 2 的间接命令缓冲区功能将几何缓冲区传递更改为 GPU 驱动。
I've been using Apple's Encoding Indirect Command Buffers on the GPU sample code as my main point of reference for this.我一直在 GPU 示例代码上使用 Apple 的编码间接命令缓冲区作为我的主要参考点。 I'm able to run this sample on my iPhone XR (although, probably off-topic, the scrolling is not smooth, it judders).
我可以在我的 iPhone XR 上运行这个示例(尽管可能偏离主题,滚动不流畅,它会颤抖)。
I'm running into difficulties however with my own code, when I try to move my geometry buffer pass into an indirect command buffer.然而,当我尝试将几何缓冲区传递到间接命令缓冲区时,我遇到了困难,但是使用我自己的代码。 When I set
supportIndirectCommandBuffers
to true
on the MTLRenderPipelineDescriptor
of the Geometry Buffer pipeline, device.makeRenderPipelineState
fails with the error当我设置
supportIndirectCommandBuffers
到true
的MTLRenderPipelineDescriptor
几何缓存管线, device.makeRenderPipelineState
失败,出现错误
AGXMetalA12 Code=3 "Fragment shader cannot be used with indirect command buffers"
AGXMetalA12 Code=3“片段着色器不能与间接命令缓冲区一起使用”
I've not been able to find any information in the documentation on this error.我无法在文档中找到有关此错误的任何信息。 I'm wondering, are there certain kinds of fragment operation that are not allowed in indirect pipelines, or some kind of limit to GPU-driven drawing that I've overlooked (the number of color attachments perhaps)?
我想知道,是否有某些类型的片段操作在间接管道中是不允许的,或者我忽略了对 GPU 驱动绘图的某种限制(可能是颜色附件的数量)?
Header shared by Metal and Swift 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 */
#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)
}
}
The above works great when triggering draws from the CPU in the usual way, but when setting supportIndirectCommandBuffers
in preparation for GPU drawing it throws the error.当以通常的方式从 CPU 触发绘制时,上述方法效果很好,但是当设置
supportIndirectCommandBuffers
以准备 GPU 绘制时,它会引发错误。
I've tried stripping down the fragment shader to just return constant values for the GBuffers, and then makeRenderPipelineState
succeeds, but when I add texture sampling back in it begins complaining again.我试过剥离片段着色器,只为 GBuffers 返回常量值,然后
makeRenderPipelineState
成功,但是当我重新添加纹理采样时,它又开始抱怨了。 I can't seem to pin down what exactly it doesn't like about the frag shader.我似乎无法确定它到底不喜欢碎片着色器的什么地方。
Looking through the code and through Metal documentation and Metal Shading Language specification, I think I know why you get this error.查看代码以及 Metal 文档和 Metal Shading Language 规范,我想我知道为什么会出现此错误。
If you look through render_command
interface that is present in metal_command_buffer
header in Metal, you'll find that to pass parameters to indirect render commands, you only have these functions: set_vertex_buffer
and set_fragment_buffer
, there is no set_vertex_texture
or set_vertex_sampler
like you have in MTLRenderCommandEncoder
.如果您查看 Metal 中
metal_command_buffer
标头中存在的render_command
接口,您会发现要将参数传递给间接渲染命令,您只有以下功能: set_vertex_buffer
和set_fragment_buffer
,没有set_vertex_texture
set_vertex_sampler
那样的set_vertex_texture
或MTLRenderCommandEncoder
。
But, since your pipeline uses shader that in turn uses textures as arguments and you indicate by using supportIndirectCommandBuffers
that you would like to use this pipeline in indirect commands, Metal has no choice but to fail pipeline creation.但是,由于您的管道使用着色器,而着色器又使用纹理作为参数,并且您通过使用
supportIndirectCommandBuffers
指示您希望在间接命令中使用此管道,因此 Metal 没有选择,只能使管道创建失败。
Instead if you want to pass textures or samplers to indirect render commands, you should use argument buffers, that you will pass to the shader that issues indirect render commands, which in turn will bind them using set_vertex_buffer
and set_fragment_buffer
for each render_command
.相反,如果你想将纹理或采样器传递给间接渲染命令,你应该使用参数缓冲区,你将传递给发出间接渲染命令的着色器,后者将使用
set_vertex_buffer
和set_fragment_buffer
为每个render_command
绑定它们。
Specification: Metal Shading Language Specification (Section 5.16)规范: 金属着色语言规范(第 5.16 节)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.