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

Extending Intel® GPA Framework with Custom Layers

Pamela_H_Intel
Moderator
0 1 2,888

Custom Layers: Part I

(Custom Layers: Part II -- Generating Custom Layers)

Authors

Jay Ravi, GPU Software Development Engineer, Intel Corporation

Pamela Harrison, Software Technical Consulting Engineer

Introduction:

Intel® GPA Framework provides a set of command-line tools for capture/playback of graphics applications. The heart of the Intel® GPA Framework is a robust, scalable, and low overhead layering framework like the Vulkan layer framework. Additionally, it comes with a set of headers/libraries to help build your own layers that can work with Intel® GPA Framework’s CLI tools.

In this article, we will explore

  • What is a layer
  • Layer anatomy
  • How to write a basic custom layer

Layer System Overview:

The Previous versions of the Intel® GPA shim were monolithic in terms of functionality. Changing one aspect of the shim would unintentionally cause regressions in other modules. When new feature requirements arose, it became harder and harder to make changes to the shim. Intel® GPA Framework was designed with a new architecture to resolve this challenge.

Pamela_H_Intel_0-1689377407693.png

 

With the new layer architecture, the Intel® GPA Framework shim is a lightweight DLL (Dynamic Link Library or shared library in Unix speak) and only handles intercepting graphics API calls. Additionally, the shim can load any layer based on command-line arguments.

 

Pamela_H_Intel_1-1689377407696.png

 

Anatomy of a Layer

A layer is built as a DLL. The core of a layer is a special structure called GPADispatchTable, defined in dispatch-table.h. The struct contains a lengthy list of every overridable graphics API call’s function pointers as members along with few function pointers which are reserved for use by the layer system. At its most basic, the layer system can be thought of as a series of these GPADispatchTables, linked toghether in a linear, order dependant sequence.

Layers must have a function with the signature GPA_LAYER_EXPORT GPADispatchTable* LAYER_ENTRYPOINT(). The layer system looks for this signature while loading the layer and with out it, a dll will not be considered a valid layer. 7. LAYER_ENTRYPOINT acts as the  setup function for a layer where layer specific behaviors and states for use within your layer can be initialized. More importantly, this function is used to set up the overrides in the layer’s dispatch table so your layer can hook graphics API calls and provide custom handling for said calls. For example, a layer may hook the Swapchain Present call to keep track of the number of frames rendered or hook create device method to know when a graphics device was created, keeping a reference to the device for use within the layer.

When a layer is loaded by the GPA shim, every layer dispatch table is stacked in loading order to ensure the graphics calls initiated by the application go through the layer chain before eventually reaching the driver. Layer overrides have complete control over a graphics call i.e., they can inspect a call’s parameters, modify parameters before sending them down the layer chain, completely ignore a call thus not sending it to driver or make additional graphics calls.

To document the layer usage, you can expose an optional entry point with the signature GPA_LAYER_EXPORT char const* LAYER_HELP_FUNCTION(). This entry point gets picked up by the gpa-help utility which displays layer usage help text if available. Another optional entry point is LAYER_GET_REQUIRED_LAYERS() which requests the gpa shim to load additional layers on initialization. An example of this, a layer can request our d3d12-state-tracker layer which keeps track of object dependencies and their various states.

 

Writing your first layer:

We will create a basic hello layer project to introduce the various pieces to writing a custom layer.

Note: We assume the requirements listed below (from Samples — Intel® GPA Framework documentation)

  • Visual Studio* 2019 or 2022 (64-bit)
  • CMake* (3.16+)
  • Python* 3.9+
  • Windows 11 SDK (10.0.22000.0) for Desktop C++
  • Vulkan* SDK (1.3.243.0)

Let’s start by creating a source file named hello-layer.cpp.

Note: If you have never created a custom layer, download the sample project and use that as a template. Either reuse the layer samples that are already shipped or create a sub project.

  1. Declare an instance of the dispatch table struct at the top of the source file preferably within a namespace.

 

 

 

 

 

