0

I have a working render to texture toy iOS app. The issue is it has a ton of jaggies because it is point sampled and not anti-aliasied:

enter image description here

I increased the sample count in my MTKView subclass to 4 to enable MSAA.

Here is what the relevant code looks like.

// render to texture render pass descriptor
renderPassDesc = MTLRenderPassDescriptor()
renderPassDesc.EI_configure(clearColor: MTLClearColorMake(1, 1, 1, 1), clearDepth: 1)

// my MTLRenderPassDescriptor extension convenience method
public func EI_configure(clearColor:MTLClearColor, clearDepth: Double) {

    // color
    colorAttachments[ 0 ] = MTLRenderPassColorAttachmentDescriptor()
    colorAttachments[ 0 ].storeAction = .store
    colorAttachments[ 0 ].loadAction = .clear
    colorAttachments[ 0 ].clearColor = clearColor

    // depth
    depthAttachment = MTLRenderPassDepthAttachmentDescriptor()
    depthAttachment.storeAction = .dontCare
    depthAttachment.loadAction = .clear
    depthAttachment.clearDepth = clearDepth;

}

I attach a color and depth buffer configured for MSAA to renderPassDesc:

// color
let colorDesc = MTLTextureDescriptor.texture2DDescriptor(pixelFormat:view.colorPixelFormat, width:Int(view.bounds.size.width), height:Int(view.bounds.size.height), mipmapped:false)
colorDesc.mipmapLevelCount = 1;
colorDesc.textureType = .type2DMultisample
colorDesc.sampleCount = view.sampleCount
colorDesc.usage = [.renderTarget, .shaderRead]
renderPassDesc.colorAttachments[ 0 ].texture = view.device!.makeTexture(descriptor:colorDesc)

// depth
let depthDesc = MTLTextureDescriptor.texture2DDescriptor(pixelFormat:.depth32Float, width:Int(view.bounds.size.width), height:Int(view.bounds.size.height), mipmapped:false)
depthDesc.mipmapLevelCount = 1;
depthDesc.textureType = .type2DMultisample
depthDesc.sampleCount = view.sampleCount
depthDesc.usage = .renderTarget
renderPassDesc.depthAttachment.texture = view.device!.makeTexture(descriptor:depthDesc)

In my draw loop I am getting the following error from my fragment shader that consumes the texture that was rendered into:

failed assertion Fragment Function(finalPassOverlayFragmentShader): incorrect type of texture (MTLTextureType2DMultisample) bound at texture binding at index 0 (expect MTLTextureType2D) for underlay[0]

This is the fragment shader:

fragment float4 finalPassOverlayFragmentShader(InterpolatedVertex vert [[ stage_in ]],
                                               texture2d<float> underlay [[ texture(0) ]],
                                               texture2d<float> overlay [[ texture(1) ]]) {

    constexpr sampler defaultSampler;

    float4 _F = overlay.sample(defaultSampler, vert.st).rgba;

    float4 _B = underlay.sample(defaultSampler, vert.st).rgba;

    float4 rgba = _F + (1.0f - _F.a) * _B;

    return rgba;
}

I am sure I have missed a setting somewhere but I cannot find it.

What have I missed here?

UPDATE 0

I now have MSAA happening for my 2-pass toy. The only problem is there is not much anti-aliasing happening. In fact it is hard to tell that anything has changed. Here are my latest settings

// color - multi-sampled texture target
let desc = MTLTextureDescriptor.texture2DDescriptor(pixelFormat:format, width:w, height:h, mipmapped:false)
desc.mipmapLevelCount = 1;
desc.textureType = .type2DMultisample
desc.sampleCount = view.sampleCount
desc.usage = .renderTarget
let tex:MTLTexture? = view.device!.makeTexture(descriptor:desc)

// color - point-sampled resolve-texture
let resolveDesc = MTLTextureDescriptor.texture2DDescriptor(pixelFormat:format, width:w, height:h, mipmapped:true)
let resolveTex:MTLTexture? = view.device!.makeTexture(descriptor:resolveDesc)

// depth texture target
let depthDesc = MTLTextureDescriptor.texture2DDescriptor(pixelFormat:.format, width:w, height:h, mipmapped:false)
depthDesc.mipmapLevelCount = 1;
depthDesc.textureType = .type2DMultisample
depthDesc.sampleCount = view.sampleCount
depthDesc.usage = .renderTarget
let depthTex:MTLTexture? = view.device!.makeTexture(descriptor:depthDesc)

