What are the use cases of sparsed VkDescriptorSetLayoutBinding? - vulkan

I have troubles figuring out any use case for VkDescriptorSetLayoutBinding::binding, here is the struct :
struct VkDescriptorSetLayoutBinding
{
uint32_t binding;
VkDescriptorType descriptorType;
uint32_t descriptorCount;
VkShaderStageFlags stageFlags;
const VkSampler* pImmutableSamplers;
};
used here to create a DescriptorSetLayout :
struct VkDescriptorSetLayoutCreateInfo
{
VkStructureType sType;
const void* pNext;
VkDescriptorSetLayoutCreateFlags flags;
uint32_t bindingCount;
const VkDescriptorSetLayoutBinding* pBindings;
};
I was wondering why the "binding" variable is not deduced from the index in the pBindings array.
After some research I found that the vulkan specs says :
The above layout definition allows the descriptor bindings to be specified sparsely such that not all binding numbers between 0 and the maximum binding number need to be specified in the pBindings array. Bindings that are not specified have a descriptorCount and stageFlags of zero, and the value of descriptorType is undefined. However, all binding numbers between 0 and the maximum binding number in the VkDescriptorSetLayoutCreateInfo::pBindings array may consume memory in the descriptor set layout even if not all descriptor bindings are used, though it should not consume additional memory from the descriptor pool.
I can't find in which case you can use those sparsed bindings, why would you leave an empty unused space ?

