vulkan: implement compute shaders

A lot of stuff probably isn't implemented yet, however the
following code already works:

```lua
local shader = love.graphics.newComputeShader [[
    layout (local_size_x = 1, local_size_y = 1) in;

    layout(r32f) uniform highp image2D out_tex;

    uniform float time;

    void computemain() {
        imageStore(out_tex, ivec2(love_GlobalThreadID.xy), vec4(time, 1.0, 1.0, 1.0));
    }
]]

local tex = love.graphics.newTexture(64, 64, {computewrite = true,format = "r32f"})

function love.draw()
    shader:send("out_tex", tex)
    shader:send("time", love.timer.getTime() % 1)
    love.graphics.dispatchThreadgroups(shader, 64, 64, 1)

    love.graphics.print("compute shader test")
    love.graphics.draw(tex, 25, 25)
end
```
This commit is contained in:
niki
2022-08-31 02:07:46 +02:00
parent e409770be1
commit a0ee9f7b9b
7 changed files with 380 additions and 157 deletions
+85 -9
View File
@@ -159,7 +159,8 @@ void Graphics::submitGpuCommands(bool present) {
imagesInFlight[imageIndex] = inFlightFences[currentFrame];
std::vector<VkCommandBuffer> submitCommandbuffers = {
dataTransferCommandBuffers.at(currentFrame),
dataTransferCommandBuffers.at(currentFrame),
computeCommandBuffers.at(currentFrame),
commandBuffers.at(currentFrame),
readbackCommandBuffers.at(currentFrame)};
@@ -642,6 +643,16 @@ graphics::StreamBuffer* Graphics::newStreamBuffer(BufferUsage type, size_t size)
return new StreamBuffer(this, type, size);
}
bool Graphics::dispatch(int x, int y, int z) {
vkCmdBindPipeline(computeCommandBuffers.at(currentFrame), VK_PIPELINE_BIND_POINT_COMPUTE, computeShader->getComputePipeline());
computeShader->cmdPushDescriptorSets(computeCommandBuffers.at(currentFrame), currentFrame, VK_PIPELINE_BIND_POINT_COMPUTE);
vkCmdDispatch(computeCommandBuffers.at(currentFrame), static_cast<uint32_t>(x), static_cast<uint32_t>(y), static_cast<uint32_t>(z));
return true;
}
Matrix4 Graphics::computeDeviceProjection(const Matrix4& projection, bool rendertotexture) const {
uint32 flags = DEVICE_PROJECTION_DEFAULT;
return calculateDeviceProjection(projection, flags);
@@ -735,7 +746,7 @@ void Graphics::beginFrame() {
void Graphics::startRecordingGraphicsCommands(bool newFrame) {
VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = 0;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
beginInfo.pInheritanceInfo = nullptr;
if (vkBeginCommandBuffer(commandBuffers.at(currentFrame), &beginInfo) != VK_SUCCESS) {
@@ -747,6 +758,9 @@ void Graphics::startRecordingGraphicsCommands(bool newFrame) {
if (vkBeginCommandBuffer(readbackCommandBuffers.at(currentFrame), &beginInfo) != VK_SUCCESS) {
throw love::Exception("failed to begin recording readback command buffer");
}
if (vkBeginCommandBuffer(computeCommandBuffers.at(currentFrame), &beginInfo) != VK_SUCCESS) {
throw love::Exception("failed to begin recording compute command buffer");
}
initDynamicState();
@@ -773,6 +787,9 @@ void Graphics::endRecordingGraphicsCommands(bool present) {
if (vkEndCommandBuffer(readbackCommandBuffers.at(currentFrame)) != VK_SUCCESS) {
throw love::Exception("failed to record read back command buffer");
}
if (vkEndCommandBuffer(computeCommandBuffers.at(currentFrame)) != VK_SUCCESS) {
throw love::Exception("failed to record compute command buffer");
}
}
void Graphics::updatedBatchedDrawBuffers() {
@@ -804,12 +821,54 @@ VkCommandBuffer Graphics::getReadbackCommandBuffer() {
return readbackCommandBuffers.at(currentFrame);
}
void Graphics::queueCleanUp(std::function<void()> cleanUp) {
cleanUpFunctions.at(currentFrame).push_back(std::move(cleanUp));
void Graphics::oneTimeCommand(std::function<void(VkCommandBuffer)> cmd) {
VkCommandBufferAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.commandPool = commandPool;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandBufferCount = 1;
VkCommandBuffer commandBuffer;
if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer) != VK_SUCCESS) {
throw love::Exception("failed to allocate one time command buffer");
}
VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
throw love::Exception("failed to start recording one time command buffer");
}
cmd(commandBuffer);
if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
throw love::Exception("failed to end recording one time command buffer");
}
VkSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffer;
if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) {
throw love::Exception("failed to submit to queue");
}
if (vkQueueWaitIdle(graphicsQueue) != VK_SUCCESS) {
throw love::Exception("failed to wait for queue idle");
}
vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
}
void Graphics::addReadbackCallback(const std::function<void()>& callback) {
readbackCallbacks.at(currentFrame).push_back(std::move(callback));
void Graphics::queueCleanUp(std::function<void()> cleanUp) {
cleanUpFunctions.at(currentFrame).push_back(cleanUp);
}
void Graphics::addReadbackCallback(std::function<void()> callback) {
readbackCallbacks.at(currentFrame).push_back(callback);
}
graphics::Shader::BuiltinUniformData Graphics::getCurrentBuiltinUniformData() {
@@ -1047,7 +1106,7 @@ QueueFamilyIndices Graphics::findQueueFamilies(VkPhysicalDevice device) {
int i = 0;
for (const auto& queueFamily : queueFamilies) {
if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT && queueFamily.queueFlags & VK_QUEUE_COMPUTE_BIT) {
indices.graphicsFamily = i;
}
@@ -1089,7 +1148,7 @@ void Graphics::createLogicalDevice() {
QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
std::set<uint32_t> uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() };
std::set<uint32_t> uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value()};
float queuePriority = 1.0f;
for (uint32_t queueFamily : uniqueQueueFamilies) {
@@ -1764,7 +1823,7 @@ void Graphics::prepareDraw(const VertexAttributes& attributes, const BufferBindi
ensureGraphicsPipelineConfiguration(configuration);
configuration.shader->cmdPushDescriptorSets(commandBuffers.at(currentFrame), static_cast<uint32_t>(currentFrame));
configuration.shader->cmdPushDescriptorSets(commandBuffers.at(currentFrame), static_cast<uint32_t>(currentFrame), VK_PIPELINE_BIND_POINT_GRAPHICS);
vkCmdBindVertexBuffers(commandBuffers.at(currentFrame), 0, static_cast<uint32_t>(bufferVector.size()), bufferVector.data(), offsets.data());
}
@@ -1926,6 +1985,10 @@ VkSampler Graphics::createSampler(const SamplerState& samplerState) {
return sampler;
}
void Graphics::setComputeShader(Shader* shader) {
computeShader = shader;
}
VkSampler Graphics::getCachedSampler(const SamplerState& samplerState) {
auto it = samplers.find(samplerState);
if (it != samplers.end()) {
@@ -2266,6 +2329,7 @@ void Graphics::createCommandBuffers() {
commandBuffers.resize(MAX_FRAMES_IN_FLIGHT);
dataTransferCommandBuffers.resize(MAX_FRAMES_IN_FLIGHT);
readbackCommandBuffers.resize(MAX_FRAMES_IN_FLIGHT);
computeCommandBuffers.resize(MAX_FRAMES_IN_FLIGHT);
VkCommandBufferAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
@@ -2296,6 +2360,16 @@ void Graphics::createCommandBuffers() {
if (vkAllocateCommandBuffers(device, &readbackAllocInfo, readbackCommandBuffers.data()) != VK_SUCCESS) {
throw love::Exception("failed to allocate readback command buffers");
}
VkCommandBufferAllocateInfo commandAllocInfo{};
commandAllocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
commandAllocInfo.commandPool = commandPool;
commandAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
commandAllocInfo.commandBufferCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
if (vkAllocateCommandBuffers(device, &commandAllocInfo, computeCommandBuffers.data()) != VK_SUCCESS) {
throw love::Exception("failed to allocate compute command buffers");
}
}
void Graphics::createSyncObjects() {
@@ -2347,6 +2421,8 @@ void Graphics::cleanup() {
vkFreeCommandBuffers(device, commandPool, MAX_FRAMES_IN_FLIGHT, commandBuffers.data());
vkFreeCommandBuffers(device, commandPool, MAX_FRAMES_IN_FLIGHT, dataTransferCommandBuffers.data());
vkFreeCommandBuffers(device, commandPool, MAX_FRAMES_IN_FLIGHT, readbackCommandBuffers.data());
vkFreeCommandBuffers(device, commandPool, MAX_FRAMES_IN_FLIGHT, computeCommandBuffers.data());
for (auto const& p : samplers) {
vkDestroySampler(device, p.second, nullptr);