mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-12-23 01:55:48 -05:00
Add VkFFT support and profiler
This commit is contained in:
parent
b7de8b8255
commit
5e71da4530
@ -30,6 +30,8 @@ option(BUNDLE "Enable distribution bundle" OFF)
|
||||
set(ARCH_OPT "native" CACHE STRING "Specify instruction set to use. Will be passed directly as `-march` or `/arch:` argument on supported compilers. \
|
||||
'native' option will figure out host machine compatibilities and set flags accordingly (even with MSVC).")
|
||||
option(ENABLE_QT6 "Build with Qt6 rather than Qt5" OFF)
|
||||
option(ENABLE_PROFILER "Enable runtime profiler" OFF)
|
||||
set(VKFFT_BACKEND 1 CACHE STRING "vkFFT Backend: 0 - Vulkan, 1 - CUDA")
|
||||
|
||||
# Sampling devices enablers
|
||||
option(ENABLE_AIRSPY "Enable AirSpy support" ON)
|
||||
@ -574,6 +576,10 @@ else()
|
||||
message(STATUS "Compiling for 16 bit Rx DSP chain")
|
||||
endif()
|
||||
|
||||
if (ENABLE_PROFILER)
|
||||
add_compile_definitions(ENABLE_PROFILER)
|
||||
endif()
|
||||
|
||||
# Set compiler options based on target architecture and selected extensions
|
||||
include(CompilerOptions)
|
||||
|
||||
|
14
external/CMakeLists.txt
vendored
14
external/CMakeLists.txt
vendored
@ -832,6 +832,20 @@ if(ENABLE_FEATURE_SATELLITETRACKER OR ENABLE_CHANNELRX_DEMODAPT)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# VkFFT (header only library)
|
||||
ExternalProject_Add(vkfft
|
||||
GIT_REPOSITORY https://github.com/DTolm/VkFFT.git
|
||||
GIT_TAG v1.3.1
|
||||
PREFIX "${EXTERNAL_BUILD_LIBRARIES}/vkfft"
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
BUILD_BYPRODUCTS ""
|
||||
INSTALL_COMMAND ""
|
||||
TEST_COMMAND ""
|
||||
)
|
||||
ExternalProject_Get_Property(vkfft source_dir)
|
||||
set(VKFFT_INCLUDE_DIR "${source_dir}" CACHE INTERNAL "")
|
||||
|
||||
# requirements needed by many packages on windows
|
||||
if (WIN32)
|
||||
ExternalProject_Add(pthreads4w
|
||||
|
@ -19,18 +19,54 @@ if(FFTW3F_FOUND)
|
||||
add_definitions(-DUSE_FFTW)
|
||||
include_directories(${FFTW3F_INCLUDE_DIRS})
|
||||
set(sdrbase_FFTW3F_LIB ${FFTW3F_LIBRARIES})
|
||||
else(FFTW3F_FOUND)
|
||||
endif(FFTW3F_FOUND)
|
||||
|
||||
# Kiss FFT is always available
|
||||
set(sdrbase_SOURCES
|
||||
${sdrbase_SOURCES}
|
||||
dsp/kissengine.cpp
|
||||
dsp/kissfft.h
|
||||
)
|
||||
set(sdrbase_HEADERS
|
||||
${sdrbase_HEADERS}
|
||||
dsp/kissengine.h
|
||||
)
|
||||
add_definitions(-DUSE_KISSFFT)
|
||||
|
||||
# Vulkan SDK: https://vulkan.lunarg.com/
|
||||
# Windows Vulkan SDK is missing glslang_c_interface.h
|
||||
# See bug: https://vulkan.lunarg.com/issue/view/63d158a85df11200d569b2ab
|
||||
# Copy it from Linux SDK
|
||||
find_package(Vulkan)
|
||||
if(Vulkan_FOUND AND (${VKFFT_BACKEND} EQUAL 0))
|
||||
set(sdrbase_SOURCES
|
||||
${sdrbase_SOURCES}
|
||||
dsp/kissengine.cpp
|
||||
dsp/kissfft.h
|
||||
dsp/vulkanvkfftengine.cpp
|
||||
dsp/vulkanvkfftengine.h
|
||||
)
|
||||
set(sdrbase_HEADERS
|
||||
${sdrbase_HEADERS}
|
||||
dsp/kissengine.h
|
||||
endif()
|
||||
|
||||
# CUDA Toolkit: https://developer.nvidia.com/cuda-downloads
|
||||
find_package(CUDA 9.0)
|
||||
if(CUDA_FOUND AND (${VKFFT_BACKEND} EQUAL 1))
|
||||
enable_language(CUDA)
|
||||
set(sdrbase_SOURCES
|
||||
${sdrbase_SOURCES}
|
||||
dsp/cudavkfftengine.cpp
|
||||
dsp/cudavkfftengine.h
|
||||
)
|
||||
add_definitions(-DUSE_KISSFFT)
|
||||
endif(FFTW3F_FOUND)
|
||||
endif()
|
||||
|
||||
if(Vulkan_FOUND OR CUDA_FOUND)
|
||||
set(sdrbase_SOURCES
|
||||
${sdrbase_SOURCES}
|
||||
dsp/vkfftengine.cpp
|
||||
dsp/vkfftengine.h
|
||||
dsp/vkfftutils.cpp
|
||||
dsp/vkfftutils.h
|
||||
)
|
||||
include_directories(${VKFFT_INCLUDE_DIR})
|
||||
endif()
|
||||
|
||||
if (LIBSIGMF_FOUND)
|
||||
set(sdrbase_SOURCES
|
||||
@ -207,6 +243,7 @@ set(sdrbase_SOURCES
|
||||
util/planespotters.cpp
|
||||
util/png.cpp
|
||||
util/prettyprint.cpp
|
||||
util/profiler.cpp
|
||||
util/radiosonde.cpp
|
||||
util/rtpsink.cpp
|
||||
util/syncmessenger.cpp
|
||||
@ -442,6 +479,7 @@ set(sdrbase_HEADERS
|
||||
util/planespotters.h
|
||||
util/png.h
|
||||
util/prettyprint.h
|
||||
util/profiler.h
|
||||
util/radiosonde.h
|
||||
util/rtpsink.h
|
||||
util/syncmessenger.h
|
||||
@ -507,6 +545,86 @@ if(DEFINED LIBSIGMF_DEPENDS)
|
||||
add_dependencies(sdrbase "${LIBSIGMF_DEPENDS}")
|
||||
endif()
|
||||
|
||||
if(Vulkan_FOUND AND (${VKFFT_BACKEND} EQUAL 0))
|
||||
target_compile_definitions(sdrbase PUBLIC -DVK_API_VERSION=11)
|
||||
target_include_directories(sdrbase PUBLIC ${Vulkan_INCLUDE_DIR} ${Vulkan_INCLUDE_DIR}/glslang/Include)
|
||||
add_compile_definitions(sdrbase VKFFT_BACKEND=0)
|
||||
|
||||
find_library(VULKAN_SPIRV_LIB SPIRV HINTS "$ENV{VULKAN_SDK}/lib" REQUIRED)
|
||||
find_library(VULKAN_SPVREMAPPER_LIB SPVRemapper HINTS "$ENV{VULKAN_SDK}/lib" REQUIRED)
|
||||
find_library(VULKAN_SPIRV_TOOLS_LIB SPIRV-Tools HINTS "$ENV{VULKAN_SDK}/lib" REQUIRED)
|
||||
find_library(VULKAN_SPIRV_TOOLS_OPT_LIB SPIRV-Tools-Opt HINTS "$ENV{VULKAN_SDK}/lib" REQUIRED)
|
||||
find_library(VULKAN_SPIRV_TOOLS_SHARED_LIB SPIRV-Tools-Shared HINTS "$ENV{VULKAN_SDK}/lib" REQUIRED)
|
||||
find_library(VULKAN_HLSL_LIB HLSL HINTS "$ENV{VULKAN_SDK}/lib" REQUIRED)
|
||||
find_library(VULKAN_OGLCOMPILER_LIB OGLCompiler HINTS "$ENV{VULKAN_SDK}/lib" REQUIRED)
|
||||
find_library(VULKAN_OSDEPENDENT_LIB OSDependent HINTS "$ENV{VULKAN_SDK}/lib" REQUIRED)
|
||||
find_library(VULKAN_GLSLANG_LIB glslang HINTS "$ENV{VULKAN_SDK}/lib" REQUIRED)
|
||||
find_library(VULKAN_GLSLANG_RES_LIB glslang-default-resource-limits HINTS "$ENV{VULKAN_SDK}/lib" REQUIRED)
|
||||
find_library(VULKAN_MACHINEINDEPENDENT_LIB MachineIndependent HINTS "$ENV{VULKAN_SDK}/lib" REQUIRED)
|
||||
find_library(VULKAN_GENERICCODEGEN_LIB GenericCodeGen HINTS "$ENV{VULKAN_SDK}/lib" REQUIRED)
|
||||
|
||||
find_library(VULKAN_SPIRVD_LIB SPIRVd HINTS "$ENV{VULKAN_SDK}/lib" REQUIRED)
|
||||
find_library(VULKAN_SPVREMAPPERD_LIB SPVRemapperd HINTS "$ENV{VULKAN_SDK}/lib" REQUIRED)
|
||||
find_library(VULKAN_SPIRV_TOOLSD_LIB SPIRV-Toolsd HINTS "$ENV{VULKAN_SDK}/lib" REQUIRED)
|
||||
find_library(VULKAN_SPIRV_TOOLS_OPTD_LIB SPIRV-Tools-Optd HINTS "$ENV{VULKAN_SDK}/lib" REQUIRED)
|
||||
find_library(VULKAN_SPIRV_TOOLS_SHAREDD_LIB SPIRV-Tools-Sharedd HINTS "$ENV{VULKAN_SDK}/lib" REQUIRED)
|
||||
find_library(VULKAN_HLSLD_LIB HLSLd HINTS "$ENV{VULKAN_SDK}/lib" REQUIRED)
|
||||
find_library(VULKAN_OGLCOMPILERD_LIB OGLCompilerd HINTS "$ENV{VULKAN_SDK}/lib" REQUIRED)
|
||||
find_library(VULKAN_OSDEPENDENTD_LIB OSDependentd HINTS "$ENV{VULKAN_SDK}/lib" REQUIRED)
|
||||
find_library(VULKAN_GLSLANGD_LIB glslangd HINTS "$ENV{VULKAN_SDK}/lib" REQUIRED)
|
||||
find_library(VULKAN_GLSLANG_RESD_LIB glslang-default-resource-limitsd HINTS "$ENV{VULKAN_SDK}/lib" REQUIRED)
|
||||
find_library(VULKAN_MACHINEINDEPENDENTD_LIB MachineIndependentd HINTS "$ENV{VULKAN_SDK}/lib" REQUIRED)
|
||||
find_library(VULKAN_GENERICCODEGEND_LIB GenericCodeGend HINTS "$ENV{VULKAN_SDK}/lib" REQUIRED)
|
||||
|
||||
target_link_libraries(sdrbase
|
||||
optimized ${VULKAN_SPIRV_LIB}
|
||||
optimized ${VULKAN_SPVREMAPPER_LIB}
|
||||
optimized ${VULKAN_SPIRV_TOOLS_LIB}
|
||||
optimized ${VULKAN_SPIRV_TOOLS_OPT_LIB}
|
||||
optimized ${VULKAN_SPIRV_TOOLS_SHARED_LIB}
|
||||
optimized ${VULKAN_HLSL_LIB}
|
||||
optimized ${VULKAN_OGLCOMPILER_LIB}
|
||||
optimized ${VULKAN_OSDEPENDENT_LIB}
|
||||
optimized ${VULKAN_GLSLANG_LIB}
|
||||
optimized ${VULKAN_GLSLANG_RES_LIB}
|
||||
optimized ${VULKAN_MACHINEINDEPENDENT_LIB}
|
||||
optimized ${VULKAN_GENERICCODEGEN_LIB}
|
||||
optimized Vulkan::Vulkan
|
||||
debug ${VULKAN_SPIRVD_LIB}
|
||||
debug ${VULKAN_SPVREMAPPERD_LIB}
|
||||
debug ${VULKAN_SPIRV_TOOLSD_LIB}
|
||||
debug ${VULKAN_SPIRV_TOOLS_OPTD_LIB}
|
||||
debug ${VULKAN_SPIRV_TOOLS_SHAREDD_LIB}
|
||||
debug ${VULKAN_HLSLD_LIB}
|
||||
debug ${VULKAN_OGLCOMPILERD_LIB}
|
||||
debug ${VULKAN_OSDEPENDENTD_LIB}
|
||||
debug ${VULKAN_GLSLANGD_LIB}
|
||||
debug ${VULKAN_GLSLANG_RESD_LIB}
|
||||
debug ${VULKAN_MACHINEINDEPENDENTD_LIB}
|
||||
debug ${VULKAN_GENERICCODEGEND_LIB}
|
||||
Vulkan::Vulkan
|
||||
)
|
||||
endif()
|
||||
|
||||
if(CUDA_FOUND AND (${VKFFT_BACKEND} EQUAL 1))
|
||||
set_property(TARGET sdrbase PROPERTY CUDA_ARCHITECTURES 60 70 75 80 86)
|
||||
add_compile_definitions(sdrbase VKFFT_BACKEND=1)
|
||||
target_compile_options(sdrbase PUBLIC
|
||||
"$<$<COMPILE_LANGUAGE:CUDA>:SHELL:
|
||||
-DVKFFT_BACKEND=1
|
||||
-gencode arch=compute_60,code=compute_60
|
||||
-gencode arch=compute_70,code=compute_70
|
||||
-gencode arch=compute_75,code=compute_75
|
||||
-gencode arch=compute_80,code=compute_80
|
||||
-gencode arch=compute_86,code=compute_86>")
|
||||
set_target_properties(sdrbase PROPERTIES CUDA_SEPARABLE_COMPILATION ON)
|
||||
set_target_properties(sdrbase PROPERTIES CUDA_RESOLVE_DEVICE_SYMBOLS ON)
|
||||
find_library(CUDA_NVRTC_LIB libnvrtc nvrtc HINTS "${CUDA_TOOLKIT_ROOT_DIR}/lib64" "${LIBNVRTC_LIBRARY_DIR}" "${CUDA_TOOLKIT_ROOT_DIR}/lib/x64" /usr/lib64 /usr/local/cuda/lib64 REQUIRED)
|
||||
find_library(CUDA_LIB cuda HINTS "${CUDA_TOOLKIT_ROOT_DIR}/lib64" "${LIBNVRTC_LIBRARY_DIR}" "${CUDA_TOOLKIT_ROOT_DIR}/lib/x64" /usr/lib64 /usr/local/cuda/lib64 REQUIRED)
|
||||
target_link_libraries(sdrbase ${CUDA_LIBRARIES} ${CUDA_LIB} ${CUDA_NVRTC_LIB})
|
||||
target_include_directories(sdrbase PUBLIC ${CUDA_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
target_link_libraries(sdrbase
|
||||
${OPUS_LIBRARIES}
|
||||
${sdrbase_FFTW3F_LIB}
|
||||
|
154
sdrbase/dsp/cudavkfftengine.cpp
Normal file
154
sdrbase/dsp/cudavkfftengine.cpp
Normal file
@ -0,0 +1,154 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// 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 "dsp/cudavkfftengine.h"
|
||||
|
||||
CUDAvkFFTEngine::CUDAvkFFTEngine()
|
||||
{
|
||||
VkFFTResult resFFT;
|
||||
resFFT = gpuInit();
|
||||
if (resFFT != VKFFT_SUCCESS)
|
||||
{
|
||||
qDebug() << "CUDAvkFFTEngine::CUDAvkFFTEngine: Failed to initialise GPU" << getVkFFTErrorString(resFFT);
|
||||
delete vkGPU;
|
||||
vkGPU = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
CUDAvkFFTEngine::~CUDAvkFFTEngine()
|
||||
{
|
||||
if (vkGPU)
|
||||
{
|
||||
freeAll();
|
||||
cuCtxDestroy(vkGPU->context);
|
||||
}
|
||||
}
|
||||
|
||||
const QString CUDAvkFFTEngine::m_name = "vkFFT (CUDA)";
|
||||
|
||||
QString CUDAvkFFTEngine::getName() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
VkFFTResult CUDAvkFFTEngine::gpuInit()
|
||||
{
|
||||
CUresult res = CUDA_SUCCESS;
|
||||
cudaError_t res2 = cudaSuccess;
|
||||
res = cuInit(0);
|
||||
if (res != CUDA_SUCCESS) {
|
||||
return VKFFT_ERROR_FAILED_TO_INITIALIZE;
|
||||
}
|
||||
res2 = cudaSetDevice((int)vkGPU->device_id);
|
||||
if (res2 != cudaSuccess) {
|
||||
return VKFFT_ERROR_FAILED_TO_SET_DEVICE_ID;
|
||||
}
|
||||
res = cuDeviceGet(&vkGPU->device, (int)vkGPU->device_id);
|
||||
if (res != CUDA_SUCCESS) {
|
||||
return VKFFT_ERROR_FAILED_TO_GET_DEVICE;
|
||||
}
|
||||
res = cuDevicePrimaryCtxRetain(&vkGPU->context, (int)vkGPU->device);
|
||||
if (res != CUDA_SUCCESS) {
|
||||
return VKFFT_ERROR_FAILED_TO_CREATE_CONTEXT;
|
||||
}
|
||||
return VKFFT_SUCCESS;
|
||||
}
|
||||
|
||||
VkFFTResult CUDAvkFFTEngine::gpuAllocateBuffers()
|
||||
{
|
||||
cudaError_t res;
|
||||
CUDAPlan *plan = reinterpret_cast<CUDAPlan *>(m_currentPlan);
|
||||
|
||||
// Allocate DMA accessible pinned memory, which may be faster than malloc'ed memory
|
||||
res = cudaHostAlloc(&plan->m_in, sizeof(Complex) * plan->n, cudaHostAllocMapped);
|
||||
if (res != cudaSuccess) {
|
||||
return VKFFT_ERROR_FAILED_TO_ALLOCATE;
|
||||
}
|
||||
res = cudaHostAlloc(&plan->m_out, sizeof(Complex) * plan->n, cudaHostAllocMapped);
|
||||
if (res != cudaSuccess) {
|
||||
return VKFFT_ERROR_FAILED_TO_ALLOCATE;
|
||||
}
|
||||
|
||||
// Allocate GPU memory
|
||||
res = cudaMalloc((void**)&plan->m_buffer, sizeof(cuFloatComplex) * plan->n * 2);
|
||||
if (res != cudaSuccess) {
|
||||
return VKFFT_ERROR_FAILED_TO_ALLOCATE;
|
||||
}
|
||||
|
||||
plan->m_configuration->buffer = (void**)&plan->m_buffer;
|
||||
|
||||
return VKFFT_SUCCESS;
|
||||
}
|
||||
|
||||
VkFFTResult CUDAvkFFTEngine::gpuConfigure()
|
||||
{
|
||||
return VKFFT_SUCCESS;
|
||||
}
|
||||
|
||||
void CUDAvkFFTEngine::transform()
|
||||
{
|
||||
if (m_currentPlan)
|
||||
{
|
||||
CUDAPlan *plan = reinterpret_cast<CUDAPlan *>(m_currentPlan);
|
||||
cudaError_t res = cudaSuccess;
|
||||
void* buffer = ((void**)&plan->m_buffer)[0];
|
||||
|
||||
// Transfer input from CPU to GPU memory
|
||||
PROFILER_START()
|
||||
res = cudaMemcpy(buffer, plan->m_in, plan->m_bufferSize, cudaMemcpyHostToDevice);
|
||||
PROFILER_STOP(QString("%1 TX %2").arg(getName()).arg(m_currentPlan->n))
|
||||
if (res != cudaSuccess) {
|
||||
qDebug() << "CUDAvkFFTEngine::transform: cudaMemcpy host to device failed";
|
||||
}
|
||||
|
||||
// Perform FFT
|
||||
PROFILER_RESTART()
|
||||
VkFFTLaunchParams launchParams = {};
|
||||
VkFFTResult resFFT = VkFFTAppend(plan->m_app, plan->m_inverse ? 1 : -1, &launchParams);
|
||||
PROFILER_STOP(QString("%1 FFT %2").arg(getName()).arg(m_currentPlan->n))
|
||||
if (resFFT != VKFFT_SUCCESS) {
|
||||
qDebug() << "CUDAvkFFTEngine::transform: VkFFTAppend failed:" << getVkFFTErrorString(resFFT);
|
||||
}
|
||||
|
||||
// Transfer result from GPU to CPU memory
|
||||
PROFILER_RESTART()
|
||||
res = cudaMemcpy(plan->m_out, buffer, plan->m_bufferSize, cudaMemcpyDeviceToHost);
|
||||
PROFILER_STOP(QString("%1 RX %2").arg(getName()).arg(m_currentPlan->n))
|
||||
if (res != cudaSuccess) {
|
||||
qDebug() << "CUDAvkFFTEngine::transform: cudaMemcpy device to host failed";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
vkFFTEngine::Plan *CUDAvkFFTEngine::gpuAllocatePlan()
|
||||
{
|
||||
return new CUDAPlan();
|
||||
}
|
||||
|
||||
void CUDAvkFFTEngine::gpuDeallocatePlan(Plan *p)
|
||||
{
|
||||
CUDAPlan *plan = reinterpret_cast<CUDAPlan *>(p);
|
||||
|
||||
cudaFree(plan->m_in);
|
||||
plan->m_in = nullptr;
|
||||
cudaFree(plan->m_out);
|
||||
plan->m_out = nullptr;
|
||||
cudaFree(plan->m_buffer);
|
||||
}
|
52
sdrbase/dsp/cudavkfftengine.h
Normal file
52
sdrbase/dsp/cudavkfftengine.h
Normal file
@ -0,0 +1,52 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// 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/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_CUDAVKFFTENGINE_H
|
||||
#define INCLUDE_CUDAVKFFTENGINE_H
|
||||
|
||||
#include "vkfftengine.h"
|
||||
|
||||
#include <cuda.h>
|
||||
#include <cuda_runtime.h>
|
||||
#include <nvrtc.h>
|
||||
#include <cuda_runtime_api.h>
|
||||
#include <cuComplex.h>
|
||||
|
||||
class SDRBASE_API CUDAvkFFTEngine : public vkFFTEngine {
|
||||
public:
|
||||
CUDAvkFFTEngine();
|
||||
virtual ~CUDAvkFFTEngine();
|
||||
|
||||
void transform() override;
|
||||
QString getName() const override;
|
||||
static const QString m_name;
|
||||
|
||||
protected:
|
||||
|
||||
struct CUDAPlan : Plan {
|
||||
cuFloatComplex* m_buffer; // GPU memory
|
||||
};
|
||||
|
||||
VkFFTResult gpuInit() override;
|
||||
VkFFTResult gpuAllocateBuffers() override;
|
||||
VkFFTResult gpuConfigure() override;
|
||||
Plan *gpuAllocatePlan() override;
|
||||
void gpuDeallocatePlan(Plan *) override;
|
||||
|
||||
};
|
||||
|
||||
#endif // INCLUDE_CUDAVKFFTENGINE_H
|
@ -1,26 +1,110 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2015-2020 Edouard Griffiths, F4EXB //
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// 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 "dsp/fftengine.h"
|
||||
#ifdef USE_KISSFFT
|
||||
#include "dsp/kissengine.h"
|
||||
#endif
|
||||
#ifdef USE_FFTW
|
||||
#include "dsp/fftwengine.h"
|
||||
#endif // USE_FFTW
|
||||
#endif
|
||||
#if VKFFT_BACKEND==0
|
||||
#include "dsp/vulkanvkfftengine.h"
|
||||
#elif VKFFT_BACKEND==1
|
||||
#include "dsp/cudavkfftengine.h"
|
||||
#endif
|
||||
|
||||
QStringList FFTEngine::m_allAvailableEngines;
|
||||
|
||||
FFTEngine::~FFTEngine()
|
||||
{
|
||||
}
|
||||
|
||||
FFTEngine* FFTEngine::create(const QString& fftWisdomFileName)
|
||||
FFTEngine* FFTEngine::create(const QString& fftWisdomFileName, const QString& preferredEngine)
|
||||
{
|
||||
#ifdef USE_FFTW
|
||||
qDebug("FFTEngine::create: using FFTW engine");
|
||||
return new FFTWEngine(fftWisdomFileName);
|
||||
#elif USE_KISSFFT
|
||||
qDebug("FFTEngine::create: using KissFFT engine");
|
||||
(void) fftWisdomFileName;
|
||||
return new KissEngine;
|
||||
#else // USE_KISSFFT
|
||||
qCritical("FFTEngine::create: no engine built");
|
||||
return 0;
|
||||
QStringList allNames = getAllNames();
|
||||
QString engine;
|
||||
|
||||
if (allNames.size() == 0)
|
||||
{
|
||||
// No engines available
|
||||
qCritical("FFTEngine::create: no engine built");
|
||||
return nullptr;
|
||||
}
|
||||
else if (!preferredEngine.isEmpty() && allNames.contains(preferredEngine))
|
||||
{
|
||||
// Use the preferred engine
|
||||
engine = preferredEngine;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use first available
|
||||
engine = allNames[0];
|
||||
}
|
||||
|
||||
qDebug("FFTEngine::create: using %s engine", qPrintable(engine));
|
||||
|
||||
#if VKFFT_BACKEND==0
|
||||
if (engine == VulkanvkFFTEngine::m_name) {
|
||||
return new VulkanvkFFTEngine();
|
||||
}
|
||||
#endif
|
||||
#if VKFFT_BACKEND==1
|
||||
if (engine == CUDAvkFFTEngine::m_name) {
|
||||
return new CUDAvkFFTEngine();
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_FFTW
|
||||
if (engine == FFTWEngine::m_name) {
|
||||
return new FFTWEngine(fftWisdomFileName);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_KISSFFT
|
||||
if (engine == KissEngine::m_name) {
|
||||
return new KissEngine;
|
||||
}
|
||||
#endif
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QStringList FFTEngine::getAllNames()
|
||||
{
|
||||
if (m_allAvailableEngines.size() == 0)
|
||||
{
|
||||
#ifdef USE_FFTW
|
||||
m_allAvailableEngines.append(FFTWEngine::m_name);
|
||||
#endif
|
||||
#ifdef USE_KISSFFT
|
||||
m_allAvailableEngines.append(KissEngine::m_name);
|
||||
#endif
|
||||
#if VKFFT_BACKEND==0
|
||||
VulkanvkFFTEngine vulkanvkFFT;
|
||||
if (vulkanvkFFT.isAvailable()) {
|
||||
m_allAvailableEngines.append(vulkanvkFFT.getName());
|
||||
}
|
||||
#elif VKFFT_BACKEND==1
|
||||
CUDAvkFFTEngine cudavkFFT;
|
||||
if (cudavkFFT.isAvailable()) {
|
||||
m_allAvailableEngines.append(cudavkFFT.getName());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return m_allAvailableEngines;
|
||||
}
|
||||
|
@ -1,9 +1,28 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2015-2020 Edouard Griffiths, F4EXB //
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// 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/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_FFTENGINE_H
|
||||
#define INCLUDE_FFTENGINE_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "dsp/dsptypes.h"
|
||||
#include "util/profiler.h"
|
||||
#include "export.h"
|
||||
|
||||
class SDRBASE_API FFTEngine {
|
||||
@ -18,7 +37,16 @@ public:
|
||||
|
||||
virtual void setReuse(bool reuse) = 0;
|
||||
|
||||
static FFTEngine* create(const QString& fftWisdomFileName);
|
||||
static FFTEngine* create(const QString& fftWisdomFileName, const QString& preferredEngine="");
|
||||
|
||||
virtual bool isAvailable() { return true; } // Is this FFT engine available to be used?
|
||||
virtual QString getName() const = 0; // Get the name of this FFT engine
|
||||
|
||||
static QStringList getAllNames(); // Get names of all available FFT engines
|
||||
|
||||
private:
|
||||
static QStringList m_allAvailableEngines;
|
||||
|
||||
};
|
||||
|
||||
#endif // INCLUDE_FFTENGINE_H
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
#include <QMutexLocker>
|
||||
#include "fftfactory.h"
|
||||
#include "maincore.h"
|
||||
|
||||
FFTFactory::FFTFactory(const QString& fftwWisdomFileName) :
|
||||
m_fftwWisdomFileName(fftwWisdomFileName)
|
||||
@ -69,12 +70,20 @@ void FFTFactory::preallocate(
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int FFTFactory::getEngine(unsigned int fftSize, bool inverse, FFTEngine **engine)
|
||||
unsigned int FFTFactory::getEngine(unsigned int fftSize, bool inverse, FFTEngine **engine, const QString& preferredEngine)
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
std::map<unsigned int, std::vector<AllocatedEngine>>& enginesBySize = inverse ?
|
||||
m_invFFTEngineBySize : m_fftEngineBySize;
|
||||
|
||||
// If no preferred engine requested, use user preference
|
||||
QString requestedEngine = preferredEngine;
|
||||
if (requestedEngine.isEmpty())
|
||||
{
|
||||
const MainSettings& mainSettings = MainCore::instance()->getSettings();
|
||||
requestedEngine = mainSettings.getFFTEngine();
|
||||
}
|
||||
|
||||
if (enginesBySize.find(fftSize) == enginesBySize.end())
|
||||
{
|
||||
qDebug("FFTFactory::getEngine: new FFT %s size: %u", (inverse ? "inv" : "fwd"), fftSize);
|
||||
@ -82,7 +91,7 @@ unsigned int FFTFactory::getEngine(unsigned int fftSize, bool inverse, FFTEngine
|
||||
std::vector<AllocatedEngine>& engines = enginesBySize[fftSize];
|
||||
engines.push_back(AllocatedEngine());
|
||||
engines.back().m_inUse = true;
|
||||
engines.back().m_engine = FFTEngine::create(m_fftwWisdomFileName);
|
||||
engines.back().m_engine = FFTEngine::create(m_fftwWisdomFileName, requestedEngine);
|
||||
engines.back().m_engine->setReuse(false);
|
||||
engines.back().m_engine->configure(fftSize, inverse);
|
||||
*engine = engines.back().m_engine;
|
||||
@ -92,9 +101,10 @@ unsigned int FFTFactory::getEngine(unsigned int fftSize, bool inverse, FFTEngine
|
||||
{
|
||||
unsigned int i = 0;
|
||||
|
||||
// Look for existing engine of requested size and type not currently in use
|
||||
for (; i < enginesBySize[fftSize].size(); i++)
|
||||
{
|
||||
if (!enginesBySize[fftSize][i].m_inUse) {
|
||||
if (!enginesBySize[fftSize][i].m_inUse && (enginesBySize[fftSize][i].m_engine->getName() == requestedEngine)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -112,7 +122,7 @@ unsigned int FFTFactory::getEngine(unsigned int fftSize, bool inverse, FFTEngine
|
||||
qDebug("FFTFactory::getEngine: create engine: %lu FFT %s size: %u", engines.size(), (inverse ? "inv" : "fwd"), fftSize);
|
||||
engines.push_back(AllocatedEngine());
|
||||
engines.back().m_inUse = true;
|
||||
engines.back().m_engine = FFTEngine::create(m_fftwWisdomFileName);
|
||||
engines.back().m_engine = FFTEngine::create(m_fftwWisdomFileName, requestedEngine);
|
||||
engines.back().m_engine->setReuse(false);
|
||||
engines.back().m_engine->configure(fftSize, inverse);
|
||||
*engine = engines.back().m_engine;
|
||||
|
@ -34,7 +34,7 @@ public:
|
||||
~FFTFactory();
|
||||
|
||||
void preallocate(unsigned int minLog2Size, unsigned int maxLog2Size, unsigned int numberFFT, unsigned int numberInvFFT);
|
||||
unsigned int getEngine(unsigned int fftSize, bool inverse, FFTEngine **engine); //!< returns an engine sequence
|
||||
unsigned int getEngine(unsigned int fftSize, bool inverse, FFTEngine **engine, const QString& preferredEngine=""); //!< returns an engine sequence
|
||||
void releaseEngine(unsigned int fftSize, bool inverse, unsigned int engineSequence);
|
||||
|
||||
private:
|
||||
|
@ -15,7 +15,8 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QElapsedTimer>
|
||||
#include <QDebug>
|
||||
|
||||
#include "dsp/fftwengine.h"
|
||||
|
||||
FFTWEngine::FFTWEngine(const QString& fftWisdomFileName) :
|
||||
@ -31,6 +32,13 @@ FFTWEngine::~FFTWEngine()
|
||||
freeAll();
|
||||
}
|
||||
|
||||
const QString FFTWEngine::m_name = "FFTW";
|
||||
|
||||
QString FFTWEngine::getName() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
void FFTWEngine::configure(int n, bool inverse)
|
||||
{
|
||||
if (m_reuse)
|
||||
@ -78,8 +86,12 @@ void FFTWEngine::configure(int n, bool inverse)
|
||||
|
||||
void FFTWEngine::transform()
|
||||
{
|
||||
PROFILER_START()
|
||||
|
||||
if(m_currentPlan != NULL)
|
||||
fftwf_execute(m_currentPlan->plan);
|
||||
|
||||
PROFILER_STOP(QString("%1 %2").arg(getName()).arg(m_currentPlan->n))
|
||||
}
|
||||
|
||||
Complex* FFTWEngine::in()
|
||||
|
@ -38,6 +38,8 @@ public:
|
||||
virtual Complex* out();
|
||||
|
||||
virtual void setReuse(bool reuse) { m_reuse = reuse; }
|
||||
QString getName() const override;
|
||||
static const QString m_name;
|
||||
|
||||
protected:
|
||||
static QMutex m_globalPlanMutex;
|
||||
|
@ -1,5 +1,12 @@
|
||||
#include "dsp/kissengine.h"
|
||||
|
||||
const QString KissEngine::m_name = "Kiss";
|
||||
|
||||
QString KissEngine::getName() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
void KissEngine::configure(int n, bool inverse)
|
||||
{
|
||||
m_fft.configure(n, inverse);
|
||||
@ -11,7 +18,11 @@ void KissEngine::configure(int n, bool inverse)
|
||||
|
||||
void KissEngine::transform()
|
||||
{
|
||||
PROFILER_START()
|
||||
|
||||
m_fft.transform(&m_in[0], &m_out[0]);
|
||||
|
||||
PROFILER_STOP(QString("%1 %2").arg(getName()).arg(m_out.size()))
|
||||
}
|
||||
|
||||
Complex* KissEngine::in()
|
||||
@ -27,4 +38,4 @@ Complex* KissEngine::out()
|
||||
void KissEngine::setReuse(bool reuse)
|
||||
{
|
||||
(void) reuse;
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,8 @@ public:
|
||||
virtual Complex* out();
|
||||
|
||||
virtual void setReuse(bool reuse);
|
||||
QString getName() const override;
|
||||
static const QString m_name;
|
||||
|
||||
protected:
|
||||
typedef kissfft<Real, Complex> KissFFT;
|
||||
|
170
sdrbase/dsp/vkfftengine.cpp
Normal file
170
sdrbase/dsp/vkfftengine.cpp
Normal file
@ -0,0 +1,170 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// 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 "dsp/vkfftengine.h"
|
||||
|
||||
QMutex vkFFTEngine::m_globalPlanMutex;
|
||||
|
||||
vkFFTEngine::vkFFTEngine() :
|
||||
m_currentPlan(nullptr),
|
||||
m_reuse(true)
|
||||
{
|
||||
vkGPU = new VkGPU();
|
||||
memset(vkGPU, sizeof(VkGPU), 0);
|
||||
vkGPU->device_id = 0; // Could be set in GUI to support multiple GPUs
|
||||
}
|
||||
|
||||
vkFFTEngine::~vkFFTEngine()
|
||||
{
|
||||
}
|
||||
|
||||
bool vkFFTEngine::isAvailable()
|
||||
{
|
||||
return vkGPU != nullptr;
|
||||
}
|
||||
|
||||
void vkFFTEngine::configure(int n, bool inverse)
|
||||
{
|
||||
if (m_reuse)
|
||||
{
|
||||
for (const auto plan : m_plans)
|
||||
{
|
||||
if ((plan->n == n) && (plan->m_inverse == inverse))
|
||||
{
|
||||
m_currentPlan = plan;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_currentPlan = gpuAllocatePlan();
|
||||
m_currentPlan->n = n;
|
||||
|
||||
QElapsedTimer t;
|
||||
t.start();
|
||||
m_globalPlanMutex.lock();
|
||||
|
||||
VkFFTResult resFFT;
|
||||
|
||||
// Allocate and intialise plan
|
||||
m_currentPlan->m_configuration = new VkFFTConfiguration();
|
||||
memset(m_currentPlan->m_configuration, sizeof(VkFFTConfiguration), 0);
|
||||
m_currentPlan->m_app = new VkFFTApplication();
|
||||
memset(m_currentPlan->m_app, sizeof(VkFFTApplication), 0);
|
||||
m_currentPlan->m_configuration->FFTdim = 1;
|
||||
m_currentPlan->m_configuration->size[0] = n;
|
||||
m_currentPlan->m_configuration->size[1] = 1;
|
||||
m_currentPlan->m_configuration->size[2] = 1;
|
||||
m_currentPlan->m_configuration->numberBatches = 1;
|
||||
m_currentPlan->m_configuration->performR2C = false;
|
||||
m_currentPlan->m_configuration->performDCT = false;
|
||||
m_currentPlan->m_configuration->doublePrecision = false;
|
||||
m_currentPlan->m_configuration->halfPrecision = false;
|
||||
m_currentPlan->m_bufferSize = sizeof(float) * 2 * n;
|
||||
m_currentPlan->m_inverse = inverse;
|
||||
|
||||
m_currentPlan->m_configuration->device = &vkGPU->device;
|
||||
#if(VKFFT_BACKEND==0)
|
||||
m_currentPlan->m_configuration->queue = &vkGPU->queue;
|
||||
m_currentPlan->m_configuration->fence = &vkGPU->fence;
|
||||
m_currentPlan->m_configuration->commandPool = &vkGPU->commandPool;
|
||||
m_currentPlan->m_configuration->physicalDevice = &vkGPU->physicalDevice;
|
||||
m_currentPlan->m_configuration->isCompilerInitialized = true;
|
||||
#endif
|
||||
m_currentPlan->m_configuration->bufferSize = &m_currentPlan->m_bufferSize;
|
||||
|
||||
resFFT = gpuAllocateBuffers();
|
||||
if (resFFT != VKFFT_SUCCESS)
|
||||
{
|
||||
qDebug() << "vkFFTEngine::configure: gpuAllocateBuffers failed:" << getVkFFTErrorString(resFFT);
|
||||
m_globalPlanMutex.unlock();
|
||||
delete m_currentPlan;
|
||||
m_currentPlan = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
m_currentPlan->m_configuration->bufferSize = &m_currentPlan->m_bufferSize;
|
||||
|
||||
resFFT = initializeVkFFT(m_currentPlan->m_app, *m_currentPlan->m_configuration);
|
||||
if (resFFT != VKFFT_SUCCESS)
|
||||
{
|
||||
qDebug() << "vkFFTEngine::configure: initializeVkFFT failed:" << getVkFFTErrorString(resFFT);
|
||||
m_globalPlanMutex.unlock();
|
||||
delete m_currentPlan;
|
||||
m_currentPlan = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
resFFT = gpuConfigure();
|
||||
if (resFFT != VKFFT_SUCCESS)
|
||||
{
|
||||
qDebug() << "vkFFTEngine::configure: gpuConfigure failed:" << getVkFFTErrorString(resFFT);
|
||||
m_globalPlanMutex.unlock();
|
||||
delete m_currentPlan;
|
||||
m_currentPlan = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
m_globalPlanMutex.unlock();
|
||||
|
||||
qDebug("FFT: creating vkFFT plan (n=%d,%s) took %lld ms", n, inverse ? "inverse" : "forward", t.elapsed());
|
||||
m_plans.push_back(m_currentPlan);
|
||||
}
|
||||
|
||||
Complex* vkFFTEngine::in()
|
||||
{
|
||||
if (m_currentPlan != nullptr) {
|
||||
return m_currentPlan->m_in;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Complex* vkFFTEngine::out()
|
||||
{
|
||||
if (m_currentPlan != nullptr) {
|
||||
return m_currentPlan->m_out;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void vkFFTEngine::freeAll()
|
||||
{
|
||||
for (auto plan : m_plans)
|
||||
{
|
||||
gpuDeallocatePlan(plan);
|
||||
delete plan;
|
||||
}
|
||||
m_plans.clear();
|
||||
}
|
||||
|
||||
vkFFTEngine::Plan::Plan() :
|
||||
m_configuration(nullptr),
|
||||
m_app(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
vkFFTEngine::Plan::~Plan()
|
||||
{
|
||||
if (m_app) {
|
||||
deleteVkFFT(m_app);
|
||||
}
|
||||
delete m_configuration;
|
||||
}
|
71
sdrbase/dsp/vkfftengine.h
Normal file
71
sdrbase/dsp/vkfftengine.h
Normal file
@ -0,0 +1,71 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// 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/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_VKFFTENGINE_H
|
||||
#define INCLUDE_VKFFTENGINE_H
|
||||
|
||||
#include <QMutex>
|
||||
|
||||
#include <vkFFT/vkFFT.h>
|
||||
|
||||
#include "dsp/fftengine.h"
|
||||
#include "dsp/vkfftutils.h"
|
||||
#include "export.h"
|
||||
|
||||
class SDRBASE_API vkFFTEngine : public FFTEngine {
|
||||
public:
|
||||
vkFFTEngine();
|
||||
virtual ~vkFFTEngine();
|
||||
|
||||
virtual void configure(int n, bool inverse);
|
||||
|
||||
virtual Complex* in();
|
||||
virtual Complex* out();
|
||||
|
||||
virtual void setReuse(bool reuse) { m_reuse = reuse; }
|
||||
bool isAvailable() override;
|
||||
|
||||
protected:
|
||||
static QMutex m_globalPlanMutex;
|
||||
|
||||
struct Plan {
|
||||
Plan();
|
||||
virtual ~Plan();
|
||||
int n;
|
||||
uint64_t m_bufferSize;
|
||||
bool m_inverse;
|
||||
VkFFTConfiguration* m_configuration;
|
||||
VkFFTApplication* m_app;
|
||||
Complex* m_in; // CPU memory
|
||||
Complex* m_out;
|
||||
};
|
||||
QList<Plan *> m_plans;
|
||||
Plan* m_currentPlan;
|
||||
bool m_reuse;
|
||||
|
||||
VkGPU *vkGPU;
|
||||
|
||||
virtual VkFFTResult gpuInit() = 0;
|
||||
virtual VkFFTResult gpuAllocateBuffers() = 0;
|
||||
virtual VkFFTResult gpuConfigure() = 0;
|
||||
virtual Plan *gpuAllocatePlan() = 0;
|
||||
virtual void gpuDeallocatePlan(Plan *plan) = 0;
|
||||
|
||||
void freeAll();
|
||||
};
|
||||
|
||||
#endif // INCLUDE_VKFFTENGINE_H
|
327
sdrbase/dsp/vkfftutils.cpp
Normal file
327
sdrbase/dsp/vkfftutils.cpp
Normal file
@ -0,0 +1,327 @@
|
||||
// Selected code from https://github.com/DTolm/VkFFT/blob/master/benchmark_scripts/vkFFT_scripts/src/utils_VkFFT.cpp
|
||||
// Formatting kept the same as source, to allow easier future merges
|
||||
|
||||
#include "vkfftutils.h"
|
||||
|
||||
#if(VKFFT_BACKEND==0)
|
||||
#include "vulkan/vulkan.h"
|
||||
#include "glslang_c_interface.h"
|
||||
#endif
|
||||
|
||||
|
||||
#if(VKFFT_BACKEND==0)
|
||||
|
||||
VkResult CreateDebugUtilsMessengerEXT(VkGPU* vkGPU, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) {
|
||||
//pointer to the function, as it is not part of the core. Function creates debugging messenger
|
||||
PFN_vkCreateDebugUtilsMessengerEXT func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(vkGPU->instance, "vkCreateDebugUtilsMessengerEXT");
|
||||
if (func != NULL) {
|
||||
return func(vkGPU->instance, pCreateInfo, pAllocator, pDebugMessenger);
|
||||
}
|
||||
else {
|
||||
return VK_ERROR_EXTENSION_NOT_PRESENT;
|
||||
}
|
||||
}
|
||||
void DestroyDebugUtilsMessengerEXT(VkGPU* vkGPU, const VkAllocationCallbacks* pAllocator) {
|
||||
//pointer to the function, as it is not part of the core. Function destroys debugging messenger
|
||||
PFN_vkDestroyDebugUtilsMessengerEXT func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(vkGPU->instance, "vkDestroyDebugUtilsMessengerEXT");
|
||||
if (func != NULL) {
|
||||
func(vkGPU->instance, vkGPU->debugMessenger, pAllocator);
|
||||
}
|
||||
}
|
||||
|
||||
static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) {
|
||||
printf("validation layer: %s\n", pCallbackData->pMessage);
|
||||
return VK_FALSE;
|
||||
}
|
||||
|
||||
VkResult setupDebugMessenger(VkGPU* vkGPU) {
|
||||
//function that sets up the debugging messenger
|
||||
if (vkGPU->enableValidationLayers == 0) return VK_SUCCESS;
|
||||
|
||||
VkDebugUtilsMessengerCreateInfoEXT createInfo = { VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT };
|
||||
createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
|
||||
createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
|
||||
createInfo.pfnUserCallback = debugCallback;
|
||||
|
||||
if (CreateDebugUtilsMessengerEXT(vkGPU, &createInfo, NULL, &vkGPU->debugMessenger) != VK_SUCCESS) {
|
||||
return VK_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
VkResult checkValidationLayerSupport() {
|
||||
//check if validation layers are supported when an instance is created
|
||||
uint32_t layerCount;
|
||||
vkEnumerateInstanceLayerProperties(&layerCount, NULL);
|
||||
|
||||
VkLayerProperties* availableLayers = (VkLayerProperties*)malloc(sizeof(VkLayerProperties) * layerCount);
|
||||
if (!availableLayers) return VK_INCOMPLETE;
|
||||
vkEnumerateInstanceLayerProperties(&layerCount, availableLayers);
|
||||
if (availableLayers) {
|
||||
for (uint64_t i = 0; i < layerCount; i++) {
|
||||
if (strcmp("VK_LAYER_KHRONOS_validation", availableLayers[i].layerName) == 0) {
|
||||
free(availableLayers);
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
}
|
||||
free(availableLayers);
|
||||
}
|
||||
else {
|
||||
return VK_INCOMPLETE;
|
||||
}
|
||||
return VK_ERROR_LAYER_NOT_PRESENT;
|
||||
}
|
||||
|
||||
std::vector<const char*> getRequiredExtensions(VkGPU* vkGPU, uint64_t sample_id) {
|
||||
std::vector<const char*> extensions;
|
||||
|
||||
if (vkGPU->enableValidationLayers) {
|
||||
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
}
|
||||
switch (sample_id) {
|
||||
#if (VK_API_VERSION>10)
|
||||
case 2: case 102:
|
||||
extensions.push_back("VK_KHR_get_physical_device_properties2");
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return extensions;
|
||||
}
|
||||
|
||||
VkResult createInstance(VkGPU* vkGPU, uint64_t sample_id) {
|
||||
//create instance - a connection between the application and the Vulkan library
|
||||
VkResult res = VK_SUCCESS;
|
||||
//check if validation layers are supported
|
||||
if (vkGPU->enableValidationLayers == 1) {
|
||||
res = checkValidationLayerSupport();
|
||||
if (res != VK_SUCCESS) return res;
|
||||
}
|
||||
|
||||
VkApplicationInfo applicationInfo = { VK_STRUCTURE_TYPE_APPLICATION_INFO };
|
||||
applicationInfo.pApplicationName = "VkFFT";
|
||||
applicationInfo.applicationVersion = (uint32_t)VkFFTGetVersion();
|
||||
applicationInfo.pEngineName = "VkFFT";
|
||||
applicationInfo.engineVersion = 1;
|
||||
#if (VK_API_VERSION>=12)
|
||||
applicationInfo.apiVersion = VK_API_VERSION_1_2;
|
||||
#elif (VK_API_VERSION==11)
|
||||
applicationInfo.apiVersion = VK_API_VERSION_1_1;
|
||||
#else
|
||||
applicationInfo.apiVersion = VK_API_VERSION_1_0;
|
||||
#endif
|
||||
|
||||
VkInstanceCreateInfo createInfo = { VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO };
|
||||
createInfo.flags = 0;
|
||||
createInfo.pApplicationInfo = &applicationInfo;
|
||||
|
||||
auto extensions = getRequiredExtensions(vkGPU, sample_id);
|
||||
createInfo.enabledExtensionCount = (uint32_t)(extensions.size());
|
||||
createInfo.ppEnabledExtensionNames = extensions.data();
|
||||
|
||||
VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo = { VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT };
|
||||
|
||||
if (vkGPU->enableValidationLayers) {
|
||||
//query for the validation layer support in the instance
|
||||
createInfo.enabledLayerCount = 1;
|
||||
const char* validationLayers = "VK_LAYER_KHRONOS_validation";
|
||||
createInfo.ppEnabledLayerNames = &validationLayers;
|
||||
debugCreateInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
|
||||
debugCreateInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
|
||||
debugCreateInfo.pfnUserCallback = debugCallback;
|
||||
createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo;
|
||||
}
|
||||
else {
|
||||
createInfo.enabledLayerCount = 0;
|
||||
|
||||
createInfo.pNext = nullptr;
|
||||
}
|
||||
|
||||
res = vkCreateInstance(&createInfo, NULL, &vkGPU->instance);
|
||||
if (res != VK_SUCCESS) {
|
||||
return res;
|
||||
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
VkResult findPhysicalDevice(VkGPU* vkGPU) {
|
||||
//check if there are GPUs that support Vulkan and select one
|
||||
VkResult res = VK_SUCCESS;
|
||||
uint32_t deviceCount;
|
||||
res = vkEnumeratePhysicalDevices(vkGPU->instance, &deviceCount, NULL);
|
||||
if (res != VK_SUCCESS) return res;
|
||||
if (deviceCount == 0) {
|
||||
return VK_ERROR_DEVICE_LOST;
|
||||
}
|
||||
|
||||
VkPhysicalDevice* devices = (VkPhysicalDevice*)malloc(sizeof(VkPhysicalDevice) * deviceCount);
|
||||
if (!devices) return VK_INCOMPLETE;
|
||||
res = vkEnumeratePhysicalDevices(vkGPU->instance, &deviceCount, devices);
|
||||
if (res != VK_SUCCESS) return res;
|
||||
if (devices) {
|
||||
vkGPU->physicalDevice = devices[vkGPU->device_id];
|
||||
free(devices);
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
else
|
||||
return VK_INCOMPLETE;
|
||||
}
|
||||
VkResult getComputeQueueFamilyIndex(VkGPU* vkGPU) {
|
||||
//find a queue family for a selected GPU, select the first available for use
|
||||
uint32_t queueFamilyCount;
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(vkGPU->physicalDevice, &queueFamilyCount, NULL);
|
||||
|
||||
VkQueueFamilyProperties* queueFamilies = (VkQueueFamilyProperties*)malloc(sizeof(VkQueueFamilyProperties) * queueFamilyCount);
|
||||
if (!queueFamilies) return VK_INCOMPLETE;
|
||||
if (queueFamilies) {
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(vkGPU->physicalDevice, &queueFamilyCount, queueFamilies);
|
||||
uint64_t i = 0;
|
||||
for (; i < queueFamilyCount; i++) {
|
||||
VkQueueFamilyProperties props = queueFamilies[i];
|
||||
|
||||
if (props.queueCount > 0 && (props.queueFlags & VK_QUEUE_COMPUTE_BIT)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(queueFamilies);
|
||||
if (i == queueFamilyCount) {
|
||||
return VK_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
vkGPU->queueFamilyIndex = i;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
else
|
||||
return VK_INCOMPLETE;
|
||||
}
|
||||
|
||||
VkResult createDevice(VkGPU* vkGPU, uint64_t sample_id) {
|
||||
//create logical device representation
|
||||
VkResult res = VK_SUCCESS;
|
||||
VkDeviceQueueCreateInfo queueCreateInfo = { VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO };
|
||||
res = getComputeQueueFamilyIndex(vkGPU);
|
||||
if (res != VK_SUCCESS) return res;
|
||||
queueCreateInfo.queueFamilyIndex = (uint32_t)vkGPU->queueFamilyIndex;
|
||||
queueCreateInfo.queueCount = 1;
|
||||
float queuePriorities = 1.0;
|
||||
queueCreateInfo.pQueuePriorities = &queuePriorities;
|
||||
VkDeviceCreateInfo deviceCreateInfo = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO };
|
||||
VkPhysicalDeviceFeatures deviceFeatures = {};
|
||||
switch (sample_id) {
|
||||
case 1: case 12: case 17: case 18: case 101: case 201: case 1001: {
|
||||
deviceFeatures.shaderFloat64 = true;
|
||||
deviceCreateInfo.enabledExtensionCount = (uint32_t)vkGPU->enabledDeviceExtensions.size();
|
||||
deviceCreateInfo.ppEnabledExtensionNames = vkGPU->enabledDeviceExtensions.data();
|
||||
deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo;
|
||||
deviceCreateInfo.queueCreateInfoCount = 1;
|
||||
deviceCreateInfo.pEnabledFeatures = &deviceFeatures;
|
||||
res = vkCreateDevice(vkGPU->physicalDevice, &deviceCreateInfo, NULL, &vkGPU->device);
|
||||
if (res != VK_SUCCESS) return res;
|
||||
vkGetDeviceQueue(vkGPU->device, (uint32_t)vkGPU->queueFamilyIndex, 0, &vkGPU->queue);
|
||||
break;
|
||||
}
|
||||
#if (VK_API_VERSION>10)
|
||||
case 2: case 102: {
|
||||
VkPhysicalDeviceFeatures2 deviceFeatures2 = {};
|
||||
VkPhysicalDevice16BitStorageFeatures shaderFloat16 = {};
|
||||
shaderFloat16.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES;
|
||||
shaderFloat16.storageBuffer16BitAccess = true;
|
||||
/*VkPhysicalDeviceShaderFloat16Int8Features shaderFloat16 = {};
|
||||
shaderFloat16.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES;
|
||||
shaderFloat16.shaderFloat16 = true;
|
||||
shaderFloat16.shaderInt8 = true;*/
|
||||
deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
||||
deviceFeatures2.pNext = &shaderFloat16;
|
||||
deviceFeatures2.features = deviceFeatures;
|
||||
vkGetPhysicalDeviceFeatures2(vkGPU->physicalDevice, &deviceFeatures2);
|
||||
deviceCreateInfo.pNext = &deviceFeatures2;
|
||||
vkGPU->enabledDeviceExtensions.push_back("VK_KHR_16bit_storage");
|
||||
deviceCreateInfo.enabledExtensionCount = (uint32_t)vkGPU->enabledDeviceExtensions.size();
|
||||
deviceCreateInfo.ppEnabledExtensionNames = vkGPU->enabledDeviceExtensions.data();
|
||||
deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo;
|
||||
deviceCreateInfo.queueCreateInfoCount = 1;
|
||||
deviceCreateInfo.pEnabledFeatures = NULL;
|
||||
res = vkCreateDevice(vkGPU->physicalDevice, &deviceCreateInfo, NULL, &vkGPU->device);
|
||||
if (res != VK_SUCCESS) return res;
|
||||
vkGetDeviceQueue(vkGPU->device, (uint32_t)vkGPU->queueFamilyIndex, 0, &vkGPU->queue);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default: {
|
||||
deviceCreateInfo.enabledExtensionCount = (uint32_t)vkGPU->enabledDeviceExtensions.size();
|
||||
deviceCreateInfo.ppEnabledExtensionNames = vkGPU->enabledDeviceExtensions.data();
|
||||
deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo;
|
||||
deviceCreateInfo.queueCreateInfoCount = 1;
|
||||
deviceCreateInfo.pEnabledFeatures = NULL;
|
||||
deviceCreateInfo.pEnabledFeatures = &deviceFeatures;
|
||||
res = vkCreateDevice(vkGPU->physicalDevice, &deviceCreateInfo, NULL, &vkGPU->device);
|
||||
if (res != VK_SUCCESS) return res;
|
||||
vkGetDeviceQueue(vkGPU->device, (uint32_t)vkGPU->queueFamilyIndex, 0, &vkGPU->queue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
VkResult createFence(VkGPU* vkGPU) {
|
||||
//create fence for synchronization
|
||||
VkResult res = VK_SUCCESS;
|
||||
VkFenceCreateInfo fenceCreateInfo = { VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
|
||||
fenceCreateInfo.flags = 0;
|
||||
res = vkCreateFence(vkGPU->device, &fenceCreateInfo, NULL, &vkGPU->fence);
|
||||
return res;
|
||||
}
|
||||
VkResult createCommandPool(VkGPU* vkGPU) {
|
||||
//create a place, command buffer memory is allocated from
|
||||
VkResult res = VK_SUCCESS;
|
||||
VkCommandPoolCreateInfo commandPoolCreateInfo = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO };
|
||||
commandPoolCreateInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
||||
commandPoolCreateInfo.queueFamilyIndex = (uint32_t)vkGPU->queueFamilyIndex;
|
||||
res = vkCreateCommandPool(vkGPU->device, &commandPoolCreateInfo, NULL, &vkGPU->commandPool);
|
||||
return res;
|
||||
}
|
||||
|
||||
VkFFTResult findMemoryType(VkGPU* vkGPU, uint64_t memoryTypeBits, uint64_t memorySize, VkMemoryPropertyFlags properties, uint32_t* memoryTypeIndex) {
|
||||
VkPhysicalDeviceMemoryProperties memoryProperties = { 0 };
|
||||
|
||||
vkGetPhysicalDeviceMemoryProperties(vkGPU->physicalDevice, &memoryProperties);
|
||||
|
||||
for (uint64_t i = 0; i < memoryProperties.memoryTypeCount; ++i) {
|
||||
if ((memoryTypeBits & ((uint64_t)1 << i)) && ((memoryProperties.memoryTypes[i].propertyFlags & properties) == properties) && (memoryProperties.memoryHeaps[memoryProperties.memoryTypes[i].heapIndex].size >= memorySize))
|
||||
{
|
||||
memoryTypeIndex[0] = (uint32_t)i;
|
||||
return VKFFT_SUCCESS;
|
||||
}
|
||||
}
|
||||
return VKFFT_ERROR_FAILED_TO_FIND_MEMORY;
|
||||
}
|
||||
|
||||
VkFFTResult allocateBuffer(VkGPU* vkGPU, VkBuffer* buffer, VkDeviceMemory* deviceMemory, VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags propertyFlags, uint64_t size) {
|
||||
//allocate the buffer used by the GPU with specified properties
|
||||
VkFFTResult resFFT = VKFFT_SUCCESS;
|
||||
VkResult res = VK_SUCCESS;
|
||||
uint32_t queueFamilyIndices;
|
||||
VkBufferCreateInfo bufferCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
|
||||
bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
bufferCreateInfo.queueFamilyIndexCount = 1;
|
||||
bufferCreateInfo.pQueueFamilyIndices = &queueFamilyIndices;
|
||||
bufferCreateInfo.size = size;
|
||||
bufferCreateInfo.usage = usageFlags;
|
||||
res = vkCreateBuffer(vkGPU->device, &bufferCreateInfo, NULL, buffer);
|
||||
if (res != VK_SUCCESS) return VKFFT_ERROR_FAILED_TO_CREATE_BUFFER;
|
||||
VkMemoryRequirements memoryRequirements = { 0 };
|
||||
vkGetBufferMemoryRequirements(vkGPU->device, buffer[0], &memoryRequirements);
|
||||
VkMemoryAllocateInfo memoryAllocateInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };
|
||||
memoryAllocateInfo.allocationSize = memoryRequirements.size;
|
||||
resFFT = findMemoryType(vkGPU, memoryRequirements.memoryTypeBits, memoryRequirements.size, propertyFlags, &memoryAllocateInfo.memoryTypeIndex);
|
||||
if (resFFT != VKFFT_SUCCESS) return resFFT;
|
||||
res = vkAllocateMemory(vkGPU->device, &memoryAllocateInfo, NULL, deviceMemory);
|
||||
if (res != VK_SUCCESS) return VKFFT_ERROR_FAILED_TO_ALLOCATE_MEMORY;
|
||||
res = vkBindBufferMemory(vkGPU->device, buffer[0], deviceMemory[0], 0);
|
||||
if (res != VK_SUCCESS) return VKFFT_ERROR_FAILED_TO_BIND_BUFFER_MEMORY;
|
||||
return resFFT;
|
||||
}
|
||||
#endif
|
||||
|
47
sdrbase/dsp/vkfftutils.h
Normal file
47
sdrbase/dsp/vkfftutils.h
Normal file
@ -0,0 +1,47 @@
|
||||
// Selected code from https://github.com/DTolm/VkFFT/blob/master/benchmark_scripts/vkFFT_scripts/include/utils_VkFFT.h
|
||||
|
||||
#ifndef VKFFT_UTILS_H
|
||||
#define VKFFT_UTILS_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <vkFFT/vkFFT.h>
|
||||
|
||||
typedef struct {
|
||||
#if(VKFFT_BACKEND==0)
|
||||
VkInstance instance;
|
||||
VkPhysicalDevice physicalDevice;
|
||||
VkPhysicalDeviceProperties physicalDeviceProperties;
|
||||
VkPhysicalDeviceMemoryProperties physicalDeviceMemoryProperties;
|
||||
VkDevice device;
|
||||
VkDebugUtilsMessengerEXT debugMessenger;
|
||||
uint64_t queueFamilyIndex;
|
||||
VkQueue queue;
|
||||
VkCommandPool commandPool;
|
||||
VkFence fence;
|
||||
std::vector<const char*> enabledDeviceExtensions;
|
||||
uint64_t enableValidationLayers;
|
||||
#elif(VKFFT_BACKEND==1)
|
||||
CUdevice device;
|
||||
CUcontext context;
|
||||
#endif
|
||||
uint64_t device_id;
|
||||
} VkGPU;
|
||||
|
||||
#if(VKFFT_BACKEND==0)
|
||||
VkResult CreateDebugUtilsMessengerEXT(VkGPU* vkGPU, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger);
|
||||
void DestroyDebugUtilsMessengerEXT(VkGPU* vkGPU, const VkAllocationCallbacks* pAllocator);
|
||||
VkResult setupDebugMessenger(VkGPU* vkGPU);
|
||||
VkResult checkValidationLayerSupport();
|
||||
std::vector<const char*> getRequiredExtensions(VkGPU* vkGPU, uint64_t sample_id);
|
||||
VkResult createInstance(VkGPU* vkGPU, uint64_t sample_id);
|
||||
VkResult findPhysicalDevice(VkGPU* vkGPU);
|
||||
VkResult getComputeQueueFamilyIndex(VkGPU* vkGPU);
|
||||
VkResult createDevice(VkGPU* vkGPU, uint64_t sample_id);
|
||||
VkResult createFence(VkGPU* vkGPU);
|
||||
VkResult createCommandPool(VkGPU* vkGPU);
|
||||
VkFFTResult findMemoryType(VkGPU* vkGPU, uint64_t memoryTypeBits, uint64_t memorySize, VkMemoryPropertyFlags properties, uint32_t* memoryTypeIndex);
|
||||
VkFFTResult allocateBuffer(VkGPU* vkGPU, VkBuffer* buffer, VkDeviceMemory* deviceMemory, VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags propertyFlags, uint64_t size);
|
||||
#endif
|
||||
|
||||
#endif // VKFFT_UTILS_H
|
344
sdrbase/dsp/vulkanvkfftengine.cpp
Normal file
344
sdrbase/dsp/vulkanvkfftengine.cpp
Normal file
@ -0,0 +1,344 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// 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);
|
||||
}
|
61
sdrbase/dsp/vulkanvkfftengine.h
Normal file
61
sdrbase/dsp/vulkanvkfftengine.h
Normal file
@ -0,0 +1,61 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// 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/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_VULKANVKFFTENGINE_H
|
||||
#define INCLUDE_VULKANVKFFTENGINE_H
|
||||
|
||||
#include "vkfftengine.h"
|
||||
|
||||
#include "vulkan/vulkan.h"
|
||||
|
||||
class SDRBASE_API VulkanvkFFTEngine : public vkFFTEngine {
|
||||
public:
|
||||
VulkanvkFFTEngine();
|
||||
virtual ~VulkanvkFFTEngine();
|
||||
|
||||
void transform() override;
|
||||
QString getName() const override;
|
||||
static const QString m_name;
|
||||
|
||||
protected:
|
||||
|
||||
struct VulkanPlan : Plan {
|
||||
VkBuffer m_inBuffer; // CPU input memory
|
||||
VkDeviceMemory m_inMemory;
|
||||
VkBuffer m_outBuffer; // CPU output memory
|
||||
VkDeviceMemory m_outMemory;
|
||||
VkBuffer m_buffer; // GPU memory
|
||||
VkDeviceMemory m_bufferDeviceMemory;
|
||||
VkCommandBuffer m_commandBuffer;
|
||||
};
|
||||
|
||||
VkFFTResult gpuInit() override;
|
||||
VkFFTResult gpuAllocateBuffers() override;
|
||||
VkFFTResult gpuConfigure() override;
|
||||
|
||||
Plan *gpuAllocatePlan() override;
|
||||
void gpuDeallocatePlan(Plan *) override;
|
||||
|
||||
VkFFTResult vulkanAllocateOut(VulkanPlan *plan);
|
||||
VkFFTResult vulkanAllocateIn(VulkanPlan *plan);
|
||||
void vulkanDeallocateOut(VulkanPlan *plan);
|
||||
void vulkanDeallocateIn(VulkanPlan *plan);
|
||||
VkFFTResult vulkanAllocateFFTCommand(VulkanPlan *plan);
|
||||
|
||||
};
|
||||
|
||||
#endif // INCLUDE_VULKANVKFFTENGINE_H
|
@ -210,6 +210,13 @@ public:
|
||||
emit preferenceChanged(Preferences::MapSmoothing);
|
||||
}
|
||||
|
||||
const QString& getFFTEngine() const { return m_preferences.getFFTEngine(); }
|
||||
void setFFTEngine(const QString& fftEngine)
|
||||
{
|
||||
m_preferences.setFFTEngine(fftEngine);
|
||||
emit preferenceChanged(Preferences::FFTEngine);
|
||||
}
|
||||
|
||||
signals:
|
||||
void preferenceChanged(int);
|
||||
|
||||
|
@ -25,6 +25,7 @@ void Preferences::resetToDefaults()
|
||||
m_multisampling = 0;
|
||||
m_mapMultisampling = 0;
|
||||
m_mapSmoothing = true;
|
||||
m_fftEngine = "FFTW";
|
||||
}
|
||||
|
||||
QByteArray Preferences::serialize() const
|
||||
@ -47,6 +48,7 @@ QByteArray Preferences::serialize() const
|
||||
s.writeBool((int) AutoUpdatePosition, m_autoUpdatePosition);
|
||||
s.writeS32((int) MapMultisampling, m_mapMultisampling);
|
||||
s.writeBool((int) MapSmoothing, m_mapSmoothing);
|
||||
s.writeString((int) FFTEngine, m_fftEngine);
|
||||
return s.final();
|
||||
}
|
||||
|
||||
@ -105,6 +107,8 @@ bool Preferences::deserialize(const QByteArray& data)
|
||||
d.readS32((int) MapMultisampling, &m_mapMultisampling, 0);
|
||||
d.readBool((int) MapSmoothing, &m_mapSmoothing, true);
|
||||
|
||||
d.readString((int) FFTEngine, &m_fftEngine, "FFTW");
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
|
@ -25,7 +25,8 @@ public:
|
||||
Multisampling,
|
||||
AutoUpdatePosition,
|
||||
MapMultisampling,
|
||||
MapSmoothing
|
||||
MapSmoothing,
|
||||
FFTEngine
|
||||
};
|
||||
|
||||
Preferences();
|
||||
@ -87,6 +88,9 @@ public:
|
||||
bool getMapSmoothing() const { return m_mapSmoothing; }
|
||||
void setMapSmoothing(bool smoothing) { m_mapSmoothing = smoothing; }
|
||||
|
||||
const QString& getFFTEngine() const { return m_fftEngine; }
|
||||
void setFFTEngine(const QString& fftEngine) { m_fftEngine = fftEngine; }
|
||||
|
||||
protected:
|
||||
QString m_sourceDevice; //!< Identification of the source used in R0 tab (GUI flavor) at startup
|
||||
int m_sourceIndex; //!< Index of the source used in R0 tab (GUI flavor) at startup
|
||||
@ -109,6 +113,8 @@ protected:
|
||||
int m_multisampling; //!< Number of samples to use for multisampling anti-aliasing for spectrums (typically 0 or 4)
|
||||
int m_mapMultisampling; //!< Number of samples to use for multisampling anti-aliasing for 2D maps (16 gives best text, if not using mapSmoothing)
|
||||
bool m_mapSmoothing; //!< Whether to use smoothing for text boxes on 2D maps
|
||||
|
||||
QString m_fftEngine; //!< FFT Engine (FFTW, Kiss, vkFFT)
|
||||
};
|
||||
|
||||
#endif // INCLUDE_PREFERENCES_H
|
||||
|
39
sdrbase/util/profiler.cpp
Normal file
39
sdrbase/util/profiler.cpp
Normal file
@ -0,0 +1,39 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// 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 "profiler.h"
|
||||
|
||||
QHash<QString, ProfileData> GlobalProfileData::m_profileData;
|
||||
QMutex GlobalProfileData::m_mutex;
|
||||
|
||||
QHash<QString, ProfileData>& GlobalProfileData::getProfileData()
|
||||
{
|
||||
m_mutex.lock();
|
||||
return m_profileData;
|
||||
}
|
||||
|
||||
void GlobalProfileData::releaseProfileData()
|
||||
{
|
||||
m_mutex.unlock();
|
||||
}
|
||||
|
||||
void GlobalProfileData::resetProfileData()
|
||||
{
|
||||
m_mutex.lock();
|
||||
m_profileData.clear();
|
||||
m_mutex.unlock();
|
||||
}
|
123
sdrbase/util/profiler.h
Normal file
123
sdrbase/util/profiler.h
Normal file
@ -0,0 +1,123 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// 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/>. //
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_UTIL_PROFILEDATA_H_
|
||||
#define INCLUDE_UTIL_PROFILEDATA_H_
|
||||
|
||||
#include <QHash>
|
||||
#include <QMutex>
|
||||
#include <QElapsedTimer>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "export.h"
|
||||
|
||||
// Profiler enables runtime collection of profile data (time taken to execute code)
|
||||
// that can be displayed in the GUI
|
||||
//
|
||||
// PROFILER_START() and PROFILER_STOP() macros should be used in the same function:
|
||||
// void func() {
|
||||
// PROFILER_START()
|
||||
// do_something();
|
||||
// PROFILER_STOP("slow_code")
|
||||
// }
|
||||
//
|
||||
// The parameters to PROFILER_STOP are:
|
||||
// name: Name for the code being profiled. Profiles using the same name are averaged.
|
||||
|
||||
#ifdef ENABLE_PROFILER
|
||||
#define PROFILER_START() \
|
||||
QElapsedTimer profileTimer; \
|
||||
profileTimer.start();
|
||||
#define PROFILER_RESTART() \
|
||||
profileTimer.start();
|
||||
#define PROFILER_STOP(name) \
|
||||
{ \
|
||||
qint64 timeNanoSec = profileTimer.nsecsElapsed(); \
|
||||
QHash<QString, ProfileData>& globalData = GlobalProfileData::getProfileData(); \
|
||||
if (!globalData.contains(name)) { \
|
||||
globalData.insert(name, ProfileData()); \
|
||||
} \
|
||||
ProfileData& profileData = globalData[name]; \
|
||||
profileData.add(timeNanoSec); \
|
||||
GlobalProfileData::releaseProfileData(); \
|
||||
}
|
||||
#else
|
||||
#define PROFILER_START()
|
||||
#define PROFILER_RESTART()
|
||||
#define PROFILER_STOP(name)
|
||||
#endif
|
||||
|
||||
class ProfileData
|
||||
{
|
||||
public:
|
||||
ProfileData() :
|
||||
m_numSamples(0),
|
||||
m_last(0),
|
||||
m_total(0)
|
||||
{ }
|
||||
|
||||
void reset()
|
||||
{
|
||||
m_numSamples = 0;
|
||||
m_total = 0;
|
||||
}
|
||||
|
||||
void add(qint64 sample)
|
||||
{
|
||||
m_last = sample;
|
||||
m_total += sample;
|
||||
m_numSamples++;
|
||||
}
|
||||
|
||||
double getAverage() const
|
||||
{
|
||||
if (m_numSamples > 0) {
|
||||
return m_total / (double)m_numSamples;
|
||||
} else {
|
||||
return nan("");
|
||||
}
|
||||
}
|
||||
|
||||
qint64 getTotal() const { return m_total; }
|
||||
qint64 getLast() const { return m_last; }
|
||||
quint64 getNumSamples() const { return m_numSamples; }
|
||||
|
||||
private:
|
||||
quint64 m_numSamples;
|
||||
qint64 m_last;
|
||||
qint64 m_total;
|
||||
};
|
||||
|
||||
// Global thread-safe profile data that can be displayed in the GUI
|
||||
class SDRBASE_API GlobalProfileData
|
||||
{
|
||||
public:
|
||||
|
||||
// Calls to getProfileData must be paired with releaseProfileData
|
||||
static QHash<QString, ProfileData>& getProfileData();
|
||||
static void releaseProfileData();
|
||||
static void resetProfileData();
|
||||
|
||||
private:
|
||||
|
||||
static QHash<QString, ProfileData> m_profileData;
|
||||
static QMutex m_mutex;
|
||||
|
||||
};
|
||||
|
||||
#endif /* INCLUDE_UTIL_PROFILEDATA_H_ */
|
@ -41,6 +41,7 @@ set(sdrgui_SOURCES
|
||||
gui/featureadddialog.cpp
|
||||
gui/featurelayout.cpp
|
||||
gui/featurepresetsdialog.cpp
|
||||
gui/fftdialog.cpp
|
||||
gui/fftwisdomdialog.cpp
|
||||
gui/flowlayout.cpp
|
||||
gui/framelesswindowresizer.cpp
|
||||
@ -65,8 +66,10 @@ set(sdrgui_SOURCES
|
||||
gui/logslider.cpp
|
||||
gui/loglabelslider.cpp
|
||||
gui/mypositiondialog.cpp
|
||||
gui/nanosecondsdelegate.cpp
|
||||
gui/pluginsdialog.cpp
|
||||
gui/presetitem.cpp
|
||||
gui/profiledialog.cpp
|
||||
gui/rollupcontents.cpp
|
||||
gui/rollupwidget.cpp
|
||||
gui/samplingdevicedialog.cpp
|
||||
@ -157,6 +160,7 @@ set(sdrgui_HEADERS
|
||||
gui/featureadddialog.h
|
||||
gui/featurelayout.h
|
||||
gui/featurepresetsdialog.h
|
||||
gui/fftdialog.h
|
||||
gui/fftwisdomdialog.h
|
||||
gui/flowlayout.h
|
||||
gui/framelesswindowresizer.h
|
||||
@ -181,9 +185,11 @@ set(sdrgui_HEADERS
|
||||
gui/logslider.h
|
||||
gui/loglabelslider.h
|
||||
gui/mypositiondialog.h
|
||||
gui/nanosecondsdelegate.h
|
||||
gui/physicalunit.h
|
||||
gui/pluginsdialog.h
|
||||
gui/presetitem.h
|
||||
gui/profiledialog.h
|
||||
gui/qtcompatibility.h
|
||||
gui/rollupcontents.h
|
||||
gui/rollupwidget.h
|
||||
@ -254,11 +260,13 @@ set(sdrgui_FORMS
|
||||
gui/fmpreemphasisdialog.ui
|
||||
gui/featureadddialog.ui
|
||||
gui/featurepresetsdialog.ui
|
||||
gui/fftdialog.ui
|
||||
gui/fftwisdomdialog.ui
|
||||
gui/glscopegui.ui
|
||||
gui/glspectrumgui.ui
|
||||
gui/graphicsdialog.ui
|
||||
gui/pluginsdialog.ui
|
||||
gui/profiledialog.ui
|
||||
gui/audiodialog.ui
|
||||
gui/audioselectdialog.ui
|
||||
gui/samplingdevicecontrol.ui
|
||||
|
48
sdrgui/gui/fftdialog.cpp
Normal file
48
sdrgui/gui/fftdialog.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// 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 "dsp/fftengine.h"
|
||||
|
||||
#include "fftdialog.h"
|
||||
#include "ui_fftdialog.h"
|
||||
|
||||
FFTDialog::FFTDialog(MainSettings& mainSettings, QWidget* parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::FFTDialog),
|
||||
m_mainSettings(mainSettings)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
for (const auto& engine: FFTEngine::getAllNames()) {
|
||||
ui->fftEngine->addItem(engine);
|
||||
}
|
||||
int idx = ui->fftEngine->findText(m_mainSettings.getFFTEngine());
|
||||
if (idx != -1) {
|
||||
ui->fftEngine->setCurrentIndex(idx);
|
||||
}
|
||||
}
|
||||
|
||||
FFTDialog::~FFTDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void FFTDialog::accept()
|
||||
{
|
||||
m_mainSettings.setFFTEngine(ui->fftEngine->currentText());
|
||||
QDialog::accept();
|
||||
}
|
44
sdrgui/gui/fftdialog.h
Normal file
44
sdrgui/gui/fftdialog.h
Normal file
@ -0,0 +1,44 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// 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/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef SDRGUI_GUI_FFTDIALOG_H_
|
||||
#define SDRGUI_GUI_FFTDIALOG_H_
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
#include "settings/mainsettings.h"
|
||||
#include "export.h"
|
||||
|
||||
namespace Ui {
|
||||
class FFTDialog;
|
||||
}
|
||||
|
||||
class SDRGUI_API FFTDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FFTDialog(MainSettings& mainSettings, QWidget* parent = nullptr);
|
||||
~FFTDialog();
|
||||
|
||||
private slots:
|
||||
void accept();
|
||||
|
||||
private:
|
||||
Ui::FFTDialog *ui;
|
||||
MainSettings& m_mainSettings;
|
||||
};
|
||||
|
||||
#endif // SDRGUI_GUI_FFTDIALOG_H_
|
104
sdrgui/gui/fftdialog.ui
Normal file
104
sdrgui/gui/fftdialog.ui
Normal file
@ -0,0 +1,104 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>FFTDialog</class>
|
||||
<widget class="QDialog" name="FFTDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>298</width>
|
||||
<height>118</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Liberation Sans</family>
|
||||
<pointsize>9</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>FFTW Wisdom file generator</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="exeLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="fftEngineLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Path to fftwf-wisdom executable</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>FFT Engine</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="fftEngine">
|
||||
<property name="toolTip">
|
||||
<string>Select FFT engine.
|
||||
|
||||
Changes only apply to new devices / channels until SDRangel is restarted.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../resources/res.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>FFTDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>FFTDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -34,6 +34,7 @@
|
||||
#include "settings/mainsettings.h"
|
||||
#include "util/messagequeue.h"
|
||||
#include "util/db.h"
|
||||
#include "util/profiler.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
@ -958,6 +959,8 @@ void GLSpectrumView::clearSpectrumHistogram()
|
||||
|
||||
void GLSpectrumView::paintGL()
|
||||
{
|
||||
PROFILER_START()
|
||||
|
||||
if (!m_mutex.tryLock(2)) {
|
||||
return;
|
||||
}
|
||||
@ -1808,6 +1811,29 @@ void GLSpectrumView::paintGL()
|
||||
}
|
||||
|
||||
m_mutex.unlock();
|
||||
|
||||
#ifdef ENABLE_PROFILER
|
||||
if (m_profileName.isEmpty())
|
||||
{
|
||||
// Try to use the window name for the profile name
|
||||
QString windowTitle;
|
||||
for (QWidget *widget = parentWidget(); widget != nullptr; widget = widget->parentWidget())
|
||||
{
|
||||
windowTitle = widget->windowTitle();
|
||||
if (!windowTitle.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Add this address so we get per-spectrum profile data
|
||||
if (windowTitle.isEmpty()) {
|
||||
m_profileName = QString("Spectrum @%1").arg((quint64)this, 0, 16);
|
||||
} else {
|
||||
m_profileName = QString("%1 @%2").arg(windowTitle).arg((quint64)this, 0, 16);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
PROFILER_STOP(m_profileName)
|
||||
} // paintGL
|
||||
|
||||
// Hightlight power band for SFDR
|
||||
|
@ -424,6 +424,10 @@ private:
|
||||
static const QVector4D m_measurementLightMarkerColor;
|
||||
static const QVector4D m_measurementDarkMarkerColor;
|
||||
|
||||
#ifdef ENABLE_PROFILER
|
||||
QString m_profileName;
|
||||
#endif
|
||||
|
||||
void updateWaterfall(const Real *spectrum);
|
||||
void update3DSpectrogram(const Real *spectrum);
|
||||
void updateHistogram(const Real *spectrum);
|
||||
|
48
sdrgui/gui/nanosecondsdelegate.cpp
Normal file
48
sdrgui/gui/nanosecondsdelegate.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// 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 "nanosecondsdelegate.h"
|
||||
|
||||
NanoSecondsDelegate::NanoSecondsDelegate()
|
||||
{
|
||||
}
|
||||
|
||||
QString NanoSecondsDelegate::displayText(const QVariant &value, const QLocale &locale) const
|
||||
{
|
||||
(void) locale;
|
||||
|
||||
if (value.toString() == "")
|
||||
{
|
||||
return "";
|
||||
}
|
||||
else
|
||||
{
|
||||
double timeInNanoSec = value.toDouble();
|
||||
QString s;
|
||||
|
||||
if (timeInNanoSec < 1e3) {
|
||||
s = QString("%1 ns").arg(timeInNanoSec, 0, 'f', 3);
|
||||
} else if (timeInNanoSec < 1e6) {
|
||||
s = QString("%1 us").arg(timeInNanoSec/1e3, 0, 'f', 3);
|
||||
} else if (timeInNanoSec < 1e9) {
|
||||
s = QString("%1 ms").arg(timeInNanoSec/1e6, 0, 'f', 3);
|
||||
} else {
|
||||
s = QString("%1 s").arg(timeInNanoSec/1e9, 0, 'f', 3);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
34
sdrgui/gui/nanosecondsdelegate.h
Normal file
34
sdrgui/gui/nanosecondsdelegate.h
Normal file
@ -0,0 +1,34 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// 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/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef SDRGUI_GUI_NANOSECONDSDELGATE_H
|
||||
#define SDRGUI_GUI_NANOSECONDSDELGATE_H
|
||||
|
||||
#include <QStyledItemDelegate>
|
||||
|
||||
#include "export.h"
|
||||
|
||||
// Delegate for table to display a time that's been measured in nanoseconds in s, ms, us or ns
|
||||
class SDRGUI_API NanoSecondsDelegate : public QStyledItemDelegate {
|
||||
|
||||
public:
|
||||
NanoSecondsDelegate();
|
||||
virtual QString displayText(const QVariant &value, const QLocale &locale) const override;
|
||||
|
||||
};
|
||||
|
||||
#endif // SDRGUI_GUI_NANOSECONDSDELGATE_H
|
135
sdrgui/gui/profiledialog.cpp
Normal file
135
sdrgui/gui/profiledialog.cpp
Normal file
@ -0,0 +1,135 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// 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 <QAbstractButton>
|
||||
|
||||
#include "profiledialog.h"
|
||||
#include "ui_profiledialog.h"
|
||||
#include "gui/nanosecondsdelegate.h"
|
||||
#include "util/profiler.h"
|
||||
|
||||
ProfileDialog::ProfileDialog(QWidget* parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::ProfileDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
connect(&m_timer, &QTimer::timeout, this, &ProfileDialog::updateData);
|
||||
resizeTable();
|
||||
m_timer.start(500);
|
||||
}
|
||||
|
||||
ProfileDialog::~ProfileDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void ProfileDialog::accept()
|
||||
{
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
void ProfileDialog::clicked(QAbstractButton *button)
|
||||
{
|
||||
if (ui->buttonBox->buttonRole(button) == QDialogButtonBox::ButtonRole::ResetRole)
|
||||
{
|
||||
ui->table->setRowCount(0);
|
||||
GlobalProfileData::resetProfileData();
|
||||
}
|
||||
}
|
||||
|
||||
void ProfileDialog::resizeTable()
|
||||
{
|
||||
int row = ui->table->rowCount();
|
||||
ui->table->setRowCount(row + 1);
|
||||
ui->table->setItem(row, COL_NAME, new QTableWidgetItem("Random-SDR[0] Spectrum @12345678910"));
|
||||
ui->table->setItem(row, COL_TOTAL, new QTableWidgetItem("1000.000 ms"));
|
||||
ui->table->setItem(row, COL_AVERAGE, new QTableWidgetItem("1000.000 ns/frame"));
|
||||
ui->table->setItem(row, COL_LAST, new QTableWidgetItem("1000.000 ms"));
|
||||
ui->table->setItem(row, COL_NUM_SAMPLES, new QTableWidgetItem("1000000000"));
|
||||
ui->table->resizeColumnsToContents();
|
||||
ui->table->setRowCount(row);
|
||||
}
|
||||
|
||||
// Update table with latest profile data
|
||||
void ProfileDialog::updateData()
|
||||
{
|
||||
QHash<QString, ProfileData>& profileData = GlobalProfileData::getProfileData();
|
||||
|
||||
QHashIterator<QString, ProfileData> itr(profileData);
|
||||
|
||||
while (itr.hasNext())
|
||||
{
|
||||
itr.next();
|
||||
QString key = itr.key();
|
||||
const ProfileData& data = itr.value();
|
||||
double totalTime = data.getTotal();
|
||||
double averageTime = data.getAverage();
|
||||
double lastTime = data.getLast();
|
||||
|
||||
int i = 0;
|
||||
for (; i < ui->table->rowCount(); i++)
|
||||
{
|
||||
QString name = ui->table->item(i, COL_NAME)->text();
|
||||
if (name == key)
|
||||
{
|
||||
// Update existing row
|
||||
ui->table->item(i, COL_TOTAL)->setData(Qt::DisplayRole, totalTime);
|
||||
ui->table->item(i, COL_AVERAGE)->setData(Qt::DisplayRole, averageTime);
|
||||
ui->table->item(i, COL_LAST)->setData(Qt::DisplayRole, lastTime);
|
||||
ui->table->item(i, COL_NUM_SAMPLES)->setData(Qt::DisplayRole, data.getNumSamples());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i >= ui->table->rowCount())
|
||||
{
|
||||
// Add new row
|
||||
ui->table->setSortingEnabled(false);
|
||||
int row = ui->table->rowCount();
|
||||
ui->table->setRowCount(row + 1);
|
||||
|
||||
QTableWidgetItem *name = new QTableWidgetItem(key);
|
||||
QTableWidgetItem *total = new QTableWidgetItem();
|
||||
QTableWidgetItem *average = new QTableWidgetItem();
|
||||
QTableWidgetItem *last = new QTableWidgetItem();
|
||||
QTableWidgetItem *numSamples = new QTableWidgetItem();
|
||||
|
||||
ui->table->setItem(row, COL_NAME, name);
|
||||
ui->table->setItem(row, COL_TOTAL, total);
|
||||
ui->table->setItem(row, COL_AVERAGE, average);
|
||||
ui->table->setItem(row, COL_LAST, last);
|
||||
ui->table->setItem(row, COL_NUM_SAMPLES, numSamples);
|
||||
|
||||
total->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||
average->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||
last->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||
numSamples->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||
|
||||
total->setData(Qt::DisplayRole, totalTime);
|
||||
average->setData(Qt::DisplayRole, averageTime);
|
||||
last->setData(Qt::DisplayRole, lastTime);
|
||||
numSamples->setData(Qt::DisplayRole, data.getNumSamples());
|
||||
|
||||
ui->table->setItemDelegateForColumn(COL_TOTAL, new NanoSecondsDelegate());
|
||||
ui->table->setItemDelegateForColumn(COL_AVERAGE, new NanoSecondsDelegate());
|
||||
ui->table->setItemDelegateForColumn(COL_LAST, new NanoSecondsDelegate());
|
||||
|
||||
ui->table->setSortingEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
GlobalProfileData::releaseProfileData();
|
||||
}
|
59
sdrgui/gui/profiledialog.h
Normal file
59
sdrgui/gui/profiledialog.h
Normal file
@ -0,0 +1,59 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// 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/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef SDRGUI_GUI_PROFILEDIALOG_H_
|
||||
#define SDRGUI_GUI_PROFILEDIALOG_H_
|
||||
|
||||
#include <QDialog>
|
||||
#include <QTimer>
|
||||
|
||||
#include "settings/mainsettings.h"
|
||||
#include "export.h"
|
||||
|
||||
namespace Ui {
|
||||
class ProfileDialog;
|
||||
}
|
||||
|
||||
class QAbstractButton;
|
||||
|
||||
class SDRGUI_API ProfileDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ProfileDialog(QWidget* parent = nullptr);
|
||||
~ProfileDialog();
|
||||
|
||||
private slots:
|
||||
void accept();
|
||||
void clicked(QAbstractButton *button);
|
||||
void updateData();
|
||||
|
||||
private:
|
||||
Ui::ProfileDialog *ui;
|
||||
QTimer m_timer;
|
||||
|
||||
enum Cols {
|
||||
COL_NAME,
|
||||
COL_TOTAL,
|
||||
COL_AVERAGE,
|
||||
COL_LAST,
|
||||
COL_NUM_SAMPLES
|
||||
};
|
||||
|
||||
void resizeTable();
|
||||
};
|
||||
|
||||
#endif // SDRGUI_GUI_PROFILEDIALOG_H_
|
142
sdrgui/gui/profiledialog.ui
Normal file
142
sdrgui/gui/profiledialog.ui
Normal file
@ -0,0 +1,142 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ProfileDialog</class>
|
||||
<widget class="QDialog" name="ProfileDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>700</width>
|
||||
<height>296</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Liberation Sans</family>
|
||||
<pointsize>9</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Profile Data</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="layout">
|
||||
<item>
|
||||
<widget class="QTableWidget" name="table">
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Name of profile</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Total</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Total time spent executing the code</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Average</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Average time executing the code</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Last</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Time for last execution of the code</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Samples</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Number of times code was executed</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Close|QDialogButtonBox::Reset</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../resources/res.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>ProfileDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>ProfileDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>clicked(QAbstractButton*)</signal>
|
||||
<receiver>ProfileDialog</receiver>
|
||||
<slot>clicked(QAbstractButton*)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>191</x>
|
||||
<y>275</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>191</x>
|
||||
<y>147</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<slots>
|
||||
<slot>clicked(QAbstractButton*)</slot>
|
||||
</slots>
|
||||
</ui>
|
@ -65,6 +65,7 @@
|
||||
#include "gui/deviceuserargsdialog.h"
|
||||
#include "gui/sdrangelsplash.h"
|
||||
#include "gui/mypositiondialog.h"
|
||||
#include "gui/fftdialog.h"
|
||||
#include "gui/fftwisdomdialog.h"
|
||||
#include "gui/workspace.h"
|
||||
#include "gui/featurepresetsdialog.h"
|
||||
@ -73,6 +74,7 @@
|
||||
#include "gui/configurationsdialog.h"
|
||||
#include "gui/dialogpositioner.h"
|
||||
#include "gui/welcomedialog.h"
|
||||
#include "gui/profiledialog.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/spectrumvis.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
@ -121,6 +123,7 @@ MainWindow::MainWindow(qtwebapp::LoggerWithFile *logger, const MainParser& parse
|
||||
m_dateTimeWidget(nullptr),
|
||||
m_showSystemWidget(nullptr),
|
||||
m_commandKeyReceiver(nullptr),
|
||||
m_profileDialog(nullptr),
|
||||
m_fftWisdomProcess(nullptr)
|
||||
{
|
||||
#ifdef ANDROID
|
||||
@ -190,7 +193,11 @@ MainWindow::MainWindow(qtwebapp::LoggerWithFile *logger, const MainParser& parse
|
||||
connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus()));
|
||||
m_statusTimer.start(1000);
|
||||
|
||||
splash->showStatusMessage("allocate FFTs...", Qt::white);
|
||||
splash->showStatusMessage("load settings...", Qt::white);
|
||||
qDebug() << "MainWindow::MainWindow: load settings...";
|
||||
|
||||
loadSettings();
|
||||
|
||||
splash->showStatusMessage("allocate FFTs...", Qt::white);
|
||||
|
||||
if (parser.getFFTWFWisdomFileName().length() != 0)
|
||||
@ -213,11 +220,6 @@ MainWindow::MainWindow(qtwebapp::LoggerWithFile *logger, const MainParser& parse
|
||||
|
||||
m_dspEngine->preAllocateFFTs();
|
||||
|
||||
splash->showStatusMessage("load settings...", Qt::white);
|
||||
qDebug() << "MainWindow::MainWindow: load settings...";
|
||||
|
||||
loadSettings();
|
||||
|
||||
splash->showStatusMessage("load plugins...", Qt::white);
|
||||
qDebug() << "MainWindow::MainWindow: load plugins...";
|
||||
|
||||
@ -322,6 +324,7 @@ MainWindow::~MainWindow()
|
||||
removeAllFeatureSets();
|
||||
|
||||
delete m_commandKeyReceiver;
|
||||
delete m_profileDialog;
|
||||
|
||||
for (const auto& workspace : m_workspaces) {
|
||||
delete workspace;
|
||||
@ -1657,6 +1660,11 @@ void MainWindow::createMenuBar(QToolButton *button)
|
||||
keepscreenonAction->setCheckable(true);
|
||||
QObject::connect(keepscreenonAction, &QAction::triggered, this, &MainWindow::on_action_View_KeepScreenOn_toggled);
|
||||
#endif
|
||||
#ifdef ENABLE_PROFILER
|
||||
QAction *profileAction = viewMenu->addAction("&Profile data...");
|
||||
profileAction->setToolTip("View profile data");
|
||||
QObject::connect(profileAction, &QAction::triggered, this, &MainWindow::on_action_Profile_triggered);
|
||||
#endif
|
||||
|
||||
QAction *newWorkspaceAction = workspacesMenu->addAction("&New");
|
||||
newWorkspaceAction->setToolTip("Add a new workspace");
|
||||
@ -1684,8 +1692,11 @@ void MainWindow::createMenuBar(QToolButton *button)
|
||||
myPositionAction->setToolTip("Set station position");
|
||||
QObject::connect(myPositionAction, &QAction::triggered, this, &MainWindow::on_action_My_Position_triggered);
|
||||
QAction *fftAction = preferencesMenu->addAction("&FFT...");
|
||||
fftAction->setToolTip("Set FFT cache");
|
||||
fftAction->setToolTip("Set FFT preferences");
|
||||
QObject::connect(fftAction, &QAction::triggered, this, &MainWindow::on_action_FFT_triggered);
|
||||
QAction *fftWisdomAction = preferencesMenu->addAction("&FFTW Wisdom...");
|
||||
fftWisdomAction->setToolTip("Set FFTW cache");
|
||||
QObject::connect(fftWisdomAction, &QAction::triggered, this, &MainWindow::on_action_FFTWisdom_triggered);
|
||||
QMenu *devicesMenu = preferencesMenu->addMenu("&Devices");
|
||||
QAction *userArgumentsAction = devicesMenu->addAction("&User arguments...");
|
||||
userArgumentsAction->setToolTip("Device custom user arguments");
|
||||
@ -1744,6 +1755,10 @@ void MainWindow::closeEvent(QCloseEvent *closeEvent)
|
||||
removeLastDeviceSet();
|
||||
}
|
||||
|
||||
if (m_profileDialog) {
|
||||
m_profileDialog->close();
|
||||
}
|
||||
|
||||
closeEvent->accept();
|
||||
}
|
||||
|
||||
@ -2223,6 +2238,17 @@ void MainWindow::on_action_View_Fullscreen_toggled(bool checked)
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_action_Profile_triggered()
|
||||
{
|
||||
if (m_profileDialog == nullptr)
|
||||
{
|
||||
m_profileDialog = new ProfileDialog();
|
||||
new DialogPositioner(m_profileDialog, true);
|
||||
}
|
||||
m_profileDialog->show();
|
||||
m_profileDialog->raise();
|
||||
}
|
||||
|
||||
void MainWindow::commandKeysConnect(QObject *object, const char *slot)
|
||||
{
|
||||
setFocus();
|
||||
@ -2345,6 +2371,14 @@ void MainWindow::on_action_commands_triggered()
|
||||
void MainWindow::on_action_FFT_triggered()
|
||||
{
|
||||
qDebug("MainWindow::on_action_FFT_triggered");
|
||||
FFTDialog fftDialog(m_mainCore->m_settings, this);
|
||||
new DialogPositioner(&fftDialog, true);
|
||||
fftDialog.exec();
|
||||
}
|
||||
|
||||
void MainWindow::on_action_FFTWisdom_triggered()
|
||||
{
|
||||
qDebug("MainWindow::on_action_FFTWisdom_triggered");
|
||||
|
||||
if (m_fftWisdomProcess)
|
||||
{
|
||||
|
@ -59,6 +59,7 @@ class Command;
|
||||
class FeatureSetPreset;
|
||||
class CommandKeyReceiver;
|
||||
class ConfigurationsDialog;
|
||||
class ProfileDialog;
|
||||
|
||||
class QMenuBar;
|
||||
class Workspace;
|
||||
@ -127,6 +128,7 @@ private:
|
||||
QAction *m_spectrumToggleViewAction;
|
||||
|
||||
CommandKeyReceiver *m_commandKeyReceiver;
|
||||
ProfileDialog *m_profileDialog;
|
||||
|
||||
QProcess *m_fftWisdomProcess;
|
||||
|
||||
@ -183,12 +185,14 @@ private slots:
|
||||
void on_action_View_KeepScreenOn_toggled(bool checked);
|
||||
#endif
|
||||
void on_action_View_Fullscreen_toggled(bool checked);
|
||||
void on_action_Profile_triggered();
|
||||
void on_action_saveAll_triggered();
|
||||
void on_action_Configurations_triggered();
|
||||
void on_action_Audio_triggered();
|
||||
void on_action_Graphics_triggered();
|
||||
void on_action_Logging_triggered();
|
||||
void on_action_FFT_triggered();
|
||||
void on_action_FFTWisdom_triggered();
|
||||
void on_action_My_Position_triggered();
|
||||
void on_action_DeviceUserArguments_triggered();
|
||||
void on_action_commands_triggered();
|
||||
|
Loading…
Reference in New Issue
Block a user