Skip to content
38 changes: 32 additions & 6 deletions cli/device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -284,18 +284,44 @@ bool VulkanDevice::init_device(const Options &opts)
if (vkEnumeratePhysicalDevices(instance, &gpu_count, gpus.data()) != VK_SUCCESS)
return false;

int selected_gpu_index = -1;
bool pci_filter_set = opts.device_pci_vendor != 0;
bool pci_filter_matched = false;
for (uint32_t i = 0; i < gpu_count; i++)
{
vkGetPhysicalDeviceProperties(gpus[i], &gpu_props);
VkPhysicalDeviceProperties gpu_enum_props;
vkGetPhysicalDeviceProperties(gpus[i], &gpu_enum_props);
LOGI("Enumerated GPU #%u:\n", i);
LOGI(" name: %s\n", gpu_props.deviceName);
LOGI(" name: %s\n", gpu_enum_props.deviceName);
LOGI(" apiVersion: %u.%u.%u\n",
VK_VERSION_MAJOR(gpu_props.apiVersion),
VK_VERSION_MINOR(gpu_props.apiVersion),
VK_VERSION_PATCH(gpu_props.apiVersion));
VK_VERSION_MAJOR(gpu_enum_props.apiVersion),
VK_VERSION_MINOR(gpu_enum_props.apiVersion),
VK_VERSION_PATCH(gpu_enum_props.apiVersion));
LOGI(" vendorID: 0x%x\n", gpu_enum_props.vendorID);
LOGI(" deviceID: 0x%x\n", gpu_enum_props.deviceID);

if (VulkanDevice::pci_filter_matches(opts,
gpu_enum_props.vendorID,
gpu_enum_props.deviceID))
{
selected_gpu_index = i;
pci_filter_matched = true;
break;
}
}

if (pci_filter_set && !pci_filter_matched)
{
LOGW("No GPU matched --device-pci-vendor 0x%x%s; falling back to default selection.\n",
opts.device_pci_vendor,
opts.device_pci_device ? " (with --device-pci-device)" : "");
}

if (opts.device_index >= 0)
if (selected_gpu_index >= 0)
{
gpu = gpus[selected_gpu_index];
}
else if (opts.device_index >= 0)
{
if (size_t(opts.device_index) >= gpus.size())
{
Expand Down
18 changes: 18 additions & 0 deletions cli/device.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ class VulkanDevice : public DeviceQueryInterface
bool null_device = false;
bool want_pipeline_stats = false;
int device_index = -1;
unsigned device_pci_vendor = 0;
unsigned device_pci_device = 0;
const VkApplicationInfo *application_info = nullptr;
const VkPhysicalDeviceFeatures2 *features = nullptr;
};
Expand Down Expand Up @@ -111,6 +113,22 @@ class VulkanDevice : public DeviceQueryInterface
return gpu_props;
}

// Pure predicate used both by init_device's selection loop and the
// gpu_selection_test. Keeps PCI filter semantics in a single place so
// the CLI and the test cannot drift apart.
static inline bool pci_filter_matches(const Options &opts,
uint32_t vendor_id,
uint32_t device_id)
{
if (opts.device_pci_vendor == 0)
return false;
if (vendor_id != opts.device_pci_vendor)
return false;
if (opts.device_pci_device != 0 && device_id != opts.device_pci_device)
return false;
return true;
}

