What's the difference between Vulkan's descriptor set layouts and `VkWriteDescriptorInfo`? - vulkan

I'm following the Vulkan tutorial about uniform buffers and I'm confused as to why we need to provide descriptor set layouts where all the informations they provide seem to already be in the VkWriteDescriptorSet structures with pass in to function vkUpdateDescriptorSets.
Why does Vulkan need both pieces of information and not just what we provide through vkUpdateDescriptorSets?
From the tutorial's code:
VkWriteDescriptorSet descriptorWrite{};
descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrite.dstSet = descriptorSets[i];
descriptorWrite.dstBinding = 0;
descriptorWrite.dstArrayElement = 0;
descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descriptorWrite.descriptorCount = 1;
descriptorWrite.pBufferInfo = &bufferInfo;
void createDescriptorSetLayout() {
VkDescriptorSetLayoutBinding uboLayoutBinding{};
uboLayoutBinding.binding = 0;
uboLayoutBinding.descriptorCount = 1;
uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
uboLayoutBinding.pImmutableSamplers = nullptr;
uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
VkDescriptorSetLayoutCreateInfo layoutInfo{};
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
layoutInfo.bindingCount = 1;
layoutInfo.pBindings = &uboLayoutBinding;
if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) {
throw std::runtime_error("failed to create descriptor set layout!");
}
}

You need a descriptor set layout before you write to a descriptor set for the same reason you need a class before you can instantiate it. You can't do A a; for some type A before you define what A is. VkWriteDescriptorSet is like doing a.x = whatever; it doesn't make sense until you know what a is, and that requires having the class definition for A.
Descriptor set layouts define what a descriptor set looks like. It specifies all of the things that go into one. vkAllocateDescriptorSets is how you create an instance of a descriptor set layout (in our analogy, it's A a;, with the set being a). VkWriteDescriptorSet is how you provide the data that goes into a descriptor set (in our analogy, it's like a.x = whatever;, only potentially for multiple members).
VkWriteDescrptorSet has a lot of data that is redundantly specified in the descriptor set layout so that the implementation does not have to store the set layout as raw data.
Let's say that binding 2 in a descriptor set is a UBO of length X. And you want to attack a buffer range to that UBO descriptor. To do that, the implementation may need to know that it is a UBO descriptor and what its length is. If it does need to know that, then it would also need to store that information inside the descriptor set.
That forces the implementation to store extra data that's only useful for writing to the descriptor. If that descriptor is only set once and never again or otherwise modified infrequently (as descriptors often are), the implementation has to keep a bunch of information around for no reason.
So Vulkan makes you decide whether to keep the information around or not.

Related

How to enable VK_KHR_maintenance2 vulkan

I'm trying to create VkImageView which will be binded to index 0.
Here is my VkImageView creation code
void Image::createImageView() {
VkImageViewUsageCreateInfo imageViewUsage;
imageViewUsage.sType=VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO;
imageViewUsage.pNext=nullptr;
imageViewUsage.usage=VK_IMAGE_USAGE_STORAGE_BIT;
VkImageViewCreateInfo viewInfo{};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.pNext=&imageViewUsage;
viewInfo.image = textureImage;
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
viewInfo.subresourceRange.baseMipLevel = 0;
viewInfo.subresourceRange.levelCount = 1;
viewInfo.subresourceRange.baseArrayLayer = 0;
viewInfo.subresourceRange.layerCount = 1;
if (vkCreateImageView(device, &viewInfo, nullptr, &textureImageView) != VK_SUCCESS) {
throw std::runtime_error("failed to create texture image view!");
}
}
When I call vkUpdateDescriptorSets I get validation error:
vkCreateImageView: Includes a pNext pointer (pCreateInfo->pNext) to a VkStructureType (VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO), but its parent extension VK_KHR_maintenance2 has not been enabled. The Vulkan spec states: Each pNext member of any structure (including this one) in the pNext chain must be either NULL or a pointer to a valid instance of VkImageViewASTCDecodeModeEXT, VkImageViewUsageCreateInfo, VkSamplerYcbcrConversionInfo, VkVideoProfileKHR, or VkVideoProfilesKHR
Before this I had set viewInfo.pNext=nullptr; for which I was getting validation error:
Write update to VkDescriptorSet 0xf018750000000004[] allocated with VkDescriptorSetLayout 0x683e70000000002[] binding #0 failed with error message: Attempted write update to image descriptor failed due to: ImageView (VkImageView 0xa3c6870000000008[]) with usage mask 0x6 being used for a descriptor update of type VK_DESCRIPTOR_TYPE_STORAGE_IMAGE does not have VK_IMAGE_USAGE_STORAGE_BIT set
Can someone please help me with some hint how exactly I can solve the error?
The error message tells you exactly what to do. If you use VkImageViewUsageCreateInfo, that means you have to enable maintenance2 extension or in turn Vulkan 1.1 to which it was promoted.
Since you seem surprised by the existence of extensions, it feels likely your use of them is just accidental. You might simply want to stop using the VkImageViewUsageCreateInfo extension struct and always set pNext to NULL.

