# Vulkan Layer Factory

## Overview

The Vulkan Layer Factory is a framework based on the canonical Vulkan layer model that facilitates the creation of Vulkan Layers. The layer factory hides the majority of the loader-layer interface, layer boilerplate, setup and initialization, and complexities of layer development.

A complete layer with the attendant support files can be produced by simply creating a subdirectory in the `sources\layer_factory` directory and adding in a simple header file and then running cmake. This layer can be used just as any other Vulkan layer.

The Vulkan Layer Factory framework produces 'Factory Layers' comprising one or more 'interceptor' objects. Interceptor objects override functions to be called before (`PreCallApiName`) or after (`PostCallApiName`) each Vulkan entrypoint of interest. Each interceptor is independent of all others within a single Factory layer. If multiple interceptors attach to the same API call the calling order is indeterminate.

### Layer Factory sample code

The base installation of the layer factory contains some sample layers, including the Vulkan Assistant Layer, demo_layer, and the starter_layer. The Starter Layer in particular is meant to serve as an example of a very simple layer implementation. The Demo Layer exhibits some new VLF ease-of-use features, including global intercepts and various output options, while the Assistant Layer is a production layer which functions to highlight performance issues and to enforce best-practices for applications.

### Create a Factory Layer

Step 1: Create a subdirectory in the `source\layer_factory` directory using a name that will serve as the base for the final layer

For example, a subdirectory named 'starter_layer' will produce `libVkLayer_starter_layer.so` and be loaded as `VK_LAYER_LUNARG_starter_layer`

Step 2: Add your interceptor file(s) to the subdirectory

This can be a single header file, or multiple header and source files. See the other VLF layers for examples.

Step 3: Create or copy an `interceptor_objects.h` file into your new directory

This should include the header file for each of the included interceptors:

`#include "whatever_you_called_your_layers_header_file.h"`

Step 4: Run CMake and build

        cd source/layer_factory
        cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug
        cd build
        make

CMake will discover all Factory Layer subdirectories in layer_factory each time it is run. A *.json will also be created with your layer binary. As part of the build process, these files will be copied to the appropriated SDK layer file locations, and will be picked up by the usual `VK_LAYERS_PATH` environment variable set by running `setup_env.sh`.

Note that adding or removing a layer factory subdirectory requires re-running CMake in order to properly recognize the additions/deletions.

## Using Layers

1. Build VK loader using normal steps (cmake and make).

The library and json files will be automatically copied to the standard SDK locations, made available by running `source setup_env.sh`.

2. Specify which layers to activate using environment variables.

    `export VK_INSTANCE_LAYERS=VK_LAYER_LUNARG_standard_validation:VK_LAYER_LUNARG_starter_layer`

3. Run your application. The layer path and layer choices will be picked up from your environment.

Alternatively, you can specify these variables on the command line, like so:

`VK_INSTANCE_LAYERS=VK_LAYER_LUNARG_demo_layer VK_LAYER_PATH=/home/user/VulkanSDK/1.0.68.0/source/layer_factory/build ./vulkaninfo`

### Layer Factory Features

The layer factory provides helper functions for layer authors to simplify layer tasks. These include some simpler output functions, debug facilities, and simple global intercept functions.

Output Helpers:

Interceptors can use base-class defined output helpers for easy access to Debug Report Extension output. These include Information(), Warning(), Performance_Warning(), and Error(), corresponding to the VkDebugReportFlagBitsEXT enumerations. Alternatively, the standard layer-provided log_msg() call can be used directly, as can printf for standard-out or OutputDebugString for Windows.

Debug Helpers:

A BreakPoint() helper can be used in an intercepted function which will generate a break in a Windows or Linux debugger.

Global Intercept Helpers:

There are two global intercept helpers, PreCallApiFunction() and PostCallApiFunction(). Overriding these virtual functions in your intercepter will result in them being called for EVERY API call.

### Details

By creating a child framework object, the factory will generate a full layer and call any overridden functions in your interceptor.

Here is a simple, and complete, interceptor (the starter_layer). This layer intercepts the memory allocate and free functions, tracking the number and total size of device memory allocations. The QueuePresent() function is also intercepted, and results are outputted on every 60th frame. Note that the function signatures are and must be identical to those in the specification.

In this example, there is a single interceptor in which the child object is named 'MemAllocLevel' and is instantiated as 'memory_allocation_stats'. A layer can contain many interceptors as long as the instantiated object names are unique within that layer.

        #pragma once
        #include <sstream>
        #include <unordered_map>

        static uint32_t display_rate = 60;

        class MemAllocLevel : public layer_factory {
            public:
                // Constructor for interceptor
                MemAllocLevel() : layer_factory(this), number_mem_objects_(0), total_memory_(0), present_count_(0) {};

                // Intercept memory allocation calls and increment counter
                VkResult PostCallAllocateMemory(VkDevice device, const VkMemoryAllocateInfo *pAllocateInfo,
                        const VkAllocationCallbacks *pAllocator, VkDeviceMemory *pMemory) {
                    number_mem_objects_++;
                    total_memory_ += pAllocateInfo->allocationSize;
                    mem_size_map_[*pMemory] = pAllocateInfo->allocationSize;
                    return VK_SUCCESS;
                };

                // Intercept free memory calls and update totals
                void PreCallFreeMemory(VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks *pAllocator) {
                    if (memory != VK_NULL_HANDLE) {
                        number_mem_objects_--;
                        VkDeviceSize this_alloc = mem_size_map_[memory];
                        total_memory_ -= this_alloc;
                    }
                }

                VkResult PreCallQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR *pPresentInfo) {
                    present_count_++;
                    if (present_count_ >= display_rate) {
                        present_count_ = 0;
                        std::stringstream message;
                        message << "Memory Allocation Count: " << number_mem_objects_ << "\n";
                        message << "Total Memory Allocation Size: " << total_memory_ << "\n\n";
                        Information(message.str());
                    }
                    return VK_SUCCESS;
                }

            private:
                // Counter for the number of currently active memory allocations
                uint32_t number_mem_objects_;
                VkDeviceSize total_memory_;
                uint32_t present_count_;
                std::unordered_map<VkDeviceMemory, VkDeviceSize> mem_size_map_;
        };

        MemAllocLevel memory_allocation_stats;

### Current known issues

- CMake MUST be run to pick up and interpret new or deleted factory layers.