Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

extensions/khr: Add VK_KHR_pipeline_binary extension #944

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

MarijnS95
Copy link
Collaborator

@MarijnS95 MarijnS95 commented Sep 19, 2024

No description provided.

@MarijnS95 MarijnS95 marked this pull request as draft September 19, 2024 21:42
@MarijnS95 MarijnS95 mentioned this pull request Sep 19, 2024
55 tasks
@MarijnS95 MarijnS95 force-pushed the khr-pipeline-binary branch 2 times, most recently from e46d73b to a97aa7d Compare September 26, 2024 10:01
@MarijnS95 MarijnS95 force-pushed the khr-pipeline-binary branch from a97aa7d to 0b62cd5 Compare December 2, 2024 12:04
@MarijnS95
Copy link
Collaborator Author

MarijnS95 commented Dec 2, 2024

Our no_std build complains:

error[E0412]: cannot find type `Vec` in this scope
  --> ash/src/extensions/khr/pipeline_binary.rs:66:19
   |
66 |     ) -> VkResult<Vec<u8>> {
   |                   ^^^ not found in this scope
   |
help: consider importing this struct
   |
3  | use alloc::vec::Vec;
   |

It's quite unfortunate that the clippy::std_instead_of_alloc lint didn't warn that Vec from the prelude is in std (this lint only triggers if use std::vec::Vec is explicitly imported). CC @i509VCB who landed this feature, I haven't found an upstream tracking issue but maybe you know about this?

@MarijnS95 MarijnS95 force-pushed the khr-pipeline-binary branch 2 times, most recently from 69cf5ec to dd71365 Compare December 2, 2024 13:12
// TODO: This returns NOT_ENOUGH_SPACE_KHR, not INCOMPLETE (because nothing is written, instead of returning an impartial result)
read_into_uninitialized_vector(|count, data| {
(self.fp.get_pipeline_binary_data_khr)(
Copy link
Collaborator Author

@MarijnS95 MarijnS95 Dec 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what to do here. We could map NOT_ENOUGH_SPACE_KHR to INCOMPLETE to make read_into_uninitialized_vector() try again, though we could also be changing it at some point to return the incomplete but partial result to the user, which is invalid here.

If we don't, we can still use read_into_uninitialized_vector() to help us call vkGetPipelineBinaryDataKHR() twice. On NOT_ENOUGH_SPACE_KHR - which is unexpected - that error is bubbled up to the caller (and they wouldn't know how to handle it, since the wrapper is the one that was dealing with querying and preallocating for the requested size initially...).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mapped to INCOMPLETE and rewrote the documentation, also to reference the upstream issue (non-public repo).

@Ralith I think this is ready for merging unless you find anything glaring, and I'll follow up and clean up depending on how the upstream discussion pans out.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After upstream clarification, VK_NOT_ENOUGH_SPACE_KHR docs are accidentally missing and will be added. There's a fundamental difference with VK_INCOMPLETE that's already described at vkGetPipelineBinaryDataKHR() and supposedly the reason why this new return value was added:

and on return pPipelineBinaryDataSize is overwritten with the size of the data, in bytes, that is required to store the binary.

So even if the previously requested size is too small, the function doesn't write data (leaves it MaybeUninit 👀) but uses pPipelineBinaryDataSize to communicate the required space again, so that the user doesn't have to call it yet another time.

This is unfortunately inconsistent from other binary-array-returning functions that predate the addition of this error return. They have their own ways of documenting "special behaviour"...


I think, but didn't ask, that this facilitates a pattern to preallocate static arrays on the stack with some predetermined upper bound. If less space is required only one API call is needed, otherwise the next call can be done immediately with the right size after heap-allocating something bigger. That pattern isn't possible with VK_INCOMPLETE (and that would write data twice, too, for functions where partial results may still be valid).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wrote a new variant of read_into_uninitialized_binary_vector() to deal with this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also added some debug_assert!(). None of the clauses invoke undefined behaviour, but do point to an incorrect implementation when returning VK_ERROR_NOT_ENOUGH_SPACE_KHR.


Separate from this all, read_into_uninitialized_vector() was initially designed (see its doc-comment) te deal with functions that arbitrarily change their count at runtime between calls, hence the infinite loop.

This behaviour is not expected for most functions currently using read_into_uninitialized_vector(), but reuse it for conciseness due to also returning VK_INCOMPLETE. These calls could benefit from the same "maximum of 2 calls" strategy (with debug asserts) and otherwise bubble VK_INCOMPLETE back to the user. Unfortunately they cannot easily use a preallocated upper-bound Vec like I did in read_into_uninitialized_binary_vector(), because the Vulkan call wouldn't immediately tell us what the desired count is if it returned VK_INCOMPLETE. We'd need a third call for that.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These calls could benefit from the same "maximum of 2 calls" strategy

What's the benefit here? Just hypothetical improved detection of implementation bugs? Seems like unnecessary churn.

@MarijnS95 MarijnS95 marked this pull request as ready for review December 2, 2024 13:22
@MarijnS95 MarijnS95 requested a review from Ralith December 2, 2024 13:22
@MarijnS95 MarijnS95 added this to the Ash 0.39 with Vulkan 1.4 milestone Dec 3, 2024
@MarijnS95 MarijnS95 force-pushed the khr-pipeline-binary branch 5 times, most recently from a28a63e to 19c3eb3 Compare December 4, 2024 17:23
@MarijnS95 MarijnS95 force-pushed the khr-pipeline-binary branch from 19c3eb3 to 7be4595 Compare December 6, 2024 14:50
pub unsafe fn get_pipeline_key(
&self,
pipeline_create_info: Option<&vk::PipelineCreateInfoKHR<'_>>,
) -> VkResult<vk::PipelineBinaryKeyKHR<'_>> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't vk::PipelineBinaryKeyKHR be an out parameter, to allow for extension? If this is for some reason undesired, then the lifetime should be 'static.

Comment on lines +68 to +69
/// Calls `f` twice until it does not return [`vk::Result::ERROR_NOT_ENOUGH_SPACE_KHR`], ensuring all
/// available binary data has been read into the vector.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"twice until" is weird word choice. Maybe "at most twice"?

count > 4096,
"Implementation should have updated the value to be higher than the initial request"
);
err_code = f(&mut count, data.as_mut_ptr().cast());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be missing the code to actually expand the Vec.

Suggested change
err_code = f(&mut count, data.as_mut_ptr().cast());
data.reserve(count);
err_code = f(&mut count, data.as_mut_ptr().cast());

Comment on lines +84 to +85
let mut count = 4096;
let mut data = Vec::<u8>::with_capacity(count);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: with_capacity may allocate extra space, and we might as well use it

Suggested change
let mut count = 4096;
let mut data = Vec::<u8>::with_capacity(count);
let mut data = Vec::<u8>::with_capacity(4096);
let mut count = data.capacity();

(and adjust the debug_assert accordingly)

pub(crate) unsafe fn read_into_uninitialized_binary_vector(
mut f: impl FnMut(&mut usize, *mut ffi::c_void) -> vk::Result,
) -> VkResult<Vec<u8>> {
let mut count = 4096;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we know that binaries are, in practice, usually smaller than this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants