在 Metal 中绘制带纹理的矩形 - Swift
Draw a textured rectangle in Metal - Swift
我正在尝试使用 Swift 在 Metal 中绘制带纹理的 2D 矩形。所有在线示例(包括以下示例)都在 Objective-C 或 Swift 的旧版本中,并且很难移植到 Swift 5:
How to draw a textured rectangle with Metal
https://developer.apple.com/documentation/metal/creating_and_sampling_textures
对我来说objective就是在视频的一角画一个简单的水印。我有一个 Renderer
class 用于捕获帧,将它们绘制到我的金属视图(这很好用)然后想在顶部绘制一个徽标。我不能对相机帧使用相同的方法,因为它们是 YUV 格式,所以使用特殊的着色器。我的水印只是一个普通的旧 PNG 文件。
我可以绘制无纹理矩形,但尝试应用纹理失败 - 没有错误,没有任何问题。屏幕上什么也没有出现。当 Metal returns 没有错误来调试这些类型的问题时,我应该检查哪些关键事项?
这是一些代码:
func drawRect(renderEncoder: MTLRenderCommandEncoder, x:Double, y:Double, w:Double, h:Double, pipelineState:MTLRenderPipelineState, viewSize: CGSize)
{
//new viewport to size of image we want to display
var viewPort = MTLViewport(originX: x, originY: y, width: w, height: h, znear: 0.0, zfar: 1.0)
var viewPortSize = simd_float2(x: Float(w), y: Float(h))
var logoQuadVertices:[AAPLVertex] = [
//Triangle1: top left, top right, bottom left
AAPLVertex(position: simd_float2(x: 0, y: 0), textureCoordinate: simd_float2(x: 0.0, y: 0.0)),
AAPLVertex(position: simd_float2(x: 100, y: 0), textureCoordinate: simd_float2(x: 1.0, y: 0.0)),
AAPLVertex(position: simd_float2(x: 0, y: 100), textureCoordinate: simd_float2(x: 0.0, y: 1.0)),
//Triangle2: bottom left, bottom right, top right
AAPLVertex(position: simd_float2(x: 0, y: 100), textureCoordinate: simd_float2(x: 0.0, y: 1.0)),
AAPLVertex(position: simd_float2(x: 100, y: 100), textureCoordinate: simd_float2(x: 1.0, y: 1.0)),
AAPLVertex(position: simd_float2(x: 100, y: 0), textureCoordinate: simd_float2(x: 1.0, y: 0.0))
]
//Set the new viewport
renderEncoder.setViewport(MTLViewport(originX: x, originY: y, width: w, height: h, znear: 0.0, zfar: 1.0))
//Debug string
renderEncoder.pushDebugGroup("DrawRect")
//Set pipeline state
renderEncoder.setRenderPipelineState(pipelineState)
//Send vertex data of rectangle to shaders
renderEncoder.setVertexBytes(logoQuadVertices, length: logoQuadVertices.count*MemoryLayout<AAPLVertex>.stride, index: Int(AAPLVertexInputIndexVertices.rawValue))
//Send vertex bytes of viewport to shaders
renderEncoder.setVertexBytes(&viewPortSize, length: MemoryLayout<simd_float2>.stride, index: Int(AAPLVertexInputIndexViewportSize.rawValue))
// Set the texture
renderEncoder.setFragmentTexture(texture, index: Int(kTextureIndexPNG.rawValue))
//Sampler state
if let samplerState = logoSamplerState {
renderEncoder.setFragmentSamplerState(samplerState, index: 0)
}
//Draw the textured rectangle
renderEncoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 6)
//Debug
renderEncoder.popDebugGroup()
}
...
// Pipeline configuration
let logoPipelineStateDescriptor = MTLRenderPipelineDescriptor()
logoPipelineStateDescriptor.label = "MyLogoPipeline"
logoPipelineStateDescriptor.sampleCount = renderDestination.sampleCount
logoPipelineStateDescriptor.vertexFunction =
defaultLibrary.makeFunction(name: "vertexShader")!
logoPipelineStateDescriptor.fragmentFunction =
defaultLibrary.makeFunction(name: "samplingShader")
logoPipelineStateDescriptor.vertexDescriptor = logoPlaneVertexDescriptor
logoPipelineStateDescriptor.colorAttachments[0].pixelFormat =
renderDestination.colorPixelFormat
logoPipelineStateDescriptor.depthAttachmentPixelFormat =
renderDestination.depthStencilPixelFormat
logoPipelineStateDescriptor.stencilAttachmentPixelFormat =
renderDestination.depthStencilPixelFormat
do {
try logoPipelineState = device.makeRenderPipelineState(descriptor: logoPipelineStateDescriptor)
} catch let error {
print("Failed to created logo pipeline state, error \(error)")
}
...
着色器:
typedef struct
{
float4 position [[position]];
float2 textureCoordinate;
} RasterizerData;
// Vertex Function
vertex RasterizerData
vertexShader(uint vertexID [[ vertex_id ]],
constant AAPLVertex *vertexArray [[ buffer(AAPLVertexInputIndexVertices) ]],
constant vector_uint2 *viewportSizePointer [[ buffer(AAPLVertexInputIndexViewportSize) ]])
{
RasterizerData out;
float2 pixelSpacePosition = vertexArray[vertexID].position.xy;
float2 viewportSize = float2(*viewportSizePointer);
out.position = vector_float4(0.0, 0.0, 0.0, 1.0);
out.position.xy = pixelSpacePosition / (viewportSize / 2.0);
out.textureCoordinate = vertexArray[vertexID].textureCoordinate;
return out;
}
// Fragment function
fragment float4
samplingShader(RasterizerData in [[stage_in]],
texture2d<half> colorTexture [[ texture(AAPLTextureIndexBaseColor) ]])
{
constexpr sampler textureSampler (mag_filter::linear,
min_filter::linear);
// Sample the texture to obtain a color
const half4 colorSample = colorTexture.sample(textureSampler, in.textureCoordinate);
// return the color of the texture
return float4(colorSample);
}
根据@trojanfoe 的建议,调试器有一些用于调试 GPU 功能的有用工具。要访问此功能,请在 运行 您的应用程序中,在 XCode 中转到“调试”>“捕获 GPU 帧”。然后您可以探索层次结构,甚至可以查看顶点是如何加载的等等。
在我的例子中,顶点是错误的。我将它们作为像素坐标提供,而不是 -1.0、1.0 纹理坐标系。当我在调试器中的 verticies 数据没有按预期显示时,这一点很明显。您的问题可能是其他问题,但这是一个很好的起点。
针对我的问题修改代码:
var logoQuadVertices:[AAPLVertex] = [
//Triangle1: top left, top right, bottom left
AAPLVertex(position: simd_float2(x: -1.0, y: -1.0), textureCoordinate: simd_float2(x: 0.0, y: 0.0)),
AAPLVertex(position: simd_float2(x: 1.0, y: -1.0), textureCoordinate: simd_float2(x: 1.0, y: 0.0)),
AAPLVertex(position: simd_float2(x: -1.0, y: 1.0), textureCoordinate: simd_float2(x: 0.0, y: 1.0)),
//Triangle2: bottom left, bottom right, top right
AAPLVertex(position: simd_float2(x: -1.0, y: 1.0), textureCoordinate: simd_float2(x: 0.0, y: 1.0)),
AAPLVertex(position: simd_float2(x: 1.0, y: 1.0), textureCoordinate: simd_float2(x: 1.0, y: 1.0)),
AAPLVertex(position: simd_float2(x: 1.0, y: -1.0), textureCoordinate: simd_float2(x: 1.0, y: 0.0))
]
我正在尝试使用 Swift 在 Metal 中绘制带纹理的 2D 矩形。所有在线示例(包括以下示例)都在 Objective-C 或 Swift 的旧版本中,并且很难移植到 Swift 5:
How to draw a textured rectangle with Metal https://developer.apple.com/documentation/metal/creating_and_sampling_textures
对我来说objective就是在视频的一角画一个简单的水印。我有一个 Renderer
class 用于捕获帧,将它们绘制到我的金属视图(这很好用)然后想在顶部绘制一个徽标。我不能对相机帧使用相同的方法,因为它们是 YUV 格式,所以使用特殊的着色器。我的水印只是一个普通的旧 PNG 文件。
我可以绘制无纹理矩形,但尝试应用纹理失败 - 没有错误,没有任何问题。屏幕上什么也没有出现。当 Metal returns 没有错误来调试这些类型的问题时,我应该检查哪些关键事项?
这是一些代码:
func drawRect(renderEncoder: MTLRenderCommandEncoder, x:Double, y:Double, w:Double, h:Double, pipelineState:MTLRenderPipelineState, viewSize: CGSize)
{
//new viewport to size of image we want to display
var viewPort = MTLViewport(originX: x, originY: y, width: w, height: h, znear: 0.0, zfar: 1.0)
var viewPortSize = simd_float2(x: Float(w), y: Float(h))
var logoQuadVertices:[AAPLVertex] = [
//Triangle1: top left, top right, bottom left
AAPLVertex(position: simd_float2(x: 0, y: 0), textureCoordinate: simd_float2(x: 0.0, y: 0.0)),
AAPLVertex(position: simd_float2(x: 100, y: 0), textureCoordinate: simd_float2(x: 1.0, y: 0.0)),
AAPLVertex(position: simd_float2(x: 0, y: 100), textureCoordinate: simd_float2(x: 0.0, y: 1.0)),
//Triangle2: bottom left, bottom right, top right
AAPLVertex(position: simd_float2(x: 0, y: 100), textureCoordinate: simd_float2(x: 0.0, y: 1.0)),
AAPLVertex(position: simd_float2(x: 100, y: 100), textureCoordinate: simd_float2(x: 1.0, y: 1.0)),
AAPLVertex(position: simd_float2(x: 100, y: 0), textureCoordinate: simd_float2(x: 1.0, y: 0.0))
]
//Set the new viewport
renderEncoder.setViewport(MTLViewport(originX: x, originY: y, width: w, height: h, znear: 0.0, zfar: 1.0))
//Debug string
renderEncoder.pushDebugGroup("DrawRect")
//Set pipeline state
renderEncoder.setRenderPipelineState(pipelineState)
//Send vertex data of rectangle to shaders
renderEncoder.setVertexBytes(logoQuadVertices, length: logoQuadVertices.count*MemoryLayout<AAPLVertex>.stride, index: Int(AAPLVertexInputIndexVertices.rawValue))
//Send vertex bytes of viewport to shaders
renderEncoder.setVertexBytes(&viewPortSize, length: MemoryLayout<simd_float2>.stride, index: Int(AAPLVertexInputIndexViewportSize.rawValue))
// Set the texture
renderEncoder.setFragmentTexture(texture, index: Int(kTextureIndexPNG.rawValue))
//Sampler state
if let samplerState = logoSamplerState {
renderEncoder.setFragmentSamplerState(samplerState, index: 0)
}
//Draw the textured rectangle
renderEncoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 6)
//Debug
renderEncoder.popDebugGroup()
}
...
// Pipeline configuration
let logoPipelineStateDescriptor = MTLRenderPipelineDescriptor()
logoPipelineStateDescriptor.label = "MyLogoPipeline"
logoPipelineStateDescriptor.sampleCount = renderDestination.sampleCount
logoPipelineStateDescriptor.vertexFunction =
defaultLibrary.makeFunction(name: "vertexShader")!
logoPipelineStateDescriptor.fragmentFunction =
defaultLibrary.makeFunction(name: "samplingShader")
logoPipelineStateDescriptor.vertexDescriptor = logoPlaneVertexDescriptor
logoPipelineStateDescriptor.colorAttachments[0].pixelFormat =
renderDestination.colorPixelFormat
logoPipelineStateDescriptor.depthAttachmentPixelFormat =
renderDestination.depthStencilPixelFormat
logoPipelineStateDescriptor.stencilAttachmentPixelFormat =
renderDestination.depthStencilPixelFormat
do {
try logoPipelineState = device.makeRenderPipelineState(descriptor: logoPipelineStateDescriptor)
} catch let error {
print("Failed to created logo pipeline state, error \(error)")
}
...
着色器:
typedef struct
{
float4 position [[position]];
float2 textureCoordinate;
} RasterizerData;
// Vertex Function
vertex RasterizerData
vertexShader(uint vertexID [[ vertex_id ]],
constant AAPLVertex *vertexArray [[ buffer(AAPLVertexInputIndexVertices) ]],
constant vector_uint2 *viewportSizePointer [[ buffer(AAPLVertexInputIndexViewportSize) ]])
{
RasterizerData out;
float2 pixelSpacePosition = vertexArray[vertexID].position.xy;
float2 viewportSize = float2(*viewportSizePointer);
out.position = vector_float4(0.0, 0.0, 0.0, 1.0);
out.position.xy = pixelSpacePosition / (viewportSize / 2.0);
out.textureCoordinate = vertexArray[vertexID].textureCoordinate;
return out;
}
// Fragment function
fragment float4
samplingShader(RasterizerData in [[stage_in]],
texture2d<half> colorTexture [[ texture(AAPLTextureIndexBaseColor) ]])
{
constexpr sampler textureSampler (mag_filter::linear,
min_filter::linear);
// Sample the texture to obtain a color
const half4 colorSample = colorTexture.sample(textureSampler, in.textureCoordinate);
// return the color of the texture
return float4(colorSample);
}
根据@trojanfoe 的建议,调试器有一些用于调试 GPU 功能的有用工具。要访问此功能,请在 运行 您的应用程序中,在 XCode 中转到“调试”>“捕获 GPU 帧”。然后您可以探索层次结构,甚至可以查看顶点是如何加载的等等。
在我的例子中,顶点是错误的。我将它们作为像素坐标提供,而不是 -1.0、1.0 纹理坐标系。当我在调试器中的 verticies 数据没有按预期显示时,这一点很明显。您的问题可能是其他问题,但这是一个很好的起点。
针对我的问题修改代码:
var logoQuadVertices:[AAPLVertex] = [
//Triangle1: top left, top right, bottom left
AAPLVertex(position: simd_float2(x: -1.0, y: -1.0), textureCoordinate: simd_float2(x: 0.0, y: 0.0)),
AAPLVertex(position: simd_float2(x: 1.0, y: -1.0), textureCoordinate: simd_float2(x: 1.0, y: 0.0)),
AAPLVertex(position: simd_float2(x: -1.0, y: 1.0), textureCoordinate: simd_float2(x: 0.0, y: 1.0)),
//Triangle2: bottom left, bottom right, top right
AAPLVertex(position: simd_float2(x: -1.0, y: 1.0), textureCoordinate: simd_float2(x: 0.0, y: 1.0)),
AAPLVertex(position: simd_float2(x: 1.0, y: 1.0), textureCoordinate: simd_float2(x: 1.0, y: 1.0)),
AAPLVertex(position: simd_float2(x: 1.0, y: -1.0), textureCoordinate: simd_float2(x: 1.0, y: 0.0))
]