金属渲染视图(MTKView)中闪烁

问题描述 投票:0回答:1

我正在尝试为某种图形软件创建一个“无限网格”,类似于 Desmos 或 Geogebra 中的网格。我希望使用金属渲染视图。我的问题是,虽然代码看起来工作正常,但它经常闪烁。

Here is a gif with the described behavior

如您所见,在第一部分中表现正常,然后开始“闪烁”

请注意,当网格缩放时,由

scrollWheel
事件触发网格重绘时,不会发生这种情况。

代码作用的简要解释

  1. draw()
    函数中,我计算视图内应该可见的点数
  2. 我调用
    drawPrimitives
    函数,其中
    instanceCount
    参数等于应可见的点数
  3. 在我的着色器文件中,我使用
    instance_id
    属性计算每个图元的位置

我认为问题应该与视图处理调整大小的方式有关,因为当缩放事件触发重绘时,一切都会按预期工作。另外,不要认为这是性能问题,因为 fps 非常高且稳定。

这里是包含完整代码的 GitHub

我尝试弄乱

isPaused
enableSetNeedsDisplay
属性,但我无法实现
isPaused = true
enableSetNeedsDisplay = false
的版本。

最让我困惑的部分是,这似乎是随机发生的,但同样,在我的情况下,性能似乎不是问题......

如何在调整窗口大小时避免烦人的闪烁效果?

这是我的视图类:

import AppKit
import MetalKit

class MetalView : MTKView {
    
    var queue : MTLCommandQueue!
    var pipelineState : MTLRenderPipelineState!
    
    private var spacing : Float = 10.0
    private var xoffset : Float = 0
    private var yoffset : Float = 0
    
    
    required init(coder: NSCoder) {
        
        super.init(coder: coder)
        
        self.device = MTLCreateSystemDefaultDevice()
        
        self.queue = device?.makeCommandQueue()
        
        self.clearColor = MTLClearColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1)
        
        self.colorPixelFormat = .bgra8Unorm
        
        self.isPaused = true; self.enableSetNeedsDisplay = true
        
        makePipeline()
        print("costructor")
        
    }
    
    override func draw(_ dirtyRect: NSRect) {
        drawGrid()
    }
    
    private func drawGrid(){
        
        guard let drawable = self.currentDrawable,
              let passDescriptor = self.currentRenderPassDescriptor,
              let commandBuffer = queue.makeCommandBuffer(),
              let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: passDescriptor)
        else {
            print("errore")
            return
        }
        
        let infos = [Float(bounds.width), Float(bounds.height), xoffset, yoffset, spacing]
        
        renderEncoder.setRenderPipelineState(pipelineState)
        renderEncoder.setVertexBytes(infos, length: MemoryLayout<Float>.size * infos.count, index: 0)
        renderEncoder.drawPrimitives(type: .point, vertexStart: 0, vertexCount: 1,
                                     instanceCount: Int(
                                        (floor((infos[0] - xoffset) / spacing)+1) *
                                        (floor((infos[1] - yoffset) / spacing)+1)))
        
        renderEncoder.endEncoding()
        
        commandBuffer.present(drawable)
        commandBuffer.commit()
        commandBuffer.waitUntilCompleted()
    }
    
    private func makePipeline() {
        
        guard let library = device?.makeDefaultLibrary() else {print("errore makePipeline"); return}
        
        let pipelineStateDescriptor = MTLRenderPipelineDescriptor()
        pipelineStateDescriptor.vertexFunction = library.makeFunction(name: "vertex_function")
        pipelineStateDescriptor.fragmentFunction = library.makeFunction(name: "fragment_function")
        pipelineStateDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
        
        do {
            pipelineState = try device?.makeRenderPipelineState(descriptor: pipelineStateDescriptor)
        }
        catch let error as NSError{
            print("Errore nella creazione della pipeline")
            print(error)
        }
        
    }
    
    func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
        
    }

这是我的着色器文件:

#include <metal_stdlib>
using namespace metal;


struct Fragment{
    float4 position [[position]];
    float pointSize [[point_size]] = 5.0f;
    float4 color;
};

vertex Fragment vertex_function (unsigned int instanceID [[instance_id]],
                                 constant float *parameters [[buffer(0)]])
{
    
    const float width = parameters[0];
    const float height = parameters[1];
    const float xoffset = parameters[2];
    const float yoffset = parameters[3];
    const float spacing = parameters[4];
    
    int xdots = floor((width - xoffset)/spacing) + 1;
    
    float non_normalized_x = xoffset + (instanceID % xdots) * spacing;
    float non_normalized_y = yoffset + (instanceID / xdots) * spacing;
    
    float normalized_x = non_normalized_x / width * 2 - 1;
    float normalized_y = non_normalized_y / height * 2 - 1;
    
    Fragment f;
    f.position = float4(normalized_x, normalized_y, 0, 1);
    
    return f;
}

fragment float4 fragment_function(Fragment in [[stage_in]])
{
    return float4(0.8,0.8,0.8,1);
}
swift macos gpu metal appkit
1个回答
0
投票

我终于找到答案了。问题是,在调整大小时,视图层会自动拉伸,然后重新绘制,从而产生“抖动”效果。 我在这个存储库中找到了解决方案。基本上我必须通过添加以下行来同步渲染视图:

 presentsWithTransaction = true

然后,在

draw()
函数中,我必须使用这 3 行:

 commandBuffer.commit()
 commandBuffer.waitUntilScheduled()
 drawable.present()

同步绘制。 我的代码位于 this repo

© www.soinside.com 2019 - 2024. All rights reserved.