Tools
Explore new features and tools within Intel® products, communities, and platforms
97 Discussions

Intel® GPA Framework: Generating Custom Layers

Pamela_H_Intel
Moderator
0 2 3,869

Custom Layers: Part II

(Custom Layers: Part I -- Extending Custom Layers)

Authors

Jay Ravi, GPU Software Development Engineer, Intel Corporation

Pamela Harrison, Software Technical Consulting Engineer

Introduction:

In the previous blog post, Custom Layers: Part I --  Extending Intel® GPA Framework with Custom Layerswe saw how to write a basic custom layer. We wrote a hook/override for a single call (Swapchain present call). But what if you are writing a complex feature and you would like to generate hooks for all graphics API calls? It would be painstaking to look up documentation for each call signature and write a matching override function in the layers. This is where a layer generator is useful.

Overview:

A layer generator constitutes a set of API metadata files, python scripts and templated source files working with the Intel® GPA Framework CMake utility functions to produce generated C++ sources for our layer project. To clarify, ‘templated’ source files don’t refer to C++ templates but a file with variable placeholders/loops/conditional statements recognized by a templating engine traditionally used in web development to generate html pages. For our generator, we use the Jinja2 templating engine.

Writing our own generated layer:

In this contrived example, we will use the generator to create a simple logging layer which prints just names of the graphics API functions inside the hooks. We will go over project files of interest in this section.

 

First, we need to create a JSON text file called vulkan-custom.json or directx-custom.json. This is a prerequisite for generated layers. Depending on which API your layer is hooking, either or both API’s, JSON files are required. The files should be in the same folder as the layer sources and should have the following content:

vulkan-custom.json

directx-custom.json

{
    "name" : "Vulkan",
    "callingConvention" : "VKAPI_CALL"
}
{
"name": "DirectX",
"callingConvention": "WINAPI"
}

 

Second, to be able to generate hook functions automatically we need metadata of the graphics API’s classes, functions and parameters. The metadata are available in JSON files for each graphics API that are shipped as part of the distribution. The metadata contains names of the function calls, parameters’ names and types, function return types, etc. Here is an excerpt from vulkan.json:

 

 

 

 

 

 

 

 

"functions": [
{
    "name": "vkAcquireFullScreenExclusiveModeEXT",
    "params": [
        {
            "InterfacePointer": true,
            "Pointer": true,
            "name": "device",
            "type": "VkDevice"
        },
        {
            "InterfacePointer": true,
            "Pointer": true,
            "name": "swapchain",
            "type": "VkSwapchainKHR"
        }
    ],
    "returnValue": {
        "Enum": true,
        "type": "VkResult"
    }
}
]

 

 

 

 

 

 

 