Determine if input attachment is valid within shader

For fragment shaders, it's possible to set color attachment indexes to VK_ATTACHMENT_UNUSED (from the C/C++ API); in that case, writes to those attachments are discarded. This is nice because it allows us to write shaders that unconditionally write to output attachments, and the writes may or may not be discarded, depending on what the renderer decided.
It's also possible to set input attachment indexes to VK_ATTACHMENT_UNUSED, but we're not allowed to read from such attachments. That means that if an input attachment could be VK_ATTACHMENT_UNUSED, the shader must know whether it should read from it or not.
Is there a glsl/spir-v builtin way to check if an input attachment is bound to a valid image-view vs pointing to VK_ATTACHMENT_UNUSED? Otherwise, the app would have to pass data to the shader determining whether is can read or not. That's kind of a pain.
Something builtin like:
layout(input_attachment_index=0, binding=42) uniform subpassInput inputData;
vec4 color = vec4(0);
if (gl_isInputAttachmentValid(0)) {
color = subpassLoad(inputData).rgba
}
Vulkan doesn't generally have convenience features. If the user is perfectly capable of doing a thing, then if the user wants that thing done, Vulkan won't do it for them. If you can provide a value that specifies whether a resource the shader wants to use is available, Vulkan is not going to provide a query for you.
So there is no such query in Vulkan. You can build one yourself quite easily, however.
In Vulkan, pipelines are compiled against a specific subpass of a specific renderpass. And whether a subpass of a renderpass uses an input attachment or not is something that is fixed to the renderpass. As such, at the moment your C++ code compiles the shader module(s) into a pipeline, it knows if the subpass uses an input attachment or not. There's no way it doesn't know.
Therefore, there is no reason your pipeline compilation code cannot provide a specialization constant for your shader to test to see if it should use the input attachment or not. Simply declare a particular specialization constant, check it in the shader, and provide the specialization to the pipeline creation step via VkPipelineShaderStageCreateInfo::pSpecializationInfo.
//In shader
layout(constant_id = 0) const bool use_input_attachment;
...
if (use_input_attachment) {
color = subpassLoad(inputData).rgba
}
//In C++
const VkSpecializationMapEntry entries[] =
{
{
0, // constantID
0, // offset
sizeof(VkBool) // size
}
};
const VkBool data[] = { /*VK_TRUE or VK_FALSE, as needed*/ };
const VkSpecializationInfo info =
{
1, // mapEntryCount
entries, // pMapEntries
sizeof(VkBool), // dataSize
data, // pData
};

buffers in CCL code samples along with the oneapi toolkit

