Skip to content
Snippets Groups Projects
Commit 7fc7b670 authored by Natalie Vock's avatar Natalie Vock
Browse files

Initial commit

parents
No related branches found
No related tags found
No related merge requests found
Showing with 1368 additions and 0 deletions
IndentWidth: 3
ColumnLimit: 120
Language: Cpp
Standard: c++20
DerivePointerAlignment: false
PointerAlignment: Left
SpaceBeforeCtorInitializerColon: True
AllowShortCaseLabelsOnASingleLine: True
AllowShortFunctionsOnASingleLine: InlineOnly
AllowShortBlocksOnASingleLine: Empty
AllowShortLambdasOnASingleLine: All
BraceWrapping:
AfterControlStatement: false
AfterEnum: false
AfterFunction: true
AfterStruct: false
BeforeElse: false
SplitEmptyFunction: false
SplitEmptyRecord: false
BeforeLambdaBody: true
[submodule "external/volk"]
path = external/volk
url = https://github.com/zeux/volk.git
# Hang Test Suite
This is a collection of Linux programs to trigger GPU hangs in various ways (primarily targeting AMD GPUs) to test the
driver stack's ability to recover from such hangs.
# Building and installing
`glslangValidator` is required as a build-time dependency. It is included in the Vulkan SDK.
Use
```shell
git clone --recursive https://gitlab.steamos.cloud/holo/HangTestSuite.git
```
or
```shell
git submodule update --init
```
after cloning. Then, use Meson to compile:
```shell
meson setup build
ninja -C build
# optionally, to directly install to /usr:
cd build
meson install
```
The install directory can be changed by changing the setup command to `meson setup -Dprefix=<install_dir> build`.
# Usage
The test suite can be run either by installing using `meson install`, or directly from the build directory.
Launching the runner executable `hangtest` without any arguments will start a test run that launches each test case
once.
The test suite will always run on the first reported Vulkan physical device. To select a different physical device,
use
[the `MESA_VK_DEVICE_SELECT` environment variable](https://docs.mesa3d.org/envvars.html?highlight=mesa_vk_device_select#envvar-MESA_VK_DEVICE_SELECT).
For debugging convenience, the executables triggering the different hang conditions can also be run standalone. Each
test corresponds to one executable with the same name.
Note: Running the test suite from inside a graphical desktop is dangerous. If resets don't work well, the session might
get killed, which in turn also kills the test suite. If possible, run the test suite from a tty or an ssh session.
## Options
#### `--stress`
To run tests in the stress-test mode, use the `--stress` option. Stress-test mode will try to hang the GPU in a loop,
while also submitting non-hanging GPU work in parallel. Stress-test runs do not terminate on their own unless there is
a failure; to end a stress test run, interrupt the process using `Ctrl+C` on the command line.
#### `--test-filter`
To limit the test run to specific tests, use `--test-filter`. Only tests whose names contain the filter string will be
executed.
Example: `--test-filter soft_recovery` will execute the tests `soft_recovery_loop` and `soft_recovery_pagefault`,
whereas `--test-filter loop` will only execute `soft_recovery_loop`.
#### `--list-tests`
Use `--list-tests` to output the names of all available tests.
#### `--test-dir` and `--dummy`
The test suite assumes that all test executables as well as the dummy executable are located in the same directory
as the runner executable (`hangtest`).
# Architecture
The test suite consists of three main components: The runner, the tests, and the dummy.\
The runner is responsible for launching tests/dummy processes, communicating with them, and aggregating test results.
Each test simulates one failure case causing a GPU to hang (for example an infinite loop in a shader). After submitting
the hanging work, the test checks if subsequent commands correctly return `VK_ERROR_DEVICE_LOST` to signal the GPU hang.
The dummy process's purpose is checking if the GPU recovery caused device loss in unrelated contexts. The dummy process
creates a Vulkan device before the GPU hangs, and after recovery has completed, the dummy process attempts to submit
work to that device. It is expected that this succeeds for a successful hang recovery.
Both the test and the dummy process communicate test results to the runner via exit codes. An exit code of 0 means
success, anything else means failure.
In order for a test to be considered successful, both the test and the dummy executable need to exit with code 0.
### Adding tests
Tests should always be named with a prefix of either `soft_recovery_` or `hard_reset_`, depending on what type of reset
they aim to trigger.
To add a new test,
- write a snippet of Vulkan code triggering the hang and save it as `<testname>.cpp` in `src/tests`
- add `'testname'` to the `tests` array in `src/meson.build`
- add `"testname"` to the `TEST_CASES` array in `src/runner.cpp`
After submitting work that triggers the hang, the test should check whether subsequent commands like `vkWaitForFences`
or the next `vkQueueSubmit` return `VK_ERROR_DEVICE_LOST`. If device loss is reported properly, the test should exit
with return code `0`, otherwise with a nonzero code.
volk_inc = include_directories('volk')
volk_lib = static_library('volk', 'volk/volk.c', include_directories: volk_inc)
volk_dep = declare_dependency(link_with: volk_lib, include_directories: volk_inc)
\ No newline at end of file
Subproject commit f2a16e3e19c2349b873343b2dc38a1d4c25af23a
/*
* Copyright © 2023 Valve Corporation
*
* 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 (including the next
* paragraph) 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.
*/
#ifndef VULKAN_DEVICE_HPP
#define VULKAN_DEVICE_HPP
#define VK_NO_PROTOTYPES
#include <cstdio>
#include <cstdlib>
#include <volk.h>
#include <vulkan/vulkan.h>
#define CHECK_VKRESULT(expr) \
do { \
VkResult _result = expr; \
if (_result != VK_SUCCESS) { \
fprintf(stderr, #expr ": result %d!\n", _result); \
abort(); \
} \
} while (0);
enum class QueueType {
GFX,
ACE,
};
struct Buffer {
VkBuffer buffer;
VkDeviceMemory memory;
VkDeviceAddress va;
void* hostMap;
};
struct CommandBuffer {
VkCommandPool pool;
VkCommandBuffer buffer;
};
struct Pipeline {
VkPipeline pipeline;
VkPipelineLayout layout;
};
class VulkanDevice final {
public:
VulkanDevice();
VulkanDevice(const VulkanDevice&) = delete;
VulkanDevice& operator=(const VulkanDevice&) = delete;
/* TODO: move constructors are implementable but we shouldn't need them */
VulkanDevice(VulkanDevice&&) = delete;
VulkanDevice& operator=(VulkanDevice&&) = delete;
~VulkanDevice();
Buffer createBuffer(VkDeviceSize size);
void destroyBuffer(Buffer& buffer);
CommandBuffer createCommandBuffer(QueueType type);
void destroyCommandBuffer(CommandBuffer& commandBuffer);
Pipeline createPipeline(uint32_t *spv, uint32_t spvSize, uint32_t pushConstantSize);
void destroyPipeline(Pipeline& pipeline);
VkFence createFence();
void destroyFence(VkFence fence);
VkDevice device() { return m_device; }
VkQueue gfxQueue() { return m_gfxQueue; }
VkQueue aceQueue() { return m_aceQueue; }
const VkPhysicalDeviceProperties& properties() const { return m_properties; }
private:
VkInstance m_instance;
VkPhysicalDevice m_physDevice;
VkPhysicalDeviceProperties m_properties;
uint32_t m_memTypeIdx;
VkDevice m_device;
uint32_t m_gfxFamilyIdx;
VkQueue m_gfxQueue;
uint32_t m_aceFamilyIdx;
VkQueue m_aceQueue;
};
#endif /* VULKAN_DEVICE_HPP */
project('HangTestSuite', ['c', 'cpp'], version : '1.0.0', meson_version : '>= 0.49', default_options : [
'cpp_std=c++20'
])
glslang_program = find_program('glslangValidator', required : true)
framework_includes = include_directories([ './include' ])
add_project_arguments(
meson.get_compiler('cpp').get_supported_arguments(
[
'-Wno-missing-field-initializers' # Missing field initializers are zeroed, this is intentionally used
]), language: 'cpp')
subdir('external')
subdir('src')
\ No newline at end of file
/*
* Copyright © 2023 Valve Corporation
*
* 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 (including the next
* paragraph) 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 "vulkan_device.hpp"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
uint32_t prefix_sum_spv[] = {
#include <shaders/prefix_sum.comp.spv.h>
};
int main(int argc, char** argv) {
if (argc < 2)
return 1;
bool stress = false;
if (!strcmp(argv[1], "--stress"))
stress = true;
int pipeFDs[2] = {};
if (argc >= 3) {
pipeFDs[0] = atoi(argv[1]);
pipeFDs[1] = atoi(argv[2]);
}
if ((!pipeFDs[0] || !pipeFDs[1]) && !stress)
return 1;
VulkanDevice device;
Buffer dummyBuf = device.createBuffer(65536 * sizeof(uint32_t));
memset(dummyBuf.hostMap, 0, 65536 * sizeof(uint32_t));
Pipeline prefixPipeline = device.createPipeline(prefix_sum_spv, sizeof(prefix_sum_spv), 8);
auto commandBuffer = device.createCommandBuffer(QueueType::ACE);
auto fence = device.createFence();
VkCommandBufferBeginInfo beginInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
};
vkBeginCommandBuffer(commandBuffer.buffer, &beginInfo);
vkCmdBindPipeline(commandBuffer.buffer, VK_PIPELINE_BIND_POINT_COMPUTE, prefixPipeline.pipeline);
vkCmdPushConstants(commandBuffer.buffer, prefixPipeline.layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, 8, &dummyBuf.va);
vkCmdDispatch(commandBuffer.buffer, 1024, 0, 0);
CHECK_VKRESULT(vkEndCommandBuffer(commandBuffer.buffer));
if (!stress) {
/* Signal to parent process that device initialization is done, then wait for the parent process */
char data = 0;
/* Since we only use one pipe for communication, we could read our own written data
* instead of waiting for the parent write. Check if what we read was our own write and
* try again if it was. */
do {
write(pipeFDs[1], &data, 1);
usleep(1000);
read(pipeFDs[0], &data, 1);
} while (data == 0);
}
VkResult result;
do {
VkSubmitInfo submitInfo = {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.commandBufferCount = 1,
.pCommandBuffers = &commandBuffer.buffer,
};
result = vkQueueSubmit(device.aceQueue(), 1, &submitInfo, fence);
if (result != VK_ERROR_DEVICE_LOST) {
CHECK_VKRESULT(result);
result = vkWaitForFences(device.device(), 1, &fence, VK_TRUE, UINT64_MAX);
}
} while (stress && result == VK_SUCCESS);
device.destroyPipeline(prefixPipeline);
device.destroyCommandBuffer(commandBuffer);
device.destroyBuffer(dummyBuf);
device.destroyFence(fence);
if (result != VK_SUCCESS)
fprintf(stderr, "failure in dummy\n");
close(pipeFDs[0]);
close(pipeFDs[1]);
return result == VK_SUCCESS ? 0 : 1;
}
\ No newline at end of file
framework_src = [ 'vulkan_device.cpp' ]
framework_lib = static_library('common_framework', framework_src,
dependencies : volk_dep,
include_directories : framework_includes)
framework_dep = declare_dependency(link_with: framework_lib, include_directories: framework_includes)
subdir('shaders')
tests = [
'soft_recovery_loop',
'soft_recovery_pagefault',
'hard_reset_cp_wait',
'hard_reset_dma_use_after_free',
]
foreach test : tests
executable(test, 'tests/' + test + '.cpp', shaders_spv,
dependencies: [ framework_dep, volk_dep ], install: true)
endforeach
executable('dummy', 'dummy.cpp', shaders_spv,
dependencies: [ framework_dep, volk_dep ], install: true)
executable('hangtest', 'runner.cpp',
dependencies: [ framework_dep, volk_dep ], install: true)
\ No newline at end of file
/*
* Copyright © 2023 Valve Corporation
*
* 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 (including the next
* paragraph) 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 <cstdio>
#include <cstdlib>
#include <cstring>
#include <getopt.h>
#include <poll.h>
#include <string>
#include <wait.h>
#include <libgen.h>
#include <vulkan_device.hpp>
static std::string TEST_CASES[] = {"soft_recovery_loop", "soft_recovery_pagefault", "hard_reset_cp_wait",
"hard_reset_dma_use_after_free"};
constexpr option options[] = {{"help", no_argument, nullptr, 0},
{"list-tests", no_argument, nullptr, 0},
{"test-dir", required_argument, nullptr, 0},
{"dummy", required_argument, nullptr, 0},
{"test-filter", required_argument, nullptr, 0},
{"stress", no_argument, nullptr, 0}};
bool runTest(const std::string& testName, const std::string& testDir, const std::string& dummyPath);
void runStress(const std::string& testDir, const std::string& dummyPath, const std::string& testFilter);
int main(int argc, char** argv) {
char exec_path[512];
readlink("/proc/self/exe", exec_path, 512);
dirname(exec_path);
std::string testDir = std::string(exec_path) + "/";
std::string dummyPath = std::string(exec_path) + "/dummy";
std::string testFilter = "";
bool stress = false;
int optIdx;
while (getopt_long(argc, argv, "", options, &optIdx) != -1) {
switch (optIdx) {
case 0:
printf(
"Usage: hangtest [--help] [--test-dir <directory>] [--dummy <exe>] [--test-filter <filter>] [--stress]\n"
"\n"
"Options summary:\n"
"--help show this message\n"
"--test-dir <directory> override search directory for test executables\n"
"--dummy <exe> override executable for dummy subprocess\n"
"--test-filter <filter> only execute tests containing filter string\n"
"--stress stress test mode: dummy process will continuously submit GPU work in parallel "
"to the tests\n");
return 0;
case 1:
for (const auto& test : TEST_CASES)
printf("%s\n", test.c_str());
return 0;
case 2: testDir = std::string(optarg) + "/"; break;
case 3: dummyPath = optarg; break;
case 4: testFilter = optarg; break;
case 5: stress = true; break;
default: fprintf(stderr, "See --help for a list of options.\n");
}
}
bool success = true;
bool is_dgpu;
{
VulkanDevice device;
is_dgpu = device.properties().deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU;
fprintf(stderr, "Running on device \"%s\"\n", device.properties().deviceName);
}
if (stress) {
fprintf(stderr, "Running in stress test mode, skipping hard reset tests.\n");
runStress(testDir, dummyPath, testFilter);
/* Returning from the stress test means we failed */
success = false;
} else {
if (is_dgpu) {
fprintf(stderr, "Running on a discrete GPU, skipping hard reset tests.\n");
}
for (auto& name : TEST_CASES) {
if (!testFilter.empty() && name.find(testFilter) == std::string::npos)
continue;
if (is_dgpu && name.find("hard") != std::string::npos)
continue;
success &= runTest(name, testDir, dummyPath);
}
}
return success ? 0 : 1;
}
pid_t launchSubprocess(const char* path, char* const* arguments) {
pid_t res = fork();
if (res == -1) {
perror("fork() failed");
abort();
}
if (!res) {
if (execv(path, arguments)) {
fprintf(stderr, "could not exec %s!\n", path);
abort();
}
}
return res;
}
#define COLOR_RESET "\033[0m"
#define COLOR_RED "\033[31m"
#define COLOR_GREEN "\033[1;32m"
#define COLOR_YELLOW "\033[1;33m"
/* Runs a test and checks for success.
* A test is only considered successful if both the test process and the dummy process return with exit code 0.
*/
bool runTest(const std::string& testName, const std::string& testDir, const std::string& dummyPath) {
int pipeFDs[2];
if (pipe(pipeFDs)) {
perror("Failed to create pipe");
return false;
}
bool success = true;
const char* resultDescription = " (GPU hang handled)";
char dummyName[] = "dummy";
std::string fdNames[2] = {
std::to_string(pipeFDs[0]),
std::to_string(pipeFDs[1]),
};
char* dummyArgs[] = {
dummyName,
fdNames[0].data(),
fdNames[1].data(),
NULL,
};
pid_t dummyPID = launchSubprocess(dummyPath.c_str(), dummyArgs);
pollfd wait_poll = {
.fd = pipeFDs[0],
.events = POLLIN,
};
if (!poll(&wait_poll, 1, 3000)) {
fprintf(stderr, "error: dummy process failed to initialize!\n");
close(pipeFDs[0]);
close(pipeFDs[1]);
return false;
}
char pipeBuffer = 0;
/* Clear the read end */
read(pipeFDs[0], &pipeBuffer, 1);
std::string testPath = testDir + testName;
char* name = strdup(testName.data());
char* testArgs[] = {
name,
NULL,
};
pid_t testPID = launchSubprocess(testPath.c_str(), testArgs);
free(name);
pid_t waitedPID;
int waitStatus;
do {
waitedPID = wait(&waitStatus);
if (WIFSTOPPED(waitStatus) || WIFCONTINUED(waitStatus))
continue;
if (waitedPID == dummyPID) {
success = false;
resultDescription = " (Innocent context killed)";
}
} while (waitedPID != testPID);
/* If the test subprocess errors, the hang was not reported correctly. */
if (success && !(WIFEXITED(waitStatus) && WEXITSTATUS(waitStatus) == 0)) {
if (WIFSIGNALED(waitStatus) && WTERMSIG(waitStatus) == SIGKILL) {
/* The Deck has udev rules that SIGKILL processes guilty of a hard reset.
* If the test process gets SIGKILLed, it's likely that it triggered this rule instead of a genuine bug.
*/
const char* warnString = isatty(2) ? COLOR_YELLOW "WARNING" COLOR_RESET : "WARNING";
fprintf(stderr, "%s: Guilty context killed by SIGKILL. This might be a bug (unless this is a Steam Deck)\n",
warnString);
} else {
success = false;
resultDescription = WIFEXITED(waitStatus) ? " (No hang reported)" : " (Guilty context crashed)";
}
}
if (success) {
/* Write something nonzero to poke the dummy process */
char data = 1;
write(pipeFDs[1], &data, 1);
int dummy_status;
wait(&dummy_status);
while (WIFSTOPPED(dummy_status) || WIFCONTINUED(dummy_status))
wait(&dummy_status);
if (!WIFEXITED(dummy_status) || WEXITSTATUS(dummy_status) != 0) {
success = false;
resultDescription = " (Innocent context killed)";
}
}
close(pipeFDs[0]);
close(pipeFDs[1]);
const char* resultStr;
if (success)
resultStr = isatty(1) ? COLOR_GREEN "OK" COLOR_RESET : "OK";
else
resultStr = isatty(1) ? COLOR_RED "FAILED" COLOR_RESET : "FAILED";
printf("%-30s %s%s\n", testName.c_str(), resultStr, resultDescription);
return success;
}
void runStress(const std::string& testDir, const std::string& dummyPath, const std::string& testFilter) {
char dummyName[] = "dummy";
char stressArg[] = "--stress";
char* dummyArgs[] = {
dummyName,
stressArg,
NULL,
};
pid_t dummyPID = launchSubprocess(dummyPath.c_str(), dummyArgs);
bool fail = false;
const char* failReason;
std::string failedTestName;
while (!fail) {
for (auto& test : TEST_CASES) {
if (test.find(testFilter) == std::string::npos)
continue;
/* Hard resets can't guarantee that innocent contexts stay alive if they submit work in parallel,
* so skip them in the stress test.
*/
if (test.find("hard") != std::string::npos)
continue;
std::string testPath = testDir + test;
char* name = test.data();
char* testArgs[] = {
name,
NULL,
};
pid_t testPID = launchSubprocess(testPath.c_str(), testArgs);
pid_t waitedPID;
int waitStatus;
do {
waitedPID = wait(&waitStatus);
if (WIFSTOPPED(waitStatus) || WIFCONTINUED(waitStatus))
continue;
if (waitedPID == dummyPID) {
fail = true;
failReason = " (Innocent context killed)";
failedTestName = test;
break;
}
} while (waitedPID != testPID);
if (fail)
break;
/* If the test subprocess errors, the hang was not reported correctly. */
if (!(WIFEXITED(waitStatus) && WEXITSTATUS(waitStatus) == 0)) {
fail = true;
failReason = WIFEXITED(waitStatus) ? " (No hang reported)" : " (Guilty context crashed)";
failedTestName = test;
break;
}
}
}
kill(dummyPID, SIGTERM);
const char* failedText = isatty(1) ? COLOR_RED "FAILED" COLOR_RESET : "FAILED";
fprintf(stderr, "Stress test %s in test %s %s", failedText, failedTestName.c_str(), failReason);
}
#version 460 core
#extension GL_EXT_buffer_reference : require
layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
layout(buffer_reference, std430) buffer DummyBuf {
uint value;
};
layout(push_constant) uniform inputs {
DummyBuf in_buf;
};
void main() {
while (in_buf.value != 0x1234) {}
// this is unreachable anyway
in_buf.value = 0x1235;
}
shaders = [
'infinite_loop.comp',
'prefix_sum.comp'
]
shaders_spv = []
foreach source : shaders
command = [
glslang_program, '-V', '--target-env', 'spirv1.5', '-x', '-o', '@OUTPUT@', '@INPUT@'
]
shaders_spv += custom_target(
source + '.spv.h',
input : source,
output : source + '.spv.h',
command : command
)
endforeach
\ No newline at end of file
#version 460 core
#extension GL_EXT_buffer_reference : require
layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
layout(buffer_reference, std430) buffer DummyBuf {
uint value[];
};
layout(push_constant) uniform inputs {
DummyBuf in_buf;
};
void main() {
/* This algorithm is not correct at all, used purely to waste GPU cycles. */
uint sum = 0;
for (uint i = 0; i < gl_GlobalInvocationID.x; ++i) {
sum += in_buf.value[i];
}
in_buf.value[gl_GlobalInvocationID.x] = sum;
}
/*
* Copyright © 2023 Valve Corporation
*
* 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 (including the next
* paragraph) 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 <vulkan_device.hpp>
/*
* Test triggering a hard reset by hanging the CP
* vkCmdWaitEvent makes the CP wait for an event to get signaled, so
* call vkCmdWaitEvent without ever setting the event
*/
int main() {
VulkanDevice device;
auto commandBuffer = device.createCommandBuffer(QueueType::GFX);
auto fence = device.createFence();
VkEventCreateInfo eventCreateInfo = {
.sType = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO,
};
VkEvent event;
CHECK_VKRESULT(vkCreateEvent(device.device(), &eventCreateInfo, nullptr, &event));
VkCommandBufferBeginInfo beginInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
};
vkBeginCommandBuffer(commandBuffer.buffer, &beginInfo);
vkCmdWaitEvents(commandBuffer.buffer, 1, &event, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, 0, nullptr, 0, nullptr, 0, nullptr);
CHECK_VKRESULT(vkEndCommandBuffer(commandBuffer.buffer));
VkResult result;
VkSubmitInfo submitInfo = {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.commandBufferCount = 1,
.pCommandBuffers = &commandBuffer.buffer,
};
CHECK_VKRESULT(vkQueueSubmit(device.gfxQueue(), 1, &submitInfo, fence));
result = vkWaitForFences(device.device(), 1, &fence, VK_TRUE, UINT64_MAX);
/* If a submission kills the GPU, RADV only returns VK_ERROR_DEVICE_LOST on the next submission. */
if (result != VK_ERROR_DEVICE_LOST) {
result = vkQueueSubmit(device.gfxQueue(), 1, &submitInfo, fence);
}
vkDestroyEvent(device.device(), event, nullptr);
device.destroyFence(fence);
device.destroyCommandBuffer(commandBuffer);
/* Only mark the test as successful if the device actually hung. */
return result == VK_ERROR_DEVICE_LOST ? 0 : 1;
}
/*
* Copyright © 2023 Valve Corporation
*
* 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 (including the next
* paragraph) 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 <vulkan_device.hpp>
/*
* Test triggering a hard reset by hanging the CP
* Triggers a CP DMA transfer to a memory region that has been freed already
*/
int main() {
VulkanDevice device;
auto buffer = device.createBuffer(1048576);
auto buffer2 = device.createBuffer(1048576);
auto commandBuffer = device.createCommandBuffer(QueueType::GFX);
auto fence = device.createFence();
VkCommandBufferBeginInfo beginInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
};
vkBeginCommandBuffer(commandBuffer.buffer, &beginInfo);
uint32_t offset = 0;
for (uint32_t i = 0; i < 1048576 / 2048; ++i, offset += 2048) {
VkBufferCopy copy = {
.srcOffset = offset,
.dstOffset = offset,
.size = 2048,
};
vkCmdCopyBuffer(commandBuffer.buffer, buffer.buffer, buffer2.buffer, 1, &copy);
}
CHECK_VKRESULT(vkEndCommandBuffer(commandBuffer.buffer));
device.destroyBuffer(buffer);
device.destroyBuffer(buffer2);
VkResult result;
VkSubmitInfo submitInfo = {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.commandBufferCount = 1,
.pCommandBuffers = &commandBuffer.buffer,
};
CHECK_VKRESULT(vkQueueSubmit(device.gfxQueue(), 1, &submitInfo, fence));
result = vkWaitForFences(device.device(), 1, &fence, VK_TRUE, UINT64_MAX);
/* If a submission kills the GPU, RADV only returns VK_ERROR_DEVICE_LOST on the next submission. */
if (result != VK_ERROR_DEVICE_LOST) {
result = vkQueueSubmit(device.gfxQueue(), 1, &submitInfo, fence);
}
device.destroyFence(fence);
device.destroyCommandBuffer(commandBuffer);
/* Only mark the test as successful if the device actually hung. */
return result == VK_ERROR_DEVICE_LOST ? 0 : 1;
}
/*
* Copyright © 2023 Valve Corporation
*
* 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 (including the next
* paragraph) 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 <vulkan_device.hpp>
uint32_t loop_shader_spv[] = {
#include <shaders/infinite_loop.comp.spv.h>
};
/*
* Test triggering soft recovery with an infinite loop in a shader
*/
int main() {
VulkanDevice device;
auto pipeline = device.createPipeline(loop_shader_spv, sizeof(loop_shader_spv), 8);
auto buffer = device.createBuffer(16);
auto* mapped_data = reinterpret_cast<uint32_t*>(buffer.hostMap);
*mapped_data = 0;
auto commandBuffer = device.createCommandBuffer(QueueType::GFX);
auto fence = device.createFence();
VkCommandBufferBeginInfo beginInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
};
vkBeginCommandBuffer(commandBuffer.buffer, &beginInfo);
vkCmdBindPipeline(commandBuffer.buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline.pipeline);
vkCmdPushConstants(commandBuffer.buffer, pipeline.layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, 8, &buffer.va);
vkCmdDispatch(commandBuffer.buffer, 64, 64, 64);
CHECK_VKRESULT(vkEndCommandBuffer(commandBuffer.buffer));
VkResult result;
VkSubmitInfo submitInfo = {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.commandBufferCount = 1,
.pCommandBuffers = &commandBuffer.buffer,
};
CHECK_VKRESULT(vkQueueSubmit(device.gfxQueue(), 1, &submitInfo, fence));
result = vkWaitForFences(device.device(), 1, &fence, VK_TRUE, UINT64_MAX);
/* If a submission kills the GPU, RADV only returns VK_ERROR_DEVICE_LOST on the next submission. */
if (result != VK_ERROR_DEVICE_LOST) {
result = vkQueueSubmit(device.gfxQueue(), 1, &submitInfo, fence);
}
device.destroyFence(fence);
device.destroyCommandBuffer(commandBuffer);
device.destroyBuffer(buffer);
device.destroyPipeline(pipeline);
/* Only mark the test as successful if the device actually hung. */
return result == VK_ERROR_DEVICE_LOST ? 0 : 1;
}
/*
* Copyright © 2023 Valve Corporation
*
* 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 (including the next
* paragraph) 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 <vulkan_device.hpp>
uint32_t loop_shader_spv[] = {
#include <shaders/infinite_loop.comp.spv.h>
};
/*
* Test triggering soft recovery by causing lots of page faults (writing to address 0 in an infinite loop)
*/
int main() {
VulkanDevice device;
auto pipeline = device.createPipeline(loop_shader_spv, sizeof(loop_shader_spv), 8);
auto buffer = device.createBuffer(16);
auto* mapped_data = reinterpret_cast<uint32_t*>(buffer.hostMap);
*mapped_data = 0;
auto commandBuffer = device.createCommandBuffer(QueueType::GFX);
auto fence = device.createFence();
VkCommandBufferBeginInfo beginInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
};
vkBeginCommandBuffer(commandBuffer.buffer, &beginInfo);
VkDeviceAddress null = 0;
vkCmdBindPipeline(commandBuffer.buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline.pipeline);
vkCmdPushConstants(commandBuffer.buffer, pipeline.layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, 8, &null);
vkCmdDispatch(commandBuffer.buffer, 64, 64, 64);
CHECK_VKRESULT(vkEndCommandBuffer(commandBuffer.buffer));
VkResult result;
VkSubmitInfo submitInfo = {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.commandBufferCount = 1,
.pCommandBuffers = &commandBuffer.buffer,
};
CHECK_VKRESULT(vkQueueSubmit(device.gfxQueue(), 1, &submitInfo, fence));
result = vkWaitForFences(device.device(), 1, &fence, VK_TRUE, UINT64_MAX);
/* If a submission kills the GPU, RADV only returns VK_ERROR_DEVICE_LOST on the next submission. */
if (result != VK_ERROR_DEVICE_LOST) {
result = vkQueueSubmit(device.gfxQueue(), 1, &submitInfo, fence);
}
device.destroyFence(fence);
device.destroyCommandBuffer(commandBuffer);
device.destroyBuffer(buffer);
device.destroyPipeline(pipeline);
/* Only mark the test as successful if the device actually hung. */
return result == VK_ERROR_DEVICE_LOST ? 0 : 1;
}
/*
* Copyright © 2023 Valve Corporation
*
* 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 (including the next
* paragraph) 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 <vulkan_device.hpp>
#include <vector>
template <typename HandleType, typename ResultType>
using EnumeratePFN = void(VKAPI_PTR*)(HandleType type, uint32_t* count, ResultType* pEnumerants);
template <typename Handle, typename ResultElement>
std::vector<ResultElement> enumerate(Handle handle, EnumeratePFN<Handle, ResultElement> pfnEnumerate) {
uint32_t valueCount;
pfnEnumerate(handle, &valueCount, nullptr);
std::vector<ResultElement> values = std::vector<ResultElement>(valueCount);
pfnEnumerate(handle, &valueCount, values.data());
return values;
}
VulkanDevice::VulkanDevice() {
CHECK_VKRESULT(volkInitialize());
VkApplicationInfo applicationInfo = {
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pApplicationName = "GPU Hang Test Suite",
.applicationVersion = VK_MAKE_API_VERSION(0, 1, 0, 0),
.apiVersion = VK_MAKE_API_VERSION(0, 1, 3, 0),
};
VkInstanceCreateInfo instanceCreateInfo = {
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.pApplicationInfo = &applicationInfo,
};
CHECK_VKRESULT(vkCreateInstance(&instanceCreateInfo, nullptr, &m_instance));
volkLoadInstanceOnly(m_instance);
/* Just choose the first physical device. If necessary, use MESA_VK_DEVICE_SELECT to switch devices. */
uint32_t deviceCount = 1;
vkEnumeratePhysicalDevices(m_instance, &deviceCount, &m_physDevice);
vkGetPhysicalDeviceProperties(m_physDevice, &m_properties);
VkPhysicalDeviceMemoryProperties memoryProperties;
vkGetPhysicalDeviceMemoryProperties(m_physDevice, &memoryProperties);
m_memTypeIdx = -1u;
for (uint32_t i = 0; i < memoryProperties.memoryTypeCount; ++i) {
if ((memoryProperties.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) &&
(memoryProperties.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) {
m_memTypeIdx = i;
break;
}
}
std::vector<VkQueueFamilyProperties> queue_families =
enumerate(m_physDevice, vkGetPhysicalDeviceQueueFamilyProperties);
for (uint32_t i = 0; i < queue_families.size(); ++i) {
if (queue_families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)
m_gfxFamilyIdx = i;
else if (queue_families[i].queueFlags & VK_QUEUE_COMPUTE_BIT)
m_aceFamilyIdx = i;
}
float prio = 1.0f;
VkDeviceQueueCreateInfo queueCreateInfos[2] = {
{
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = m_gfxFamilyIdx,
.queueCount = 1,
.pQueuePriorities = &prio,
},
{
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = m_aceFamilyIdx,
.queueCount = 1,
.pQueuePriorities = &prio,
},
};
VkPhysicalDeviceVulkan12Features vk12features = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES,
.bufferDeviceAddress = true,
};
VkPhysicalDeviceFeatures2 features = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
.pNext = &vk12features,
};
VkDeviceCreateInfo deviceCreateInfo = {
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.pNext = &features,
.queueCreateInfoCount = 2,
.pQueueCreateInfos = queueCreateInfos,
};
CHECK_VKRESULT(vkCreateDevice(m_physDevice, &deviceCreateInfo, nullptr, &m_device));
volkLoadDevice(m_device);
vkGetDeviceQueue(m_device, m_gfxFamilyIdx, 0, &m_gfxQueue);
vkGetDeviceQueue(m_device, m_aceFamilyIdx, 0, &m_aceQueue);
}
Buffer VulkanDevice::createBuffer(VkDeviceSize size) {
uint32_t familyIndices[2] = { m_gfxFamilyIdx, m_aceFamilyIdx };
VkBufferCreateInfo createInfo = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.size = size,
.usage = VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
.sharingMode = VK_SHARING_MODE_CONCURRENT,
.queueFamilyIndexCount = 2,
.pQueueFamilyIndices = familyIndices,
};
VkBuffer buffer;
CHECK_VKRESULT(vkCreateBuffer(m_device, &createInfo, nullptr, &buffer));
VkMemoryRequirements requirements;
vkGetBufferMemoryRequirements(m_device, buffer, &requirements);
VkDeviceMemory memory;
VkMemoryAllocateFlagsInfo flagsInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO,
.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT,
};
VkMemoryAllocateInfo allocateInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.pNext = &flagsInfo,
.allocationSize = requirements.size,
.memoryTypeIndex = m_memTypeIdx,
};
CHECK_VKRESULT(vkAllocateMemory(m_device, &allocateInfo, nullptr, &memory));
vkBindBufferMemory(m_device, buffer, memory, 0);
VkBufferDeviceAddressInfo addressInfo = {
.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO,
.buffer = buffer,
};
VkDeviceAddress va = vkGetBufferDeviceAddress(m_device, &addressInfo);
void *hostMap;
CHECK_VKRESULT(vkMapMemory(m_device, memory, 0, VK_WHOLE_SIZE, 0, &hostMap));
return {
.buffer = buffer,
.memory = memory,
.va = va,
.hostMap = hostMap,
};
}
void VulkanDevice::destroyBuffer(Buffer& buffer) {
vkUnmapMemory(m_device, buffer.memory);
vkDestroyBuffer(m_device, buffer.buffer, nullptr);
vkFreeMemory(m_device, buffer.memory, nullptr);
}
CommandBuffer VulkanDevice::createCommandBuffer(QueueType type) {
VkCommandPoolCreateInfo poolCreateInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.queueFamilyIndex = type == QueueType::GFX ? m_gfxFamilyIdx : m_aceFamilyIdx
};
VkCommandPool pool;
CHECK_VKRESULT(vkCreateCommandPool(m_device, &poolCreateInfo, nullptr, &pool));
VkCommandBufferAllocateInfo allocateInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = pool,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = 1,
};
VkCommandBuffer buffer;
CHECK_VKRESULT(vkAllocateCommandBuffers(m_device, &allocateInfo, &buffer));
return {
.pool = pool,
.buffer = buffer,
};
}
void VulkanDevice::destroyCommandBuffer(CommandBuffer& commandBuffer) {
vkDestroyCommandPool(m_device, commandBuffer.pool, nullptr);
}
Pipeline VulkanDevice::createPipeline(uint32_t* spv, uint32_t spvSize, uint32_t pushConstantSize) {
VkShaderModuleCreateInfo moduleCreateInfo = {
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
.codeSize = spvSize,
.pCode = spv,
};
VkShaderModule module;
CHECK_VKRESULT(vkCreateShaderModule(m_device, &moduleCreateInfo, nullptr, &module));
VkPushConstantRange range = {
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
.size = pushConstantSize,
};
VkPipelineLayoutCreateInfo layoutCreateInfo = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.pushConstantRangeCount = 1,
.pPushConstantRanges = &range,
};
VkPipelineLayout layout;
CHECK_VKRESULT(vkCreatePipelineLayout(m_device, &layoutCreateInfo, nullptr, &layout));
VkComputePipelineCreateInfo pipelineCreateInfo = {
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
.stage = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_COMPUTE_BIT,
.module = module,
.pName = "main",
},
.layout = layout,
};
VkPipeline pipeline;
CHECK_VKRESULT(vkCreateComputePipelines(m_device, VK_NULL_HANDLE, 1, &pipelineCreateInfo, nullptr, &pipeline));
vkDestroyShaderModule(m_device, module, nullptr);
return {
.pipeline = pipeline,
.layout = layout,
};
}
void VulkanDevice::destroyPipeline(Pipeline& pipeline) {
vkDestroyPipelineLayout(m_device, pipeline.layout, nullptr);
vkDestroyPipeline(m_device, pipeline.pipeline, nullptr);
}
VkFence VulkanDevice::createFence() {
VkFenceCreateInfo fenceCreateInfo = {
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
};
VkFence fence;
CHECK_VKRESULT(vkCreateFence(m_device, &fenceCreateInfo, nullptr, &fence));
return fence;
}
void VulkanDevice::destroyFence(VkFence fence) {
vkDestroyFence(m_device, fence, nullptr);
}
VulkanDevice::~VulkanDevice() {
vkDestroyDevice(m_device, nullptr);
vkDestroyInstance(m_instance, nullptr);
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment