Search code examples
iostexturesmetalmetalkit

MetalKit view MTKView showing blurry stroke and extrapolating their designated path/size


I'm trying to plot squares using MetalKit. Each square has an origin and a size, like this:

Origin:

▿ (204.5746214852199, 62.83450704225352)
  - x : 204.5746214852199
  - y : 62.83450704225352

Size:

▿ (1.4612472963229992, 1.4612676056338028)
  - width : 1.4612472963229992
  - height : 1.4612676056338028

The problem is that when MetalKit draws those, each square is drawn a different weird way that exceeds the expected area and shape for what a square should look like (see image below) - yes, each red cluster should represent a single 1x1 square of the same size of those grey squares underneath. Furthermore, its borders are also blurry (this is a zoomed in image). I am using this sampler:

constexpr sampler textureSampler(mag_filter::nearest,
                                 min_filter::nearest);

greyscale pixel board with some red pixels

In the image above, the greyscale squares is what each red square should be fitting to (their borders and exact position).

Any clue how to achieve this? Could MetalKit be losing decimal precision along the way? How can I make the squares edges sharp?

Here's my vertices function and position struct: https://github.com/s1ddok/MetalCoreGraphics/blob/d32c680a156548e20e9e9deb06e14cc260bcdea1/MetalCoreGraphics/Shaders.metal#L12-L30

Any leads would be super helpful here! Appreciate it!


Solution

  • I was able to figure things out in the end. It was a combination of several things.

    1. The little squares (here named pixels) were exceeding their expected bounds because they were being antialiased. Since I'm using a CGContext to bridge between CPU and GPU memory (strategy taken from here), I had to add the following code (both lines!):
    context.setShouldAntialias(false)
    context.setAllowsAntialiasing(false)
    
    1. The pixels were blurred when zooming in because of the CALayer magnification filter, and not Metal's. The solution to that was quite simple:
    layer.magnificationFilter = .nearest // Disables pixel interpolation when zooming in
    
    1. Last but not least, the pixel coordinates (and size) weren't matching with the underlying pixel-perfect chess-looking board because the pixels I was drawing were actually CGRects (context.addRects()), and those CGRects had a too-precise width/height, often smaller than 1.00 (even though that wasn't the case in the original question, it had floating points which isn't really a thing when drawing individual pixels - there's no such thing as half of a pixel). So the solution to this was to use a canvas that has the exact number of pixels needed, and have all the CGRects I'm drawing to have a 1x1pt size. This way it's not only more performant (since there won't be floating point calculations all the time), but it will always be pixel perfect.

    See the final result below (this view is zoomed in by 150 times using UIScrollView):

    final result

    Hope this helps somebody out there! Thanks everyone who contributed!