Skip to content

Commit

Permalink
test: External memory FD
Browse files Browse the repository at this point in the history
Actually two apps. One app acts as exporter, exporting memory to an
opaque fd containing data about its color render target, while the
other app acts as importer. The exporter waits for the importer to
connect via UNIX domain socket and then it sends the opaque fd. The
importer connects to the exporter and retrieves the fd to use for
importing memory.
  • Loading branch information
antonio-lunarg committed Dec 12, 2024
1 parent 9943cf4 commit eb82b6b
Show file tree
Hide file tree
Showing 6 changed files with 728 additions and 0 deletions.
1 change: 1 addition & 0 deletions test/test_apps/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ add_subdirectory(multisample-depth)
add_subdirectory(pipeline-binaries)
add_subdirectory(host-image-copy)
add_subdirectory(shader-objects)
add_subdirectory(external-memory-fd)
28 changes: 28 additions & 0 deletions test/test_apps/external-memory-fd/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
###############################################################################
# Copyright (c) 2024 LunarG, Inc.
# All rights reserved
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
#
# Author: LunarG Team
# Description: CMake script for VK_KHR_external_memory_fd tests
###############################################################################

add_subdirectory(export)
add_subdirectory(import)
71 changes: 71 additions & 0 deletions test/test_apps/external-memory-fd/export/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
###############################################################################
# Copyright (c) 2024 LunarG, Inc.
# All rights reserved
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
#
# Author: LunarG Team
# Description: CMake script for VK_KHR_external_memory_fd export test app
###############################################################################

add_executable(gfxrecon-external-memory-fd-export "")

target_sources(gfxrecon-external-memory-fd-export
PRIVATE
${CMAKE_CURRENT_LIST_DIR}/app.cpp
${CMAKE_CURRENT_LIST_DIR}/../../common/test_app_base.cpp)

target_include_directories(gfxrecon-external-memory-fd-export PUBLIC
${CMAKE_BINARY_DIR}
${CMAKE_CURRENT_LIST_DIR}/../../common)

target_link_libraries(gfxrecon-external-memory-fd-export
gfxrecon_application
gfxrecon_decode
gfxrecon_graphics
gfxrecon_format
gfxrecon_util
SDL3::SDL3
platform_specific)

if (MSVC)
# Force inclusion of "gfxrecon_disable_popup_result" variable in linking.
# On 32-bit windows, MSVC prefixes symbols with "_" but on 64-bit windows it doesn't.
if(CMAKE_SIZEOF_VOID_P EQUAL 4)
target_link_options(gfxrecon-replay PUBLIC "LINKER:/Include:_gfxrecon_disable_popup_result")
else()
target_link_options(gfxrecon-replay PUBLIC "LINKER:/Include:gfxrecon_disable_popup_result")
endif()
endif()

common_build_directives(gfxrecon-external-memory-fd-export)

add_custom_command(
TARGET gfxrecon-external-memory-fd-export
POST_BUILD
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_BUILD_TYPE})
if (WIN32)
add_custom_command(TARGET gfxrecon-external-memory-fd-export POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_RUNTIME_DLLS:gfxrecon-external-memory-fd-export> $<TARGET_FILE_DIR:gfxrecon-external-memory-fd-export>
COMMAND_EXPAND_LISTS)
endif ()

install(TARGETS gfxrecon-external-memory-fd-export RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})

add_dependencies(gfxrecon-testapps gfxrecon-external-memory-fd-export)
274 changes: 274 additions & 0 deletions test/test_apps/external-memory-fd/export/app.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
/*
** Copyright (c) 2024 LunarG, Inc.
**
** Permission is hereby granted, free of charge, to any person obtaining a
** copy of this software and associated documentation files (the "Software"),
** to deal in the Software without restriction, including without limitation
** the rights to use, copy, modify, merge, publish, distribute, sublicense,
** and/or sell copies of the Software, and to permit persons to whom the
** Software is furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in
** all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
** DEALINGS IN THE SOFTWARE.
*/

#include <fcntl.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

#include <iostream>

