706 words
4 minutes
EchoEngine开发笔记-GraphicWidget

对于渲染引擎来说,UI中的图形显示组件最为重要,因为它是显示渲染结果和与用户进行交互的窗口,所以编写一篇笔记来记录图形显示组件的实现过程是很有必要的。

GraphicWidget#

设计思路#

  • 在EchoEngine中,我设计了一个名为GraphicWidget类,此类是用于在EchoEditor中显示图形渲染结果的组件。在一开始时,我是以QOpenGLWidget为基类来设计的,但考虑到未来该项目将支持多个图形API,如OpenGL、DirectX、Vulkan等,所以最终我决定使用QWidget作为基类。
  • 基于目前开发计划是围绕OpenGL来设计的,因此我打算使用GLFW来进行图形上下文管理。

实现过程#

  • 重写两个重要函数:paintEventresizeEvent,在paintEvent中绘制图形,在resizeEvent中更新窗口大小。
  • 重写paintEngine函数,返回nullptr,表示禁用QPainter绘制组件,完全使用渲染引擎来绘制。
  • 在构造函数中设置setAttribute(Qt::WA_PaintOnScreen);, 用于控制Qt在控件上直接进行绘制操作,而不通过缓冲区(例如双缓冲)进行中间绘制。
  • paintEvent中,获取当前OpenGL上下文,并使用OpenGL来绘制图形,最后交换缓冲区将渲染结果显示到屏幕上。
// GraphicWidget.h
#pragma once

struct GLFWwindow;

namespace EchoEditor {

    /// @brief 图形显示组件
    class GraphicWidget : public QWidget
    {
        Q_OBJECT
    public:
        GraphicWidget(uint32_t nWidth, uint32_t nHeight, QWidget* parent = nullptr);
        virtual ~GraphicWidget();

    public:
        virtual void paintEvent(QPaintEvent* event) override;
        virtual void resizeEvent(QResizeEvent* event) override;

        /// @brief 获取绘制引擎
        /// @return 返回nullptr:表示禁用QPainter绘制组件,完全使用渲染引擎来绘制
        virtual QPaintEngine* paintEngine() const override { return nullptr; }

    private:
        /// @brief 初始化
        void Initialize();

    private:
        GLFWwindow* m_pWindow = nullptr;
    };

}

// GraphicWidget.cpp
#include "pch.h"
#include "GraphicWidget.h"

#include <GLFW/glfw3.h>
#include <glad/glad.h>

namespace EchoEditor {

    static bool s_bGLFWInitialiazed = false;

    GraphicWidget::GraphicWidget(uint32_t nWidth, uint32_t nHeight, QWidget* parent)
        : QWidget(parent)
    {
        // 设置直接绘制在屏幕上
        setAttribute(Qt::WA_PaintOnScreen);
        // 设置大小
        resize((int)nWidth, (int)nHeight);
        // 初始化
        Initialize();
    }

    GraphicWidget::~GraphicWidget()
    {
        glfwDestroyWindow(m_pWindow);
    }

    void GraphicWidget::paintEvent(QPaintEvent* event)
    {
        //设置当前上下文
        glfwMakeContextCurrent(m_pWindow);

        // OpenGL rendering here
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        // 交换缓冲区
        glfwSwapBuffers(m_pWindow);
    }

    void GraphicWidget::resizeEvent(QResizeEvent* event)
    {
    }

    void GraphicWidget::Initialize()
    {
        if (!s_bGLFWInitialiazed)
        {
            // glfwTerminate on system shutdown
            int success = glfwInit();
            ECHO_CORE_ASSERT(success, "Could not intialiaz GLFW!");
            s_bGLFWInitialiazed = true;
        }
        glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
        m_pWindow = glfwCreateWindow(width(), height(), "EchoEngine", nullptr, nullptr);
        glfwMakeContextCurrent(m_pWindow);

        //初始化GLAD
        int iStatus = gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
        ECHO_CORE_ASSERT(iStatus, "Failed to initiazlize Glad!");
        //设置垂直同步
        glfwSwapInterval(1);

        #if defined(_WIN32)// Windows: Use glfwGetWin32Window
            QWindow* pGLFWWindow = QWindow::fromWinId((WId)glfwGetWin32Window(m_pWindow));
        #else
            ECHO_CORE_ASSERT(false, "Unsupported platform for embedding GLFW window.");
        #endif
        //将GLFW嵌入到GraphicWidget中
        QVBoxLayout* layout = new QVBoxLayout();
        QWidget* container = QWidget::createWindowContainer(pGLFWWindow, this);
        layout->addWidget(container);
        setLayout(layout);
    }

}

测试效果#

GraphicWidget

由图中可以看到,图形显示组件已经成功将组件的背景色设置为深灰色!


Reference#

EchoEngine开发笔记-GraphicWidget
https://jerryym.github.io/posts/echoengine/echoengine开发笔记-graphicwidget/
Author
_进击のJerry_
Published at
2025-01-01