// render pass descriptor
renderPassDesc = MTLRenderPassDescriptor()
// color
renderPassDesc.colorAttachments[ 0 ] = MTLRenderPassColorAttachmentDescriptor()
renderPassDesc.colorAttachments[ 0 ].storeAction = .storeAndMultisampleResolve
renderPassDesc.colorAttachments[ 0 ].loadAction = .clear
renderPassDesc.colorAttachments[ 0 ].clearColor = MTLClearColorMake(0.25, 0.25, 0.25, 1)
renderPassDesc.colorAttachments[ 0 ].texture = tex
renderPassDesc.colorAttachments[ 0 ].resolveTexture = resolveTex
// depth
renderPassDesc.depthAttachment = MTLRenderPassDepthAttachmentDescriptor()
renderPassDesc.depthAttachment.storeAction = .dontCare
renderPassDesc.depthAttachment.loadAction = .clear
renderPassDesc.depthAttachment.clearDepth = 1;
renderPassDesc.depthAttachment.texture = depthTex

UPDATE 1

The jaggies appear to be from the render-to-texture and not in the asset. Below is a side by side comparison. the top image is rendered using a single pass with MSAA enabled. The bottom image is rendered to texture. The jaggies are clearly visible in the bottom image

single pass enter image description here

2-pass enter image description here

dugla
  • 12,774
  • 26
  • 88
  • 136
  • Regarding your update, are you now using `resolveTex` and not `tex` for the compositing pass with `finalPassOverlayFragmentShader`? – Ken Thomases Nov 27 '18 at 20:58
  • Yes. Creating the resolve texture fixed the bug. However I see no anti-aliasing. So to clarify. MS is enabled for the render to texture step. I use that texture as the backdrop atop of which I composite a semi-transparent texture. I am seeing the jaggies in the texture rendered during the render-to-texture step. The composite is fine. – dugla Nov 27 '18 at 21:47
  • Actually, there is slight anti-aliasing happening. But nothing like the AA I see when I use multi-sampling in a non-render-to-texture example. Weird. – dugla Nov 27 '18 at 21:52
  • Are you sure the jaggies aren't in the inputs to your render-to-texture pass, rather than a result of it? – Ken Thomases Nov 28 '18 at 00:16

1 Answers1

1

The error is not about your render target (a.k.a. color and depth attachments). It's about a texture you're passing in via the render command encoder's fragment texture table — that is, where you're calling setFragmentTexture(_:index:). The one you're passing for index 0 is a .type2DMultisample when the shader is coded to expect .type2D, because you declared underlay as a texture2d<...>.

Your setup for MSAA is OK for an intermediate step. You will eventually need to resolve the texture to a non-multisampled texture in order to draw it to screen. For that step (which might be for this render command encoder or a later one, depending on your needs), you need to set the storeAction for the color attachment to either .multisampleResolve or .storeAndMultisampleResolve. And you need to set the resolveTexture to a 2D texture. That could be one of your own or a drawable's texture.

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
  • Thanks so much Ken. Let me try and unpack your answer. 1) What is the proper syntax in my frag shader to allow consumption of a multi-sample vs. the current point-sampled texture. 2) My render-to-texture pass results in a texture (defined as multi-sample). My toy app takes that multi-sample texture and an overlay texture and composites them in the frag shader shown above. Super simple. 3) I don't fully understand the role of the resolve texture, can you elaborate and also distinguish between .multisampleResolve and .storeAndMultisampleResolve? Can you point me to the relevant docs on this. – dugla Nov 27 '18 at 15:04
  • I did a bit more digging in the docs. Regarding the use if the resolve texture and store actions. If I set the color-attachment store action to .storeAndMultisampleResolve what store action to I set for the resolve texture? – dugla Nov 27 '18 at 15:15
  • You would declare `underlay` as `texture2d_ms`, but then you can only `read()` from it, not `sample()`. How exactly do you want the compositing to work? It seems to me like you're expecting the anti-aliasing to be resolved before that compositing step, so you'll want to do that. Multi-sampling is only an intermediate step. An MS texture has multiple values (samples) per texcoord, but eventually you want a single value, for example when drawn to screen. Resolving is producing that single value from the multiple values. – Ken Thomases Nov 27 '18 at 20:48
  • `.multisampleResolve` stores the resolved data in the `resolveTexture` but otherwise discards the data of the MS texture. `.storeAndMultisampleResolve` both resolves to the `resolveTexture` and stores the data in the MS texture. You'd only do that if you need to do something else with it after resolving. If you haven't already, check out the [Metal Programming Guide](https://developer.apple.com/library/archive/documentation/Miscellaneous/Conceptual/MetalProgrammingGuide/Introduction/Introduction.html). It's "archived" but still relevant. See the Graphics Rendering and What's New pages. – Ken Thomases Nov 27 '18 at 20:54
  • I solved the jaggies issue. I forgot to use UIScreen.main.scale to upscale my render targets. AA is happening now. Sweet. – dugla Nov 28 '18 at 13:18