Рисование эллипса на iOS с использованием шейдеров Swift, Metal и fragment

#ios #swift #graphics #metal

#iOS #swift #графика #Металлические

Вопрос:

Я новичок в Swift и Metal, и я пытаюсь создать простой движок рендеринга для iOS только для учебных целей. Итак, я застрял в рисовании простого эллипса с использованием функции fragment shader. На самом деле я делаю рендеринг квадрата, передаю нормализованные координаты устройства верхней левой и нижней правой вершин этого квадрата и, наконец, в шейдере фрагментов вычисляю, находится ли каждый фрагмент внутри эллипса. Я уже делал это в OpenGL, и все работало нормально. Я не уверен, что это правильный способ визуализации эллипса, но этот подход, похоже, не работает в Metal.

Итак, вот функции шейдеров:

 #include <metal_stdlib>
using namespace metal;

struct EllipseIn {
    float4 position [[attribute(0)]];
    float4 color [[attribute(1)]];
    float3 tlPosition [[attribute(2)]];
    float3 brPosition [[attribute(3)]];
};

struct EllipseOut {
    float4 position [[position]];
    float4 color;
    float3 tlPosition [[flat]];
    float3 brPosition [[flat]];
};

vertex EllipseOut ellipse_vertex(const EllipseIn ellipseIn [[stage_in]]) {
    EllipseOut ellipseOut;
    ellipseOut.position = ellipseIn.position;
    ellipseOut.color = ellipseIn.color;
    ellipseOut.tlPosition = ellipseIn.tlPosition;
    ellipseOut.brPosition = ellipseIn.brPosition;
    return ellipseOut;
}

fragment float4 ellipse_fragment(EllipseOut ellipseIn [[stage_in]]) {
    float3 t = (ellipseIn.brPosition - ellipseIn.tlPosition) / 2;
    float a = t.x;
    float b = t.y;
    float x = ellipseIn.position.x;
    float y = ellipseIn.position.y;
    float xc = ellipseIn.tlPosition.x   a;
    float yc = ellipseIn.tlPosition.y   b;
    float x2 = x - xc;
    float y2 = y - yc;
    float c = x2 / a;
    float d = y2 / b;
    float res = c * c   d * d;
    
    if (res > 1)
        discard_fragment();

    return ellipseIn.color;
}
 

И вот код Swift:

 import Foundation
import MetalKit

struct EllipseVertexDescriptor {
    var position: SIMD4<Float>
    var color: SIMD4<Float>
    var tlPosition: SIMD3<Float>
    var brPosition: SIMD3<Float>
}

class EllipseRenderer : PrimitiveRenderer {
    private var pipelineState: MTLRenderPipelineState!
    
    func preparePipelineState(device: MTLDevice, library: MTLLibrary) {
        let ellipseVertexFunction = library.makeFunction(name: "ellipse_vertex")
        let ellipseFragmentFunction = library.makeFunction(name: "ellipse_fragment")

        let ellipsePipelineDescriptor = MTLRenderPipelineDescriptor()
        ellipsePipelineDescriptor.vertexFunction = ellipseVertexFunction
        ellipsePipelineDescriptor.fragmentFunction = ellipseFragmentFunction
        ellipsePipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
        ellipsePipelineDescriptor.colorAttachments[0].isBlendingEnabled = true

        let ellipseVertexDescriptor = MTLVertexDescriptor()
        
        ellipseVertexDescriptor.attributes[0].format = .float4
        ellipseVertexDescriptor.attributes[0].offset = 0
        ellipseVertexDescriptor.attributes[0].bufferIndex = 0
        
        ellipseVertexDescriptor.attributes[1].format = .float4
        ellipseVertexDescriptor.attributes[1].offset = MemoryLayout<SIMD4<Float>>.stride
        ellipseVertexDescriptor.attributes[1].bufferIndex = 0
        
        ellipseVertexDescriptor.attributes[2].format = .float3
        ellipseVertexDescriptor.attributes[2].offset = MemoryLayout<SIMD4<Float>>.stride   MemoryLayout<SIMD4<Float>>.stride
        ellipseVertexDescriptor.attributes[2].bufferIndex = 0
        
        ellipseVertexDescriptor.attributes[3].format = .float3
        ellipseVertexDescriptor.attributes[3].offset = MemoryLayout<SIMD4<Float>>.stride   MemoryLayout<SIMD4<Float>>.stride   MemoryLayout<SIMD3<Float>>.stride
        ellipseVertexDescriptor.attributes[3].bufferIndex = 0

        ellipseVertexDescriptor.layouts[0].stride = MemoryLayout<EllipseVertexDescriptor>.stride

        ellipsePipelineDescriptor.vertexDescriptor = ellipseVertexDescriptor

        pipelineState = try! device.makeRenderPipelineState(descriptor: ellipsePipelineDescriptor)
    }

