When we print error messages, we want to include as much useful information as possible, while also not giving anything that would just be noise. This requires at a LogError
call to have this information available.
In the past, there was a lot of parameters passed around functions only for the sake of being able to print it out in the LogError
message. The ErrorObject
was created as a way to improve this. With a easier way to pass information, it is less error prone to both forgot the information and/or provide bad information
The chassis.cpp
holds the single ErrorObject
reference which is passed to all PreCallValidate
calls.
It is very important to know "where" in a function call the error occured, this is where the Location
object comes in.
inside chassis.cpp
we generate the starting Location
for ErrorObject
. From here inside each function, we can append what we need
// example
bool PreCallValidateQueueBindSparse(/*..*/ ErrorObject &error_obj) const {
// automatically has Func::vkQueueBindSparse
// note: error_obj.location is used if just want to print the function
LogError("VUID-A", error_obj.location);
for (uint32_t i = 0; i < bindInfoCount; ++i) {
const Location loc = error_obj.location.dot(Field::pBindInfo, i);
LogError("VUID-B", loc); // i == 3
for (uint32_t j = 0; j < bufferBindCount; ++j) {
const Location buffer_loc = loc.dot(Field::pBufferBinds, j);
LogError("VUID-C", buffer_loc); // j == 2
}
if (pNext == VkTimelineSemaphoreSubmitInfo) {
// use pNext() instead of dot() to print out it was part of a pNext chain
const Location pnext_loc = loc.pNext(Struct::VkTimelineSemaphoreSubmitInfo, Field::waitSemaphoreValueCount);
LogError("VUID-D", pnext_loc);
}
}
}
will produce the following location in the error message
[VUID-A] vkQueueBindSparse():
[VUID-B] vkQueueBindSparse(): pBindInfo[3]
[VUID-C] vkQueueBindSparse(): pBindInfo[3].pBufferBinds[2]
[VUID-D] vkQueueBindSparse(): pBindInfo[3].pNext<VkTimelineSemaphoreSubmitInfo>.waitSemaphoreValueCount
We generate the
Func
/Struct
/Field
for all possible items from the XML
using the Location::Fields()
you can print the location, minus the function, as a string
const Location loc = error_obj.location.dot(Field::pBindInfo, i); // vkQueueBindSparse(): pBindInfo[3]
// prints "pBindInfo[3]"
LogError(/*..*/, "%s". loc.Fields().c_str());
When using the .dot()
operation, it returns a new copy of Location
. If you chain two .dot().dot()
you need to be mindful.
The following will produce a stack-use-after-scope
runtime error
const Location layout_loc = loc.dot(Field::attachment).dot(Field::layout);
LogError(/*..*/, layout_loc, "error");
The 2 ways around the are:
// Create 2nd variable
const Location attachment_loc = loc.dot(Field::attachment)
const Location layout_loc = attachment_loc.dot(Field::layout);
LogError(/*..*/, layout_loc, "good");
or has been found to be more common
// Pass an argument
LogError(/*..*/, loc.dot(Field::attachment).dot(Field::layout), "good");
The original design was to not make copies of Location
and just have it modify the ErrorObject::Location
but this would require you to remove items after a function call
// example
error_obj.location.add(Struct::VkSomething);
ValidateItem(error_obj);
error_obj.location.remove(Struct::VkSomething);
which slowly leads to a LOT more code and becomes very error prone to forget to remove Location
values. Instead the dot
operator return a new copy of Location
.
// TODO