// Special helper which deals with SAMPLER_YCBCR_CONVERSION_CREATE_INFO.
// Prefer using this instead of vkCreateSampler directly.
// The relevant pNext will be mutated into CONVERSION_INFO in-place if it exists.
Expand Down
8 changes: 8 additions & 0 deletions cli/fossilize_replay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3637,6 +3637,8 @@ static void print_help()
LOGI("fossilize-replay\n"
"\t[--help]\n"
"\t[--device-index <index>]\n"
"\t[--device-pci-vendor <vendorID_hex>]\n"
"\t[--device-pci-device <deviceID_hex>]\n"
"\t[--enable-validation]\n"
"\t[--enable-pipeline-stats <path>]\n"
"\t[--spirv-val]\n"
Expand Down Expand Up @@ -4570,6 +4572,12 @@ int main(int argc, char *argv[])
cbs.default_handler = [&](const char *arg) { databases.push_back(arg); };
cbs.add("--help", [](CLIParser &parser) { print_help(); parser.end(); });
cbs.add("--device-index", [&](CLIParser &parser) { opts.device_index = parser.next_uint(); });
cbs.add("--device-pci-vendor", [&](CLIParser &parser) {
opts.device_pci_vendor = strtoul(parser.next_string(), nullptr, 0);
});
cbs.add("--device-pci-device", [&](CLIParser &parser) {
opts.device_pci_device = strtoul(parser.next_string(), nullptr, 0);
});
cbs.add("--enable-validation", [&](CLIParser &) { opts.enable_validation = true; });
cbs.add("--spirv-val", [&](CLIParser &) { replayer_opts.spirv_validate = true; });
cbs.add("--on-disk-pipeline-cache", [&](CLIParser &parser) { replayer_opts.on_disk_pipeline_cache_path = parser.next_string(); });
Expand Down
11 changes: 11 additions & 0 deletions cli/fossilize_replay_linux.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include <sys/timerfd.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <fcntl.h>
#include <limits.h>
#include <errno.h>
Expand Down Expand Up @@ -524,6 +525,7 @@ bool ProcessProgress::start_child_process(vector<ProcessProgress> &siblings)
if (pipe(input_fds) < 0)
return false;

pid_t parent_pid = getpid();
pid_t new_pid = fork(); // Fork off a child.
if (new_pid > 0)
{
Expand Down Expand Up @@ -560,6 +562,15 @@ bool ProcessProgress::start_child_process(vector<ProcessProgress> &siblings)
close(Global::control_fd);

// We're the child process.
// Kill the child immediately if the parent (fossilize_replay / steam) dies.
if (prctl(PR_SET_PDEATHSIG, SIGKILL) != 0)
{
LOGE("Failed to set PR_SET_PDEATHSIG, aborting child startup.\n");
_exit(EXIT_FAILURE);
}
if (getppid() != parent_pid)
_exit(EXIT_FAILURE);

// Unblock the signal mask.
if (pthread_sigmask(SIG_SETMASK, &Global::old_mask, nullptr) != 0)
return EXIT_FAILURE;
Expand Down
4 changes: 4 additions & 0 deletions fossilize_external_replayer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ class ExternalReplayer
// Maps to --device-index.
unsigned device_index;

// Maps to --device-pci-vendor and --device-pci-device.
unsigned device_pci_vendor;
unsigned device_pci_device;

// Carve out a range of which pipelines to replay if use_pipeline_range is set.
// Used for multi-process replays where each process gets its own slice to churn through.
unsigned start_graphics_index;
Expand Down
26 changes: 26 additions & 0 deletions fossilize_external_replayer_linux.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/prctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <sched.h>
Expand Down Expand Up @@ -704,6 +705,21 @@ void ExternalReplayer::Impl::start_replayer_process(const ExternalReplayer::Opti
sprintf(index_name, "%u", options.device_index);
argv.push_back(index_name);

char pci_vendor_name[16], pci_device_name[16];
if (options.device_pci_vendor != 0)
{
argv.push_back("--device-pci-vendor");
snprintf(pci_vendor_name, sizeof(pci_vendor_name), "0x%x", options.device_pci_vendor);
argv.push_back(pci_vendor_name);
}

if (options.device_pci_device != 0)
{
argv.push_back("--device-pci-device");
snprintf(pci_device_name, sizeof(pci_device_name), "0x%x", options.device_pci_device);
argv.push_back(pci_device_name);
}

char graphics_range_start[16], graphics_range_end[16];
char compute_range_start[16], compute_range_end[16];
char raytracing_range_start[16], raytracing_range_end[16];
Expand Down Expand Up @@ -948,6 +964,7 @@ bool ExternalReplayer::Impl::start(const ExternalReplayer::Options &options)
if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, control_fds) < 0)
return false;

pid_t parent_pid = getpid();
pid_t new_pid = fork();
if (new_pid > 0)
{
Expand All @@ -966,6 +983,15 @@ bool ExternalReplayer::Impl::start(const ExternalReplayer::Options &options)
}
else if (new_pid == 0)
{
// Kill replayer process immediately if parent application dies.
if (prctl(PR_SET_PDEATHSIG, SIGKILL) != 0)
{
LOGE("Failed to set PR_SET_PDEATHSIG, aborting child startup.\n");
_exit(1);
}
if (getppid() != parent_pid)
_exit(1);

close(fds[0]);
close(control_fds[1]);
close(child_fds[0]);
Expand Down
13 changes: 13 additions & 0 deletions fossilize_external_replayer_windows.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,19 @@ bool ExternalReplayer::Impl::start(const ExternalReplayer::Options &options)
cmdline += " --device-index ";
cmdline += std::to_string(options.device_index);

char hex_buf[32];
if (options.device_pci_vendor != 0)
{
snprintf(hex_buf, sizeof(hex_buf), " --device-pci-vendor 0x%x", options.device_pci_vendor);
cmdline += hex_buf;
}

if (options.device_pci_device != 0)
{
snprintf(hex_buf, sizeof(hex_buf), " --device-pci-device 0x%x", options.device_pci_device);
cmdline += hex_buf;
}

if (options.enable_validation)
cmdline += " --enable-validation";

Expand Down
6 changes: 6 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ if (NOT WIN32)
add_test(NAME spinlock-futex-test COMMAND futex-test)
else()
add_test(NAME linux-futex-test COMMAND futex-test)
add_executable(orphan-prevention-test orphan_prevention_test.cpp)
target_link_libraries(orphan-prevention-test fossilize)
add_test(NAME orphan-prevention-test COMMAND orphan-prevention-test)
add_executable(gpu-selection-test gpu_selection_test.cpp)
target_link_libraries(gpu-selection-test cli-utils fossilize)
add_test(NAME gpu-selection-test COMMAND gpu-selection-test)
endif()
endif()

113 changes: 113 additions & 0 deletions test/gpu_selection_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#include "cli/device.hpp"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

using namespace Fossilize;

// Synthetic GPU catalogue. Real systems can have multiple iGPUs + dGPUs,
// so the selection loop must tolerate mixed vendor IDs and not assume
// the matching GPU is at index 0.
struct FakeGpu
{
uint32_t vendor_id;
uint32_t device_id;
const char *name;
};

static const FakeGpu kCatalogue[] = {
{ 0x10de, 0x2488, "NVIDIA RTX 3070" },
{ 0x1002, 0x73bf, "AMD Radeon RX 6700 XT" },
{ 0x8086, 0x4907, "Intel UHD 770" },
};

static int select_first_match(VulkanDevice::Options opts)
{
for (size_t i = 0; i < sizeof(kCatalogue) / sizeof(kCatalogue[0]); i++)
{
if (VulkanDevice::pci_filter_matches(opts,
kCatalogue[i].vendor_id,
kCatalogue[i].device_id))
return int(i);
}
return -1;
}

int main()
{
printf("[TEST] Testing GPU PCI filter selection logic...\n");

// Case 1: vendor-only filter matches the first NVIDIA dGPU.
{
VulkanDevice::Options opts = {};
opts.device_pci_vendor = 0x10de;
int idx = select_first_match(opts);
printf(" vendor=0x10de -> index %d (%s)\n", idx,
idx >= 0 ? kCatalogue[idx].name : "(no match)");
assert(idx == 0);
}

// Case 2: vendor+device matches NVIDIA RTX 3070 exactly.
{
VulkanDevice::Options opts = {};
opts.device_pci_vendor = 0x10de;
opts.device_pci_device = 0x2488;
int idx = select_first_match(opts);
printf(" vendor=0x10de device=0x2488 -> index %d (%s)\n", idx,
idx >= 0 ? kCatalogue[idx].name : "(no match)");
assert(idx == 0);
}

// Case 3: vendor matches but specific device does not.
{
VulkanDevice::Options opts = {};
opts.device_pci_vendor = 0x10de;
opts.device_pci_device = 0x9999;
int idx = select_first_match(opts);
printf(" vendor=0x10de device=0x9999 -> index %d\n", idx);
assert(idx == -1);
}

// Case 4: no filter (vendor == 0) means "do not filter".
{
VulkanDevice::Options opts = {};
int idx = select_first_match(opts);
printf(" no filter -> index %d\n", idx);
assert(idx == -1);
}

// Case 5: vendor-only filter selects AMD in the middle slot.
{
VulkanDevice::Options opts = {};
opts.device_pci_vendor = 0x1002;
int idx = select_first_match(opts);
printf(" vendor=0x1002 -> index %d (%s)\n", idx,
idx >= 0 ? kCatalogue[idx].name : "(no match)");
assert(idx == 1);
}

// Case 6: vendor+device matches the last entry (Intel iGPU).
{
VulkanDevice::Options opts = {};
opts.device_pci_vendor = 0x8086;
opts.device_pci_device = 0x4907;
int idx = select_first_match(opts);
printf(" vendor=0x8086 device=0x4907 -> index %d (%s)\n", idx,
idx >= 0 ? kCatalogue[idx].name : "(no match)");
assert(idx == 2);
}

// Case 7: AMD vendor but with NVIDIA device id -- must NOT match.
{
VulkanDevice::Options opts = {};
opts.device_pci_vendor = 0x1002;
opts.device_pci_device = 0x2488;
int idx = select_first_match(opts);
printf(" vendor=0x1002 device=0x2488 -> index %d\n", idx);
assert(idx == -1);
}

printf("[TEST] SUCCESS: GPU PCI filter selection logic verified.\n");
return 0;
}
Loading