├── LICENSE ├── debug.h ├── fpscounter.cpp ├── fpscounter.h ├── glwidget.cpp ├── glwidget.h ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── mainwindow.ui ├── offscreen_render.pro ├── renderer.cpp ├── renderer.h ├── renderthread.cpp ├── renderthread.h ├── sierpinski.cpp ├── sierpinski.h ├── texturebuffer.cpp └── texturebuffer.h /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jia Lihong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /debug.h: -------------------------------------------------------------------------------- 1 | #ifndef DEBUG_H 2 | #define DEBUG_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class RAIITimer 10 | { 11 | public: 12 | explicit RAIITimer(const std::string &tag) 13 | : m_tag(tag) 14 | , m_printed(false) 15 | { 16 | m_timer.start(); 17 | qDebug() << m_tag.c_str() << "begin"; 18 | } 19 | ~RAIITimer() 20 | { 21 | stop(); 22 | } 23 | void stop() 24 | { 25 | if (m_printed) 26 | { 27 | return; 28 | } 29 | m_printed = true; 30 | auto ns = m_timer.nsecsElapsed(); 31 | auto ms = ns / 1000000.0; 32 | qDebug() << m_tag.c_str() << "cost" << ms << "ms"; 33 | } 34 | 35 | private: 36 | std::string m_tag; 37 | bool m_printed; 38 | QElapsedTimer m_timer; 39 | }; 40 | 41 | // glCheckError_ && glDebugOutput is from https://learnopengl.com/In-Practice/Debugging 42 | 43 | inline GLenum glCheckError_(const char *file, int line) 44 | { 45 | GLenum errorCode; 46 | while ((errorCode = QOpenGLContext::currentContext()->functions()->glGetError()) != GL_NO_ERROR) 47 | { 48 | const char *error; 49 | switch (errorCode) 50 | { 51 | case GL_INVALID_ENUM: error = "INVALID_ENUM"; break; 52 | case GL_INVALID_VALUE: error = "INVALID_VALUE"; break; 53 | case GL_INVALID_OPERATION: error = "INVALID_OPERATION"; break; 54 | case GL_STACK_OVERFLOW: error = "STACK_OVERFLOW"; break; 55 | case GL_STACK_UNDERFLOW: error = "STACK_UNDERFLOW"; break; 56 | case GL_OUT_OF_MEMORY: error = "OUT_OF_MEMORY"; break; 57 | case GL_INVALID_FRAMEBUFFER_OPERATION: error = "INVALID_FRAMEBUFFER_OPERATION"; break; 58 | } 59 | qDebug() << error << " | " << file << " (" << line << ")"; 60 | } 61 | return errorCode; 62 | } 63 | #define glCheckError() glCheckError_(__FILE__, __LINE__) 64 | 65 | inline void APIENTRY glDebugOutput(GLenum source, 66 | GLenum type, 67 | GLuint id, 68 | GLenum severity, 69 | GLsizei, 70 | const GLchar *message, 71 | const void *) 72 | { 73 | // ignore non-significant error/warning codes 74 | // if(id == 131169 || id == 131185 || id == 131218 || id == 131204) return; 75 | 76 | qDebug() << "---------------"; 77 | qDebug() << "Debug message (" << id << "):" << message; 78 | 79 | switch (source) 80 | { 81 | case GL_DEBUG_SOURCE_API: qDebug() << "Source: API"; break; 82 | case GL_DEBUG_SOURCE_WINDOW_SYSTEM: qDebug() << "Source: Window System"; break; 83 | case GL_DEBUG_SOURCE_SHADER_COMPILER: qDebug() << "Source: Shader Compiler"; break; 84 | case GL_DEBUG_SOURCE_THIRD_PARTY: qDebug() << "Source: Third Party"; break; 85 | case GL_DEBUG_SOURCE_APPLICATION: qDebug() << "Source: Application"; break; 86 | case GL_DEBUG_SOURCE_OTHER: qDebug() << "Source: Other"; break; 87 | } 88 | 89 | switch (type) 90 | { 91 | case GL_DEBUG_TYPE_ERROR: qDebug() << "Type: Error"; break; 92 | case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: qDebug() << "Type: Deprecated Behaviour"; break; 93 | case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: qDebug() << "Type: Undefined Behaviour"; break; 94 | case GL_DEBUG_TYPE_PORTABILITY: qDebug() << "Type: Portability"; break; 95 | case GL_DEBUG_TYPE_PERFORMANCE: qDebug() << "Type: Performance"; break; 96 | case GL_DEBUG_TYPE_MARKER: qDebug() << "Type: Marker"; break; 97 | case GL_DEBUG_TYPE_PUSH_GROUP: qDebug() << "Type: Push Group"; break; 98 | case GL_DEBUG_TYPE_POP_GROUP: qDebug() << "Type: Pop Group"; break; 99 | case GL_DEBUG_TYPE_OTHER: qDebug() << "Type: Other"; break; 100 | } 101 | 102 | switch (severity) 103 | { 104 | case GL_DEBUG_SEVERITY_HIGH: qDebug() << "Severity: high"; break; 105 | case GL_DEBUG_SEVERITY_MEDIUM: qDebug() << "Severity: medium"; break; 106 | case GL_DEBUG_SEVERITY_LOW: qDebug() << "Severity: low"; break; 107 | case GL_DEBUG_SEVERITY_NOTIFICATION: qDebug() << "Severity: notification"; break; 108 | } 109 | } 110 | 111 | #endif // DEBUG_H 112 | -------------------------------------------------------------------------------- /fpscounter.cpp: -------------------------------------------------------------------------------- 1 | #include "fpscounter.h" 2 | 3 | #include 4 | 5 | FpsCounter *FpsCounter::instance() 6 | { 7 | static FpsCounter s_instance; 8 | return &s_instance; 9 | } 10 | 11 | // one type is only used in one thread 12 | void FpsCounter::frame(FpsCounter::Type t) 13 | { 14 | m_frameCount[t]++; 15 | } 16 | 17 | std::string FpsCounter::toString() 18 | { 19 | int64_t ms = microSecondsFromEpoch(); 20 | 21 | std::string result; 22 | for (int i = 0; i < TypeCount; i++) 23 | { 24 | result += m_title[i] + ": "; 25 | if (m_frameCount[i] == 0) 26 | { 27 | result += "0"; 28 | } 29 | else 30 | { 31 | float fps = m_frameCount[i] * 1.0f / ((ms - m_lastTick[i]) / 1000000.0f); 32 | result += std::to_string(fps); 33 | } 34 | result += " "; 35 | 36 | m_frameCount[i] = 0; 37 | m_lastTick[i] = ms; 38 | } 39 | return result; 40 | } 41 | 42 | FpsCounter::FpsCounter() 43 | { 44 | m_title[Render] = "Render"; 45 | m_title[Display] = "Display"; 46 | 47 | for (int i = 0; i < TypeCount; i++) 48 | { 49 | m_frameCount[i] = 0; 50 | m_lastTick[i] = 0; 51 | } 52 | } 53 | 54 | int64_t FpsCounter::microSecondsFromEpoch() 55 | { 56 | auto t = std::chrono::system_clock::now(); 57 | return std::chrono::duration_cast(t.time_since_epoch()).count(); 58 | } 59 | -------------------------------------------------------------------------------- /fpscounter.h: -------------------------------------------------------------------------------- 1 | #ifndef FPSCOUNTER_H 2 | #define FPSCOUNTER_H 3 | 4 | #include 5 | #include 6 | 7 | class FpsCounter 8 | { 9 | public: 10 | enum Type 11 | { 12 | Render = 0, 13 | Display, 14 | TypeCount 15 | }; 16 | 17 | public: 18 | static FpsCounter *instance(); 19 | void frame(Type t); 20 | std::string toString(); 21 | static int64_t microSecondsFromEpoch(); 22 | 23 | private: 24 | FpsCounter(); 25 | 26 | private: 27 | FpsCounter(const FpsCounter &) = delete; 28 | FpsCounter &operator =(const FpsCounter &) = delete; 29 | FpsCounter(const FpsCounter &&) = delete; 30 | FpsCounter &operator =(const FpsCounter &&) = delete; 31 | 32 | private: 33 | std::string m_title[TypeCount]; 34 | std::atomic m_frameCount[TypeCount]; 35 | int64_t m_lastTick[TypeCount]; // in microsecond 36 | }; 37 | 38 | #endif // FPSCOUNTER_H 39 | -------------------------------------------------------------------------------- /glwidget.cpp: -------------------------------------------------------------------------------- 1 | #include "glwidget.h" 2 | #include "texturebuffer.h" 3 | #include "renderthread.h" 4 | #include "debug.h" 5 | #include "fpscounter.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | namespace 15 | { 16 | float vertices[] = 17 | { 18 | -1.0f, -1.0f, 0.0f, 0.0f, 19 | -1.0f, 1.0f, 0.0f, 1.0f, 20 | 1.0f, -1.0f, 1.0f, 0.0f, 21 | -1.0f, 1.0f, 0.0f, 1.0f, 22 | 1.0f, 1.0f, 1.0f, 1.0f, 23 | 1.0f, -1.0f, 1.0f, 0.0f, 24 | }; 25 | } 26 | 27 | GLWidget::GLWidget(QWidget *parent) 28 | : QOpenGLWidget(parent) 29 | { 30 | } 31 | 32 | GLWidget::~GLWidget() 33 | { 34 | } 35 | 36 | void GLWidget::initializeGL() 37 | { 38 | initRenderThread(); 39 | 40 | qDebug() << initializeOpenGLFunctions(); 41 | 42 | // glEnable(GL_DEBUG_OUTPUT); 43 | // glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); 44 | // glDebugMessageCallback(glDebugOutput, nullptr); 45 | 46 | char vertexShaderSource[] = 47 | "#version 450 core\n" 48 | "layout (location = 0) in vec2 vPos;\n" 49 | "layout (location = 1) in vec2 texCoord;\n" 50 | "out vec2 TexCoord;\n" 51 | "void main()\n" 52 | "{\n" 53 | " gl_Position = vec4(vPos, 0.0, 1.0);\n" 54 | " TexCoord = texCoord;\n" 55 | "}\n"; 56 | char fragmentShaderSource[] = 57 | "#version 450 core\n" 58 | "out vec4 FragColor;\n" 59 | "in vec2 TexCoord;\n" 60 | "uniform sampler2D ourTexture;\n" 61 | "void main()\n" 62 | "{\n" 63 | " FragColor = texture(ourTexture, TexCoord);\n" 64 | "}\n"; 65 | 66 | m_program.reset(new QOpenGLShaderProgram); 67 | m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource); 68 | m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource); 69 | m_program->link(); 70 | 71 | glGenVertexArrays(1, &m_vao); 72 | glBindVertexArray(m_vao); 73 | 74 | glGenBuffers(1, &m_vbo); 75 | glBindBuffer(GL_ARRAY_BUFFER, m_vbo); 76 | glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); 77 | 78 | glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), reinterpret_cast(0)); 79 | glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), reinterpret_cast(2 * sizeof(float))); 80 | glEnableVertexAttribArray(0); 81 | glEnableVertexAttribArray(1); 82 | 83 | glBindVertexArray(0); 84 | 85 | glCheckError(); 86 | } 87 | 88 | void GLWidget::paintGL() 89 | { 90 | RAIITimer t("GLWidget::paintGL"); 91 | 92 | glViewport(0, 0, width(), height()); 93 | 94 | m_program->bind(); 95 | 96 | glBindVertexArray(m_vao); 97 | if (TextureBuffer::instance()->ready()) 98 | { 99 | TextureBuffer::instance()->drawTexture(QOpenGLContext::currentContext(), sizeof(vertices) / sizeof(float) / 4); 100 | } 101 | glBindVertexArray(0); 102 | 103 | m_program->release(); 104 | 105 | FpsCounter::instance()->frame(FpsCounter::Display); 106 | } 107 | 108 | void GLWidget::resizeGL(int w, int h) 109 | { 110 | m_thread->setNewSize(w, h); 111 | } 112 | 113 | void GLWidget::initRenderThread() 114 | { 115 | auto context = QOpenGLContext::currentContext(); 116 | auto mainSurface = context->surface(); 117 | 118 | auto renderSurface = new QOffscreenSurface(nullptr, this); 119 | renderSurface->setFormat(context->format()); 120 | renderSurface->create(); 121 | 122 | context->doneCurrent(); 123 | m_thread = new RenderThread(renderSurface, context, this); 124 | context->makeCurrent(mainSurface); 125 | 126 | connect(m_thread, &RenderThread::imageReady, this, [this](){ 127 | update(); 128 | }, Qt::QueuedConnection); 129 | m_thread->start(); 130 | } 131 | -------------------------------------------------------------------------------- /glwidget.h: -------------------------------------------------------------------------------- 1 | #ifndef GLWIDGET_H 2 | #define GLWIDGET_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | class RenderThread; 11 | 12 | class GLWidget : public QOpenGLWidget, public QOpenGLFunctions_4_5_Core 13 | { 14 | public: 15 | GLWidget(QWidget *parent = nullptr); 16 | ~GLWidget() override; 17 | 18 | protected: 19 | void initializeGL() override; 20 | void paintGL() override; 21 | void resizeGL(int w, int h) override; 22 | 23 | private: 24 | void initRenderThread(); 25 | 26 | private: 27 | unsigned m_vao = 0; 28 | unsigned m_vbo = 0; 29 | std::unique_ptr m_program; 30 | RenderThread *m_thread = nullptr; 31 | }; 32 | 33 | #endif // GLWIDGET_H 34 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | QApplication a(argc, argv); 7 | MainWindow w; 8 | w.show(); 9 | 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "ui_mainwindow.h" 3 | #include "renderthread.h" 4 | #include "texturebuffer.h" 5 | #include "glwidget.h" 6 | #include "fpscounter.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | MainWindow::MainWindow(QWidget *parent) : 13 | QMainWindow(parent), 14 | ui(new Ui::MainWindow) 15 | { 16 | ui->setupUi(this); 17 | 18 | startTimer(0); 19 | } 20 | 21 | MainWindow::~MainWindow() 22 | { 23 | delete ui; 24 | } 25 | 26 | void MainWindow::timerEvent(QTimerEvent *) 27 | { 28 | ui->openGLWidget->update(); 29 | 30 | static int i = 0; 31 | i++; 32 | 33 | const int INTERVAL = 30; 34 | if (i % INTERVAL == 0) // update fps message every INTERVAL frame 35 | { 36 | QString fps = QString::fromStdString(FpsCounter::instance()->toString()); 37 | qDebug() << fps; 38 | statusBar()->showMessage(fps); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | 6 | namespace Ui { 7 | class MainWindow; 8 | } 9 | 10 | class RenderThread; 11 | class GLWidget; 12 | 13 | class QTimerEvent; 14 | class QResizeEvent; 15 | 16 | class MainWindow : public QMainWindow 17 | { 18 | Q_OBJECT 19 | 20 | public: 21 | explicit MainWindow(QWidget *parent = nullptr); 22 | ~MainWindow() override; 23 | 24 | protected: 25 | void timerEvent(QTimerEvent *) override; 26 | 27 | private: 28 | Ui::MainWindow *ui; 29 | RenderThread *m_thread; 30 | }; 31 | 32 | #endif // MAINWINDOW_H 33 | -------------------------------------------------------------------------------- /mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Offscreen Render 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 0 23 | 0 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | Qt::Horizontal 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 0 47 | 0 48 | 400 49 | 26 50 | 51 | 52 | 53 | 54 | 55 | TopToolBarArea 56 | 57 | 58 | false 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | GLWidget 67 | QOpenGLWidget 68 |
glwidget.h
69 |
70 |
71 | 72 | 73 |
74 | -------------------------------------------------------------------------------- /offscreen_render.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2019-07-14T14:05:46 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui 8 | 9 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 10 | 11 | TARGET = offscreen_render 12 | TEMPLATE = app 13 | 14 | # The following define makes your compiler emit warnings if you use 15 | # any feature of Qt which has been marked as deprecated (the exact warnings 16 | # depend on your compiler). Please consult the documentation of the 17 | # deprecated API in order to know how to port your code away from it. 18 | DEFINES += QT_DEPRECATED_WARNINGS 19 | 20 | # You can also make your code fail to compile if you use deprecated APIs. 21 | # In order to do so, uncomment the following line. 22 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 23 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 24 | 25 | CONFIG += c++11 26 | 27 | SOURCES += \ 28 | fpscounter.cpp \ 29 | glwidget.cpp \ 30 | main.cpp \ 31 | mainwindow.cpp \ 32 | renderer.cpp \ 33 | renderthread.cpp \ 34 | sierpinski.cpp \ 35 | texturebuffer.cpp 36 | 37 | HEADERS += \ 38 | debug.h \ 39 | fpscounter.h \ 40 | glwidget.h \ 41 | mainwindow.h \ 42 | renderer.h \ 43 | renderthread.h \ 44 | sierpinski.h \ 45 | texturebuffer.h 46 | 47 | FORMS += \ 48 | mainwindow.ui 49 | 50 | # Default rules for deployment. 51 | qnx: target.path = /tmp/$${TARGET}/bin 52 | else: unix:!android: target.path = /opt/$${TARGET}/bin 53 | !isEmpty(target.path): INSTALLS += target 54 | -------------------------------------------------------------------------------- /renderer.cpp: -------------------------------------------------------------------------------- 1 | #include "renderer.h" 2 | #include "debug.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | Renderer::Renderer(QObject *parent) 12 | : QObject(parent) 13 | { 14 | init(); 15 | } 16 | 17 | Renderer::~Renderer() 18 | { 19 | uninit(); 20 | } 21 | 22 | void Renderer::render(int width, int height) 23 | { 24 | RAIITimer t("Renderer::render"); 25 | if (m_width != width || m_height != height) 26 | { 27 | m_width = width; 28 | m_height = height; 29 | adjustSize(); 30 | } 31 | 32 | static float degree = 0.0f; 33 | degree += 1.0f; 34 | 35 | QMatrix4x4 rotate; 36 | rotate.setToIdentity(); 37 | rotate.rotate(degree, 0, 0, 1); 38 | 39 | glViewport(m_viewportX, m_viewportY, m_viewportWidth, m_viewportHeight); 40 | glClear(GL_COLOR_BUFFER_BIT); 41 | 42 | m_program->bind(); 43 | m_program->setUniformValue("rotate", rotate); 44 | 45 | glBindBuffer(GL_ARRAY_BUFFER, m_vbo); 46 | glDrawArrays(GL_TRIANGLES, 0, static_cast(m_sierpinski->vertexCount())); 47 | glBindBuffer(GL_ARRAY_BUFFER, 0); 48 | 49 | m_program->release(); 50 | glFinish(); 51 | } 52 | 53 | void Renderer::init() 54 | { 55 | initializeOpenGLFunctions(); 56 | 57 | // glEnable(GL_DEBUG_OUTPUT); 58 | // glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); 59 | // glDebugMessageCallback(glDebugOutput, nullptr); 60 | 61 | qDebug() << reinterpret_cast(glGetString(GL_VERSION)); 62 | 63 | glClearColor(0.0f, 0.0f, 0.0f, 1.0f); 64 | 65 | glGenFramebuffers(1, &m_fbo); 66 | glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); 67 | 68 | glGenTextures(1, &m_texture); 69 | glBindTexture(GL_TEXTURE_2D, m_texture); 70 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 71 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 72 | glBindTexture(GL_TEXTURE_2D, 0); 73 | 74 | glGenRenderbuffers(1, &m_rbo); 75 | 76 | adjustSize(); 77 | 78 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texture, 0); 79 | glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_rbo); 80 | 81 | char vertexShaderSource[] = 82 | "#version 330 core\n" 83 | "layout (location = 0) in vec2 vPos;\n" 84 | "uniform mat4 rotate;\n" 85 | "void main()\n" 86 | "{\n" 87 | " gl_Position = rotate * vec4(vPos.x, vPos.y, 0.0, 1.0);\n" 88 | "}\n"; 89 | char fragmentShaderSource[] = 90 | "#version 330 core\n" 91 | "out vec4 FragColor;\n" 92 | "void main()\n" 93 | "{\n" 94 | " FragColor = vec4(1.0, 0.5, 0.2, 1.0);\n" 95 | "}\n"; 96 | 97 | m_program.reset(new QOpenGLShaderProgram); 98 | m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource); 99 | m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource); 100 | m_program->link(); 101 | 102 | m_sierpinski.reset(new Sierpinski(15)); 103 | 104 | glGenBuffers(1, &m_vbo); 105 | glBindBuffer(GL_ARRAY_BUFFER, m_vbo); 106 | glBufferData(GL_ARRAY_BUFFER, m_sierpinski->size() * sizeof(float), m_sierpinski->data(), GL_STATIC_DRAW); 107 | glEnableVertexAttribArray(0); 108 | glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), reinterpret_cast(0)); 109 | glBindBuffer(GL_ARRAY_BUFFER, 0); 110 | } 111 | 112 | void Renderer::uninit() 113 | { 114 | glDeleteRenderbuffers(1, &m_rbo); 115 | glDeleteTextures(1, &m_texture); 116 | glDeleteFramebuffers(1, &m_fbo); 117 | } 118 | 119 | void Renderer::adjustSize() 120 | { 121 | glBindTexture(GL_TEXTURE_2D, m_texture); 122 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_width, m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); 123 | glBindTexture(GL_TEXTURE_2D, 0); 124 | 125 | glBindRenderbuffer(GL_RENDERBUFFER, m_rbo); 126 | glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, m_width, m_height); 127 | glBindRenderbuffer(GL_RENDERBUFFER, 0); 128 | 129 | if (m_width > m_height) 130 | { 131 | m_viewportX = (m_width - m_height) / 2; 132 | m_viewportY = 0; 133 | m_viewportWidth = m_height; 134 | m_viewportHeight = m_height; 135 | } 136 | else 137 | { 138 | m_viewportX = 0; 139 | m_viewportY = (m_height - m_width) / 2; 140 | m_viewportWidth = m_width; 141 | m_viewportHeight = m_width; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /renderer.h: -------------------------------------------------------------------------------- 1 | #ifndef RENDERER_H 2 | #define RENDERER_H 3 | 4 | #include "sierpinski.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | class Renderer : public QObject, public QOpenGLFunctions_4_5_Core 13 | { 14 | Q_OBJECT 15 | public: 16 | explicit Renderer(QObject *parent = nullptr); 17 | ~Renderer(); 18 | 19 | void render(int width, int height); 20 | 21 | private: 22 | void init(); 23 | void uninit(); 24 | void adjustSize(); 25 | 26 | private: 27 | Renderer(const Renderer &) = delete; 28 | Renderer &operator =(const Renderer &) = delete; 29 | Renderer(const Renderer &&) = delete; 30 | Renderer &operator =(const Renderer &&) = delete; 31 | 32 | private: 33 | int m_width = 0; 34 | int m_height = 0; 35 | int m_viewportX = 0; 36 | int m_viewportY = 0; 37 | int m_viewportWidth = 0; 38 | int m_viewportHeight = 0; 39 | bool m_sizeChanged = true; 40 | 41 | unsigned m_vao = 0; 42 | unsigned m_vbo = 0; 43 | unsigned m_fbo = 0; 44 | unsigned m_rbo = 0; 45 | unsigned m_texture = 0; 46 | 47 | std::unique_ptr m_program; 48 | std::unique_ptr m_sierpinski; 49 | }; 50 | 51 | #endif // RENDERER_H 52 | -------------------------------------------------------------------------------- /renderthread.cpp: -------------------------------------------------------------------------------- 1 | #include "renderthread.h" 2 | #include "renderer.h" 3 | #include "texturebuffer.h" 4 | #include "debug.h" 5 | #include "fpscounter.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | RenderThread::RenderThread(QSurface *surface, QOpenGLContext *mainContext, QObject *parent) 12 | : QThread(parent) 13 | , m_mainContext(mainContext) 14 | , m_surface(surface) 15 | { 16 | m_renderContext = new QOpenGLContext; 17 | m_renderContext->setFormat(m_mainContext->format()); 18 | m_renderContext->setShareContext(m_mainContext); 19 | m_renderContext->create(); 20 | m_renderContext->moveToThread(this); 21 | } 22 | 23 | RenderThread::~RenderThread() 24 | { 25 | m_running = false; 26 | wait(); 27 | } 28 | 29 | // called in UI thread 30 | void RenderThread::setNewSize(int width, int height) 31 | { 32 | QMutexLocker lock(&m_mutex); 33 | m_width = width; 34 | m_height = height; 35 | } 36 | 37 | // called in render thread 38 | void RenderThread::run() 39 | { 40 | m_renderContext->makeCurrent(m_surface); 41 | 42 | TextureBuffer::instance()->createTexture(m_renderContext); 43 | 44 | Renderer renderer; 45 | 46 | while (m_running) 47 | { 48 | int width = 0; 49 | int height = 0; 50 | { 51 | QMutexLocker lock(&m_mutex); 52 | width = m_width; 53 | height = m_height; 54 | } 55 | renderer.render(width, height); 56 | TextureBuffer::instance()->updateTexture(m_renderContext, width, height); 57 | emit imageReady(); 58 | FpsCounter::instance()->frame(FpsCounter::Render); 59 | } 60 | 61 | TextureBuffer::instance()->deleteTexture(m_renderContext); 62 | } 63 | -------------------------------------------------------------------------------- /renderthread.h: -------------------------------------------------------------------------------- 1 | #ifndef RENDERTHREAD_H 2 | #define RENDERTHREAD_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class RenderThread : public QThread, public QOpenGLFunctions 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | RenderThread(QSurface *surface, QOpenGLContext *mainContext, QObject *parent = nullptr); 17 | ~RenderThread(); 18 | 19 | void setNewSize(int width, int height); 20 | 21 | signals: 22 | void imageReady(); 23 | 24 | protected: 25 | void run() override; 26 | 27 | private: 28 | RenderThread(const RenderThread &) = delete; 29 | RenderThread &operator =(const RenderThread &) = delete; 30 | RenderThread(const RenderThread &&) = delete; 31 | RenderThread &operator =(const RenderThread &&) = delete; 32 | 33 | private: 34 | bool m_running = true; 35 | 36 | int m_width = 100; 37 | int m_height = 100; 38 | QMutex m_mutex; 39 | 40 | QOpenGLContext *m_mainContext; 41 | QOpenGLContext *m_renderContext = nullptr; 42 | QSurface *m_surface; 43 | }; 44 | 45 | #endif // RENDERTHREAD_H 46 | -------------------------------------------------------------------------------- /sierpinski.cpp: -------------------------------------------------------------------------------- 1 | #include "sierpinski.h" 2 | 3 | Sierpinski::Sierpinski(int iterationCount) 4 | { 5 | Vertex v1{0.0f, 1.0f}; 6 | Vertex v2{0.866f, -0.5f}; 7 | Vertex v3{-0.866f, -0.5f}; 8 | 9 | add(v1, v2, v3); 10 | 11 | for (int i = 0; i < iterationCount; i++) 12 | { 13 | iterate(); 14 | } 15 | } 16 | 17 | Sierpinski::Vertex Sierpinski::mid(const Sierpinski::Vertex &v1, const Sierpinski::Vertex &v2) 18 | { 19 | return Vertex{(v1.x + v2.x) / 2, (v1.y + v2.y) / 2}; 20 | } 21 | 22 | void Sierpinski::add(const Sierpinski::Vertex &v) 23 | { 24 | m_vertices.push_back(v.x); 25 | m_vertices.push_back(v.y); 26 | } 27 | 28 | void Sierpinski::add(const Sierpinski::Vertex &v1, const Sierpinski::Vertex &v2, const Sierpinski::Vertex &v3) 29 | { 30 | add(v1); 31 | add(v2); 32 | add(v3); 33 | } 34 | 35 | void Sierpinski::iterate() 36 | { 37 | std::vector cur; 38 | cur.swap(m_vertices); 39 | m_vertices.reserve(cur.size() * 3); 40 | 41 | for (int i = 0; i < cur.size(); i += 6) 42 | { 43 | Vertex v1{cur[i], cur[i + 1]}; 44 | Vertex v2{cur[i + 2], cur[i + 3]}; 45 | Vertex v3{cur[i + 4], cur[i + 5]}; 46 | 47 | Vertex v12 = mid(v1, v2); 48 | Vertex v23 = mid(v2, v3); 49 | Vertex v31 = mid(v3, v1); 50 | 51 | add(v1, v12, v31); 52 | add(v12, v2, v23); 53 | add(v31, v23, v3); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /sierpinski.h: -------------------------------------------------------------------------------- 1 | #ifndef SIERPINSKI_H 2 | #define SIERPINSKI_H 3 | 4 | #include 5 | 6 | class Sierpinski 7 | { 8 | public: 9 | explicit Sierpinski(int iterationCount); 10 | 11 | float *data() { return m_vertices.data(); } 12 | size_t size() const { return m_vertices.size(); } 13 | size_t vertexCount() const { return m_vertices.size() / 2; } 14 | 15 | private: 16 | struct Vertex 17 | { 18 | float x; 19 | float y; 20 | }; 21 | 22 | private: 23 | Vertex mid(const Vertex &v1, const Vertex &v2); 24 | void add(const Vertex &v); 25 | void add(const Vertex &v1, const Vertex &v2, const Vertex &v3); 26 | 27 | private: 28 | void iterate(); 29 | 30 | private: 31 | std::vector m_vertices; 32 | }; 33 | 34 | #endif // SIERPINSKI_H 35 | -------------------------------------------------------------------------------- /texturebuffer.cpp: -------------------------------------------------------------------------------- 1 | #include "texturebuffer.h" 2 | #include "debug.h" 3 | 4 | #include 5 | #include 6 | 7 | TextureBuffer *TextureBuffer::instance() 8 | { 9 | static TextureBuffer s_instance; 10 | return &s_instance; 11 | } 12 | 13 | TextureBuffer::TextureBuffer() 14 | : m_ready(false) 15 | , m_texture(0) 16 | { 17 | } 18 | 19 | TextureBuffer::~TextureBuffer() 20 | { 21 | } 22 | 23 | void TextureBuffer::createTexture(QOpenGLContext *context) 24 | { 25 | context->functions()->glGenTextures(1, &m_texture); 26 | m_ready = true; 27 | } 28 | 29 | void TextureBuffer::deleteTexture(QOpenGLContext *context) 30 | { 31 | context->functions()->glDeleteTextures(1, &m_texture); 32 | } 33 | 34 | // called in render thread 35 | void TextureBuffer::updateTexture(QOpenGLContext *context, int width, int height) 36 | { 37 | RAIITimer t("ImageBuffer::updateTexture"); 38 | QMutexLocker lock(&m_mutex); 39 | 40 | auto f = context->functions(); 41 | f->glActiveTexture(GL_TEXTURE0); 42 | f->glBindTexture(GL_TEXTURE_2D, m_texture); 43 | f->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); 44 | f->glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, width, height, 0); 45 | f->glBindTexture(GL_TEXTURE_2D, 0); 46 | f->glFinish(); 47 | } 48 | 49 | // called in main thread 50 | void TextureBuffer::drawTexture(QOpenGLContext *context, int vertextCount) 51 | { 52 | RAIITimer t("ImageBuffer::drawTexture"); 53 | QMutexLocker lock(&m_mutex); 54 | 55 | auto f = context->functions(); 56 | 57 | f->glBindTexture(GL_TEXTURE_2D, m_texture); 58 | 59 | f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 60 | f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 61 | 62 | f->glActiveTexture(GL_TEXTURE0); 63 | 64 | f->glDrawArrays(GL_TRIANGLES, 0, vertextCount); 65 | f->glBindTexture(GL_TEXTURE_2D, 0); 66 | 67 | //f->glFinish(); 68 | } 69 | -------------------------------------------------------------------------------- /texturebuffer.h: -------------------------------------------------------------------------------- 1 | #ifndef TEXTUREBUFFER_H 2 | #define TEXTUREBUFFER_H 3 | 4 | #include 5 | #include 6 | 7 | class TextureBuffer 8 | { 9 | public: 10 | static TextureBuffer *instance(); 11 | 12 | bool ready() const { return m_ready; } 13 | 14 | void createTexture(QOpenGLContext *context); 15 | void deleteTexture(QOpenGLContext *context); 16 | 17 | void updateTexture(QOpenGLContext *context, int width, int height); 18 | void drawTexture(QOpenGLContext *context, int vertextCount); 19 | 20 | private: 21 | TextureBuffer(); 22 | ~TextureBuffer(); 23 | 24 | private: 25 | TextureBuffer(const TextureBuffer &) = delete; 26 | TextureBuffer &operator =(const TextureBuffer &) = delete; 27 | TextureBuffer(const TextureBuffer &&) = delete; 28 | TextureBuffer &operator =(const TextureBuffer &&) = delete; 29 | 30 | private: 31 | mutable QMutex m_mutex; 32 | 33 | bool m_ready; 34 | 35 | unsigned m_texture; 36 | }; 37 | 38 | #endif // TEXTUREBUFFER_H 39 | --------------------------------------------------------------------------------