    func render(device: MTLDevice, encoder: MTLRenderCommandEncoder, primitive: MetalPrimitive) {
        var mp = primitive
        guard let gp = mp.data as? CliGeometryPrimitiveData,
              let tlp = gp.tlPositionN, //top left position in normalized device coordinates
              let brp = gp.brPositionN, //bottom right position in normalized device coordinates
              let fillColor = gp.fillColorN else {return}

        if (mp.redraw) {
            let vertices: [EllipseVertexDescriptor] = [
                EllipseVertexDescriptor(
                    position: SIMD4<Float>(tlp.x, tlp.y, 0, 1),
                    color: SIMD4<Float>(fillColor.x, fillColor.y, fillColor.z, fillColor.w),
                    tlPosition: SIMD3<Float>(tlp.x, tlp.y, 0),
                    brPosition: SIMD3<Float>(brp.x, brp.y, 0)
                ),
                EllipseVertexDescriptor(
                    position: SIMD4<Float>(tlp.x, brp.y, 0, 1),
                    color: SIMD4<Float>(fillColor.x, fillColor.y, fillColor.z, fillColor.w),
                    tlPosition: SIMD3<Float>(tlp.x, tlp.y, 0),
                    brPosition: SIMD3<Float>(brp.x, brp.y, 0)
                ),
                EllipseVertexDescriptor(
                    position: SIMD4<Float>(brp.x, brp.y, 0, 1),
                    color: SIMD4<Float>(fillColor.x, fillColor.y, fillColor.z, fillColor.w),
                    tlPosition: SIMD3<Float>(tlp.x, tlp.y, 0),
                    brPosition: SIMD3<Float>(brp.x, brp.y, 0)
                ),
                EllipseVertexDescriptor(
                    position: SIMD4<Float>(brp.x, tlp.y, 0, 1),
                    color: SIMD4<Float>(fillColor.x, fillColor.y, fillColor.z, fillColor.w),
                    tlPosition: SIMD3<Float>(tlp.x, tlp.y, 0),
                    brPosition: SIMD3<Float>(brp.x, brp.y, 0)
                ),
            ]

            let indicies: [UInt16] = [
                0, 1, 2,
                2, 3, 0,
            ]

            mp.vertexBuffer = device.makeBuffer(bytes: vertices, length: vertices.count * MemoryLayout<EllipseVertexDescriptor>.stride, options: [])
            mp.indexBuffer = device.makeBuffer(bytes: indicies, length: indicies.count * MemoryLayout<UInt16>.size, options: [])
        }

        encoder.setRenderPipelineState(pipelineState)
        encoder.setVertexBuffer(mp.vertexBuffer!, offset: 0, index: 0)
        encoder.drawIndexedPrimitives(type: .triangle, indexCount: 6, indexType: .uint16, indexBuffer: mp.indexBuffer!, indexBufferOffset: 0)
    }
}
 

Результатом приведенного выше кода является то, что все фрагменты отбрасываются.

Чего мне не хватает? Любая помощь будет оценена.