GPADispatchTable sDispatchTable = {"Hello Layer"}; // layer's instance of Dispatch Table

 

 

 

 

 

 

  1. In this example code, we want to count frames rendered by overriding the present call on the swapchain. Our override’s call signature is identical to original present call. We have a counter incrementing every time a present call is made. We then forward the calls down the layer chain by calling the function pointer on the dispatch table. Note that you must call the dispatchtable version of the API call as part of your override function, or else the API call will stop here, not travel down the rest of the layer system and not be executed correctly, probably leading to crashes or corruption.

 

 

 

 

 

HRESULT WINAPI PresentCallOverride(IDXGISwapChain* pSwapChain, UINT SyncInterval, UINT Flags)
{
    static uint64_t sFrameCounter = 0;
    sFrameCounter++;
    GPA_LOG_INFO("Frames presented %llu", sFrameCounter);
    // Forward call to the next layer
    return sDispatchTable.DirectX.IDXGISwapChainTable.Present(pSwapChain, SyncInterval, Flags);
}

 

 

 

 

 

 

  1. Define the layer entry point function. The body of this function has a log statement and assigns our override for present call to the corresponding function pointer in the dispatch table. We then return a pointer to this instance of the dispatch table. The layer system will then patch the pointers of the dispatch table and appropriately sends the calls to the runtime/driver.

 

 

 

 

 

//! Define Entry Point a.k.a layer main function.
GPA_LAYER_EXPORT GPADispatchTable* LAYER_ENTRYPOINT(size_t /*argc*/, gpa::utility::KeyValPair const* /*keyValPairs*/)
{  
    GPA_LOG_INFO("Hello Layer Loaded");

    //! provide custom handling for DXGI Swapchain present call
    hello_layer::sDispatchTable.DirectX.IDXGISwapChainTable.Present = hello_layer::PresentCallOverride;

    // return a pointer to our custom dispatch table
    return &hello_layer::sDispatchTable;
}

 

 

 

 

 

 

We now have implemented a basic layer which simply counts the frames rendered and prints it to standard output.

Next, let’s create a CMake project for your layer which can generate a layer DLL. Use the samples project shipped with Intel® GPA Framework distribution as a starting point and this project as a sub-directory. The samples project sets up the Intel® GPA Framework dependencies and imports all necessary targets. Add the following code snippet to your CMakeLists.txt for your hello layer project.

 

 

 

 

 

set(TARGET hello-layer)

set(SOURCES hello-layer.cpp)

gpa_framework_add_layer(${TARGET}
    SOURCES ${SOURCES}
)
target_link_libraries(${TARGET}
    PRIVATE
        GPA::logger
)

 

 

 

 

 

 

The gpa_framework_add_layer() function handles all boilerplate code to create a layer target. Additionally you can link with other libraries that your layer may depend upon. In this, we are linking with logger library.

To build the project, follow the instructions provided here the Intel® GPA Framework documentation page - Samples — Intel® GPA Framework documentation.

 

And there you have it, your first Intel® GPA Framework custom layer. You may load this layer with Intel® GPA Framework CLI tools like gpa-injector/gpa-player just like the existing pre-built layers.

gpa-injector.exe –-layer hello-layer <path to app>
gpa-player.exe –-pre-layer hello-layer <path to stream>

 

Summary

In this article we discussed what constitutes an Intel® GPA Framework layer and how to write one ourselves using the tools provided in the Intel® GPA Framework distribution. In the next article, we will discuss how to use the layer generator for more complex use cases. For example, what if you want to override every graphics API call? It would be a lot of manual work to achieve this. That is where the layer generator is most helpful. See Custom Layers: Part II - Intel® GPA Framework: Generating Custom Layers for writing custom layers with layer generator.

 

Resources

Download Intel® Graphics Performance Analyzers

Samples — Intel® GPA Framework documentation

 

1 Comment
TRACKER
Beginner

Still interested in this topic.

Really thanks your guys' hard and inteligent work, I have built some layers with my own.

 

Howerver, the gpa-injector sometimes cannot inject into some APPs (maybe anti-injector or something like that), so I want to do it by myself. I mean, use my own injector, but inject gpa's capture layer.

 

In other words, I want to use your interface to capture trace with gpa's capture layer.

 

I have checked your cpp interface guide https://intel.github.io/gpasdk-doc/cppapi/library_root.html#.

 

But I still cannot find what kind of dll I should use to inject to APP process, as well as what kind of function I should call in my own injector. 

 

Could you pld kindly do help?