├── .gitignore ├── images └── empty.jpg ├── fonts ├── OpenSans-Bold.ttf └── OpenSans-Regular.ttf ├── shaders ├── noop_comp.spv ├── texture_frag.spv ├── texture_vert.spv ├── texture.frag ├── texture.vert ├── noop.comp ├── pixelate.comp ├── solarize.comp ├── crop.comp ├── nearestneighbor.comp ├── emboss.comp ├── lanczos.comp └── gaussblur.comp ├── screenshots ├── ShaderDev01.jpg └── ShaderDev02.jpg ├── src ├── globals.h ├── outputedit.cpp ├── outputedit.h ├── linenumberarea.cpp ├── vulkanview.h ├── linenumberarea.h ├── resources.qrc ├── mainwindow.h ├── mainwindow.cpp ├── vulkanwindow.h ├── main.cpp ├── syntaxhighlighter.h ├── codeedit.h ├── windowmanager.h ├── controlswidget.h ├── vulkanview.cpp ├── vulkanwindow.cpp ├── stylesheet.qss ├── ShaderDev.pro ├── controlswidget.cpp ├── windowmanager.cpp ├── codeedit.cpp ├── mainwindow.ui ├── controlswidget.ui ├── syntaxhighlighter.cpp ├── vulkanrenderer.h └── vulkanrenderer.cpp └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | screenshots/ 2 | -------------------------------------------------------------------------------- /images/empty.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttddee/ShaderDev/HEAD/images/empty.jpg -------------------------------------------------------------------------------- /fonts/OpenSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttddee/ShaderDev/HEAD/fonts/OpenSans-Bold.ttf -------------------------------------------------------------------------------- /shaders/noop_comp.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttddee/ShaderDev/HEAD/shaders/noop_comp.spv -------------------------------------------------------------------------------- /shaders/texture_frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttddee/ShaderDev/HEAD/shaders/texture_frag.spv -------------------------------------------------------------------------------- /shaders/texture_vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttddee/ShaderDev/HEAD/shaders/texture_vert.spv -------------------------------------------------------------------------------- /fonts/OpenSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttddee/ShaderDev/HEAD/fonts/OpenSans-Regular.ttf -------------------------------------------------------------------------------- /screenshots/ShaderDev01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttddee/ShaderDev/HEAD/screenshots/ShaderDev01.jpg -------------------------------------------------------------------------------- /screenshots/ShaderDev02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttddee/ShaderDev/HEAD/screenshots/ShaderDev02.jpg -------------------------------------------------------------------------------- /src/globals.h: -------------------------------------------------------------------------------- 1 | #ifndef GLOBALS_H 2 | #define GLOBALS_H 3 | 4 | #include 5 | 6 | namespace ShaderDev { 7 | 8 | typedef std::vector ShaderCode; 9 | 10 | } 11 | 12 | #endif // GLOBALS_H 13 | -------------------------------------------------------------------------------- /shaders/texture.frag: -------------------------------------------------------------------------------- 1 | #version 440 2 | 3 | layout(location = 0) in vec2 v_texcoord; 4 | 5 | layout(location = 0) out vec4 fragColor; 6 | 7 | layout(binding = 1) uniform sampler2D tex; 8 | 9 | void main() 10 | { 11 | fragColor = texture(tex, v_texcoord); 12 | } 13 | -------------------------------------------------------------------------------- /src/outputedit.cpp: -------------------------------------------------------------------------------- 1 | #include "outputedit.h" 2 | 3 | #include 4 | 5 | OutputEdit::OutputEdit(QWidget *parent) 6 | : QTextEdit(parent) 7 | { 8 | this->setReadOnly(true); 9 | } 10 | 11 | void OutputEdit::setErrorMsg(const std::string &msg) 12 | { 13 | this->setText(QString::fromStdString(msg)); 14 | } 15 | -------------------------------------------------------------------------------- /src/outputedit.h: -------------------------------------------------------------------------------- 1 | #ifndef OUTPUTEDIT_H 2 | #define OUTPUTEDIT_H 3 | 4 | #include 5 | #include 6 | 7 | class OutputEdit : public QTextEdit 8 | { 9 | public: 10 | OutputEdit(QWidget *parent = nullptr); 11 | 12 | void setErrorMsg(const std::string& msg); 13 | }; 14 | 15 | #endif // OUTPUTEDIT_H 16 | -------------------------------------------------------------------------------- /src/linenumberarea.cpp: -------------------------------------------------------------------------------- 1 | #include "linenumberarea.h" 2 | 3 | LineNumberArea::LineNumberArea(CodeEdit *editor) 4 | : QWidget(editor) 5 | { 6 | codeEditor = editor; 7 | } 8 | 9 | QSize LineNumberArea::sizeHint() const 10 | { 11 | return QSize(codeEditor->lineNumberAreaWidth(), 0); 12 | } 13 | 14 | void LineNumberArea::paintEvent(QPaintEvent *event) 15 | { 16 | (codeEditor->lineNumberAreaPaintEvent(event)); 17 | } 18 | -------------------------------------------------------------------------------- /shaders/texture.vert: -------------------------------------------------------------------------------- 1 | #version 440 2 | 3 | layout(location = 0) in vec4 position; 4 | layout(location = 1) in vec2 texcoord; 5 | 6 | layout(location = 0) out vec2 v_texcoord; 7 | 8 | layout(std140, binding = 0) uniform buf { 9 | mat4 mvp; 10 | } ubuf; 11 | 12 | out gl_PerVertex { vec4 gl_Position; }; 13 | 14 | void main() 15 | { 16 | v_texcoord = texcoord; 17 | gl_Position = ubuf.mvp * position; 18 | } 19 | -------------------------------------------------------------------------------- /src/vulkanview.h: -------------------------------------------------------------------------------- 1 | #ifndef VULKANVIEW_H 2 | #define VULKANVIEW_H 3 | 4 | #include 5 | 6 | #include "vulkanwindow.h" 7 | 8 | class VulkanView : public QWidget 9 | { 10 | Q_OBJECT 11 | public: 12 | explicit VulkanView(QWidget *parent = nullptr); 13 | VulkanWindow* getVulkanWindow(); 14 | 15 | private: 16 | QWidget* vulkanWrapper; 17 | VulkanWindow* vulkanWindow; 18 | QVulkanInstance instance; 19 | 20 | }; 21 | 22 | #endif // VULKANVIEW_H 23 | -------------------------------------------------------------------------------- /src/linenumberarea.h: -------------------------------------------------------------------------------- 1 | #ifndef LINENUMBERAREA_H 2 | #define LINENUMBERAREA_H 3 | 4 | #include 5 | #include 6 | 7 | #include "codeedit.h" 8 | 9 | class LineNumberArea : public QWidget 10 | { 11 | Q_OBJECT 12 | 13 | public: 14 | LineNumberArea(CodeEdit *editor); 15 | 16 | QSize sizeHint() const; 17 | 18 | protected: 19 | void paintEvent(QPaintEvent *event); 20 | 21 | private: 22 | CodeEdit *codeEditor; 23 | }; 24 | #endif // LINENUMBERAREA_H 25 | -------------------------------------------------------------------------------- /shaders/noop.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (local_size_x = 16, local_size_y = 16) in; 4 | layout (binding = 0, rgba8) uniform readonly image2D inputImage; 5 | layout (binding = 1, rgba8) uniform image2D resultImage; 6 | 7 | void main() 8 | { 9 | ivec2 pixelCoords = ivec2(gl_GlobalInvocationID.xy); 10 | 11 | vec3 rgb = imageLoad(inputImage, pixelCoords).rgb; 12 | 13 | vec4 pixel = vec4(rgb, 1.0); 14 | 15 | imageStore(resultImage, pixelCoords, pixel); 16 | } -------------------------------------------------------------------------------- /src/resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | ../fonts/OpenSans-Bold.ttf 4 | ../fonts/OpenSans-Regular.ttf 5 | stylesheet.qss 6 | ../images/empty.jpg 7 | ../shaders/noop_comp.spv 8 | ../shaders/texture_frag.spv 9 | ../shaders/texture_vert.spv 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | 6 | #include "syntaxhighlighter.h" 7 | #include "windowmanager.h" 8 | 9 | QT_BEGIN_NAMESPACE 10 | namespace Ui { class MainWindow; } 11 | QT_END_NAMESPACE 12 | 13 | class MainWindow : public QMainWindow 14 | { 15 | Q_OBJECT 16 | 17 | public: 18 | MainWindow(QWidget *parent = nullptr); 19 | 20 | void setStatusMessage(const QString& text); 21 | 22 | ~MainWindow(); 23 | 24 | private: 25 | Ui::MainWindow *ui; 26 | SyntaxHighlighter *highlighter; 27 | WindowManager* wManager; 28 | 29 | }; 30 | #endif // MAINWINDOW_H 31 | -------------------------------------------------------------------------------- /shaders/pixelate.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (local_size_x = 16, local_size_y = 16) in; 4 | layout (binding = 0, rgba8) uniform readonly image2D inputImage; 5 | layout (binding = 1, rgba8) uniform image2D resultImage; 6 | 7 | int filterSize = 10; 8 | 9 | void main() 10 | { 11 | if(gl_GlobalInvocationID.x % filterSize == 0 && gl_GlobalInvocationID.y % filterSize == 0) 12 | { 13 | vec3 rgb = imageLoad(inputImage, ivec2(gl_GlobalInvocationID.xy)).rgb; 14 | 15 | vec4 res = vec4(vec3(rgb), 1.0); 16 | 17 | for(int i = 0; i < filterSize; i++) 18 | { 19 | for (int j = 0; j < filterSize; j++) 20 | { 21 | imageStore(resultImage, ivec2(gl_GlobalInvocationID.x + i, gl_GlobalInvocationID.y + j), res); 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "ui_mainwindow.h" 3 | 4 | #include 5 | 6 | MainWindow::MainWindow(QWidget *parent) 7 | : QMainWindow(parent) 8 | , ui(new Ui::MainWindow) 9 | { 10 | ui->setupUi(this); 11 | 12 | highlighter = new SyntaxHighlighter(ui->codeEdit->document()); 13 | 14 | wManager = new WindowManager(this, 15 | ui->controlsWidget, 16 | ui->vulkanWidget->getVulkanWindow(), 17 | ui->codeEdit, 18 | ui->outputEdit); 19 | } 20 | 21 | void MainWindow::setStatusMessage(const QString &text) 22 | { 23 | statusBar()->showMessage(text); 24 | } 25 | 26 | MainWindow::~MainWindow() 27 | { 28 | delete ui; 29 | delete wManager; 30 | } 31 | 32 | -------------------------------------------------------------------------------- /shaders/solarize.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (local_size_x = 16, local_size_y = 16) in; 4 | layout (binding = 0, rgba8) uniform readonly image2D inputImage; 5 | layout (binding = 1, rgba8) uniform image2D resultImage; 6 | 7 | float rThresh = 0.5; 8 | float gThresh = 0.5; 9 | float bThresh = 0.5; 10 | 11 | void main() 12 | { 13 | vec3 rgb = imageLoad(inputImage, ivec2(gl_GlobalInvocationID.xy)).rgb; 14 | 15 | if(rgb[0] < rThresh) 16 | { 17 | rgb[0] = 1.0 - rgb[0]; 18 | } 19 | if(rgb[1] < gThresh) 20 | { 21 | rgb[1] = 1.0 - rgb[1]; 22 | } 23 | if(rgb[2] < bThresh) 24 | { 25 | rgb[2] = 1.0 - rgb[2]; 26 | } 27 | 28 | vec4 res = vec4(vec3(rgb), 1.0); 29 | 30 | imageStore(resultImage, ivec2(gl_GlobalInvocationID.xy), res); 31 | 32 | } -------------------------------------------------------------------------------- /src/vulkanwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef VULKANWINDOW_H 2 | #define VULKANWINDOW_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class VulkanRenderer; 9 | 10 | class VulkanWindow : public QVulkanWindow 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | QVulkanWindowRenderer *createRenderer() override; 16 | 17 | VulkanRenderer* getRenderer(); 18 | bool getShowOriginal(); 19 | void setShowOriginal(bool b); 20 | 21 | private: 22 | void mousePressEvent(QMouseEvent *) override; 23 | void mouseReleaseEvent(QMouseEvent *) override; 24 | void mouseMoveEvent(QMouseEvent *) override; 25 | void wheelEvent(QWheelEvent *) override; 26 | 27 | VulkanRenderer* renderer; 28 | bool pressed = false; 29 | QPoint lastPos; 30 | float scale = 1.0f; 31 | bool showOriginal = false; 32 | 33 | signals: 34 | void rendererHasBeenCreated(); 35 | 36 | }; 37 | 38 | #endif // VULKANWINDOW_H 39 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | int main(int argc, char *argv[]) 9 | { 10 | QApplication a(argc, argv); 11 | 12 | // Load font 13 | int fontId = QFontDatabase::addApplicationFont(":/OpenSans-Regular.ttf"); 14 | QFontDatabase::addApplicationFont(":/OpenSans-Bold.ttf"); 15 | if (fontId != -1) 16 | a.setFont(QFont("Open Sans")); 17 | else 18 | std::cout << "Problem loading font." << std::endl; 19 | 20 | // Load style sheet 21 | QFile f(":/stylesheet.qss"); 22 | f.open(QFile::ReadOnly); 23 | QString style = QLatin1String(f.readAll()); 24 | a.setStyleSheet(style); 25 | 26 | MainWindow w; 27 | w.setWindowState(Qt::WindowMaximized); 28 | auto title = QString("ShaderDev - v%1.%2.%3").arg(VERSION_MAJOR).arg(VERSION_MINOR).arg(VERSION_BUILD); 29 | w.setWindowTitle(title); 30 | 31 | w.show(); 32 | 33 | return a.exec(); 34 | } 35 | -------------------------------------------------------------------------------- /src/syntaxhighlighter.h: -------------------------------------------------------------------------------- 1 | #ifndef SYNTAXHIGHLIGHTER_H 2 | #define SYNTAXHIGHLIGHTER_H 3 | 4 | #include 5 | #include 6 | 7 | class SyntaxHighlighter : public QSyntaxHighlighter 8 | { 9 | Q_OBJECT 10 | 11 | public: 12 | SyntaxHighlighter(QTextDocument *parent = 0); 13 | 14 | protected: 15 | void highlightBlock(const QString &text) override; 16 | 17 | private: 18 | struct HighlightingRule 19 | { 20 | QRegularExpression pattern; 21 | QTextCharFormat format; 22 | }; 23 | QVector highlightingRules; 24 | 25 | QRegularExpression commentStartExpression; 26 | QRegularExpression commentEndExpression; 27 | 28 | QTextCharFormat keywordFormat; 29 | QTextCharFormat classFormat; 30 | QTextCharFormat singleLineCommentFormat; 31 | QTextCharFormat multiLineCommentFormat; 32 | QTextCharFormat quotationFormat; 33 | QTextCharFormat functionFormat; 34 | }; 35 | 36 | #endif // SYNTAXHIGHLIGHTER_H 37 | -------------------------------------------------------------------------------- /src/codeedit.h: -------------------------------------------------------------------------------- 1 | #ifndef CODEEDIT_H 2 | #define CODEEDIT_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "globals.h" 10 | 11 | using namespace ShaderDev; 12 | 13 | class CodeEdit : public QPlainTextEdit 14 | { 15 | Q_OBJECT 16 | 17 | public: 18 | CodeEdit(QWidget *parent = nullptr); 19 | void lineNumberAreaPaintEvent(QPaintEvent *event); 20 | int lineNumberAreaWidth(); 21 | 22 | ShaderCode spirV; 23 | 24 | protected: 25 | void resizeEvent(QResizeEvent *event) override; 26 | 27 | private: 28 | SpvCompiler compiler; 29 | std::string code; 30 | QWidget *lineNumberArea; 31 | 32 | 33 | signals: 34 | void requestErrorMessageUpdate(const std::string&); 35 | void shaderCompiledSuccessfully(); 36 | 37 | private slots: 38 | void handleCodeHasChanged(); 39 | void updateLineNumberAreaWidth(int newBlockCount); 40 | void highlightCurrentLine(); 41 | void updateLineNumberArea(const QRect &rect, int dy); 42 | }; 43 | 44 | #endif // CODEEDIT_H 45 | -------------------------------------------------------------------------------- /shaders/crop.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (local_size_x = 16, local_size_y = 16) in; 4 | layout (binding = 0, rgba8) uniform readonly image2D inputImage; 5 | layout (binding = 1, rgba8) uniform image2D resultImage; 6 | 7 | layout(push_constant) uniform pushConstants { 8 | layout(offset = 0) float leftCrop; 9 | layout(offset = 4) float topCrop; 10 | layout(offset = 8) float rightCrop; 11 | layout(offset = 12) float bottomCrop; 12 | } u_pushConstants; 13 | 14 | void main() 15 | { 16 | ivec2 imgSize = imageSize(inputImage); 17 | 18 | ivec2 pixelCoords = ivec2(gl_GlobalInvocationID.xy); 19 | 20 | vec3 rgb = imageLoad(inputImage, pixelCoords + ivec2(u_pushConstants.leftCrop, u_pushConstants.topCrop)).rgb; 21 | 22 | vec4 pixel = vec4(rgb, 1.0); 23 | 24 | if(gl_GlobalInvocationID.x <= imgSize.x - u_pushConstants.leftCrop - u_pushConstants.rightCrop && 25 | gl_GlobalInvocationID.y <= imgSize.y - u_pushConstants.topCrop - u_pushConstants.bottomCrop) 26 | { 27 | imageStore(resultImage, pixelCoords, pixel); 28 | } 29 | } -------------------------------------------------------------------------------- /src/windowmanager.h: -------------------------------------------------------------------------------- 1 | #ifndef WINDOWMANAGER_H 2 | #define WINDOWMANAGER_H 3 | 4 | #include 5 | 6 | #include "codeedit.h" 7 | #include "controlswidget.h" 8 | #include "vulkanwindow.h" 9 | #include "outputedit.h" 10 | 11 | class MainWindow; 12 | 13 | class WindowManager : public QObject 14 | { 15 | Q_OBJECT 16 | 17 | public: 18 | WindowManager(MainWindow* w, ControlsWidget* c, VulkanWindow* v, CodeEdit* e, OutputEdit* oe); 19 | 20 | private: 21 | MainWindow* mainWindow; 22 | ControlsWidget* controlsWidget; 23 | VulkanWindow* vulkanWindow; 24 | CodeEdit* codeEdit; 25 | OutputEdit* outputEdit; 26 | 27 | public slots: 28 | void handleRendererHasBeenCreated(); 29 | void handleImagePathHasChanged(const QString& path); 30 | void handleFileLoadingRequest(const QString& path); 31 | void handleShaderSavingRequest(const QString& path); 32 | void handleCompiledSavingRequest(const QString& path); 33 | void handleRequestErrorMessageUpdate(const std::string& msg); 34 | void handleShaderCompiled(); 35 | void handleOriginalCheckboxStateChanged(bool state); 36 | 37 | }; 38 | 39 | #endif // WINDOWMANAGER_H 40 | -------------------------------------------------------------------------------- /src/controlswidget.h: -------------------------------------------------------------------------------- 1 | #ifndef CONTROLSWIDGET_H 2 | #define CONTROLSWIDGET_H 3 | 4 | #include 5 | 6 | namespace Ui { 7 | class ControlsWidget; 8 | } 9 | 10 | class ControlsWidget : public QWidget 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit ControlsWidget(QWidget *parent = nullptr); 16 | ~ControlsWidget(); 17 | 18 | void handleShaderHasBeenSaved(const QString& path); 19 | 20 | private: 21 | Ui::ControlsWidget *ui; 22 | QString imagePath; 23 | QString shaderPath; 24 | QString fileName; 25 | bool fileIsDirty = false; 26 | 27 | signals: 28 | void imagePathHasChanged(const QString& path); 29 | void requestFileLoading(const QString& path); 30 | void requestShaderSaving(const QString& path); 31 | void requestCompiledSaving(const QString& path); 32 | void originalCheckboxStateChanged(bool state); 33 | 34 | public slots: 35 | void handleLoadImageButtonClicked(); 36 | void handleLoadShaderButtonClicked(); 37 | void handleSaveShaderButtonClicked(); 38 | void handleSaveCompiledButtonClicked(); 39 | void handleCodeHasChanged(); 40 | void handleOriginalCheckboxStateChanged(int state); 41 | }; 42 | 43 | #endif // CONTROLSWIDGET_H 44 | -------------------------------------------------------------------------------- /shaders/nearestneighbor.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (local_size_x = 16, local_size_y = 16) in; 4 | layout (binding = 0, rgba8) uniform readonly image2D inputImage; 5 | layout (binding = 1, rgba8) uniform image2D resultImage; 6 | 7 | float avg(vec3 vecIn) 8 | { 9 | float vecOut = vecIn[0] + vecIn[1] + vecIn[2] / 3.0; 10 | return vecOut; 11 | } 12 | 13 | void main() 14 | { 15 | const int filterSize = 10; 16 | 17 | vec3 nearest = {0.0, 0.0, 0.0}; 18 | float nearestAvg = 1.0; 19 | 20 | vec4 res; 21 | 22 | int n = -1; 23 | for (int i=0; i 5 | #include 6 | 7 | VulkanView::VulkanView(QWidget *parent) : QWidget(parent) 8 | { 9 | qDebug("Creating Vulkan instance"); 10 | 11 | // Set up validation layers 12 | #ifdef QT_DEBUG 13 | instance.setLayers(QByteArrayList() 14 | << "VK_LAYER_LUNARG_parameter_validation" 15 | << "VK_LAYER_LUNARG_object_tracker" 16 | << "VK_LAYER_LUNARG_core_validation" 17 | << "VK_LAYER_LUNARG_image" 18 | << "VK_LAYER_LUNARG_swapchain"); 19 | #endif 20 | 21 | if (!instance.create()) 22 | qFatal("Failed to create Vulkan instance: %d", instance.errorCode()); 23 | 24 | 25 | // Create a VulkanWindow 26 | vulkanWindow = new VulkanWindow(); 27 | vulkanWindow->setVulkanInstance(&instance); 28 | 29 | vulkanWindow->setPreferredColorFormats(QVector() << VK_FORMAT_B8G8R8A8_SRGB); 30 | 31 | // Create Vulkan window container and put in layout 32 | vulkanWrapper = QWidget::createWindowContainer(vulkanWindow); 33 | QHBoxLayout* layout = new QHBoxLayout(); 34 | layout->addWidget(vulkanWrapper); 35 | this->setLayout(layout); 36 | 37 | } 38 | 39 | VulkanWindow* VulkanView::getVulkanWindow() 40 | { 41 | return vulkanWindow; 42 | } 43 | -------------------------------------------------------------------------------- /src/vulkanwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "vulkanwindow.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include "vulkanrenderer.h" 8 | 9 | QVulkanWindowRenderer *VulkanWindow::createRenderer() 10 | { 11 | qDebug("Creating renderer"); 12 | 13 | renderer = new VulkanRenderer(this); 14 | 15 | return renderer; 16 | } 17 | 18 | VulkanRenderer* VulkanWindow::getRenderer() 19 | { 20 | return renderer; 21 | } 22 | 23 | bool VulkanWindow::getShowOriginal() 24 | { 25 | return showOriginal; 26 | } 27 | 28 | void VulkanWindow::setShowOriginal(bool b) 29 | { 30 | showOriginal = b; 31 | } 32 | 33 | void VulkanWindow::mousePressEvent(QMouseEvent *e) 34 | { 35 | pressed = true; 36 | lastPos = e->pos(); 37 | } 38 | 39 | void VulkanWindow::mouseReleaseEvent(QMouseEvent *) 40 | { 41 | pressed = false; 42 | } 43 | 44 | void VulkanWindow::mouseMoveEvent(QMouseEvent *e) 45 | { 46 | if (!pressed) 47 | return; 48 | 49 | float dx = e->pos().x() - lastPos.x(); 50 | float dy = e->pos().y() - lastPos.y(); 51 | float magnitude = std::max(abs(dx),abs(dy)); 52 | 53 | if ( magnitude > 0.0f ) 54 | { 55 | renderer->translate(dx, dy); 56 | } 57 | 58 | lastPos = e->pos(); 59 | } 60 | 61 | void VulkanWindow::wheelEvent(QWheelEvent *e) 62 | { 63 | e->delta() > 0 ? scale += scale*0.1f : scale -= scale*0.1f; 64 | 65 | 66 | //Limit the scale, 67 | scale = scale > 0.1f ? scale : 0.1f; 68 | scale = scale < 5.0f ? scale : 5.0f; 69 | renderer->scale(scale); 70 | } 71 | 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ShaderDev 2 | **A desktop application for developing GLSL compute shaders for the purpose of image processing.** 3 | 4 | ![](/screenshots/ShaderDev01.jpg?raw=true "") 5 | 6 | ![](/screenshots/ShaderDev02.jpg?raw=true "") 7 | 8 | # Dependencies 9 | 10 | - [Qt](https://www.qt.io/download) >= 5.10 11 | - [SpvShaderCompiler](https://github.com/ttddee/SpvShaderCompiler) 12 | - [glslang](https://github.com/KhronosGroup/glslang) 13 | - [Vulkan](https://www.khronos.org/vulkan/) 14 | 15 | # Build 16 | 17 | The easiest way to build is to open the project in QtCreator and compile for the desired platform. 18 | 19 | QMake is set up for either Linux or Windows. 20 | 21 | # Usage 22 | 23 | Load the file **shaders/noop.comp** to see what a simple shader looks like. This one loads the image and renders it to the screen, without doing anything else. 24 | 25 | ``` 26 | #version 450 27 | 28 | layout (local_size_x = 16, local_size_y = 16) in; 29 | layout (binding = 0, rgba8) uniform readonly image2D inputImage; 30 | layout (binding = 1, rgba8) uniform image2D resultImage; 31 | 32 | void main() 33 | { 34 | ivec2 pixelCoords = ivec2(gl_GlobalInvocationID.xy); 35 | 36 | vec3 rgb = imageLoad(inputImage, pixelCoords).rgb; 37 | 38 | vec4 pixel = vec4(rgb, 1.0); 39 | 40 | imageStore(resultImage, pixelCoords, pixel); 41 | } 42 | ``` 43 | 44 | There are two bindings: 45 | 46 | - **inputImage**: The image that was loaded from disk. 47 | - **resultImage**: The final image that will be written to. 48 | 49 | In the **shaders** folder there are also a couple of examples. I will add more in the future. 50 | 51 | -------------------------------------------------------------------------------- /shaders/lanczos.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (local_size_x = 16, local_size_y = 16) in; 4 | layout (binding = 0, rgba8) uniform readonly image2D inputImage; 5 | layout (binding = 1, rgba8) uniform image2D resultImage; 6 | 7 | void main() 8 | { 9 | ivec2 pixelCoords = ivec2(gl_GlobalInvocationID.xy); 10 | 11 | vec2 scaleFactor = vec2(0.5, 1.0); 12 | 13 | ivec2 centerCoordinate = ivec2(pixelCoords); 14 | ivec2 oneStepLeftCoordinate = ivec2(pixelCoords.x - 1, pixelCoords.y); 15 | ivec2 twoStepsLeftCoordinate = ivec2(pixelCoords.x - 2, pixelCoords.y); 16 | ivec2 threeStepsLeftCoordinate = ivec2(pixelCoords.x - 3, pixelCoords.y); 17 | ivec2 fourStepsLeftCoordinate = ivec2(pixelCoords.x - 4, pixelCoords.y); 18 | ivec2 oneStepRightCoordinate = ivec2(pixelCoords.x + 1, pixelCoords.y); 19 | ivec2 twoStepsRightCoordinate = ivec2(pixelCoords.x + 2, pixelCoords.y); 20 | ivec2 threeStepsRightCoordinate = ivec2(pixelCoords.x + 3, pixelCoords.y); 21 | ivec2 fourStepsRightCoordinate = ivec2(pixelCoords.x + 4, pixelCoords.y); 22 | 23 | vec3 rgb = imageLoad(inputImage, centerCoordinate).rgb * 0.38026; 24 | 25 | rgb += imageLoad(inputImage, oneStepLeftCoordinate).rgb * 0.27667; 26 | rgb += imageLoad(inputImage, oneStepRightCoordinate).rgb * 0.27667; 27 | 28 | rgb += imageLoad(inputImage, twoStepsLeftCoordinate).rgb * 0.08074; 29 | rgb += imageLoad(inputImage, twoStepsRightCoordinate).rgb * 0.08074; 30 | 31 | rgb += imageLoad(inputImage, threeStepsLeftCoordinate).rgb * -0.02612; 32 | rgb += imageLoad(inputImage, threeStepsRightCoordinate).rgb * -0.02612; 33 | 34 | rgb += imageLoad(inputImage, fourStepsLeftCoordinate).rgb * -0.02143; 35 | rgb += imageLoad(inputImage, fourStepsRightCoordinate).rgb * -0.02143; 36 | 37 | vec4 pixel = vec4(rgb, 1.0); 38 | 39 | ivec2 outputCoords = ivec2(pixelCoords * scaleFactor); 40 | 41 | imageStore(resultImage, outputCoords, pixel); 42 | } -------------------------------------------------------------------------------- /src/stylesheet.qss: -------------------------------------------------------------------------------- 1 | /* 2 | BGDark: #181b1e 3 | BGMid: #1d2024 4 | BGLight: #282d31 5 | BGHover: #626971 6 | Red: #dc4c46 7 | RedHover: #e8514b 8 | Green: #9bcf43 9 | GreenHover: #a4da47 10 | Blue: #00b6dd 11 | BlueHover: #01c0e9 12 | FGDark: #9299a1 13 | FGLight: #f1f1f1 14 | */ 15 | 16 | QWidget { 17 | background-color: #1d2024; 18 | color: #f1f1f1; 19 | font: 14px "Open Sans"; 20 | } 21 | 22 | QPlainTextEdit { 23 | background-color: #282d31; 24 | color: #f1f1f1; 25 | } 26 | 27 | ControlsWidget { 28 | background-color: #282d31; 29 | color: #f1f1f1; 30 | } 31 | 32 | /*Horizontal and Vertical Lines*/ 33 | QFrame[frameShape="4"], 34 | QFrame[frameShape="5"] { 35 | background-color: #9299a1; 36 | } 37 | 38 | QPushButton { 39 | background-color: #282d31; 40 | color: #f1f1f1; 41 | border: none; 42 | border-radius: 3px; 43 | font-weight: bold; 44 | } 45 | QPushButton:hover { 46 | background-color: #626971; 47 | } 48 | QPushButton:disabled { 49 | color: #9299a1; 50 | background-color: #1d2024; 51 | } 52 | 53 | QScrollBar:vertical { 54 | border: 0px; 55 | background: #1d2024; 56 | } 57 | QScrollBar::handle:vertical { 58 | background: #626971; 59 | } 60 | QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { 61 | border: 0px; 62 | background: none; 63 | color: none; 64 | } 65 | QScrollBar::add-line:vertical { 66 | border: none; 67 | background: none; 68 | } 69 | QScrollBar::sub-line:vertical { 70 | border: none; 71 | background: none; 72 | } 73 | 74 | LineNumberArea { 75 | font-weight: bold; 76 | } 77 | 78 | QCheckBox { 79 | background-color: #181b1e; 80 | color: #f1f1f1; 81 | } 82 | QCheckBox::indicator:checked { 83 | background-color: #00b6dd; 84 | color: #f1f1f1; 85 | border-radius: 3px; 86 | } 87 | QCheckBox::indicator:unchecked { 88 | background-color: #282d31; 89 | color: #f1f1f1; 90 | border-radius: 3px; 91 | } 92 | QCheckBox::indicator:unchecked:hover { 93 | background-color: #626971; 94 | color: #f1f1f1; 95 | } 96 | 97 | 98 | -------------------------------------------------------------------------------- /shaders/gaussblur.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (local_size_x = 16, local_size_y = 16) in; 4 | layout (binding = 0, rgba8) uniform readonly image2D inputImage; 5 | layout (binding = 1, rgba8) uniform image2D resultImage; 6 | 7 | // Separable Gaussian Blur based on: https://callumhay.blogspot.com/2010/09/gaussian-blur-shader-glsl.html 8 | 9 | void main() 10 | { 11 | const int kernelSize = 9; 12 | 13 | float sigma = 3.0; 14 | float blurSize = 1.0; 15 | const float pi = 3.14159265f; 16 | 17 | const float numBlurPixelsPerSide = (kernelSize - 1) / 2 * blurSize; 18 | 19 | vec3 incrementalGaussian; 20 | incrementalGaussian.x = 1.0f / (sqrt(2.0f * pi) * sigma); 21 | incrementalGaussian.y = exp(-0.5f / (sigma * sigma)); 22 | incrementalGaussian.z = incrementalGaussian.y * incrementalGaussian.y; 23 | 24 | vec3 avgValue = vec3(0.0f, 0.0f, 0.0f); 25 | float coefficientSum = 0.0f; 26 | 27 | avgValue += imageLoad(inputImage, ivec2(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y)).rgb * incrementalGaussian.x; 28 | coefficientSum += incrementalGaussian.x; 29 | incrementalGaussian.xy *= incrementalGaussian.yz; 30 | 31 | // Horizontal 32 | for (float i = 1.0f; i <= numBlurPixelsPerSide; i++) 33 | { 34 | avgValue += imageLoad(inputImage, ivec2(gl_GlobalInvocationID.x - i * blurSize, gl_GlobalInvocationID.y)).rgb * incrementalGaussian.x; 35 | avgValue += imageLoad(inputImage, ivec2(gl_GlobalInvocationID.x + i * blurSize, gl_GlobalInvocationID.y)).rgb * incrementalGaussian.x; 36 | coefficientSum += 2 * incrementalGaussian.x; 37 | incrementalGaussian.xy *= incrementalGaussian.yz; 38 | } 39 | 40 | // Vertical 41 | for (float i = 1.0f; i <= numBlurPixelsPerSide; i++) 42 | { 43 | avgValue += imageLoad(inputImage, ivec2(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y - i * blurSize)).rgb * incrementalGaussian.y; 44 | avgValue += imageLoad(inputImage, ivec2(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y + i * blurSize)).rgb * incrementalGaussian.y; 45 | coefficientSum += 2 * incrementalGaussian.y; 46 | incrementalGaussian.xy *= incrementalGaussian.yz; 47 | } 48 | 49 | vec4 res = vec4(avgValue / coefficientSum, 1.0); 50 | 51 | imageStore(resultImage, ivec2(gl_GlobalInvocationID.xy), res); 52 | } -------------------------------------------------------------------------------- /src/ShaderDev.pro: -------------------------------------------------------------------------------- 1 | QT += core gui 2 | 3 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 4 | 5 | CONFIG += c++17 6 | 7 | DEFINES += QT_DEPRECATED_WARNINGS 8 | 9 | #------------------------------- Versioning 10 | 11 | VERSION_MAJOR = 0 12 | VERSION_MINOR = 1 13 | VERSION_BUILD = 1 14 | 15 | DEFINES += "VERSION_MAJOR=$$VERSION_MAJOR"\ 16 | "VERSION_MINOR=$$VERSION_MINOR"\ 17 | "VERSION_BUILD=$$VERSION_BUILD" 18 | 19 | VERSION = $${VERSION_MAJOR}.$${VERSION_MINOR}.$${VERSION_BUILD} 20 | 21 | #------------------------------- 22 | 23 | SOURCES += \ 24 | codeedit.cpp \ 25 | controlswidget.cpp \ 26 | linenumberarea.cpp \ 27 | main.cpp \ 28 | mainwindow.cpp \ 29 | outputedit.cpp \ 30 | syntaxhighlighter.cpp \ 31 | vulkanrenderer.cpp \ 32 | vulkanview.cpp \ 33 | vulkanwindow.cpp \ 34 | windowmanager.cpp 35 | 36 | HEADERS += \ 37 | codeedit.h \ 38 | controlswidget.h \ 39 | globals.h \ 40 | linenumberarea.h \ 41 | mainwindow.h \ 42 | outputedit.h \ 43 | syntaxhighlighter.h \ 44 | vulkanrenderer.h \ 45 | vulkanview.h \ 46 | vulkanwindow.h \ 47 | windowmanager.h 48 | 49 | FORMS += \ 50 | controlswidget.ui \ 51 | mainwindow.ui 52 | 53 | RESOURCES += \ 54 | resources.qrc 55 | 56 | DISTFILES += \ 57 | stylesheet.qss 58 | 59 | win32-msvc* { 60 | 61 | INCLUDEPATH += $$PWD/../external/SpvShaderCompiler/include 62 | 63 | LIBS += -L$$PWD/../external/SpvShaderCompiler/lib -lSpvShaderCompiler 64 | LIBS += -L$$PWD/../external/SpvShaderCompiler/lib -lGenericCodeGen 65 | LIBS += -L$$PWD/../external/SpvShaderCompiler/lib -lglslang 66 | LIBS += -L$$PWD/../external/SpvShaderCompiler/lib -lHLSL 67 | LIBS += -L$$PWD/../external/SpvShaderCompiler/lib -lMachineIndependent 68 | LIBS += -L$$PWD/../external/SpvShaderCompiler/lib -lOGLCompiler 69 | LIBS += -L$$PWD/../external/SpvShaderCompiler/lib -lOSDependent 70 | LIBS += -L$$PWD/../external/SpvShaderCompiler/lib -lSPIRV 71 | LIBS += -L$$PWD/../external/SpvShaderCompiler/lib -lSPVRemapper 72 | 73 | } 74 | 75 | linux-g++ { 76 | 77 | INCLUDEPATH += $$PWD/../external/linux/SpvShaderCompiler/include 78 | 79 | LIBS += -L$$PWD/../external/linux/SpvShaderCompiler/lib -lSpvShaderCompiler 80 | LIBS += -L$$PWD/../external/linux/SpvShaderCompiler/lib -lglslang 81 | LIBS += -L$$PWD/../external/linux/SpvShaderCompiler/lib -lHLSL 82 | LIBS += -L$$PWD/../external/linux/SpvShaderCompiler/lib -lMachineIndependent 83 | LIBS += -L$$PWD/../external/linux/SpvShaderCompiler/lib -lOGLCompiler 84 | LIBS += -L$$PWD/../external/linux/SpvShaderCompiler/lib -lOSDependent 85 | LIBS += -L$$PWD/../external/linux/SpvShaderCompiler/lib -lSPIRV 86 | LIBS += -L$$PWD/../external/linux/SpvShaderCompiler/lib -lSPVRemapper 87 | LIBS += -L$$PWD/../external/linux/SpvShaderCompiler/lib -lGenericCodeGen 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/controlswidget.cpp: -------------------------------------------------------------------------------- 1 | #include "controlswidget.h" 2 | #include "ui_controlswidget.h" 3 | 4 | #include 5 | 6 | ControlsWidget::ControlsWidget(QWidget *parent) : 7 | QWidget(parent), 8 | ui(new Ui::ControlsWidget) 9 | { 10 | ui->setupUi(this); 11 | 12 | connect(ui->loadImageButton, &QPushButton::clicked, 13 | this, &ControlsWidget::handleLoadImageButtonClicked); 14 | connect(ui->loadShaderButton, &QPushButton::clicked, 15 | this, &ControlsWidget::handleLoadShaderButtonClicked); 16 | connect(ui->saveShaderButton, &QPushButton::clicked, 17 | this, &ControlsWidget::handleSaveShaderButtonClicked); 18 | connect(ui->saveCompiledButton, &QPushButton::clicked, 19 | this, &ControlsWidget::handleSaveCompiledButtonClicked); 20 | connect(ui->originalCheckBox, &QCheckBox::stateChanged, 21 | this, &ControlsWidget::handleOriginalCheckboxStateChanged); 22 | } 23 | 24 | void ControlsWidget::handleShaderHasBeenSaved(const QString& path) 25 | { 26 | fileIsDirty = false; 27 | ui->fileNameLabel->setText(fileName); 28 | QFileInfo fi(path); 29 | ui->fileNameLabel->setText(fi.fileName()); 30 | fileName = fi.fileName(); 31 | shaderPath = path; 32 | } 33 | 34 | void ControlsWidget::handleLoadImageButtonClicked() 35 | { 36 | QFileDialog dialog(nullptr); 37 | dialog.setFileMode(QFileDialog::ExistingFile); 38 | dialog.setNameFilter(tr("Images (*.jpg *.png)")); 39 | dialog.setViewMode(QFileDialog::Detail); 40 | dialog.setDirectory(QCoreApplication::applicationDirPath()); 41 | if (dialog.exec()) 42 | { 43 | auto files = dialog.selectedFiles(); 44 | imagePath = files[0]; 45 | 46 | emit imagePathHasChanged(imagePath); 47 | } 48 | } 49 | 50 | void ControlsWidget::handleLoadShaderButtonClicked() 51 | { 52 | QFileDialog dialog(nullptr); 53 | dialog.setFileMode(QFileDialog::ExistingFile); 54 | dialog.setNameFilter(tr("Shaders (*.comp)")); 55 | dialog.setViewMode(QFileDialog::Detail); 56 | dialog.setDirectory(QCoreApplication::applicationDirPath()); 57 | if (dialog.exec()) 58 | { 59 | auto files = dialog.selectedFiles(); 60 | shaderPath = files[0]; 61 | 62 | QFileInfo fi(shaderPath); 63 | ui->fileNameLabel->setText(fi.fileName()); 64 | fileName = fi.fileName(); 65 | 66 | emit requestFileLoading(shaderPath); 67 | } 68 | } 69 | 70 | void ControlsWidget::handleSaveShaderButtonClicked() 71 | { 72 | auto saveFileName = QFileDialog::getSaveFileName(this, 73 | tr("Save Shader"), "", 74 | tr("Shader (*.comp)")); 75 | if(!saveFileName.isEmpty()) 76 | { 77 | emit requestShaderSaving(saveFileName); 78 | } 79 | } 80 | 81 | void ControlsWidget::handleSaveCompiledButtonClicked() 82 | { 83 | auto saveFileName = QFileDialog::getSaveFileName(this, 84 | tr("Save SPV"), "", 85 | tr("SPIR-V (*.spv)")); 86 | if(!saveFileName.isEmpty()) 87 | { 88 | emit requestCompiledSaving(saveFileName); 89 | } 90 | } 91 | 92 | void ControlsWidget::handleCodeHasChanged() 93 | { 94 | fileIsDirty = true; 95 | if(fileName.size() > 0) 96 | { 97 | ui->fileNameLabel->setText(fileName + "*"); 98 | } 99 | } 100 | 101 | void ControlsWidget::handleOriginalCheckboxStateChanged(int state) 102 | { 103 | if(state == 0) 104 | { 105 | emit originalCheckboxStateChanged(false); 106 | } 107 | else 108 | { 109 | emit originalCheckboxStateChanged(true); 110 | } 111 | } 112 | 113 | ControlsWidget::~ControlsWidget() 114 | { 115 | delete ui; 116 | } 117 | -------------------------------------------------------------------------------- /src/windowmanager.cpp: -------------------------------------------------------------------------------- 1 | #include "windowmanager.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "mainwindow.h" 9 | #include "vulkanrenderer.h" 10 | 11 | WindowManager::WindowManager(MainWindow* w, ControlsWidget* c, VulkanWindow* v, CodeEdit* e, OutputEdit* oe) 12 | { 13 | mainWindow = w; 14 | controlsWidget = c; 15 | vulkanWindow = v; 16 | codeEdit = e; 17 | outputEdit = oe; 18 | 19 | connect(vulkanWindow, &VulkanWindow::rendererHasBeenCreated, 20 | this, &WindowManager::handleRendererHasBeenCreated); 21 | connect(controlsWidget, &ControlsWidget::imagePathHasChanged, 22 | this, &WindowManager::handleImagePathHasChanged); 23 | connect(controlsWidget, &ControlsWidget::requestFileLoading, 24 | this, &WindowManager::handleFileLoadingRequest); 25 | connect(controlsWidget, &ControlsWidget::requestShaderSaving, 26 | this, &WindowManager::handleShaderSavingRequest); 27 | connect(controlsWidget, &ControlsWidget::requestCompiledSaving, 28 | this, &WindowManager::handleCompiledSavingRequest); 29 | connect(codeEdit, &CodeEdit::requestErrorMessageUpdate, 30 | this, &WindowManager::handleRequestErrorMessageUpdate); 31 | connect(codeEdit, &CodeEdit::shaderCompiledSuccessfully, 32 | this, &WindowManager::handleShaderCompiled); 33 | connect(codeEdit, &CodeEdit::textChanged, 34 | controlsWidget, &ControlsWidget::handleCodeHasChanged); 35 | connect(controlsWidget, &ControlsWidget::originalCheckboxStateChanged, 36 | this, &WindowManager::handleOriginalCheckboxStateChanged); 37 | } 38 | 39 | void WindowManager::handleRendererHasBeenCreated() 40 | { 41 | QString status = "GPU: " + vulkanWindow->getRenderer()->getGpuName(); 42 | mainWindow->setStatusMessage(status); 43 | } 44 | 45 | void WindowManager::handleImagePathHasChanged(const QString& path) 46 | { 47 | vulkanWindow->getRenderer()->updateImage(path); 48 | } 49 | 50 | void WindowManager::handleFileLoadingRequest(const QString& path) 51 | { 52 | QFile f(path); 53 | if (!f.open((QFile::ReadOnly | QFile::Text))) 54 | { 55 | qWarning("Failed to open file."); 56 | } 57 | 58 | QTextStream in (&f); 59 | auto t = in.readAll(); 60 | 61 | codeEdit->blockSignals(true); 62 | codeEdit->setPlainText(t); 63 | codeEdit->update(); 64 | codeEdit->blockSignals(false); 65 | } 66 | 67 | void WindowManager::handleShaderSavingRequest(const QString& path) 68 | { 69 | QFile file(path); 70 | if (!file.open(QIODevice::WriteOnly)) 71 | { 72 | QMessageBox::information(controlsWidget, tr("Unable to open file"), 73 | file.errorString()); 74 | return; 75 | } 76 | QTextStream out(&file); 77 | out << codeEdit->document()->toPlainText(); 78 | controlsWidget->handleShaderHasBeenSaved(path); 79 | } 80 | 81 | void WindowManager::handleCompiledSavingRequest(const QString& path) 82 | { 83 | if (codeEdit->spirV.size() == 0) 84 | { 85 | QMessageBox::information(controlsWidget, "Unable to open file", "Nothing compiled yet?"); 86 | } 87 | else 88 | { 89 | auto data = vulkanWindow->getRenderer()->uintVecToCharVec(codeEdit->spirV); 90 | 91 | std::ofstream f; 92 | f.open(path.toStdString(), std::ios_base::binary); 93 | f.write((char*) &data[0], data.size() * sizeof (char)); 94 | f.close(); 95 | } 96 | } 97 | 98 | void WindowManager::handleRequestErrorMessageUpdate(const std::string& msg) 99 | { 100 | outputEdit->setErrorMsg(msg); 101 | } 102 | 103 | void WindowManager::handleShaderCompiled() 104 | { 105 | vulkanWindow->getRenderer()->updateShader(codeEdit->spirV); 106 | } 107 | 108 | void WindowManager::handleOriginalCheckboxStateChanged(bool state) 109 | { 110 | vulkanWindow->setShowOriginal(state); 111 | vulkanWindow->getRenderer()->updateShader(codeEdit->spirV); 112 | } 113 | -------------------------------------------------------------------------------- /src/codeedit.cpp: -------------------------------------------------------------------------------- 1 | #include "codeedit.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "linenumberarea.h" 9 | 10 | CodeEdit::CodeEdit(QWidget *parent) 11 | : QPlainTextEdit(parent) 12 | { 13 | lineNumberArea = new LineNumberArea(this); 14 | 15 | connect(this, &CodeEdit::textChanged, 16 | this, &CodeEdit::handleCodeHasChanged); 17 | connect(this, &CodeEdit::blockCountChanged, 18 | this, &CodeEdit::updateLineNumberAreaWidth); 19 | connect(this, &CodeEdit::updateRequest, 20 | this, &CodeEdit::updateLineNumberArea); 21 | connect(this, &CodeEdit::cursorPositionChanged, 22 | this, &CodeEdit::highlightCurrentLine); 23 | 24 | // Set Tab key to four spaces 25 | setTabStopDistance(QFontMetrics(font()).horizontalAdvance(' ') * 4); 26 | 27 | updateLineNumberAreaWidth(0); 28 | highlightCurrentLine(); 29 | } 30 | 31 | int CodeEdit::lineNumberAreaWidth() 32 | { 33 | int digits = 3; 34 | int max = qMax(1, blockCount()); 35 | while (max >= 10) 36 | { 37 | max /= 10; 38 | ++digits; 39 | } 40 | int space = 3 + fontMetrics().horizontalAdvance(QLatin1Char('9')) * digits; 41 | 42 | return space; 43 | } 44 | 45 | void CodeEdit::updateLineNumberAreaWidth(int /* newBlockCount */) 46 | { 47 | setViewportMargins(lineNumberAreaWidth(), 0, 0, 0); 48 | } 49 | 50 | void CodeEdit::updateLineNumberArea(const QRect &rect, int dy) 51 | { 52 | if (dy) 53 | lineNumberArea->scroll(0, dy); 54 | else 55 | lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height()); 56 | 57 | if (rect.contains(viewport()->rect())) 58 | updateLineNumberAreaWidth(0); 59 | } 60 | 61 | void CodeEdit::resizeEvent(QResizeEvent *e) 62 | { 63 | QPlainTextEdit::resizeEvent(e); 64 | 65 | QRect cr = contentsRect(); 66 | lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height())); 67 | } 68 | 69 | void CodeEdit::highlightCurrentLine() 70 | { 71 | QList extraSelections; 72 | 73 | if (!isReadOnly()) { 74 | QTextEdit::ExtraSelection selection; 75 | 76 | QColor lineColor = QColor("#1d2024"); 77 | 78 | selection.format.setBackground(lineColor); 79 | selection.format.setProperty(QTextFormat::FullWidthSelection, true); 80 | selection.cursor = textCursor(); 81 | selection.cursor.clearSelection(); 82 | extraSelections.append(selection); 83 | } 84 | 85 | setExtraSelections(extraSelections); 86 | } 87 | 88 | void CodeEdit::lineNumberAreaPaintEvent(QPaintEvent *event) 89 | { 90 | QPainter painter(lineNumberArea); 91 | painter.fillRect(event->rect(), QColor("#181b1e")); 92 | 93 | QTextBlock block = firstVisibleBlock(); 94 | int blockNumber = block.blockNumber(); 95 | int top = qRound(blockBoundingGeometry(block).translated(contentOffset()).top()); 96 | int bottom = top + qRound(blockBoundingRect(block).height()); 97 | 98 | while (block.isValid() && top <= event->rect().bottom()) 99 | { 100 | if (block.isVisible() && bottom >= event->rect().top()) 101 | { 102 | QString number = QString::number(blockNumber + 1).append(" "); 103 | painter.setPen(QColor("#9299a1")); 104 | painter.drawText(0, top, lineNumberArea->width(), fontMetrics().height(), 105 | Qt::AlignRight, number); 106 | } 107 | 108 | block = block.next(); 109 | top = bottom; 110 | bottom = top + qRound(blockBoundingRect(block).height()); 111 | ++blockNumber; 112 | } 113 | } 114 | 115 | void CodeEdit::handleCodeHasChanged() 116 | { 117 | code.clear(); 118 | code.append(this->document()->toPlainText().toStdString()); 119 | 120 | if (compiler.compileGLSLFromCode(code.data(), "comp")) 121 | { 122 | spirV = compiler.getSpirV(); 123 | 124 | emit shaderCompiledSuccessfully(); 125 | } 126 | else 127 | { 128 | std::cout << "Shader is not valid: " << std::endl; 129 | std::cout << compiler.getError() << std::endl; 130 | 131 | } 132 | emit requestErrorMessageUpdate(compiler.getError()); 133 | } 134 | -------------------------------------------------------------------------------- /src/mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 896 10 | 600 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 0 25 | 2 26 | 27 | 28 | 29 | Qt::Horizontal 30 | 31 | 32 | 33 | 34 | 0 35 | 0 36 | 37 | 38 | 39 | 40 | 700 41 | 0 42 | 43 | 44 | 45 | 46 | 47 | 48 | 1 49 | 0 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 0 62 | 0 63 | 64 | 65 | 66 | 67 | 700 68 | 0 69 | 70 | 71 | 72 | 73 | 16777215 74 | 200 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 1 84 | 0 85 | 86 | 87 | 88 | 89 | 0 90 | 0 91 | 92 | 93 | 94 | 95 | 250 96 | 200 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | VulkanView 111 | QWidget 112 |
vulkanview.h
113 | 1 114 |
115 | 116 | ControlsWidget 117 | QWidget 118 |
controlswidget.h
119 | 1 120 |
121 | 122 | OutputEdit 123 | QTextEdit 124 |
outputedit.h
125 |
126 | 127 | CodeEdit 128 | QPlainTextEdit 129 |
codeedit.h
130 |
131 |
132 | 133 | 134 |
135 | -------------------------------------------------------------------------------- /src/controlswidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | ControlsWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 227 10 | 403 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Qt::Horizontal 23 | 24 | 25 | 26 | 27 | 28 | 29 | Qt::Horizontal 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 28 38 | 16777215 39 | 40 | 41 | 42 | File: 43 | 44 | 45 | 46 | 47 | 48 | 49 | Qt::Horizontal 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 0 58 | 20 59 | 60 | 61 | 62 | Save Shader 63 | 64 | 65 | 66 | 67 | 68 | 69 | Show Original 70 | 71 | 72 | 73 | 74 | 75 | 76 | Qt::Vertical 77 | 78 | 79 | 80 | 20 81 | 40 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 0 91 | 20 92 | 93 | 94 | 95 | Load Shader 96 | 97 | 98 | 99 | 100 | 101 | 102 | Qt::Horizontal 103 | 104 | 105 | 106 | 107 | 108 | 109 | Save Compiled Output 110 | 111 | 112 | 113 | 114 | 115 | 116 | Qt::Horizontal 117 | 118 | 119 | 120 | 121 | 122 | 123 | none 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 0 132 | 20 133 | 134 | 135 | 136 | Load Image 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /src/syntaxhighlighter.cpp: -------------------------------------------------------------------------------- 1 | #include "syntaxhighlighter.h" 2 | 3 | SyntaxHighlighter::SyntaxHighlighter(QTextDocument *parent) 4 | : QSyntaxHighlighter(parent) 5 | { 6 | HighlightingRule rule; 7 | 8 | keywordFormat.setForeground(QColor("#00b6dd")); 9 | keywordFormat.setFontWeight(QFont::Bold); 10 | const QString keywordPatterns[] = { 11 | QStringLiteral("\\bdefine\\b"), QStringLiteral("\\bundef\\b"), QStringLiteral("\\bif\\b"), 12 | QStringLiteral("\\bifdef\\b"), QStringLiteral("\\bifndef\\b"), QStringLiteral("\\belse\\b"), 13 | QStringLiteral("\\belif\\b"), QStringLiteral("\\bendif\\b"), QStringLiteral("\\berror\\b"), 14 | QStringLiteral("\\bpragma\\b"), QStringLiteral("\\bextension\\b"), QStringLiteral("\\bversion\\b"), 15 | QStringLiteral("\\bprecision\\b"), QStringLiteral("\\bhighp\\b"), QStringLiteral("\\bmediump\\b"), 16 | QStringLiteral("\\blowp\\b"), QStringLiteral("\\bbreak\\b"), QStringLiteral("\\bcase\\b"), 17 | QStringLiteral("\\bcontinue\\b"), QStringLiteral("\\bdefault\\b"), QStringLiteral("\\bdiscard\\b"), 18 | QStringLiteral("\\bdo\\b"), QStringLiteral("\\belse\\b"), QStringLiteral("\\bfor\\b"), 19 | QStringLiteral("\\bif\\b"), QStringLiteral("\\breturn\\b"), QStringLiteral("\\bswitch\\b"), 20 | QStringLiteral("\\bwhile\\b"), QStringLiteral("\\bvoid\\b"), QStringLiteral("\\bbool\\b"), 21 | QStringLiteral("\\bint\\b"), QStringLiteral("\\buint\\b"), QStringLiteral("\\bfloat\\b"), 22 | QStringLiteral("\\bdouble\\b"), QStringLiteral("\\bvec[2]\\b"), QStringLiteral("\\bvec[3]\\b"), 23 | QStringLiteral("\\bvec[4]\\b"), QStringLiteral("\\blayout\\b"), QStringLiteral("\\battribute\\b"), 24 | QStringLiteral("\\bcentroid\\b"), QStringLiteral("\\bsampler\\b"), QStringLiteral("\\bpatch\\b"), 25 | QStringLiteral("\\bconst\\b"), QStringLiteral("\\bflat\\b"), QStringLiteral("\\bin\\b"), 26 | QStringLiteral("\\binout\\b"), QStringLiteral("\\binvariant\\b"), QStringLiteral("\\bnoperspective\\b"), 27 | QStringLiteral("\\bout\\b"), QStringLiteral("\\bsmooth\\b"), QStringLiteral("\\buniform\\b"), 28 | QStringLiteral("\\bvarying\\b"), QStringLiteral("\\bbuffer\\b"), QStringLiteral("\\bshared\\b"), 29 | QStringLiteral("\\bcoherent\\b"), QStringLiteral("\\breadonly\\b"), QStringLiteral("\\bwriteonly\\b"), 30 | QStringLiteral("\\bvolatile\\b"), QStringLiteral("\\brestrict\\b"), QStringLiteral("\\bivec2\\b") 31 | }; 32 | for (const QString &pattern : keywordPatterns) { 33 | rule.pattern = QRegularExpression(pattern); 34 | rule.format = keywordFormat; 35 | highlightingRules.append(rule); 36 | } 37 | 38 | classFormat.setFontWeight(QFont::Bold); 39 | classFormat.setForeground(QColor("#dc4c46")); 40 | rule.pattern = QRegularExpression(QStringLiteral("\\bQ[A-Za-z]+\\b")); 41 | rule.format = classFormat; 42 | highlightingRules.append(rule); 43 | 44 | quotationFormat.setForeground(QColor("#9bcf43")); 45 | rule.pattern = QRegularExpression(QStringLiteral("\".*\"")); 46 | rule.format = quotationFormat; 47 | highlightingRules.append(rule); 48 | 49 | functionFormat.setFontItalic(true); 50 | functionFormat.setForeground(QColor("#00b6dd")); 51 | rule.pattern = QRegularExpression(QStringLiteral("\\b[A-Za-z0-9_]+(?=\\()")); 52 | rule.format = functionFormat; 53 | highlightingRules.append(rule); 54 | 55 | singleLineCommentFormat.setForeground(QColor("#e8514b")); 56 | rule.pattern = QRegularExpression(QStringLiteral("//[^\n]*")); 57 | rule.format = singleLineCommentFormat; 58 | highlightingRules.append(rule); 59 | 60 | multiLineCommentFormat.setForeground(QColor("#dc4c46")); 61 | 62 | commentStartExpression = QRegularExpression(QStringLiteral("/\\*")); 63 | commentEndExpression = QRegularExpression(QStringLiteral("\\*/")); 64 | } 65 | 66 | void SyntaxHighlighter::highlightBlock(const QString &text) 67 | { 68 | for (const HighlightingRule &rule : qAsConst(highlightingRules)) { 69 | QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text); 70 | while (matchIterator.hasNext()) { 71 | QRegularExpressionMatch match = matchIterator.next(); 72 | setFormat(match.capturedStart(), match.capturedLength(), rule.format); 73 | } 74 | } 75 | 76 | setCurrentBlockState(0); 77 | 78 | int startIndex = 0; 79 | if (previousBlockState() != 1) 80 | startIndex = text.indexOf(commentStartExpression); 81 | 82 | while (startIndex >= 0) { 83 | QRegularExpressionMatch match = commentEndExpression.match(text, startIndex); 84 | int endIndex = match.capturedStart(); 85 | int commentLength = 0; 86 | if (endIndex == -1) { 87 | setCurrentBlockState(1); 88 | commentLength = text.length() - startIndex; 89 | } else { 90 | commentLength = endIndex - startIndex 91 | + match.capturedLength(); 92 | } 93 | setFormat(startIndex, commentLength, multiLineCommentFormat); 94 | startIndex = text.indexOf(commentStartExpression, startIndex + commentLength); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/vulkanrenderer.h: -------------------------------------------------------------------------------- 1 | #ifndef VULKANRENDERER_H 2 | #define VULKANRENDERER_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "globals.h" 10 | 11 | using namespace ShaderDev; 12 | 13 | class VulkanWindow; 14 | 15 | class VulkanRenderer : public QVulkanWindowRenderer 16 | { 17 | public: 18 | VulkanRenderer(VulkanWindow *w); 19 | ~VulkanRenderer(); 20 | 21 | // Initialize 22 | void initResources() override; 23 | void initSwapChainResources() override; 24 | void releaseSwapChainResources() override; 25 | void releaseResources() override; 26 | 27 | void startNextFrame() override; 28 | 29 | void updateImage(const QString& path); 30 | void updateShader(const ShaderCode& code); 31 | 32 | void translate(float dx, float dy); 33 | void scale(float s); 34 | 35 | std::vector uintVecToCharVec(const std::vector& in); 36 | 37 | QString getGpuName(); 38 | 39 | private: 40 | // Setup 41 | VulkanWindow *window; 42 | VkDevice device; 43 | VkPhysicalDevice physicalDevice; 44 | QVulkanDeviceFunctions *devFuncs; 45 | QVulkanFunctions *f; 46 | 47 | // Initialize 48 | void createVertexBuffer(); 49 | void createSampler(); 50 | void createGraphicsDescriptors(); 51 | void createGraphicsPipelineCache(); 52 | void createGraphicsPipelineLayout(); 53 | void createGraphicsPipeline(); 54 | 55 | // Load image 56 | bool createTexture(const QString &name); 57 | bool createTextureImage(const QSize &size, VkImage *image, VkDeviceMemory *mem, 58 | VkImageTiling tiling, VkImageUsageFlags usage, uint32_t memIndex); 59 | bool writeLinearImage(const QImage &img, VkImage image, VkDeviceMemory memory); 60 | 61 | // Compute 62 | // ONCE 63 | void createComputePipelineLayout(); 64 | void createComputeQueue(); 65 | void createComputeCommandPool(); 66 | void createQueryPool(); 67 | 68 | // RECURRING 69 | // Load shader 70 | VkShaderModule createShaderFromFile(const QString &name); // TODO: Take this out 71 | VkShaderModule createShaderFromCode(const ShaderCode& code); 72 | 73 | bool createComputeRenderTarget( uint32_t width, uint32_t height); 74 | 75 | void createComputeDescriptors(); 76 | void updateComputeDescriptors(); 77 | void createComputeCommandBuffer(); 78 | void createComputePipeline(); 79 | void recordComputeCommandBuffer(); 80 | 81 | // Called in startNextFrame() 82 | void submitComputeCommands(); 83 | void createRenderPass(); 84 | 85 | void updateVertexData(int, int); 86 | 87 | ShaderCode shaderCode; 88 | 89 | VkDeviceMemory bufMem = VK_NULL_HANDLE; 90 | VkBuffer buf = VK_NULL_HANDLE; 91 | VkDescriptorBufferInfo uniformBufInfo[QVulkanWindow::MAX_CONCURRENT_FRAME_COUNT]; 92 | 93 | VkDescriptorPool descPool = VK_NULL_HANDLE; 94 | VkDescriptorSetLayout descSetLayout = VK_NULL_HANDLE; 95 | VkDescriptorSet descSet[QVulkanWindow::MAX_CONCURRENT_FRAME_COUNT]; 96 | 97 | VkPipelineCache pipelineCache = VK_NULL_HANDLE; 98 | VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; 99 | VkPipeline pipeline = VK_NULL_HANDLE; 100 | VkQueryPool queryPool = VK_NULL_HANDLE; 101 | 102 | VkSampler sampler = VK_NULL_HANDLE; 103 | VkFormat texFormat; 104 | VkImage texImage = VK_NULL_HANDLE; 105 | VkDeviceMemory texMem = VK_NULL_HANDLE; 106 | bool texLayoutPending = false; 107 | VkImageView texView = VK_NULL_HANDLE; 108 | VkImage texStaging = VK_NULL_HANDLE; 109 | VkDeviceMemory texStagingMem = VK_NULL_HANDLE; 110 | bool texStagingPending = false; 111 | QSize texSize; 112 | 113 | QImage cpuImage; 114 | QString imagePath = ":/empty.jpg"; 115 | 116 | int concurrentFrameCount; 117 | 118 | QMatrix4x4 projection; 119 | float rotation = 0.0f; 120 | float position_x = 0.0f; 121 | float position_y = 0.0f; 122 | float position_z = 0.0f; 123 | float scaleXY = 1.0f; 124 | 125 | // Compute resources 126 | struct Compute 127 | { 128 | VkQueue queue; // Separate queue for compute commands (queue family may differ from the one used for graphics) 129 | VkCommandPool commandPool; // Use a separate command pool (queue family may differ from the one used for graphics) 130 | VkCommandBuffer commandBuffer; // Command buffer storing the dispatch commands and barriers 131 | VkCommandBuffer commandBufferInit; // Command buffer used only for initial initialization and transfering data accross the pci bus 132 | VkFence fence; // Synchronization fence to avoid rewriting compute CB if still in use 133 | uint32_t queueFamilyIndex; // Family index of the graphics queue, used for barriers 134 | }; 135 | 136 | Compute compute; 137 | VkPipelineLayout computePipelineLayout = VK_NULL_HANDLE; 138 | VkPipeline computePipeline = VK_NULL_HANDLE; 139 | VkDescriptorSetLayout computeDescriptorSetLayout = VK_NULL_HANDLE; 140 | VkDescriptorSet computeDescriptorSet; 141 | 142 | VkDeviceMemory computeRenderTargetMemory = VK_NULL_HANDLE; 143 | VkImage computeRenderTarget = VK_NULL_HANDLE; 144 | VkImageView computeRenderTargetView = VK_NULL_HANDLE; 145 | 146 | }; 147 | 148 | 149 | 150 | #endif // VULKANRENDERER_H 151 | -------------------------------------------------------------------------------- /src/vulkanrenderer.cpp: -------------------------------------------------------------------------------- 1 | #include "vulkanrenderer.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "vulkanwindow.h" 13 | 14 | // Use a triangle strip to get a quad. 15 | static float vertexData[] = { // Y up, front = CW 16 | // x, y, z, u, v 17 | -1, -1, 0, 0, 1, 18 | -1, 1, 0, 0, 0, 19 | 1, -1, 0, 1, 1, 20 | 1, 1, 0, 1, 0 21 | }; 22 | 23 | static const int UNIFORM_DATA_SIZE = 16 * sizeof(float); 24 | 25 | static inline VkDeviceSize aligned(VkDeviceSize v, VkDeviceSize byteAlign) 26 | { 27 | return (v + byteAlign - 1) & ~(byteAlign - 1); 28 | } 29 | 30 | VulkanRenderer::VulkanRenderer(VulkanWindow *w) 31 | : window(w) 32 | { 33 | concurrentFrameCount = window->concurrentFrameCount(); 34 | } 35 | 36 | void VulkanRenderer::initResources() 37 | { 38 | qDebug("initResources"); 39 | 40 | device = window->device(); 41 | physicalDevice = window->physicalDevice(); 42 | devFuncs = window->vulkanInstance()->deviceFunctions(device); 43 | f = window->vulkanInstance()->functions(); 44 | 45 | // Create texture 46 | if (!createTexture(imagePath)) 47 | qFatal("Failed to create texture"); 48 | 49 | createVertexBuffer(); 50 | createSampler(); 51 | createGraphicsDescriptors(); 52 | createGraphicsPipelineCache(); 53 | createGraphicsPipelineLayout(); 54 | createGraphicsPipeline(); 55 | 56 | //Compute 57 | // Create render target 58 | if (!createComputeRenderTarget(cpuImage.width(), cpuImage.height())) 59 | qFatal("Failed to create compute render target."); 60 | 61 | createQueryPool(); 62 | createComputeDescriptors(); 63 | createComputePipelineLayout(); 64 | createComputePipeline(); 65 | createComputeQueue(); 66 | createComputeCommandPool(); 67 | createComputeCommandBuffer(); 68 | 69 | recordComputeCommandBuffer(); 70 | 71 | emit window->rendererHasBeenCreated(); 72 | } 73 | 74 | QString VulkanRenderer::getGpuName() 75 | { 76 | VkPhysicalDeviceProperties deviceProperties = {}; 77 | f->vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties); 78 | auto deviceName = QString::fromLatin1(deviceProperties.deviceName); 79 | 80 | return deviceName; 81 | } 82 | 83 | void VulkanRenderer::createVertexBuffer() 84 | { 85 | // Create vertex buffer 86 | const VkPhysicalDeviceLimits *pdevLimits = &window->physicalDeviceProperties()->limits; 87 | const VkDeviceSize uniAlign = pdevLimits->minUniformBufferOffsetAlignment; 88 | qDebug("uniform buffer offset alignment is %u", (uint) uniAlign); 89 | VkBufferCreateInfo bufInfo; 90 | memset(&bufInfo, 0, sizeof(bufInfo)); 91 | bufInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; 92 | // Our internal layout is vertex, uniform, uniform, ... with each uniform buffer start offset aligned to uniAlign. 93 | const VkDeviceSize vertexAllocSize = aligned(sizeof(vertexData), uniAlign); 94 | const VkDeviceSize uniformAllocSize = aligned(UNIFORM_DATA_SIZE, uniAlign); 95 | bufInfo.size = vertexAllocSize + concurrentFrameCount * uniformAllocSize; 96 | bufInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; 97 | 98 | VkResult err = devFuncs->vkCreateBuffer(device, &bufInfo, nullptr, &buf); 99 | if (err != VK_SUCCESS) 100 | qFatal("Failed to create buffer: %d", err); 101 | 102 | VkMemoryRequirements memReq; 103 | devFuncs->vkGetBufferMemoryRequirements(device, buf, &memReq); 104 | 105 | VkMemoryAllocateInfo memAllocInfo = { 106 | VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, 107 | nullptr, 108 | memReq.size, 109 | window->hostVisibleMemoryIndex() 110 | }; 111 | 112 | err = devFuncs->vkAllocateMemory(device, &memAllocInfo, nullptr, &bufMem); 113 | if (err != VK_SUCCESS) 114 | qFatal("Failed to allocate memory: %d", err); 115 | 116 | err = devFuncs->vkBindBufferMemory(device, buf, bufMem, 0); 117 | if (err != VK_SUCCESS) 118 | qFatal("Failed to bind buffer memory: %d", err); 119 | 120 | quint8 *p; 121 | err = devFuncs->vkMapMemory(device, bufMem, 0, memReq.size, 0, reinterpret_cast(&p)); 122 | if (err != VK_SUCCESS) 123 | qFatal("Failed to map memory: %d", err); 124 | memcpy(p, vertexData, sizeof(vertexData)); 125 | QMatrix4x4 ident; 126 | memset(uniformBufInfo, 0, sizeof(uniformBufInfo)); 127 | for (int i = 0; i < concurrentFrameCount; ++i) { 128 | const VkDeviceSize offset = vertexAllocSize + i * uniformAllocSize; 129 | memcpy(p + offset, ident.constData(), 16 * sizeof(float)); 130 | uniformBufInfo[i].buffer = buf; 131 | uniformBufInfo[i].offset = offset; 132 | uniformBufInfo[i].range = uniformAllocSize; 133 | } 134 | devFuncs->vkUnmapMemory(device, bufMem); 135 | } 136 | 137 | void VulkanRenderer::createSampler() 138 | { 139 | // Create sampler 140 | VkSamplerCreateInfo samplerInfo; 141 | memset(&samplerInfo, 0, sizeof(samplerInfo)); 142 | samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; 143 | samplerInfo.magFilter = VK_FILTER_LINEAR; 144 | samplerInfo.minFilter = VK_FILTER_LINEAR; 145 | samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; 146 | samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; 147 | samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; 148 | samplerInfo.anisotropyEnable = VK_FALSE; 149 | VkResult err = devFuncs->vkCreateSampler(device, &samplerInfo, nullptr, &sampler); 150 | if (err != VK_SUCCESS) 151 | qFatal("Failed to create sampler: %d", err); 152 | } 153 | 154 | void VulkanRenderer::createGraphicsDescriptors() 155 | { 156 | // Create descriptor pool 157 | VkDescriptorPoolSize descPoolSizes[3] = { 158 | { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1 * uint32_t(concurrentFrameCount) }, 159 | { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1 * uint32_t(concurrentFrameCount) }, 160 | { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 2 * uint32_t(concurrentFrameCount) } //two per frame 161 | }; 162 | VkDescriptorPoolCreateInfo descPoolInfo; 163 | memset(&descPoolInfo, 0, sizeof(descPoolInfo)); 164 | descPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; 165 | descPoolInfo.maxSets = 4; 166 | descPoolInfo.poolSizeCount = 3; 167 | descPoolInfo.pPoolSizes = descPoolSizes; 168 | VkResult err = devFuncs->vkCreateDescriptorPool(device, &descPoolInfo, nullptr, &descPool); 169 | if (err != VK_SUCCESS) 170 | qFatal("Failed to create descriptor pool: %d", err); 171 | 172 | // Create DescriptorSetLayout 173 | VkDescriptorSetLayoutBinding layoutBinding[2] = 174 | { 175 | { 176 | 0, // binding 177 | VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 178 | 1, // descriptorCount 179 | VK_SHADER_STAGE_VERTEX_BIT, 180 | nullptr 181 | }, 182 | { 183 | 1, // binding 184 | VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 185 | 1, // descriptorCount 186 | VK_SHADER_STAGE_FRAGMENT_BIT, 187 | nullptr 188 | } 189 | }; 190 | VkDescriptorSetLayoutCreateInfo descLayoutInfo = { 191 | VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, 192 | nullptr, 193 | 0, 194 | 2, // bindingCount 195 | layoutBinding 196 | }; 197 | err = devFuncs->vkCreateDescriptorSetLayout(device, &descLayoutInfo, nullptr, &descSetLayout); 198 | if (err != VK_SUCCESS) 199 | qFatal("Failed to create descriptor set layout: %d", err); 200 | 201 | } 202 | 203 | void VulkanRenderer::createGraphicsPipelineCache() 204 | { 205 | // Pipeline cache 206 | VkPipelineCacheCreateInfo pipelineCacheInfo; 207 | memset(&pipelineCacheInfo, 0, sizeof(pipelineCacheInfo)); 208 | pipelineCacheInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; 209 | VkResult err = devFuncs->vkCreatePipelineCache(device, &pipelineCacheInfo, nullptr, &pipelineCache); 210 | if (err != VK_SUCCESS) 211 | qFatal("Failed to create pipeline cache: %d", err); 212 | } 213 | 214 | void VulkanRenderer::createGraphicsPipelineLayout() 215 | { 216 | // Pipeline layout 217 | VkPipelineLayoutCreateInfo pipelineLayoutInfo; 218 | memset(&pipelineLayoutInfo, 0, sizeof(pipelineLayoutInfo)); 219 | pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; 220 | pipelineLayoutInfo.setLayoutCount = 1; 221 | pipelineLayoutInfo.pSetLayouts = &descSetLayout; 222 | VkResult err = devFuncs->vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout); 223 | if (err != VK_SUCCESS) 224 | qFatal("Failed to create pipeline layout: %d", err); 225 | } 226 | 227 | void VulkanRenderer::createGraphicsPipeline() 228 | { 229 | // Vertex and Fragment shader 230 | VkShaderModule vertShaderModule = createShaderFromFile(":/texture_vert.spv"); 231 | VkShaderModule fragShaderModule = createShaderFromFile(":/texture_frag.spv"); 232 | 233 | // Graphics pipeline 234 | VkGraphicsPipelineCreateInfo pipelineInfo; 235 | memset(&pipelineInfo, 0, sizeof(pipelineInfo)); 236 | pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; 237 | 238 | VkPipelineShaderStageCreateInfo shaderStages[2] = { 239 | { 240 | VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, 241 | nullptr, 242 | 0, 243 | VK_SHADER_STAGE_VERTEX_BIT, 244 | vertShaderModule, 245 | "main", 246 | nullptr 247 | }, 248 | { 249 | VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, 250 | nullptr, 251 | 0, 252 | VK_SHADER_STAGE_FRAGMENT_BIT, 253 | fragShaderModule, 254 | "main", 255 | nullptr 256 | } 257 | }; 258 | 259 | pipelineInfo.stageCount = 2; 260 | pipelineInfo.pStages = shaderStages; 261 | 262 | // Vertex binding 263 | VkVertexInputBindingDescription vertexBindingDesc = { 264 | 0, // binding 265 | 5 * sizeof(float), 266 | VK_VERTEX_INPUT_RATE_VERTEX 267 | }; 268 | VkVertexInputAttributeDescription vertexAttrDesc[] = { 269 | { // position 270 | 0, // location 271 | 0, // binding 272 | VK_FORMAT_R32G32B32_SFLOAT, 273 | 0 274 | }, 275 | { // texcoord 276 | 1, 277 | 0, 278 | VK_FORMAT_R32G32_SFLOAT, 279 | 3 * sizeof(float) 280 | } 281 | }; 282 | 283 | VkPipelineVertexInputStateCreateInfo vertexInputInfo; 284 | vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; 285 | vertexInputInfo.pNext = nullptr; 286 | vertexInputInfo.flags = 0; 287 | vertexInputInfo.vertexBindingDescriptionCount = 1; 288 | vertexInputInfo.pVertexBindingDescriptions = &vertexBindingDesc; 289 | vertexInputInfo.vertexAttributeDescriptionCount = 2; 290 | vertexInputInfo.pVertexAttributeDescriptions = vertexAttrDesc; 291 | 292 | pipelineInfo.pVertexInputState = &vertexInputInfo; 293 | 294 | VkPipelineInputAssemblyStateCreateInfo ia; 295 | memset(&ia, 0, sizeof(ia)); 296 | ia.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; 297 | ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; 298 | pipelineInfo.pInputAssemblyState = &ia; 299 | 300 | // The viewport and scissor will be set dynamically via vkCmdSetViewport/Scissor. 301 | // This way the pipeline does not need to be touched when resizing the window. 302 | VkPipelineViewportStateCreateInfo vp; 303 | memset(&vp, 0, sizeof(vp)); 304 | vp.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; 305 | vp.viewportCount = 1; 306 | vp.scissorCount = 1; 307 | pipelineInfo.pViewportState = &vp; 308 | 309 | VkPipelineRasterizationStateCreateInfo rs; 310 | memset(&rs, 0, sizeof(rs)); 311 | rs.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; 312 | rs.polygonMode = VK_POLYGON_MODE_FILL; 313 | rs.cullMode = VK_CULL_MODE_BACK_BIT; 314 | rs.frontFace = VK_FRONT_FACE_CLOCKWISE; 315 | rs.lineWidth = 1.0f; 316 | pipelineInfo.pRasterizationState = &rs; 317 | 318 | VkPipelineMultisampleStateCreateInfo ms; 319 | memset(&ms, 0, sizeof(ms)); 320 | ms.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; 321 | ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; 322 | pipelineInfo.pMultisampleState = &ms; 323 | 324 | VkPipelineDepthStencilStateCreateInfo ds; 325 | memset(&ds, 0, sizeof(ds)); 326 | ds.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; 327 | ds.depthTestEnable = VK_TRUE; 328 | ds.depthWriteEnable = VK_TRUE; 329 | ds.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL; 330 | pipelineInfo.pDepthStencilState = &ds; 331 | 332 | VkPipelineColorBlendStateCreateInfo cb; 333 | memset(&cb, 0, sizeof(cb)); 334 | cb.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; 335 | // assume pre-multiplied alpha, blend, write out all of rgba 336 | VkPipelineColorBlendAttachmentState att; 337 | memset(&att, 0, sizeof(att)); 338 | att.colorWriteMask = 0xF; 339 | att.blendEnable = VK_TRUE; 340 | att.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; 341 | att.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; 342 | att.colorBlendOp = VK_BLEND_OP_ADD; 343 | att.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; 344 | att.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; 345 | att.alphaBlendOp = VK_BLEND_OP_ADD; 346 | cb.attachmentCount = 1; 347 | cb.pAttachments = &att; 348 | pipelineInfo.pColorBlendState = &cb; 349 | 350 | VkDynamicState dynEnable[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; 351 | VkPipelineDynamicStateCreateInfo dyn; 352 | memset(&dyn, 0, sizeof(dyn)); 353 | dyn.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; 354 | dyn.dynamicStateCount = sizeof(dynEnable) / sizeof(VkDynamicState); 355 | dyn.pDynamicStates = dynEnable; 356 | pipelineInfo.pDynamicState = &dyn; 357 | 358 | pipelineInfo.layout = pipelineLayout; 359 | pipelineInfo.renderPass = window->defaultRenderPass(); 360 | 361 | VkResult err = devFuncs->vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineInfo, nullptr, &pipeline); 362 | if (err != VK_SUCCESS) 363 | qFatal("Failed to create graphics pipeline: %d", err); 364 | 365 | if (vertShaderModule) 366 | devFuncs->vkDestroyShaderModule(device, vertShaderModule, nullptr); 367 | if (fragShaderModule) 368 | devFuncs->vkDestroyShaderModule(device, fragShaderModule, nullptr); 369 | } 370 | 371 | VkShaderModule VulkanRenderer::createShaderFromFile(const QString &name) 372 | { 373 | QFile file(name); 374 | if (!file.open(QIODevice::ReadOnly)) { 375 | qWarning("Failed to read shader %s", qPrintable(name)); 376 | return VK_NULL_HANDLE; 377 | } 378 | QByteArray blob = file.readAll(); 379 | file.close(); 380 | 381 | VkShaderModuleCreateInfo shaderInfo; 382 | memset(&shaderInfo, 0, sizeof(shaderInfo)); 383 | shaderInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; 384 | shaderInfo.codeSize = blob.size(); 385 | shaderInfo.pCode = reinterpret_cast(blob.constData()); 386 | VkShaderModule shaderModule; 387 | VkResult err = devFuncs->vkCreateShaderModule(window->device(), &shaderInfo, nullptr, &shaderModule); 388 | if (err != VK_SUCCESS) { 389 | qWarning("Failed to create shader module: %d", err); 390 | return VK_NULL_HANDLE; 391 | } 392 | 393 | return shaderModule; 394 | } 395 | 396 | bool VulkanRenderer::createComputeRenderTarget(uint32_t width, uint32_t height) 397 | { 398 | VkImageCreateInfo imageInfo = {}; 399 | 400 | imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; 401 | imageInfo.imageType = VK_IMAGE_TYPE_2D; 402 | imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; 403 | imageInfo.extent.width = width; 404 | imageInfo.extent.height = height; 405 | imageInfo.extent.depth = 1; 406 | imageInfo.mipLevels = 1; 407 | imageInfo.arrayLayers = 1; 408 | imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; 409 | imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; 410 | imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT; 411 | imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; 412 | imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; 413 | 414 | //Create the opaque structure that will be referenced later 415 | VkResult err = devFuncs->vkCreateImage(device, &imageInfo, nullptr, &computeRenderTarget); 416 | if (err != VK_SUCCESS) { 417 | qWarning("Failed to create linear image for texture: %d", err); 418 | return false; 419 | } 420 | 421 | //Get how much memory do we need and how it should aligned 422 | VkMemoryRequirements memReq; 423 | devFuncs->vkGetImageMemoryRequirements(device, computeRenderTarget, &memReq); 424 | 425 | //The render target will be on the gpu 426 | uint32_t memIndex = window->deviceLocalMemoryIndex(); 427 | 428 | if (!(memReq.memoryTypeBits & (1 << memIndex))) { 429 | VkPhysicalDeviceMemoryProperties physDevMemProps; 430 | window->vulkanInstance()->functions()->vkGetPhysicalDeviceMemoryProperties(window->physicalDevice(), &physDevMemProps); 431 | for (uint32_t i = 0; i < physDevMemProps.memoryTypeCount; ++i) { 432 | if (!(memReq.memoryTypeBits & (1 << i))) 433 | continue; 434 | memIndex = i; 435 | } 436 | } 437 | 438 | VkMemoryAllocateInfo allocInfo = { 439 | VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, 440 | nullptr, 441 | memReq.size, 442 | memIndex 443 | }; 444 | qDebug("allocating %u bytes for texture image", uint32_t(memReq.size)); 445 | 446 | err = devFuncs->vkAllocateMemory(device, &allocInfo, nullptr, &computeRenderTargetMemory); 447 | if (err != VK_SUCCESS) { 448 | qWarning("Failed to allocate memory for linear image: %d", err); 449 | return false; 450 | } 451 | 452 | //Associate the image with this chunk of memory 453 | err = devFuncs->vkBindImageMemory(device, computeRenderTarget, computeRenderTargetMemory, 0); 454 | if (err != VK_SUCCESS) { 455 | qWarning("Failed to bind linear image memory: %d", err); 456 | return false; 457 | } 458 | 459 | VkImageViewCreateInfo viewInfo = {}; 460 | viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; 461 | viewInfo.image = computeRenderTarget; 462 | viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; 463 | viewInfo.format = texFormat; 464 | viewInfo.components.r = VK_COMPONENT_SWIZZLE_R; 465 | viewInfo.components.g = VK_COMPONENT_SWIZZLE_G; 466 | viewInfo.components.b = VK_COMPONENT_SWIZZLE_B; 467 | viewInfo.components.a = VK_COMPONENT_SWIZZLE_A; 468 | viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 469 | viewInfo.subresourceRange.levelCount = viewInfo.subresourceRange.layerCount = 1; 470 | 471 | err = devFuncs->vkCreateImageView(device, &viewInfo, nullptr, &computeRenderTargetView); 472 | if (err != VK_SUCCESS) { 473 | qWarning("Failed to create image view for texture: %d", err); 474 | return false; 475 | } 476 | 477 | return true; 478 | } 479 | 480 | bool VulkanRenderer::createTexture(const QString &name) 481 | { 482 | cpuImage = QImage(name); 483 | if (cpuImage.isNull()) { 484 | qWarning("Failed to load image %s", qPrintable(name)); 485 | return false; 486 | } 487 | 488 | updateVertexData(cpuImage.width(), cpuImage.height()); 489 | 490 | // Convert to byte ordered RGBA8. Use premultiplied alpha, see pColorBlendState in the pipeline. 491 | cpuImage = cpuImage.convertToFormat(QImage::Format_RGBA8888_Premultiplied); 492 | 493 | // Set to sRGB 494 | texFormat = VK_FORMAT_R8G8B8A8_SRGB; 495 | 496 | // Now we can either map and copy the image data directly, or have to go 497 | // through a staging buffer to copy and convert into the internal optimal 498 | // tiling format. 499 | VkFormatProperties props; 500 | f->vkGetPhysicalDeviceFormatProperties(window->physicalDevice(), texFormat, &props); 501 | const bool canSampleLinear = (props.linearTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT); 502 | const bool canSampleOptimal = (props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT); 503 | if (!canSampleLinear && !canSampleOptimal) { 504 | qWarning("Neither linear nor optimal image sampling is supported for RGBA8"); 505 | return false; 506 | } 507 | 508 | static bool alwaysStage = true; //Force usage accross the PCI bus 509 | 510 | if (canSampleLinear && !alwaysStage) { 511 | if (!createTextureImage(cpuImage.size(), 512 | &texImage, &texMem, 513 | VK_IMAGE_TILING_LINEAR, VK_IMAGE_USAGE_SAMPLED_BIT, 514 | window->hostVisibleMemoryIndex())) 515 | return false; 516 | 517 | if (!writeLinearImage(cpuImage, texImage, texMem)) 518 | return false; 519 | 520 | texLayoutPending = true; 521 | } else { 522 | if (!createTextureImage(cpuImage.size(), &texStaging, &texStagingMem, 523 | VK_IMAGE_TILING_LINEAR, VK_IMAGE_USAGE_TRANSFER_SRC_BIT, 524 | window->hostVisibleMemoryIndex())) 525 | return false; 526 | 527 | if (!createTextureImage(cpuImage.size(), &texImage, &texMem, 528 | VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT, 529 | window->deviceLocalMemoryIndex())) 530 | return false; 531 | 532 | if (!writeLinearImage(cpuImage, texStaging, texStagingMem)) 533 | return false; 534 | 535 | texStagingPending = true; 536 | } 537 | 538 | VkImageViewCreateInfo viewInfo; 539 | memset(&viewInfo, 0, sizeof(viewInfo)); 540 | viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; 541 | viewInfo.image = texImage; 542 | viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; 543 | viewInfo.format = texFormat; 544 | viewInfo.components.r = VK_COMPONENT_SWIZZLE_R; 545 | viewInfo.components.g = VK_COMPONENT_SWIZZLE_G; 546 | viewInfo.components.b = VK_COMPONENT_SWIZZLE_B; 547 | viewInfo.components.a = VK_COMPONENT_SWIZZLE_A; 548 | viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 549 | viewInfo.subresourceRange.levelCount = viewInfo.subresourceRange.layerCount = 1; 550 | 551 | VkResult err = devFuncs->vkCreateImageView(device, &viewInfo, nullptr, &texView); 552 | if (err != VK_SUCCESS) { 553 | qWarning("Failed to create image view for texture: %d", err); 554 | return false; 555 | } 556 | 557 | texSize = cpuImage.size(); 558 | 559 | return true; 560 | } 561 | 562 | void VulkanRenderer::createComputeDescriptors() 563 | { 564 | if (computeDescriptorSetLayout == VK_NULL_HANDLE) 565 | { 566 | // Define the layout of the input of the shader. 567 | // 1 image to read, 1 image to write 568 | VkDescriptorSetLayoutBinding bindings[2]= {}; 569 | 570 | bindings[0].binding = 0; 571 | bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; 572 | bindings[0].descriptorCount = 1; 573 | bindings[0].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; 574 | 575 | bindings[1].binding = 1; 576 | bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; 577 | bindings[1].descriptorCount = 1; 578 | bindings[1].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; 579 | 580 | VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo {}; 581 | descriptorSetLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; 582 | descriptorSetLayoutCreateInfo.pBindings = bindings; 583 | descriptorSetLayoutCreateInfo.bindingCount = 2; 584 | 585 | //Create the layout, store it to share between shaders 586 | VkResult err = devFuncs->vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCreateInfo, nullptr, &computeDescriptorSetLayout); 587 | if (err != VK_SUCCESS) 588 | qFatal("Failed to create compute descriptor set layout: %d", err); 589 | } 590 | 591 | //Descriptor sets 592 | for (int i = 0; i < concurrentFrameCount; ++i) 593 | { 594 | VkDescriptorSetAllocateInfo descSetAllocInfo = { 595 | VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, 596 | nullptr, 597 | descPool, 598 | 1, 599 | &descSetLayout 600 | }; 601 | VkResult err = devFuncs->vkAllocateDescriptorSets(device, &descSetAllocInfo, &descSet[i]); 602 | if (err != VK_SUCCESS) 603 | qFatal("Failed to allocate descriptor set: %d", err); 604 | 605 | { 606 | VkDescriptorSetAllocateInfo descSetAllocInfo = { 607 | VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, 608 | nullptr, 609 | descPool, 610 | 1, 611 | &computeDescriptorSetLayout 612 | }; 613 | VkResult err = devFuncs->vkAllocateDescriptorSets(device, &descSetAllocInfo, &computeDescriptorSet); 614 | if (err != VK_SUCCESS) 615 | qFatal("Failed to allocate descriptor set: %d", err); 616 | } 617 | } 618 | 619 | updateComputeDescriptors(); 620 | } 621 | 622 | void VulkanRenderer::updateComputeDescriptors() 623 | { 624 | for (int i = 0; i < concurrentFrameCount; ++i) 625 | { 626 | VkWriteDescriptorSet descWrite[2]; 627 | memset(descWrite, 0, sizeof(descWrite)); 628 | descWrite[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; 629 | descWrite[0].dstSet = descSet[i]; 630 | descWrite[0].dstBinding = 0; 631 | descWrite[0].descriptorCount = 1; 632 | descWrite[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; 633 | descWrite[0].pBufferInfo = &uniformBufInfo[i]; 634 | 635 | VkDescriptorImageInfo descImageInfo = { 636 | sampler, 637 | computeRenderTargetView, 638 | VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL 639 | }; 640 | 641 | descWrite[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; 642 | descWrite[1].dstSet = descSet[i]; 643 | descWrite[1].dstBinding = 1; 644 | descWrite[1].descriptorCount = 1; 645 | descWrite[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; 646 | descWrite[1].pImageInfo = &descImageInfo; 647 | 648 | devFuncs->vkUpdateDescriptorSets(device, 2, descWrite, 0, nullptr); 649 | } 650 | 651 | { 652 | 653 | VkDescriptorImageInfo destinationInfo = { }; 654 | destinationInfo.imageView = computeRenderTargetView; 655 | destinationInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL; 656 | 657 | VkDescriptorImageInfo sourceInfo = { }; 658 | sourceInfo.imageView = texView; 659 | sourceInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL; 660 | 661 | VkWriteDescriptorSet descWrite[2]= {}; 662 | 663 | descWrite[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; 664 | descWrite[0].dstSet = computeDescriptorSet; 665 | descWrite[0].dstBinding = 0; 666 | descWrite[0].descriptorCount = 1; 667 | descWrite[0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE ; 668 | descWrite[0].pImageInfo = &sourceInfo; 669 | 670 | descWrite[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; 671 | descWrite[1].dstSet = computeDescriptorSet; 672 | descWrite[1].dstBinding = 1; 673 | descWrite[1].descriptorCount = 1; 674 | descWrite[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE ; 675 | descWrite[1].pImageInfo = &destinationInfo; 676 | devFuncs->vkUpdateDescriptorSets(device, 2, descWrite, 0, nullptr); 677 | } 678 | } 679 | 680 | void VulkanRenderer::createComputePipelineLayout() 681 | { 682 | //Now create the layout info 683 | VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; 684 | pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; 685 | pipelineLayoutInfo.setLayoutCount = 1; 686 | pipelineLayoutInfo.pSetLayouts = &computeDescriptorSetLayout; 687 | 688 | //Create the layout, store it to share between shaders 689 | VkResult err = devFuncs->vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &computePipelineLayout); 690 | if (err != VK_SUCCESS) 691 | qFatal("Failed to create compute pipeline layout: %d", err); 692 | } 693 | 694 | void VulkanRenderer::createComputePipeline() 695 | { 696 | // Loads shader and creates a pipeline 697 | // Shaders 698 | VkShaderModule computeShaderModule = createShaderFromCode(shaderCode); 699 | 700 | VkPipelineShaderStageCreateInfo computeStage = { 701 | 702 | VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, 703 | nullptr, 704 | 0, 705 | VK_SHADER_STAGE_COMPUTE_BIT, 706 | computeShaderModule, 707 | "main", 708 | nullptr 709 | }; 710 | 711 | VkComputePipelineCreateInfo pipelineInfo = {}; 712 | //pipelineInfo.pNext = nullptr; 713 | //pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; 714 | pipelineInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; 715 | pipelineInfo.stage = computeStage; 716 | pipelineInfo.layout = computePipelineLayout; 717 | 718 | VkResult err = devFuncs->vkCreateComputePipelines(device, pipelineCache, 1, &pipelineInfo, nullptr, &computePipeline); 719 | if (err != VK_SUCCESS) 720 | qFatal("Failed to create compute pipeline: %d", err); 721 | 722 | if (computeShaderModule) 723 | devFuncs->vkDestroyShaderModule(device, computeShaderModule, nullptr); 724 | } 725 | 726 | void VulkanRenderer::createComputeQueue() 727 | { 728 | VkPhysicalDevice physicalDevice = window->physicalDevice(); 729 | 730 | uint32_t queueFamilyCount; 731 | f->vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, NULL); 732 | 733 | std::vector queueFamilyProperties; 734 | queueFamilyProperties.resize(queueFamilyCount); 735 | 736 | f->vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilyProperties.data()); 737 | 738 | bool computeQueueFound = false; 739 | for (auto i = 0U; i < queueFamilyProperties.size(); ++i) 740 | { 741 | if ((queueFamilyProperties[i].queueFlags & VK_QUEUE_COMPUTE_BIT) && ((queueFamilyProperties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) == 0)) 742 | { 743 | compute.queueFamilyIndex = i; 744 | computeQueueFound = true; 745 | break; 746 | } 747 | } 748 | 749 | computeQueueFound = false; 750 | 751 | // If there is no dedicated compute queue, just find the first queue family that supports compute 752 | if (!computeQueueFound) 753 | { 754 | for (auto i = 0U; i < queueFamilyProperties.size(); ++i) 755 | { 756 | if (queueFamilyProperties[i].queueFlags & VK_QUEUE_COMPUTE_BIT) 757 | { 758 | compute.queueFamilyIndex = i; 759 | computeQueueFound = true; 760 | break; 761 | } 762 | } 763 | } 764 | 765 | // Get a compute queue from the device 766 | devFuncs->vkGetDeviceQueue(device, compute.queueFamilyIndex, 0, &compute.queue); 767 | } 768 | 769 | void VulkanRenderer::createComputeCommandPool() 770 | { 771 | // Separate command pool as queue family for compute may be different than graphics 772 | VkCommandPoolCreateInfo cmdPoolInfo = {}; 773 | cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; 774 | cmdPoolInfo.queueFamilyIndex = compute.queueFamilyIndex; 775 | cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; 776 | 777 | VkResult err; 778 | 779 | err = devFuncs->vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &compute.commandPool); 780 | 781 | if (err != VK_SUCCESS) 782 | qFatal("Failed to create compute command pool: %d", err); 783 | } 784 | 785 | void VulkanRenderer::createQueryPool() 786 | { 787 | VkQueryPoolCreateInfo queryPooloolInfo = {}; 788 | queryPooloolInfo.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO; 789 | queryPooloolInfo.pNext = nullptr; 790 | queryPooloolInfo.queryType = VK_QUERY_TYPE_TIMESTAMP; 791 | queryPooloolInfo.queryCount = 2; 792 | 793 | VkResult err = devFuncs->vkCreateQueryPool(device, &queryPooloolInfo, nullptr, &queryPool); 794 | if (err != VK_SUCCESS) 795 | qFatal("Failed to create query pool: %d", err); 796 | } 797 | 798 | void VulkanRenderer::createComputeCommandBuffer() 799 | { 800 | // Create a command buffer for compute operations 801 | VkCommandBufferAllocateInfo commandBufferAllocateInfo {}; 802 | commandBufferAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; 803 | commandBufferAllocateInfo.commandPool = compute.commandPool; 804 | commandBufferAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; 805 | commandBufferAllocateInfo.commandBufferCount = 2; 806 | 807 | VkCommandBuffer buffers[2] = {}; 808 | VkResult err = devFuncs->vkAllocateCommandBuffers(device, &commandBufferAllocateInfo, &buffers[0]); 809 | 810 | if (err != VK_SUCCESS) 811 | qFatal("Failed to allocate descriptor set: %d", err); 812 | 813 | compute.commandBuffer = buffers[0]; 814 | compute.commandBufferInit= buffers[1]; 815 | 816 | // Fence for compute CB sync 817 | VkFenceCreateInfo fenceCreateInfo = {}; 818 | fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; 819 | fenceCreateInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; 820 | 821 | err = devFuncs->vkCreateFence(device, &fenceCreateInfo, nullptr, &compute.fence); 822 | 823 | if (err != VK_SUCCESS) 824 | qFatal("Failed to create fence: %d", err); 825 | 826 | // Flush the queue if we're rebuilding the command buffer after a pipeline change to ensure it's not currently in use 827 | devFuncs->vkQueueWaitIdle(compute.queue); 828 | 829 | VkCommandBufferBeginInfo cmdBufferBeginInfo {}; 830 | cmdBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; 831 | 832 | err = devFuncs->vkBeginCommandBuffer(compute.commandBufferInit, &cmdBufferBeginInfo); 833 | 834 | if (err != VK_SUCCESS) 835 | qFatal("Failed to begin command buffer: %d", err); 836 | 837 | VkCommandBuffer cb = compute.commandBufferInit; 838 | 839 | // Make the barriers for the resources 840 | VkImageMemoryBarrier barrier = {}; 841 | memset(&barrier, 0, sizeof(barrier)); 842 | barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; 843 | barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 844 | barrier.subresourceRange.levelCount = barrier.subresourceRange.layerCount = 1; 845 | 846 | barrier.oldLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; 847 | barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; 848 | barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; 849 | barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; 850 | barrier.image = texStaging; 851 | 852 | devFuncs->vkCmdPipelineBarrier(cb, 853 | VK_PIPELINE_STAGE_HOST_BIT, 854 | VK_PIPELINE_STAGE_TRANSFER_BIT, 855 | 0, 0, nullptr, 0, nullptr, 856 | 1, &barrier); 857 | 858 | barrier.oldLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; 859 | barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; 860 | barrier.srcAccessMask = 0; 861 | barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; 862 | barrier.image = texImage; 863 | 864 | devFuncs->vkCmdPipelineBarrier(cb, 865 | VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 866 | VK_PIPELINE_STAGE_TRANSFER_BIT, 867 | 0, 0, nullptr, 0, nullptr, 868 | 1, &barrier); 869 | 870 | VkImageCopy copyInfo; 871 | memset(©Info, 0, sizeof(copyInfo)); 872 | copyInfo.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 873 | copyInfo.srcSubresource.layerCount = 1; 874 | copyInfo.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 875 | copyInfo.dstSubresource.layerCount = 1; 876 | copyInfo.extent.width = texSize.width(); 877 | copyInfo.extent.height = texSize.height(); 878 | copyInfo.extent.depth = 1; 879 | devFuncs->vkCmdCopyImage(cb, texStaging, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, 880 | texImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©Info); 881 | 882 | { 883 | barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; 884 | barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; 885 | barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; 886 | barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; 887 | barrier.image = texImage; 888 | 889 | devFuncs->vkCmdPipelineBarrier(cb, 890 | VK_PIPELINE_STAGE_TRANSFER_BIT, 891 | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 892 | 0, 0, nullptr, 0, nullptr, 893 | 1, &barrier); 894 | 895 | barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; 896 | barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; 897 | barrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT; 898 | barrier.dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT; 899 | barrier.image = computeRenderTarget; 900 | 901 | devFuncs->vkCmdPipelineBarrier(cb, 902 | VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 903 | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 904 | 0, 0, nullptr, 0, nullptr, 905 | 1, &barrier); 906 | } 907 | 908 | devFuncs->vkCmdBindPipeline(compute.commandBufferInit, VK_PIPELINE_BIND_POINT_COMPUTE, computePipeline); 909 | devFuncs->vkCmdBindDescriptorSets(compute.commandBufferInit, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 0, 1, &computeDescriptorSet, 0, 0); 910 | devFuncs->vkCmdDispatch(compute.commandBufferInit, cpuImage.width() / 16, cpuImage.height() / 16, 1); 911 | 912 | { 913 | //Make the barriers for the resources 914 | VkImageMemoryBarrier barrier[2] = {}; 915 | 916 | barrier[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; 917 | barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 918 | barrier[0].subresourceRange.levelCount = barrier[0].subresourceRange.layerCount = 1; 919 | 920 | barrier[0].oldLayout = VK_IMAGE_LAYOUT_GENERAL; 921 | barrier[0].newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; 922 | barrier[0].srcAccessMask = 0; 923 | barrier[0].dstAccessMask = 0; 924 | barrier[0].image = texImage; 925 | 926 | barrier[1].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; 927 | barrier[1].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 928 | barrier[1].subresourceRange.levelCount = barrier[1].subresourceRange.layerCount = 1; 929 | 930 | barrier[1].oldLayout = VK_IMAGE_LAYOUT_GENERAL; 931 | barrier[1].newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; 932 | barrier[1].srcAccessMask = 0; 933 | barrier[1].dstAccessMask = 0; 934 | barrier[1].image = computeRenderTarget; 935 | 936 | devFuncs->vkCmdPipelineBarrier(cb, 937 | VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 938 | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 939 | 0, 0, nullptr, 0, nullptr, 940 | 2, &barrier[0]); 941 | } 942 | 943 | devFuncs->vkEndCommandBuffer(compute.commandBufferInit); 944 | } 945 | 946 | 947 | bool VulkanRenderer::createTextureImage(const QSize &size, VkImage *image, VkDeviceMemory *mem, 948 | VkImageTiling tiling, VkImageUsageFlags usage, uint32_t memIndex) 949 | { 950 | VkImageCreateInfo imageInfo; 951 | memset(&imageInfo, 0, sizeof(imageInfo)); 952 | imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; 953 | imageInfo.imageType = VK_IMAGE_TYPE_2D; 954 | imageInfo.format = texFormat; 955 | imageInfo.extent.width = size.width(); 956 | imageInfo.extent.height = size.height(); 957 | imageInfo.extent.depth = 1; 958 | imageInfo.mipLevels = 1; 959 | imageInfo.arrayLayers = 1; 960 | imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; 961 | imageInfo.tiling = tiling; 962 | imageInfo.usage = usage; 963 | imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; 964 | 965 | VkResult err = devFuncs->vkCreateImage(device, &imageInfo, nullptr, image); 966 | if (err != VK_SUCCESS) { 967 | qWarning("Failed to create linear image for texture: %d", err); 968 | return false; 969 | } 970 | 971 | VkMemoryRequirements memReq; 972 | devFuncs->vkGetImageMemoryRequirements(device, *image, &memReq); 973 | 974 | if (!(memReq.memoryTypeBits & (1 << memIndex))) { 975 | VkPhysicalDeviceMemoryProperties physDevMemProps; 976 | window->vulkanInstance()->functions()->vkGetPhysicalDeviceMemoryProperties(window->physicalDevice(), &physDevMemProps); 977 | for (uint32_t i = 0; i < physDevMemProps.memoryTypeCount; ++i) { 978 | if (!(memReq.memoryTypeBits & (1 << i))) 979 | continue; 980 | memIndex = i; 981 | } 982 | } 983 | 984 | VkMemoryAllocateInfo allocInfo = { 985 | VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, 986 | nullptr, 987 | memReq.size, 988 | memIndex 989 | }; 990 | qDebug("allocating %u bytes for texture image", uint32_t(memReq.size)); 991 | 992 | err = devFuncs->vkAllocateMemory(device, &allocInfo, nullptr, mem); 993 | if (err != VK_SUCCESS) { 994 | qWarning("Failed to allocate memory for linear image: %d", err); 995 | return false; 996 | } 997 | 998 | err = devFuncs->vkBindImageMemory(device, *image, *mem, 0); 999 | if (err != VK_SUCCESS) { 1000 | qWarning("Failed to bind linear image memory: %d", err); 1001 | return false; 1002 | } 1003 | 1004 | return true; 1005 | } 1006 | 1007 | bool VulkanRenderer::writeLinearImage(const QImage &img, VkImage image, VkDeviceMemory memory) 1008 | { 1009 | VkImageSubresource subres = { 1010 | VK_IMAGE_ASPECT_COLOR_BIT, 1011 | 0, // mip level 1012 | 0 1013 | }; 1014 | VkSubresourceLayout layout; 1015 | devFuncs->vkGetImageSubresourceLayout(device, image, &subres, &layout); 1016 | 1017 | uchar *p; 1018 | VkResult err = devFuncs->vkMapMemory(device, memory, layout.offset, layout.size, 0, reinterpret_cast(&p)); 1019 | if (err != VK_SUCCESS) { 1020 | qWarning("Failed to map memory for linear image: %d", err); 1021 | return false; 1022 | } 1023 | 1024 | for (int y = 0; y < img.height(); ++y) { 1025 | const uchar *line = img.constScanLine(y); 1026 | memcpy(p, line, img.width() * 4); 1027 | p += layout.rowPitch; 1028 | } 1029 | 1030 | devFuncs->vkUnmapMemory(device, memory); 1031 | return true; 1032 | } 1033 | 1034 | void VulkanRenderer::updateVertexData( int w, int h) 1035 | { 1036 | vertexData[0] = -0.002 * w; 1037 | vertexData[5] = -0.002 * w; 1038 | vertexData[10] = 0.002 * w; 1039 | vertexData[15] = 0.002 * w; 1040 | vertexData[1] = -0.002 * h; 1041 | vertexData[6] = 0.002 * h; 1042 | vertexData[11] = -0.002 * h; 1043 | vertexData[16] = 0.002 * h; 1044 | } 1045 | 1046 | 1047 | void VulkanRenderer::initSwapChainResources() 1048 | { 1049 | qDebug("initSwapChainResources"); 1050 | 1051 | // Projection matrix 1052 | projection = window->clipCorrectionMatrix(); // adjust for Vulkan-OpenGL clip space differences 1053 | const QSize sz = window->swapChainImageSize(); 1054 | projection.perspective(45.0f, sz.width() / (float) sz.height(), 0.01f, 100.0f); 1055 | projection.translate(0, 0, -4); 1056 | } 1057 | 1058 | void VulkanRenderer::recordComputeCommandBuffer() 1059 | { 1060 | // Records the compute command buffer for using the texture image 1061 | // Needs the right render target 1062 | 1063 | // Flush the queue if we're rebuilding the command buffer after a pipeline change to ensure it's not currently in use 1064 | devFuncs->vkQueueWaitIdle(compute.queue); 1065 | 1066 | VkCommandBufferBeginInfo cmdBufferBeginInfo {}; 1067 | cmdBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; 1068 | 1069 | VkResult err = devFuncs->vkBeginCommandBuffer(compute.commandBuffer, &cmdBufferBeginInfo); 1070 | if (err != VK_SUCCESS) 1071 | qFatal("Failed to begin command buffer: %d", err); 1072 | 1073 | VkCommandBuffer cb = compute.commandBuffer; 1074 | 1075 | { 1076 | //Make the barriers for the resources 1077 | VkImageMemoryBarrier barrier[2] = {}; 1078 | 1079 | barrier[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; 1080 | barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 1081 | barrier[0].subresourceRange.levelCount = barrier[0].subresourceRange.layerCount = 1; 1082 | 1083 | barrier[0].oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; 1084 | barrier[0].newLayout = VK_IMAGE_LAYOUT_GENERAL; 1085 | barrier[0].srcAccessMask = 0; 1086 | barrier[0].dstAccessMask = 0; 1087 | barrier[0].image = texImage; 1088 | 1089 | barrier[1].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; 1090 | barrier[1].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 1091 | barrier[1].subresourceRange.levelCount = barrier[1].subresourceRange.layerCount = 1; 1092 | 1093 | barrier[1].oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; 1094 | barrier[1].newLayout = VK_IMAGE_LAYOUT_GENERAL; 1095 | barrier[1].srcAccessMask = 0; 1096 | barrier[1].dstAccessMask = 0; 1097 | barrier[1].image = computeRenderTarget; 1098 | 1099 | devFuncs->vkCmdPipelineBarrier(cb, 1100 | VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 1101 | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 1102 | 0, 0, nullptr, 0, nullptr, 1103 | 2, &barrier[0]); 1104 | } 1105 | 1106 | devFuncs->vkCmdBindPipeline(compute.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipeline); 1107 | devFuncs->vkCmdBindDescriptorSets(compute.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 0, 1, &computeDescriptorSet, 0, 0); 1108 | devFuncs->vkCmdDispatch(compute.commandBuffer, cpuImage.width() / 16, cpuImage.height() / 16, 1); 1109 | 1110 | { 1111 | //Make the barriers for the resources 1112 | VkImageMemoryBarrier barrier[2] = {}; 1113 | 1114 | barrier[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; 1115 | barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 1116 | barrier[0].subresourceRange.levelCount = barrier[0].subresourceRange.layerCount = 1; 1117 | 1118 | barrier[0].oldLayout = VK_IMAGE_LAYOUT_GENERAL; 1119 | barrier[0].newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; 1120 | barrier[0].srcAccessMask = 0; 1121 | barrier[0].dstAccessMask = 0; 1122 | barrier[0].image = texImage; 1123 | 1124 | barrier[1].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; 1125 | barrier[1].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 1126 | barrier[1].subresourceRange.levelCount = barrier[1].subresourceRange.layerCount = 1; 1127 | 1128 | barrier[1].oldLayout = VK_IMAGE_LAYOUT_GENERAL; 1129 | barrier[1].newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; 1130 | barrier[1].srcAccessMask = 0; 1131 | barrier[1].dstAccessMask = 0; 1132 | barrier[1].image = computeRenderTarget; 1133 | 1134 | devFuncs->vkCmdPipelineBarrier(cb, 1135 | VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 1136 | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 1137 | 0, 0, nullptr, 0, nullptr, 1138 | 2, &barrier[0]); 1139 | } 1140 | 1141 | devFuncs->vkEndCommandBuffer(compute.commandBuffer); 1142 | } 1143 | 1144 | void VulkanRenderer::createRenderPass() 1145 | { 1146 | VkCommandBuffer cb = window->currentCommandBuffer(); 1147 | const QSize sz = window->swapChainImageSize(); 1148 | 1149 | // Clear background 1150 | VkClearColorValue clearColor = {{ 0.0f, 0.0f, 0.0f, 1.0f }}; 1151 | VkClearDepthStencilValue clearDS = { 1, 0 }; 1152 | VkClearValue clearValues[2]; 1153 | memset(clearValues, 0, sizeof(clearValues)); 1154 | clearValues[0].color = clearColor; 1155 | clearValues[1].depthStencil = clearDS; 1156 | 1157 | VkRenderPassBeginInfo rpBeginInfo; 1158 | memset(&rpBeginInfo, 0, sizeof(rpBeginInfo)); 1159 | rpBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; 1160 | rpBeginInfo.renderPass = window->defaultRenderPass(); 1161 | rpBeginInfo.framebuffer = window->currentFramebuffer(); 1162 | rpBeginInfo.renderArea.extent.width = sz.width(); 1163 | rpBeginInfo.renderArea.extent.height = sz.height(); 1164 | rpBeginInfo.clearValueCount = 2; 1165 | rpBeginInfo.pClearValues = clearValues; 1166 | VkCommandBuffer cmdBuf = window->currentCommandBuffer(); 1167 | devFuncs->vkCmdBeginRenderPass(cmdBuf, &rpBeginInfo, VK_SUBPASS_CONTENTS_INLINE); 1168 | 1169 | quint8 *p; 1170 | VkResult err = devFuncs->vkMapMemory(device, bufMem, uniformBufInfo[window->currentFrame()].offset, 1171 | UNIFORM_DATA_SIZE, 0, reinterpret_cast(&p)); 1172 | if (err != VK_SUCCESS) 1173 | qFatal("Failed to map memory: %d", err); 1174 | QMatrix4x4 m = projection; 1175 | 1176 | QMatrix4x4 rotation; 1177 | rotation.setToIdentity(); 1178 | 1179 | QMatrix4x4 translation; 1180 | translation.setToIdentity(); 1181 | translation.translate(position_x, position_y, position_z); 1182 | 1183 | QMatrix4x4 scale; 1184 | scale.setToIdentity(); 1185 | scale.scale(scaleXY, scaleXY, scaleXY); 1186 | 1187 | m = m * translation * scale; 1188 | 1189 | memcpy(p, m.constData(), 16 * sizeof(float)); 1190 | devFuncs->vkUnmapMemory(device, bufMem); 1191 | 1192 | devFuncs->vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); 1193 | devFuncs->vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, 1194 | &descSet[window->currentFrame()], 0, nullptr); 1195 | VkDeviceSize vbOffset = 0; 1196 | devFuncs->vkCmdBindVertexBuffers(cb, 0, 1, &buf, &vbOffset); 1197 | 1198 | //negative viewport 1199 | VkViewport viewport; 1200 | viewport.x = 0; 1201 | viewport.y = 0; 1202 | viewport.width = sz.width(); 1203 | viewport.height = sz.height(); 1204 | viewport.minDepth = 0; 1205 | viewport.maxDepth = 1; 1206 | devFuncs->vkCmdSetViewport(cb, 0, 1, &viewport); 1207 | 1208 | VkRect2D scissor; 1209 | scissor.offset.x = scissor.offset.y = 0; 1210 | scissor.extent.width = viewport.width; 1211 | scissor.extent.height = viewport.height; 1212 | devFuncs->vkCmdSetScissor(cb, 0, 1, &scissor); 1213 | 1214 | devFuncs->vkCmdDraw(cb, 4, 1, 0, 0); 1215 | 1216 | devFuncs->vkCmdEndRenderPass(cmdBuf); 1217 | } 1218 | 1219 | void VulkanRenderer::submitComputeCommands() 1220 | { 1221 | // Submit compute commands 1222 | // Use a fence to ensure that compute command buffer has finished executing before using it again 1223 | devFuncs->vkWaitForFences(device, 1, &compute.fence, VK_TRUE, UINT64_MAX); 1224 | devFuncs->vkResetFences(device, 1, &compute.fence); 1225 | 1226 | //Do the copy on the compute queue 1227 | if (texStagingPending) 1228 | { 1229 | texStagingPending = false; 1230 | VkSubmitInfo computeSubmitInfo {}; 1231 | computeSubmitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; 1232 | computeSubmitInfo.commandBufferCount = 1; 1233 | computeSubmitInfo.pCommandBuffers = &compute.commandBufferInit; 1234 | devFuncs->vkQueueSubmit(compute.queue, 1, &computeSubmitInfo, compute.fence); 1235 | } 1236 | else 1237 | { 1238 | VkSubmitInfo computeSubmitInfo {}; 1239 | computeSubmitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; 1240 | computeSubmitInfo.commandBufferCount = 1; 1241 | computeSubmitInfo.pCommandBuffers = &compute.commandBuffer; 1242 | devFuncs->vkQueueSubmit(compute.queue, 1, &computeSubmitInfo, compute.fence); 1243 | } 1244 | } 1245 | 1246 | void VulkanRenderer::updateImage(const QString& path) 1247 | { 1248 | imagePath = path; 1249 | 1250 | // Create texture 1251 | if (!createTexture(imagePath)) 1252 | qFatal("Failed to create texture"); 1253 | 1254 | // Updates the projection size 1255 | createVertexBuffer(); 1256 | 1257 | // Create render target 1258 | if (!createComputeRenderTarget(cpuImage.width(), cpuImage.height())) 1259 | qFatal("Failed to create compute render target."); 1260 | 1261 | updateComputeDescriptors(); 1262 | 1263 | createComputeCommandBuffer(); 1264 | recordComputeCommandBuffer(); 1265 | 1266 | window->requestUpdate(); 1267 | } 1268 | 1269 | void VulkanRenderer::updateShader(const ShaderCode& code) 1270 | { 1271 | shaderCode = code; 1272 | 1273 | createComputePipeline(); 1274 | recordComputeCommandBuffer(); 1275 | 1276 | window->requestUpdate(); 1277 | } 1278 | 1279 | std::vector VulkanRenderer::uintVecToCharVec(const std::vector& in) 1280 | { 1281 | std::vector out; 1282 | 1283 | for (size_t i = 0; i < in.size(); i++) 1284 | { 1285 | out.push_back(in[i] >> 0); 1286 | out.push_back(in[i] >> 8); 1287 | out.push_back(in[i] >> 16); 1288 | out.push_back(in[i] >> 24); 1289 | } 1290 | 1291 | return out; 1292 | } 1293 | 1294 | VkShaderModule VulkanRenderer::createShaderFromCode(const ShaderCode& code) 1295 | { 1296 | // TODO: Refactor this! 1297 | // If this receives empty shader code we go ahead and read the NOOP shader from disk and continue rendering. 1298 | // Pretty bad, needs to be changed. 1299 | 1300 | if (shaderCode.size() == 0 || window->getShowOriginal()) 1301 | { 1302 | QFile file(":/noop_comp.spv"); 1303 | if (!file.open(QIODevice::ReadOnly)) { 1304 | qWarning("Failed to read shader."); 1305 | return VK_NULL_HANDLE; 1306 | } 1307 | QByteArray blob = file.readAll(); 1308 | file.close(); 1309 | 1310 | VkShaderModuleCreateInfo shaderInfo; 1311 | memset(&shaderInfo, 0, sizeof(shaderInfo)); 1312 | shaderInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; 1313 | shaderInfo.codeSize = blob.size(); 1314 | shaderInfo.pCode = reinterpret_cast(blob.constData()); 1315 | VkShaderModule shaderModule; 1316 | VkResult err = devFuncs->vkCreateShaderModule(window->device(), &shaderInfo, nullptr, &shaderModule); 1317 | if (err != VK_SUCCESS) { 1318 | qWarning("Failed to create shader module: %d", err); 1319 | return VK_NULL_HANDLE; 1320 | } 1321 | 1322 | return shaderModule; 1323 | } 1324 | else 1325 | { 1326 | auto codeChar = uintVecToCharVec(code); // TODO: Is this necessary? 1327 | 1328 | QByteArray codeArray = QByteArray(reinterpret_cast(codeChar.data()), codeChar.size()); 1329 | 1330 | VkShaderModuleCreateInfo shaderInfo; 1331 | memset(&shaderInfo, 0, sizeof(shaderInfo)); 1332 | shaderInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; 1333 | shaderInfo.codeSize = codeArray.size(); 1334 | shaderInfo.pCode = reinterpret_cast(codeArray.constData()); //reinterpret_cast(code.data()); 1335 | VkShaderModule shaderModule; 1336 | VkResult err = devFuncs->vkCreateShaderModule(window->device(), &shaderInfo, nullptr, &shaderModule); 1337 | if (err != VK_SUCCESS) { 1338 | qWarning("Failed to create shader module: %d", err); 1339 | return VK_NULL_HANDLE; 1340 | } 1341 | 1342 | return shaderModule; 1343 | } 1344 | } 1345 | 1346 | void VulkanRenderer::startNextFrame() 1347 | { 1348 | qDebug("startNextFrame"); 1349 | 1350 | submitComputeCommands(); 1351 | 1352 | createRenderPass(); 1353 | 1354 | window->frameReady(); 1355 | } 1356 | 1357 | void VulkanRenderer::translate(float dx, float dy) 1358 | { 1359 | const QSize sz = window->swapChainImageSize(); 1360 | const float speed_x = 4.f * abs(dx) / sz.width(); // normalize according to the window size 1361 | const float speed_y = 4.f * abs(dy) / sz.height(); 1362 | const float sign_dx = dx < 0.0f ? -1.0f : 1.0f; 1363 | const float sign_dy = -dy < 0.0f ? -1.0f : 1.0f; // invert y, it comes in window coordinate system 1364 | 1365 | position_x += sign_dx * speed_x; 1366 | position_y += sign_dy * speed_y; 1367 | 1368 | window->requestUpdate(); 1369 | } 1370 | 1371 | void VulkanRenderer::scale(float s) 1372 | { 1373 | scaleXY = s; 1374 | window->requestUpdate(); 1375 | } 1376 | 1377 | void VulkanRenderer::releaseSwapChainResources() 1378 | { 1379 | qDebug("releaseSwapChainResources"); 1380 | } 1381 | 1382 | void VulkanRenderer::releaseResources() 1383 | { 1384 | qDebug("releaseResources"); 1385 | 1386 | devFuncs->vkQueueWaitIdle(compute.queue); 1387 | 1388 | if (queryPool) { 1389 | devFuncs->vkDestroyQueryPool(device, queryPool, nullptr); 1390 | queryPool = VK_NULL_HANDLE; 1391 | } 1392 | 1393 | if (sampler) { 1394 | devFuncs->vkDestroySampler(device, sampler, nullptr); 1395 | sampler = VK_NULL_HANDLE; 1396 | } 1397 | 1398 | if (texStaging) { 1399 | devFuncs->vkDestroyImage(device, texStaging, nullptr); 1400 | texStaging = VK_NULL_HANDLE; 1401 | } 1402 | 1403 | if (texStagingMem) { 1404 | devFuncs->vkFreeMemory(device, texStagingMem, nullptr); 1405 | texStagingMem = VK_NULL_HANDLE; 1406 | } 1407 | 1408 | if (texView) { 1409 | devFuncs->vkDestroyImageView(device, texView, nullptr); 1410 | texView = VK_NULL_HANDLE; 1411 | } 1412 | 1413 | if (texImage) { 1414 | devFuncs->vkDestroyImage(device, texImage, nullptr); 1415 | texImage = VK_NULL_HANDLE; 1416 | } 1417 | 1418 | if (texMem) { 1419 | devFuncs->vkFreeMemory(device, texMem, nullptr); 1420 | texMem = VK_NULL_HANDLE; 1421 | } 1422 | 1423 | if (pipeline) { 1424 | devFuncs->vkDestroyPipeline(device, pipeline, nullptr); 1425 | pipeline = VK_NULL_HANDLE; 1426 | } 1427 | 1428 | if (pipelineLayout) { 1429 | devFuncs->vkDestroyPipelineLayout(device, pipelineLayout, nullptr); 1430 | pipelineLayout = VK_NULL_HANDLE; 1431 | } 1432 | 1433 | if (pipelineCache) { 1434 | devFuncs->vkDestroyPipelineCache(device, pipelineCache, nullptr); 1435 | pipelineCache = VK_NULL_HANDLE; 1436 | } 1437 | 1438 | if (descSetLayout) { 1439 | devFuncs->vkDestroyDescriptorSetLayout(device, descSetLayout, nullptr); 1440 | descSetLayout = VK_NULL_HANDLE; 1441 | } 1442 | 1443 | if (descPool) { 1444 | devFuncs->vkDestroyDescriptorPool(device, descPool, nullptr); 1445 | descPool = VK_NULL_HANDLE; 1446 | } 1447 | 1448 | if (buf) { 1449 | devFuncs->vkDestroyBuffer(device, buf, nullptr); 1450 | buf = VK_NULL_HANDLE; 1451 | } 1452 | 1453 | if (bufMem) { 1454 | devFuncs->vkFreeMemory(device, bufMem, nullptr); 1455 | bufMem = VK_NULL_HANDLE; 1456 | } 1457 | 1458 | if ( computeRenderTargetView ) { 1459 | devFuncs->vkDestroyImageView(device, computeRenderTargetView, nullptr); 1460 | computeRenderTargetView = VK_NULL_HANDLE; 1461 | } 1462 | 1463 | if ( computeRenderTarget ) { 1464 | devFuncs->vkDestroyImage(device, computeRenderTarget, nullptr); 1465 | computeRenderTarget = VK_NULL_HANDLE; 1466 | } 1467 | 1468 | if ( computeRenderTargetMemory ) { 1469 | devFuncs->vkFreeMemory(device, computeRenderTargetMemory, nullptr); 1470 | computeRenderTarget = VK_NULL_HANDLE; 1471 | } 1472 | 1473 | if ( computeDescriptorSetLayout ) { 1474 | devFuncs->vkDestroyDescriptorSetLayout(device, computeDescriptorSetLayout, nullptr); 1475 | computeDescriptorSetLayout = VK_NULL_HANDLE; 1476 | } 1477 | 1478 | if ( computePipeline ) { 1479 | devFuncs->vkDestroyPipeline(device, computePipeline, nullptr); 1480 | computePipeline = VK_NULL_HANDLE; 1481 | } 1482 | 1483 | if ( computePipelineLayout ) { 1484 | devFuncs->vkDestroyPipelineLayout(device, computePipelineLayout, nullptr); 1485 | computePipelineLayout = VK_NULL_HANDLE; 1486 | } 1487 | 1488 | if ( compute.fence ) { 1489 | devFuncs->vkDestroyFence(device, compute.fence, nullptr); 1490 | compute.fence = VK_NULL_HANDLE; 1491 | } 1492 | 1493 | if ( compute.commandPool ) 1494 | { 1495 | VkCommandBuffer buffers[2]= 1496 | { 1497 | compute.commandBuffer, 1498 | compute.commandBufferInit, 1499 | }; 1500 | 1501 | devFuncs->vkFreeCommandBuffers(device, compute.commandPool, 2, &buffers[0]); 1502 | devFuncs->vkDestroyCommandPool(device, compute.commandPool, nullptr); 1503 | } 1504 | } 1505 | 1506 | VulkanRenderer::~VulkanRenderer() 1507 | { 1508 | qDebug("------------------> Destroying VulkanRenderer"); 1509 | } 1510 | --------------------------------------------------------------------------------