#include <vulkan/vulkan_core.h>

#include <util/logging.h>

#include <test_app_base.h>

#ifdef __linux__
#define HAVE_MSGHDR_MSG_CONTROL
#endif

GFXRECON_BEGIN_NAMESPACE(gfxrecon)

GFXRECON_BEGIN_NAMESPACE(test_app)

GFXRECON_BEGIN_NAMESPACE(external_memory_fd_export)

class App : public gfxrecon::test::TestAppBase
{
public:
App() = default;
virtual ~App() = default;
App(const App&) = delete;
App& operator=(const App&) = delete;
App(App&&) = delete;
App& operator=(App&&) = delete;

private:
const uint32_t buffer_size_ = sizeof(uint32_t[42]);
VkBuffer buffer_ = VK_NULL_HANDLE;
VkDeviceMemory exportable_memory_ = VK_NULL_HANDLE;

void configure_instance_builder(test::InstanceBuilder& instance_builder) override;
void configure_physical_device_selector(test::PhysicalDeviceSelector& phys_device_selector) override;

uint32_t find_memory_type(uint32_t memoryTypeBits, VkMemoryPropertyFlags memory_property_flags);
void create_buffer();

int get_exportable_fd();
void send_exportable_fd(int exportable_fd);
ssize_t send_int(int conn_fd, int data);

void cleanup() override;
bool frame(const int frame_num) override;
void setup() override;
};

void App::configure_instance_builder(test::InstanceBuilder& instance_builder)
{
instance_builder.enable_extension(VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME);
instance_builder.set_headless(true);
}

void App::configure_physical_device_selector(test::PhysicalDeviceSelector& phys_device_selector)
{
phys_device_selector.add_required_extension(VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME);
phys_device_selector.add_required_extension(VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME);
}

uint32_t App::find_memory_type(uint32_t memoryTypeBits, VkMemoryPropertyFlags memory_property_flags)
{
VkPhysicalDeviceMemoryProperties memory_properties;
init.inst_disp.getPhysicalDeviceMemoryProperties(init.physical_device, &memory_properties);

for (uint32_t i = 0; i < memory_properties.memoryTypeCount; ++i)
{
if ((memoryTypeBits & (1 << i)) && (memory_properties.memoryTypes[i].propertyFlags & memory_property_flags) > 0)
{
return i;
break;
}
}

throw std::runtime_error("Could not find required memory type");
}

