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;
}
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).