// Take care of pending timer queries. // If we can't use `sample_counters_in_buffer` we have to create a dummy blit encoder! // // There is a known bug in Metal where blit encoders won't write timestamps if they don't have a blit operation. // See https://github.com/gpuweb/gpuweb/issues/2046#issuecomment-1205793680 & https://source.chromium.org/chromium/chromium/src/+/006c4eb70c96229834bbaf271290f40418144cd3:third_party/dawn/src/dawn/native/metal/BackendMTL.mm;l=350 // // To make things worse: // * what counts as a blit operation is a bit unclear, experimenting seemed to indicate that resolve_counters doesn't count. // * in some cases (when?) using `set_start_of_encoder_sample_index` doesn't work, so we have to use `set_end_of_encoder_sample_index` instead // // All this means that pretty much the only *reliable* thing as of writing is to: // * create a dummy blit encoder using set_end_of_encoder_sample_index // * do a dummy write that is known to be not optimized out. // * close the encoder since we used set_end_of_encoder_sample_index and don't want to get any extra stuff in there. // * create another encoder for whatever we actually had in mind. let supports_sample_counters_in_buffer = self
.shared
.private_caps
.timestamp_query_support
.contains(TimestampQuerySupport::ON_BLIT_ENCODER);
if !self.state.pending_timer_queries.is_empty() && !supports_sample_counters_in_buffer {
objc::rc::autoreleasepool(|| { let descriptor = metal::BlitPassDescriptor::new(); letmut last_query = None; for (i, (set, index)) inself.state.pending_timer_queries.drain(..).enumerate()
{ let sba_descriptor = descriptor
.sample_buffer_attachments()
.object_at(i as _)
.unwrap();
sba_descriptor
.set_sample_buffer(set.counter_sample_buffer.as_ref().unwrap());
// Here be dragons: // As mentioned above, for some reasons using the start of the encoder won't yield any results sometimes!
sba_descriptor
.set_start_of_encoder_sample_index(metal::COUNTER_DONT_SAMPLE);
sba_descriptor.set_end_of_encoder_sample_index(index as _);
last_query = Some((set, index));
} let encoder = cmd_buf.blit_command_encoder_with_descriptor(descriptor);
// As explained above, we need to do some write: // Conveniently, we have a buffer with every query set, that we can use for this for a dummy write, // since we know that it is going to be overwritten again on timer resolve and HAL doesn't define its state before that. let raw_range = metal::NSRange {
location: last_query.as_ref().unwrap().1as u64 * crate::QUERY_SIZE,
length: 1,
};
encoder.fill_buffer(
&last_query.as_ref().unwrap().0.raw_buffer,
raw_range, 255, // Don't write 0, so it's easier to identify if something went wrong.
);
// UNTESTED: // If the above described issue with empty blit encoder applies to `sample_counters_in_buffer` as well, we should use the same workaround instead! for (set, index) inself.state.pending_timer_queries.drain(..) {
debug_assert!(supports_sample_counters_in_buffer);
encoder.sample_counters_in_buffer(
set.counter_sample_buffer.as_ref().unwrap(),
index as _, true,
)
}
} self.state.blit.as_ref().unwrap()
}
// Extend with the sizes of the mapped vertex buffers, in the order // they were added to the map.
result_sizes.extend(stage_info.vertex_buffer_mappings.iter().map(|vbm| { self.vertex_buffer_size_map
.get(&(vbm.id as u64))
.map(|size| u32::try_from(size.get()).unwrap_or(u32::MAX))
.unwrap_or_default()
}));
if !result_sizes.is_empty() {
Some((slot as _, result_sizes))
} else {
None
}
}
}
implcrate::CommandEncoder forsuper::CommandEncoder { type A = super::Api;
unsafefn begin_encoding(&mutself, label: crate::Label) -> Result<(), crate::DeviceError> { let queue = &self.raw_queue.lock(); let retain_references = self.shared.settings.retain_command_buffer_references; let raw = objc::rc::autoreleasepool(move || { let cmd_buf_ref = if retain_references {
queue.new_command_buffer()
} else {
queue.new_command_buffer_with_unretained_references()
}; iflet Some(label) = label {
cmd_buf_ref.set_label(label);
}
cmd_buf_ref.to_owned()
});
self.raw_cmd_buf = Some(raw);
Ok(())
}
unsafefn discard_encoding(&mutself) { self.leave_blit(); // when discarding, we don't have a guarantee that // everything is in a good state, so check carefully iflet Some(encoder) = self.state.render.take() {
encoder.end_encoding();
} iflet Some(encoder) = self.state.compute.take() {
encoder.end_encoding();
} self.raw_cmd_buf = None;
}
unsafefn end_encoding(&mutself) -> Result<super::CommandBuffer, crate::DeviceError> { // Handle pending timer query if any. if !self.state.pending_timer_queries.is_empty() { self.leave_blit(); self.enter_blit();
}
unsafefn begin_query(&mutself, set: &super::QuerySet, index: u32) { match set.ty {
wgt::QueryType::Occlusion => { self.state
.render
.as_ref()
.unwrap()
.set_visibility_result_mode(
metal::MTLVisibilityResultMode::Boolean,
index as u64 * crate::QUERY_SIZE,
);
}
_ => {}
}
} unsafefn end_query(&mutself, set: &super::QuerySet, _index: u32) { match set.ty {
wgt::QueryType::Occlusion => { self.state
.render
.as_ref()
.unwrap()
.set_visibility_result_mode(metal::MTLVisibilityResultMode::Disabled, 0);
}
_ => {}
}
} unsafefn write_timestamp(&mutself, set: &super::QuerySet, index: u32) { let support = self.shared.private_caps.timestamp_query_support;
debug_assert!(
support.contains(TimestampQuerySupport::STAGE_BOUNDARIES), "Timestamp queries are not supported"
); let sample_buffer = set.counter_sample_buffer.as_ref().unwrap(); let with_barrier = true;
// Try to use an existing encoder for timestamp query if possible. // This works only if it's supported for the active encoder. iflet (true, Some(encoder)) = (
support.contains(TimestampQuerySupport::ON_BLIT_ENCODER), self.state.blit.as_ref(),
) {
encoder.sample_counters_in_buffer(sample_buffer, index as _, with_barrier);
} elseiflet (true, Some(encoder)) = (
support.contains(TimestampQuerySupport::ON_RENDER_ENCODER), self.state.render.as_ref(),
) {
encoder.sample_counters_in_buffer(sample_buffer, index as _, with_barrier);
} elseiflet (true, Some(encoder)) = (
support.contains(TimestampQuerySupport::ON_COMPUTE_ENCODER), self.state.compute.as_ref(),
) {
encoder.sample_counters_in_buffer(sample_buffer, index as _, with_barrier);
} else { // If we're here it means we either have no encoder open, or it's not supported to sample within them. // If this happens with render/compute open, this is an invalid usage!
debug_assert!(self.state.render.is_none() && self.state.compute.is_none());
// But otherwise it means we'll put defer this to the next created encoder. self.state.pending_timer_queries.push((set.clone(), index));
// Ensure we didn't already have a blit open. self.leave_blit();
};
}
unsafefn reset_queries(&mutself, set: &super::QuerySet, range: Range<u32>) { let encoder = self.enter_blit(); let raw_range = metal::NSRange {
location: range.start as u64 * crate::QUERY_SIZE,
length: (range.end - range.start) as u64 * crate::QUERY_SIZE,
};
encoder.fill_buffer(&set.raw_buffer, raw_range, 0);
}
unsafefn copy_query_results(
&mutself,
set: &super::QuerySet,
range: Range<u32>,
buffer: &super::Buffer,
offset: wgt::BufferAddress,
_: wgt::BufferSize, // Metal doesn't support queries that are bigger than a single element are not supported
) { let encoder = self.enter_blit(); match set.ty {
wgt::QueryType::Occlusion => { let size = (range.end - range.start) as u64 * crate::QUERY_SIZE;
encoder.copy_from_buffer(
&set.raw_buffer,
range.start as u64 * crate::QUERY_SIZE,
&buffer.raw,
offset,
size,
);
}
wgt::QueryType::Timestamp => {
encoder.resolve_counters(
set.counter_sample_buffer.as_ref().unwrap(),
metal::NSRange::new(range.start as u64, (range.end - range.start) as u64),
&buffer.raw,
offset,
);
}
wgt::QueryType::PipelineStatistics(_) => todo!(),
}
}
for (set, index) inself.state.pending_timer_queries.drain(..) { let sba_descriptor = next_sba_descriptor();
sba_descriptor.set_sample_buffer(set.counter_sample_buffer.as_ref().unwrap());
sba_descriptor.set_start_of_vertex_sample_index(index as _);
sba_descriptor.set_end_of_fragment_sample_index(metal::COUNTER_DONT_SAMPLE);
}
sba_descriptor.set_start_of_vertex_sample_index(
timestamp_writes
.beginning_of_pass_write_index
.map_or(metal::COUNTER_DONT_SAMPLE, |i| i as _),
);
sba_descriptor.set_end_of_fragment_sample_index(
timestamp_writes
.end_of_pass_write_index
.map_or(metal::COUNTER_DONT_SAMPLE, |i| i as _),
);
}
unsafefn set_bind_group(
&mutself,
layout: &super::PipelineLayout,
group_index: u32,
group: &super::BindGroup,
dynamic_offsets: &[wgt::DynamicOffset],
) { let bg_info = &layout.bind_group_infos[group_index as usize];
iflet Some(ref encoder) = self.state.render { letmut changes_sizes_buffer = false; for index in0..group.counters.vs.buffers { let buf = &group.buffers[index as usize]; letmut offset = buf.offset; iflet Some(dyn_index) = buf.dynamic_index {
offset += dynamic_offsets[dyn_index as usize] as wgt::BufferAddress;
}
encoder.set_vertex_buffer(
(bg_info.base_resource_indices.vs.buffers + index) as u64,
Some(buf.ptr.as_native()),
offset,
); iflet Some(size) = buf.binding_size { let br = naga::ResourceBinding {
group: group_index,
binding: buf.binding_location,
}; self.state.storage_buffer_length_map.insert(br, size);
changes_sizes_buffer = true;
}
} if changes_sizes_buffer { iflet Some((index, sizes)) = self.state.make_sizes_buffer_update(
naga::ShaderStage::Vertex,
&mutself.temp.binding_sizes,
) {
encoder.set_vertex_bytes(
index as _,
(sizes.len() * WORD_SIZE) as u64,
sizes.as_ptr().cast(),
);
}
}
changes_sizes_buffer = false; for index in0..group.counters.fs.buffers { let buf = &group.buffers[(group.counters.vs.buffers + index) as usize]; letmut offset = buf.offset; iflet Some(dyn_index) = buf.dynamic_index {
offset += dynamic_offsets[dyn_index as usize] as wgt::BufferAddress;
}
encoder.set_fragment_buffer(
(bg_info.base_resource_indices.fs.buffers + index) as u64,
Some(buf.ptr.as_native()),
offset,
); iflet Some(size) = buf.binding_size { let br = naga::ResourceBinding {
group: group_index,
binding: buf.binding_location,
}; self.state.storage_buffer_length_map.insert(br, size);
changes_sizes_buffer = true;
}
} if changes_sizes_buffer { iflet Some((index, sizes)) = self.state.make_sizes_buffer_update(
naga::ShaderStage::Fragment,
&mutself.temp.binding_sizes,
) {
encoder.set_fragment_bytes(
index as _,
(sizes.len() * WORD_SIZE) as u64,
sizes.as_ptr().cast(),
);
}
}
for index in0..group.counters.vs.samplers { let res = group.samplers[index as usize];
encoder.set_vertex_sampler_state(
(bg_info.base_resource_indices.vs.samplers + index) as u64,
Some(res.as_native()),
);
} for index in0..group.counters.fs.samplers { let res = group.samplers[(group.counters.vs.samplers + index) as usize];
encoder.set_fragment_sampler_state(
(bg_info.base_resource_indices.fs.samplers + index) as u64,
Some(res.as_native()),
);
}
for index in0..group.counters.vs.textures { let res = group.textures[index as usize];
encoder.set_vertex_texture(
(bg_info.base_resource_indices.vs.textures + index) as u64,
Some(res.as_native()),
);
} for index in0..group.counters.fs.textures { let res = group.textures[(group.counters.vs.textures + index) as usize];
encoder.set_fragment_texture(
(bg_info.base_resource_indices.fs.textures + index) as u64,
Some(res.as_native()),
);
}
// Call useResource on all textures and buffers used indirectly so they are alive for (resource, use_info) in group.resources_to_use.iter() {
encoder.use_resource_at(resource.as_native(), use_info.uses, use_info.stages);
}
}
letmut changes_sizes_buffer = false; for index in0..group.counters.cs.buffers { let buf = &group.buffers[(index_base.buffers + index) as usize]; letmut offset = buf.offset; iflet Some(dyn_index) = buf.dynamic_index {
offset += dynamic_offsets[dyn_index as usize] as wgt::BufferAddress;
}
encoder.set_buffer(
(bg_info.base_resource_indices.cs.buffers + index) as u64,
Some(buf.ptr.as_native()),
offset,
); iflet Some(size) = buf.binding_size { let br = naga::ResourceBinding {
group: group_index,
binding: buf.binding_location,
}; self.state.storage_buffer_length_map.insert(br, size);
changes_sizes_buffer = true;
}
} if changes_sizes_buffer { iflet Some((index, sizes)) = self.state.make_sizes_buffer_update(
naga::ShaderStage::Compute,
&mutself.temp.binding_sizes,
) {
encoder.set_bytes(
index as _,
(sizes.len() * WORD_SIZE) as u64,
sizes.as_ptr().cast(),
);
}
}
for index in0..group.counters.cs.samplers { let res = group.samplers[(index_base.samplers + index) as usize];
encoder.set_sampler_state(
(bg_info.base_resource_indices.cs.samplers + index) as u64,
Some(res.as_native()),
);
} for index in0..group.counters.cs.textures { let res = group.textures[(index_base.textures + index) as usize];
encoder.set_texture(
(bg_info.base_resource_indices.cs.textures + index) as u64,
Some(res.as_native()),
);
}
// Call useResource on all textures and buffers used indirectly so they are alive for (resource, use_info) in group.resources_to_use.iter() { if !use_info.visible_in_compute { continue;
}
encoder.use_resource(resource.as_native(), use_info.uses);
}
}
}
unsafefn set_push_constants(
&mutself,
layout: &super::PipelineLayout,
stages: wgt::ShaderStages,
offset_bytes: u32,
data: &[u32],
) { let state_pc = &mutself.state.push_constants; if state_pc.len() < layout.total_push_constants as usize {
state_pc.resize(layout.total_push_constants as usize, 0);
}
debug_assert_eq!(offset_bytes as usize % WORD_SIZE, 0);
let offset_words = offset_bytes as usize / WORD_SIZE;
state_pc[offset_words..offset_words + data.len()].copy_from_slice(data);
if stages.contains(wgt::ShaderStages::COMPUTE) { self.state.compute.as_ref().unwrap().set_bytes(
layout.push_constants_infos.cs.unwrap().buffer_index as _,
(layout.total_push_constants as usize * WORD_SIZE) as _,
state_pc.as_ptr().cast(),
)
} if stages.contains(wgt::ShaderStages::VERTEX) { self.state.render.as_ref().unwrap().set_vertex_bytes(
layout.push_constants_infos.vs.unwrap().buffer_index as _,
(layout.total_push_constants as usize * WORD_SIZE) as _,
state_pc.as_ptr().cast(),
)
} if stages.contains(wgt::ShaderStages::FRAGMENT) { self.state.render.as_ref().unwrap().set_fragment_bytes(
layout.push_constants_infos.fs.unwrap().buffer_index as _,
(layout.total_push_constants as usize * WORD_SIZE) as _,
state_pc.as_ptr().cast(),
)
}
}
objc::rc::autoreleasepool(|| { // TimeStamp Queries and ComputePassDescriptor were both introduced in Metal 2.3 (macOS 11, iOS 14) // and we currently only need ComputePassDescriptor for timestamp queries let encoder = ifself.shared.private_caps.timestamp_query_support.is_empty() {
raw.new_compute_command_encoder()
} else { let descriptor = metal::ComputePassDescriptor::new();
for (set, index) inself.state.pending_timer_queries.drain(..) { let sba_descriptor = next_sba_descriptor();
sba_descriptor.set_sample_buffer(set.counter_sample_buffer.as_ref().unwrap());
sba_descriptor.set_start_of_encoder_sample_index(index as _);
sba_descriptor.set_end_of_encoder_sample_index(metal::COUNTER_DONT_SAMPLE);
}
sba_descriptor.set_start_of_encoder_sample_index(
timestamp_writes
.beginning_of_pass_write_index
.map_or(metal::COUNTER_DONT_SAMPLE, |i| i as _),
);
sba_descriptor.set_end_of_encoder_sample_index(
timestamp_writes
.end_of_pass_write_index
.map_or(metal::COUNTER_DONT_SAMPLE, |i| i as _),
);
}
impl Drop forsuper::CommandEncoder { fn drop(&mutself) { // Metal raises an assert when a MTLCommandEncoder is deallocated without a call // to endEncoding. This isn't documented in the general case at // https://developer.apple.com/documentation/metal/mtlcommandencoder, but for the // more-specific MTLComputeCommandEncoder it is stated as a requirement at // https://developer.apple.com/documentation/metal/mtlcomputecommandencoder. It // appears to be a requirement for all MTLCommandEncoder objects. Failing to call // endEncoding causes a crash with the message 'Command encoder released without // endEncoding'. To prevent this, we explicitiy call discard_encoding, which // calls end_encoding on any still-held metal::CommandEncoders. unsafe { self.discard_encoding();
} self.counters.command_encoders.sub(1);
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.27 Sekunden
(vorverarbeitet am 2026-06-18)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.