I Was going through the CCL code samples along with the oneapi toolkit.
In the below DPC++(SYCL) code initially sendbuf a buffer is created in the cpu side and is not initialised and in the part where offloading to target device takes place the dev_acc_sbuf[id] variable, which is a variable in the kernel scope is modified. This variable(dev_acc_sbuf) is not hence used in the program neither is its value copied back to sendbuf.Then in the next line the sendbuf variable is used for allreduce. I am not able to understand how changing the dev_acc_sbuf makes change in the sendbuf.
cl::sycl::queue q;
cl::sycl::buffer<int, 1> sendbuf(COUNT);
/* open sendbuf and modify it on the target device side */
q.submit([&](cl::sycl::handler& cgh) {
auto dev_acc_sbuf = sendbuf.get_access<mode::write>(cgh);
cgh.parallel_for<class allreduce_test_sbuf_modify>(range<1>{COUNT}, [=](item<1> id) {
dev_acc_sbuf[id] += 1;
});
});
/* invoke ccl_allreduce on the CPU side */
ccl_allreduce(&sendbuf,
&recvbuf,
COUNT,
ccl_dtype_int,
ccl_reduction_sum,
NULL,
NULL,
stream,
&request);
In the line "auto dev_acc_sbuf = sendbuf.get_access<mode::write>(cgh);" the dev_acc_sbuf is a handle that accesses sendbuf and not a seperate buffer. The changes made in the dev_acc_sbuf handle gets reflected to the original buffer ie the sendbuffer . This is an advantage in SYCL as the changes made in the kernel scope is automatically copied back to the original variable
On most systems, the host and the device do not share physical memory, the CPU might use RAM and the GPU might use its own global memory. SYCL needs to know which data it will be sharing between the host and the devices.
For this purpose, SYCL uses its buffers, the buffer class is generic over the element type and the number of dimensions. When passed a raw pointer, the buffer(T* ptr, range size) constructor takes ownership of the memory it has been passed. This means that we absolutely cannot use that memory ourselves while the buffer exists, which is why we begin a C++ scope. At the end of their scope, the buffers will be destroyed and the memory returned to the user. A size argument is a range object, which has to have the same number of dimensions as the buffer and is initialized with the number of elements in each dimension. Here, we have one dimension with one element.
Buffers are not associated with a particular queue or context, so they are capable of handling data transparently between multiple devices.
Accessors are used to access request control over the device memory from the buffer objects. Their modes will take care of data movement between host and device. So we need not have to explicitly copy back the result from device to host.
Below is the example for more clarification:
#include <bits/stdc++.h>
#include <CL/sycl.hpp>
using namespace std;
class vector_addition;
int main(int, char**) {
//creating host memory
int *a=(int *)malloc(10*sizeof(int));
int *b=(int *)malloc(10*sizeof(int));
int *c=(int *)malloc(10*sizeof(int));
for(int i=0;i<10;i++){
a[i]=i;
b[i]=10-i;
}
cl::sycl::default_selector device_selector;
cl::sycl::queue queue(device_selector);
std::cout << "Running on "<< queue.get_device().get_info<cl::sycl::info::device::name>()<< "\n";
{
//creating buffer from pointer of host memory
cl::sycl::buffer<int, 1> a_sycl{a, cl::sycl::range<1>{10} };
cl::sycl::buffer<int, 1> b_sycl{b, cl::sycl::range<1>{10} };
cl::sycl::buffer<int, 1> c_sycl{c, cl::sycl::range<1>{10} };
queue.submit([&] (cl::sycl::handler& cgh) {
//creating accessor of buffer with proper mode
auto a_acc = a_sycl.get_access<cl::sycl::access::mode::read>(cgh);
auto b_acc = b_sycl.get_access<cl::sycl::access::mode::read>(cgh);
auto c_acc = c_sycl.get_access<cl::sycl::access::mode::write>(cgh);//responsible for copying back to host memory
//kernel for execution
cgh.parallel_for<class vector_addition>(cl::sycl::range<1>{ 10 }, [=](cl::sycl::id<1> idx) {
c_acc[idx] = a_acc[idx] + b_acc[idx];
});
});
}
for(int i=0;i<10;i++){
cout<<c[i]<<" ";
}
cout<<"\n";
return 0;
}

Get generic type using Mono embedded

