TheCherno——Engine(十八)渲染上下文的抽象
除了OpenGL,图形API还有Vulca、DirectX、Metal等。见下表:
图形API不只有OpenGL,但这里考虑方便上手和引擎的轻量级,还是选择OpenGL
渲染上下文(Rendering Context)是图形编程中的一个核心概念,特别是在使用 OpenGL、DirectX、Vulkan 等图形 API 时。它代表了与图形硬件(如 GPU)进行交互的环境或状态集合。以下是对渲染上下文的详细解释:
1.渲染上下文的基本概念
定义:
渲染上下文是一个抽象的概念,表示图形 API 与 GPU 之间的连接和状态管理。
它包含了当前图形渲染所需的所有状态信息,例如着色器程序、缓冲区、纹理、混合模式等。
作用:
渲染上下文为图形 API 提供了一个执行环境,所有的渲染命令都在这个上下文中执行。
它确保渲染命令能够正确地发送到 GPU 并执行。
2.渲染上下文的具体内容
渲染上下文通常包含以下信息:
当前绑定的资源:
例如顶点缓冲区、索引缓冲区、纹理、帧缓冲区等。
着色器程序:
当前使用的顶点着色器、片段着色器等。
状态设置:
例如深度测试、混合模式、裁剪设置等。
视口和裁剪区域:
定义了渲染的目标区域。
矩阵和变换:
例如模型视图矩阵、投影矩阵等(在 OpenGL 中常见)。
3.渲染上下文的创建和管理
创建:
在使用图形 API(如 OpenGL)时,渲染上下文通常与窗口系统(如 GLFW、SDL)一起创建。
例如,在 OpenGL 中,使用
wglCreateContext(Windows)、glXCreateContext(Linux)或CGLCreateContext(macOS)来创建渲染上下文。
绑定:
渲染上下文需要与一个窗口或表面(Surface)绑定,以便将渲染结果输出到屏幕上。
例如,在 OpenGL 中,使用
wglMakeCurrent(Windows)或glXMakeCurrent(Linux)来绑定上下文。
销毁:
当不再需要渲染上下文时,需要显式地销毁它以释放资源。
4.渲染上下文与多线程
单线程:
在单线程环境中,通常只有一个渲染上下文,所有的渲染命令都在这个上下文中执行。
多线程:
在多线程环境中,可以创建多个渲染上下文,并在不同的线程中使用它们。
需要注意的是,多个上下文之间可能需要共享资源(如纹理、缓冲区),这需要通过上下文共享机制来实现。
在WindowsWindow.cpp中,在创建窗口指针m_Window后,需要为窗口设置当前渲染上下文,即指定OpenGL上下文为当前线程的上下文。但Window平台窗口的渲染上下文,比如这里的glfw窗口上下文后面也可能集成Vulcan的。为了使得后面扩展更容易,这里对渲染上下文的相关指令进行“抽象”(这么表述应该是可以的)。
在DTClass项目,src文件夹下的DTClass新建Renderer文件夹,再新建GraphicsContext.h
里面是纯虚函数,是接口
#pragma once namespace DTC { class GraphicsContext { public: virtual void Init() = 0; virtual void SwapBuffers() = 0; }; }在DTClass项目的Platform文件夹下的OpenGL文件下,新建OpenGLContext.h和OpenGLContext.cpp
OpenGLContext.h
#pragma once #include "DTClass/Renderer/GraphicsContext.h" struct GLFWwindow; namespace DTC { class OpenGLContext : public GraphicsContext { public: OpenGLContext(GLFWwindow* windowHandle); virtual void Init() override; virtual void SwapBuffers() override; private: GLFWwindow* m_windowHandle; }; }OpenGLContext.cpp
#include "dtcpch.h" #include "OpenGLContext.h" #include <glad/glad.h> #include <GLFW/glfw3.h> namespace DTC { DTC::OpenGLContext::OpenGLContext(GLFWwindow* windowHandle) :m_windowHandle(windowHandle) { DTC_CORE_ASSERT(windowHandle, "handle为空"); } void OpenGLContext::Init() { glfwMakeContextCurrent(m_windowHandle); int status = gladLoadGLLoader((GLADloadproc)glfwGetProcAddress); DTC_CORE_ASSERT(status, "Failed to initalized Glad"); } void OpenGLContext::SwapBuffers() { glfwSwapBuffers(m_windowHandle); } }替换的是下面的内容:(在WindowsWindow.cpp中)
新的WindowsWindow.h 新增头文件和成员变量
#pragma once #include "DTClass/Window.h" #include "DTClass/Renderer/GraphicsContext.h" #include<GLFW/glfw3.h> namespace DTC { //Windows平台的窗口:Window类的派生类 class WindowsWindow :public Window { public: WindowsWindow(const WindowProps& props); //构造函数(调用了初始化函数) virtual ~WindowsWindow(); //析构函数 void OnUpdate() override; //重写:更新函数 //重写:获得窗口宽度和高度 inline unsigned int GetWidth() const override { return m_Data.Width; }; inline unsigned int GetHeight() const override { return m_Data.Height; }; //重写:设置回调函数 inline void SetEventCallback(const EventCallbackFn& callback)override { m_Data.EventCallback = callback; }; //重写:同步 void SetVSync(bool enable) override; bool IsVSync()const override; inline virtual void* GetNativeWindow() const { return m_Window; } private: virtual void Init(const WindowProps& props); //初始化 virtual void ShutDown(); //关闭 private: GLFWwindow* m_Window; //m_Window 指向 GLFW 库中定义的 GLFWwindow 结构体 GraphicsContext* m_Context; struct WindowData { //窗口信息 std::string Title; //窗口标题 unsigned int Width, Height; //宽度和高度 bool VSync; //同步 EventCallbackFn EventCallback;//事件的回调函数 }; WindowData m_Data; }; }新的WindowsWinow.cpp
#include "dtcpch.h" #include "WindowsWindow.h" #include "DTClass/Events/ApplicationEvent.h" #include "DTClass/Events/MouseEvent.h" #include "DTClass/Events/KeyEvent.h" #include "Platform/OpenGL/OpenGLContext.h" namespace DTC { static bool s_GLFWInitialized = false; //窗口是否已经初始化 Window* Window::Create(const WindowProps& props) { return new WindowsWindow(props); //创建一个Windows平台的窗口 } //WindowsWindow的构造函数 WindowsWindow::WindowsWindow(const WindowProps& props) { Init(props); //完成窗口的初始化 } //WindowsWindow的析构函数 WindowsWindow::~WindowsWindow() { ShutDown(); //关闭窗口 } //WinowsWindow的初始化(构造函数调用) void WindowsWindow::Init(const WindowProps& props) { m_Data.Title = props.Title; //窗口标题 m_Data.Width = props.Width; //窗口宽度 m_Data.Height = props.Height; //窗口高度 //信息输出 DTC_CORE_INFO("创建了一个Windows窗口{0},{1},{2}", props.Title, props.Width, props.Height); if (!s_GLFWInitialized) //如果未初始化,则执行下面的代码块 { // 初始化 GLFW 库的内部状态 // 它会设置 GLFW 所需的平台特定资源(如窗口系统、输入设备等)。 // 如果初始化成功(返回1),GLFW 就可以用于创建窗口、处理输入等操作。 int success = glfwInit(); DTC_CORE_ASSERT("不能创建新的glfw,{0}", success); glfwSetErrorCallback([](int error_code, const char* description) { DTC_CORE_ERROR("GLFW错误:错误码({0}):{1} ", error_code, description); }); //当 GLFW 库发生错误时,会调用这个回调函数 s_GLFWInitialized = true; //表示 GLFW 已经成功初始化 } // 使用GLFW创建一个窗口,并将创建的窗口句柄存储在 m_Window (指向 GLFWwindow 结构体的指针)变量中 m_Window = glfwCreateWindow((int)props.Width, (int)props.Height, m_Data.Title.c_str(), nullptr, nullptr); m_Context = new OpenGLContext(m_Window); m_Context->Init(); // 这行代码将 m_Window 所指向的窗口的 OpenGL 上下文设置为当前线程的上下文 //glfwMakeContextCurrent(m_Window); //int status = gladLoadGLLoader((GLADloadproc)glfwGetProcAddress); //DTC_CORE_ASSERT(status, "Failed to initalized Glad"); // 用于将自定义的指针数据m_Data关联到指定的窗口对象m_Window上 // 方便后续回调函数通过m_Window找到这个m_Data,然后访问EventCallbackFn glfwSetWindowUserPointer(m_Window, &m_Data); // 启用垂直同步 // 确保每一帧都在显示器刷新时显示,从而避免画面撕裂和卡顿现象? SetVSync(true); // 设置GLFW回调函数 // 参数一是用于监听的窗口,参数二是回调函数 // 1 窗口大小改变事件 glfwSetWindowSizeCallback(m_Window, [](GLFWwindow* window, int width, int height) { WindowData& data = *(WindowData*)glfwGetWindowUserPointer(window); data.Width = width; data.Height = height; WindowResizeEvent event(width, height); data.EventCallback(event); //传回Application中的OnEvent函数 }); // 2 窗口关闭事件 glfwSetWindowCloseCallback(m_Window, [](GLFWwindow* window) { WindowData& data = *(WindowData*)glfwGetWindowUserPointer(window); WindowCloseEvent event; data.EventCallback(event); }); // 3 键盘按键事件 glfwSetKeyCallback(m_Window, [](GLFWwindow* window, int key, int scancode, int action, int mods) { WindowData& data = *(WindowData*)glfwGetWindowUserPointer(window); switch (action) { case GLFW_PRESS: //按下 { KeyPressedEvent event(key, 0); data.EventCallback(event); break; } case GLFW_RELEASE: //释放 { KeyReleasedEvent event(key); data.EventCallback(event); break; } case GLFW_REPEAT: //重复 { KeyPressedEvent event(key, 1); data.EventCallback(event); break; } } }); // 4 鼠标键事件 glfwSetMouseButtonCallback(m_Window, [](GLFWwindow* window, int button, int action, int mods) { WindowData& data = *(WindowData*)glfwGetWindowUserPointer(window); switch (action) { case GLFW_PRESS: //按下 { MouseButtonPressedEvent event(button); data.EventCallback(event); break; } case GLFW_RELEASE: //释放 { MouseButtonReleasedEvent event(button); data.EventCallback(event); break; } } }); // 5 鼠标滚动事件 glfwSetScrollCallback(m_Window, [](GLFWwindow* window, double xoffset, double yoffset) { WindowData& data = *(WindowData*)glfwGetWindowUserPointer(window); MouseScrolledEvent event((float)xoffset, (float)yoffset); data.EventCallback(event); }); // 6 鼠标移动事件 glfwSetCursorPosCallback(m_Window, [](GLFWwindow* window, double xpos, double ypos) { WindowData& data = *(WindowData*)glfwGetWindowUserPointer(window); MouseMovedEvent event((float)xpos, (float)ypos); data.EventCallback(event); }); // 7 输入字符事件 glfwSetCharCallback(m_Window, [](GLFWwindow* window, unsigned int codepoint) { WindowData& data = *(WindowData*)glfwGetWindowUserPointer(window); KeyTypedEvent event(codepoint); data.EventCallback(event); }); } //关闭窗口 void WindowsWindow::ShutDown() { glfwDestroyWindow(m_Window); } // 更新窗口 void WindowsWindow::OnUpdate() { // 轮询事件 // 用于处理所有待处理的事件,如键盘输入、鼠标移动、窗口调整等。 // 它会轮询事件队列并调用相应的回调函数,以确保应用程序可以响应用户的输入和系统事件 glfwPollEvents(); //交换缓冲区 m_Context->SwapBuffers(); //glfwSwapBuffers(m_Window); } void WindowsWindow::SetVSync(bool enable) { if (enable) glfwSwapInterval(1); //值为1时,表示启用垂直同步,图像渲染将与显示器的垂直刷新频率同步 else glfwSwapInterval(0); //当值为0时,表示禁用垂直同步,图像渲染将不受显示器刷新频率的限制,渲染完成后立即交换缓冲区,可能会导致画面撕裂,但可以减少延迟? m_Data.VSync = enable; } bool WindowsWindow::IsVSync() const { return m_Data.VSync; } }