2023-08-13 18:52:57 -04:00
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
2023-11-19 00:43:20 -05:00
|
|
|
// Copyright (C) 2023 Jon Beniston, M7RCE <jon@beniston.com> //
|
2023-08-13 18:52:57 -04:00
|
|
|
// //
|
|
|
|
// This program is free software; you can redistribute it and/or modify //
|
|
|
|
// it under the terms of the GNU General Public License as published by //
|
|
|
|
// the Free Software Foundation as version 3 of the License, or //
|
|
|
|
// (at your option) any later version. //
|
|
|
|
// //
|
|
|
|
// This program is distributed in the hope that it will be useful, //
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
|
|
|
// GNU General Public License V3 for more details. //
|
|
|
|
// //
|
|
|
|
// You should have received a copy of the GNU General Public License //
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
#include <QDebug>
|
|
|
|
|
|
|
|
#include "glslang_c_interface.h"
|
|
|
|
|
|
|
|
#include "dsp/vulkanvkfftengine.h"
|
|
|
|
|
|
|
|
class GLSInitialiser {
|
|
|
|
public:
|
|
|
|
GLSInitialiser() {
|
|
|
|
glslang_initialize_process();
|
|
|
|
};
|
|
|
|
|
|
|
|
~GLSInitialiser() {
|
|
|
|
glslang_finalize_process();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
static GLSInitialiser glsInitialiser;
|
|
|
|
|
|
|
|
VulkanvkFFTEngine::VulkanvkFFTEngine()
|
|
|
|
{
|
|
|
|
VkFFTResult resFFT;
|
|
|
|
resFFT = gpuInit();
|
|
|
|
if (resFFT != VKFFT_SUCCESS)
|
|
|
|
{
|
|
|
|
qDebug() << "VulkanvkFFTEngine::VulkanvkFFTEngine: Failed to initialise GPU:" << getVkFFTErrorString(resFFT);
|
|
|
|
delete vkGPU;
|
|
|
|
vkGPU = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
VulkanvkFFTEngine::~VulkanvkFFTEngine()
|
|
|
|
{
|
|
|
|
if (vkGPU)
|
|
|
|
{
|
|
|
|
freeAll();
|
|
|
|
vkDestroyFence(vkGPU->device, vkGPU->fence, nullptr);
|
|
|
|
vkDestroyCommandPool(vkGPU->device, vkGPU->commandPool, nullptr);
|
|
|
|
vkDestroyDevice(vkGPU->device, nullptr);
|
|
|
|
DestroyDebugUtilsMessengerEXT(vkGPU, nullptr);
|
|
|
|
vkDestroyInstance(vkGPU->instance, nullptr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const QString VulkanvkFFTEngine::m_name = "vkFFT (Vulkan)";
|
|
|
|
|
|
|
|
QString VulkanvkFFTEngine::getName() const
|
|
|
|
{
|
|
|
|
return m_name;
|
|
|
|
}
|
|
|
|
|
|
|
|
VkFFTResult VulkanvkFFTEngine::gpuInit()
|
|
|
|
{
|
|
|
|
VkResult res = VK_SUCCESS;
|
|
|
|
|
|
|
|
// To enable validation on Windows:
|
|
|
|
// set VK_LAYER_PATH=%VULKAN_SDK%\Bin
|
|
|
|
// set VK_INSTANCE_LAYERS=VK_LAYER_LUNARG_api_dump;VK_LAYER_KHRONOS_validation
|
|
|
|
// https://vulkan.lunarg.com/doc/view/1.3.204.1/windows/layer_configuration.html
|
|
|
|
// Create vk_layer_settings.txt in working dir
|
|
|
|
// Or run vkconfig to do so
|
|
|
|
|
|
|
|
// Create instance - a connection between the application and the Vulkan library
|
|
|
|
res = createInstance(vkGPU, 0);
|
|
|
|
if (res != 0) {
|
|
|
|
return VKFFT_ERROR_FAILED_TO_CREATE_INSTANCE;
|
|
|
|
}
|
|
|
|
// Set up the debugging messenger
|
|
|
|
res = setupDebugMessenger(vkGPU);
|
|
|
|
if (res != 0) {
|
|
|
|
return VKFFT_ERROR_FAILED_TO_SETUP_DEBUG_MESSENGER;
|
|
|
|
}
|
|
|
|
// Check if there are GPUs that support Vulkan and select one
|
|
|
|
res = findPhysicalDevice(vkGPU);
|
|
|
|
if (res != 0) {
|
|
|
|
return VKFFT_ERROR_FAILED_TO_FIND_PHYSICAL_DEVICE;
|
|
|
|
}
|
|
|
|
// Create logical device representation
|
|
|
|
res = createDevice(vkGPU, 0);
|
|
|
|
if (res != 0) {
|
|
|
|
return VKFFT_ERROR_FAILED_TO_CREATE_DEVICE;
|
|
|
|
}
|
|
|
|
// Create fence for synchronization
|
|
|
|
res = createFence(vkGPU);
|
|
|
|
if (res != 0) {
|
|
|
|
return VKFFT_ERROR_FAILED_TO_CREATE_FENCE;
|
|
|
|
}
|
|
|
|
// Create a place, command buffer memory is allocated from
|
|
|
|
res = createCommandPool(vkGPU);
|
|
|
|
if (res != 0) {
|
|
|
|
return VKFFT_ERROR_FAILED_TO_CREATE_COMMAND_POOL;
|
|
|
|
}
|
|
|
|
vkGetPhysicalDeviceProperties(vkGPU->physicalDevice, &vkGPU->physicalDeviceProperties);
|
|
|
|
vkGetPhysicalDeviceMemoryProperties(vkGPU->physicalDevice, &vkGPU->physicalDeviceMemoryProperties);
|
|
|
|
|
|
|
|
return VKFFT_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
VkFFTResult VulkanvkFFTEngine::gpuAllocateBuffers()
|
|
|
|
{
|
|
|
|
VkFFTResult resFFT;
|
|
|
|
VulkanPlan *plan = reinterpret_cast<VulkanPlan *>(m_currentPlan);
|
|
|
|
|
|
|
|
// Allocate GPU memory
|
|
|
|
resFFT = allocateBuffer(vkGPU,
|
|
|
|
&plan->m_buffer,
|
|
|
|
&plan->m_bufferDeviceMemory,
|
|
|
|
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
|
|
|
VK_MEMORY_HEAP_DEVICE_LOCAL_BIT,
|
|
|
|
plan->m_bufferSize);
|
|
|
|
if (resFFT != VKFFT_SUCCESS) {
|
|
|
|
return resFFT;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Allocate CPU/GPU memory (Requires m_currentPlan->m_buffer to have been created)
|
|
|
|
resFFT = vulkanAllocateIn(plan);
|
|
|
|
if (resFFT != VKFFT_SUCCESS) {
|
|
|
|
return resFFT;
|
|
|
|
}
|
|
|
|
resFFT = vulkanAllocateOut(plan);
|
|
|
|
if (resFFT != VKFFT_SUCCESS) {
|
|
|
|
return resFFT;
|
|
|
|
}
|
|
|
|
|
|
|
|
plan->m_configuration->buffer = &plan->m_buffer;
|
|
|
|
|
|
|
|
return VKFFT_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
VkFFTResult VulkanvkFFTEngine::gpuConfigure()
|
|
|
|
{
|
|
|
|
VkFFTResult resFFT;
|
|
|
|
VulkanPlan *plan = reinterpret_cast<VulkanPlan *>(m_currentPlan);
|
|
|
|
|
|
|
|
// Allocate command buffer with command to perform FFT
|
|
|
|
resFFT = vulkanAllocateFFTCommand(plan);
|
|
|
|
if (resFFT != VKFFT_SUCCESS) {
|
|
|
|
return resFFT;
|
|
|
|
}
|
|
|
|
|
|
|
|
return VKFFT_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Allocate CPU to GPU memory buffer
|
|
|
|
VkFFTResult VulkanvkFFTEngine::vulkanAllocateIn(VulkanPlan *plan)
|
|
|
|
{
|
|
|
|
VkFFTResult resFFT;
|
|
|
|
VkResult res = VK_SUCCESS;
|
|
|
|
VkBuffer* buffer = (VkBuffer*)&plan->m_buffer;
|
|
|
|
|
|
|
|
resFFT = allocateBuffer(vkGPU, &plan->m_inBuffer, &plan->m_inMemory, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, m_currentPlan->m_bufferSize);
|
|
|
|
if (resFFT != VKFFT_SUCCESS) {
|
|
|
|
return resFFT;
|
|
|
|
}
|
|
|
|
|
|
|
|
void* data;
|
|
|
|
res = vkMapMemory(vkGPU->device, plan->m_inMemory, 0, plan->m_bufferSize, 0, &data);
|
|
|
|
if (res != VK_SUCCESS) {
|
|
|
|
return VKFFT_ERROR_FAILED_TO_MAP_MEMORY;
|
|
|
|
}
|
|
|
|
plan->m_in = (Complex*) data;
|
|
|
|
|
|
|
|
return VKFFT_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Allocate GPU to CPU memory buffer
|
|
|
|
VkFFTResult VulkanvkFFTEngine::vulkanAllocateOut(VulkanPlan *plan)
|
|
|
|
{
|
|
|
|
VkFFTResult resFFT;
|
|
|
|
VkResult res;
|
|
|
|
VkBuffer* buffer = (VkBuffer*)&plan->m_buffer;
|
|
|
|
|
|
|
|
resFFT = allocateBuffer(vkGPU, &plan->m_outBuffer, &plan->m_outMemory, VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, m_currentPlan->m_bufferSize);
|
|
|
|
if (resFFT != VKFFT_SUCCESS) {
|
|
|
|
return resFFT;
|
|
|
|
}
|
|
|
|
|
|
|
|
void* data;
|
|
|
|
res = vkMapMemory(vkGPU->device, plan->m_outMemory, 0, plan->m_bufferSize, 0, &data);
|
|
|
|
if (res != VK_SUCCESS) {
|
|
|
|
return VKFFT_ERROR_FAILED_TO_MAP_MEMORY;
|
|
|
|
}
|
|
|
|
plan->m_out = (Complex*) data;
|
|
|
|
|
|
|
|
return VKFFT_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
void VulkanvkFFTEngine::vulkanDeallocateIn(VulkanPlan *plan)
|
|
|
|
{
|
|
|
|
vkUnmapMemory(vkGPU->device, plan->m_inMemory);
|
|
|
|
vkDestroyBuffer(vkGPU->device, plan->m_inBuffer, nullptr);
|
|
|
|
vkFreeMemory(vkGPU->device, plan->m_inMemory, nullptr);
|
|
|
|
plan->m_in = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
void VulkanvkFFTEngine::vulkanDeallocateOut(VulkanPlan *plan)
|
|
|
|
{
|
|
|
|
vkUnmapMemory(vkGPU->device, plan->m_outMemory);
|
|
|
|
vkDestroyBuffer(vkGPU->device, plan->m_outBuffer, nullptr);
|
|
|
|
vkFreeMemory(vkGPU->device, plan->m_outMemory, nullptr);
|
|
|
|
plan->m_out = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
VkFFTResult VulkanvkFFTEngine::vulkanAllocateFFTCommand(VulkanPlan *plan)
|
|
|
|
{
|
|
|
|
VkFFTResult resFFT;
|
|
|
|
VkResult res = VK_SUCCESS;
|
|
|
|
VkCommandBufferAllocateInfo commandBufferAllocateInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
|
|
|
|
commandBufferAllocateInfo.commandPool = vkGPU->commandPool;
|
|
|
|
commandBufferAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
|
|
|
commandBufferAllocateInfo.commandBufferCount = 1;
|
|
|
|
res = vkAllocateCommandBuffers(vkGPU->device, &commandBufferAllocateInfo, &plan->m_commandBuffer);
|
|
|
|
if (res != 0) {
|
|
|
|
return VKFFT_ERROR_FAILED_TO_ALLOCATE_COMMAND_BUFFERS;
|
|
|
|
}
|
|
|
|
VkCommandBufferBeginInfo commandBufferBeginInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
|
|
|
|
commandBufferBeginInfo.flags = 0;
|
|
|
|
res = vkBeginCommandBuffer(plan->m_commandBuffer, &commandBufferBeginInfo);
|
|
|
|
if (res != 0) {
|
|
|
|
return VKFFT_ERROR_FAILED_TO_BEGIN_COMMAND_BUFFER;
|
|
|
|
}
|
|
|
|
|
|
|
|
VkBuffer* buffer = (VkBuffer*)&plan->m_buffer;
|
|
|
|
|
|
|
|
// Copy from CPU to GPU
|
|
|
|
VkBufferCopy copyRegionIn = { 0 };
|
|
|
|
copyRegionIn.srcOffset = 0;
|
|
|
|
copyRegionIn.dstOffset = 0;
|
|
|
|
copyRegionIn.size = plan->m_bufferSize;
|
|
|
|
vkCmdCopyBuffer(plan->m_commandBuffer, plan->m_inBuffer, buffer[0], 1, ©RegionIn);
|
|
|
|
|
|
|
|
// Wait for copy to complete
|
|
|
|
VkMemoryBarrier memoryBarrierIn = {
|
|
|
|
VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
|
|
|
0,
|
|
|
|
VK_ACCESS_SHADER_WRITE_BIT,
|
|
|
|
VK_ACCESS_SHADER_READ_BIT,
|
|
|
|
};
|
|
|
|
vkCmdPipelineBarrier(
|
|
|
|
plan->m_commandBuffer,
|
|
|
|
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
|
|
|
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
|
|
|
0,
|
|
|
|
1,
|
|
|
|
&memoryBarrierIn,
|
|
|
|
0, 0, 0, 0);
|
|
|
|
|
|
|
|
// Perform FFT
|
|
|
|
VkFFTLaunchParams launchParams = {};
|
|
|
|
launchParams.commandBuffer = &plan->m_commandBuffer;
|
|
|
|
resFFT = VkFFTAppend(plan->m_app, plan->m_inverse, &launchParams);
|
|
|
|
if (resFFT != VKFFT_SUCCESS) {
|
|
|
|
return resFFT;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for FFT to complete
|
|
|
|
VkMemoryBarrier memoryBarrierOut = {
|
|
|
|
VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
|
|
|
0,
|
|
|
|
VK_ACCESS_SHADER_WRITE_BIT,
|
|
|
|
VK_ACCESS_HOST_READ_BIT,
|
|
|
|
};
|
|
|
|
vkCmdPipelineBarrier(
|
|
|
|
plan->m_commandBuffer,
|
|
|
|
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
|
|
|
VK_PIPELINE_STAGE_HOST_BIT,
|
|
|
|
0,
|
|
|
|
1,
|
|
|
|
&memoryBarrierIn,
|
|
|
|
0, 0, 0, 0);
|
|
|
|
|
|
|
|
// Copy from GPU to CPU
|
|
|
|
VkBufferCopy copyRegionOut = { 0 };
|
|
|
|
copyRegionOut.srcOffset = 0;
|
|
|
|
copyRegionOut.dstOffset = 0;
|
|
|
|
copyRegionOut.size = plan->m_bufferSize;
|
|
|
|
vkCmdCopyBuffer(plan->m_commandBuffer, buffer[0], plan->m_outBuffer, 1, ©RegionOut);
|
|
|
|
|
|
|
|
res = vkEndCommandBuffer(plan->m_commandBuffer);
|
|
|
|
if (res != 0) {
|
|
|
|
return VKFFT_ERROR_FAILED_TO_END_COMMAND_BUFFER;
|
|
|
|
}
|
|
|
|
return VKFFT_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
void VulkanvkFFTEngine::transform()
|
|
|
|
{
|
|
|
|
PROFILER_START()
|
|
|
|
|
|
|
|
VkResult res = VK_SUCCESS;
|
|
|
|
VulkanPlan *plan = reinterpret_cast<VulkanPlan *>(m_currentPlan);
|
|
|
|
|
|
|
|
VkSubmitInfo submitInfo = { VK_STRUCTURE_TYPE_SUBMIT_INFO };
|
|
|
|
submitInfo.commandBufferCount = 1;
|
|
|
|
submitInfo.pCommandBuffers = &plan->m_commandBuffer;
|
|
|
|
res = vkQueueSubmit(vkGPU->queue, 1, &submitInfo, vkGPU->fence);
|
|
|
|
if (res != 0) {
|
|
|
|
qDebug() << "VulkanvkFFTEngine::transform: Failed to submit to queue";
|
|
|
|
}
|
|
|
|
res = vkWaitForFences(vkGPU->device, 1, &vkGPU->fence, VK_TRUE, 100000000000);
|
|
|
|
if (res != 0) {
|
|
|
|
qDebug() << "VulkanvkFFTEngine::transform: Failed to wait for fences";
|
|
|
|
}
|
|
|
|
res = vkResetFences(vkGPU->device, 1, &vkGPU->fence);
|
|
|
|
if (res != 0) {
|
|
|
|
qDebug() << "VulkanvkFFTEngine::transform: Failed to reset fences";
|
|
|
|
}
|
|
|
|
|
|
|
|
PROFILER_STOP(QString("%1 FFT %2").arg(getName()).arg(m_currentPlan->n))
|
|
|
|
}
|
|
|
|
|
|
|
|
vkFFTEngine::Plan *VulkanvkFFTEngine::gpuAllocatePlan()
|
|
|
|
{
|
|
|
|
return new VulkanPlan();
|
|
|
|
}
|
|
|
|
|
|
|
|
void VulkanvkFFTEngine::gpuDeallocatePlan(Plan *p)
|
|
|
|
{
|
|
|
|
VulkanPlan *plan = reinterpret_cast<VulkanPlan *>(p);
|
|
|
|
|
|
|
|
vulkanDeallocateOut(plan);
|
|
|
|
vulkanDeallocateIn(plan);
|
|
|
|
|
|
|
|
vkFreeCommandBuffers(vkGPU->device, vkGPU->commandPool, 1, &plan->m_commandBuffer);
|
|
|
|
vkDestroyBuffer(vkGPU->device, plan->m_buffer, nullptr);
|
|
|
|
vkFreeMemory(vkGPU->device, plan->m_bufferDeviceMemory, nullptr);
|
|
|
|
}
|