Efficient thread-safe ktxTexture_VkUploadEx_WithSuballocator() with queue guard callbacks - #1231
Efficient thread-safe ktxTexture_VkUploadEx_WithSuballocator() with queue guard callbacks#1231toomuchvoltage wants to merge 11 commits into
ktxTexture_VkUploadEx_WithSuballocator() with queue guard callbacks#1231Conversation
…in conjunction with guarded suballocator callbacks.
We used to use |
|
I'm using |
The improved validation was post 1.4.313. The reporter of issue #1092 was using 1.4.335. |
…stead at the source.
You were right. This turned out to be unnecessary and I've reverted the change related to it. I realized I could just set the initial layout to |
… test the feature. An actual test case would need multiple textures simultaneously uploaded to and the entire test environment to re-use the mutexes provided in relevant scenarios. Such scenarios include other simultaneous accesses to the queue creating textures or arena `VkDeviceMemory`s.
|
Hi @MarkCallow I just added 82a5bad to demonstrate sample usage of the guarded callbacks. Truth is, it won't stress test the feature nor is that really feasible with the current single-texture test cases. Even if there were test cases requiring multiple textures, the guards within would need to be used application-wide where ever applicable. (i.e. if the graphics queue is creating textures, that would mean re-use for all accesses to graphics queue. Or any arena VkDeviceMemory accesses globally.) If you feel like this is unnecessary, I can revert. Eager to hear back. |
It is great to have a test even if it is not a stress test. I would love to have non-interactive tests of the uploaders, maybe using gtest like I will properly review this PR early next week. Please be aware that I will not merge this until v5.0.0 has been released. I can't give a date for that at present. |
|
Hi @MarkCallow , just circling back on this. It's perfectly fine if this goes out post-5.0.0. Truth is these are on-the-field improvements resulting from a commercial game on Steam shipped with LibKTX2. I'm hesitant to link it since I personally wouldn't feel comfortable with the self promotion here, but of course figuring out the title is trivial given my handle. And I personally do not have experience with GHA CI, but I suspect paid plans (which this should be?) should have no issues with GPU'd instances. |
MarkCallow
left a comment
There was a problem hiding this comment.
Is it necessary to use guarded memory allocation callbacks when using the queue guards
As I am no expert in this, I would like to find an expert to review it. From my side it looks fine except for a couple of minor comment issues.
|
Hi @MarkCallow , appreciate the feedback. The guarded memory callbacks are absolutely necessary. That said, I may have done a more heavy handed version than is necessary with VMA since I used my own pattern from my engine (which obviously needs to be more explicit): https://github.com/toomuchvoltage/HighOmega-public/blob/sauray_vkquake2/HighOmega/src/gl.cpp#L313-L474 VMA's allocation, image/buffer bind and free calls are all thread-safe. However, mapping and unmapping calls are not. They only check to ensure that no For an expert pair of eyes, I would solicit Adam Sawicki's advice. He is the original author of VMA. His handle is @sawickiap on GitHub. I'm confident he's within reach for Khronos. |
|
All done @MarkCallow , ready for another pair of eyes. |
Thanks. Working on finding a reviewer. |
|
One of my Khronos colleagues asked codex to analyze this. This is what it said.
@toomuchvoltage you have already pointed out the last item. It sounds like we need to look at the vdi queue handling. What do you think? |
|
Hi, I'm the developer of the VMA library. I'm sorry for the delayed response. Mapping in VMA is thread-safe. About raw Vulkan (functions
However, using the recommended library functions
For more information, see this documentation chapter: |
|
Thanks @sawickiap. @toomuchvoltage do you have any comments on this or the codex review? |
|
Hi @MarkCallow @sawickiap , deeply appreciate the reviews. Fantastic to be informed about On the P1 issue: once again, since I was applying the learning from my own engine, I simply brought the assumptions as well for general use. The point is absolutely correct and my engine has a thread-safe cache for per-thread KTX VDIs. Thread-safe objects: Destruction: I guess this becoming the general usage pattern was implicit in my assumptions. We can ask for assurances on this in the documentation. On the first P2 issue: my engine effectively has 1 copy queue, 1 transfer queue and 1 graphics queue. Is it safe to assume that this is a globally recommended pattern? Is it safe or performant for other engines to have multiple transfer queues for example? If so, we can simply pass a typed On the second P2 issue: Very fine point regarding On the third P2 issue: Good point, I guess this really falls under an overall discovery in this process that can be addressed in this PR. I'll get another commit together to address these. |
…-safety. * Passing the queue to be guarded to the lock/unlock callbacks. * `mt64()` needs guarding, but VMA (unless initialized otherwise) is thread-safe by default. * Much better clean-up on `ktxTexture_LoadImageData()`'s failure.
|
Hi @MarkCallow , I just pushed a commit addressing the raised issues. Note that I finally decided to pass a |
|
Perhaps https://vulkan.gpuinfo.org can answer your questions about support for multiple queues per family. In one of your commit messages you write that a per-thread VDI is necessary. If that is the case does it no make sense to pass the device from that to the allocator callbacks, similar to what was requested in issue #1212? Doing so would require changing the signatures of the allocator callback functions. I wonder if there is any way to do that without having to create another set of |
Hi @MarkCallow, no that is not necessary.
Each Ultimately, what is being proposed in #1212 breaks separation of concerns. A type erased I will proceed to prepare a commit for exhaustive clean-ups in all failure cases of |
|
All done @MarkCallow . A quick review of the specification for EDIT: Force pushed to get the checks running again. There was a test infrastructure failure and I wanted to make sure I'm not introducing new issues with this commit. |
…a failed texture upload.
So the main contribution of this PR is to make
ktxTexture_VkUploadEx_WithSuballocator()more efficient in a threaded environment. Previously, the entire call would have to be guarded with a queue guard which would effectively make a single upload call block other upload calls or any Vulkan call needing the same queue. With this PR and the introduction ofktxTexture_VkUploadEx_WithSuballocatorAndQueueGuard(), only submissions to the queue inside the call are guarded individually leaving other calls toUploadEx()(or just general queue accesses from Vulkan) unblocked until they need the queue.The PR also includes a couple of other fixes as well:
ktxTexture_LoadImageData()mid-call failed, it would return a failure code but leave mapped memory dangling. This normally would be wasteful but not fatal. However, if we are utilizing synchronization primitives in the callbacks this would lead to a deadlock. Mapping memory would need to enter the critical section of a memory guard (protectingVkDeviceMemoryaccess) and unmapping would have to leave it. This is originally how the issue was discovered.Also changing(erroneous, see below.)destStageFlags = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;todestStageFlags = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;to allow usage of the transfer queue as well. The PR was tested in an engine which exclusively creates textures (KTX or uncompressed) on the transfer queue before doing a queue family ownership transfer to graphics.This was tested on a video game environment with
385KTX textures being loaded by 6 asset loading threads. Resolutions ranged from5548x3636to32x32with the across the board average being1806.04x1755.35.The execution environment had the following hardware specs:
Timing statistics of upload (including the queue guard) before the optimization (3 runs in ms):
Here are the same statistics collected after the optimization:
Here's a screenshot of said game environment:

More information can be provided on the application if requested.