1

I am rendering point fragments from a buffer with this call:

renderEncoder.drawPrimitives(type: .point,
                             vertexStart: 0,
                             vertexCount: 1,
                             instanceCount: emitter.currentParticles)

emitter.currentParticles is the total number of particles in the buffer. Is it possible to somehow draw only a portion of the buffer?

I have tried this, but it draws the first half of the buffer:

renderEncoder.drawPrimitives(type: .point,
                             vertexStart: emitter.currentParticles / 2,
                             vertexCount: 1,
                             instanceCount: emitter.currentParticles / 2)

In fact, it seems that vertexStart has no effect. I can seemingly set it to any value, and it still starts at 0.

Edit:

Pipeline configuration:

private func buildParticlePipelineStates() {
    do {
        guard let library = Renderer.device.makeDefaultLibrary(),
        let function = library.makeFunction(name: "compute") else { return }

        // particle update pipeline state
        particlesPipelineState = try Renderer.device.makeComputePipelineState(function: function)

        // render pipeline state
        let vertexFunction = library.makeFunction(name: "vertex_particle")
        let fragmentFunction = library.makeFunction(name: "fragment_particle")
        let descriptor = MTLRenderPipelineDescriptor()
        descriptor.vertexFunction = vertexFunction
        descriptor.fragmentFunction = fragmentFunction

        descriptor.colorAttachments[0].pixelFormat = renderPixelFormat
        descriptor.colorAttachments[0].isBlendingEnabled = true
        descriptor.colorAttachments[0].rgbBlendOperation = .add
        descriptor.colorAttachments[0].alphaBlendOperation = .add
        descriptor.colorAttachments[0].sourceRGBBlendFactor = .sourceAlpha
        descriptor.colorAttachments[0].sourceAlphaBlendFactor = .sourceAlpha
        descriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha
        descriptor.colorAttachments[0].destinationAlphaBlendFactor = .oneMinusSourceAlpha

        renderPipelineState = try
        Renderer.device.makeRenderPipelineState(descriptor: descriptor)
        renderPipelineState = try Renderer.device.makeRenderPipelineState(descriptor: descriptor)
    } catch let error {
        print(error.localizedDescription)
    }
}

Vertex shader:

struct VertexOut {
    float4 position   [[ position ]];
    float  point_size [[ point_size ]];
    float4 color;
};


vertex VertexOut vertex_particle(constant float2 &size [[buffer(0)]],
                             device Particle *particles [[buffer(1)]],
                             constant float2 &emitterPosition [[ buffer(2) ]],
                             uint instance [[instance_id]])
{
    VertexOut out;

    float2 position = particles[instance].position + emitterPosition;
    out.position.xy = position.xy / size * 2.0 - 1.0;
    out.position.z = 0;
    out.position.w = 1;
    out.point_size = particles[instance].size * particles[instance].scale;
    out.color = particles[instance].color;
    return out;
}

fragment float4 fragment_particle(VertexOut in [[ stage_in ]],
                              texture2d<float> particleTexture [[ texture(0) ]],
                              float2 point [[ point_coord ]]) {
    constexpr sampler default_sampler;

    float4 color = particleTexture.sample(default_sampler, point);

    if ((color.a < 0.01) || (in.color.a < 0.01)) {
        discard_fragment();
    }
    color = float4(in.color.xyz, 0.2 * color.a * in.color.a);
    return color;
}
Jeshua Lacock
  • 5,730
  • 1
  • 28
  • 58
  • What are you using instance count and not vertex count for the number of points you want to render? – Ken Thomases May 26 '19 at 18:44
  • That is how they set it up in the example I am using from the book Metal by Tutorials. So, should I have instaceCount to one, and vertexCount the number of particles to render? Or? Is it possible to draw a range of the buffer? – Jeshua Lacock May 26 '19 at 20:47
  • Instancing is a particular, somewhat advanced technique. If you're not familiar with it, you probably shouldn't be using it, yet. So, yes, you should generally be using vertexCount as the number of particles to render. You can specify instanceCount as 1 or just use the method that doesn't take an instanceCount parameter. All of that said, using instancing or not has implications for the vertex descriptor (if you're using that) and the vertex shader. Those may need to be adjusted but I can't tell you how since you haven't shown them. – Ken Thomases May 26 '19 at 22:23
  • It looks like it must have been set up to use instancing since I tried changing it as you suggest and it doesn't work. If more code would be helpful, just let me know what you would like to see. Any idea why vertexStart seems to have no effect? – Jeshua Lacock May 26 '19 at 23:02
  • Show how you're configuring the pipeline descriptor and, in particular, its vertex descriptor if you're using that. Also, show the vertex shader. – Ken Thomases May 27 '19 at 01:10
  • Thanks! I added the requested code. Please let me know if you would like to see anything else, or if you have any questions. – Jeshua Lacock May 27 '19 at 21:32

1 Answers1

0

You're not using a vertex descriptor nor a [[stage_in]] parameter for your vertex shader. So, Metal is not fetching/gathering vertex data for you. You're just indexing into a buffer that's laid out with your vertex data already in the format you want. That's fine. See my answer here for more info about vertex descriptor.

Given that, though, the vertexStart parameter of the draw call only affects the value of a parameter to your vertex function with the [[vertex_id]] attribute. Your vertex function doesn't have such a parameter, let alone use it. Instead it uses an [[instance_id]] parameter to index into the vertex data buffer. You can read another of my answers here for a quick primer on draw calls and how they result in calls to your vertex shader function.

There are a couple of ways you could change things to draw only half of the points. You could change the draw call you use to:

renderEncoder.drawPrimitives(type: .point,
                             vertexStart: 0,
                             vertexCount: 1,
                             instanceCount: emitter.currentParticles / 2,
                             baseInstance: emitter.currentParticles / 2)

This would not require any changes to the vertex shader. It just changes the range of values fed to the instance parameter. However, since it doesn't seem like this is really a case of instancing, I recommend that you change the shader and your draw call. For the shader, rename the instance parameter to vertex or vid and change its attribute from [[instance_id]] to [[vertex_id]]. Then, change the draw call to:

renderEncoder.drawPrimitives(type: .point,
                             vertexStart: emitter.currentParticles / 2,
                             vertexCount: emitter.currentParticles / 2)

In truth, they basically behave the same way in this case, but the latter better represents what you're doing (and the draw call is simpler, which is nice).

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
  • Thanks. Do you think I would get better performance if I do use instancing? I am trying to get as many particles to render in a pass as possible. I guess how to use instancing with this setup is another question. ;) – Jeshua Lacock May 29 '19 at 07:46
  • One other side question if I may (if it's possible I'll post another question). Given the same emitter buffer, is it possible to say have 10 different instances of each point with each having slightly different coordinates? – Jeshua Lacock May 29 '19 at 07:52
  • I don't know for sure, but I doubt instancing will perform better (or worse). To your second question, yes, that's more or less the point of instancing, to draw more things without having to compute separate vertex data for them all on the CPU nor send that much vertex data to the GPU. – Ken Thomases May 29 '19 at 14:13