blob: 10988b87fc155759c0590ce73094b31395078380 [file] [log] [blame]
/*
*
* Copyright (C) 2015-2016 Valve Corporation
* Copyright (C) 2015-2016 LunarG, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Author: Cody Northrop <cody@lunarg.com>
* Author: David Pinedo <david@lunarg.com>
* Author: Jon Ashburn <jon@lunarg.com>
*
*
* rfb.cpp is based off of screenshot.cpp from VulkanTools, with some changes to
* output to an rfb server instead of a file.
*/
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <fstream>
#include <iostream>
#include <list>
#include <map>
#include <set>
#include <unordered_map>
#include <vector>
using namespace std;
#include "rfb_server.h"
#include "vk_dispatch_table_helper.h"
#include "vk_layer_config.h"
#include "vk_layer_extension_utils.h"
#include "vk_layer_table.h"
#include "vk_layer_utils.h"
namespace screenshot {
static int globalLockInitialized = 0;
static loader_platform_thread_mutex globalLock;
const char *vk_screenshot_format = nullptr;
bool printFormatWarning = true;
typedef enum colorSpaceFormat {
UNDEFINED = 0,
UNORM = 1,
SNORM = 2,
USCALED = 3,
SSCALED = 4,
UINT = 5,
SINT = 6,
SRGB = 7
} colorSpaceFormat;
colorSpaceFormat userColorSpaceFormat = UNDEFINED;
// unordered map: associates a swap chain with a device, image extent, format,
// and list of images
typedef struct {
VkDevice device;
VkExtent2D imageExtent;
VkFormat format;
VkImage *imageList;
RFBServer server;
} SwapchainMapStruct;
static unordered_map<VkSwapchainKHR, SwapchainMapStruct *> swapchainMap;
// unordered map: associates an image with a device, image extent, and format
typedef struct {
VkDevice device;
VkExtent2D imageExtent;
VkFormat format;
} ImageMapStruct;
static unordered_map<VkImage, ImageMapStruct *> imageMap;
// unordered map: associates a device with a queue, commandPool, and physical
// device also contains per device info including dispatch table
typedef struct {
VkLayerDispatchTable *device_dispatch_table;
bool wsi_enabled;
VkQueue queue;
std::list<VkCommandPool> commandPools;
VkPhysicalDevice physicalDevice;
PFN_vkSetDeviceLoaderData pfn_dev_init;
} DeviceMapStruct;
static unordered_map<VkDevice, DeviceMapStruct *> deviceMap;
// unordered map: associates a physical device with an instance
typedef struct {
VkInstance instance;
} PhysDeviceMapStruct;
static unordered_map<VkPhysicalDevice, PhysDeviceMapStruct *> physDeviceMap;
static bool memory_type_from_properties(VkPhysicalDeviceMemoryProperties *memory_properties,
uint32_t typeBits, VkFlags requirements_mask,
uint32_t *typeIndex) {
// Search memtypes to find first index with those properties
for (uint32_t i = 0; i < 32; i++) {
if ((typeBits & 1) == 1) {
// Type is available, does it match user properties?
if ((memory_properties->memoryTypes[i].propertyFlags & requirements_mask) ==
requirements_mask) {
*typeIndex = i;
return true;
}
}
typeBits >>= 1;
}
// No memory types matched, return failure
return false;
}
static DeviceMapStruct *get_dev_info(VkDevice dev) {
auto it = deviceMap.find(dev);
if (it == deviceMap.end())
return NULL;
else
return it->second;
}
static void init_screenshot() {
if (!globalLockInitialized) {
// TODO/TBD: Need to delete this mutex sometime. How??? One
// suggestion is to call this during vkCreateInstance(), and then we
// can clean it up during vkDestroyInstance(). However, that requires
// that the layer have per-instance locks. We need to come back and
// address this soon.
loader_platform_thread_create_mutex(&globalLock);
globalLockInitialized = 1;
}
}
// Track allocated resources in writePPM()
// and clean them up when they go out of scope.
struct WritePPMCleanupData {
VkDevice device;
VkLayerDispatchTable *pTableDevice;
VkImage image2;
VkImage image3;
VkDeviceMemory mem2;
VkDeviceMemory mem3;
bool mem2mapped;
bool mem3mapped;
VkCommandBuffer commandBuffer;
VkCommandPool commandPool;
~WritePPMCleanupData();
};
WritePPMCleanupData::~WritePPMCleanupData() {
if (mem2mapped)
pTableDevice->UnmapMemory(device, mem2);
if (mem2)
pTableDevice->FreeMemory(device, mem2, NULL);
if (image2)
pTableDevice->DestroyImage(device, image2, NULL);
if (mem3mapped)
pTableDevice->UnmapMemory(device, mem3);
if (mem3)
pTableDevice->FreeMemory(device, mem3, NULL);
if (image3)
pTableDevice->DestroyImage(device, image3, NULL);
if (commandBuffer)
pTableDevice->FreeCommandBuffers(device, commandPool, 1, &commandBuffer);
}
// Save an image to a PPM image file.
//
// This function issues commands to copy/convert the swapchain image
// from whatever compatible format the swapchain image uses
// to a single format (VK_FORMAT_R8G8B8A8_UNORM) so that the converted
// result can be easily written to a PPM file.
//
// Error handling: If there is a problem, this function should silently
// fail without affecting the Present operation going on in the caller.
// The numerous debug asserts are to catch programming errors and are not
// expected to assert. Recovery and clean up are implemented for image memory
// allocation failures.
// (TODO) It would be nice to pass any failure info to DebugReport or something.
static void writeScreenshot(SwapchainMapStruct *swapchain, VkImage image1) {
VkResult err;
bool pass;
// Bail immediately if we can't find the image.
if (imageMap.empty() || imageMap.find(image1) == imageMap.end())
return;
// Collect object info from maps. This info is generally recorded
// by the other functions hooked in this layer.
VkDevice device = imageMap[image1]->device;
VkPhysicalDevice physicalDevice = deviceMap[device]->physicalDevice;
VkInstance instance = physDeviceMap[physicalDevice]->instance;
VkQueue queue = deviceMap[device]->queue;
DeviceMapStruct *devMap = get_dev_info(device);
if (NULL == devMap) {
assert(0);
return;
}
VkLayerDispatchTable *pTableDevice = devMap->device_dispatch_table;
VkLayerDispatchTable *pTableQueue =
get_dev_info(static_cast<VkDevice>(static_cast<void *>(queue)))->device_dispatch_table;
VkLayerInstanceDispatchTable *pInstanceTable;
pInstanceTable = instance_dispatch_table(instance);
// Gather incoming image info and check image format for compatibility with
// the target format.
// This function supports both 24-bit and 32-bit swapchain images.
uint32_t const width = imageMap[image1]->imageExtent.width;
uint32_t const height = imageMap[image1]->imageExtent.height;
VkFormat const format = imageMap[image1]->format;
uint32_t const numChannels = FormatChannelCount(format);
if (!swapchain->server.Initialize(width, height, 5900))
return;
if ((3 != numChannels) && (4 != numChannels)) {
assert(0);
return;
}
// Initial dest format is undefined as we will look for one
VkFormat destformat = VK_FORMAT_UNDEFINED;
// This variable set by readScreenShotFormatENV func during init
if (userColorSpaceFormat != UNDEFINED) {
switch (userColorSpaceFormat) {
case UNORM:
if (numChannels == 4)
destformat = VK_FORMAT_R8G8B8A8_UNORM;
else
destformat = VK_FORMAT_R8G8B8_UNORM;
break;
case SRGB:
if (numChannels == 4)
destformat = VK_FORMAT_R8G8B8A8_SRGB;
else
destformat = VK_FORMAT_R8G8B8_SRGB;
break;
case SNORM:
if (numChannels == 4)
destformat = VK_FORMAT_R8G8B8A8_SNORM;
else
destformat = VK_FORMAT_R8G8B8_SNORM;
break;
case USCALED:
if (numChannels == 4)
destformat = VK_FORMAT_R8G8B8A8_USCALED;
else
destformat = VK_FORMAT_R8G8B8_USCALED;
break;
case SSCALED:
if (numChannels == 4)
destformat = VK_FORMAT_R8G8B8A8_SSCALED;
else
destformat = VK_FORMAT_R8G8B8_SSCALED;
break;
case UINT:
if (numChannels == 4)
destformat = VK_FORMAT_R8G8B8A8_UINT;
else
destformat = VK_FORMAT_R8G8B8_UINT;
break;
case SINT:
if (numChannels == 4)
destformat = VK_FORMAT_R8G8B8A8_SINT;
else
destformat = VK_FORMAT_R8G8B8_SINT;
break;
default:
destformat = VK_FORMAT_UNDEFINED;
break;
}
}
// User did not require sepecific format so we use same colorspace with
// swapchain format
if (destformat == VK_FORMAT_UNDEFINED) {
// Here we reserve swapchain color space only as RGBA swizzle will be later.
//
// One Potential optimization here would be: set destination to RGB all the
// time instead RGBA. PPM does not support Alpha channel, so we can write
// RGB one row by row but RGBA written one pixel at a time.
// This requires BLIT operation to get involved but current drivers (mostly)
// does not support BLIT operations on 3 Channel rendertargets.
// So format conversion gets costly.
if (numChannels == 4) {
if (FormatIsUNorm(format))
destformat = VK_FORMAT_R8G8B8A8_UNORM;
else if (FormatIsSRGB(format))
destformat = VK_FORMAT_R8G8B8A8_SRGB;
else if (FormatIsSNorm(format))
destformat = VK_FORMAT_R8G8B8A8_SNORM;
else if (FormatIsUScaled(format))
destformat = VK_FORMAT_R8G8B8A8_USCALED;
else if (FormatIsSScaled(format))
destformat = VK_FORMAT_R8G8B8A8_SSCALED;
else if (FormatIsUInt(format))
destformat = VK_FORMAT_R8G8B8A8_UINT;
else if (FormatIsSInt(format))
destformat = VK_FORMAT_R8G8B8A8_SINT;
} else { // numChannels 3
if (FormatIsUNorm(format))
destformat = VK_FORMAT_R8G8B8_UNORM;
else if (FormatIsSRGB(format))
destformat = VK_FORMAT_R8G8B8_SRGB;
else if (FormatIsSNorm(format))
destformat = VK_FORMAT_R8G8B8_SNORM;
else if (FormatIsUScaled(format))
destformat = VK_FORMAT_R8G8B8_USCALED;
else if (FormatIsSScaled(format))
destformat = VK_FORMAT_R8G8B8_SSCALED;
else if (FormatIsUInt(format))
destformat = VK_FORMAT_R8G8B8_UINT;
else if (FormatIsSInt(format))
destformat = VK_FORMAT_R8G8B8_SINT;
}
}
// Still could not find the right format then we use UNORM
if (destformat == VK_FORMAT_UNDEFINED) {
#ifdef ANDROID
#else
if (printFormatWarning) {
fprintf(
stderr,
"Swapchain format is not in the list:\nUNORM, SNORM, USCALED, SSCALED, UINT, SINT, SRGB\n"
"UNORM colorspace will be used instead\n");
printFormatWarning = false;
}
#endif
if (numChannels == 4)
destformat = VK_FORMAT_R8G8B8A8_UNORM;
else
destformat = VK_FORMAT_R8G8B8_UNORM;
}
if ((FormatCompatibilityClass(destformat) != FormatCompatibilityClass(format))) {
assert(0);
return;
}
// General Approach
//
// The idea here is to copy/convert the swapchain image into another image
// that can be mapped and read by the CPU to produce a PPM file.
// The image must be untiled and converted to a specific format for easy
// parsing. The memory for the final image must be host-visible.
// Note that in Vulkan, a BLIT operation must be used to perform a format
// conversion.
//
// Devices vary in their ability to blit to/from linear and optimal tiling.
// So we must query the device properties to get this information.
//
// If the device cannot BLIT to a LINEAR image, then the operation must be
// done in two steps:
// 1) BLIT the swapchain image (image1) to a temp image (image2) that is
// created with TILING_OPTIMAL.
// 2) COPY image2 to another temp image (image3) that is created with
// TILING_LINEAR.
// 3) Map image 3 and write the PPM file.
//
// If the device can BLIT to a LINEAR image, then:
// 1) BLIT the swapchain image (image1) to a temp image (image2) that is
// created with TILING_LINEAR.
// 2) Map image 2 and write the PPM file.
//
// There seems to be no way to tell if the swapchain image (image1) is tiled
// or not. We therefore assume that the BLIT operation can always read from
// both linear and optimal tiled (swapchain) images.
// There is therefore no point in looking at the BLIT_SRC properties.
//
// There is also the optimization where the incoming and target formats are
// the same. In this case, just do a COPY.
VkFormatProperties targetFormatProps;
pInstanceTable->GetPhysicalDeviceFormatProperties(physicalDevice, destformat, &targetFormatProps);
bool need2steps = false;
bool copyOnly = false;
if (destformat == format) {
copyOnly = true;
} else {
bool const bltLinear =
targetFormatProps.linearTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT ? true : false;
bool const bltOptimal =
targetFormatProps.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT ? true : false;
if (!bltLinear && !bltOptimal) {
// Cannot blit to either target tiling type. It should be pretty
// unlikely to have a device that cannot blit to either type.
// But punt by just doing a copy and possibly have the wrong
// colors. This should be quite rare.
copyOnly = true;
} else if (!bltLinear && bltOptimal) {
// Cannot blit to a linear target but can blt to optimal, so copy
// after blit is needed.
need2steps = true;
}
// Else bltLinear is available and only 1 step is needed.
}
// Put resources that need to be cleaned up in a struct with a destructor
// so that things get cleaned up when this function is exited.
WritePPMCleanupData data = {};
data.device = device;
data.pTableDevice = pTableDevice;
// Set up the image creation info for both the blit and copy images, in case
// both are needed.
VkImageCreateInfo imgCreateInfo2 = {
VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
NULL,
0,
VK_IMAGE_TYPE_2D,
destformat,
{width, height, 1},
1,
1,
VK_SAMPLE_COUNT_1_BIT,
VK_IMAGE_TILING_LINEAR,
VK_IMAGE_USAGE_TRANSFER_DST_BIT,
VK_SHARING_MODE_EXCLUSIVE,
0,
NULL,
VK_IMAGE_LAYOUT_UNDEFINED,
};
VkImageCreateInfo imgCreateInfo3 = imgCreateInfo2;
// If we need both images, set up image2 to be read/write and tiled.
if (need2steps) {
imgCreateInfo2.tiling = VK_IMAGE_TILING_OPTIMAL;
imgCreateInfo2.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
}
VkMemoryAllocateInfo memAllocInfo = {
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL,
0, // allocationSize, queried later
0 // memoryTypeIndex, queried later
};
VkMemoryRequirements memRequirements;
VkPhysicalDeviceMemoryProperties memoryProperties;
// Create image2 and allocate its memory. It could be the intermediate or
// final image.
err = pTableDevice->CreateImage(device, &imgCreateInfo2, NULL, &data.image2);
assert(!err);
if (VK_SUCCESS != err)
return;
pTableDevice->GetImageMemoryRequirements(device, data.image2, &memRequirements);
memAllocInfo.allocationSize = memRequirements.size;
pInstanceTable->GetPhysicalDeviceMemoryProperties(physicalDevice, &memoryProperties);
pass = memory_type_from_properties(
&memoryProperties, memRequirements.memoryTypeBits,
need2steps ? VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT : VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
&memAllocInfo.memoryTypeIndex);
assert(pass);
err = pTableDevice->AllocateMemory(device, &memAllocInfo, NULL, &data.mem2);
assert(!err);
if (VK_SUCCESS != err)
return;
err = pTableQueue->BindImageMemory(device, data.image2, data.mem2, 0);
assert(!err);
if (VK_SUCCESS != err)
return;
// Create image3 and allocate its memory, if needed.
if (need2steps) {
err = pTableDevice->CreateImage(device, &imgCreateInfo3, NULL, &data.image3);
assert(!err);
if (VK_SUCCESS != err)
return;
pTableDevice->GetImageMemoryRequirements(device, data.image3, &memRequirements);
memAllocInfo.allocationSize = memRequirements.size;
pInstanceTable->GetPhysicalDeviceMemoryProperties(physicalDevice, &memoryProperties);
pass = memory_type_from_properties(&memoryProperties, memRequirements.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
&memAllocInfo.memoryTypeIndex);
assert(pass);
err = pTableDevice->AllocateMemory(device, &memAllocInfo, NULL, &data.mem3);
assert(!err);
if (VK_SUCCESS != err)
return;
err = pTableQueue->BindImageMemory(device, data.image3, data.mem3, 0);
assert(!err);
if (VK_SUCCESS != err)
return;
}
// Set up the command buffer. We get a command buffer from a pool we saved
// in a hooked function, which would be the application's pool.
if (deviceMap[device]->commandPools.empty()) {
assert(!deviceMap[device]->commandPools.empty());
return;
}
VkCommandPool commandPool = deviceMap[device]->commandPools.front();
const VkCommandBufferAllocateInfo allocCommandBufferInfo = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, NULL, commandPool,
VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1};
data.commandPool = commandPool;
err = pTableDevice->AllocateCommandBuffers(device, &allocCommandBufferInfo, &data.commandBuffer);
assert(!err);
if (VK_SUCCESS != err)
return;
VkDevice cmdBuf = static_cast<VkDevice>(static_cast<void *>(data.commandBuffer));
deviceMap.emplace(cmdBuf, devMap);
VkLayerDispatchTable *pTableCommandBuffer;
pTableCommandBuffer = get_dev_info(cmdBuf)->device_dispatch_table;
// We have just created a dispatchable object, but the dispatch table has
// not been placed in the object yet. When a "normal" application creates
// a command buffer, the dispatch table is installed by the top-level api
// binding (trampoline.c). But here, we have to do it ourselves.
if (!devMap->pfn_dev_init) {
*((const void **)data.commandBuffer) = *(void **)device;
} else {
err = devMap->pfn_dev_init(device, (void *)data.commandBuffer);
assert(!err);
}
const VkCommandBufferBeginInfo commandBufferBeginInfo = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL,
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, nullptr};
err = pTableCommandBuffer->BeginCommandBuffer(data.commandBuffer, &commandBufferBeginInfo);
assert(!err);
// This barrier is used to transition from/to present Layout
VkImageMemoryBarrier presentMemoryBarrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
NULL,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_ACCESS_TRANSFER_READ_BIT,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
image1,
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
// This barrier is used to transition from a newly-created layout to a blt
// or copy destination layout.
VkImageMemoryBarrier destMemoryBarrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
NULL,
0,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
data.image2,
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
// This barrier is used to transition a dest layout to general layout.
VkImageMemoryBarrier generalMemoryBarrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
NULL,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_ACCESS_TRANSFER_READ_BIT,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_IMAGE_LAYOUT_GENERAL,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
data.image2,
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
VkPipelineStageFlags srcStages = VK_PIPELINE_STAGE_TRANSFER_BIT;
VkPipelineStageFlags dstStages = VK_PIPELINE_STAGE_TRANSFER_BIT;
// The source image needs to be transitioned from present to transfer
// source.
pTableCommandBuffer->CmdPipelineBarrier(data.commandBuffer, srcStages, dstStages, 0, 0, NULL, 0,
NULL, 1, &presentMemoryBarrier);
// image2 needs to be transitioned from its undefined state to transfer
// destination.
pTableCommandBuffer->CmdPipelineBarrier(data.commandBuffer, srcStages, dstStages, 0, 0, NULL, 0,
NULL, 1, &destMemoryBarrier);
const VkImageCopy imageCopyRegion = {{VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1},
{0, 0, 0},
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1},
{0, 0, 0},
{width, height, 1}};
if (copyOnly) {
pTableCommandBuffer->CmdCopyImage(data.commandBuffer, image1,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, data.image2,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &imageCopyRegion);
} else {
VkImageBlit imageBlitRegion = {};
imageBlitRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
imageBlitRegion.srcSubresource.baseArrayLayer = 0;
imageBlitRegion.srcSubresource.layerCount = 1;
imageBlitRegion.srcSubresource.mipLevel = 0;
imageBlitRegion.srcOffsets[1].x = width;
imageBlitRegion.srcOffsets[1].y = height;
imageBlitRegion.srcOffsets[1].z = 1;
imageBlitRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
imageBlitRegion.dstSubresource.baseArrayLayer = 0;
imageBlitRegion.dstSubresource.layerCount = 1;
imageBlitRegion.dstSubresource.mipLevel = 0;
imageBlitRegion.dstOffsets[1].x = width;
imageBlitRegion.dstOffsets[1].y = height;
imageBlitRegion.dstOffsets[1].z = 1;
pTableCommandBuffer->CmdBlitImage(
data.commandBuffer, image1, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, data.image2,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &imageBlitRegion, VK_FILTER_NEAREST);
if (need2steps) {
// image 3 needs to be transitioned from its undefined state to a
// transfer destination.
destMemoryBarrier.image = data.image3;
pTableCommandBuffer->CmdPipelineBarrier(data.commandBuffer, srcStages, dstStages, 0, 0, NULL,
0, NULL, 1, &destMemoryBarrier);
// Transition image2 so that it can be read for the upcoming copy to
// image 3.
destMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
destMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
destMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
destMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
destMemoryBarrier.image = data.image2;
pTableCommandBuffer->CmdPipelineBarrier(data.commandBuffer, srcStages, dstStages, 0, 0, NULL,
0, NULL, 1, &destMemoryBarrier);
// This step essentially untiles the image.
pTableCommandBuffer->CmdCopyImage(data.commandBuffer, data.image2,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, data.image3,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &imageCopyRegion);
generalMemoryBarrier.image = data.image3;
}
}
// The destination needs to be transitioned from the optimal copy format to
// the format we can read with the CPU.
pTableCommandBuffer->CmdPipelineBarrier(data.commandBuffer, srcStages, dstStages, 0, 0, NULL, 0,
NULL, 1, &generalMemoryBarrier);
// Restore the swap chain image layout to what it was before.
// This may not be strictly needed, but it is generally good to restore
// things to original state.
presentMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
presentMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
presentMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
presentMemoryBarrier.dstAccessMask = 0;
pTableCommandBuffer->CmdPipelineBarrier(data.commandBuffer, srcStages, dstStages, 0, 0, NULL, 0,
NULL, 1, &presentMemoryBarrier);
err = pTableCommandBuffer->EndCommandBuffer(data.commandBuffer);
assert(!err);
VkFence nullFence = {VK_NULL_HANDLE};
VkSubmitInfo submitInfo;
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.pNext = NULL;
submitInfo.waitSemaphoreCount = 0;
submitInfo.pWaitSemaphores = NULL;
submitInfo.pWaitDstStageMask = NULL;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &data.commandBuffer;
submitInfo.signalSemaphoreCount = 0;
submitInfo.pSignalSemaphores = NULL;
err = pTableQueue->QueueSubmit(queue, 1, &submitInfo, nullFence);
assert(!err);
err = pTableQueue->QueueWaitIdle(queue);
assert(!err);
err = pTableDevice->DeviceWaitIdle(device);
assert(!err);
// Map the final image so that the CPU can read it.
const VkImageSubresource sr = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0};
VkSubresourceLayout srLayout;
const char *ptr;
if (!need2steps) {
pTableDevice->GetImageSubresourceLayout(device, data.image2, &sr, &srLayout);
err = pTableDevice->MapMemory(device, data.mem2, 0, VK_WHOLE_SIZE, 0, (void **)&ptr);
assert(!err);
if (VK_SUCCESS != err)
return;
data.mem2mapped = true;
} else {
pTableDevice->GetImageSubresourceLayout(device, data.image3, &sr, &srLayout);
err = pTableDevice->MapMemory(device, data.mem3, 0, VK_WHOLE_SIZE, 0, (void **)&ptr);
assert(!err);
if (VK_SUCCESS != err)
return;
data.mem3mapped = true;
}
{
swapchain->server.WaitForFramebufferUpdate();
swapchain->server.StartUpdate();
for (uint32_t y = 0; y < height; y++) {
const unsigned int *row = (const unsigned int *)ptr;
const char *row_data = (const char *)row;
swapchain->server.SendBytes(row_data, width * 4);
ptr += srLayout.rowPitch;
}
}
// Clean up handled by ~WritePPMCleanupData()
}
VKAPI_ATTR VkResult VKAPI_CALL CreateInstance(const VkInstanceCreateInfo *pCreateInfo,
const VkAllocationCallbacks *pAllocator,
VkInstance *pInstance) {
VkLayerInstanceCreateInfo *chain_info = get_chain_info(pCreateInfo, VK_LAYER_LINK_INFO);
assert(chain_info->u.pLayerInfo);
PFN_vkGetInstanceProcAddr fpGetInstanceProcAddr =
chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr;
assert(fpGetInstanceProcAddr);
PFN_vkCreateInstance fpCreateInstance =
(PFN_vkCreateInstance)fpGetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateInstance");
if (fpCreateInstance == NULL) {
return VK_ERROR_INITIALIZATION_FAILED;
}
// Advance the link info for the next element on the chain
chain_info->u.pLayerInfo = chain_info->u.pLayerInfo->pNext;
VkResult result = fpCreateInstance(pCreateInfo, pAllocator, pInstance);
if (result != VK_SUCCESS)
return result;
initInstanceTable(*pInstance, fpGetInstanceProcAddr);
init_screenshot();
return result;
}
// TODO hook DestroyInstance to cleanup
static void createDeviceRegisterExtensions(const VkDeviceCreateInfo *pCreateInfo, VkDevice device) {
uint32_t i;
DeviceMapStruct *devMap = get_dev_info(device);
VkLayerDispatchTable *pDisp = devMap->device_dispatch_table;
PFN_vkGetDeviceProcAddr gpa = pDisp->GetDeviceProcAddr;
pDisp->CreateSwapchainKHR = (PFN_vkCreateSwapchainKHR)gpa(device, "vkCreateSwapchainKHR");
pDisp->GetSwapchainImagesKHR =
(PFN_vkGetSwapchainImagesKHR)gpa(device, "vkGetSwapchainImagesKHR");
pDisp->AcquireNextImageKHR = (PFN_vkAcquireNextImageKHR)gpa(device, "vkAcquireNextImageKHR");
pDisp->QueuePresentKHR = (PFN_vkQueuePresentKHR)gpa(device, "vkQueuePresentKHR");
devMap->wsi_enabled = false;
for (i = 0; i < pCreateInfo->enabledExtensionCount; i++) {
if (strcmp(pCreateInfo->ppEnabledExtensionNames[i], VK_KHR_SWAPCHAIN_EXTENSION_NAME) == 0)
devMap->wsi_enabled = true;
}
}
VKAPI_ATTR VkResult VKAPI_CALL CreateDevice(VkPhysicalDevice gpu,
const VkDeviceCreateInfo *pCreateInfo,
const VkAllocationCallbacks *pAllocator,
VkDevice *pDevice) {
VkLayerDeviceCreateInfo *chain_info = get_chain_info(pCreateInfo, VK_LAYER_LINK_INFO);
assert(chain_info->u.pLayerInfo);
PFN_vkGetInstanceProcAddr fpGetInstanceProcAddr =
chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr;
PFN_vkGetDeviceProcAddr fpGetDeviceProcAddr = chain_info->u.pLayerInfo->pfnNextGetDeviceProcAddr;
VkInstance instance = physDeviceMap[gpu]->instance;
PFN_vkCreateDevice fpCreateDevice =
(PFN_vkCreateDevice)fpGetInstanceProcAddr(instance, "vkCreateDevice");
if (fpCreateDevice == NULL) {
return VK_ERROR_INITIALIZATION_FAILED;
}
// Advance the link info for the next element on the chain
chain_info->u.pLayerInfo = chain_info->u.pLayerInfo->pNext;
VkResult result = fpCreateDevice(gpu, pCreateInfo, pAllocator, pDevice);
if (result != VK_SUCCESS) {
return result;
}
assert(deviceMap.find(*pDevice) == deviceMap.end());
DeviceMapStruct *deviceMapElem = new DeviceMapStruct;
deviceMap[*pDevice] = deviceMapElem;
// Setup device dispatch table
deviceMapElem->device_dispatch_table = new VkLayerDispatchTable;
layer_init_device_dispatch_table(*pDevice, deviceMapElem->device_dispatch_table,
fpGetDeviceProcAddr);
createDeviceRegisterExtensions(pCreateInfo, *pDevice);
// Create a mapping from a device to a physicalDevice
deviceMapElem->physicalDevice = gpu;
// store the loader callback for initializing created dispatchable objects
chain_info = get_chain_info(pCreateInfo, VK_LOADER_DATA_CALLBACK);
if (chain_info) {
deviceMapElem->pfn_dev_init = chain_info->u.pfnSetDeviceLoaderData;
} else {
deviceMapElem->pfn_dev_init = NULL;
}
return result;
}
VKAPI_ATTR VkResult VKAPI_CALL EnumeratePhysicalDevices(VkInstance instance,
uint32_t *pPhysicalDeviceCount,
VkPhysicalDevice *pPhysicalDevices) {
VkResult result;
VkLayerInstanceDispatchTable *pTable = instance_dispatch_table(instance);
result = pTable->EnumeratePhysicalDevices(instance, pPhysicalDeviceCount, pPhysicalDevices);
if (result == VK_SUCCESS && *pPhysicalDeviceCount > 0 && pPhysicalDevices) {
for (uint32_t i = 0; i < *pPhysicalDeviceCount; i++) {
// Create a mapping from a physicalDevice to an instance
if (physDeviceMap[pPhysicalDevices[i]] == NULL) {
PhysDeviceMapStruct *physDeviceMapElem = new PhysDeviceMapStruct;
physDeviceMap[pPhysicalDevices[i]] = physDeviceMapElem;
}
physDeviceMap[pPhysicalDevices[i]]->instance = instance;
}
}
return result;
}
VKAPI_ATTR void VKAPI_CALL DestroyDevice(VkDevice device, const VkAllocationCallbacks *pAllocator) {
DeviceMapStruct *devMap = get_dev_info(device);
assert(devMap);
VkLayerDispatchTable *pDisp = devMap->device_dispatch_table;
pDisp->DestroyDevice(device, pAllocator);
loader_platform_thread_lock_mutex(&globalLock);
delete pDisp;
delete devMap;
deviceMap.erase(device);
loader_platform_thread_unlock_mutex(&globalLock);
}
VKAPI_ATTR void VKAPI_CALL GetDeviceQueue(VkDevice device, uint32_t queueNodeIndex,
uint32_t queueIndex, VkQueue *pQueue) {
DeviceMapStruct *devMap = get_dev_info(device);
assert(devMap);
VkLayerDispatchTable *pDisp = devMap->device_dispatch_table;
pDisp->GetDeviceQueue(device, queueNodeIndex, queueIndex, pQueue);
// Save the device queue in a map if we are taking screenshots.
loader_platform_thread_lock_mutex(&globalLock);
VkDevice que = static_cast<VkDevice>(static_cast<void *>(*pQueue));
deviceMap.emplace(que, devMap);
// Create a mapping from a device to a queue
devMap->queue = *pQueue;
loader_platform_thread_unlock_mutex(&globalLock);
}
VKAPI_ATTR VkResult VKAPI_CALL CreateCommandPool(VkDevice device,
const VkCommandPoolCreateInfo *pCreateInfo,
const VkAllocationCallbacks *pAllocator,
VkCommandPool *pCommandPool) {
DeviceMapStruct *devMap = get_dev_info(device);
assert(devMap);
VkLayerDispatchTable *pDisp = devMap->device_dispatch_table;
VkResult result = pDisp->CreateCommandPool(device, pCreateInfo, pAllocator, pCommandPool);
// Save the command pool on a map if we are taking screenshots.
loader_platform_thread_lock_mutex(&globalLock);
// Create a mapping from a device to a commandPool
devMap->commandPools.push_front(*pCommandPool);
loader_platform_thread_unlock_mutex(&globalLock);
return result;
}
VKAPI_ATTR void VKAPI_CALL DestroyCommandPool(VkDevice device, VkCommandPool commandPool,
const VkAllocationCallbacks *pAllocator) {
DeviceMapStruct *devMap = get_dev_info(device);
assert(devMap);
VkLayerDispatchTable *pDisp = devMap->device_dispatch_table;
pDisp->DestroyCommandPool(device, commandPool, pAllocator);
// Remove the command pool from the map if we are taking screenshots.
loader_platform_thread_lock_mutex(&globalLock);
// Remove the commandPool from the device mapping
devMap->commandPools.remove(commandPool);
loader_platform_thread_unlock_mutex(&globalLock);
return;
}
VKAPI_ATTR VkResult VKAPI_CALL CreateSwapchainKHR(VkDevice device,
const VkSwapchainCreateInfoKHR *pCreateInfo,
const VkAllocationCallbacks *pAllocator,
VkSwapchainKHR *pSwapchain) {
DeviceMapStruct *devMap = get_dev_info(device);
assert(devMap);
VkLayerDispatchTable *pDisp = devMap->device_dispatch_table;
// This layer does an image copy later on, and the copy command expects the
// transfer src bit to be on.
VkSwapchainCreateInfoKHR myCreateInfo = *pCreateInfo;
myCreateInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
VkResult result = pDisp->CreateSwapchainKHR(device, &myCreateInfo, pAllocator, pSwapchain);
// Save the swapchain in a map of we are taking screenshots.
loader_platform_thread_lock_mutex(&globalLock);
if (result == VK_SUCCESS) {
// Create a mapping for a swapchain to a device, image extent, and
// format
SwapchainMapStruct *swapchainMapElem = new SwapchainMapStruct;
swapchainMapElem->device = device;
swapchainMapElem->imageExtent = pCreateInfo->imageExtent;
swapchainMapElem->format = pCreateInfo->imageFormat;
swapchainMap.insert(make_pair(*pSwapchain, swapchainMapElem));
// Create a mapping for the swapchain object into the dispatch table
// TODO is this needed? screenshot_device_table_map.emplace((void
// *)pSwapchain, pTable);
}
loader_platform_thread_unlock_mutex(&globalLock);
return result;
}
VKAPI_ATTR VkResult VKAPI_CALL GetSwapchainImagesKHR(VkDevice device, VkSwapchainKHR swapchain,
uint32_t *pCount, VkImage *pSwapchainImages) {
DeviceMapStruct *devMap = get_dev_info(device);
assert(devMap);
VkLayerDispatchTable *pDisp = devMap->device_dispatch_table;
VkResult result = pDisp->GetSwapchainImagesKHR(device, swapchain, pCount, pSwapchainImages);
// Save the swapchain images in a map if we are taking screenshots
loader_platform_thread_lock_mutex(&globalLock);
if (result == VK_SUCCESS && pSwapchainImages && !swapchainMap.empty() &&
swapchainMap.find(swapchain) != swapchainMap.end()) {
unsigned i;
for (i = 0; i < *pCount; i++) {
// Create a mapping for an image to a device, image extent, and
// format
if (imageMap[pSwapchainImages[i]] == NULL) {
ImageMapStruct *imageMapElem = new ImageMapStruct;
imageMap[pSwapchainImages[i]] = imageMapElem;
}
imageMap[pSwapchainImages[i]]->device = swapchainMap[swapchain]->device;
imageMap[pSwapchainImages[i]]->imageExtent = swapchainMap[swapchain]->imageExtent;
imageMap[pSwapchainImages[i]]->format = swapchainMap[swapchain]->format;
}
// Add list of images to swapchain to image map
SwapchainMapStruct *swapchainMapElem = swapchainMap[swapchain];
if (i >= 1 && swapchainMapElem) {
VkImage *imageList = new VkImage[i];
swapchainMapElem->imageList = imageList;
for (unsigned j = 0; j < i; j++) {
swapchainMapElem->imageList[j] = pSwapchainImages[j];
}
}
}
loader_platform_thread_unlock_mutex(&globalLock);
return result;
}
VKAPI_ATTR VkResult VKAPI_CALL QueuePresentKHR(VkQueue queue,
const VkPresentInfoKHR *pPresentInfo) {
static int frameNumber = 0;
if (frameNumber == 10) {
fflush(stdout); /* *((int*)0)=0; */
}
DeviceMapStruct *devMap = get_dev_info((VkDevice)queue);
assert(devMap);
VkLayerDispatchTable *pDisp = devMap->device_dispatch_table;
VkResult result = pDisp->QueuePresentKHR(queue, pPresentInfo);
loader_platform_thread_lock_mutex(&globalLock);
if (result == VK_SUCCESS) {
VkImage image;
VkSwapchainKHR swapchain;
// We'll dump only one image: the first
swapchain = pPresentInfo->pSwapchains[0];
image = swapchainMap[swapchain]->imageList[pPresentInfo->pImageIndices[0]];
writeScreenshot(swapchainMap[swapchain], image);
}
frameNumber++;
loader_platform_thread_unlock_mutex(&globalLock);
return result;
}
static const VkLayerProperties global_layer = {
"VK_LAYER_GOOGLE_rfb",
VK_MAKE_VERSION(1, 0, VK_HEADER_VERSION),
1,
"Layer: rfb",
};
VKAPI_ATTR VkResult VKAPI_CALL EnumerateInstanceLayerProperties(uint32_t *pCount,
VkLayerProperties *pProperties) {
return util_GetLayerProperties(1, &global_layer, pCount, pProperties);
}
VKAPI_ATTR VkResult VKAPI_CALL EnumerateDeviceLayerProperties(VkPhysicalDevice physicalDevice,
uint32_t *pCount,
VkLayerProperties *pProperties) {
return util_GetLayerProperties(1, &global_layer, pCount, pProperties);
}
VKAPI_ATTR VkResult VKAPI_CALL EnumerateInstanceExtensionProperties(
const char *pLayerName, uint32_t *pCount, VkExtensionProperties *pProperties) {
if (pLayerName && !strcmp(pLayerName, global_layer.layerName))
return util_GetExtensionProperties(0, NULL, pCount, pProperties);
return VK_ERROR_LAYER_NOT_PRESENT;
}
VKAPI_ATTR VkResult VKAPI_CALL
EnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice, const char *pLayerName,
uint32_t *pCount, VkExtensionProperties *pProperties) {
if (pLayerName && !strcmp(pLayerName, global_layer.layerName))
return util_GetExtensionProperties(0, NULL, pCount, pProperties);
assert(physicalDevice);
VkLayerInstanceDispatchTable *pTable = instance_dispatch_table(physicalDevice);
return pTable->EnumerateDeviceExtensionProperties(physicalDevice, pLayerName, pCount,
pProperties);
}
static PFN_vkVoidFunction intercept_core_instance_command(const char *name);
static PFN_vkVoidFunction intercept_core_device_command(const char *name);
static PFN_vkVoidFunction intercept_khr_swapchain_command(const char *name, VkDevice dev);
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL GetDeviceProcAddr(VkDevice dev, const char *funcName) {
PFN_vkVoidFunction proc = intercept_core_device_command(funcName);
if (proc)
return proc;
if (dev == NULL) {
return NULL;
}
proc = intercept_khr_swapchain_command(funcName, dev);
if (proc)
return proc;
DeviceMapStruct *devMap = get_dev_info(dev);
assert(devMap);
VkLayerDispatchTable *pDisp = devMap->device_dispatch_table;
if (pDisp->GetDeviceProcAddr == NULL)
return NULL;
return pDisp->GetDeviceProcAddr(dev, funcName);
}
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL GetInstanceProcAddr(VkInstance instance,
const char *funcName) {
PFN_vkVoidFunction proc = intercept_core_instance_command(funcName);
if (proc)
return proc;
assert(instance);
proc = intercept_core_device_command(funcName);
if (!proc)
proc = intercept_khr_swapchain_command(funcName, VK_NULL_HANDLE);
if (proc)
return proc;
VkLayerInstanceDispatchTable *pTable = instance_dispatch_table(instance);
if (pTable->GetInstanceProcAddr == NULL)
return NULL;
return pTable->GetInstanceProcAddr(instance, funcName);
}
static PFN_vkVoidFunction intercept_core_instance_command(const char *name) {
static const struct {
const char *name;
PFN_vkVoidFunction proc;
} core_instance_commands[] = {
{"vkGetInstanceProcAddr", reinterpret_cast<PFN_vkVoidFunction>(GetInstanceProcAddr)},
{"vkCreateInstance", reinterpret_cast<PFN_vkVoidFunction>(CreateInstance)},
{"vkCreateDevice", reinterpret_cast<PFN_vkVoidFunction>(CreateDevice)},
{"vkEnumeratePhysicalDevices",
reinterpret_cast<PFN_vkVoidFunction>(EnumeratePhysicalDevices)},
{"vkEnumerateInstanceLayerProperties",
reinterpret_cast<PFN_vkVoidFunction>(EnumerateInstanceLayerProperties)},
{"vkEnumerateDeviceLayerProperties",
reinterpret_cast<PFN_vkVoidFunction>(EnumerateDeviceLayerProperties)},
{"vkEnumerateInstanceExtensionProperties",
reinterpret_cast<PFN_vkVoidFunction>(EnumerateInstanceExtensionProperties)},
{"vkEnumerateDeviceExtensionProperties",
reinterpret_cast<PFN_vkVoidFunction>(EnumerateDeviceExtensionProperties)}};
for (size_t i = 0; i < ARRAY_SIZE(core_instance_commands); i++) {
if (!strcmp(core_instance_commands[i].name, name))
return core_instance_commands[i].proc;
}
return nullptr;
}
static PFN_vkVoidFunction intercept_core_device_command(const char *name) {
static const struct {
const char *name;
PFN_vkVoidFunction proc;
} core_device_commands[] = {
{"vkGetDeviceProcAddr", reinterpret_cast<PFN_vkVoidFunction>(GetDeviceProcAddr)},
{"vkGetDeviceQueue", reinterpret_cast<PFN_vkVoidFunction>(GetDeviceQueue)},
{"vkCreateCommandPool", reinterpret_cast<PFN_vkVoidFunction>(CreateCommandPool)},
{"vkDestroyCommandPool", reinterpret_cast<PFN_vkVoidFunction>(DestroyCommandPool)},
{"vkDestroyDevice", reinterpret_cast<PFN_vkVoidFunction>(DestroyDevice)},
};
for (size_t i = 0; i < ARRAY_SIZE(core_device_commands); i++) {
if (!strcmp(core_device_commands[i].name, name))
return core_device_commands[i].proc;
}
return nullptr;
}
static PFN_vkVoidFunction intercept_khr_swapchain_command(const char *name, VkDevice dev) {
static const struct {
const char *name;
PFN_vkVoidFunction proc;
} khr_swapchain_commands[] = {
{"vkCreateSwapchainKHR", reinterpret_cast<PFN_vkVoidFunction>(CreateSwapchainKHR)},
{"vkGetSwapchainImagesKHR", reinterpret_cast<PFN_vkVoidFunction>(GetSwapchainImagesKHR)},
{"vkQueuePresentKHR", reinterpret_cast<PFN_vkVoidFunction>(QueuePresentKHR)},
};
if (dev) {
DeviceMapStruct *devMap = get_dev_info(dev);
if (!devMap->wsi_enabled)
return nullptr;
}
for (size_t i = 0; i < ARRAY_SIZE(khr_swapchain_commands); i++) {
if (!strcmp(khr_swapchain_commands[i].name, name))
return khr_swapchain_commands[i].proc;
}
return nullptr;
}
} // namespace screenshot
// loader-layer interface v0, just wrappers since there is only a layer
VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL
vkEnumerateInstanceLayerProperties(uint32_t *pCount, VkLayerProperties *pProperties) {
return screenshot::EnumerateInstanceLayerProperties(pCount, pProperties);
}
VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceLayerProperties(
VkPhysicalDevice physicalDevice, uint32_t *pCount, VkLayerProperties *pProperties) {
// the layer command handles VK_NULL_HANDLE just fine internally
assert(physicalDevice == VK_NULL_HANDLE);
return screenshot::EnumerateDeviceLayerProperties(VK_NULL_HANDLE, pCount, pProperties);
}
VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceExtensionProperties(
const char *pLayerName, uint32_t *pCount, VkExtensionProperties *pProperties) {
return screenshot::EnumerateInstanceExtensionProperties(pLayerName, pCount, pProperties);
}
VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL
vkEnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice, const char *pLayerName,
uint32_t *pCount, VkExtensionProperties *pProperties) {
// the layer command handles VK_NULL_HANDLE just fine internally
assert(physicalDevice == VK_NULL_HANDLE);
return screenshot::EnumerateDeviceExtensionProperties(VK_NULL_HANDLE, pLayerName, pCount,
pProperties);
}
VK_LAYER_EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr(VkDevice dev,
const char *funcName) {
return screenshot::GetDeviceProcAddr(dev, funcName);
}
VK_LAYER_EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL
vkGetInstanceProcAddr(VkInstance instance, const char *funcName) {
return screenshot::GetInstanceProcAddr(instance, funcName);
}