Error Object

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

Single ErrorObject

The chassis.cpp holds the single ErrorObject reference which is passed to all PreCallValidate calls.

Location

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 fields in the error messages

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());

Limitations

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");

Using stack and making copies

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.

LogObjectList

// TODO