How to I create a generic List<String> object using mono embedded calls? I can get List's MonoClass:
MonoClass* list = mono_class_from_name(mscorlibimage,
"System.Collections.Generic", "List`1");
and I see in docs that there's
mono_class_from_generic_parameter(MonoGenericParam*...)
but I have no idea where and how to get the MonoGenericParam. Or perhaps I need to construct a valid name for mono_class_from_name? I think this can be a bit slower but I'd accept that for now. I tried
MonoClass* list = mono_class_from_name(mscorlib::get().image, "System.Collections.Generic", "List`1[System.String]");
but no luck.
UPDATE:
OK I found a way. Still I'd like to see if there's an official way of doing thing, as this hack looks too dirty to me.
Basically I searched mono sources for generic methods and found mono_class_bind_generic_parameters (see https://raw.github.com/mono/mono/master/mono/metadata/reflection.c). I had to link to libmono-2.0.a in addition to .so to use it. But it worked:
extern "C" MonoClass*
mono_class_bind_generic_parameters(MonoClass *klass,
int type_argc, MonoType **types, bool is_dynamic);
MonoClass* list = mono_class_from_name(mscorlib::get().image,
"System.Collections.Generic", "List`1");
MonoClass* strcls = mono_class_from_name(mscorlib::get().image, "System", "String");
printf("str class: %p\n", strcls);
MonoType* strtype = mono_class_get_type(strcls);
printf("str type: %p\n", strtype);
MonoType* types[1];
types[0] = strtype;
list = mono_class_bind_generic_parameters(list, 1, types, false);
printf("list[string] class: %p\n", list);
MonoObject* obj = mono_object_new(domain, list);
printf("list[string] created: %p\n", obj);
I suppose I can take sources (UPDATE: hardly so) of these methods and reimplement them (they parse metadata, etc) - if I don't want to link to .a - but I wonder if there's a simpler way. Mono docs just don't answer anything, as they use to.
UPDATE: found this thread: http://mono.1490590.n4.nabble.com/Embedded-API-Method-signature-not-found-with-generic-parameter-td4660157.html which seems to say that no embedded API exists for what I want (i.e. they do not bother to expose mono_class_bind_generic_parameters). Can someone prove that it's correct? With that method, by the way, I get MonoReflectionType* and no way to get back MonoType* from it - while it is as easy to as getting ->type from the structure - which is internal and access via functions to it is internal. Mono Embedded should be called "Mono Internal" instead.
UPDATE: another method is to hack mono_class_inflate_generic_type using copy of internal structures:
struct _MonoGenericInst {
uint32_t id; /* unique ID for debugging */
uint32_t type_argc : 22; /* number of type arguments */
uint32_t is_open : 1; /* if this is an open type */
MonoType *type_argv [1];
};
struct _MonoGenericContext {
/* The instantiation corresponding to the class generic parameters */
MonoGenericInst *class_inst;
/* The instantiation corresponding to the method generic parameters */
void *method_inst;
};
_MonoGenericInst clsctx;
clsctx.type_argc = 1;
clsctx.is_open = 0;
clsctx.type_argv[0] = mono_class_get_type(System::String::_SClass());
MonoGenericContext ctx;
ctx.method_inst = 0;
ctx.class_inst = &clsctx;
MonoType* lt = mono_class_inflate_generic_type(
mono_class_get_type(System::Collections::Generic::List<System::String>::_SClass()),
&ctx);
This doesn't require static link to .a but is even a worse hack. And mono_class_inflate_generic_type is marked as DEPRECATED - so, if this is deprecated, then which is the modern one?
In many cases a mono embedding conundrum can be resolved by using a managed helper method. This is the approach used here.
So we have:
A managed helper method that accepts a generic type definition and an array of generic parameter types.
A client method that accepts a generic type definition name (e.g.: System.Collections.Generic.List`1), an assembly image that contains the type (or use an Assembly Qualified name) and an object of the required generic parameter type. We retrieve the underlying monoType for the object.
Note that when passing type info into the managed layer it has to be an instance of MonoReflectionType as obtained from mono_type_get_object().
The managed helper method is trivial and does the actual instantiation:
public static object CreateInstanceOfGenericType(Type genericTypeDefinition, Type[] parms)
{
// construct type from definition
Type constructedType = genericTypeDefinition.MakeGenericType(parms);
// create instance of constructed type
object obj = Activator.CreateInstance(constructedType);
return obj;
}
The helper code is called, in this case, from Objective-C:
+ (id)createInstanceOfGenericTypeDefinition:(char *)genericTypeDefinitionName monoImage:(MonoImage *)monoImage itemObject:(id)itemObject
{
// get the contained item monoType
MonoType *monoType = [DBType monoTypeForMonoObject:[itemObject monoObject]];
MonoReflectionType *monoReflectionType = mono_type_get_object([DBManagedEnvironment currentDomain], monoType);
// build a System.Array of item types
DBManagedObject *argType = [[DBManagedObject alloc] initWithMonoObject:(MonoObject *)monoReflectionType];
NSArray *argTypes = #[argType];
DBSystem_Array *dbsAargTypes = [argTypes dbsArrayWithTypeName:#"System.Type"];
// get the generic type definition
//
// Retrieves a MonoType from given name. If the name is not fully qualified,
// it defaults to get the type from the image or, if image is NULL or loading
// from it fails, uses corlib.
// This is the embedded equivalent of System.Type.GetType();
MonoType *monoGenericTypeDefinition = mono_reflection_type_from_name(genericTypeDefinitionName, monoImage);
// create instance using helper method
MonoMethod *helperMethod = [DBManagedEnvironment dubrovnikMonoMethodWithName:"CreateInstanceOfGenericType" className:"Dubrovnik.FrameworkHelper.GenericHelper" argCount:2];
void *hargs [2];
hargs[0] = mono_type_get_object([DBManagedEnvironment currentDomain], monoGenericTypeDefinition);
hargs[1] = [dbsAargTypes monoArray]; // a monoArray *
MonoObject *monoException = NULL;
MonoObject *monoObject = mono_runtime_invoke(helperMethod, NULL, hargs, &monoException);
if (monoException) NSRaiseExceptionFromMonoException(monoException);
id object = [System_Object subclassObjectWithMonoObject:monoObject];
return object;
}
For the complete code see Dubrovnik on Github

How to get access to WriteableBitmap.PixelBuffer pixels with C++?

There are a lot of samples for C#, but only some code snippets for C++ on MSDN. I have put it together and I think it will work, but I am not sure if I am releasing all the COM references I have to.
Your code is correct--the reference count on the IBufferByteAccess interface of *buffer is incremented by the call to QueryInterface, and you must call Release once to release that reference.
However, if you use ComPtr<T>, this becomes much simpler--with ComPtr<T>, you cannot call any of the three members of IUnknown (AddRef, Release, and QueryInterface); it prevents you from calling them. Instead, it encapsulates calls to these member functions in a way that makes it difficult to screw things up. Here's an example of how this would look:
// Get the buffer from the WriteableBitmap:
IBuffer^ buffer = bitmap->PixelBuffer;
// Convert from C++/CX to the ABI IInspectable*:
ComPtr<IInspectable> bufferInspectable(AsInspectable(buffer));
// Get the IBufferByteAccess interface:
ComPtr<IBufferByteAccess> bufferBytes;
ThrowIfFailed(bufferInspectable.As(&bufferBytes));
// Use it:
byte* pixels(nullptr);
ThrowIfFailed(bufferBytes->Buffer(&pixels));
The call to bufferInspectable.As(&bufferBytes) performs a safe QueryInterface: it computes the IID from the type of bufferBytes, performs the QueryInterface, and attaches the resulting pointer to bufferBytes. When bufferBytes goes out of scope, it will automatically call Release. The code has the same effect as yours, but without the error-prone explicit resource management.
The example uses the following two utilities, which help to keep the code clean:
auto AsInspectable(Object^ const object) -> Microsoft::WRL::ComPtr<IInspectable>
{
return reinterpret_cast<IInspectable*>(object);
}
auto ThrowIfFailed(HRESULT const hr) -> void
{
if (FAILED(hr))
throw Platform::Exception::CreateException(hr);
}
Observant readers will notice that because this code uses a ComPtr for the IInspectable* we get from buffer, this code actually performs an additional AddRef/Release compared to the original code. I would argue that the chance of this impacting performance is minimal, and it's best to start from code that is easy to verify as correct, then optimize for performance once the hot spots are understood.
This is what I tried so far:
// Get the buffer from the WriteableBitmap
IBuffer^ buffer = bitmap->PixelBuffer;
// Get access to the base COM interface of the buffer (IUnknown)
IUnknown* pUnk = reinterpret_cast<IUnknown*>(buffer);
// Use IUnknown to get the IBufferByteAccess interface of the buffer to get access to the bytes
// This requires #include <Robuffer.h>
IBufferByteAccess* pBufferByteAccess = nullptr;
HRESULT hr = pUnk->QueryInterface(IID_PPV_ARGS(&pBufferByteAccess));
if (FAILED(hr))
{
throw Platform::Exception::CreateException(hr);
}
// Get the pointer to the bytes of the buffer
byte *pixels = nullptr;
pBufferByteAccess->Buffer(&pixels);
// *** Do the work on the bytes here ***
// Release reference to IBufferByteAccess created by QueryInterface.
// Perhaps this might be done before doing more work with the pixels buffer,
// but it's possible that without it - the buffer might get released or moved
// by the time you are done using it.
pBufferByteAccess->Release();
When using C++/WinRT (instead of C++/CX) there's a more convenient (and more dangerous) alternative. The language projection generates a data() helper function on the IBuffer interface that returns a uint8_t* into the memory buffer.
Assuming that bitmap is of type WriteableBitmap the code can be trimmed down to this:
uint8_t* pixels{ bitmap.PixelBuffer().data() };
// *** Do the work on the bytes here ***
// No cleanup required; it has already been dealt with inside data()'s implementation
In the code pixels is a raw pointer into data controlled by the bitmap instance. As such it is only valid as long as bitmap is alive, but there is nothing in the code that helps the compiler (or a reader) track that dependency.
For reference, there's an example in the WriteableBitmap::PixelBuffer documentation illustrating the use of the (otherwise undocumented) helper function data().