Third, templated C++ sources written in Jinja2 templating language. Templates contains a mix of data from JSON and Jinja function helpers to process. Additionally custom JSON files can be used to provide special handling for certain functions. Here is an excerpt from a template file. There is a lot going on here but the Jinja comments ({#..#}) explain each line. Additionally, the template legend gives a quick overview of the template syntax. We encourage you to check the Jinja 2 documentation for more information on templates.

 

 

 

 

 

 

 

{# Loop over function definitions and create layer overrides for each of them#}
{% for function in data.functions %}
{{function.returnValue.type}} {{data.callingConvention}} {{function.name | cStyleMethodName}}({{function |fullParameterString(true)}})
{
{# Use GPA log function to print function name #}
GPA_LOG_INFO("%s", "{{function.name}}");

{# Generate this statement ‘retValue =‘ if this function is not a void type #}

{% if not function.returnValue.Void %} {{function.returnValue.type}} retValue = {% endif %}
{#Call corresponding function in the dispatch table to forward call in the layer chain.#}
sDispatchTable.{{data.name}}.{{function.name  | cStyleMethodName}}({{function | parameterNameString(true)}});
{# Generate return state if this is not a void function#}
{% if not function.returnValue.Void %}return retValue; {% endif %}
}
{% endfor %}

 

 

 

 

 

 

 

Template Legend:

data – root dictionary object of JSON file in memory.

name – API name currently being processed e.g., Vulkan. Comes from *-custom.json file.

functions – array of function definitions in json

var|jinja_filter - variables can be modified by Jinja filters using a pipe syntax. Filters are helper python functions that can further process and return processed data.

Jinja Constructs:

{{…}} – Print expressions

{%…%} – Statements such as loops/conditionals

{#…#} – Jinja comments. Not displayed in the final source file.

 

Data for this template is populated from JSON files we discussed before. Here is an example function override generated by the above template.

 

 

 

 

 

 

 

HRESULT WINAPI D3D12CreateDevice(IUnknown * pAdapter, D3D_FEATURE_LEVEL MinimumFeatureLevel, const IID & riid, void ** ppDevice)
{
    GPA_LOG_INFO("%s", "D3D12CreateDevice");
     HRESULT retValue =
    sDispatchTable.DirectX.D3D12CreateDevice(pAdapter, MinimumFeatureLevel, riid, ppDevice);
     return retValue;
}

 

 

 

 

 

 

 

And here is another function with void return type.

 

 

 

 

 

 

 

void WINAPI Unmap(ID3D12Resource* self, UINT Subresource, const D3D12_RANGE * pWrittenRange)
{
    GPA_LOG_INFO("%s", "ID3D12Resource::Unmap");
    sDispatchTable.DirectX.ID3D12ResourceTable.Unmap(self, Subresource, pWrittenRange);
}

 

 

 

 

 

 

To bring all these files together we need the CMake function below, gpa_framework_add_layer(). It takes template files as arguments, which invokes the python generators. The inputs being the JSON files and templates and outputs are source files generated per supported API (Vulkan*, DirectX*). These generated source files are automatically added to the layer target.

 

 

 

 

 

 

gpa_framework_add_layer(${TARGET}
        SOURCES
            hello-generated-layer.cpp
        FULL_TEMPLATES
            templates/hello-generated.h.j2 # template for headers
            templates/hello-generated.cpp.j2 # template for sources
            templates/hello-generated-method-list.inl.j2
)

 

 

 

 

 

 

Summary

Layer generator is a vital tool for developing a lot of the well know prebuilt Intel® GPA Framework layers such as capture layer, logging layer, state tracking layer and many more. It is the quickest way to generate overrides for all graphics API calls. Users can build debugging tools using this generator to inspect every API call in a manner that they choose or create a new layer that uses a custom logging library for each call.

Resources

Download Intel® Graphics Performance Analyzers

Samples — Intel® GPA Framework documentation

Template Designer Documentation — Jinja Documentation (3.1.x) (palletsprojects.com)

 

About the Author
Software engineer for 20+ years. Excels in all things software, plus connecting people and teams for optimal synergy.
2 Comments
TRACKER
Beginner

Very useful!

Just a simple question.

How can I add a custom layer to GPA's shimLoader?

Because sometimes, I need to capture frame using  Graphic Monitor's Auto-detect Launched Application(Details can be referred to  https://www.intel.com/content/www/us/en/docs/gpa/user-guide/2022-4/connect-to-running-app.html)

TRACKER_0-1697515714495.png

 

I found that GPA only load the offical layers(such as HUD layers, which can be selected  by a  checkbox in UI)

TRACKER_1-1697515851846.png

 

Could you pls kindly help?

Xiaohan_Wang_Intel

@TRACKER I am glad to help you here!

GPA Framework is a set of command line-based tools. Enabling a custom layer is a GPA framework feature instead of a GPA feature.

This is our official user guide here. Intel® Graphics Performance Analyzers Framework (Intel® GPA Framework) — Intel® GPA Framework documentation

You can use bellow command to inject to the target application.

gpa-injector --layer [your_custom_layer_name] [path_to_executable]

 At this moment, enabling custom layer is not an option in GPA.

To enable this feature, you need first to download and install GPA Framework bellow.

Download Intel® Graphics Performance Analyzers

Screenshot 2023-11-13 180817.png

Then follow this tutorial to build your project, and then you will be able to use your custom layer in gpa-injector. Hope this helps!