Binding indices are hard-coded into shaders (you can define binding indices via specialization constants, but otherwise, they're part of the shader code). So let's imagine that you have the code for a shaders stage. And you want to use it in two different pipelines (A and B). And let's say that the descriptor set layouts for these pipelines are not meant to be compatible; we just want to reuse the shader.
Well, the binding indices in your shader didn't change; they can't change. So if this shader has a UBO in binding 3 of set 0, then any descriptor set layout it gets used with must have a UBO in binding 3 of set 0.
Maybe in pipeline A, some shader other than the one we reuse might use bindings 0, 1, and 2 from set 0. But what if none of the other shaders for pipeline B need binding index 2? Maybe the fragment shader in pipeline A used 3 descriptor resources, but the one in pipeline B only needs 2.
Having sparse descriptor bindings allow you to reuse compiled shader modules without having to reassign the binding indices within a shader. Oh yes, you have to make sure that all such shaders are compatible with each other (that they don't use the same set+binding index in different ways), but other than that, you can mix-and-match freely.
And it should be noted that contiguous bindings has almost never been a requirement of any API. In OpenGL, your shader pipeline could use texture units 2, 40, and 32, and that's 100% fine.
Why should it be different for Vulkan, just because its resource binding model is more abstract?

Related

How does the pipeline know which descriptor layout to use when binding descriptors?

When calling vkCmdBindDescriptorSets, I have to pass the number of the first set and an array of descriptor sets that I would like bound. I then get to use whichever set I like in my shader using layout(set = X, binding = 0).
My question is the following. The descriptor set layout for the set was only specified at descriptor set creation. Yet when I bind, I can bind any descriptor set to any set number using the above function. Is it up to my to keep my shader layout and binding consistent with the layout specified amongst pipeline creation? Otherwise, how does the pipeline/shader "know" which layout my specific set is using?
In Vulkan, unless otherwise noted, it's always "up to you". This is no exception.
If you attempt to render/dispatch with a pipeline and bound descriptor sets that do not have matching layouts, undefined behavior results.
The pipeline "knows" which layout you're using by fiat. The whole point of a layout is that it "lays out" the arrangement of the internal data representing how those descriptors are organized. So where "binding 2" is within whatever internal data structure the implementation uses for defining that is determined solely by the layout.
A layout is therefore kind of like a struct in C or C++. You can't pass a pointer to a struct of type B to a function that expects a pointer to a struct of type A. Well, you can if you do a bunch of casts, but when the function accesses that pointer, undefined behavior results.
The same goes for pipelines and bound descriptor sets: they must use compatible layouts, or undefined behavior results.

How do I use Smalltalk's built in Parser or Compiler to parse my own messages with optional parameters?

I'm trying to create a live-coding environment in Smalltalk. The objects I'm using have long complex methods with many parameters for creating new instances. I want to be able to write a very short "command" that just specifies the parameters I'm interested in and provides default values for all the other parameters.
For instance instead of writing something like:
Polyhedron shape: #cube size: 1 rotationSpeed: 5 rotationDirection: 0.707 color: red texture: false
In my live coding environment I want to write:
poly shape: #cube
Some of the arguments could be complex and contain their own messages like:
poly shape: #cube rotationSpeed: ((pot1 max: 0) min: speedLimit)
So rather than just trying to parse the live coding string myself I thought it would be easier if I could use Smalltalk's built in parser or compiler to decode the string and call Polyhedron with the full creation message including default values.
How would I do that?
I've got as far as getting an array of tokens out of the parser but that doesn't identify message parameters and arguments. Before I tried to implement argument parsing myself I figured there's likely to be something in Smalltalk that could give me a list of message parameters and arguments?
It doesn't look like you have to tweak the compiler. To provide default values simply have your objects be born with them. How? In the initialize method:
Polyhedron >> initialize
super initialize.
shape := #triangle.
size := 1.
rotationSpeed := 5.
rotationDirection := 0.707.
color: Color red texture: false
The #initialize message is sent when you send Polyhedron new. Thus, if you want to change the shape, you only need to send
Polyhedron new shape: #cube
Note by the way that it is usually a good idea to send super initialize so to give superclasses a chance to perform their initialization routines.
Note also that Polyhedra is the plural of Polyhedron, and class names are usually in singular.
Addendum
In case you cannot edit the class Polyhedron, you can still reference it and therefore send messages to it. In particular, you can create new instance creation methods, shorter than the ones you already have. Here is how to do this:
Polyedron compile: 'shape: aSymbol
^self
shape: aSymbol
size: 1
rotationSpeed: 5
rotationDirection: 0.707
color: Color rd texture: false'
Note that the argument of #compile: is the source code of the method you want to add. By using this new method you will be able to just say
Polyedron shape: #cube
to get what you wanted.

How does gcc push local variables on to the stack?

void
f
()
{
int a[1];
int b;
int c;
int d[1];
}
I have found that these local variables, for this example, are not pushed on to the stack in order. b and c are pushed in the order of their declaration, but, a and d are grouped together. So the compiler is allocating arrays differently from any other built in type or object.
Is this a C/C++ requirement or gcc implementation detail?
The C standard says nothing about the order in which local variables are allocated. It doesn't even use the word "stack". It only requires that local variables have a lifetime that begins on entry to the nearest enclosing block (basically when execution reaches the {) and ends on exit from that block (reaching the }), and that each object has a unique address. It does acknowledge that two unrelated variables might happen to be adjacent in memory (for obscure technical reasons involving pointer arithmetic), but doesn't say when this might happen.
The order in which variables are allocated is entirely up to the whim of the compiler, and you should not write code that depends on any particular ordering. A compiler might lay out local variables in the order in which they're declared, or alphabetically by name, or it might group some variables together if that happens to result in faster code.
If you need to variables to be allocated in a particular order, you can wrap them in an array or a structure.
(If you were to look at the generated machine code, you'd most likely find that the variables are not "pushed onto the stack" one by one. Instead, the compiler will probably generate a single instruction to adjust the stack pointer by a certain number of bytes, effectively allocating a single chunk of memory to hold all the local variables for the function or block. Code that accesses a given variable will then use its offset within the stack frame.)
And since your function doesn't do anything with its local variables, the compiler might just not bother allocating space for them at all, particularly if you request optimization with -O3 or something similar.
The compiler can order the local variables however it wants. It may even choose to either not allocate them at all (for example, if they're not used, or are optimized away through propagation/ciscizing/keeping in register/etc) or allocate the same stack location for multiple locals that have disjoint live ranges.
There is no common implementation detail to outline how a particular compiler does it, as it may change at any time.
Typically, compilers will try to group similar sized variables (and/or alignments) together to minimize wasted space through "gaps", but there are so many other factors involved.
structs and arrays have slightly different requirements, but that's beyond the scope of this question I believe.

Thrift go support of map with struct value as key

I experimented golang generation with Thrift 0.9.1, for example,
thrift definition,
struct AppIdLeveledHashKeyTimeKeyHour {
1: required i32 appId
2: required LeveledHashKey leveledHashKey
3: required TimeKeyHour timeKeyHour
}
typedef map<AppIdLeveledHashKeyTimeKeyHour, ...sth else...> EventSliceShardIdValue
in the generated code, EventSliceShardIdValue would be,
type EventSliceShardIdValue map[*AppIdLeveledHashKeyTimeKeyHour]EventSliceAppIdLeveledHashKeyTimeKeyHourValue
you can find the key part is a pointer which represents memory address. In golang a pointer as map key (instead of a value, or hash of the obj) is useless in most cases. To use a combination of some fields as map key, the definition should use a value type like
map[AppIdLeveledHashKeyTimeKeyHour]EventSliceAppIdLeveledHashKeyTimeKeyHourValue
Is it a problem of Thrift's go support (or I misused sth)? Any workaround to solve this problem in thrift?
Structs (without pointers) can only be used as map keys under certain limited circumstances (they must be comparable per http://golang.org/ref/spec#Comparison_operators); it's possible that AppIdLeveledHashKeyTimeKeyHour doesn't fit this definition, so it's not actually possible to build a map without using a pointer for the key.

IAR initializer function placement

Does anybody know how to deal with the following problem:
I have an IAR Embedded workbench. The project is using the SDRAM for running it's code and Flash ROM too. The code for SDRAM is loaded from SD Card. However, in SDRAM there are also some data stored, like global or static variables. Some of them have to be initialized. The initialization step, the iar_data_init3 function call, goes after the low_level_init function. So the problem is that for initialization of some of the variables in SDRAM, the initializer function is called from iar_data_init3, the code of which is inside of the SDRAM itself. Which is wrong because the loading of SDRAM code from SD Card is not yet done.
I have tried manual initialization as described in the C/C++ development guide, but this didn't help.
The function which is called is __sti__routine, which provides initialization of variables. All of these functions are generated by IAR. Is there any way to tell the linker to put the initializer functions to Flash ROM?
EDIT 1:
Here is information from IAR manual for C/C++.
It is an example of how to use manual initialization.
In the linker config file:
initialize manually { section MYSECTION };
Then IAR documentation says:
you can use this source code example to initialize the section:
#pragma section = "MYSECTION"
#pragma section = "MYSECTION_init"
void DoInit()
{
char * from = __section_begin("MYSECTION_init");
char * to = __section_begin("MYSECTION");
memcpy(to, from, __section_size("MYSECTION"));
}
I can't understand however, first of all,
what is the difference between
MYSECTION_init and MYSECTION.
Aslo, if I have a global variable:
SomeClass myclass;
And it should be placed in SDRAM,
then how does the initialization is done for it? I want to manually initialize the variable,
and place that initializing functions to flash ROM. (the problem is that by placing variable to SDRAM it's initializing function also is placed to SDRAM).
You can specify the location of variables and functions through the use of pragma preprocessor directives. You will need to use either one of the predefined sections or define your own.
You don't mention the specific flavor of IAR you're using. The following is from the Renesas IAR Compiler Reference Guide but you should check the proper reference guide to make sure that the syntax is exactly the same and to learn what the predefined sections are.
Use the # operator or the #pragma location directive to place
groups of functions or global and static variables in named segments,
without having explicit control of each object. The variables must be
declared either __no_init or const. The segments can, for
example, be placed in specific areas of memory, or initialized or
copied in controlled ways using the segment begin and end operators.
This is also useful if you want an interface between separately
linked units, for example an application project and a boot loader
project. Use named segments when absolute control over the placement
of individual variables is not needed, or not useful.
Examples of placing functions in named segments
void f(void) # "FUNCTIONS";
void g(void) # "FUNCTIONS"
{
}
#pragma location="FUNCTIONS"
void h(void);
To override the default segment allocation, you can explicitly specify
a memory attribute other than the default:
__code32 void f(void) # "FUNCTIONS";
Edit
Based on your comments you should have a linker file named generic_cortex.icf that defines your memory regions. In it should be instructions somewhat similar to the following:
/* Define the addressable memory */
define memory Mem with size = 4G;
/* Define a region named SDCARD with start address 0xA0000000 and to be 256 Mbytes large */
define region SDCARD = Mem:[from 0xA0000000 size 0xFFFFFFF ];
/* Define a region named SDRAM with start address 0xB0000000 and to be 256 Mbytes large */
define region SDRAM = Mem:[from 0xB0000000 size 0xFFFFFFF ];
/* Place sections named MyCardStuff in the SDCARD region */
place in SDCARD {section MyCardStuff };
/* Place sections named MyRAMStuff in the SDRAM region */
place in SDRAM {section MyRAMStuff };
/* Override default copy initialization for named section */
initialize manually { section MyRAMStuff };
The actual names, addresses and sizes will be different but should look similar. I'm just using the full size of the first two dynamic memory areas from the datasheet. What's happening here is you are assigning names to address space for the different types of memory (i.e. your SD Card and SDRAM) so that sections named during the compile will be placed in the correct locations by the linker.
So first you must define the address space with define memory:
The maximum size of possible addressable memories
The define memory directive defines a memory space with a given size,
which is the maximum possible amount of addressable memory, not
necessarily physically available.
Then tell it which chips go where with define region:
Available physical memory
The define region directive defines a region in the available memories
in which specific sections of application code and sections of
application data can be placed.
Next the linker needs to know in what region to place the named section with place in:
Placing sections in regions
The place at and place into directives place sets of sections with
similar attributes into previously defined regions.
And tell the linker you want to override part of it's initialization with initialize manually:
Initializing the application
The directives initialize and do not initialize control how the
application should be started. With these directives, the application
can initialize global symbols at startup, and copy pieces of code.
Finally, in your C file, tell the compiler what goes into what sections and how to initialize sections declared manually.
SomeClass myClass # "MyCardStuff";
#pragma section = "MyCardStuff"
#pragma section = "MySDRAMStuff"
void DoInit()
{
/* Copy your code and variables from your SD Card into SDRAM */
char * from = __section_begin("MyCardStuff");
char * to = __section_begin("MySDRAMStuff");
memcpy(to, from, __section_size("MySDRAMStuff"));
/* Initialize your variables */
myClass.init();
}
In order to customize startup initialization among multiple different memory devices, you will need to study the IAR Development Guide for ARM very carefully. Also try turning on the --log initialization option and studying the logs and the map files to make sure you are getting what you want.