VPP  0.7
A high-level modern C++ API for Vulkan
Debugging VPP programs

Debugging VPP programs

VPP offers some features helpful for debugging. You can find a short overview of them in this section.

Debug probes

It is still harder to debug shader code than regular C++ code, as we have no source level debugger available. One of particular simple methods to use in such cases, known even to beginner programmers, is to temporarily add logging to the program in the form of printf statements. For graphics applications however this is impractical, as we are working on millions of pixels in parallel. Better way would be to have a "printf" which generates images. That's exactly what debug probes in VPP do.

Images generated by debug probes are not displayed anywhere, but there is one way that they are intended to be viewed: use rendering debugger like RenderDoc. These debug probe images will appear as additional images in the pipeline state and you can conveniently examine them.

Debug probes are extremely useful when you do not get result from your shader that you expected and suspect a computation error somewhere. They can help e.g. to visualize intermediate results.

More information on debug probes can be found on the documentation page for vpp::Shader and vpp::FragmentShader classes, which implement probe functionality as methods.

Vulkan validation support

VPP provides support for Vulkan validation layer. In order to turn on the validation, supply Instance::VALIDATION flag to the constructor of vpp::Instance object.

This however does not suffice, as we did not specify where the validation warnings should go. vpp::StreamDebugReporter class allows simple redirection of these messages into std::ostream. To gather the messages, create a std::ostream (with lifetime as long as your vpp::Instance object) and vpp::StreamDebugReporter object. Supply references to the stream and instance objects in its constructor. This is sufficient to have validation messages routed to specified stream.

An example:

class MyRenderingEngine
{
private:
#ifdef _DEBUG
static const unsigned int INSTANCE_FLAGS = vpp::Instance::VALIDATION;
#else
static const unsigned int INSTANCE_FLAGS = 0u;
#endif
std::ostringstream m_validationLog;
vpp::Instance m_instance;
vpp::StreamDebugReporter m_debugReporter;
// ...
public:
MyRenderingEngine :: MyRenderingEngine() :
m_instance ( INSTANCE_FLAGS ),
m_debugReporter ( m_validationLog, m_instance ),
// ...
{
// ...
}
// ...
};

It can be also a file stream or any other kind of C++ output stream.

In case if more advanced error processing is needed, you ca create your own subclass of vpp::DebugReporter class and use it instead of vpp::StreamDebugReporter.

class MyDebugReporter : public vpp::DebugReporter
{
public:
virtual VkBool32 debugReport (
VkDebugReportFlagsEXT flags,
VkDebugReportObjectTypeEXT objectType,
uint64_t object,
size_t location,
int32_t messageCode,
const char* pLayerPrefix,
const char* pMessage );
virtual void shaderCompilationLog (
const std::string& shaderCode,
const char* pShaderType );
};

You can override two methods here (both are optional): debugReport and shaderCompilationLog. Mosty likely you will be interested in the first one. The second is explained below.

When overriding debugReport, return VK_FALSE value, as Vulkan specification recommends. Returning VK_TRUE will abort your program.

Shader compilation log

Although VPP tranparently handles SPIR-V generation, sometimes it is useful to look at the disassembled version of generated SPIR-V code. Use this feature e.g. if you suspect a bug in VPP. In order to obtain the code dump, you need to do two things:

void MyPipeline :: fVertexShader ( vpp::VertexShader* pShader )
{
using namespace vpp;
// This enables the code dump. Put anywhere in the shader code.
pShader->DebugCodeDump();
const Mat4 m2w = inFramePar [ & GFramePar::m_model2world ];
const Mat4 w2v = inFramePar [ & GFramePar::m_world2view ];
const Mat4 v2p = inFramePar [ & GFramePar::m_view2projected ];
const Vec4 inPos = m_vertices [ & GVertexAttr::m_position ];
const Vec4 inColor = m_vertices [ & GVertexAttr::m_color ];
const Vec4 result = v2p * w2v * m2w * inPos;
pShader->outVertex.position = result;
Output< decltype ( m_ioColor ) > outColor ( m_ioColor );
outColor = inColor;
}

The vertex shader shown above will emit SPIR-V code like this:

-----------------------------------------------------
Shader compilation (vertex)
-------------------- Code start ---------------------
// Module Version 10000
// Generated by (magic number): 1
// Id's are bound by 39
Capability Shader
1: ExtInstImport "GLSL.std.450"
MemoryModel Logical GLSL450
EntryPoint Vertex 4 "main"
Source GLSL 450
Name 4 "main"
Name 9 "struct vppui::TFramePar<2>"
Name 31 "gl_PerVertex"
MemberName 31(gl_PerVertex) 0 "gl_Position"
MemberName 31(gl_PerVertex) 1 "gl_PointSize"
MemberDecorate 9(struct vppui::TFramePar<2>) 0 Offset 0
MemberDecorate 9(struct vppui::TFramePar<2>) 0 ColMajor
MemberDecorate 9(struct vppui::TFramePar<2>) 0 MatrixStride 16
MemberDecorate 9(struct vppui::TFramePar<2>) 1 Offset 64
MemberDecorate 9(struct vppui::TFramePar<2>) 1 ColMajor
MemberDecorate 9(struct vppui::TFramePar<2>) 1 MatrixStride 16
MemberDecorate 9(struct vppui::TFramePar<2>) 2 Offset 128
MemberDecorate 9(struct vppui::TFramePar<2>) 2 ColMajor
MemberDecorate 9(struct vppui::TFramePar<2>) 2 MatrixStride 16
Decorate 9(struct vppui::TFramePar<2>) Block
Decorate 11 DescriptorSet 0
Decorate 11 Binding 0
Decorate 24 Binding 0
Decorate 24 Location 0
Decorate 26 Binding 0
Decorate 26 Location 1
Decorate 31(gl_PerVertex) Block
MemberDecorate 31(gl_PerVertex) 0 BuiltIn Position
MemberDecorate 31(gl_PerVertex) 1 BuiltIn PointSize
Decorate 38 Location 0
2: TypeVoid
3: TypeFunction 2
6: TypeFloat 32
7: TypeVector 6(float) 4
8: TypeMatrix 7(fvec4) 4
9(struct vppui::TFramePar<2>): TypeStruct 8 8 8
10: TypePointer Uniform 9(struct vppui::TFramePar<2>)
11: 10(ptr) Variable Uniform
12: TypeInt 32 0
13: 12(int) Constant 0
14: TypePointer Uniform 8
17: 12(int) Constant 1
20: 12(int) Constant 2
23: TypePointer Input 7(fvec4)
24: 23(ptr) Variable Input
26: 23(ptr) Variable Input
31(gl_PerVertex): TypeStruct 7(fvec4) 6(float)
32: TypePointer Output 31(gl_PerVertex)
33: 32(ptr) Variable Output
34: TypeInt 32 1
35: 34(int) Constant 0
36: TypePointer Output 7(fvec4)
38: 36(ptr) Variable Output
4(main): 2 Function None 3
5: Label
15: 14(ptr) AccessChain 11 13
16: 8 Load 15
18: 14(ptr) AccessChain 11 17
19: 8 Load 18
21: 14(ptr) AccessChain 11 20
22: 8 Load 21
25: 7(fvec4) Load 24
27: 7(fvec4) Load 26
28: 8 MatrixTimesMatrix 22 19
29: 8 MatrixTimesMatrix 28 16
30: 7(fvec4) MatrixTimesVector 29 25
37: 36(ptr) AccessChain 33 35
Store 37 30
Store 38 27
FunctionEnd
--------------------- Code end ----------------------