void App::create_buffer()
{
VkExternalMemoryBufferCreateInfo external_mem_buf_create_info = {};
external_mem_buf_create_info.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO;
external_mem_buf_create_info.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;

VkBufferCreateInfo buffer_create_info = {};
buffer_create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
buffer_create_info.pNext = &external_mem_buf_create_info;
buffer_create_info.flags = 0u;
buffer_create_info.size = buffer_size_;
buffer_create_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
buffer_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
buffer_create_info.queueFamilyIndexCount = 0u;
buffer_create_info.pQueueFamilyIndices = nullptr;
VkResult result = init.disp.createBuffer(&buffer_create_info, nullptr, &buffer_);
VERIFY_VK_RESULT("Failed to create buffer", result);

VkMemoryRequirements buf_mem_requirements;
init.disp.getBufferMemoryRequirements(buffer_, &buf_mem_requirements);

VkExportMemoryAllocateInfo export_mem_alloc_info = {};
export_mem_alloc_info.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO;
export_mem_alloc_info.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;

VkMemoryAllocateInfo buf_mem_allocate_info;
buf_mem_allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
buf_mem_allocate_info.pNext = &export_mem_alloc_info;
buf_mem_allocate_info.allocationSize = buf_mem_requirements.size;
buf_mem_allocate_info.memoryTypeIndex =
find_memory_type(buf_mem_requirements.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
result = init.disp.allocateMemory(&buf_mem_allocate_info, nullptr, &exportable_memory_);
VERIFY_VK_RESULT("Failed to exportable memory", result);

result = init.disp.bindBufferMemory(buffer_, exportable_memory_, 0u);
VERIFY_VK_RESULT("Failed to bind memory", result);

uint32_t* data = nullptr;
result = init.disp.mapMemory(exportable_memory_, 0u, buffer_size_, 0u, reinterpret_cast<void**>(&data));
VERIFY_VK_RESULT("Failed to map buffer memory", result);
for (uint32_t i = 0; i < buffer_size_ / sizeof(uint32_t); ++i)
{
data[i] = i;
}

init.disp.unmapMemory(exportable_memory_);
}

int App::get_exportable_fd()
{
VkMemoryGetFdInfoKHR get_fd_info = {};
get_fd_info.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR;
get_fd_info.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
get_fd_info.memory = exportable_memory_;

int exportable_fd = -1;
VkResult result = init.disp.getMemoryFdKHR(&get_fd_info, &exportable_fd);
VERIFY_VK_RESULT("Failed to get memory fd", result);
return exportable_fd;
}

void App::send_exportable_fd(int exportable_fd)
{
// Need to send the fd to the importer process
int external_socket = socket(PF_UNIX, SOCK_STREAM, 0);
GFXRECON_ASSERT(external_socket >= 0)
sockaddr_un un = {};
un.sun_family = AF_UNIX;

const char* socket_name = "/tmp/.external-memory";
snprintf(un.sun_path, sizeof(un.sun_path), "%s", socket_name);
unlink(un.sun_path);

int bind_result = bind(external_socket, reinterpret_cast<struct sockaddr*>(&un), sizeof(un));
GFXRECON_ASSERT(bind_result >= 0)

int listen_result = listen(external_socket, 1);
GFXRECON_ASSERT(listen_result >= 0)

GFXRECON_LOG_INFO("Waiting for importer to connect");
// Blocking
int conn_fd = accept(external_socket, nullptr, nullptr);
GFXRECON_ASSERT(conn_fd >= 0)

// Send fd
ssize_t send_result = send_int(conn_fd, exportable_fd);
GFXRECON_ASSERT(send_result >= 0);

close(conn_fd);
close(external_socket);
}

ssize_t App::send_int(int conn_fd, int data)
{
struct msghdr msg = {};
struct iovec iov[1];

#ifdef HAVE_MSGHDR_MSG_CONTROL
union
{
struct cmsghdr cm;
char control[CMSG_SPACE(sizeof(int))];
} control_un = {};

msg.msg_control = control_un.control;
msg.msg_controllen = sizeof(control_un.control);

struct cmsghdr* cmptr = CMSG_FIRSTHDR(&msg);
cmptr->cmsg_len = CMSG_LEN(sizeof(int));
cmptr->cmsg_level = SOL_SOCKET;
cmptr->cmsg_type = SCM_RIGHTS;
*reinterpret_cast<int*>(CMSG_DATA(cmptr)) = data;
#else
msg.msg_accrights = (caddr_t)&data;
msg.msg_accrightslen = sizeof(int);
#endif

msg.msg_name = NULL;
msg.msg_namelen = 0;

char c[] = "1";
iov[0].iov_base = c;
iov[0].iov_len = 1;
msg.msg_iov = iov;
msg.msg_iovlen = 1;

return sendmsg(conn_fd, &msg, 0);
}

bool App::frame(const int frame_num)
{
return false;
}

void App::cleanup()
{
init.disp.destroyBuffer(buffer_, nullptr);
init.disp.freeMemory(exportable_memory_, nullptr);
}

void App::setup()
{
create_buffer();
int fd = get_exportable_fd();
GFXRECON_LOG_INFO("Exporting fd (%d)", fd);
send_exportable_fd(fd);
GFXRECON_LOG_INFO("Bye");
}

GFXRECON_END_NAMESPACE(external_memory_fd_export)

GFXRECON_END_NAMESPACE(test_app)

GFXRECON_END_NAMESPACE(gfxrecon)

int main(int argc, char* argv[])
{
try
{
gfxrecon::test_app::external_memory_fd_export::App app;
app.run("external memory fd export");
return 0;
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
return -1;
}
}
Loading

0 comments on commit eb82b6b

Please sign in to comment.